Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.kernel.org/pub/scm/git/git.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes2
-rw-r--r--.gitignore11
-rw-r--r--.mailmap5
-rw-r--r--Documentation/.gitattributes1
-rw-r--r--Documentation/.gitignore1
-rw-r--r--Documentation/CodingGuidelines112
-rw-r--r--Documentation/Makefile101
-rw-r--r--Documentation/RelNotes-1.5.3.3.txt2
-rw-r--r--Documentation/RelNotes-1.5.3.5.txt60
-rw-r--r--Documentation/RelNotes-1.5.3.6.txt48
-rw-r--r--Documentation/RelNotes-1.5.3.7.txt45
-rw-r--r--Documentation/RelNotes-1.5.3.8.txt25
-rw-r--r--Documentation/RelNotes-1.5.4.1.txt17
-rw-r--r--Documentation/RelNotes-1.5.4.2.txt43
-rw-r--r--Documentation/RelNotes-1.5.4.txt368
-rw-r--r--Documentation/RelNotes-1.5.5.txt78
-rw-r--r--Documentation/SubmittingPatches70
-rw-r--r--Documentation/asciidoc.conf11
-rw-r--r--Documentation/blame-options.txt4
-rwxr-xr-xDocumentation/cat-texi.perl38
-rwxr-xr-xDocumentation/cmd-list.perl151
-rw-r--r--Documentation/config.txt342
-rw-r--r--Documentation/core-tutorial.txt64
-rw-r--r--Documentation/cvs-migration.txt10
-rw-r--r--Documentation/diff-format.txt227
-rw-r--r--Documentation/diff-generate-patch.txt161
-rw-r--r--Documentation/diff-options.txt55
-rw-r--r--Documentation/everyday.txt71
-rw-r--r--Documentation/fetch-options.txt14
-rw-r--r--Documentation/git-add.txt36
-rw-r--r--Documentation/git-am.txt16
-rw-r--r--Documentation/git-annotate.txt4
-rw-r--r--Documentation/git-apply.txt47
-rw-r--r--Documentation/git-archimport.txt2
-rw-r--r--Documentation/git-archive.txt2
-rw-r--r--Documentation/git-bisect.txt42
-rw-r--r--Documentation/git-blame.txt8
-rw-r--r--Documentation/git-branch.txt50
-rw-r--r--Documentation/git-bundle.txt10
-rw-r--r--Documentation/git-cat-file.txt4
-rw-r--r--Documentation/git-check-attr.txt7
-rw-r--r--Documentation/git-check-ref-format.txt6
-rw-r--r--Documentation/git-checkout-index.txt2
-rw-r--r--Documentation/git-checkout.txt11
-rw-r--r--Documentation/git-cherry-pick.txt26
-rw-r--r--Documentation/git-cherry.txt2
-rw-r--r--Documentation/git-citool.txt6
-rw-r--r--Documentation/git-clean.txt4
-rw-r--r--Documentation/git-clone.txt20
-rw-r--r--Documentation/git-commit-tree.txt6
-rw-r--r--Documentation/git-commit.txt65
-rw-r--r--Documentation/git-config.txt30
-rw-r--r--Documentation/git-count-objects.txt2
-rw-r--r--Documentation/git-cvsexportcommit.txt26
-rw-r--r--Documentation/git-cvsimport.txt8
-rw-r--r--Documentation/git-cvsserver.txt10
-rw-r--r--Documentation/git-daemon.txt8
-rw-r--r--Documentation/git-describe.txt6
-rw-r--r--Documentation/git-diff-files.txt2
-rw-r--r--Documentation/git-diff-index.txt2
-rw-r--r--Documentation/git-diff-tree.txt2
-rw-r--r--Documentation/git-diff.txt12
-rw-r--r--Documentation/git-fast-export.txt83
-rw-r--r--Documentation/git-fast-import.txt105
-rw-r--r--Documentation/git-fetch-pack.txt4
-rw-r--r--Documentation/git-fetch.txt4
-rw-r--r--Documentation/git-filter-branch.txt20
-rw-r--r--Documentation/git-fmt-merge-msg.txt4
-rw-r--r--Documentation/git-format-patch.txt24
-rw-r--r--Documentation/git-fsck-objects.txt2
-rw-r--r--Documentation/git-fsck.txt2
-rw-r--r--Documentation/git-gc.txt52
-rw-r--r--Documentation/git-get-tar-commit-id.txt8
-rw-r--r--Documentation/git-grep.txt6
-rw-r--r--Documentation/git-gui.txt4
-rw-r--r--Documentation/git-hash-object.txt2
-rw-r--r--Documentation/git-help.txt95
-rw-r--r--Documentation/git-http-fetch.txt2
-rw-r--r--Documentation/git-http-push.txt10
-rw-r--r--Documentation/git-imap-send.txt2
-rw-r--r--Documentation/git-index-pack.txt10
-rw-r--r--Documentation/git-init-db.txt2
-rw-r--r--Documentation/git-init.txt4
-rw-r--r--Documentation/git-instaweb.txt15
-rw-r--r--Documentation/git-local-fetch.txt66
-rw-r--r--Documentation/git-log.txt30
-rw-r--r--Documentation/git-lost-found.txt6
-rw-r--r--Documentation/git-ls-files.txt14
-rw-r--r--Documentation/git-ls-remote.txt6
-rw-r--r--Documentation/git-ls-tree.txt2
-rw-r--r--Documentation/git-mailinfo.txt4
-rw-r--r--Documentation/git-mailsplit.txt2
-rw-r--r--Documentation/git-merge-base.txt2
-rw-r--r--Documentation/git-merge-file.txt4
-rw-r--r--Documentation/git-merge-index.txt4
-rw-r--r--Documentation/git-merge-one-file.txt2
-rw-r--r--Documentation/git-merge-tree.txt2
-rw-r--r--Documentation/git-merge.txt30
-rw-r--r--Documentation/git-mergetool.txt12
-rw-r--r--Documentation/git-mktag.txt2
-rw-r--r--Documentation/git-mktree.txt2
-rw-r--r--Documentation/git-mv.txt4
-rw-r--r--Documentation/git-name-rev.txt4
-rw-r--r--Documentation/git-pack-objects.txt13
-rw-r--r--Documentation/git-pack-redundant.txt8
-rw-r--r--Documentation/git-pack-refs.txt2
-rw-r--r--Documentation/git-parse-remote.txt2
-rw-r--r--Documentation/git-patch-id.txt2
-rw-r--r--Documentation/git-peek-remote.txt8
-rw-r--r--Documentation/git-prune-packed.txt6
-rw-r--r--Documentation/git-prune.txt7
-rw-r--r--Documentation/git-pull.txt27
-rw-r--r--Documentation/git-push.txt71
-rw-r--r--Documentation/git-quiltimport.txt2
-rw-r--r--Documentation/git-read-tree.txt8
-rw-r--r--Documentation/git-rebase.txt10
-rw-r--r--Documentation/git-receive-pack.txt4
-rw-r--r--Documentation/git-reflog.txt19
-rw-r--r--Documentation/git-relink.txt9
-rw-r--r--Documentation/git-remote.txt18
-rw-r--r--Documentation/git-repack.txt14
-rw-r--r--Documentation/git-repo-config.txt2
-rw-r--r--Documentation/git-request-pull.txt2
-rw-r--r--Documentation/git-rerere.txt8
-rw-r--r--Documentation/git-reset.txt36
-rw-r--r--Documentation/git-rev-list.txt338
-rw-r--r--Documentation/git-rev-parse.txt93
-rw-r--r--Documentation/git-revert.txt15
-rw-r--r--Documentation/git-rm.txt8
-rw-r--r--Documentation/git-runstatus.txt68
-rw-r--r--Documentation/git-send-email.txt62
-rw-r--r--Documentation/git-send-pack.txt8
-rw-r--r--Documentation/git-sh-setup.txt11
-rw-r--r--Documentation/git-shell.txt2
-rw-r--r--Documentation/git-shortlog.txt26
-rw-r--r--Documentation/git-show-branch.txt2
-rw-r--r--Documentation/git-show-index.txt2
-rw-r--r--Documentation/git-show-ref.txt6
-rw-r--r--Documentation/git-show.txt8
-rw-r--r--Documentation/git-ssh-fetch.txt52
-rw-r--r--Documentation/git-ssh-upload.txt48
-rw-r--r--Documentation/git-stash.txt22
-rw-r--r--Documentation/git-status.txt41
-rw-r--r--Documentation/git-stripspace.txt4
-rw-r--r--Documentation/git-submodule.txt14
-rw-r--r--Documentation/git-svn.txt36
-rw-r--r--Documentation/git-symbolic-ref.txt4
-rw-r--r--Documentation/git-tag.txt6
-rw-r--r--Documentation/git-tar-tree.txt2
-rw-r--r--Documentation/git-tools.txt5
-rw-r--r--Documentation/git-unpack-file.txt2
-rw-r--r--Documentation/git-unpack-objects.txt2
-rw-r--r--Documentation/git-update-index.txt12
-rw-r--r--Documentation/git-update-ref.txt2
-rw-r--r--Documentation/git-update-server-info.txt2
-rw-r--r--Documentation/git-upload-archive.txt2
-rw-r--r--Documentation/git-upload-pack.txt2
-rw-r--r--Documentation/git-var.txt8
-rw-r--r--Documentation/git-verify-pack.txt2
-rw-r--r--Documentation/git-verify-tag.txt2
-rw-r--r--Documentation/git-web--browse.txt78
-rw-r--r--Documentation/git-whatchanged.txt2
-rw-r--r--Documentation/git-write-tree.txt2
-rw-r--r--Documentation/git.txt53
-rw-r--r--Documentation/gitattributes.txt113
-rw-r--r--Documentation/gitcli.txt113
-rw-r--r--Documentation/gitignore.txt13
-rw-r--r--Documentation/gitk.txt8
-rw-r--r--Documentation/gitmodules.txt6
-rw-r--r--Documentation/glossary.txt21
-rw-r--r--Documentation/hooks.txt29
-rw-r--r--Documentation/howto/maintain-git.txt277
-rw-r--r--Documentation/howto/recover-corrupted-blob-object.txt134
-rw-r--r--Documentation/howto/using-merge-subtree.txt75
-rw-r--r--Documentation/i18n.txt2
-rwxr-xr-xDocumentation/install-webdoc.sh16
-rw-r--r--Documentation/manpage-1.72.xsl17
-rw-r--r--Documentation/repository-layout.txt10
-rw-r--r--Documentation/rev-list-options.txt361
-rw-r--r--Documentation/technical/.gitignore1
-rw-r--r--Documentation/technical/api-allocation-growing.txt34
-rw-r--r--Documentation/technical/api-builtin.txt63
-rw-r--r--Documentation/technical/api-decorate.txt6
-rw-r--r--Documentation/technical/api-diff.txt166
-rw-r--r--Documentation/technical/api-directory-listing.txt76
-rw-r--r--Documentation/technical/api-gitattributes.txt111
-rw-r--r--Documentation/technical/api-grep.txt8
-rw-r--r--Documentation/technical/api-hash.txt6
-rw-r--r--Documentation/technical/api-in-core-index.txt21
-rw-r--r--Documentation/technical/api-index-skel.txt15
-rwxr-xr-xDocumentation/technical/api-index.sh28
-rw-r--r--Documentation/technical/api-lockfile.txt74
-rw-r--r--Documentation/technical/api-object-access.txt15
-rw-r--r--Documentation/technical/api-parse-options.txt6
-rw-r--r--Documentation/technical/api-path-list.txt9
-rw-r--r--Documentation/technical/api-quote.txt10
-rw-r--r--Documentation/technical/api-remote.txt123
-rw-r--r--Documentation/technical/api-revision-walking.txt9
-rw-r--r--Documentation/technical/api-run-command.txt171
-rw-r--r--Documentation/technical/api-setup.txt13
-rw-r--r--Documentation/technical/api-strbuf.txt6
-rw-r--r--Documentation/technical/api-tree-walking.txt12
-rw-r--r--Documentation/technical/api-xdiff-interface.txt7
-rw-r--r--Documentation/technical/pack-format.txt94
-rw-r--r--Documentation/technical/racy-git.txt2
-rw-r--r--Documentation/tutorial-2.txt2
-rw-r--r--Documentation/tutorial.txt24
-rw-r--r--Documentation/urls.txt8
-rw-r--r--Documentation/user-manual.conf2
-rw-r--r--Documentation/user-manual.txt550
-rwxr-xr-xGIT-VERSION-GEN13
-rw-r--r--INSTALL4
-rw-r--r--Makefile219
l---------RelNotes2
-rw-r--r--archive-tar.c2
-rw-r--r--archive.c84
-rw-r--r--attr.c11
-rw-r--r--builtin-add.c161
-rw-r--r--builtin-apply.c274
-rw-r--r--builtin-archive.c86
-rw-r--r--builtin-blame.c54
-rw-r--r--builtin-branch.c223
-rw-r--r--builtin-bundle.c365
-rw-r--r--builtin-checkout-index.c4
-rw-r--r--builtin-clean.c178
-rw-r--r--builtin-commit-tree.c4
-rw-r--r--builtin-commit.c992
-rw-r--r--builtin-config.c125
-rw-r--r--builtin-count-objects.c32
-rw-r--r--builtin-describe.c94
-rw-r--r--builtin-diff-files.c4
-rw-r--r--builtin-diff-index.c4
-rw-r--r--builtin-diff-tree.c31
-rw-r--r--builtin-diff.c48
-rwxr-xr-xbuiltin-fast-export.c407
-rw-r--r--builtin-fetch--tool.c26
-rw-r--r--builtin-fetch-pack.c (renamed from fetch-pack.c)308
-rw-r--r--builtin-fetch.c634
-rw-r--r--builtin-fmt-merge-msg.c5
-rw-r--r--builtin-for-each-ref.c145
-rw-r--r--builtin-fsck.c101
-rw-r--r--builtin-gc.c50
-rw-r--r--builtin-grep.c25
-rw-r--r--builtin-http-fetch.c87
-rw-r--r--builtin-init-db.c16
-rw-r--r--builtin-log.c247
-rw-r--r--builtin-ls-files.c141
-rw-r--r--builtin-ls-remote.c110
-rw-r--r--builtin-mailinfo.c9
-rw-r--r--builtin-mailsplit.c44
-rw-r--r--builtin-merge-ours.c28
-rw-r--r--builtin-mv.c95
-rw-r--r--builtin-name-rev.c96
-rw-r--r--builtin-pack-objects.c343
-rw-r--r--builtin-pack-refs.c47
-rw-r--r--builtin-prune-packed.c14
-rw-r--r--builtin-prune.c60
-rw-r--r--builtin-push.c195
-rw-r--r--builtin-read-tree.c5
-rw-r--r--builtin-reflog.c61
-rw-r--r--builtin-rerere.c41
-rw-r--r--builtin-reset.c88
-rw-r--r--builtin-rev-list.c152
-rw-r--r--builtin-rev-parse.c165
-rw-r--r--builtin-revert.c158
-rw-r--r--builtin-rm.c57
-rw-r--r--builtin-runstatus.c37
-rw-r--r--builtin-send-pack.c654
-rw-r--r--builtin-shortlog.c187
-rw-r--r--builtin-show-branch.c4
-rw-r--r--builtin-show-ref.c3
-rw-r--r--builtin-symbolic-ref.c52
-rw-r--r--builtin-tag.c267
-rw-r--r--builtin-unpack-objects.c10
-rw-r--r--builtin-update-index.c20
-rw-r--r--builtin-update-ref.c65
-rw-r--r--builtin-write-tree.c74
-rw-r--r--builtin.h13
-rw-r--r--bundle.c358
-rw-r--r--bundle.h25
-rw-r--r--cache-tree.c59
-rw-r--r--cache-tree.h5
-rw-r--r--cache.h188
-rw-r--r--check-racy.c2
-rw-r--r--color.c66
-rw-r--r--color.h17
-rw-r--r--combine-diff.c42
-rw-r--r--command-list.txt130
-rw-r--r--commit.c910
-rw-r--r--commit.h33
-rw-r--r--compat/fopen.c26
-rw-r--r--compat/inet_ntop.c1
-rw-r--r--compat/inet_pton.c1
-rw-r--r--compat/mkdtemp.c8
-rw-r--r--compat/qsort.c62
-rw-r--r--config.c267
-rw-r--r--config.mak.in8
-rw-r--r--configure.ac101
-rw-r--r--connect.c150
-rwxr-xr-xcontrib/completion/git-completion.bash63
-rw-r--r--contrib/emacs/git-blame.el10
-rw-r--r--contrib/emacs/git.el342
-rwxr-xr-xcontrib/examples/git-clean.sh118
-rwxr-xr-xcontrib/examples/git-commit.sh (renamed from git-commit.sh)23
-rwxr-xr-xcontrib/examples/git-fetch.sh (renamed from git-fetch.sh)0
-rwxr-xr-xcontrib/examples/git-ls-remote.sh (renamed from git-ls-remote.sh)7
-rwxr-xr-xcontrib/examples/git-merge-ours.sh (renamed from git-merge-ours.sh)2
-rwxr-xr-xcontrib/examples/git-revert.sh197
-rwxr-xr-xcontrib/examples/git-svnimport.perl (renamed from git-svnimport.perl)0
-rw-r--r--contrib/examples/git-svnimport.txt (renamed from Documentation/git-svnimport.txt)0
-rwxr-xr-xcontrib/examples/git-tag.sh2
-rwxr-xr-xcontrib/fast-import/git-p4224
-rwxr-xr-xcontrib/gitview/gitview17
-rwxr-xr-xcontrib/hg-to-git/hg-to-git.py48
-rw-r--r--contrib/hooks/post-receive-email233
-rwxr-xr-x[-rw-r--r--]contrib/remotes2config.sh8
-rw-r--r--convert.c148
-rw-r--r--csum-file.c45
-rw-r--r--csum-file.h11
-rw-r--r--daemon.c14
-rw-r--r--diff-delta.c50
-rw-r--r--diff-lib.c313
-rw-r--r--diff.c724
-rw-r--r--diff.h52
-rw-r--r--diffcore-break.c23
-rw-r--r--diffcore-delta.c54
-rw-r--r--diffcore-rename.c364
-rw-r--r--diffcore.h4
-rw-r--r--dir.c321
-rw-r--r--dir.h45
-rw-r--r--entry.c12
-rw-r--r--environment.c8
-rw-r--r--exec_cmd.c132
-rw-r--r--exec_cmd.h3
-rw-r--r--fast-import.c173
-rw-r--r--fetch-pack.h26
-rw-r--r--fetch.h54
-rwxr-xr-xgenerate-cmdlist.sh31
-rwxr-xr-xgit-add--interactive.perl376
-rwxr-xr-xgit-am.sh115
-rwxr-xr-xgit-bisect.sh306
-rwxr-xr-xgit-checkout.sh113
-rwxr-xr-xgit-clean.sh99
-rwxr-xr-xgit-clone.sh144
-rw-r--r--git-compat-util.h79
-rwxr-xr-xgit-cvsexportcommit.perl137
-rwxr-xr-xgit-cvsimport.perl66
-rwxr-xr-xgit-cvsserver.perl48
-rwxr-xr-xgit-filter-branch.sh79
-rw-r--r--git-gui/.gitignore5
-rwxr-xr-xgit-gui/GIT-VERSION-GEN2
-rw-r--r--git-gui/Makefile213
-rwxr-xr-xgit-gui/git-gui.sh894
-rw-r--r--git-gui/lib/about.tcl86
-rw-r--r--git-gui/lib/blame.tcl54
-rw-r--r--git-gui/lib/branch_checkout.tcl16
-rw-r--r--git-gui/lib/branch_create.tcl38
-rw-r--r--git-gui/lib/branch_delete.tcl22
-rw-r--r--git-gui/lib/branch_rename.tcl22
-rw-r--r--git-gui/lib/browser.tcl22
-rw-r--r--git-gui/lib/checkout_op.tcl60
-rw-r--r--git-gui/lib/choose_repository.tcl1038
-rw-r--r--git-gui/lib/choose_rev.tcl37
-rw-r--r--git-gui/lib/commit.tcl219
-rw-r--r--git-gui/lib/console.tcl91
-rw-r--r--git-gui/lib/database.tcl34
-rw-r--r--git-gui/lib/date.tcl53
-rw-r--r--git-gui/lib/diff.tcl32
-rw-r--r--git-gui/lib/error.tcl22
-rw-r--r--git-gui/lib/git-gui.icobin0 -> 3638 bytes
-rw-r--r--git-gui/lib/index.tcl142
-rw-r--r--git-gui/lib/logo.tcl43
-rw-r--r--git-gui/lib/merge.tcl61
-rw-r--r--git-gui/lib/option.tcl166
-rw-r--r--git-gui/lib/remote.tcl53
-rw-r--r--git-gui/lib/remote_branch_delete.tcl46
-rw-r--r--git-gui/lib/shortcut.tcl57
-rw-r--r--git-gui/lib/spellcheck.tcl363
-rw-r--r--git-gui/lib/status_bar.tcl43
-rw-r--r--git-gui/lib/transport.tcl48
-rw-r--r--git-gui/lib/win32.tcl26
-rw-r--r--git-gui/lib/win32_shortcut.js34
-rw-r--r--git-gui/macosx/AppMain.tcl22
-rw-r--r--git-gui/macosx/Info.plist28
-rw-r--r--git-gui/macosx/git-gui.icnsbin0 -> 28866 bytes
-rw-r--r--git-gui/po/.gitignore2
-rw-r--r--git-gui/po/README209
-rw-r--r--git-gui/po/de.po1952
-rw-r--r--git-gui/po/fr.po1875
-rw-r--r--git-gui/po/git-gui.pot1778
-rw-r--r--git-gui/po/glossary/Makefile9
-rw-r--r--git-gui/po/glossary/de.po189
-rw-r--r--git-gui/po/glossary/fr.po166
-rw-r--r--git-gui/po/glossary/git-gui-glossary.pot168
-rw-r--r--git-gui/po/glossary/git-gui-glossary.txt38
-rw-r--r--git-gui/po/glossary/it.po184
-rwxr-xr-xgit-gui/po/glossary/txt-to-pot.sh48
-rw-r--r--git-gui/po/glossary/zh_cn.po170
-rw-r--r--git-gui/po/hu.po1937
-rw-r--r--git-gui/po/it.po1913
-rw-r--r--git-gui/po/ja.po1883
-rw-r--r--git-gui/po/po2msg.sh152
-rw-r--r--git-gui/po/ru.po1893
-rw-r--r--git-gui/po/sv.po1895
-rw-r--r--git-gui/po/zh_cn.po1769
-rw-r--r--git-gui/windows/git-gui.sh16
-rwxr-xr-xgit-instaweb.sh111
-rwxr-xr-xgit-lost-found.sh3
-rwxr-xr-xgit-merge-one-file.sh4
-rwxr-xr-xgit-merge.sh137
-rwxr-xr-xgit-mergetool.sh124
-rwxr-xr-xgit-pull.sh102
-rwxr-xr-xgit-quiltimport.sh49
-rwxr-xr-xgit-rebase--interactive.sh106
-rwxr-xr-xgit-rebase.sh94
-rwxr-xr-xgit-relink.perl2
-rwxr-xr-xgit-remote.perl13
-rwxr-xr-xgit-repack.sh27
-rwxr-xr-xgit-request-pull.sh7
-rwxr-xr-xgit-send-email.perl301
-rwxr-xr-xgit-sh-setup.sh91
-rwxr-xr-xgit-stash.sh70
-rwxr-xr-xgit-submodule.sh182
-rwxr-xr-xgit-svn.perl1000
-rwxr-xr-xgit-web--browse.sh160
-rw-r--r--git.c92
-rw-r--r--git.spec.in92
-rw-r--r--gitk-git/Makefile63
-rw-r--r--[-rwxr-xr-x]gitk-git/gitk (renamed from gitk)3387
-rw-r--r--gitk-git/po/.gitignore1
-rw-r--r--gitk-git/po/de.po725
-rw-r--r--gitk-git/po/po2msg.sh133
-rw-r--r--gitweb/INSTALL2
-rw-r--r--gitweb/README232
-rw-r--r--gitweb/gitweb.css8
-rwxr-xr-xgitweb/gitweb.perl565
-rw-r--r--hash-object.c2
-rw-r--r--hash.c110
-rw-r--r--hash.h43
-rw-r--r--help.c331
-rw-r--r--http-push.c431
-rw-r--r--http-walker.c (renamed from http-fetch.c)384
-rw-r--r--http.c232
-rw-r--r--http.h57
-rw-r--r--ident.c64
-rw-r--r--imap-send.c4
-rw-r--r--index-pack.c63
-rw-r--r--list-objects.c11
-rw-r--r--local-fetch.c254
-rw-r--r--lockfile.c43
-rw-r--r--log-tree.c38
-rw-r--r--log-tree.h1
-rw-r--r--mailmap.c7
-rw-r--r--match-trees.c2
-rw-r--r--merge-file.c2
-rw-r--r--merge-index.c4
-rw-r--r--merge-recursive.c42
-rw-r--r--merge-tree.c2
-rw-r--r--object.c39
-rw-r--r--pack-write.c29
-rw-r--r--pack.h3
-rw-r--r--pager.c18
-rw-r--r--parse-options.c397
-rw-r--r--parse-options.h119
-rw-r--r--patch-ids.c2
-rw-r--r--peek-remote.c75
-rw-r--r--perl/Git.pm68
-rw-r--r--perl/Makefile.PL6
-rw-r--r--pretty.c813
-rw-r--r--progress.c208
-rw-r--r--progress.h22
-rw-r--r--quote.c34
-rw-r--r--quote.h4
-rw-r--r--reachable.c15
-rw-r--r--read-cache.c402
-rw-r--r--receive-pack.c51
-rw-r--r--refs.c187
-rw-r--r--refs.h8
-rw-r--r--remote.c403
-rw-r--r--remote.h74
-rw-r--r--revision.c202
-rw-r--r--revision.h24
-rw-r--r--rsh.c79
-rw-r--r--rsh.h7
-rw-r--r--run-command.c79
-rw-r--r--run-command.h24
-rw-r--r--send-pack.c445
-rw-r--r--send-pack.h18
-rw-r--r--server-info.c4
-rw-r--r--setup.c217
-rw-r--r--sha1_file.c69
-rw-r--r--sha1_name.c48
-rw-r--r--shallow.c5
-rw-r--r--shell.c8
-rw-r--r--show-index.c2
-rw-r--r--sideband.c71
-rw-r--r--ssh-fetch.c166
-rw-r--r--ssh-pull.c4
-rw-r--r--ssh-push.c4
-rw-r--r--ssh-upload.c143
-rw-r--r--strbuf.c44
-rw-r--r--strbuf.h13
-rw-r--r--t/.gitattributes1
-rw-r--r--t/README14
-rw-r--r--t/diff-lib.sh4
-rw-r--r--t/lib-git-svn.sh26
-rwxr-xr-xt/t0000-basic.sh50
-rwxr-xr-xt/t0001-init.sh16
-rwxr-xr-xt/t0003-attributes.sh49
-rwxr-xr-xt/t0020-crlf.sh91
-rwxr-xr-xt/t0021-conversion.sh7
-rwxr-xr-xt/t0030-stripspace.sh40
-rwxr-xr-xt/t0040-parse-options.sh117
-rwxr-xr-xt/t1000-read-tree-m-3way.sh161
-rwxr-xr-xt/t1200-tutorial.sh8
-rwxr-xr-xt/t1300-repo-config.sh102
-rwxr-xr-xt/t1302-repo-version.sh47
-rwxr-xr-xt/t1303-wacky-config.sh37
-rwxr-xr-xt/t1400-update-ref.sh45
-rwxr-xr-xt/t1410-reflog.sh19
-rwxr-xr-xt/t1500-rev-parse.sh4
-rwxr-xr-xt/t2000-checkout-cache-clash.sh4
-rwxr-xr-xt/t2002-checkout-cache-u.sh4
-rwxr-xr-xt/t2008-checkout-subdir.sh82
-rwxr-xr-xt/t2100-update-cache-badpath.sh4
-rwxr-xr-xt/t2200-add-update.sh30
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh43
-rwxr-xr-xt/t3020-ls-files-error-unmatch.sh4
-rwxr-xr-xt/t3200-branch.sh36
-rwxr-xr-xt/t3201-branch-contains.sh58
-rwxr-xr-xt/t3210-pack-refs.sh26
-rwxr-xr-xt/t3300-funny-names.sh6
-rwxr-xr-xt/t3400-rebase.sh4
-rwxr-xr-xt/t3402-rebase-merge.sh7
-rwxr-xr-xt/t3403-rebase-skip.sh25
-rwxr-xr-xt/t3404-rebase-interactive.sh51
-rwxr-xr-xt/t3501-revert-cherry-pick.sh4
-rwxr-xr-xt/t3502-cherry-pick-merge.sh123
-rwxr-xr-xt/t3600-rm.sh19
-rwxr-xr-xt/t3700-add.sh24
-rwxr-xr-xt/t3701-add-interactive.sh69
-rwxr-xr-xt/t3800-mktag.sh89
-rwxr-xr-xt/t3901-i18n-patch.sh8
-rwxr-xr-xt/t3902-quoted.sh7
-rwxr-xr-xt/t4000-diff-format.sh2
-rwxr-xr-xt/t4001-diff-rename.sh6
-rwxr-xr-xt/t4008-diff-break-rewrite.sh8
-rwxr-xr-xt/t4015-diff-whitespace.sh218
-rwxr-xr-xt/t4017-diff-retval.sh29
-rwxr-xr-x[-rw-r--r--]t/t4018-diff-funcname.sh0
-rwxr-xr-xt/t4019-diff-wserror.sh123
-rwxr-xr-xt/t4020-diff-external.sh2
-rwxr-xr-xt/t4021-format-patch-numbered.sh106
-rwxr-xr-xt/t4021-format-patch-signer-mime.sh42
-rwxr-xr-xt/t4022-diff-rewrite.sh26
-rwxr-xr-xt/t4023-diff-rename-typechange.sh86
-rwxr-xr-xt/t4024-diff-optimize-common.sh157
-rwxr-xr-xt/t4025-hunk-header.sh44
-rwxr-xr-xt/t4026-color.sh69
-rw-r--r--t/t4100/t-apply-1.patch2
-rw-r--r--t/t4100/t-apply-2.patch2
-rw-r--r--t/t4100/t-apply-5.patch2
-rw-r--r--t/t4100/t-apply-6.patch2
-rwxr-xr-xt/t4103-apply-binary.sh68
-rwxr-xr-xt/t4113-apply-ending.sh8
-rwxr-xr-xt/t4116-apply-reverse.sh4
-rwxr-xr-xt/t4119-apply-config.sh2
-rwxr-xr-xt/t4124-apply-ws-rule.sh151
-rwxr-xr-xt/t4200-rerere.sh2
-rwxr-xr-xt/t4201-shortlog.sh2
-rwxr-xr-xt/t4202-log.sh74
-rwxr-xr-xt/t5100-mailinfo.sh2
-rw-r--r--t/t5100/info00095
-rw-r--r--t/t5100/msg00092
-rw-r--r--t/t5100/patch000913
-rw-r--r--t/t5100/sample.mbox26
-rwxr-xr-xt/t5300-pack-object.sh46
-rwxr-xr-xt/t5302-pack-index.sh56
-rw-r--r--t/t5304-prune.sh32
-rwxr-xr-xt/t5400-send-pack.sh48
-rwxr-xr-xt/t5401-update-hooks.sh18
-rwxr-xr-xt/t5402-post-merge-hook.sh4
-rwxr-xr-xt/t5404-tracking-branches.sh53
-rwxr-xr-xt/t5405-send-pack-rewind.sh42
-rwxr-xr-xt/t5406-remote-rejects.sh24
-rwxr-xr-xt/t5500-fetch-pack.sh4
-rwxr-xr-xt/t5502-quickfetch.sh33
-rwxr-xr-xt/t5510-fetch.sh146
-rwxr-xr-xt/t5512-ls-remote.sh52
-rwxr-xr-xt/t5515-fetch-merge-logic.sh3
-rw-r--r--t/t5515/fetch.br-branches-default-merge3
-rw-r--r--t/t5515/fetch.br-branches-default-merge_branches-default3
-rw-r--r--t/t5515/fetch.br-branches-default-octopus4
-rw-r--r--t/t5515/fetch.br-branches-default-octopus_branches-default4
-rw-r--r--t/t5515/fetch.br-branches-one-merge3
-rw-r--r--t/t5515/fetch.br-branches-one-merge_branches-one3
-rw-r--r--t/t5515/fetch.br-branches-one-octopus1
-rw-r--r--t/t5515/fetch.br-branches-one-octopus_branches-one1
-rw-r--r--t/t5515/fetch.br-config-glob-octopus2
-rw-r--r--t/t5515/fetch.br-config-glob-octopus_config-glob2
-rw-r--r--t/t5515/fetch.br-remote-glob-octopus2
-rw-r--r--t/t5515/fetch.br-remote-glob-octopus_remote-glob2
-rwxr-xr-xt/t5516-fetch-push.sh74
-rwxr-xr-xt/t5517-push-mirror.sh228
-rwxr-xr-xt/t5520-pull.sh39
-rwxr-xr-xt/t5530-upload-pack-error.sh73
-rwxr-xr-xt/t5600-clone-fail-cleanup.sh12
-rwxr-xr-xt/t5700-clone-reference.sh4
-rwxr-xr-xt/t5701-clone-local.sh8
-rwxr-xr-xt/t5702-clone-options.sh22
-rwxr-xr-xt/t5710-info-alternate.sh26
-rwxr-xr-xt/t6006-rev-list-format.sh10
-rwxr-xr-xt/t6008-rev-list-submodule.sh42
-rwxr-xr-xt/t6009-rev-list-parent.sh38
-rwxr-xr-xt/t6023-merge-file.sh12
-rwxr-xr-xt/t6024-recursive-merge.sh2
-rwxr-xr-xt/t6025-merge-symlinks.sh21
-rwxr-xr-xt/t6030-bisect-porcelain.sh167
-rwxr-xr-xt/t6101-rev-parse-parents.sh2
-rwxr-xr-x[-rw-r--r--]t/t6300-for-each-ref.sh69
-rwxr-xr-xt/t7001-mv.sh54
-rwxr-xr-xt/t7002-grep.sh4
-rwxr-xr-xt/t7003-filter-branch.sh16
-rwxr-xr-xt/t7004-tag.sh219
-rwxr-xr-xt/t7005-editor.sh11
-rwxr-xr-xt/t7010-setup.sh164
-rwxr-xr-xt/t7101-reset.sh24
-rwxr-xr-xt/t7102-reset.sh26
-rwxr-xr-xt/t7103-reset-bare.sh28
-rwxr-xr-xt/t7104-reset.sh46
-rwxr-xr-xt/t7201-co.sh74
-rwxr-xr-xt/t7300-clean.sh149
-rwxr-xr-xt/t7400-submodule-basic.sh56
-rwxr-xr-xt/t7500-commit.sh45
-rwxr-xr-x[-rw-r--r--]t/t7501-commit.sh220
-rwxr-xr-xt/t7502-commit.sh186
-rwxr-xr-xt/t7502-status.sh152
-rwxr-xr-xt/t7503-pre-commit-hook.sh88
-rwxr-xr-xt/t7504-commit-msg-hook.sh220
-rwxr-xr-xt/t7505-prepare-commit-msg-hook.sh155
-rwxr-xr-xt/t7600-merge.sh4
-rwxr-xr-xt/t8004-blame.sh73
-rwxr-xr-xt/t9001-send-email.sh96
-rwxr-xr-xt/t9100-git-svn-basic.sh28
-rwxr-xr-xt/t9101-git-svn-props.sh2
-rwxr-xr-xt/t9103-git-svn-tracked-directory-removed.sh39
-rwxr-xr-xt/t9106-git-svn-commit-diff-clobber.sh18
-rwxr-xr-xt/t9106-git-svn-dcommit-clobber-series.sh63
-rwxr-xr-xt/t9107-git-svn-migrate.sh16
-rwxr-xr-xt/t9114-git-svn-dcommit-merge.sh5
-rwxr-xr-xt/t9116-git-svn-log.sh80
-rwxr-xr-xt/t9117-git-svn-init-clone.sh55
-rwxr-xr-xt/t9118-git-svn-funky-branch-names.sh40
-rwxr-xr-xt/t9119-git-svn-info.sh370
-rwxr-xr-xt/t9200-git-cvsexportcommit.sh55
-rwxr-xr-xt/t9300-fast-import.sh24
-rwxr-xr-xt/t9301-fast-export.sh123
-rwxr-xr-xt/t9400-git-cvsserver-server.sh47
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh49
-rwxr-xr-xt/t9600-cvsimport.sh150
-rw-r--r--t/test-lib.sh123
-rw-r--r--tag.c10
-rw-r--r--templates/Makefile2
-rw-r--r--templates/hooks--commit-msg3
-rw-r--r--templates/hooks--pre-commit6
-rw-r--r--templates/hooks--prepare-commit-msg36
-rw-r--r--templates/hooks--update39
-rw-r--r--test-parse-options.c37
-rwxr-xr-xtest-sha1.sh4
-rw-r--r--trace.c10
-rw-r--r--transport.c818
-rw-r--r--transport.h72
-rw-r--r--tree-diff.c17
-rw-r--r--tree-walk.c10
-rw-r--r--tree.c4
-rw-r--r--unpack-trees.c56
-rw-r--r--unpack-trees.h1
-rw-r--r--upload-pack.c363
-rw-r--r--usage.c6
-rw-r--r--utf8.c103
-rw-r--r--utf8.h5
-rw-r--r--var.c4
-rw-r--r--walker.c (renamed from fetch.c)85
-rw-r--r--walker.h37
-rw-r--r--ws.c203
-rw-r--r--wt-status.c213
-rw-r--r--wt-status.h8
-rw-r--r--xdiff-interface.c38
-rw-r--r--xdiff-interface.h1
-rw-r--r--xdiff/xdiffi.c2
-rw-r--r--xdiff/xutils.c2
691 files changed, 56188 insertions, 14455 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..6b9c715d21
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+* whitespace=!indent,trail,space
+*.[ch] whitespace
diff --git a/.gitignore b/.gitignore
index 62afef2347..165b256e2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,12 +35,12 @@ git-diff-files
git-diff-index
git-diff-tree
git-describe
+git-fast-export
git-fast-import
git-fetch
git-fetch--tool
git-fetch-pack
git-filter-branch
-git-findtags
git-fmt-merge-msg
git-for-each-ref
git-format-patch
@@ -57,7 +57,6 @@ git-index-pack
git-init
git-init-db
git-instaweb
-git-local-fetch
git-log
git-lost-found
git-ls-files
@@ -109,7 +108,6 @@ git-rev-list
git-rev-parse
git-revert
git-rm
-git-runstatus
git-send-email
git-send-pack
git-sh-setup
@@ -119,16 +117,11 @@ git-show
git-show-branch
git-show-index
git-show-ref
-git-ssh-fetch
-git-ssh-pull
-git-ssh-push
-git-ssh-upload
git-stash
git-status
git-stripspace
git-submodule
git-svn
-git-svnimport
git-symbolic-ref
git-tag
git-tar-tree
@@ -142,6 +135,7 @@ git-upload-pack
git-var
git-verify-pack
git-verify-tag
+git-web--browse
git-whatchanged
git-write-tree
git-core-*/?*
@@ -154,6 +148,7 @@ test-delta
test-dump-cache-tree
test-genrandom
test-match-trees
+test-parse-options
test-sha1
common-cmds.h
*.tar.gz
diff --git a/.mailmap b/.mailmap
index 5529b198e8..f88ae77a1f 100644
--- a/.mailmap
+++ b/.mailmap
@@ -6,6 +6,7 @@
#
Aneesh Kumar K.V <aneesh.kumar@gmail.com>
+Brian M. Carlson <sandals@crustytoothpaste.ath.cx>
Chris Shoemaker <c.shoemaker@cox.net>
Dana L. How <danahow@gmail.com>
Dana L. How <how@deathvalley.cswitch.com>
@@ -16,6 +17,7 @@ H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
H. Peter Anvin <hpa@tazenda.sc.orionmulti.com>
H. Peter Anvin <hpa@trantor.hos.anvin.org>
Horst H. von Brand <vonbrand@inf.utfsm.cl>
+Jay Soffian <jaysoffian+git@gmail.com>
Joachim Berdal Haga <cjhaga@fys.uio.no>
Jon Loeliger <jdl@freescale.com>
Jon Seymour <jon@blackcubes.dyndns.org>
@@ -24,6 +26,7 @@ Karl Hasselström <kha@treskal.com>
Kent Engstrom <kent@lysator.liu.se>
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>
Michael Coleman <tutufan@gmail.com>
@@ -37,9 +40,11 @@ Sam Vilain <sam@vilain.net>
Santi Béjar <sbejar@gmail.com>
Sean Estabrooks <seanlkml@sympatico.ca>
Shawn O. Pearce <spearce@spearce.org>
+Steven Grimm <koreth@midwinter.com>
Theodore Ts'o <tytso@mit.edu>
Tony Luck <tony.luck@intel.com>
Uwe Kleine-König <Uwe_Zeisberger@digi.com>
+Uwe Kleine-König <Uwe.Kleine-Koenig@digi.com>
Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de>
Uwe Kleine-König <uzeisberger@io.fsforth.de>
Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de>
diff --git a/Documentation/.gitattributes b/Documentation/.gitattributes
new file mode 100644
index 0000000000..ddb030137d
--- /dev/null
+++ b/Documentation/.gitattributes
@@ -0,0 +1 @@
+*.txt whitespace
diff --git a/Documentation/.gitignore b/Documentation/.gitignore
index a37b2152bd..2f938f471a 100644
--- a/Documentation/.gitignore
+++ b/Documentation/.gitignore
@@ -2,6 +2,7 @@
*.html
*.[1-8]
*.made
+git.info
howto-index.txt
doc.dep
cmds-*.txt
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
new file mode 100644
index 0000000000..3b042db624
--- /dev/null
+++ b/Documentation/CodingGuidelines
@@ -0,0 +1,112 @@
+Like other projects, we also have some guidelines to keep to the
+code. For git in general, three rough rules are:
+
+ - Most importantly, we never say "It's in POSIX; we'll happily
+ ignore your needs should your system not conform to it."
+ We live in the real world.
+
+ - However, we often say "Let's stay away from that construct,
+ it's not even in POSIX".
+
+ - In spite of the above two rules, we sometimes say "Although
+ this is not in POSIX, it (is so convenient | makes the code
+ much more readable | has other good characteristics) and
+ practically all the platforms we care about support it, so
+ let's use it".
+
+ Again, we live in the real world, and it is sometimes a
+ judgement call, the decision based more on real world
+ constraints people face than what the paper standard says.
+
+
+As for more concrete guidelines, just imitate the existing code
+(this is a good guideline, no matter which project you are
+contributing to). But if you must have a list of rules,
+here they are.
+
+For shell scripts specifically (not exhaustive):
+
+ - 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 ${parameter#word} and its [#%] siblings, and their
+ doubled "longest matching" form.
+
+ - We use Arithmetic Expansion $(( ... )).
+
+ - No "Substring Expansion" ${parameter:offset:length}.
+
+ - No shell arrays.
+
+ - No strlen ${#parameter}.
+
+ - No regexp ${parameter/pattern/string}.
+
+ - We do not use Process Substitution <(list) or >(list).
+
+ - We prefer "test" over "[ ... ]".
+
+ - We do not write the noiseword "function" in front of shell
+ functions.
+
+For C programs:
+
+ - We use tabs to indent, and interpret tabs as taking up to
+ 8 spaces.
+
+ - We try to keep to at most 80 characters per line.
+
+ - When declaring pointers, the star sides with the variable
+ name, i.e. "char *string", not "char* string" or
+ "char * string". This makes it easier to understand code
+ like "char *string, c;".
+
+ - We avoid using braces unnecessarily. I.e.
+
+ if (bla) {
+ x = 1;
+ }
+
+ is frowned upon. A gray area is when the statement extends
+ over a few lines, and/or you have a lengthy comment atop of
+ it. Also, like in the Linux kernel, if there is a long list
+ of "else if" statements, it can make sense to add braces to
+ single line blocks.
+
+ - Try to make your code understandable. You may put comments
+ in, but comments invariably tend to stale out when the code
+ they were describing changes. Often splitting a function
+ into two makes the intention of the code much clearer.
+
+ - Double negation is often harder to understand than no negation
+ at all.
+
+ - Some clever tricks, like using the !! operator with arithmetic
+ constructs, can be extremely confusing to others. Avoid them,
+ unless there is a compelling reason to use them.
+
+ - Use the API. No, really. We have a strbuf (variable length
+ string), several arrays with the ALLOC_GROW() macro, a
+ path_list for sorted string lists, a hash map (mapping struct
+ objects) named "struct decorate", amongst other things.
+
+ - When you come up with an API, document it.
+
+ - The first #include in C files, except in platform specific
+ compat/ implementations, should be git-compat-util.h or another
+ header file that includes it, such as cache.h or builtin.h.
+
+ - If you are planning a new command, consider writing it in shell
+ or perl first, so that changes in semantics can be easily
+ changed and discussed. Many git commands started out like
+ that, and a few are still scripts.
+
+ - Avoid introducing a new dependency into git. This means you
+ usually should stay away from scripting languages not already
+ used in the git core command set (unless your command is clearly
+ separate from it, such as an importer to convert random-scm-X
+ repositories to git).
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 39ec0ede02..43781fb248 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -2,10 +2,14 @@ MAN1_TXT= \
$(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
$(wildcard git-*.txt)) \
gitk.txt
-MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt
+MAN5_TXT=gitattributes.txt gitignore.txt gitcli.txt gitmodules.txt
MAN7_TXT=git.txt
-DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT))
+MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
+MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
+MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT))
+
+DOC_HTML=$(MAN_HTML)
ARTICLES = tutorial
ARTICLES += tutorial-2
@@ -19,7 +23,10 @@ ARTICLES += everyday
ARTICLES += git-tools
ARTICLES += glossary
# with their own formatting rules.
-SP_ARTICLES = howto/revert-branch-rebase user-manual
+SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual
+API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
+SP_ARTICLES += $(API_DOCS)
+SP_ARTICLES += technical/api-index
DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
@@ -29,6 +36,7 @@ DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
prefix?=$(HOME)
bindir?=$(prefix)/bin
+htmldir?=$(prefix)/share/doc/git-doc
mandir?=$(prefix)/share/man
man1dir=$(mandir)/man1
man5dir=$(mandir)/man5
@@ -37,9 +45,7 @@ man7dir=$(mandir)/man7
ASCIIDOC=asciidoc
ASCIIDOC_EXTRA =
-ifdef ASCIIDOC8
-ASCIIDOC_EXTRA += -a asciidoc7compatible
-endif
+MANPAGE_XSL = callouts.xsl
INSTALL?=install
RM ?= rm -f
DOC_REF = origin/man
@@ -48,10 +54,21 @@ infodir?=$(prefix)/share/info
MAKEINFO=makeinfo
INSTALL_INFO=install-info
DOCBOOK2X_TEXI=docbook2x-texi
+ifndef PERL_PATH
+ PERL_PATH = /usr/bin/perl
+endif
-include ../config.mak.autogen
-include ../config.mak
+ifdef ASCIIDOC8
+ASCIIDOC_EXTRA += -a asciidoc7compatible
+endif
+ifdef DOCBOOK_XSL_172
+ASCIIDOC_EXTRA += -a docbook-xsl-172
+MANPAGE_XSL = manpage-1.72.xsl
+endif
+
#
# Please note that there is a minor bug in asciidoc.
# The version after 6.0.3 _will_ include the patch found here:
@@ -72,25 +89,29 @@ man1: $(DOC_MAN1)
man5: $(DOC_MAN5)
man7: $(DOC_MAN7)
-info: git.info
+info: git.info gitman.info
install: man
- $(INSTALL) -d -m755 $(DESTDIR)$(man1dir)
- $(INSTALL) -d -m755 $(DESTDIR)$(man5dir)
- $(INSTALL) -d -m755 $(DESTDIR)$(man7dir)
- $(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir)
- $(INSTALL) -m644 $(DOC_MAN5) $(DESTDIR)$(man5dir)
- $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(man5dir)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(man7dir)
+ $(INSTALL) -m 644 $(DOC_MAN1) $(DESTDIR)$(man1dir)
+ $(INSTALL) -m 644 $(DOC_MAN5) $(DESTDIR)$(man5dir)
+ $(INSTALL) -m 644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
install-info: info
- $(INSTALL) -d -m755 $(DESTDIR)$(infodir)
- $(INSTALL) -m644 git.info $(DESTDIR)$(infodir)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(infodir)
+ $(INSTALL) -m 644 git.info gitman.info $(DESTDIR)$(infodir)
if test -r $(DESTDIR)$(infodir)/dir; then \
$(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) git.info ;\
+ $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) gitman.info ;\
else \
echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \
fi
+install-html: html
+ sh ./install-webdoc.sh $(DESTDIR)$(htmldir)
+
../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
$(MAKE) -C ../ GIT-VERSION-FILE
@@ -101,7 +122,7 @@ install-info: info
#
doc.dep : $(wildcard *.txt) build-docdep.perl
$(RM) $@+ $@
- perl ./build-docdep.perl >$@+
+ $(PERL_PATH) ./build-docdep.perl >$@+
mv $@+ $@
-include doc.dep
@@ -118,18 +139,21 @@ cmds_txt = cmds-ancillaryinterrogators.txt \
$(cmds_txt): cmd-list.made
-cmd-list.made: cmd-list.perl $(MAN1_TXT)
+cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
$(RM) $@
- perl ./cmd-list.perl
+ $(PERL_PATH) ./cmd-list.perl ../command-list.txt
date >$@
git.7 git.html: git.txt
clean:
- $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 *.texi *.texi+ howto-index.txt howto/*.html doc.dep
+ $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7
+ $(RM) *.texi *.texi+ git.info gitman.info
+ $(RM) howto-index.txt howto/*.html doc.dep
+ $(RM) technical/api-*.html technical/api-index.txt
$(RM) $(cmds_txt) *.made
-%.html : %.txt
+$(MAN_HTML): %.html : %.txt
$(RM) $@+ $@
$(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \
$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
@@ -137,7 +161,7 @@ clean:
%.1 %.5 %.7 : %.xml
$(RM) $@
- xmlto -m callouts.xsl man $<
+ xmlto -m $(MANPAGE_XSL) man $<
%.xml : %.txt
$(RM) $@+ $@
@@ -148,18 +172,41 @@ clean:
user-manual.xml: user-manual.txt user-manual.conf
$(ASCIIDOC) -b docbook -d book $<
+technical/api-index.txt: technical/api-index-skel.txt \
+ technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
+ cd technical && sh ./api-index.sh
+
+$(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt
+ $(ASCIIDOC) -b xhtml11 -f asciidoc.conf \
+ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt
+
XSLT = docbook.xsl
XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
user-manual.html: user-manual.xml
xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
-git.info: user-manual.xml
- $(RM) $@ $*.texi $*.texi+
- $(DOCBOOK2X_TEXI) user-manual.xml --to-stdout >$*.texi+
- perl fix-texi.perl <$*.texi+ >$*.texi
+git.info: user-manual.texi
+ $(MAKEINFO) --no-split -o $@ user-manual.texi
+
+user-manual.texi: user-manual.xml
+ $(RM) $@+ $@
+ $(DOCBOOK2X_TEXI) user-manual.xml --to-stdout | $(PERL_PATH) fix-texi.perl >$@+
+ mv $@+ $@
+
+gitman.texi: $(MAN_XML) cat-texi.perl
+ $(RM) $@+ $@
+ ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --to-stdout $(xml);)) | \
+ $(PERL_PATH) cat-texi.perl $@ >$@+
+ mv $@+ $@
+
+gitman.info: gitman.texi
$(MAKEINFO) --no-split $*.texi
- $(RM) $*.texi $*.texi+
+
+$(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
+ $(RM) $@+ $@
+ $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+
+ mv $@+ $@
howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
$(RM) $@+ $@
@@ -180,6 +227,6 @@ install-webdoc : html
sh ./install-webdoc.sh $(WEBDOC_DEST)
quick-install:
- sh ./install-doc-quick.sh $(DOC_REF) $(mandir)
+ sh ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
.PHONY: .FORCE-GIT-VERSION-FILE
diff --git a/Documentation/RelNotes-1.5.3.3.txt b/Documentation/RelNotes-1.5.3.3.txt
index 2a7bfdd5cc..d213846951 100644
--- a/Documentation/RelNotes-1.5.3.3.txt
+++ b/Documentation/RelNotes-1.5.3.3.txt
@@ -12,7 +12,7 @@ Fixes since v1.5.3.2
* The default shell on some FreeBSD did not execute the
argument parsing code correctly and made git unusable.
- * git-svn incorrectly spawned pager even when the user user
+ * git-svn incorrectly spawned pager even when the user
explicitly asked not to.
* sample post-receive hook overquoted the envelope sender
diff --git a/Documentation/RelNotes-1.5.3.5.txt b/Documentation/RelNotes-1.5.3.5.txt
index de38a84ad6..7ff1d5d0d1 100644
--- a/Documentation/RelNotes-1.5.3.5.txt
+++ b/Documentation/RelNotes-1.5.3.5.txt
@@ -4,37 +4,91 @@ GIT v1.5.3.5 Release Notes
Fixes since v1.5.3.4
--------------------
+ * Comes with git-gui 0.8.4.
+
* "git-config" silently ignored options after --list; now it will
error out with a usage message.
* "git-config --file" failed if the argument used a relative path
as it changed directories before opening the file.
+ * "git-config --file" now displays a proper error message if it
+ cannot read the file specified on the command line.
+
* "git-config", "git-diff", "git-apply" failed if run from a
subdirectory with relative GIT_DIR and GIT_WORK_TREE set.
+ * "git-blame" crashed if run during a merge conflict.
+
* "git-add -i" did not handle single line hunks correctly.
- * "git-rebase -i" failed if external diff drivers were used for one
- or more files in a commit. It now avoids calling the external
- diff drivers.
+ * "git-rebase -i" and "git-stash apply" failed if external diff
+ drivers were used for one or more files in a commit. They now
+ avoid calling the external diff drivers.
* "git-log --follow" did not work unless diff generation (e.g. -p)
was also requested.
+ * "git-log --follow -B" did not work at all. Fixed.
+
+ * "git-log -M -B" did not correctly handle cases of very large files
+ being renamed and replaced by very small files in the same commit.
+
* "git-log" printed extra newlines between commits when a diff
was generated internally (e.g. -S or --follow) but not displayed.
* "git-push" error message is more helpful when pushing to a
repository with no matching refs and none specified.
+ * "git-push" now respects + (force push) on wildcard refspecs,
+ matching the behavior of git-fetch.
+
* "git-filter-branch" now updates the working directory when it
has finished filtering the current branch.
* "git-instaweb" no longer fails on Mac OS X.
+ * "git-cvsexportcommit" didn't always create new parent directories
+ before trying to create new child directories. Fixed.
+
+ * "git-fetch" printed a scary (but bogus) error message while
+ fetching a tag that pointed to a tree or blob. The error did
+ not impact correctness, only user perception. The bogus error
+ is no longer printed.
+
+ * "git-ls-files --ignored" did not properly descend into non-ignored
+ directories that themselves contained ignored files if d_type
+ was not supported by the filesystem. This bug impacted systems
+ such as AFS. Fixed.
+
+ * Git segfaulted when reading an invalid .gitattributes file. Fixed.
+
+ * post-receive-email example hook was fixed for non-fast-forward
+ updates.
+
* Documentation updates for supported (but previously undocumented)
options of "git-archive" and "git-reflog".
* "make clean" no longer deletes the configure script that ships
with the git tarball, making multiple architecture builds easier.
+
+ * "git-remote show origin" spewed a warning message from Perl
+ when no remote is defined for the current branch via
+ branch.<name>.remote configuration settings.
+
+ * Building with NO_PERL_MAKEMAKER excessively rebuilt contents
+ of perl/ subdirectory by rewriting perl.mak.
+
+ * http.sslVerify configuration settings were not used in scripted
+ Porcelains.
+
+ * "git-add" leaked a bit of memory while scanning for files to add.
+
+ * A few workarounds to squelch false warnings from recent gcc have
+ been added.
+
+ * "git-send-pack $remote frotz" segfaulted when there is nothing
+ named 'frotz' on the local end.
+
+ * "git-rebase --interactive" did not handle its "--strategy" option
+ properly.
diff --git a/Documentation/RelNotes-1.5.3.6.txt b/Documentation/RelNotes-1.5.3.6.txt
new file mode 100644
index 0000000000..069a2b2cf9
--- /dev/null
+++ b/Documentation/RelNotes-1.5.3.6.txt
@@ -0,0 +1,48 @@
+GIT v1.5.3.6 Release Notes
+==========================
+
+Fixes since v1.5.3.5
+--------------------
+
+ * git-cvsexportcommit handles root commits better.
+
+ * git-svn dcommit used to clobber when sending a series of
+ patches.
+
+ * git-svn dcommit failed after attempting to rebase when
+ started with a dirty index; now it stops upfront.
+
+ * git-grep sometimes refused to work when your index was
+ unmerged.
+
+ * "git-grep -A1 -B2" acted as if it was told to run "git -A1 -B21".
+
+ * git-hash-object did not honor configuration variables, such as
+ core.compression.
+
+ * git-index-pack choked on a huge pack on 32-bit machines, even when
+ large file offsets are supported.
+
+ * atom feeds from git-web said "10" for the month of November.
+
+ * a memory leak in commit walker was plugged.
+
+ * When git-send-email inserted the original author's From:
+ address in body, it did not mark the message with
+ Content-type: as needed.
+
+ * git-revert and git-cherry-pick incorrectly refused to start
+ when the work tree was dirty.
+
+ * git-clean did not honor core.excludesfile configuration.
+
+ * git-add mishandled ".gitignore" files when applying them to
+ subdirectories.
+
+ * While importing a too branchy history, git-fastimport did not
+ honor delta depth limit properly.
+
+ * Support for zlib implementations that lack ZLIB_VERNUM and definition
+ of deflateBound() has been added.
+
+ * Quite a lot of documentation clarifications.
diff --git a/Documentation/RelNotes-1.5.3.7.txt b/Documentation/RelNotes-1.5.3.7.txt
new file mode 100644
index 0000000000..2f690616c8
--- /dev/null
+++ b/Documentation/RelNotes-1.5.3.7.txt
@@ -0,0 +1,45 @@
+GIT v1.5.3.7 Release Notes
+==========================
+
+Fixes since v1.5.3.6
+--------------------
+
+ * git-send-email added 8-bit contents to the payload without
+ marking it as 8-bit in a CTE header.
+
+ * "git-bundle create a.bndl HEAD" dereferenced the symref and
+ did not record the ref as 'HEAD'; this prevented a bundle
+ from being used as a normal source of git-clone.
+
+ * The code to reject nonsense command line of the form
+ "git-commit -a paths..." and "git-commit --interactive
+ paths..." were broken.
+
+ * Adding a signature that is not ASCII-only to an original
+ commit that is ASCII-only would make the result non-ASCII.
+ "git-format-patch -s" did not mark such a message correctly
+ with MIME encoding header.
+
+ * git-add sometimes did not mark the resulting index entry
+ stat-clean. This affected only cases when adding the
+ contents with the same length as the previously staged
+ contents, and the previous staging made the index entry
+ "racily clean".
+
+ * git-commit did not honor GIT_INDEX_FILE the user had in the
+ environment.
+
+ * When checking out a revision, git-checkout did not report where the
+ updated HEAD is if you happened to have a file called HEAD in the
+ work tree.
+
+ * "git-rev-list --objects" mishandled a tree that points at a
+ submodule.
+
+ * "git cvsimport" was not ready for packed refs that "git gc" can
+ produce and gave incorrect results.
+
+ * Many scripted Porcelains were confused when you happened to have a
+ file called "HEAD" in your work tree.
+
+Also it contains updates to the user manual and documentation.
diff --git a/Documentation/RelNotes-1.5.3.8.txt b/Documentation/RelNotes-1.5.3.8.txt
new file mode 100644
index 0000000000..0e3ff58a46
--- /dev/null
+++ b/Documentation/RelNotes-1.5.3.8.txt
@@ -0,0 +1,25 @@
+GIT v1.5.3.8 Release Notes
+==========================
+
+Fixes since v1.5.3.7
+--------------------
+
+ * Some documentation used "email.com" as an example domain.
+
+ * git-svn fix to handle funky branch and project names going over
+ http/https correctly.
+
+ * git-svn fix to tone down a needlessly alarming warning message.
+
+ * git-clone did not correctly report errors while fetching over http.
+
+ * git-send-email added redundant Message-Id: header to the outgoing
+ e-mail when the patch text already had one.
+
+ * a read-beyond-end-of-buffer bug in configuration file updater was fixed.
+
+ * git-grep used to show the same hit repeatedly for unmerged paths.
+
+ * After amending the patch title in "git-am -i", the command did not
+ report the patch it applied with the updated title.
+
diff --git a/Documentation/RelNotes-1.5.4.1.txt b/Documentation/RelNotes-1.5.4.1.txt
new file mode 100644
index 0000000000..d4e44b8b09
--- /dev/null
+++ b/Documentation/RelNotes-1.5.4.1.txt
@@ -0,0 +1,17 @@
+GIT v1.5.4.1 Release Notes
+==========================
+
+Fixes since v1.5.4
+------------------
+
+ * "git-commit -C $tag" used to work but rewrite in C done in
+ 1.5.4 broke it.
+
+ * An entry in the .gitattributes file that names a pattern in a
+ subdirectory of the directory it is in did not match
+ correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should
+ match "a/b/foo.c" but it didn't).
+
+ * Customized color specification was parsed incorrectly when
+ numeric color values are used. This was fixed in 1.5.4.1.
+
diff --git a/Documentation/RelNotes-1.5.4.2.txt b/Documentation/RelNotes-1.5.4.2.txt
new file mode 100644
index 0000000000..21d0df59fb
--- /dev/null
+++ b/Documentation/RelNotes-1.5.4.2.txt
@@ -0,0 +1,43 @@
+GIT v1.5.4.2 Release Notes
+==========================
+
+Fixes since v1.5.4
+------------------
+
+ * The configuration parser was not prepared to see string
+ valued variables misspelled as boolean and segfaulted.
+
+ * Temporary files left behind due to interrupted object
+ transfers were not cleaned up with "git prune".
+
+ * "git config --unset" was confused when the unset variables
+ were spelled with continuation lines in the config file.
+
+ * The merge message detection in "git cvsimport" did not catch
+ a message that began with "Merge...".
+
+ * "git status" suggests "git rm --cached" for unstaging the
+ earlier "git add" before the initial commit.
+
+ * "git status" output was incorrect during a partial commit.
+
+ * "git bisect" refused to start when the HEAD was detached.
+
+ * "git bisect" allowed a wildcard character in the commit
+ message expanded while writing its log file.
+
+ * Manual pages were not formatted correctly with docbook xsl
+ 1.72; added a workaround.
+
+ * "git-commit -C $tag" used to work but rewrite in C done in
+ 1.5.4 broke it. This was fixed in 1.5.4.1.
+
+ * An entry in the .gitattributes file that names a pattern in a
+ subdirectory of the directory it is in did not match
+ correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should
+ match "a/b/foo.c" but it didn't). This was fixed in 1.5.4.1.
+
+ * Customized color specification was parsed incorrectly when
+ numeric color values are used. This was fixed in 1.5.4.1.
+
+ * http transport misbehaved when linked with curl-gnutls.
diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt
index ceee857232..f1323b6174 100644
--- a/Documentation/RelNotes-1.5.4.txt
+++ b/Documentation/RelNotes-1.5.4.txt
@@ -1,35 +1,377 @@
GIT v1.5.4 Release Notes
========================
+Removal
+-------
+
+ * "git svnimport" was removed in favor of "git svn". It is still there
+ in the source tree (contrib/examples) but unsupported.
+
+ * As git-commit and git-status have been rewritten, "git runstatus"
+ helper script lost all its users and has been removed.
+
+
+Temporarily disabled
+--------------------
+
+ * "git http-push" is known not to work well with cURL library older
+ than 7.16, and we had reports of repository corruption. It is
+ disabled on such platforms for now. Unfortunately, 1.5.3.8 shares
+ the same issue. In other words, this does not mean you will be
+ fine if you stick to an older git release. For now, please do not
+ use http-push from older git with cURL older than 7.16 if you
+ value your data. A proper fix will hopefully materialize in
+ later versions.
+
+
+Deprecation notices
+-------------------
+
+ * From v1.6.0, git will by default install dashed form of commands
+ (e.g. "git-commit") outside of users' normal $PATH, and will install
+ only selected commands ("git" itself, and "gitk") in $PATH. This
+ implies:
+
+ - Using dashed forms of git commands (e.g. "git-commit") from the
+ command line has been informally deprecated since early 2006, but
+ now it officially is, and will be removed in the future. Use
+ dash-less forms (e.g. "git commit") instead.
+
+ - Using dashed forms from your scripts, without first prepending the
+ return value from "git --exec-path" to the scripts' PATH, has been
+ informally deprecated since early 2006, but now it officially is.
+
+ - Use of dashed forms with "PATH=$(git --exec-path):$PATH; export
+ PATH" early in your script is not deprecated with this change.
+
+ Users are strongly encouraged to adjust their habits and scripts now
+ to prepare for this change.
+
+ * The post-receive hook was introduced in March 2007 to supersede
+ the post-update hook, primarily to overcome the command line length
+ limitation of the latter. Use of post-update hook will be deprecated
+ in future versions of git, starting from v1.6.0.
+
+ * "git lost-found" was deprecated in favor of "git fsck"'s --lost-found
+ option, and will be removed in the future.
+
+ * "git peek-remote" is deprecated, as "git ls-remote" was written in C
+ and works for all transports; "git peek-remote" will be removed in
+ the future.
+
+ * "git repo-config" which was an old name for "git config" command
+ has been supported without being advertised for a long time. The
+ next feature release will remove it.
+
+ * From v1.6.0, the repack.usedeltabaseoffset config option will default
+ to true, which will give denser packfiles (i.e. more efficient storage).
+ The downside is that git older than version 1.4.4 will not be able
+ to directly use a repository packed using this setting.
+
+ * From v1.6.0, the pack.indexversion config option will default to 2,
+ which is slightly more efficient, and makes repacking more immune to
+ data corruptions. Git older than version 1.5.2 may revert to version 1
+ of the pack index with a manual "git index-pack" to be able to directly
+ access corresponding pack files.
+
+
Updates since v1.5.3
--------------------
- * git-reset is now built-in.
+ * Comes with much improved gitk, with i18n.
- * git-send-email can optionally talk over ssmtp and use SMTP-AUTH.
+ * Comes with git-gui 0.9.2 with i18n.
- * git-rebase learned --whitespace option.
+ * gitk is now merged as a subdirectory of git.git project, in
+ preparation for its i18n.
- * git-remote knows --mirror mode.
+ * progress displays from many commands are a lot nicer to the eye.
+ Transfer commands show throughput data.
- * git-merge can call the "post-merge" hook.
+ * many commands that pay attention to per-directory .gitignore now do
+ so lazily, which makes the usual case go much faster.
- * git-pack-objects can optionally run deltification with multiple threads.
+ * Output processing for '--pretty=format:<user format>' has been
+ optimized.
- * git-archive can optionally substitute keywords in files marked with
- export-subst attribute.
+ * Rename detection of diff family while detecting exact matches has
+ been greatly optimized.
+
+ * Rename detection of diff family tries to make more natural looking
+ pairing. Earlier, if multiple identical rename sources were
+ found in the preimage, the source used was picked pretty much at random.
+
+ * Value "true" for color.diff and color.status configuration used to
+ mean "always" (even when the output is not going to a terminal).
+ This has been corrected to mean the same thing as "auto".
+
+ * "git diff" Porcelain now respects diff.external configuration, which
+ is another way to specify GIT_EXTERNAL_DIFF.
+
+ * "git diff" can be told to use different prefixes other than
+ "a/" and "b/" e.g. "git diff --src-prefix=l/ --dst-prefix=k/".
+
+ * "git diff" sometimes did not quote paths with funny
+ characters properly.
+
+ * "git log" (and any revision traversal commands) misbehaved
+ when --diff-filter is given but was not asked to actually
+ produce diff.
+
+ * HTTP proxy can be specified per remote repository using
+ remote.*.httpproxy configuration, or global http.proxy configuration
+ variable.
* Various Perforce importer updates.
+ * Example update and post-receive hooks have been improved.
+
+ * Any command that wants to take a commit object name can now use
+ ":/string" syntax to name a commit.
+
+ * "git reset" is now built-in and its output can be squelched with -q.
+
+ * "git reset --hard" does not make any sense in a bare
+ repository, but did not error out; fixed.
+
+ * "git send-email" can optionally talk over ssmtp and use SMTP-AUTH.
+
+ * "git rebase" learned --whitespace option.
+
+ * In "git rebase", when you decide not to replay a particular change
+ after the command stopped with a conflict, you can say "git rebase
+ --skip" without first running "git reset --hard", as the command now
+ runs it for you.
+
+ * "git rebase --interactive" mode can now work on detached HEAD.
+
+ * Other minor to serious bugs in "git rebase -i" have been fixed.
+
+ * "git rebase" now detaches head during its operation, so after a
+ successful "git rebase" operation, the reflog entry branch@{1} for
+ the current branch points at the commit before the rebase was
+ started.
+
+ * "git rebase -i" also triggers rerere to help your repeated merges.
+
+ * "git merge" can call the "post-merge" hook.
+
+ * "git pack-objects" can optionally run deltification with multiple
+ threads.
+
+ * "git archive" can optionally substitute keywords in files marked with
+ export-subst attribute.
+
+ * "git cherry-pick" made a misguided attempt to repeat the original
+ command line in the generated log message, when told to cherry-pick a
+ commit by naming a tag that points at it. It does not anymore.
+
+ * "git for-each-ref" learned %(xxxdate:<date-format>) syntax to show the
+ various date fields in different formats.
+
+ * "git gc --auto" is a low-impact way to automatically run a variant of
+ "git repack" that does not lose unreferenced objects (read: safer
+ than the usual one) after the user accumulates too many loose
+ objects.
+
+ * "git clean" has been rewritten in C.
+
+ * You need to explicitly set clean.requireForce to "false" to allow
+ "git clean" without -f to do any damage (lack of the configuration
+ variable used to mean "do not require -f option to lose untracked
+ files", but we now use the safer default).
+
+ * The kinds of whitespace errors "git diff" and "git apply" notice (and
+ fix) can be controlled via 'core.whitespace' configuration variable
+ and 'whitespace' attribute in .gitattributes file.
+
+ * "git push" learned --dry-run option to show what would happen if a
+ push is run.
+
+ * "git push" does not update a tracking ref on the local side when the
+ remote refused to update the corresponding ref.
+
+ * "git push" learned --mirror option. This is to push the local refs
+ one-to-one to the remote, and deletes refs from the remote that do
+ not exist anymore in the repository on the pushing side.
+
+ * "git push" can remove a corrupt ref at the remote site with the usual
+ ":ref" refspec.
+
+ * "git remote" knows --mirror mode. This is to set up configuration to
+ push into a remote repository to store local branch heads to the same
+ branch on the remote side, and remove branch heads locally removed
+ from local repository at the same time. Suitable for pushing into a
+ back-up repository.
+
+ * "git remote" learned "rm" subcommand.
+
+ * "git cvsserver" can be run via "git shell". Also, "cvs" is
+ recognized as a synonym for "git cvsserver", so that CVS users
+ can be switched to git just by changing their login shell.
+
+ * "git cvsserver" acts more like receive-pack by running post-receive
+ and post-update hooks.
+
+ * "git am" and "git rebase" are far less verbose.
+
+ * "git pull" learned to pass --[no-]ff option to underlying "git
+ merge".
+
+ * "git pull --rebase" is a different way to integrate what you fetched
+ into your current branch.
+
+ * "git fast-export" produces data-stream that can be fed to fast-import
+ to reproduce the history recorded in a git repository.
+
+ * "git add -i" takes pathspecs to limit the set of files to work on.
+
+ * "git add -p" is a short-hand to go directly to the selective patch
+ subcommand in the interactive command loop and to exit when done.
+
+ * "git add -i" UI has been colorized. The interactive prompt
+ and menu can be colored by setting color.interactive
+ configuration. The diff output (including the hunk picker)
+ are colored with color.diff configuration.
+
+ * "git commit --allow-empty" allows you to create a single-parent
+ commit that records the same tree as its parent, overriding the usual
+ safety valve.
+
+ * "git commit --amend" can amend a merge that does not change the tree
+ from its first parent.
+
+ * "git commit" used to unconditionally strip comment lines that
+ began with '#' and removed excess blank lines. This behavior has
+ been made configurable.
+
+ * "git commit" has been rewritten in C.
+
+ * "git stash random-text" does not create a new stash anymore. It was
+ a UI mistake. Use "git stash save random-text", or "git stash"
+ (without extra args) for that.
+
+ * "git stash clear extra-text" does not clear the whole stash
+ anymore. It is tempting to expect "git stash clear stash@{2}"
+ to drop only a single named stash entry, and it is rude to
+ discard everything when that is asked (but not provided).
+
+ * "git prune --expire <time>" can exempt young loose objects from
+ getting pruned.
+
+ * "git branch --contains <commit>" can list branches that are
+ descendants of a given commit.
+
+ * "git log" learned --early-output option to help interactive GUI
+ implementations.
+
+ * "git bisect" learned "skip" action to mark untestable commits.
+
+ * "git bisect visualize" learned a shorter synonym "git bisect view".
+
+ * "git bisect visualize" runs "git log" in a non-windowed
+ environments. It also can be told what command to run (e.g. "git
+ bisect visualize tig").
+
+ * "git format-patch" learned "format.numbered" configuration variable
+ to automatically turn --numbered option on when more than one commits
+ are formatted.
+
+ * "git ls-files" learned "--exclude-standard" to use the canned set of
+ exclude files.
+
+ * "git tag -a -f existing" begins the editor session using the existing
+ annotation message.
+
+ * "git tag -m one -m bar" (multiple -m options) behaves similarly to
+ "git commit"; the parameters to -m options are formatted as separate
+ paragraphs.
+
+ * The format "git show" outputs an annotated tag has been updated to
+ include "Tagger: " and "Date: " lines from the tag itself. Strictly
+ speaking this is a backward incompatible change, but this is a
+ reasonable usability fix and people's scripts shouldn't have been
+ relying on the exact output from "git show" Porcelain anyway.
+
+ * "git cvsimport" did not notice errors from underlying "cvsps"
+ and produced a corrupt import silently.
+
+ * "git cvsexportcommit" learned -w option to specify and switch to the
+ CVS working directory.
+
+ * "git checkout" from a subdirectory learned to use "../path" to allow
+ checking out a path outside the current directory without cd'ing up.
+
+ * "git checkout" from and to detached HEAD leaves a bit more
+ information in the reflog.
+
+ * "git send-email --dry-run" shows full headers for easier diagnosis.
+
+ * "git merge-ours" is now built-in.
+
+ * "git svn" learned "info" and "show-externals" subcommands.
+
+ * "git svn" run from a subdirectory failed to read settings from the
+ .git/config.
+
+ * "git svn" learned --use-log-author option, which picks up more
+ descriptive name from From: and Signed-off-by: lines in the commit
+ message.
+
+ * "git svn" wasted way too much disk to record revision mappings
+ between svn and git; a new representation that is much more compact
+ for this information has been introduced to correct this.
+
+ * "git svn" left temporary index files it used without cleaning them
+ up; this was corrected.
+
+ * "git status" from a subdirectory now shows relative paths, which
+ makes copy-and-pasting for git-checkout/git-add/git-rm easier. The
+ traditional behavior to show the full path relative to the top of
+ the work tree can be had by setting status.relativepaths
+ configuration variable to false.
+
+ * "git blame" kept text for each annotated revision in core needlessly;
+ this has been corrected.
+
+ * "git shortlog" learned to default to HEAD when the standard input is
+ a terminal and the user did not give any revision parameter.
+
+ * "git shortlog" learned "-e" option to show e-mail addresses as well as
+ authors' names.
+
+ * "git help" learned "-w" option to show documentation in browsers.
+
+ * In addition there are quite a few internal clean-ups. Notably:
+
+ - many fork/exec have been replaced with run-command API,
+ brought from the msysgit effort.
+
+ - introduction and more use of the option parser API.
+
+ - enhancement and more use of the strbuf API.
+
+ * Makefile tweaks to support HP-UX is in.
+
Fixes since v1.5.3
------------------
All of the fixes in v1.5.3 maintenance series are included in
this release, unless otherwise noted.
---
-exec >/var/tmp/1
-O=v1.5.3.2-99-ge4b2890
-echo O=`git describe refs/heads/master`
-git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
+These fixes are only in v1.5.4 and not backported to v1.5.3 maintenance
+series.
+
+ * The way "git diff --check" behaves is much more consistent with the way
+ "git apply --whitespace=warn" works.
+
+ * "git svn" talking with the SVN over HTTP will correctly quote branch
+ and project names.
+
+ * "git config" did not work correctly on platforms that define
+ REG_NOMATCH to an even number.
+
+ * Recent versions of AsciiDoc 8 has a change to break our
+ documentation; a workaround has been implemented.
+ * "git diff --color-words" colored context lines in a wrong color.
diff --git a/Documentation/RelNotes-1.5.5.txt b/Documentation/RelNotes-1.5.5.txt
new file mode 100644
index 0000000000..c8b4f72c23
--- /dev/null
+++ b/Documentation/RelNotes-1.5.5.txt
@@ -0,0 +1,78 @@
+GIT v1.5.5 Release Notes
+========================
+
+Updates since v1.5.4
+--------------------
+
+(performance)
+
+ * On platforms with suboptimal qsort(3) implementation, there
+ is an option to use more reasonable substitute we ship with
+ our software.
+
+ * New configuration variable "pack.packsizelimit" can be used
+ in place of command line option --max-pack-size.
+
+ * "git fetch" over the native git protocol used to make a
+ connection to find out the set of current remote refs and
+ another to actually download the pack data. We now use only
+ one connection for these tasks.
+
+ * "git commit" does not run lstat(2) more than necessary
+ anymore.
+
+(usability, bells and whistles)
+
+ * You can be warned when core.autocrlf conversion is applied in
+ such a way that results in an irreversible conversion.
+
+ * A pattern "foo/" in .gitignore file now matches a directory
+ "foo". Pattern "foo" also matches as before.
+
+ * "git describe" learned to limit the tags to be used for
+ naming with --match option.
+
+ * "git describe --contains" now barfs when the named commit
+ cannot be described.
+
+ * bash completion's prompt helper function can talk about
+ operation in-progress (e.g. merge, rebase, etc.).
+
+ * "git commit" learned a new hook "prepare-commit-msg" that can
+ inspect what is going to be committed and prepare the commit
+ log message template to be edited.
+
+ * "git gui" learned an auto-spell checking.
+
+ * "git send-email" learned to prompt for passwords
+ interactively.
+
+ * "git send-email" learned an easier way to suppress CC
+ recipients.
+
+ * Various "git cvsimport", "git cvsexportcommit", "git svn" and
+ "git p4" improvements.
+
+(internal)
+
+ * Duplicated code between git-help and git-instaweb that
+ launches user's preferred browser has been refactored.
+
+ * It is now easier to write test scripts that records known
+ breakages.
+
+
+Fixes since v1.5.4
+------------------
+
+All of the fixes in v1.5.4 maintenance series are included in
+this release, unless otherwise noted.
+
+
+---
+exec >/var/tmp/1
+O=v1.5.4
+O=v1.5.4.2-122-g7cb97da
+echo O=`git describe refs/heads/master`
+git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
+
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 61635bf04d..0e155c936c 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -10,7 +10,7 @@ Checklist (and a short version for the impatient):
- the first line of the commit message should be a short
description and should skip the full stop
- if you want your work included in git.git, add a
- "Signed-off-by: Your Name <your@email.com>" line to the
+ "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
@@ -20,9 +20,6 @@ Checklist (and a short version for the impatient):
Patch:
- use "git format-patch -M" to create the patch
- - send your patch to <git@vger.kernel.org>. If you use
- git-send-email(1), please test it first by sending
- email to yourself.
- do not PGP sign your patch
- do not attach your patch, but read in the mail
body, unless you cannot teach your mailer to
@@ -31,13 +28,15 @@ Checklist (and a short version for the impatient):
corrupt whitespaces.
- provide additional information (which is unsuitable for
the commit message) between the "---" and the diffstat
- - send the patch to the list (git@vger.kernel.org) and the
- maintainer (gitster@pobox.com).
- if you change, add, or remove a command line option or
make some other user interface change, the associated
documentation should be updated as well.
- if your name is not writable in ASCII, make sure that
you send off a message in the correct encoding.
+ - send the patch to the list (git@vger.kernel.org) and the
+ maintainer (gitster@pobox.com) if (and only if) the patch
+ is ready for inclusion. If you use git-send-email(1),
+ please test it first by sending email to yourself.
Long version:
@@ -113,7 +112,12 @@ lose tabs that way if you are not careful.
It is a common convention to prefix your subject line with
[PATCH]. This lets people easily distinguish patches from other
-e-mail discussions.
+e-mail discussions. Use of additional markers after PATCH and
+the closing bracket to mark the nature of the patch is also
+encouraged. E.g. [PATCH/RFC] is often used when the patch is
+not ready to be applied but it is for discussion, [PATCH v2],
+[PATCH v3] etc. are often seen when you are sending an update to
+what you have previously sent.
"git format-patch" command follows the best current practice to
format the body of an e-mail message. At the beginning of the
@@ -158,7 +162,8 @@ Note that your maintainer does not necessarily read everything
on the git mailing list. If your patch is for discussion first,
send it "To:" the mailing list, and optionally "cc:" him. If it
is trivially correct or after the list reached a consensus, send
-it "To:" the maintainer and optionally "cc:" the list.
+it "To:" the maintainer and optionally "cc:" the list for
+inclusion.
Also note that your maintainer does not actively involve himself in
maintaining what are in contrib/ hierarchy. When you send fixes and
@@ -211,10 +216,53 @@ then you just add a line saying
This line can be automatically added by git if you run the git-commit
command with the -s option.
-Some people also put extra tags at the end. They'll just be ignored for
-now, but you can do this to mark internal company procedures or just
-point out some special detail about the sign-off.
+Notice that you can place your own Signed-off-by: line when
+forwarding somebody else's patch with the above rules for
+D-C-O. Indeed you are encouraged to do so. Do not forget to
+place an in-body "From: " line at the beginning to properly attribute
+the change to its true author (see (2) above).
+
+Some people also 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.
+
+------------------------------------------------
+An ideal patch flow
+
+Here is an ideal patch flow for this project the current maintainer
+suggests to the contributors:
+
+ (0) You come up with an itch. You code it up.
+
+ (1) Send it to the list and cc people who may need to know about
+ the change.
+
+ The people who may need to know are the ones whose code you
+ are butchering. These people happen to be the ones who are
+ most likely to be knowledgeable enough to help you, but
+ they have no obligation to help you (i.e. you ask for help,
+ don't demand). "git log -p -- $area_you_are_modifying" would
+ help you find out who they are.
+
+ (2) You get comments and suggestions for improvements. You may
+ even get them in a "on top of your change" patch form.
+
+ (3) Polish, refine, and re-send to the list and the people who
+ spend their time to improve your patch. Go back to step (2).
+
+ (4) The list forms consensus that the last round of your patch is
+ good. Send it to the list and cc the maintainer.
+
+ (5) A topic branch is created with the patch and is merged to 'next',
+ and cooked further and eventually graduates to 'master'.
+In any time between the (2)-(3) cycle, the maintainer may pick it up
+from the list and queue it to 'pu', in order to make it easier for
+people play with it without having to pick up and apply the patch to
+their trees themselves.
------------------------------------------------
MUA specific hints
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
index af5b1558a6..10c1a151a4 100644
--- a/Documentation/asciidoc.conf
+++ b/Documentation/asciidoc.conf
@@ -1,6 +1,6 @@
-## gitlink: macro
+## linkgit: macro
#
-# Usage: gitlink:command[manpage-section]
+# Usage: linkgit:command[manpage-section]
#
# Note, {0} is the manpage section, while {target} is the command.
#
@@ -15,7 +15,7 @@ endsb=&#93;
tilde=&#126;
ifdef::backend-docbook[]
-[gitlink-inlinemacro]
+[linkgit-inlinemacro]
{0%{target}}
{0#<citerefentry>}
{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>}
@@ -23,7 +23,9 @@ ifdef::backend-docbook[]
endif::backend-docbook[]
ifdef::backend-docbook[]
+ifndef::docbook-xsl-172[]
# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
+# v1.72 breaks with this because it replaces dots not in roff requests.
[listingblock]
<example><title>{title}</title>
<literallayout>
@@ -36,6 +38,7 @@ ifdef::doctype-manpage[]
endif::doctype-manpage[]
</literallayout>
{title#}</example>
+endif::docbook-xsl-172[]
endif::backend-docbook[]
ifdef::doctype-manpage[]
@@ -58,6 +61,6 @@ endif::backend-docbook[]
endif::doctype-manpage[]
ifdef::backend-xhtml11[]
-[gitlink-inlinemacro]
+[linkgit-inlinemacro]
<a href="{target}.html">{target}{0?({0})}</a>
endif::backend-xhtml11[]
diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index 17379f0576..c11bb7d36c 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -39,7 +39,7 @@ of lines before or after the line given by <start>.
Show raw timestamp (Default: off).
-S <revs-file>::
- Use revs from revs-file instead of calling gitlink:git-rev-list[1].
+ Use revs from revs-file instead of calling linkgit:git-rev-list[1].
-p, --porcelain::
Show in a format designed for machine consumption.
@@ -52,7 +52,7 @@ of lines before or after the line given by <start>.
When <rev> is not specified, the command annotates the
changes starting backwards from the working tree copy.
This flag makes the command pretend as if the working
- tree copy has the contents of he named file (specify
+ tree copy has the contents of the named file (specify
`-` to make the command read from the standard input).
-M|<num>|::
diff --git a/Documentation/cat-texi.perl b/Documentation/cat-texi.perl
new file mode 100755
index 0000000000..e3d8e9faa8
--- /dev/null
+++ b/Documentation/cat-texi.perl
@@ -0,0 +1,38 @@
+#!/usr/bin/perl -w
+
+my @menu = ();
+my $output = $ARGV[0];
+
+open TMP, '>', "$output.tmp";
+
+while (<STDIN>) {
+ next if (/^\\input texinfo/../\@node Top/);
+ next if (/^\@bye/ || /^\.ft/);
+ if (s/^\@top (.*)/\@node $1,,,Top/) {
+ push @menu, $1;
+ }
+ s/\(\@pxref{\[URLS\]}\)//;
+ print TMP;
+}
+close TMP;
+
+printf '\input texinfo
+@setfilename gitman.info
+@documentencoding us-ascii
+@node Top,,%s
+@top Git Manual Pages
+@documentlanguage en
+@menu
+', $menu[0];
+
+for (@menu) {
+ print "* ${_}::\n";
+}
+print "\@end menu\n";
+open TMP, '<', "$output.tmp";
+while (<TMP>) {
+ print;
+}
+close TMP;
+print "\@bye\n";
+unlink "$output.tmp";
diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
index 1061fd8bcd..04f99778d8 100755
--- a/Documentation/cmd-list.perl
+++ b/Documentation/cmd-list.perl
@@ -3,7 +3,8 @@
use File::Compare qw(compare);
sub format_one {
- my ($out, $name) = @_;
+ my ($out, $nameattr) = @_;
+ my ($name, $attr) = @$nameattr;
my ($state, $description);
$state = 0;
open I, '<', "$name.txt" or die "No such file $name.txt";
@@ -26,8 +27,11 @@ sub format_one {
die "No description found in $name.txt";
}
if (my ($verify_name, $text) = ($description =~ /^($name) - (.*)/)) {
- print $out "gitlink:$name\[1\]::\n";
- print $out "\t$text.\n\n";
+ print $out "linkgit:$name\[1\]::\n\t";
+ if ($attr =~ / deprecated /) {
+ print $out "(deprecated) ";
+ }
+ print $out "$text.\n\n";
}
else {
die "Description does not match $name: $description";
@@ -35,12 +39,13 @@ sub format_one {
}
my %cmds = ();
-while (<DATA>) {
+for (sort <>) {
next if /^#/;
chomp;
- my ($name, $cat) = /^(\S+)\s+(.*)$/;
- push @{$cmds{$cat}}, $name;
+ my ($name, $cat, $attr) = /^(\S+)\s+(.*?)(?:\s+(.*))?$/;
+ $attr = '' unless defined $attr;
+ push @{$cmds{$cat}}, [$name, " $attr "];
}
for my $cat (qw(ancillaryinterrogators
@@ -67,137 +72,3 @@ for my $cat (qw(ancillaryinterrogators
rename "$out+", "$out";
}
}
-
-# The following list is sorted with "sort -d" to make it easier
-# to find entry in the resulting git.html manual page.
-__DATA__
-git-add mainporcelain
-git-am mainporcelain
-git-annotate ancillaryinterrogators
-git-apply plumbingmanipulators
-git-archimport foreignscminterface
-git-archive mainporcelain
-git-bisect mainporcelain
-git-blame ancillaryinterrogators
-git-branch mainporcelain
-git-bundle mainporcelain
-git-cat-file plumbinginterrogators
-git-check-attr purehelpers
-git-checkout mainporcelain
-git-checkout-index plumbingmanipulators
-git-check-ref-format purehelpers
-git-cherry ancillaryinterrogators
-git-cherry-pick mainporcelain
-git-citool mainporcelain
-git-clean mainporcelain
-git-clone mainporcelain
-git-commit mainporcelain
-git-commit-tree plumbingmanipulators
-git-config ancillarymanipulators
-git-count-objects ancillaryinterrogators
-git-cvsexportcommit foreignscminterface
-git-cvsimport foreignscminterface
-git-cvsserver foreignscminterface
-git-daemon synchingrepositories
-git-describe mainporcelain
-git-diff mainporcelain
-git-diff-files plumbinginterrogators
-git-diff-index plumbinginterrogators
-git-diff-tree plumbinginterrogators
-git-fast-import ancillarymanipulators
-git-fetch mainporcelain
-git-fetch-pack synchingrepositories
-git-filter-branch ancillarymanipulators
-git-fmt-merge-msg purehelpers
-git-for-each-ref plumbinginterrogators
-git-format-patch mainporcelain
-git-fsck ancillaryinterrogators
-git-gc mainporcelain
-git-get-tar-commit-id ancillaryinterrogators
-git-grep mainporcelain
-git-gui mainporcelain
-git-hash-object plumbingmanipulators
-git-http-fetch synchelpers
-git-http-push synchelpers
-git-imap-send foreignscminterface
-git-index-pack plumbingmanipulators
-git-init mainporcelain
-git-instaweb ancillaryinterrogators
-gitk mainporcelain
-git-local-fetch synchingrepositories
-git-log mainporcelain
-git-lost-found ancillarymanipulators
-git-ls-files plumbinginterrogators
-git-ls-remote plumbinginterrogators
-git-ls-tree plumbinginterrogators
-git-mailinfo purehelpers
-git-mailsplit purehelpers
-git-merge mainporcelain
-git-merge-base plumbinginterrogators
-git-merge-file plumbingmanipulators
-git-merge-index plumbingmanipulators
-git-merge-one-file purehelpers
-git-mergetool ancillarymanipulators
-git-merge-tree ancillaryinterrogators
-git-mktag plumbingmanipulators
-git-mktree plumbingmanipulators
-git-mv mainporcelain
-git-name-rev plumbinginterrogators
-git-pack-objects plumbingmanipulators
-git-pack-redundant plumbinginterrogators
-git-pack-refs ancillarymanipulators
-git-parse-remote synchelpers
-git-patch-id purehelpers
-git-peek-remote purehelpers
-git-prune ancillarymanipulators
-git-prune-packed plumbingmanipulators
-git-pull mainporcelain
-git-push mainporcelain
-git-quiltimport foreignscminterface
-git-read-tree plumbingmanipulators
-git-rebase mainporcelain
-git-receive-pack synchelpers
-git-reflog ancillarymanipulators
-git-relink ancillarymanipulators
-git-remote ancillarymanipulators
-git-repack ancillarymanipulators
-git-request-pull foreignscminterface
-git-rerere ancillaryinterrogators
-git-reset mainporcelain
-git-revert mainporcelain
-git-rev-list plumbinginterrogators
-git-rev-parse ancillaryinterrogators
-git-rm mainporcelain
-git-runstatus ancillaryinterrogators
-git-send-email foreignscminterface
-git-send-pack synchingrepositories
-git-shell synchelpers
-git-shortlog mainporcelain
-git-show mainporcelain
-git-show-branch ancillaryinterrogators
-git-show-index plumbinginterrogators
-git-show-ref plumbinginterrogators
-git-sh-setup purehelpers
-git-ssh-fetch synchingrepositories
-git-ssh-upload synchingrepositories
-git-stash mainporcelain
-git-status mainporcelain
-git-stripspace purehelpers
-git-submodule mainporcelain
-git-svn foreignscminterface
-git-svnimport foreignscminterface
-git-symbolic-ref plumbingmanipulators
-git-tag mainporcelain
-git-tar-tree plumbinginterrogators
-git-unpack-file plumbinginterrogators
-git-unpack-objects plumbingmanipulators
-git-update-index plumbingmanipulators
-git-update-ref plumbingmanipulators
-git-update-server-info synchingrepositories
-git-upload-archive synchelpers
-git-upload-pack synchelpers
-git-var plumbinginterrogators
-git-verify-pack plumbinginterrogators
-git-verify-tag ancillaryinterrogators
-git-whatchanged ancillaryinterrogators
-git-write-tree plumbingmanipulators
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d4a476e2ff..7b676710ba 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -115,7 +115,7 @@ porcelain configuration variables in the respective porcelain documentation.
core.fileMode::
If false, the executable bit differences between the index and
the working copy are ignored; useful on broken filesystems like FAT.
- See gitlink:git-update-index[1]. True by default.
+ See linkgit:git-update-index[1]. True by default.
core.quotepath::
The commands that output paths (e.g. `ls-files`,
@@ -139,10 +139,55 @@ core.autocrlf::
"text" (i.e. be subjected to the autocrlf mechanism) is
decided purely based on the contents.
+core.safecrlf::
+ If true, makes git check if converting `CRLF` as controlled by
+ `core.autocrlf` is reversible. Git will verify if a command
+ modifies a file in the work tree either directly or indirectly.
+ For example, committing a file followed by checking out the
+ same file should yield the original file in the work tree. If
+ this is not the case for the current setting of
+ `core.autocrlf`, git will reject the file. The variable can
+ be set to "warn", in which case git will only warn about an
+ irreversible conversion but continue the operation.
++
+CRLF conversion bears a slight chance of corrupting data.
+autocrlf=true will convert CRLF to LF during commit and LF to
+CRLF during checkout. A file that contains a mixture of LF and
+CRLF before the commit cannot be recreated by git. For text
+files this is the right thing to do: it corrects line endings
+such that we have only LF line endings in the repository.
+But for binary files that are accidentally classified as text the
+conversion can corrupt data.
++
+If you recognize such corruption early you can easily fix it by
+setting the conversion type explicitly in .gitattributes. Right
+after committing you still have the original file in your work
+tree and this file is not yet corrupted. You can explicitly tell
+git that this file is binary and git will handle the file
+appropriately.
++
+Unfortunately, the desired effect of cleaning up text files with
+mixed line endings and the undesired effect of corrupting binary
+files cannot be distinguished. In both cases CRLFs are removed
+in an irreversible way. For text files this is the right thing
+to do because CRLFs are line endings, while for binary files
+converting CRLFs corrupts data.
++
+Note, this safety check does not mean that a checkout will generate a
+file identical to the original file for a different setting of
+`core.autocrlf`, but only for the current one. For example, a text
+file with `LF` would be accepted with `core.autocrlf=input` and could
+later be checked out with `core.autocrlf=true`, in which case the
+resulting file would contain `CRLF`, although the original file
+contained `LF`. However, in both work trees the line endings would be
+consistent, that is either all `LF` or all `CRLF`, but never mixed. A
+file with mixed line endings would be reported by the `core.safecrlf`
+mechanism.
+
core.symlinks::
If false, symbolic links are checked out as small plain files that
- contain the link text. gitlink:git-update-index[1] and
- gitlink:git-add[1] will not change the recorded type to regular
+ contain the link text. linkgit:git-update-index[1] and
+ linkgit:git-add[1] will not change the recorded type to regular
file. Useful on filesystems like FAT that do not support
symbolic links. True by default.
@@ -163,7 +208,7 @@ core.ignoreStat::
The working copy files are assumed to stay unchanged until you
mark them otherwise manually - Git will not detect the file changes
by lstat() calls. This is useful on systems where those are very
- slow, such as Microsoft Windows. See gitlink:git-update-index[1].
+ slow, such as Microsoft Windows. See linkgit:git-update-index[1].
False by default.
core.preferSymlinkRefs::
@@ -176,10 +221,10 @@ core.bare::
If true this repository is assumed to be 'bare' and has no
working directory associated with it. If this is the case a
number of commands that require a working directory will be
- disabled, such as gitlink:git-add[1] or gitlink:git-merge[1].
+ disabled, such as linkgit:git-add[1] or linkgit:git-merge[1].
+
-This setting is automatically guessed by gitlink:git-clone[1] or
-gitlink:git-init[1] when the repository was created. By default a
+This setting is automatically guessed by linkgit:git-clone[1] or
+linkgit:git-init[1] when the repository was created. By default a
repository that ends in "/.git" is assumed to be not bare (bare =
false), while all other repositories are assumed to be bare (bare
= true).
@@ -216,7 +261,7 @@ core.sharedRepository::
group-writable). When 'all' (or 'world' or 'everybody'), the
repository will be readable by all users, additionally to being
group-shareable. When 'umask' (or 'false'), git will use permissions
- reported by umask(2). See gitlink:git-init[1]. False by default.
+ reported by umask(2). See linkgit:git-init[1]. False by default.
core.warnAmbiguousRefs::
If true, git will warn you if the ref name you passed it is ambiguous
@@ -226,13 +271,15 @@ core.compression::
An integer -1..9, indicating a default compression level.
-1 is the zlib default. 0 means no compression,
and 1..9 are various speed/size tradeoffs, 9 being slowest.
+ If set, this provides a default to other compression variables,
+ such as 'core.loosecompression' and 'pack.compression'.
core.loosecompression::
An integer -1..9, indicating the compression level for objects that
are not in a pack file. -1 is the zlib default. 0 means no
compression, and 1..9 are various speed/size tradeoffs, 9 being
slowest. If not set, defaults to core.compression. If that is
- not set, defaults to 0 (best speed).
+ not set, defaults to 1 (best speed).
core.packedGitWindowSize::
Number of bytes of a pack file to map into memory in a
@@ -279,7 +326,7 @@ core.excludesfile::
In addition to '.gitignore' (per-directory) and
'.git/info/exclude', git looks into this file for patterns
of files which are not meant to be tracked. See
- gitlink:gitignore[5].
+ linkgit:gitignore[5].
core.editor::
Commands such as `commit` and `tag` that lets you edit
@@ -293,8 +340,22 @@ core.pager::
The command that git will use to paginate output. Can be overridden
with the `GIT_PAGER` environment variable.
+core.whitespace::
+ A comma separated list of common whitespace problems to
+ notice. `git diff` will use `color.diff.whitespace` to
+ highlight them, and `git apply --whitespace=error` will
+ consider them as errors:
++
+* `trailing-space` treats trailing whitespaces at the end of the line
+ as an error (enabled by default).
+* `space-before-tab` treats a space character that appears immediately
+ before a tab character in the initial indent part of the line as an
+ error (enabled by default).
+* `indent-with-non-tab` treats a line that is indented with 8 or more
+ space characters as an error (not enabled by default).
+
alias.*::
- Command aliases for the gitlink:git[1] command wrapper - e.g.
+ Command aliases for the linkgit:git[1] command wrapper - e.g.
after defining "alias.last = cat-file commit HEAD", the invocation
"git last" is equivalent to "git cat-file commit HEAD". To avoid
confusion and troubles with script usage, aliases that
@@ -310,24 +371,25 @@ it will be treated as a shell command. For example, defining
apply.whitespace::
Tells `git-apply` how to handle whitespaces, in the same way
- as the '--whitespace' option. See gitlink:git-apply[1].
+ as the '--whitespace' option. See linkgit:git-apply[1].
branch.autosetupmerge::
Tells `git-branch` and `git-checkout` to setup new branches
- so that gitlink:git-pull[1] will appropriately merge from that
+ so that linkgit:git-pull[1] will appropriately merge from that
remote branch. Note that even if this option is not set,
this behavior can be chosen per-branch using the `--track`
- and `--no-track` options. This option defaults to false.
+ and `--no-track` options. This option defaults to true.
branch.<name>.remote::
When in branch <name>, it tells `git fetch` which remote to fetch.
If this option is not given, `git fetch` defaults to remote "origin".
branch.<name>.merge::
- When in branch <name>, it tells `git fetch` the default refspec to
- be marked for merging in FETCH_HEAD. The value has exactly to match
- a remote part of one of the refspecs which are fetched from the remote
- given by "branch.<name>.remote".
+ When in branch <name>, it tells `git fetch` the default
+ refspec to be marked for merging in FETCH_HEAD. The value is
+ handled like the remote part of a refspec, and must match a
+ ref which is fetched from the remote given by
+ "branch.<name>.remote".
The merge information is used by `git pull` (which at first calls
`git fetch`) to lookup the default branch for merging. Without
this option, `git pull` defaults to merge the first refspec fetched.
@@ -339,18 +401,30 @@ branch.<name>.merge::
branch.<name>.mergeoptions::
Sets default options for merging into branch <name>. The syntax and
- supported options are equal to that of gitlink:git-merge[1], but
+ supported options are equal to that of linkgit:git-merge[1], but
option values containing whitespace characters are currently not
supported.
+branch.<name>.rebase::
+ When true, rebase the branch <name> on top of the fetched branch,
+ instead of merging the default branch from the default remote.
+ *NOTE*: this is a possibly dangerous operation; do *not* use
+ it unless you understand the implications (see linkgit:git-rebase[1]
+ for details).
+
+browser.<tool>.path::
+ Override the path for the given tool that may be used to
+ browse HTML help (see '-w' option in linkgit:git-help[1]) or a
+ working repository in gitweb (see linkgit:git-instaweb[1]).
+
clean.requireForce::
- A boolean to make git-clean do nothing unless given -f or -n. Defaults
- to false.
+ A boolean to make git-clean do nothing unless given -f
+ or -n. Defaults to true.
color.branch::
A boolean to enable/disable color in the output of
- gitlink:git-branch[1]. May be set to `true` (or `always`),
- `false` (or `never`) or `auto`, in which case colors are used
+ linkgit:git-branch[1]. May be set to `always`,
+ `false` (or `never`) or `auto` (or `true`), in which case colors are used
only when the output is to a terminal. Defaults to false.
color.branch.<slot>::
@@ -368,17 +442,30 @@ second is the background. The position of the attribute, if any,
doesn't matter.
color.diff::
- When true (or `always`), always use colors in patch.
- When false (or `never`), never. When set to `auto`, use
- colors only when the output is to the terminal.
+ 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.
color.diff.<slot>::
Use customized color for diff colorization. `<slot>` specifies
which part of the patch to use the specified color, and is one
of `plain` (context text), `meta` (metainformation), `frag`
(hunk header), `old` (removed lines), `new` (added lines),
- `commit` (commit headers), or `whitespace` (highlighting dubious
- whitespace). The values of these variables may be specified as
+ `commit` (commit headers), or `whitespace` (highlighting
+ whitespace errors). The values of these variables may be specified as
+ in color.branch.<slot>.
+
+color.interactive::
+ When set to `always`, always use colors for interactive prompts
+ and displays (such as those used by "git add --interactive").
+ When false (or `never`), never. When set to `true` or `auto`, use
+ colors only when the output is to the terminal. Defaults to false.
+
+color.interactive.<slot>::
+ Use customized color for `git add --interactive`
+ output. `<slot>` may be `prompt`, `header`, or `help`, for
+ three distinct types of normal output from interactive
+ programs. The values of these variables may be specified as
in color.branch.<slot>.
color.pager::
@@ -387,8 +474,8 @@ color.pager::
color.status::
A boolean to enable/disable color in the output of
- gitlink:git-status[1]. May be set to `true` (or `always`),
- `false` (or `never`) or `auto`, in which case colors are used
+ linkgit:git-status[1]. May be set to `always`,
+ `false` (or `never`) or `auto` (or `true`), in which case colors are used
only when the output is to a terminal. Defaults to false.
color.status.<slot>::
@@ -402,6 +489,13 @@ color.status.<slot>::
commit.template::
Specify a file to use as the template for new commit messages.
+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.
+
diff.autorefreshindex::
When using `git diff` to compare with work tree
files, do not consider stat-only change as changed.
@@ -412,6 +506,13 @@ diff.autorefreshindex::
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. 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.renameLimit::
The number of files to consider when performing the copy/rename
detection; equivalent to the git diff option '-l'.
@@ -429,11 +530,18 @@ fetch.unpackLimit::
exceeds this limit then the received pack will be stored as
a pack, after adding any missing delta bases. Storing the
pack from a push can make the push operation complete faster,
- especially on slow filesystems.
+ especially on slow filesystems. If not set, the value of
+ `transfer.unpackLimit` is used instead.
+
+format.numbered::
+ A boolean which can enable sequence numbers in patch subjects.
+ Setting this option to "auto" will enable it only if there is
+ more than one patch. See --numbered option in
+ linkgit:git-format-patch[1].
format.headers::
Additional email headers to include in a patch to be submitted
- by mail. See gitlink:git-format-patch[1].
+ by mail. See linkgit:git-format-patch[1].
format.suffix::
The default for format-patch is to output files with the suffix
@@ -449,14 +557,14 @@ gc.auto::
When there are approximately more than this many loose
objects in the repository, `git gc --auto` will pack them.
Some Porcelain commands use this command to perform a
- light-weight garbage collection from time to time. Setting
- this to 0 disables it.
+ light-weight garbage collection from time to time. The
+ default value is 6700. Setting this to 0 disables it.
gc.autopacklimit::
When there are more than this many packs that are not
marked with `*.keep` file in the repository, `git gc
- --auto` consolidates them into one larger pack. Setting
- this to 0 disables this.
+ --auto` consolidates them into one larger pack. The
+ default value is 20. Setting this to 0 disables it.
gc.packrefs::
`git gc` does not run `git pack-refs` in a bare repository by
@@ -481,25 +589,27 @@ gc.reflogexpireunreachable::
gc.rerereresolved::
Records of conflicted merge you resolved earlier are
kept for this many days when `git rerere gc` is run.
- The default is 60 days. See gitlink:git-rerere[1].
+ The default is 60 days. See linkgit:git-rerere[1].
gc.rerereunresolved::
Records of conflicted merge you have not resolved are
kept for this many days when `git rerere gc` is run.
- The default is 15 days. See gitlink:git-rerere[1].
+ The default is 15 days. See linkgit:git-rerere[1].
rerere.enabled::
Activate recording of resolved conflicts, so that identical
conflict hunks can be resolved automatically, should they
- be encountered again. See gitlink:git-rerere[1].
+ be encountered again. linkgit:git-rerere[1] command is by
+ default enabled if you create `rr-cache` directory under
+ `$GIT_DIR`, but can be disabled by setting this option to false.
gitcvs.enabled::
Whether the CVS server interface is enabled for this repository.
- See gitlink:git-cvsserver[1].
+ See linkgit:git-cvsserver[1].
gitcvs.logfile::
Path to a log file where the CVS server interface well... logs
- various stuff. See gitlink:git-cvsserver[1].
+ various stuff. See linkgit:git-cvsserver[1].
gitcvs.allbinary::
If true, all files are sent to the client in mode '-kb'. This
@@ -512,7 +622,7 @@ gitcvs.dbname::
derived from the git repository. The exact meaning depends on the
used database driver, for SQLite (which is the default driver) this
is a filename. Supports variable substitution (see
- gitlink:git-cvsserver[1] for details). May not contain semicolons (`;`).
+ linkgit:git-cvsserver[1] for details). May not contain semicolons (`;`).
Default: '%Ggitcvs.%m.sqlite'
gitcvs.dbdriver::
@@ -521,19 +631,33 @@ gitcvs.dbdriver::
with 'DBD::SQLite', reported to work with 'DBD::Pg', and
reported *not* to work with 'DBD::mysql'. Experimental feature.
May not contain double colons (`:`). Default: 'SQLite'.
- See gitlink:git-cvsserver[1].
+ See linkgit:git-cvsserver[1].
gitcvs.dbuser, gitcvs.dbpass::
Database user and password. Only useful if setting 'gitcvs.dbdriver',
since SQLite has no concept of database users and/or passwords.
'gitcvs.dbuser' supports variable substitution (see
- gitlink:git-cvsserver[1] for details).
+ linkgit:git-cvsserver[1] for details).
All gitcvs variables except for 'gitcvs.allbinary' can also be
specified as 'gitcvs.<access_method>.<varname>' (where 'access_method'
is one of "ext" and "pserver") to make them apply only for the given
access method.
+help.browser::
+ Specify the browser that will be used to display help in the
+ 'web' format. See linkgit:git-help[1].
+
+help.format::
+ Override the default help format used by linkgit:git-help[1].
+ Values 'man', 'info', 'web' and 'html' are supported. 'man' is
+ the default. 'web' and 'html' are the same.
+
+http.proxy::
+ Override the HTTP proxy, normally configured using the 'http_proxy'
+ environment variable (see linkgit:curl[1]). This can be overridden
+ on a per-remote basis; see remote.<name>.proxy
+
http.sslVerify::
Whether to verify the SSL certificate when fetching or pushing
over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
@@ -580,16 +704,35 @@ i18n.commitEncoding::
does not care per se, but this information is necessary e.g. when
importing commits from emails or in the gitk graphical history
browser (and possibly at other places in the future or in other
- porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'.
+ porcelains). See e.g. linkgit:git-mailinfo[1]. Defaults to 'utf-8'.
i18n.logOutputEncoding::
Character encoding the commit messages are converted to when
running `git-log` and friends.
+instaweb.browser::
+ Specify the program that will be used to browse your working
+ repository in gitweb. See linkgit:git-instaweb[1].
+
+instaweb.httpd::
+ The HTTP daemon command-line to start gitweb on your working
+ repository. See linkgit:git-instaweb[1].
+
+instaweb.local::
+ If true the web server started by linkgit:git-instaweb[1] will
+ 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].
+
+instaweb.port::
+ The port number to bind the gitweb httpd to. See
+ linkgit:git-instaweb[1].
+
log.showroot::
If true, the initial commit will be shown as a big creation event.
This is equivalent to a diff against an empty tree.
- Tools like gitlink:git-log[1] or gitlink:git-whatchanged[1], which
+ Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
normally hide the root commit will now show it. True by default.
merge.summary::
@@ -598,7 +741,7 @@ merge.summary::
merge.tool::
Controls which merge resolution program is used by
- gitlink:git-mergetool[1]. Valid values are: "kdiff3", "tkdiff",
+ linkgit:git-mergetool[1]. Valid values are: "kdiff3", "tkdiff",
"meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and "opendiff".
merge.verbosity::
@@ -611,27 +754,31 @@ merge.verbosity::
merge.<driver>.name::
Defines a human readable name for a custom low-level
- merge driver. See gitlink:gitattributes[5] for details.
+ merge driver. See linkgit:gitattributes[5] for details.
merge.<driver>.driver::
Defines the command that implements a custom low-level
- merge driver. See gitlink:gitattributes[5] for details.
+ merge driver. See linkgit:gitattributes[5] for details.
merge.<driver>.recursive::
Names a low-level merge driver to be used when
performing an internal merge between common ancestors.
- See gitlink:gitattributes[5] for details.
+ See linkgit:gitattributes[5] for details.
+
+mergetool.<tool>.path::
+ Override the path for the given tool. This is useful in case
+ your tool is not in the PATH.
pack.window::
- The size of the window used by gitlink:git-pack-objects[1] when no
+ The size of the window used by linkgit:git-pack-objects[1] when no
window size is given on the command line. Defaults to 10.
pack.depth::
- The maximum delta depth used by gitlink:git-pack-objects[1] when no
+ The maximum delta depth used by linkgit:git-pack-objects[1] when no
maximum depth is given on the command line. Defaults to 50.
pack.windowMemory::
- The window memory size limit used by gitlink:git-pack-objects[1]
+ The window memory size limit used by linkgit:git-pack-objects[1]
when no limit is given on the command line. The value can be
suffixed with "k", "m", or "g". Defaults to 0, meaning no
limit.
@@ -641,25 +788,42 @@ pack.compression::
in a pack file. -1 is the zlib default. 0 means no
compression, and 1..9 are various speed/size tradeoffs, 9 being
slowest. If not set, defaults to core.compression. If that is
- not set, defaults to -1.
+ not set, defaults to -1, the zlib default, which is "a default
+ compromise between speed and compression (currently equivalent
+ to level 6)."
pack.deltaCacheSize::
The maximum memory in bytes used for caching deltas in
- gitlink:git-pack-objects[1].
+ linkgit:git-pack-objects[1].
A value of 0 means no limit. Defaults to 0.
pack.deltaCacheLimit::
The maximum size of a delta, that is cached in
- gitlink:git-pack-objects[1]. Defaults to 1000.
+ linkgit:git-pack-objects[1]. Defaults to 1000.
pack.threads::
Specifies the number of threads to spawn when searching for best
- delta matches. This requires that gitlink:git-pack-objects[1]
+ delta matches. This requires that linkgit:git-pack-objects[1]
be compiled with pthreads otherwise this option is ignored with a
warning. This is meant to reduce packing time on multiprocessor
machines. The required amount of memory for the delta search window
is however multiplied by the number of threads.
+pack.indexVersion::
+ Specify the default pack index version. Valid values are 1 for
+ legacy pack index used by Git versions prior to 1.5.2, and 2 for
+ the new pack index with capabilities for packs larger than 4 GB
+ as well as proper protection against the repacking of corrupted
+ packs. Version 2 is selected and this config option ignored
+ whenever the corresponding pack is larger than 2 GB. Otherwise
+ the default is 1.
+
+pack.packSizeLimit:
+ The default maximum size of a pack. This setting only affects
+ packing to a file, i.e. the git:// protocol is unaffected. It
+ can be overridden by the `\--max-pack-size` option of
+ linkgit:git-repack[1].
+
pull.octopus::
The default merge strategy to use when pulling multiple branches
at once.
@@ -668,28 +832,33 @@ pull.twohead::
The default merge strategy to use when pulling a single branch.
remote.<name>.url::
- The URL of a remote repository. See gitlink:git-fetch[1] or
- gitlink:git-push[1].
+ The URL of a remote repository. See linkgit:git-fetch[1] or
+ linkgit:git-push[1].
+
+remote.<name>.proxy::
+ For remotes that require curl (http, https and ftp), the URL to
+ the proxy to use for that remote. Set to the empty string to
+ disable proxying for that remote.
remote.<name>.fetch::
- The default set of "refspec" for gitlink:git-fetch[1]. See
- gitlink:git-fetch[1].
+ The default set of "refspec" for linkgit:git-fetch[1]. See
+ linkgit:git-fetch[1].
remote.<name>.push::
- The default set of "refspec" for gitlink:git-push[1]. See
- gitlink:git-push[1].
+ The default set of "refspec" for linkgit:git-push[1]. See
+ linkgit:git-push[1].
remote.<name>.skipDefaultUpdate::
If true, this remote will be skipped by default when updating
- using the remote subcommand of gitlink:git-remote[1].
+ using the update subcommand of linkgit:git-remote[1].
remote.<name>.receivepack::
The default program to execute on the remote side when pushing. See
- option \--exec of gitlink:git-push[1].
+ option \--exec of linkgit:git-push[1].
remote.<name>.uploadpack::
The default program to execute on the remote side when fetching. See
- option \--exec of gitlink:git-fetch-pack[1].
+ option \--exec of linkgit:git-fetch-pack[1].
remote.<name>.tagopt::
Setting this value to --no-tags disables automatic tag following when fetching
@@ -697,51 +866,57 @@ remote.<name>.tagopt::
remotes.<group>::
The list of remotes which are fetched by "git remote update
- <group>". See gitlink:git-remote[1].
+ <group>". See linkgit:git-remote[1].
repack.usedeltabaseoffset::
- Allow gitlink:git-repack[1] to create packs that uses
+ Allow linkgit:git-repack[1] to create packs that uses
delta-base offset. Defaults to false.
show.difftree::
- The default gitlink:git-diff-tree[1] arguments to be used
- for gitlink:git-show[1].
+ The default linkgit:git-diff-tree[1] arguments to be used
+ for linkgit:git-show[1].
showbranch.default::
- The default set of branches for gitlink:git-show-branch[1].
- See gitlink:git-show-branch[1].
+ The default set of branches for linkgit:git-show-branch[1].
+ See linkgit:git-show-branch[1].
+
+status.relativePaths::
+ By default, linkgit:git-status[1] shows paths relative to the
+ current directory. Setting this variable to `false` shows paths
+ relative to the repository root (this was the default for git
+ prior to v1.5.4).
tar.umask::
This variable can be used to restrict the permission bits of
tar archive entries. The default is 0002, which turns off the
world write bit. The special value "user" indicates that the
archiving user's umask will be used instead. See umask(2) and
- gitlink:git-archive[1].
+ linkgit:git-archive[1].
user.email::
Your email address to be recorded in any newly created commits.
Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
- 'EMAIL' environment variables. See gitlink:git-commit-tree[1].
+ 'EMAIL' environment variables. See linkgit:git-commit-tree[1].
user.name::
Your full name to be recorded in any newly created commits.
Can be overridden by the 'GIT_AUTHOR_NAME' and 'GIT_COMMITTER_NAME'
- environment variables. See gitlink:git-commit-tree[1].
+ environment variables. See linkgit:git-commit-tree[1].
user.signingkey::
- If gitlink:git-tag[1] is not selecting the key you want it to
+ If linkgit:git-tag[1] is not selecting the key you want it to
automatically when creating a signed tag, you can override the
default selection with this variable. This option is passed
unchanged to gpg's --local-user parameter, so you may specify a key
using any method that gpg supports.
whatchanged.difftree::
- The default gitlink:git-diff-tree[1] arguments to be used
- for gitlink:git-whatchanged[1].
+ The default linkgit:git-diff-tree[1] arguments to be used
+ for linkgit:git-whatchanged[1].
imap::
The configuration variables in the 'imap' section are described
- in gitlink:git-imap-send[1].
+ in linkgit:git-imap-send[1].
receive.unpackLimit::
If the number of objects received in a push is below this
@@ -750,7 +925,8 @@ receive.unpackLimit::
exceeds this limit then the received pack will be stored as
a pack, after adding any missing delta bases. Storing the
pack from a push can make the push operation complete faster,
- especially on slow filesystems.
+ especially on slow filesystems. If not set, the value of
+ `transfer.unpackLimit` is used instead.
receive.denyNonFastForwards::
If set to true, git-receive-pack will deny a ref update which is
@@ -761,3 +937,9 @@ receive.denyNonFastForwards::
transfer.unpackLimit::
When `fetch.unpackLimit` or `receive.unpackLimit` are
not set, the value of this variable is used instead.
+ The default value is 100.
+
+web.browser::
+ Specify a web browser that may be used by some commands.
+ Currently only linkgit:git-instaweb[1] and linkgit:git-help[1]
+ may use it.
diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt
index 6b2590d072..aa40dfd36a 100644
--- a/Documentation/core-tutorial.txt
+++ b/Documentation/core-tutorial.txt
@@ -553,13 +553,8 @@ can explore on your own.
[NOTE]
Most likely, you are not directly using the core
-git Plumbing commands, but using Porcelain like Cogito on top
-of it. Cogito works a bit differently and you usually do not
-have to run `git-update-index` yourself for changed files (you
-do tell underlying git about additions and removals via
-`cg-add` and `cg-rm` commands). Just before you make a commit
-with `cg-commit`, Cogito figures out which files you modified,
-and runs `git-update-index` on them for you.
+git Plumbing commands, but using Porcelain such as `git-add`, `git-rm'
+and `git-commit'.
Tagging a version
@@ -583,7 +578,7 @@ particular state. You can, for example, do
$ git diff my-first-tag
----------------
-to diff your current state against that tag (which at this point will
+to diff your current state against that tag which at this point will
obviously be an empty diff, but if you continue to develop and commit
stuff, you can use your tag as an "anchor-point" to see what has changed
since you tagged it.
@@ -686,8 +681,8 @@ $ git reset
and in fact a lot of the common git command combinations can be scripted
with the `git xyz` interfaces. You can learn things by just looking
-at what the various git scripts do. For example, `git reset` is the
-above two lines implemented in `git-reset`, but some things like
+at what the various git scripts do. For example, `git reset` used to be
+the above two lines implemented in `git-reset`, but some things like
`git status` and `git commit` are slightly more complex scripts around
the basic git commands.
@@ -805,8 +800,8 @@ you have, you can say
$ git branch
------------
-which is nothing more than a simple script around `ls .git/refs/heads`.
-There will be asterisk in front of the branch you are currently on.
+which used to be nothing more than a simple script around `ls .git/refs/heads`.
+There will be an asterisk in front of the branch you are currently on.
Sometimes you may wish to create a new branch _without_ actually
checking it out and switching to it. If so, just use the command
@@ -833,7 +828,7 @@ that branch, and do some work there.
------------------------------------------------
$ git checkout mybranch
$ echo "Work, work, work" >>hello
-$ git commit -m 'Some work.' -i hello
+$ git commit -m "Some work." -i hello
------------------------------------------------
Here, we just added another line to `hello`, and we used a shorthand for
@@ -858,7 +853,7 @@ hasn't happened in the `master` branch at all. Then do
------------
$ echo "Play, play, play" >>hello
$ echo "Lots of fun" >>example
-$ git commit -m 'Some fun.' -i hello example
+$ git commit -m "Some fun." -i hello example
------------
since the master branch is obviously in a much better mood.
@@ -883,7 +878,7 @@ script called `git merge`, which wants to know which branches you want
to resolve and what the merge is all about:
------------
-$ git merge "Merge work in mybranch" HEAD mybranch
+$ git merge -m "Merge work in mybranch" mybranch
------------
where the first argument is going to be used as the commit message if
@@ -936,12 +931,13 @@ Another useful tool, especially if you do not always work in X-Window
environment, is `git show-branch`.
------------------------------------------------
-$ git show-branch --topo-order master mybranch
+$ git-show-branch --topo-order --more=1 master mybranch
* [master] Merge work in mybranch
! [mybranch] Some work.
--
- [master] Merge work in mybranch
*+ [mybranch] Some work.
+* [master^] Some fun.
------------------------------------------------
The first two lines indicate that it is showing the two branches
@@ -952,17 +948,29 @@ 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 (`*`
-shows an ordinary commit on the current branch, `.` is a merge commit), which
+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,
because `mybranch` has not been merged to incorporate these
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~1' is the first parent of 'master'
+are branch heads. 'master^' is the first parent of 'master'
branch head. Please see 'git-rev-parse' documentation if you
see more complex cases.
+[NOTE]
+Without the '--more=1' option, 'git-show-branch' would not output the
+'[master^]' commit, as '[mybranch]' commit is a common ancestor of
+both 'master' and 'mybranch' tips. Please see 'git-show-branch'
+documentation for details.
+
+[NOTE]
+If there were more commits on the 'master' branch after the merge, the
+merge commit itself would not be shown by 'git-show-branch' by
+default. You would need to provide '--sparse' option to make the
+merge commit visible in this case.
+
Now, let's pretend you are the one who did all the work in
`mybranch`, and the fruit of your hard work has finally been merged
to the `master` branch. Let's go back to `mybranch`, and run
@@ -970,7 +978,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
------------
$ git checkout mybranch
-$ git merge "Merge upstream changes." HEAD master
+$ git merge -m "Merge upstream changes." master
------------
This outputs something like this (the actual commit object names
@@ -1082,11 +1090,6 @@ server like git Native transport does. Any stock HTTP server
that does not even support directory index would suffice. But
you must prepare your repository with `git-update-server-info`
to help dumb transport downloaders.
-+
-There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload`
-programs, which are 'commit walkers'; they outlived their
-usefulness when git Native and SSH transports were introduced,
-and not used by `git pull` or `git push` scripts.
Once you fetch from the remote repository, you `merge` that
with your current branch.
@@ -1149,7 +1152,7 @@ back to the earlier repository with "hello" and "example" file,
and bring ourselves back to the pre-merge state:
------------
-$ git show-branch --more=3 master mybranch
+$ git show-branch --more=2 master mybranch
! [master] Merge work in mybranch
* [mybranch] Merge work in mybranch
--
@@ -1193,7 +1196,7 @@ $ mb=$(git-merge-base HEAD mybranch)
The command writes the commit object name of the common ancestor
to the standard output, so we captured its output to a variable,
-because we will be using it in the next step. BTW, the common
+because we will be using it in the next step. By the way, the common
ancestor commit is the "New day." commit in this case. You can
tell it by:
@@ -1212,7 +1215,7 @@ $ git-read-tree -m -u $mb HEAD mybranch
This is the same `git-read-tree` command we have already seen,
but it takes three trees, unlike previous examples. This reads
the contents of each tree into different 'stage' in the index
-file (the first tree goes to stage 1, the second stage 2,
+file (the first tree goes to stage 1, the second to stage 2,
etc.). After reading three trees into three stages, the paths
that are the same in all three stages are 'collapsed' into stage
0. Also paths that are the same in two of three stages are
@@ -1459,8 +1462,7 @@ Although git is a truly distributed system, it is often
convenient to organize your project with an informal hierarchy
of developers. Linux kernel development is run this way. There
is a nice illustration (page 17, "Merges to Mainline") in
-link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf
-[Randy Dunlap's presentation].
+link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf[Randy Dunlap's presentation].
It should be stressed that this hierarchy is purely *informal*.
There is nothing fundamental in git that enforces the "chain of
@@ -1613,8 +1615,8 @@ in both of them. You could merge in 'diff-fix' first and then
'commit-fix' next, like this:
------------
-$ git merge 'Merge fix in diff-fix' master diff-fix
-$ git merge 'Merge fix in commit-fix' master commit-fix
+$ git merge -m "Merge fix in diff-fix" diff-fix
+$ git merge -m "Merge fix in commit-fix" commit-fix
------------
Which would result in:
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
index 3b6b494162..ea98900228 100644
--- a/Documentation/cvs-migration.txt
+++ b/Documentation/cvs-migration.txt
@@ -36,12 +36,12 @@ them first before running git pull.
================================
The `pull` command knows where to get updates from because of certain
configuration variables that were set by the first `git clone`
-command; see `git config -l` and the gitlink:git-config[1] man
+command; see `git config -l` and the linkgit:git-config[1] man
page for details.
================================
You can update the shared repository with your changes by first committing
-your changes, and then using the gitlink:git-push[1] command:
+your changes, and then using the linkgit:git-push[1] command:
------------------------------------------------
$ git push origin master
@@ -88,7 +88,7 @@ Next, give every team member read/write access to this repository. One
easy way to do this is to give all the team members ssh access to the
machine where the repository is hosted. If you don't want to give them a
full shell on the machine, there is a restricted shell which only allows
-users to do git pushes and pulls; see gitlink:git-shell[1].
+users to do git pushes and pulls; see linkgit:git-shell[1].
Put all the committers in the same group, and make the repository
writable by that group:
@@ -106,7 +106,7 @@ Importing a CVS archive
First, install version 2.1 or higher of cvsps from
link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
sure it is in your path. Then cd to a checked out CVS working directory
-of the project you are interested in and run gitlink:git-cvsimport[1]:
+of the project you are interested in and run linkgit:git-cvsimport[1]:
-------------------------------------------
$ git cvsimport -C <destination> <module>
@@ -146,7 +146,7 @@ Providing CVS Access to a git Repository
----------------------------------------
It is also possible to provide true CVS access to a git repository, so
-that developers can still use CVS; see gitlink:git-cvsserver[1] for
+that developers can still use CVS; see linkgit:git-cvsserver[1] for
details.
Alternative Development Models
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
index 001503205b..400cbb3b1c 100644
--- a/Documentation/diff-format.txt
+++ b/Documentation/diff-format.txt
@@ -1,5 +1,5 @@
-The output format from "git-diff-index", "git-diff-tree" and
-"git-diff-files" are very similar.
+The output format from "git-diff-index", "git-diff-tree",
+"git-diff-files" and "git diff --raw" are very similar.
These commands all compare two sets of things; what is
compared differs:
@@ -62,7 +62,8 @@ respectively.
diff format for merges
----------------------
-"git-diff-tree" and "git-diff-files" can take '-c' or '--cc' option
+"git-diff-tree", "git-diff-files" and "git-diff --raw"
+can take '-c' or '--cc' option
to generate diff output also for merge commits. The output differs
from the format described above in the following way:
@@ -82,161 +83,65 @@ Note that 'combined diff' lists only files which were modified from
all parents.
-Generating patches with -p
---------------------------
-
-When "git-diff-index", "git-diff-tree", or "git-diff-files" are run
-with a '-p' option, they do not produce the output described above;
-instead they produce a 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.
-
-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.
-+
-When rename/copy is involved, `file1` and `file2` show the
-name of the source file of the rename/copy and the name of
-the file that rename/copy produces, respectively.
-
-2. It is followed by one or more extended header lines:
-
- old mode <mode>
- new mode <mode>
- deleted file mode <mode>
- new file mode <mode>
- copy from <path>
- copy to <path>
- rename from <path>
- rename to <path>
- 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.
-
-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.
-
-
-combined diff format
---------------------
-
-git-diff-tree and git-diff-files can take '-c' or '--cc' option
-to produce 'combined diff', which looks like this:
-
-------------
-diff --combined describe.c
-index fabadb8,cc95eb0..4866510
---- a/describe.c
-+++ b/describe.c
-@@@ -98,20 -98,12 +98,20 @@@
- return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
- }
-
-- static void describe(char *arg)
- -static void describe(struct commit *cmit, int last_one)
-++static void describe(char *arg, int last_one)
- {
- + unsigned char sha1[20];
- + struct commit *cmit;
- struct commit_list *list;
- static int initialized = 0;
- struct commit_name *n;
-
- + if (get_sha1(arg, sha1) < 0)
- + usage(describe_usage);
- + cmit = lookup_commit_reference(sha1);
- + if (!cmit)
- + usage(describe_usage);
- +
- if (!initialized) {
- initialized = 1;
- for_each_ref(get_name);
-------------
-
-1. It is preceded with a "git diff" header, that looks like
- this (when '-c' option is used):
-
- diff --combined file
-+
-or like this (when '--cc' option is used):
-
- diff --c file
-
-2. It is followed by one or more extended header lines
- (this example shows a merge with two parents):
-
- index <hash>,<hash>..<hash>
- mode <mode>,<mode>..<mode>
- new file mode <mode>
- deleted file mode <mode>,<mode>
-+
-The `mode <mode>,<mode>..<mode>` line appears only if at least one of
-the <mode> is different from the rest. Extended headers with
-information about detected contents movement (renames and
-copying detection) are designed to work with diff of two
-<tree-ish> and are not used by combined diff format.
-
-3. It is followed by two-line from-file/to-file header
-
- --- a/file
- +++ b/file
-+
-Similar to two-line header for traditional 'unified' diff
-format, `/dev/null` is used to signal created or deleted
-files.
-
-4. Chunk header format is modified to prevent people from
- accidentally feeding it to `patch -p1`. Combined diff format
- was created for review of merge commit changes, and was not
- meant for apply. The change is similar to the change in the
- extended 'index' header:
-
- @@@ <from-file-range> <from-file-range> <to-file-range> @@@
-+
-There are (number of parents + 1) `@` characters in the chunk
-header for combined diff format.
-
-Unlike the traditional 'unified' diff format, which shows two
-files A and B with a single column that has `-` (minus --
-appears in A but removed in B), `+` (plus -- missing in A but
-added to B), or `" "` (space -- unchanged) prefix, this format
-compares two or more files file1, file2,... with one file X, and
-shows how X differs from each of fileN. One column for each of
-fileN is prepended to the output line to note how X's line is
-different from it.
-
-A `-` character in the column N means that the line appears in
-fileN but it does not appear in the result. A `+` character
-in the column N means that the line appears in the last file,
-and fileN does not have that line (in other words, the line was
-added, from the point of view of that parent).
-
-In the above example output, the function signature was changed
-from both files (hence two `-` removals from both file1 and
-file2, plus `++` to mean one line that was added does not appear
-in either file1 nor file2). Also two other lines are the same
-from file1 but do not appear in file2 (hence prefixed with ` +`).
-
-When shown by `git diff-tree -c`, it compares the parents of a
-merge commit with the merge result (i.e. file1..fileN are the
-parents). When shown by `git diff-files -c`, it compares the
-two unresolved merge parents with the working tree file
-(i.e. file1 is stage 2 aka "our version", file2 is stage 3 aka
-"their version").
+include::diff-generate-patch.txt[]
+
+
+other diff formats
+------------------
+
+The `--summary` option describes newly added, deleted, renamed and
+copied files. The `--stat` option adds diffstat(1) graph to the
+output. These options can be combined with other options, such as
+`-p`, and are meant for human consumption.
+
+When showing a change that involves a rename or a copy, `--stat` output
+formats the pathnames compactly by combining common prefix and suffix of
+the pathnames. For example, a change that moves `arch/i386/Makefile` to
+`arch/x86/Makefile` while modifying 4 lines will be shown like this:
+
+------------------------------------
+arch/{i386 => x86}/Makefile | 4 +--
+------------------------------------
+
+The `--numstat` option gives the diffstat(1) information but is designed
+for easier machine consumption. An entry in `--numstat` output looks
+like this:
+
+----------------------------------------
+1 2 README
+3 1 arch/{i386 => x86}/Makefile
+----------------------------------------
+
+That is, from left to right:
+
+. the number of added lines;
+. a tab;
+. the number of deleted lines;
+. a tab;
+. pathname (possibly with rename/copy information);
+. a newline.
+
+When `-z` output option is in effect, the output is formatted this way:
+
+----------------------------------------
+1 2 README NUL
+3 1 NUL arch/i386/Makefile NUL arch/x86/Makefile NUL
+----------------------------------------
+
+That is:
+
+. the number of added lines;
+. a tab;
+. the number of deleted lines;
+. a tab;
+. a NUL (only exists if renamed/copied);
+. pathname in preimage;
+. a NUL (only exists if renamed/copied);
+. pathname in postimage (only exists if renamed/copied);
+. a NUL.
+
+The extra `NUL` before the preimage path in renamed case is to allow
+scripts that read the output to tell if the current record being read is
+a single-path record or a rename/copy record without reading ahead.
+After reading added and deleted lines, reading up to `NUL` would yield
+the pathname, but if that is `NUL`, the record will show two paths.
diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt
new file mode 100644
index 0000000000..029c5f2b82
--- /dev/null
+++ b/Documentation/diff-generate-patch.txt
@@ -0,0 +1,161 @@
+Generating patches with -p
+--------------------------
+
+When "git-diff-index", "git-diff-tree", or "git-diff-files" are run
+with a '-p' option, "git diff" without the '--raw' option, or
+"git log" with the "-p" option, they
+do not produce the output described above; instead they produce a
+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.
+
+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.
++
+When rename/copy is involved, `file1` and `file2` show the
+name of the source file of the rename/copy and the name of
+the file that rename/copy produces, respectively.
+
+2. It is followed by one or more extended header lines:
+
+ old mode <mode>
+ new mode <mode>
+ deleted file mode <mode>
+ new file mode <mode>
+ copy from <path>
+ copy to <path>
+ rename from <path>
+ rename to <path>
+ 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.
+
+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.
+
+
+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.
+A 'combined diff' format looks like this:
+
+------------
+diff --combined describe.c
+index fabadb8,cc95eb0..4866510
+--- a/describe.c
++++ b/describe.c
+@@@ -98,20 -98,12 +98,20 @@@
+ return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+ }
+
+- static void describe(char *arg)
+ -static void describe(struct commit *cmit, int last_one)
+++static void describe(char *arg, int last_one)
+ {
+ + unsigned char sha1[20];
+ + struct commit *cmit;
+ struct commit_list *list;
+ static int initialized = 0;
+ struct commit_name *n;
+
+ + if (get_sha1(arg, sha1) < 0)
+ + usage(describe_usage);
+ + cmit = lookup_commit_reference(sha1);
+ + if (!cmit)
+ + usage(describe_usage);
+ +
+ if (!initialized) {
+ initialized = 1;
+ for_each_ref(get_name);
+------------
+
+1. It is preceded with a "git diff" header, that looks like
+ this (when '-c' option is used):
+
+ diff --combined file
++
+or like this (when '--cc' option is used):
+
+ diff --c file
+
+2. It is followed by one or more extended header lines
+ (this example shows a merge with two parents):
+
+ index <hash>,<hash>..<hash>
+ mode <mode>,<mode>..<mode>
+ new file mode <mode>
+ deleted file mode <mode>,<mode>
++
+The `mode <mode>,<mode>..<mode>` line appears only if at least one of
+the <mode> is different from the rest. Extended headers with
+information about detected contents movement (renames and
+copying detection) are designed to work with diff of two
+<tree-ish> and are not used by combined diff format.
+
+3. It is followed by two-line from-file/to-file header
+
+ --- a/file
+ +++ b/file
++
+Similar to two-line header for traditional 'unified' diff
+format, `/dev/null` is used to signal created or deleted
+files.
+
+4. Chunk header format is modified to prevent people from
+ accidentally feeding it to `patch -p1`. Combined diff format
+ was created for review of merge commit changes, and was not
+ meant for apply. The change is similar to the change in the
+ extended 'index' header:
+
+ @@@ <from-file-range> <from-file-range> <to-file-range> @@@
++
+There are (number of parents + 1) `@` characters in the chunk
+header for combined diff format.
+
+Unlike the traditional 'unified' diff format, which shows two
+files A and B with a single column that has `-` (minus --
+appears in A but removed in B), `+` (plus -- missing in A but
+added to B), or `" "` (space -- unchanged) prefix, this format
+compares two or more files file1, file2,... with one file X, and
+shows how X differs from each of fileN. One column for each of
+fileN is prepended to the output line to note how X's line is
+different from it.
+
+A `-` character in the column N means that the line appears in
+fileN but it does not appear in the result. A `+` character
+in the column N means that the line appears in the last file,
+and fileN does not have that line (in other words, the line was
+added, from the point of view of that parent).
+
+In the above example output, the function signature was changed
+from both files (hence two `-` removals from both file1 and
+file2, plus `++` to mean one line that was added does not appear
+in either file1 nor file2). Also two other lines are the same
+from file1 but do not appear in file2 (hence prefixed with ` +`).
+
+When shown by `git diff-tree -c`, it compares the parents of a
+merge commit with the merge result (i.e. file1..fileN are the
+parents). When shown by `git diff-files -c`, it compares the
+two unresolved merge parents with the working tree file
+(i.e. file1 is stage 2 aka "our version", file2 is stage 3 aka
+"their version").
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index b1f528ae88..8d35cbd60d 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -1,5 +1,27 @@
+// Please don't remove this comment as asciidoc behaves badly when
+// the first non-empty line is ifdef/ifndef. The symptom is that
+// without this comment the <git-diff-core> attribute conditionally
+// defined below ends up being defined unconditionally.
+// Last checked with asciidoc 7.0.2.
+
+ifndef::git-format-patch[]
+ifndef::git-diff[]
+ifndef::git-log[]
+:git-diff-core: 1
+endif::git-log[]
+endif::git-diff[]
+endif::git-format-patch[]
+
+ifdef::git-format-patch[]
-p::
- Generate patch (see section on generating patches)
+ Generate patches without diffstat.
+endif::git-format-patch[]
+
+ifndef::git-format-patch[]
+-p::
+ Generate patch (see section on generating patches).
+ {git-diff? This is the default.}
+endif::git-format-patch[]
-u::
Synonym for "-p".
@@ -13,6 +35,7 @@
--raw::
Generate the raw format.
+ {git-diff-core? This is the default.}
--patch-with-raw::
Synonym for "-p --raw".
@@ -41,6 +64,7 @@
--patch-with-stat::
Synonym for "-p --stat".
+ {git-format-patch? This is the default.}
-z::
NUL-line termination on output. This affects the --raw
@@ -69,7 +93,9 @@
--check::
Warn if changes introduce trailing whitespace
- or an indent that uses a space before a tab.
+ or an indent that uses a space before a tab. Exits with
+ non-zero status if problems are found. Not compatible with
+ --exit-code.
--full-index::
Instead of the first handful characters, show full
@@ -151,19 +177,19 @@
Shorthand for "--text".
--ignore-space-at-eol::
- Ignore changes in white spaces at EOL.
+ Ignore changes in whitespace at EOL.
--ignore-space-change::
- Ignore changes in amount of white space. This ignores white
- space at line end, and consider all other sequences of one or
- more white space characters to be equivalent.
+ Ignore changes in amount of whitespace. This ignores whitespace
+ at line end, and considers all other sequences of one or
+ more whitespace characters to be equivalent.
-b::
Shorthand for "--ignore-space-change".
--ignore-all-space::
- Ignore white space when comparing lines. This ignores
- difference even if one line has white space where the other
+ Ignore whitespace when comparing lines. This ignores
+ differences even if one line has whitespace where the other
line has none.
-w::
@@ -179,11 +205,20 @@
--ext-diff::
Allow an external diff helper to be executed. If you set an
- external diff driver with gitlink:gitattributes[5], you need
- to use this option with gitlink:git-log[1] and friends.
+ external diff driver with linkgit:gitattributes[5], you need
+ to use this option with linkgit:git-log[1] and friends.
--no-ext-diff::
Disallow external diff drivers.
+--src-prefix=<prefix>::
+ Show the given source prefix instead of "a/".
+
+--dst-prefix=<prefix>::
+ Show the given destination prefix instead of "b/".
+
+--no-prefix::
+ Do not show any source or destination prefix.
+
For more detailed explanation on these common options, see also
link:diffcore.html[diffcore documentation].
diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt
index 08c61b1f1a..fdbd15a181 100644
--- a/Documentation/everyday.txt
+++ b/Documentation/everyday.txt
@@ -25,16 +25,12 @@ Basic Repository[[Basic Repository]]
Everybody uses these commands to maintain git repositories.
- * gitlink:git-init[1] or gitlink:git-clone[1] to create a
+ * linkgit:git-init[1] or linkgit:git-clone[1] to create a
new repository.
- * gitlink:git-fsck[1] to check the repository for errors.
+ * linkgit:git-fsck[1] to check the repository for errors.
- * gitlink:git-prune[1] to remove unused objects in the repository.
-
- * gitlink:git-repack[1] to pack loose objects for efficiency.
-
- * gitlink:git-gc[1] to do common housekeeping tasks such as
+ * linkgit:git-gc[1] to do common housekeeping tasks such as
repack and prune.
Examples
@@ -45,24 +41,21 @@ Check health and remove cruft.::
------------
$ git fsck <1>
$ git count-objects <2>
-$ git repack <3>
-$ git gc <4>
+$ git gc <3>
------------
+
<1> running without `\--full` is usually cheap and assures the
repository health reasonably well.
<2> check how many loose objects there are and how much
disk space is wasted by not repacking.
-<3> without `-a` repacks incrementally. repacking every 4-5MB
-of loose objects accumulation may be a good rule of thumb.
-<4> it is easier to use `git gc` than individual housekeeping commands
-such as `prune` and `repack`. This runs `repack -a -d`.
+<3> repacks the local repository and performs other housekeeping tasks. Running
+without `--prune` is a safe operation even while other ones are in progress.
Repack a small project into single pack.::
+
------------
-$ git repack -a -d <1>
-$ git prune
+$ git gc <1>
+$ git gc --prune
------------
+
<1> pack all the objects reachable from the refs into one pack,
@@ -76,28 +69,28 @@ A standalone individual developer does not exchange patches with
other people, and works alone in a single repository, using the
following commands.
- * gitlink:git-show-branch[1] to see where you are.
+ * linkgit:git-show-branch[1] to see where you are.
- * gitlink:git-log[1] to see what happened.
+ * linkgit:git-log[1] to see what happened.
- * gitlink:git-checkout[1] and gitlink:git-branch[1] to switch
+ * linkgit:git-checkout[1] and linkgit:git-branch[1] to switch
branches.
- * gitlink:git-add[1] to manage the index file.
+ * linkgit:git-add[1] to manage the index file.
- * gitlink:git-diff[1] and gitlink:git-status[1] to see what
+ * linkgit:git-diff[1] and linkgit:git-status[1] to see what
you are in the middle of doing.
- * gitlink:git-commit[1] to advance the current branch.
+ * linkgit:git-commit[1] to advance the current branch.
- * gitlink:git-reset[1] and gitlink:git-checkout[1] (with
+ * linkgit:git-reset[1] and linkgit:git-checkout[1] (with
pathname parameters) to undo changes.
- * gitlink:git-merge[1] to merge between local branches.
+ * linkgit:git-merge[1] to merge between local branches.
- * gitlink:git-rebase[1] to maintain topic branches.
+ * linkgit:git-rebase[1] to maintain topic branches.
- * gitlink:git-tag[1] to mark known point.
+ * linkgit:git-tag[1] to mark known point.
Examples
~~~~~~~~
@@ -109,7 +102,7 @@ $ tar zxf frotz.tar.gz
$ cd frotz
$ git-init
$ git add . <1>
-$ git commit -m 'import of frotz source tree.'
+$ git commit -m "import of frotz source tree."
$ git tag v2.43 <2>
------------
+
@@ -163,16 +156,16 @@ A developer working as a participant in a group project needs to
learn how to communicate with others, and uses these commands in
addition to the ones needed by a standalone developer.
- * gitlink:git-clone[1] from the upstream to prime your local
+ * linkgit:git-clone[1] from the upstream to prime your local
repository.
- * gitlink:git-pull[1] and gitlink:git-fetch[1] from "origin"
+ * linkgit:git-pull[1] and linkgit:git-fetch[1] from "origin"
to keep up-to-date with the upstream.
- * gitlink:git-push[1] to shared repository, if you adopt CVS
+ * linkgit:git-push[1] to shared repository, if you adopt CVS
style shared repository workflow.
- * gitlink:git-format-patch[1] to prepare e-mail submission, if
+ * linkgit:git-format-patch[1] to prepare e-mail submission, if
you adopt Linux kernel-style public forum workflow.
Examples
@@ -189,7 +182,7 @@ $ git pull <3>
$ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <4>
$ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5>
$ git reset --hard ORIG_HEAD <6>
-$ git prune <7>
+$ git gc --prune <7>
$ git fetch --tags <8>
------------
+
@@ -265,17 +258,17 @@ project receives changes made by others, reviews and integrates
them and publishes the result for others to use, using these
commands in addition to the ones needed by participants.
- * gitlink:git-am[1] to apply patches e-mailed in from your
+ * linkgit:git-am[1] to apply patches e-mailed in from your
contributors.
- * gitlink:git-pull[1] to merge from your trusted lieutenants.
+ * linkgit:git-pull[1] to merge from your trusted lieutenants.
- * gitlink:git-format-patch[1] to prepare and send suggested
+ * linkgit:git-format-patch[1] to prepare and send suggested
alternative to contributors.
- * gitlink:git-revert[1] to undo botched commits.
+ * linkgit:git-revert[1] to undo botched commits.
- * gitlink:git-push[1] to publish the bleeding edge.
+ * linkgit:git-push[1] to publish the bleeding edge.
Examples
@@ -300,7 +293,7 @@ $ git merge topic/one topic/two && git merge hold/linus <8>
$ git checkout maint
$ git cherry-pick master~4 <9>
$ compile/test
-$ git tag -s -m 'GIT 0.99.9x' v0.99.9x <10>
+$ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
$ git fetch ko && git show-branch master maint 'tags/ko-*' <11>
$ git push ko <12>
$ git push ko v0.99.9x <13>
@@ -350,10 +343,10 @@ Repository Administration[[Repository Administration]]
A repository administrator uses the following tools to set up
and maintain access to the repository by developers.
- * gitlink:git-daemon[1] to allow anonymous download from
+ * linkgit:git-daemon[1] to allow anonymous download from
repository.
- * gitlink:git-shell[1] can be used as a 'restricted login shell'
+ * linkgit:git-shell[1] can be used as a 'restricted login shell'
for shared central repository users.
link:howto/update-hook-example.txt[update hook howto] has a good
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index da034223f3..b675911480 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -23,11 +23,15 @@
fetches is a descendant of `<lbranch>`. This option
overrides that check.
+ifdef::git-pull[]
+\--no-tags::
+endif::git-pull[]
+ifndef::git-pull[]
-n, \--no-tags::
- By default, `git-fetch` fetches tags that point at
- objects that are downloaded from the remote repository
- and stores them locally. This option disables this
- automatic tag following.
+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.
-t, \--tags::
Most of the tags are fetched automatically as branch
@@ -50,5 +54,5 @@
\--depth=<depth>::
Deepen the history of a 'shallow' repository created by
- `git clone` with `--depth=<depth>` option (see gitlink:git-clone[1])
+ `git clone` with `--depth=<depth>` option (see linkgit:git-clone[1])
by the specified number of commits.
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 2fe7355555..47799097ce 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -8,7 +8,7 @@ git-add - Add file contents to the index
SYNOPSIS
--------
[verse]
-'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh]
+'git-add' [-n] [-v] [-f] [--interactive | -i] [--patch | -p] [-u] [--refresh]
[--] <filepattern>...
DESCRIPTION
@@ -37,7 +37,7 @@ directory recursion or filename globbing performed by Git (quote your
globs before the shell) will be silently ignored. The 'add' command can
be used to add ignored files with the `-f` (force) option.
-Please see gitlink:git-commit[1] for alternative ways to add content to a
+Please see linkgit:git-commit[1] for alternative ways to add content to a
commit.
@@ -50,10 +50,10 @@ OPTIONS
and `dir/file2`) can be given to add all files in the
directory, recursively.
--n::
+-n, \--dry-run::
Don't actually add the file(s), just show if they exist.
--v::
+-v, \--verbose::
Be verbose.
-f::
@@ -61,14 +61,21 @@ OPTIONS
-i, \--interactive::
Add modified contents in the working tree interactively to
- the index.
+ the index. Optional path arguments may be supplied to limit
+ operation to a subset of the working tree. See ``Interactive
+ mode'' for details.
+
+-p, \--patch::
+ Similar to Interactive mode but the initial command loop is
+ bypassed and the 'patch' subcommand is invoked using each of
+ the specified filepatterns before exiting.
-u::
Update only files that git already knows about. This is similar
to what "git commit -a" does in preparation for making a commit,
except that the update is limited to paths specified on the
- command line. If no paths are specified, all tracked files are
- updated.
+ command line. If no paths are specified, all tracked files in the
+ current directory and its subdirectories are updated.
\--refresh::
Don't add the file(s), but only refresh their stat()
@@ -210,6 +217,8 @@ patch::
k - do not decide on this hunk now, and view the previous
undecided hunk
K - do not decide on this hunk now, and view the previous hunk
+ s - split the current hunk into smaller hunks
+ ? - print help
+
After deciding the fate for all hunks, if there is any hunk
that was chosen, the index is updated with the selected hunks.
@@ -222,11 +231,12 @@ diff::
See Also
--------
-gitlink:git-status[1]
-gitlink:git-rm[1]
-gitlink:git-mv[1]
-gitlink:git-commit[1]
-gitlink:git-update-index[1]
+linkgit:git-status[1]
+linkgit:git-rm[1]
+linkgit:git-reset[1]
+linkgit:git-mv[1]
+linkgit:git-commit[1]
+linkgit:git-update-index[1]
Author
------
@@ -238,4 +248,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index e4a6b3a6f0..2ffba2102b 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -37,10 +37,10 @@ OPTIONS
area to store extracted patches.
-k, --keep::
- Pass `-k` flag to `git-mailinfo` (see gitlink:git-mailinfo[1]).
+ Pass `-k` flag to `git-mailinfo` (see linkgit:git-mailinfo[1]).
-u, --utf8::
- Pass `-u` flag to `git-mailinfo` (see gitlink:git-mailinfo[1]).
+ Pass `-u` flag to `git-mailinfo` (see linkgit:git-mailinfo[1]).
The proposed commit log message taken from the e-mail
is re-coded into UTF-8 encoding (configuration variable
`i18n.commitencoding` can be used to specify project's
@@ -51,7 +51,7 @@ default. You could use `--no-utf8` to override this.
--no-utf8::
Pass `-n` flag to `git-mailinfo` (see
- gitlink:git-mailinfo[1]).
+ linkgit:git-mailinfo[1]).
-3, --3way::
When the patch does not apply cleanly, fall back on
@@ -61,15 +61,15 @@ default. You could use `--no-utf8` to override this.
-b, --binary::
Pass `--allow-binary-replacement` flag to `git-apply`
- (see gitlink:git-apply[1]).
+ (see linkgit:git-apply[1]).
--whitespace=<option>::
- This flag is passed to the `git-apply` (see gitlink:git-apply[1])
+ This flag is passed to the `git-apply` (see linkgit:git-apply[1])
program that applies
the patch.
-C<n>, -p<n>::
- These flags are passed to the `git-apply` (see gitlink:git-apply[1])
+ These flags are passed to the `git-apply` (see linkgit:git-apply[1])
program that applies
the patch.
@@ -144,7 +144,7 @@ names.
SEE ALSO
--------
-gitlink:git-apply[1].
+linkgit:git-apply[1].
Author
@@ -157,4 +157,4 @@ Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.o
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-annotate.txt b/Documentation/git-annotate.txt
index 02dc4740d0..45a6a7251e 100644
--- a/Documentation/git-annotate.txt
+++ b/Documentation/git-annotate.txt
@@ -20,7 +20,7 @@ include::blame-options.txt[]
SEE ALSO
--------
-gitlink:git-blame[1]
+linkgit:git-blame[1]
AUTHOR
------
@@ -28,4 +28,4 @@ Written by Ryan Anderson <ryan@michonline.com>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index c1c54bfe0b..2dec2ec1cf 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -13,7 +13,7 @@ SYNOPSIS
[--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse]
[--allow-binary-replacement | --binary] [--reject] [-z]
[-pNUM] [-CNUM] [--inaccurate-eof] [--cached]
- [--whitespace=<nowarn|warn|error|error-all|strip>]
+ [--whitespace=<nowarn|warn|fix|error|error-all>]
[--exclude=PATH] [--verbose] [<patch>...]
DESCRIPTION
@@ -77,7 +77,7 @@ the information is read from the current index instead.
Apply the patch in reverse.
--reject::
- For atomicity, gitlink:git-apply[1] by default fails the whole patch and
+ For atomicity, linkgit:git-apply[1] by default fails the whole patch and
does not touch the working tree when some of the hunks
do not apply. This option makes it apply
the parts of the patch that are applicable, and leave the
@@ -101,7 +101,7 @@ the information is read from the current index instead.
ever ignored.
--unidiff-zero::
- By default, gitlink:git-apply[1] expects that the patch being
+ By default, linkgit:git-apply[1] expects that the patch being
applied is a unified diff with at least one line of context.
This provides good safety measures, but breaks down when
applying a diff generated with --unified=0. To bypass these
@@ -112,14 +112,14 @@ discouraged.
--apply::
If you use any of the options marked "Turns off
- 'apply'" above, gitlink:git-apply[1] reads and outputs the
+ 'apply'" above, linkgit:git-apply[1] reads and outputs the
information you asked without actually applying the
patch. Give this flag after those flags to also apply
the patch.
--no-add::
When applying a patch, ignore additions made by the
- patch. This can be used to extract common part between
+ patch. This can be used to extract the common part between
two files by first running `diff` on them and applying
the result with this option, which would apply the
deletion part but not addition part.
@@ -135,25 +135,32 @@ discouraged.
be useful when importing patchsets, where you want to exclude certain
files or directories.
---whitespace=<option>::
- When applying a patch, detect a new or modified line
- that ends with trailing whitespaces (this includes a
- line that solely consists of whitespaces). By default,
- the command outputs warning messages and applies the
- patch.
- When gitlink:git-apply[1] is used for statistics and not applying a
- patch, it defaults to `nowarn`.
- You can use different `<option>` to control this
- behavior:
+--whitespace=<action>::
+ When applying a patch, detect a new or modified line that has
+ 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.
++
+By default, the command outputs warning messages but applies the patch.
+When linkgit:git-apply[1] is used for statistics and not applying a
+patch, it defaults to `nowarn`.
++
+You can use different `<action>` to control this
+behavior:
+
* `nowarn` turns off the trailing whitespace warning.
* `warn` outputs warnings for a few such errors, but applies the
- patch (default).
+ patch as-is (default).
+* `fix` outputs warnings for a few such errors, and applies the
+ patch after fixing them (`strip` is a synonym --- the tool
+ used to consider only trailing whitespaces as errors, and the
+ fix involved 'stripping' them, but modern gits do more).
* `error` outputs warnings for a few such errors, and refuses
to apply the patch.
* `error-all` is similar to `error` but shows all errors.
-* `strip` outputs warnings for a few such errors, strips out the
- trailing whitespaces and applies the patch.
--inaccurate-eof::
Under certain circumstances, some versions of diff do not correctly
@@ -176,7 +183,7 @@ apply.whitespace::
Submodules
----------
-If the patch contains any changes to submodules then gitlink:git-apply[1]
+If the patch contains any changes to submodules then linkgit:git-apply[1]
treats these changes as follows.
If --index is specified (explicitly or implicitly), then the submodule
@@ -199,4 +206,4 @@ Documentation by Junio C Hamano
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt
index 7091b8d61c..bd20fd8206 100644
--- a/Documentation/git-archimport.txt
+++ b/Documentation/git-archimport.txt
@@ -117,4 +117,4 @@ Documentation by Junio C Hamano, Martin Langhoff and the git-list <git@vger.kern
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
index 7cd6526552..d3eaa16af1 100644
--- a/Documentation/git-archive.txt
+++ b/Documentation/git-archive.txt
@@ -118,4 +118,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index 1072fb87d1..96585ae8d9 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -16,8 +16,9 @@ The command takes various subcommands, and different options depending
on the subcommand:
git bisect start [<bad> [<good>...]] [--] [<paths>...]
- git bisect bad <rev>
- git bisect good <rev>
+ git bisect bad [<rev>]
+ git bisect good [<rev>...]
+ git bisect skip [<rev>...]
git bisect reset [<branch>]
git bisect visualize
git bisect replay <logfile>
@@ -91,7 +92,16 @@ During the bisection process, you can say
$ git bisect visualize
------------
-to see the currently remaining suspects in `gitk`.
+to see the currently remaining suspects in `gitk`. `visualize` is a bit
+too long to type and `view` is provided as a synonym.
+
+If `DISPLAY` environment variable is not set, `git log` is used
+instead. You can even give command line options such as `-p` and
+`--stat`.
+
+------------
+$ git bisect view --stat
+------------
Bisect log and bisect replay
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -134,6 +144,20 @@ $ git reset --hard HEAD~3 # try 3 revs before what
Then compile and test the one you chose to try. After that, tell
bisect what the result was as usual.
+Bisect skip
+~~~~~~~~~~~~
+
+Instead of choosing by yourself a nearby commit, you may just want git
+to do it for you using:
+
+------------
+$ git bisect skip # Current version cannot be tested
+------------
+
+But computing the commit to test may be slower afterwards and git may
+eventually not be able to tell the first bad among a bad and one or
+more "skip"ped commits.
+
Cutting down bisection by giving more parameters to bisect start
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -167,14 +191,18 @@ $ git bisect run my_script
------------
Note that the "run" script (`my_script` in the above example) should
-exit with code 0 in case the current source code is good and with a
-code between 1 and 127 (included) in case the current source code is
-bad.
+exit with code 0 in case the current source code is good. Exit with a
+code between 1 and 127 (inclusive), except 125, if the current
+source code is bad.
Any other exit code will abort the automatic bisect process. (A
program that does "exit(-1)" leaves $? = 255, see exit(3) manual page,
the value is chopped with "& 0377".)
+The special exit code 125 should be used when the current source code
+cannot be tested. If the "run" script exits with this code, the current
+revision will be skipped, see `git bisect skip` above.
+
You may often find that during bisect you want to have near-constant
tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or
"revision that does not have this commit needs this patch applied to
@@ -199,4 +227,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index 66f1203701..14163b65f9 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -21,7 +21,7 @@ last modified the line. Optionally, start annotating from the given revision.
Also it can limit the range of lines annotated.
This report doesn't tell you anything about lines which have been deleted or
-replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe"
+replaced; you need to use a tool such as linkgit:git-diff[1] or the "pickaxe"
interface briefly mentioned in the following paragraph.
Apart from supporting file annotation, git also supports searching the
@@ -41,7 +41,7 @@ OPTIONS
include::blame-options.txt[]
-c::
- Use the same output mode as gitlink:git-annotate[1] (Default: off).
+ Use the same output mode as linkgit:git-annotate[1] (Default: off).
--score-debug::
Include debugging information related to the movement of
@@ -184,7 +184,7 @@ commit commentary), a blame viewer won't ever care.
SEE ALSO
--------
-gitlink:git-annotate[1]
+linkgit:git-annotate[1]
AUTHOR
------
@@ -192,4 +192,4 @@ Written by Junio C Hamano <junkio@cox.net>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index b7285bcdbc..7e8874acaa 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -10,6 +10,7 @@ SYNOPSIS
[verse]
'git-branch' [--color | --no-color] [-r | -a]
[-v [--abbrev=<length> | --no-abbrev]]
+ [--contains <commit>]
'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
'git-branch' (-m | -M) [<oldbranch>] <newbranch>
'git-branch' (-d | -D) [-r] <branchname>...
@@ -20,6 +21,9 @@ With no arguments given a list of existing branches
will be shown, the current branch will be highlighted with an asterisk.
Option `-r` causes the remote-tracking branches to be listed,
and option `-a` shows both.
+With `--contains <commit>`, shows only the branches that
+contains the named commit (in other words, the branches whose
+tip commits are descendant of the named commit).
In its second form, a new branch named <branchname> will be created.
It will start out with a head equal to the one given as <start-point>.
@@ -30,11 +34,11 @@ 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 can setup the
-branch so that gitlink:git-pull[1] will appropriately merge from that
-remote branch. If this behavior is desired, it is possible to make it
-the default using the global `branch.autosetupmerge` configuration
-flag. Otherwise, it can be chosen per-branch using the `--track`
+When a local branch is started off a remote branch, git sets up the
+branch so that linkgit:git-pull[1] will appropriately merge from that
+remote branch. If this behavior is not desired, it is possible to
+disable it using the global `branch.autosetupmerge` configuration
+flag. That setting can be overridden by using the `--track`
and `--no-track` options.
With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
@@ -45,17 +49,22 @@ to happen.
With a `-d` or `-D` option, `<branchname>` will be deleted. You may
specify more than one branch for deletion. If the branch currently
-has a reflog then the reflog will also be deleted. Use -r together with -d
-to delete remote-tracking branches.
+has a reflog then the reflog will also be deleted.
+
+Use -r together with -d to delete remote-tracking branches. Note, that it
+only makes sense to delete remote-tracking branches if they no longer exist
+in remote repository or if linkgit:git-fetch[1] was configured not to fetch
+them again. See also 'prune' subcommand of linkgit:git-remote[1] for way to
+clean up all obsolete remote-tracking branches.
OPTIONS
-------
-d::
- Delete a branch. The branch must be fully merged.
+ Delete a branch. The branch must be fully merged in HEAD.
-D::
- Delete a branch irrespective of its index status.
+ Delete a branch irrespective of its merged status.
-l::
Create the branch's reflog. This activates recording of
@@ -85,7 +94,7 @@ OPTIONS
-a::
List both remote-tracking branches and local branches.
--v::
+-v, --verbose::
Show sha1 and commit subject line for each head.
--abbrev=<length>::
@@ -99,13 +108,14 @@ OPTIONS
Set up configuration so that git-pull will automatically
retrieve data from the remote branch. Use this if you always
pull from the same remote branch into the new branch, or if you
- don't want to use "git pull <repository> <refspec>" explicitly. Set the
- branch.autosetupmerge configuration variable to true if you
+ don't want to use "git pull <repository> <refspec>" explicitly.
+ This behavior is the default. Set the
+ branch.autosetupmerge configuration variable to false if you
want git-checkout and git-branch to always behave as if
- '--track' were given.
+ '--no-track' were given.
--no-track::
- When -b is given and a branch is created off a remote branch,
+ When a branch is created off a remote branch,
set up configuration so that git-pull will not retrieve data
from the remote branch, ignoring the branch.autosetupmerge
configuration variable.
@@ -113,7 +123,7 @@ OPTIONS
<branchname>::
The name of the branch to create or delete.
The new branch name must pass all checks defined by
- gitlink:git-check-ref-format[1]. Some of these checks
+ linkgit:git-check-ref-format[1]. Some of these checks
may restrict the characters allowed in a branch name.
<start-point>::
@@ -153,9 +163,11 @@ $ git branch -d -r origin/todo origin/html origin/man <1>
$ git branch -D test <2>
------------
+
-<1> Delete remote-tracking branches "todo", "html", "man"
-<2> Delete "test" branch even if the "master" branch does not have all
-commits from test branch.
+<1> Delete remote-tracking branches "todo", "html", "man". Next 'fetch' or
+'pull' will create them again unless you configure them not to. See
+linkgit:git-fetch[1].
+<2> Delete "test" branch even if the "master" branch (or whichever branch is
+currently checked out) does not have all commits from test branch.
Notes
@@ -176,4 +188,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt
index 0cc6511bdf..72f080a972 100644
--- a/Documentation/git-bundle.txt
+++ b/Documentation/git-bundle.txt
@@ -23,7 +23,7 @@ be directly connected so the interactive git protocols (git, ssh,
rsync, http) cannot be used. This command provides support for
git-fetch and git-pull to operate by packaging objects and references
in an archive at the originating machine, then importing those into
-another repository using gitlink:git-fetch[1] and gitlink:git-pull[1]
+another repository using linkgit:git-fetch[1] and linkgit:git-pull[1]
after moving the archive by some means (i.e., by sneakernet). As no
direct connection between repositories exists, the user must specify a
basis for the bundle that is held by the destination repository: the
@@ -51,12 +51,12 @@ list-heads <file>::
printed out.
unbundle <file>::
- Passes the objects in the bundle to gitlink:git-index-pack[1]
+ Passes the objects in the bundle to linkgit:git-index-pack[1]
for storage in the repository, then prints the names of all
defined references. If a reflist is given, only references
matching those in the given list are printed. This command is
really plumbing, intended to be called only by
- gitlink:git-fetch[1].
+ linkgit:git-fetch[1].
[git-rev-list-args...]::
A list of arguments, acceptable to git-rev-parse and
@@ -73,7 +73,7 @@ unbundle <file>::
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 is
- acting like gitlink:git-fetch-pack[1]).
+ acting like linkgit:git-fetch-pack[1]).
SPECIFYING REFERENCES
---------------------
@@ -145,4 +145,4 @@ Written by Mark Levedahl <mdl123@verizon.net>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt
index afa095c795..df42cb10f2 100644
--- a/Documentation/git-cat-file.txt
+++ b/Documentation/git-cat-file.txt
@@ -21,7 +21,7 @@ OPTIONS
<object>::
The name of the object to show.
For a more complete list of ways to spell object names, see
- "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+ "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
-t::
Instead of the content, show the object type identified by
@@ -70,4 +70,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt
index 856d2af2ed..290f10f169 100644
--- a/Documentation/git-check-attr.txt
+++ b/Documentation/git-check-attr.txt
@@ -23,6 +23,11 @@ OPTIONS
be treated as an attribute.
+SEE ALSO
+--------
+linkgit:gitattributes[5].
+
+
Author
------
Written by Junio C Hamano <junkio@cox.net>
@@ -33,4 +38,4 @@ Documentation by James Bowes.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index 13a5f43049..a676880429 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -35,7 +35,7 @@ imposes the following rules on how refs are named:
These rules makes it easy for shell script based tools to parse
refnames, pathname expansion by the shell when a refname is used
unquoted (by mistake), and also avoids ambiguities in certain
-refname expressions (see gitlink:git-rev-parse[1]). Namely:
+refname expressions (see linkgit:git-rev-parse[1]). Namely:
. double-dot `..` are often used as in `ref1..ref2`, and in some
context this notation means `{caret}ref1 ref2` (i.e. not in
@@ -47,9 +47,9 @@ refname expressions (see gitlink:git-rev-parse[1]). Namely:
. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
value and store it in dstref" in fetch and push operations.
It may also be used to select a specific object such as with
- gitlink:git-cat-file[1] "git-cat-file blob v1.3.3:refs.c".
+ linkgit:git-cat-file[1] "git-cat-file blob v1.3.3:refs.c".
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt
index b1a8ce110c..cbbb0b5099 100644
--- a/Documentation/git-checkout-index.txt
+++ b/Documentation/git-checkout-index.txt
@@ -181,4 +181,4 @@ Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 2e58481ed6..b4cfa044bb 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -44,7 +44,7 @@ OPTIONS
-b::
Create a new branch named <new_branch> and start it at
<branch>. The new branch name must pass all checks defined
- by gitlink:git-check-ref-format[1]. Some of these checks
+ by linkgit:git-check-ref-format[1]. Some of these checks
may restrict the characters allowed in a branch name.
--track::
@@ -52,10 +52,11 @@ OPTIONS
set up configuration so that git-pull will automatically
retrieve data from the remote branch. Use this if you always
pull from the same remote branch into the new branch, or if you
- don't want to use "git pull <repository> <refspec>" explicitly. Set the
- branch.autosetupmerge configuration variable to true if you
+ don't want to use "git pull <repository> <refspec>" explicitly.
+ This behavior is the default. Set the
+ branch.autosetupmerge configuration variable to false if you
want git-checkout and git-branch to always behave as if
- '--track' were given.
+ '--no-track' were given.
--no-track::
When -b is given and a branch is created off a remote branch,
@@ -216,4 +217,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 47b1e8c2fc..877ab66ef5 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit
SYNOPSIS
--------
-'git-cherry-pick' [--edit] [-n] [-x] <commit>
+'git-cherry-pick' [--edit] [-n] [-m parent-number] [-x] <commit>
DESCRIPTION
-----------
@@ -20,18 +20,19 @@ OPTIONS
<commit>::
Commit to cherry-pick.
For a more complete list of ways to spell commits, see
- "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+ "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
-e|--edit::
With this option, `git-cherry-pick` will let you edit the commit
- message prior committing.
+ message prior to committing.
-x::
- Cause the command to append which commit was
- cherry-picked after the original commit message when
- making a commit. 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
+ 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
+ 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
other hand you are cherry-picking between two publicly
visible branches (e.g. backporting a fix to a
maintenance branch for an older release from a
@@ -43,6 +44,13 @@ OPTIONS
described above, and `-r` was to disable it. Now the
default is not to do `-x` so this option is a no-op.
+-m parent-number|--mainline parent-number::
+ Usually you cannot revert a merge because you do not know which
+ side of the merge should be considered the mainline. This
+ option specifies the parent number (starting from 1) of
+ the mainline and allows cherry-pick to replay the change
+ relative to the specified parent.
+
-n|--no-commit::
Usually the command automatically creates a commit with
a commit log message stating which commit was
@@ -67,4 +75,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt
index e6943822cd..b0468aa746 100644
--- a/Documentation/git-cherry.txt
+++ b/Documentation/git-cherry.txt
@@ -66,4 +66,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-citool.txt b/Documentation/git-citool.txt
index 5217ab2234..aca1d75e50 100644
--- a/Documentation/git-citool.txt
+++ b/Documentation/git-citool.txt
@@ -14,10 +14,10 @@ DESCRIPTION
A Tcl/Tk based graphical interface to review modified files, stage
them into the index, enter a commit message and record the new
commit onto the current branch. This interface is an alternative
-to the less interactive gitlink:git-commit[1] program.
+to the less interactive linkgit:git-commit[1] program.
git-citool is actually a standard alias for 'git gui citool'.
-See gitlink:git-gui[1] for more details.
+See linkgit:git-gui[1] for more details.
Author
------
@@ -29,4 +29,4 @@ Documentation by Shawn O. Pearce <spearce@spearce.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
index e3252d59da..5e9da036ba 100644
--- a/Documentation/git-clean.txt
+++ b/Documentation/git-clean.txt
@@ -39,7 +39,7 @@ OPTIONS
-x::
Don't use the ignore rules. This allows removing all untracked
files, including build products. This can be used (possibly in
- conjunction with gitlink:git-reset[1]) to create a pristine
+ conjunction with linkgit:git-reset[1]) to create a pristine
working directory to test a clean build.
-X::
@@ -54,4 +54,4 @@ Written by Pavel Roskin <proski@gnu.org>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 253f4f03c5..975824301a 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -12,7 +12,7 @@ SYNOPSIS
'git-clone' [--template=<template_directory>]
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare]
[-o <name>] [-u <upload-pack>] [--reference <repository>]
- [--depth <depth>] <repository> [<directory>]
+ [--depth <depth>] [--] <repository> [<directory>]
DESCRIPTION
-----------
@@ -62,6 +62,15 @@ OPTIONS
.git/objects/info/alternates to share the objects
with the source repository. The resulting repository
starts out without any object of its own.
++
+*NOTE*: this is a possibly dangerous operation; do *not* use
+it unless you understand what it does. If you clone your
+repository using this option, then delete branches in the
+source repository and then run linkgit:git-gc[1] using the
+'--prune' option in the source repository, it may remove
+objects which are referenced by the cloned repository.
+
+
--reference <repository>::
If the reference repository is on the local machine
@@ -111,11 +120,11 @@ OPTIONS
--depth <depth>::
Create a 'shallow' clone with a history truncated to the
- specified number of revs. A shallow repository has
+ specified number of revisions. A shallow repository has a
number of limitations (you cannot clone or fetch from
it, nor push from nor into it), but is adequate if you
- want to only look at near the tip of a large project
- with a long history, and would want to send in a fixes
+ are only interested in the recent history of a large project
+ with a long history, and would want to send in fixes
as patches.
<repository>::
@@ -130,6 +139,7 @@ OPTIONS
for "host.xz:foo/.git"). Cloning into an existing directory
is not allowed.
+:git-clone: 1
include::urls.txt[]
Examples
@@ -190,4 +200,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt
index a2537e179a..170803a6d0 100644
--- a/Documentation/git-commit-tree.txt
+++ b/Documentation/git-commit-tree.txt
@@ -13,7 +13,7 @@ SYNOPSIS
DESCRIPTION
-----------
This is usually not what an end user wants to run directly. See
-gitlink:git-commit[1] instead.
+linkgit:git-commit[1] instead.
Creates a new commit object based on the provided tree object and
emits the new commit object id on stdout. If no parent is given then
@@ -90,7 +90,7 @@ include::i18n.txt[]
See Also
--------
-gitlink:git-write-tree[1]
+linkgit:git-write-tree[1]
Author
@@ -103,4 +103,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index e54fb12103..b4ae61ff46 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -10,8 +10,8 @@ SYNOPSIS
[verse]
'git-commit' [-a | --interactive] [-s] [-v] [-u]
[(-c | -C) <commit> | -F <file> | -m <msg> | --amend]
- [--no-verify] [-e] [--author <author>]
- [--] [[-i | -o ]<file>...]
+ [--allow-empty] [--no-verify] [-e] [--author <author>]
+ [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
DESCRIPTION
-----------
@@ -20,11 +20,11 @@ commit along with a log message describing the changes you have made.
The content to be added can be specified in several ways:
-1. by using gitlink:git-add[1] to incrementally "add" changes to the
+1. by using linkgit:git-add[1] to incrementally "add" changes to the
index before using the 'commit' command (Note: even modified
files must be "added");
-2. by using gitlink:git-rm[1] to remove files from the working tree
+2. by using linkgit:git-rm[1] to remove files from the working tree
and the index, again before using the 'commit' command;
3. by listing files as arguments to the 'commit' command, in which
@@ -41,13 +41,13 @@ The content to be added can be specified in several ways:
by one which files should be part of the commit, before finalizing the
operation. Currently, this is done by invoking `git-add --interactive`.
-The gitlink:git-status[1] command can be used to obtain a
+The linkgit:git-status[1] command can be used to obtain a
summary of what is included by any of the above for the next
commit by giving the same set of parameters you would give to
this command.
If you make a commit and then found a mistake immediately after
-that, you can recover from it with gitlink:git-reset[1].
+that, you can recover from it with linkgit:git-reset[1].
OPTIONS
@@ -86,9 +86,25 @@ OPTIONS
Add Signed-off-by line at the end of the commit message.
--no-verify::
- This option bypasses the pre-commit hook.
+ This option bypasses the pre-commit and commit-msg hooks.
See also link:hooks.html[hooks].
+--allow-empty::
+ 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.
+
+--cleanup=<mode>::
+ This option sets how the commit message is cleaned up.
+ The '<mode>' can be one of 'verbatim', 'whitespace', 'strip',
+ and 'default'. The 'default' mode will strip leading and
+ trailing empty lines and #commentary from the commit message
+ only if the message is to be edited. Otherwise only whitespace
+ removed. The 'verbatim' mode does not change message at all,
+ 'whitespace' removes just leading/trailing whitespace lines
+ and 'strip' removes both whitespace and commentary.
+
-e|--edit::
The message taken from file with `-F`, command line with
`-m`, and from file with `-C` are usually used as the
@@ -154,10 +170,13 @@ EXAMPLES
--------
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 gitlink:git-add[1]. Removal
-of a file is staged with gitlink:git-rm[1]. After building the
-state to be committed incrementally with these commands, `git
-commit` (without any pathname parameter) is used to record what
+called the "index" with linkgit:git-add[1]. 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>`,
+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,
+`git commit` (without any pathname parameter) is used to record what
has been staged so far. This is the most basic form of the
command. An example:
@@ -210,13 +229,13 @@ $ git commit
this second commit would record the changes to `hello.c` and
`hello.h` as expected.
-After a merge (initiated by either gitlink:git-merge[1] or
-gitlink:git-pull[1]) stops because of conflicts, cleanly merged
+After a merge (initiated by either linkgit:git-merge[1] or
+linkgit:git-pull[1]) stops because of conflicts, cleanly merged
paths are already staged to be committed for you, and paths that
conflicted are left in unmerged state. You would have to first
-check which paths are conflicting with gitlink:git-status[1]
+check which paths are conflicting with linkgit:git-status[1]
and after fixing them manually in your working tree, you would
-stage the result as usual with gitlink:git-add[1]:
+stage the result as usual with linkgit:git-add[1]:
------------
$ git status | grep unmerged
@@ -261,18 +280,18 @@ order).
HOOKS
-----
-This command can run `commit-msg`, `pre-commit`, and
-`post-commit` hooks. See link:hooks.html[hooks] for more
+This command can run `commit-msg`, `prepare-commit-msg`, `pre-commit`,
+and `post-commit` hooks. See link:hooks.html[hooks] for more
information.
SEE ALSO
--------
-gitlink:git-add[1],
-gitlink:git-rm[1],
-gitlink:git-mv[1],
-gitlink:git-merge[1],
-gitlink:git-commit-tree[1]
+linkgit:git-add[1],
+linkgit:git-rm[1],
+linkgit:git-mv[1],
+linkgit:git-merge[1],
+linkgit:git-commit-tree[1]
Author
------
@@ -282,4 +301,4 @@ Junio C Hamano <junkio@cox.net>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index a592b61e2f..fa161718dd 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -20,6 +20,8 @@ SYNOPSIS
'git-config' [<file-option>] --rename-section old_name new_name
'git-config' [<file-option>] --remove-section name
'git-config' [<file-option>] [-z|--null] -l | --list
+'git-config' [<file-option>] --get-color name [default]
+'git-config' [<file-option>] --get-colorbool name [stdout-is-tty]
DESCRIPTION
-----------
@@ -128,12 +130,27 @@ See also <<FILES>>.
-z, --null::
For all options that output values and/or keys, always
- end values with with the null character (instead of a
+ end values with the null character (instead of a
newline). Use newline instead as a delimiter between
key and value. This allows for secure parsing of the
output without getting confused e.g. by values that
contain line breaks.
+--get-colorbool name [stdout-is-tty]::
+
+ Find the color setting for `name` (e.g. `color.diff`) and output
+ "true" or "false". `stdout-is-tty` should be either "true" or
+ "false", and is taken into account when configuration says
+ "auto". If `stdout-is-tty` is missing, then checks the standard
+ output of the command itself, and exits with status 0 if color
+ is to be used, or exits with status 1 otherwise.
+
+--get-color name default::
+
+ Find the color configured for `name` (e.g. `color.diff.new`) and
+ output it as the ANSI color escape sequence to the standard
+ output. The optional `default` parameter is used instead, if
+ there is no color configured for `name`.
[[FILES]]
FILES
@@ -292,6 +309,15 @@ To add a new proxy, without altering any of the existing ones, use
% git config core.gitproxy '"proxy-command" for example.com'
------------
+An example to use customized color from the configuration in your
+script:
+
+------------
+#!/bin/sh
+WS=$(git config --get-color color.diff.whitespace "blue reverse")
+RESET=$(git config --get-color "" "reset")
+echo "${WS}your whitespace color or blue reverse${RESET}"
+------------
include::config.txt[]
@@ -306,4 +332,4 @@ Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.ker
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt
index 81614111a4..7fb08e9348 100644
--- a/Documentation/git-count-objects.txt
+++ b/Documentation/git-count-objects.txt
@@ -34,4 +34,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt
index 4c8d1e6386..9a47b4c397 100644
--- a/Documentation/git-cvsexportcommit.txt
+++ b/Documentation/git-cvsexportcommit.txt
@@ -8,7 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout
SYNOPSIS
--------
-'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-w cvsworkdir] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
DESCRIPTION
@@ -16,8 +16,9 @@ DESCRIPTION
Exports a commit from GIT to a CVS checkout, making it easier
to merge patches from a git repository into a CVS repository.
-Execute it from the root of the CVS working copy. GIT_DIR must be defined.
-See examples below.
+Specify the name of a CVS checkout using the -w switch or execute it
+from the root of the CVS working copy. In the latter case GIT_DIR must
+be defined. See examples below.
It does its best to do the safe thing, it will check that the files are
unchanged and up to date in the CVS checkout, and it will not autocommit
@@ -61,6 +62,11 @@ OPTIONS
-u::
Update affected files from CVS repository before attempting export.
+-w::
+ Specify the location of the CVS checkout to use for the export. This
+ option does not require GIT_DIR to be set before execution if the
+ current directory is within a git repository.
+
-v::
Verbose.
@@ -73,7 +79,13 @@ Merge one patch into CVS::
$ export GIT_DIR=~/project/.git
$ cd ~/project_cvs_checkout
$ git-cvsexportcommit -v <commit-sha1>
-$ cvs commit -F .mgs <files>
+$ cvs commit -F .msg <files>
+------------
+
+Merge one patch into CVS (-c and -w options). The working directory is within the Git Repo::
++
+------------
+ $ git-cvsexportcommit -v -c -w ~/project_cvs_checkout <commit-sha1>
------------
Merge pending patches into CVS automatically -- only if you really know what you are doing::
@@ -86,12 +98,12 @@ $ git-cherry cvshead myhead | sed -n 's/^+ //p' | xargs -l1 git-cvsexportcommit
Author
------
-Written by Martin Langhoff <martin@catalyst.net.nz>
+Written by Martin Langhoff <martin@catalyst.net.nz> and others.
Documentation
--------------
-Documentation by Martin Langhoff <martin@catalyst.net.nz>
+Documentation by Martin Langhoff <martin@catalyst.net.nz> and others.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt
index fdd7ec7edd..6f91b9ea2a 100644
--- a/Documentation/git-cvsimport.txt
+++ b/Documentation/git-cvsimport.txt
@@ -107,8 +107,8 @@ If you need to pass multiple options, separate them with a comma.
-M <regex>::
Attempt to detect merges based on the commit message with a custom
- regex. It can be used with '-m' to also see the default regexes.
- You must escape forward slashes.
+ regex. It can be used with '-m' to enable the default regexes
+ as well. You must escape forward slashes.
-S <regex>::
Skip paths matching the regex.
@@ -142,7 +142,7 @@ file each time git-cvsimport is run.
+
It is not recommended to use this feature if you intend to
export changes back to CVS again later with
-gitlink:git-cvsexportcommit[1].
+linkgit:git-cvsexportcommit[1].
-h::
Print a short usage message and exit.
@@ -166,4 +166,4 @@ Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index 258a62f7e9..d3e99931d7 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -28,7 +28,7 @@ OPTIONS
-------
All these options obviously only make sense if enforced by the server side.
-They have been implemented to resemble the gitlink:git-daemon[1] options as
+They have been implemented to resemble the linkgit:git-daemon[1] options as
closely as possible.
--base-path <path>::
@@ -183,7 +183,7 @@ access method and requested operation.
That means that even if you offer only read access (e.g. by using
the pserver method), git-cvsserver should have write access to
the database to work reliably (otherwise you need to make sure
-that the database if up-to-date all the time git-cvsserver is run).
+that the database is up-to-date any time git-cvsserver is executed).
By default it uses SQLite databases in the git directory, named
`gitcvs.<module_name>.sqlite`. Note that the SQLite backend creates
@@ -204,7 +204,7 @@ about `DBI->connect()`.
gitcvs.dbname::
Database name. The exact meaning depends on the
- used database driver, for SQLite this is a filename.
+ selected database driver, for SQLite this is a filename.
Supports variable substitution (see below). May
not contain semicolons (`;`).
Default: '%Ggitcvs.%m.sqlite'
@@ -215,7 +215,7 @@ gitcvs.dbdriver::
with 'DBD::SQLite', reported to work with
'DBD::Pg', and reported *not* to work with 'DBD::mysql'.
Please regard this as an experimental feature. May not
- contain double colons (`:`).
+ contain colons (`:`).
Default: 'SQLite'
gitcvs.dbuser::
@@ -319,4 +319,4 @@ Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index 99e47c9c25..fd83bc7833 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -31,8 +31,8 @@ pass some directory paths as 'git-daemon' arguments, you can further restrict
the offers to a whitelist comprising of those.
By default, only `upload-pack` service is enabled, which serves
-`git-fetch-pack` and `git-peek-remote` clients that are invoked
-from `git-fetch`, `git-ls-remote`, and `git-clone`.
+`git-fetch-pack` and `git-ls-remote` clients, which are invoked
+from `git-fetch`, `git-pull`, and `git-clone`.
This is ideally suited for read-only updates, i.e., pulling from
git repositories.
@@ -166,7 +166,7 @@ the per-repository configuration file can be used to enable or
disable them.
upload-pack::
- This serves `git-fetch-pack` and `git-peek-remote`
+ This serves `git-fetch-pack` and `git-ls-remote`
clients. It is enabled by default, but a repository can
disable it by setting `daemon.uploadpack` configuration
item to `false`.
@@ -272,4 +272,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index ac23e28f27..1c3dfb40c6 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -51,6 +51,10 @@ OPTIONS
being employed to standard error. The tag name will still
be printed to standard out.
+--match <pattern>::
+ Only consider tags matching the given pattern (can be used to avoid
+ leaking private tags made from the repository).
+
EXAMPLES
--------
@@ -123,4 +127,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt
index d8a0a86022..6d2ea16a25 100644
--- a/Documentation/git-diff-files.txt
+++ b/Documentation/git-diff-files.txt
@@ -57,4 +57,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt
index 7bd262cefd..e867778590 100644
--- a/Documentation/git-diff-index.txt
+++ b/Documentation/git-diff-index.txt
@@ -129,4 +129,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt
index 6b3f74efe7..58d02c6a20 100644
--- a/Documentation/git-diff-tree.txt
+++ b/Documentation/git-diff-tree.txt
@@ -165,4 +165,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index ce0f502468..57c28628bb 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -21,7 +21,7 @@ tree and the index file, or the index file and the working tree.
the index (staging area for the next commit). In other
words, the differences are what you _could_ tell git to
further add to the index but you still haven't. You can
- stage these changes by using gitlink:git-add[1].
+ 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
@@ -67,14 +67,15 @@ for the last two forms that use ".." notations, can be any
<tree-ish>.
For a more complete list of ways to spell <commit>, see
-"SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
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 gitlink:git-rev-parse[1].
+"SPECIFYING RANGES" section in linkgit:git-rev-parse[1].
OPTIONS
-------
+:git-diff: 1
include::diff-options.txt[]
<path>...::
@@ -82,6 +83,9 @@ include::diff-options.txt[]
the diff to the named paths (you can give directory
names and get diff for all files under them).
+Output format
+-------------
+include::diff-format.txt[]
EXAMPLES
--------
@@ -164,4 +168,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt
new file mode 100644
index 0000000000..6dac475a0b
--- /dev/null
+++ b/Documentation/git-fast-export.txt
@@ -0,0 +1,83 @@
+git-fast-export(1)
+==================
+
+NAME
+----
+git-fast-export - Git data exporter
+
+
+SYNOPSIS
+--------
+'git-fast-export [options]' | 'git-fast-import'
+
+DESCRIPTION
+-----------
+This program dumps the given revisions in a form suitable to be piped
+into linkgit:git-fast-import[1].
+
+You can use it as a human readable bundle replacement (see
+linkgit:git-bundle[1]), or as a kind of an interactive
+linkgit:git-filter-branch[1].
+
+
+OPTIONS
+-------
+--progress=<n>::
+ Insert 'progress' statements every <n> objects, to be shown by
+ linkgit:git-fast-import[1] during import.
+
+--signed-tags=(verbatim|warn|strip|abort)::
+ Specify how to handle signed tags. Since any transformation
+ after the export can change the tag names (which can also happen
+ when excluding revisions) the signatures will not match.
++
+When asking to 'abort' (which is the default), this program will die
+when encountering a signed tag. With 'strip', the tags will be made
+unsigned, with 'verbatim', they will be silently exported
+and with 'warn', they will be exported, but you will see a warning.
+
+
+EXAMPLES
+--------
+
+-------------------------------------------------------------------
+$ git fast-export --all | (cd /empty/repository && git fast-import)
+-------------------------------------------------------------------
+
+This will export the whole repository and import it into the existing
+empty repository. Except for reencoding commits that are not in
+UTF-8, it would be a one-to-one mirror.
+
+-----------------------------------------------------
+$ git fast-export master~5..master |
+ sed "s|refs/heads/master|refs/heads/other|" |
+ git fast-import
+-----------------------------------------------------
+
+This makes a new branch called 'other' from 'master~5..master'
+(i.e. if 'master' has linear history, it will take the last 5 commits).
+
+Note that this assumes that none of the blobs and commit messages
+referenced by that revision range contains the string
+'refs/heads/master'.
+
+
+Limitations
+-----------
+
+Since linkgit:git-fast-import[1] 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[7] suite
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index d5119678b5..96f6767075 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -24,7 +24,7 @@ updated branch and tag refs, fully updating the current repository
with the newly imported data.
The fast-import backend itself can import into an empty repository (one that
-has already been initialized by gitlink:git-init[1]) or incrementally
+has already been initialized by linkgit:git-init[1]) or incrementally
update an existing populated repository. Whether or not incremental
imports are supported from a particular foreign source depends on
the frontend program in use.
@@ -82,7 +82,7 @@ OPTIONS
This information may be useful after importing projects
whose total object set exceeds the 4 GiB packfile limit,
as these commits can be used as edge points during calls
- to gitlink:git-pack-objects[1].
+ to linkgit:git-pack-objects[1].
--quiet::
Disable all non-fatal output, making fast-import silent when it
@@ -220,7 +220,7 @@ variation in formatting will cause fast-import to reject the value.
+
An example value is ``Tue Feb 6 11:22:18 2007 -0500''. The Git
parser is accurate, but a little on the lenient side. It is the
-same parser used by gitlink:git-am[1] when applying patches
+same parser used by linkgit:git-am[1] when applying patches
received from email.
+
Some malformed strings may be accepted as valid dates. In some of
@@ -256,7 +256,7 @@ timezone.
This particular format is supplied as its short to implement and
may be useful to a process that wants to create a new commit
right now, without needing to use a working directory or
-gitlink:git-update-index[1].
+linkgit:git-update-index[1].
+
If separate `author` and `committer` commands are used in a `commit`
the timestamps may not match, as the system clock will be polled
@@ -411,7 +411,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 gitlink:git-rev-parse[1] for details.
+ ``SPECIFYING REVISIONS'' in linkgit:git-rev-parse[1] for details.
The special case of restarting an incremental import from the
current branch value should be written as:
@@ -649,7 +649,7 @@ recommended, as the frontend does not (easily) have access to the
complete set of bytes which normally goes into such a signature.
If signing is required, create lightweight tags from within fast-import with
`reset`, then create the annotated versions of those tags offline
-with the standard gitlink:git-tag[1] process.
+with the standard linkgit:git-tag[1] process.
`reset`
~~~~~~~
@@ -805,6 +805,93 @@ 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.
+Crash Reports
+-------------
+If fast-import is supplied invalid input it will terminate with a
+non-zero exit status and create a crash report in the top level of
+the Git repository it was importing into. Crash reports contain
+a snapshot of the internal fast-import state as well as the most
+recent commands that lead up to the crash.
+
+All recent commands (including stream comments, file changes and
+progress commands) are shown in the command history within the crash
+report, but raw file data and commit messages are excluded from the
+crash report. This exclusion saves space within the report file
+and reduces the amount of buffering that fast-import must perform
+during execution.
+
+After writing a crash report fast-import will close the current
+packfile and export the marks table. This allows the frontend
+developer to inspect the repository state and resume the import from
+the point where it crashed. The modified branches and tags are not
+updated during a crash, as the import did not complete successfully.
+Branch and tag information can be found in the crash report and
+must be applied manually if the update is needed.
+
+An example crash:
+
+====
+ $ cat >in <<END_OF_INPUT
+ # my very first test commit
+ commit refs/heads/master
+ committer Shawn O. Pearce <spearce> 19283 -0400
+ # who is that guy anyway?
+ data <<EOF
+ this is my commit
+ EOF
+ M 644 inline .gitignore
+ data <<EOF
+ .gitignore
+ EOF
+ M 777 inline bob
+ END_OF_INPUT
+
+ $ git-fast-import <in
+ fatal: Corrupt mode: M 777 inline bob
+ fast-import: dumping crash report to .git/fast_import_crash_8434
+
+ $ cat .git/fast_import_crash_8434
+ fast-import crash report:
+ fast-import process: 8434
+ parent process : 1391
+ at Sat Sep 1 00:58:12 2007
+
+ fatal: Corrupt mode: M 777 inline bob
+
+ Most Recent Commands Before Crash
+ ---------------------------------
+ # my very first test commit
+ commit refs/heads/master
+ committer Shawn O. Pearce <spearce> 19283 -0400
+ # who is that guy anyway?
+ data <<EOF
+ M 644 inline .gitignore
+ data <<EOF
+ * M 777 inline bob
+
+ Active Branch LRU
+ -----------------
+ active_branches = 1 cur, 5 max
+
+ pos clock name
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 1) 0 refs/heads/master
+
+ Inactive Branches
+ -----------------
+ refs/heads/master:
+ status : active loaded dirty
+ tip commit : 0000000000000000000000000000000000000000
+ old tree : 0000000000000000000000000000000000000000
+ cur tree : 0000000000000000000000000000000000000000
+ commit clock: 0
+ last pack :
+
+
+ -------------------
+ END OF CRASH REPORT
+====
+
Tips and Tricks
---------------
The following tips and tricks have been collected from various
@@ -863,7 +950,7 @@ is not `refs/heads/TAG_FIXUP`).
When committing fixups, consider using `merge` to connect the
commit(s) which are supplying file revisions to the fixup branch.
-Doing so will allow tools such as gitlink:git-blame[1] to track
+Doing so will allow tools such as linkgit:git-blame[1] to track
through the real commit history and properly annotate the source
files.
@@ -892,7 +979,7 @@ Repacking Historical Data
~~~~~~~~~~~~~~~~~~~~~~~~~
If you are repacking very old imported data (e.g. older than the
last year), consider expending some extra CPU time and supplying
-\--window=50 (or higher) when you run gitlink:git-repack[1].
+\--window=50 (or higher) when you run linkgit:git-repack[1].
This will take longer, but will also produce a smaller packfile.
You only need to expend the effort once, and everyone using your
project will benefit from the smaller repository.
@@ -1027,4 +1114,4 @@ Documentation by Shawn O. Pearce <spearce@spearce.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
index a99a5b321f..2b8ffe5324 100644
--- a/Documentation/git-fetch-pack.txt
+++ b/Documentation/git-fetch-pack.txt
@@ -12,7 +12,7 @@ SYNOPSIS
DESCRIPTION
-----------
-Usually you would want to use gitlink:git-fetch[1] which is a
+Usually you would want to use linkgit:git-fetch[1] which is a
higher level wrapper of this command instead.
Invokes 'git-upload-pack' on a potentially remote repository,
@@ -93,4 +93,4 @@ Documentation by Junio C Hamano.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
index 9003473596..d982f961fc 100644
--- a/Documentation/git-fetch.txt
+++ b/Documentation/git-fetch.txt
@@ -39,7 +39,7 @@ include::urls-remotes.txt[]
SEE ALSO
--------
-gitlink:git-pull[1]
+linkgit:git-pull[1]
Author
@@ -53,4 +53,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index ba9b4fbca7..e22dfa5803 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -72,7 +72,7 @@ OPTIONS
This is the filter for modifying the environment in which
the commit will be performed. Specifically, you might want
to rewrite the author/committer name/email/time environment
- variables (see gitlink:git-commit[1] for details). Do not forget
+ variables (see linkgit:git-commit[1] for details). Do not forget
to re-export the variables.
--tree-filter <command>::
@@ -86,13 +86,13 @@ OPTIONS
--index-filter <command>::
This is the filter for rewriting the index. It is similar to the
tree filter but does not check out the tree, which makes it much
- faster. For hairy cases, see gitlink:git-update-index[1].
+ faster. For hairy cases, see linkgit:git-update-index[1].
--parent-filter <command>::
This is the filter for rewriting the commit's parent list.
It will receive the parent string on stdin and shall output
the new parent string on stdout. The parent string is in
- a format accepted by gitlink:git-commit-tree[1]: empty for
+ a format accepted by linkgit:git-commit-tree[1]: empty for
the initial commit, "-p parent" for a normal commit and
"-p parent1 -p parent2 -p parent3 ..." for a merge commit.
@@ -105,7 +105,7 @@ OPTIONS
--commit-filter <command>::
This is the filter for performing the commit.
If this filter is specified, it will be called instead of the
- gitlink:git-commit-tree[1] command, with arguments of the form
+ linkgit:git-commit-tree[1] command, with arguments of the form
"<TREE_ID> [-p <PARENT_COMMIT_ID>]..." and the log message on
stdin. The commit id is expected on stdout.
+
@@ -116,7 +116,7 @@ have all of them as parents.
You can use the 'map' convenience function in this filter, and other
convenience functions, too. For example, calling 'skip_commit "$@"'
will leave out the current commit (but not its changes! If you want
-that, use gitlink:git-rebase[1] instead).
+that, use linkgit:git-rebase[1] instead).
--tag-name-filter <command>::
This is the filter for rewriting tag names. When passed,
@@ -152,14 +152,14 @@ definition impossible to preserve signatures at any rate.)
does this in the '.git-rewrite/' directory but you can override
that choice by this parameter.
--f\|--force::
+-f|--force::
`git filter-branch` refuses to start with an existing temporary
directory or when there are already refs starting with
'refs/original/', unless forced.
<rev-list-options>::
When options are given after the new branch name, they will
- be passed to gitlink:git-rev-list[1]. Only commits in the resulting
+ be passed to linkgit:git-rev-list[1]. Only commits in the resulting
output will be filtered, although the filtered commits can still
reference parents which are outside of that set.
@@ -219,7 +219,7 @@ git filter-branch --commit-filter '
fi' HEAD
------------------------------------------------------------------------------
-The function 'skip_commits' is defined as follows:
+The function 'skip_commit' is defined as follows:
--------------------------
skip_commit()
@@ -249,7 +249,7 @@ will print.
*NOTE* the changes introduced by the commits, and which are not reverted
by subsequent commits, will still be in the rewritten branch. If you want
to throw out _changes_ together with the commits, you should use the
-interactive mode of gitlink:git-rebase[1].
+interactive mode of linkgit:git-rebase[1].
Consider this history:
@@ -295,4 +295,4 @@ Documentation by Petr Baudis and the git list.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt
index 7088ed4095..8615ae353e 100644
--- a/Documentation/git-fmt-merge-msg.txt
+++ b/Documentation/git-fmt-merge-msg.txt
@@ -46,7 +46,7 @@ merge.summary::
SEE ALSO
--------
-gitlink:git-merge[1]
+linkgit:git-merge[1]
Author
@@ -59,4 +59,4 @@ Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.o
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index c9857a2d62..651efe6ca1 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -9,9 +9,10 @@ git-format-patch - Prepare patches for e-mail submission
SYNOPSIS
--------
[verse]
-'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--thread]
+'git-format-patch' [-k] [-o <dir> | --stdout] [--thread]
[--attach[=<boundary>] | --inline[=<boundary>]]
[-s | --signoff] [<common diff options>]
+ [-n | --numbered | -N | --no-numbered]
[--start-number <n>] [--numbered-files]
[--in-reply-to=Message-Id] [--suffix=.<sfx>]
[--ignore-if-in-upstream]
@@ -24,7 +25,7 @@ DESCRIPTION
Prepare each commit with its patch in
one file per commit, formatted to resemble UNIX mailbox format.
The output of this command is convenient for e-mail submission or
-for use with gitlink:git-am[1].
+for use with linkgit:git-am[1].
There are two ways to specify which commits to operate on.
@@ -33,7 +34,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 gitlink:git-rev-parse[1]) means the
+ REVISIONS" section in linkgit:git-rev-parse[1]) means the
commits in the specified range.
A single commit, when interpreted as a <revision range>
@@ -65,6 +66,7 @@ reference.
OPTIONS
-------
+:git-format-patch: 1
include::diff-options.txt[]
-<n>::
@@ -77,6 +79,9 @@ include::diff-options.txt[]
-n|--numbered::
Name output in '[PATCH n/m]' format.
+-N|--no-numbered::
+ Name output in '[PATCH]' format.
+
--start-number <n>::
Start numbering the patches at <n> instead of 1.
@@ -142,15 +147,16 @@ not add any suffix.
CONFIGURATION
-------------
-You can specify extra mail header lines to be added to each
-message in the repository configuration. You can also specify
-new defaults for the subject prefix and file suffix.
+You can specify extra mail header lines to be added to each message
+in the repository configuration, new defaults for the subject prefix
+and file suffix, and number patches when outputting more than one.
------------
[format]
headers = "Organization: git-foo\n"
subjectprefix = CHANGE
suffix = .txt
+ numbered = auto
------------
@@ -168,7 +174,7 @@ git-format-patch origin::
is created in the current directory.
git-format-patch \--root origin::
- Extract all commits which that leads to 'origin' since the
+ Extract all commits that lead to 'origin' since the
inception of the project.
git-format-patch -M -B origin::
@@ -186,7 +192,7 @@ git-format-patch -3::
See Also
--------
-gitlink:git-am[1], gitlink:git-send-email[1]
+linkgit:git-am[1], linkgit:git-send-email[1]
Author
@@ -199,4 +205,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-fsck-objects.txt b/Documentation/git-fsck-objects.txt
index f21061ecfe..6e9f717642 100644
--- a/Documentation/git-fsck-objects.txt
+++ b/Documentation/git-fsck-objects.txt
@@ -13,5 +13,5 @@ SYNOPSIS
DESCRIPTION
-----------
-This is a synonym for gitlink:git-fsck[1]. Please refer to the
+This is a synonym for linkgit:git-fsck[1]. Please refer to the
documentation of that command.
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index 45c0bee50a..f16cb98612 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -150,4 +150,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt
index b9d5660eac..4b2dfefa6a 100644
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
@@ -15,11 +15,12 @@ DESCRIPTION
Runs a number of housekeeping tasks within the current repository,
such as compressing file revisions (to reduce disk space and increase
performance) and removing unreachable objects which may have been
-created from prior invocations of gitlink:git-add[1].
+created from prior invocations of linkgit:git-add[1].
Users are encouraged to run this task on a regular basis within
each repository to maintain good disk space utilization and good
-operating performance.
+operating performance. Some git commands may automatically run
+`git-gc`; see the `--auto` flag below for details.
OPTIONS
-------
@@ -44,18 +45,23 @@ OPTIONS
few hundred changesets or so.
--auto::
- With this option, `git gc` checks if there are too many
- loose objects in the repository and runs
- gitlink:git-repack[1] with `-d -l` option to pack them.
- The threshold for loose objects is set with `gc.auto` configuration
- variable, and can be disabled by setting it to 0. Some
- Porcelain commands use this after they perform operation
- that could create many loose objects automatically.
- Additionally, when there are too many packs are present,
- they are consolidated into one larger pack by running
- the `git-repack` command with `-A` option. The
- threshold for number of packs is set with
- `gc.autopacklimit` configuration variable.
+ With this option, `git gc` checks whether any housekeeping is
+ required; if not, it exits without performing any work.
+ Some git commands run `git gc --auto` after performing
+ operations that could create many loose objects.
++
+Housekeeping is required if there are too many loose objects or
+too many packs in the repository. If the number of loose objects
+exceeds the value of the `gc.auto` configuration variable, then
+all loose objects are combined into a single pack using
+`git-repack -d -l`. Setting the value of `gc.auto` to 0
+disables automatic packing of loose objects.
++
+If the number of packs exceeds the value of `gc.autopacklimit`,
+then existing packs (except those marked with a `.keep` file)
+are consolidated into a single pack by using the `-A` option of
+`git-repack`. Setting `gc.autopacklimit` to 0 disables
+automatic consolidation of packs.
Configuration
-------------
@@ -84,23 +90,23 @@ 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`. Without the configuration, `git-pack-refs`
-is not run in bare repositories by default, to allow older dumb-transport
-clients fetch from the repository, but this will change in the future.
+`git gc` runs `git-pack-refs`. This can be set to "nobare" to enable
+it within all non-bare repos or it can be set to a boolean value.
+This defaults to true.
The optional configuration variable 'gc.aggressiveWindow' controls how
much time is spent optimizing the delta compression of the objects in
the repository when the --aggressive option is specified. The larger
the value, the more time is spent optimizing the delta compression. See
-the documentation for the --window' option in gitlink:git-repack[1] for
+the documentation for the --window' option in linkgit:git-repack[1] for
more details. This defaults to 10.
See Also
--------
-gitlink:git-prune[1]
-gitlink:git-reflog[1]
-gitlink:git-repack[1]
-gitlink:git-rerere[1]
+linkgit:git-prune[1]
+linkgit:git-reflog[1]
+linkgit:git-repack[1]
+linkgit:git-rerere[1]
Author
------
@@ -108,4 +114,4 @@ Written by Shawn O. Pearce <spearce@spearce.org>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-get-tar-commit-id.txt b/Documentation/git-get-tar-commit-id.txt
index 9b5f86fc30..dea41490c4 100644
--- a/Documentation/git-get-tar-commit-id.txt
+++ b/Documentation/git-get-tar-commit-id.txt
@@ -3,7 +3,7 @@ git-get-tar-commit-id(1)
NAME
----
-git-get-tar-commit-id - Extract commit ID from an archive created using git-tar-tree
+git-get-tar-commit-id - Extract commit ID from an archive created using git-archive
SYNOPSIS
@@ -14,12 +14,12 @@ SYNOPSIS
DESCRIPTION
-----------
Acts as a filter, extracting the commit ID stored in archives created by
-git-tar-tree. It reads only the first 1024 bytes of input, thus its
+linkgit:git-archive[1]. It reads only the first 1024 bytes of input, thus its
runtime is not influenced by the size of <tarfile> very much.
If no commit ID is found, git-get-tar-commit-id quietly exists with a
return code of 1. This can happen if <tarfile> had not been created
-using git-tar-tree or if the first parameter of git-tar-tree had been
+using git-archive or if the first parameter of git-archive had been
a tree ID instead of a commit ID or tag.
@@ -33,4 +33,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 97faaa1d3a..71a73354f8 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -75,9 +75,11 @@ OPTIONS
-n::
Prefix the line number to matching lines.
--l | --files-with-matches | -L | --files-without-match::
+-l | --files-with-matches | --name-only | -L | --files-without-match::
Instead of showing every matched line, show only the
names of files that contain (or do not contain) matches.
+ For better compatability with git-diff, --name-only is a
+ synonym for --files-with-matches.
-c | --count::
Instead of showing every matched line, show the number of
@@ -143,4 +145,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt
index 13252a1aa6..6d6cd5d87c 100644
--- a/Documentation/git-gui.txt
+++ b/Documentation/git-gui.txt
@@ -16,7 +16,7 @@ on allowing users to make changes to their repository by making
new commits, amending existing ones, creating branches, performing
local merges, and fetching/pushing to remote repositories.
-Unlike gitlink:gitk[1], git-gui focuses on commit generation
+Unlike linkgit:gitk[1], git-gui focuses on commit generation
and single file annotation, and does not show project history.
It does however supply menu actions to start a gitk session from
within git-gui.
@@ -112,4 +112,4 @@ Documentation by Shawn O. Pearce <spearce@spearce.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt
index 616f196d81..33030c022f 100644
--- a/Documentation/git-hash-object.txt
+++ b/Documentation/git-hash-object.txt
@@ -42,4 +42,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt
new file mode 100644
index 0000000000..0926dc12ba
--- /dev/null
+++ b/Documentation/git-help.txt
@@ -0,0 +1,95 @@
+git-help(1)
+===========
+
+NAME
+----
+git-help - display help information about git
+
+SYNOPSIS
+--------
+'git help' [-a|--all|-i|--info|-m|--man|-w|--web] [COMMAND]
+
+DESCRIPTION
+-----------
+
+With no options and no COMMAND given, the synopsis of the 'git'
+command and a list of the most commonly used git commands are printed
+on the standard output.
+
+If the option '--all' or '-a' is given, then all available commands are
+printed on the standard output.
+
+If a git command is named, a manual page for that command is brought
+up. The 'man' program is used by default for this purpose, but this
+can be overridden by other options or configuration variables.
+
+Note that 'git --help ...' is identical as 'git help ...' because the
+former is internally converted into the latter.
+
+OPTIONS
+-------
+-a|--all::
+ Prints all the available commands on the standard output. This
+ option supersedes any other option.
+
+-i|--info::
+ Use the 'info' program to display the manual page, instead of
+ the 'man' program that is used by default.
+
+-m|--man::
+ Use the 'man' program to display the manual page. This may be
+ used to override a value set in the 'help.format'
+ configuration variable.
+
+-w|--web::
+ Use a web browser to display the HTML manual page, instead of
+ the 'man' program that is used by default.
++
+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
+(called by 'git-help') will pick a suitable default. See
+linkgit:git-web--browse[1] for more information about this.
+
+CONFIGURATION VARIABLES
+-----------------------
+
+If no command line option is passed, the 'help.format' configuration
+variable will be checked. The following values are supported for this
+variable; they make 'git-help' behave as their corresponding command
+line option:
+
+* "man" corresponds to '-m|--man',
+* "info" corresponds to '-i|--info',
+* "web" or "html" correspond to '-w|--web',
+
+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].
+
+Note that these configuration variables should probably be set using
+the '--global' flag, for example like this:
+
+------------------------------------------------
+$ git config --global help.format web
+$ 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[7] 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[7] suite
diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt
index 389c6edfb8..b784a9d07e 100644
--- a/Documentation/git-http-fetch.txt
+++ b/Documentation/git-http-fetch.txt
@@ -53,4 +53,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt
index 9afb860381..0b82722342 100644
--- a/Documentation/git-http-push.txt
+++ b/Documentation/git-http-push.txt
@@ -8,13 +8,16 @@ git-http-push - Push objects over HTTP/DAV to another repository
SYNOPSIS
--------
-'git-http-push' [--all] [--force] [--verbose] <url> <ref> [<ref>...]
+'git-http-push' [--all] [--dry-run] [--force] [--verbose] <url> <ref> [<ref>...]
DESCRIPTION
-----------
Sends missing objects to remote repository, and updates the
remote branch.
+*NOTE*: This command is temporarily disabled if your cURL
+library is older than 7.16, as the combination has been reported
+not to work and sometimes corrupts repository.
OPTIONS
-------
@@ -30,6 +33,9 @@ OPTIONS
the remote repository can lose commits; use it with
care.
+--dry-run::
+ Do everything except actually send the updates.
+
--verbose::
Report the list of objects being walked locally and the
list of objects successfully sent to the remote repository.
@@ -95,4 +101,4 @@ Documentation by Nick Hengeveld
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt
index eca9e9ccef..522b73c12f 100644
--- a/Documentation/git-imap-send.txt
+++ b/Documentation/git-imap-send.txt
@@ -59,4 +59,4 @@ Documentation by Mike McCormack
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
index bf5c2bddf4..72b5d00116 100644
--- a/Documentation/git-index-pack.txt
+++ b/Documentation/git-index-pack.txt
@@ -43,10 +43,10 @@ OPTIONS
a default name determined from the pack content. If
<pack-file> is not specified consider using --keep to
prevent a race condition between this process and
- gitlink::git-repack[1].
+ linkgit:git-repack[1].
--fix-thin::
- It is possible for gitlink:git-pack-objects[1] to build
+ It is possible for linkgit:git-pack-objects[1] to build
"thin" pack, which records objects in deltified form based on
objects not included in the pack to reduce network traffic.
Those objects are expected to be present on the receiving end
@@ -59,7 +59,7 @@ OPTIONS
Before moving the index into its final destination
create an empty .keep file for the associated pack file.
This option is usually necessary with --stdin to prevent a
- simultaneous gitlink:git-repack[1] process from deleting
+ simultaneous linkgit:git-repack[1] process from deleting
the newly constructed pack and index before refs can be
updated to use objects contained in the pack.
@@ -83,7 +83,7 @@ Once the index has been created, the list of object names is sorted
and the SHA1 hash of that list is printed to stdout. If --stdin was
also used then this is prefixed by either "pack\t", or "keep\t" if a
new .keep file was successfully created. This is useful to remove a
-.keep file used as a lock to prevent the race with gitlink:git-repack[1]
+.keep file used as a lock to prevent the race with linkgit:git-repack[1]
mentioned above.
@@ -97,4 +97,4 @@ Documentation by Sergey Vlasov
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
index d4e01cb325..439cabb737 100644
--- a/Documentation/git-init-db.txt
+++ b/Documentation/git-init-db.txt
@@ -14,5 +14,5 @@ SYNOPSIS
DESCRIPTION
-----------
-This is a synonym for gitlink:git-init[1]. Please refer to the
+This is a synonym for linkgit:git-init[1]. Please refer to the
documentation of that command.
diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt
index 07484a4fd0..62914da97b 100644
--- a/Documentation/git-init.txt
+++ b/Documentation/git-init.txt
@@ -52,7 +52,7 @@ is given:
- 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository
readable by all users.
-By default, the configuration flag receive.denyNonFastforward is enabled
+By default, the configuration flag receive.denyNonFastForwards is enabled
in shared repositories, so that you cannot force a non fast-forwarding push
into it.
@@ -111,4 +111,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt
index 735008c1ab..51f1532ef7 100644
--- a/Documentation/git-instaweb.txt
+++ b/Documentation/git-instaweb.txt
@@ -38,10 +38,11 @@ OPTIONS
The port number to bind the httpd to. (Default: 1234)
-b|--browser::
-
- The web browser command-line to execute to view the gitweb page.
- If blank, the URL of the gitweb instance will be printed to
- stdout. (Default: 'firefox')
+ The web browser that should be used to view the gitweb
+ page. This will be passed to the 'git-web--browse' helper
+ script along with the URL of the gitweb instance. See
+ linkgit:git-web--browse[1] for more information about this. If
+ the script fails, the URL will be printed to stdout.
--start::
Start the httpd instance and exit. This does not generate
@@ -71,6 +72,10 @@ 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>
@@ -81,4 +86,4 @@ Documentation by Eric Wong <normalperson@yhbt.net>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-local-fetch.txt b/Documentation/git-local-fetch.txt
deleted file mode 100644
index e830deeff3..0000000000
--- a/Documentation/git-local-fetch.txt
+++ /dev/null
@@ -1,66 +0,0 @@
-git-local-fetch(1)
-==================
-
-NAME
-----
-git-local-fetch - Duplicate another git repository on a local system
-
-
-SYNOPSIS
---------
-[verse]
-'git-local-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n]
- commit-id path
-
-DESCRIPTION
------------
-THIS COMMAND IS DEPRECATED.
-
-Duplicates another git repository on a local system.
-
-OPTIONS
--------
--c::
- Get the commit objects.
--t::
- Get trees associated with the commit objects.
--a::
- Get all the objects.
--v::
- Report what is downloaded.
--s::
- Instead of regular file-to-file copying use symbolic links to the objects
- in the remote repository.
--l::
- Before attempting symlinks (if -s is specified) or file-to-file copying the
- remote objects, try to hardlink the remote objects into the local
- repository.
--n::
- Never attempt to file-to-file copy remote objects. Only useful with
- -s or -l command-line options.
-
--w <filename>::
- Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on
- the local end after the transfer is complete.
-
---stdin::
- Instead of a commit id on the command line (which is not expected in this
- case), 'git-local-fetch' expects lines on stdin in the format
-
- <commit-id>['\t'<filename-as-in--w>]
-
---recover::
- Verify that everything reachable from target is fetched. Used after
- an earlier fetch is interrupted.
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 5ec547cc0c..ebaee4b334 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -14,18 +14,17 @@ DESCRIPTION
-----------
Shows the commit logs.
-The command takes options applicable to the gitlink:git-rev-list[1]
+The command takes options applicable to the linkgit:git-rev-list[1]
command to control what is shown and how, and options applicable to
-the gitlink:git-diff-tree[1] commands to control how the changes
+the linkgit:git-diff-tree[1] commands to control how the changes
each commit introduces are shown.
-This manual page describes only the most frequently used options.
-
OPTIONS
-------
-include::pretty-options.txt[]
+:git-log: 1
+include::diff-options.txt[]
-<n>::
Limits the number of commits to show.
@@ -36,21 +35,7 @@ include::pretty-options.txt[]
`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
- gitlink:git-rev-parse[1].
-
---first-parent::
- Follow only the first parent commit upon seeing a merge
- commit. This option gives a better overview of the
- evolution of a particular branch.
-
--p::
- Show the change the commit introduces in a patch form.
-
--g, \--walk-reflogs::
- Show commits as they were recorded in the reflog. The log contains
- a record about how the tip of a reference was changed.
- Cannot be combined with --reverse.
- See also gitlink:git-reflog[1].
+ linkgit:git-rev-parse[1].
--decorate::
Print out the ref names of any commits that are shown.
@@ -76,8 +61,11 @@ include::pretty-options.txt[]
Show only commits that affect the specified paths.
+include::rev-list-options.txt[]
+
include::pretty-formats.txt[]
+include::diff-generate-patch.txt[]
Examples
--------
@@ -124,4 +112,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-lost-found.txt b/Documentation/git-lost-found.txt
index bc739117be..b1c797f109 100644
--- a/Documentation/git-lost-found.txt
+++ b/Documentation/git-lost-found.txt
@@ -11,6 +11,10 @@ SYNOPSIS
DESCRIPTION
-----------
+
+*NOTE*: this command is deprecated. Use linkgit:git-fsck[1] with
+the option '--lost-found' instead.
+
Finds dangling commits and tags from the object database, and
creates refs to them in the .git/lost-found/ directory. Commits and
tags that dereference to commits are stored in .git/lost-found/commit,
@@ -74,4 +78,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index 9e454f0a4d..da9ebf405c 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -15,6 +15,7 @@ SYNOPSIS
[-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>]\*
@@ -77,6 +78,10 @@ OPTIONS
read additional exclude patterns that apply only to the
directory and its subdirectories in <file>.
+--exclude-standard::
+ Add the standard git exclusions: .git/info/exclude, .gitignore
+ in each directory, and the user's global exclusion file.
+
--error-unmatch::
If any <file> does not appear in the index, treat this as an
error (return 1).
@@ -100,7 +105,8 @@ OPTIONS
-v::
Similar to `-t`, but use lowercase letters for files
- that are marked as 'always matching index'.
+ that are marked as 'assume unchanged' (see
+ linkgit:git-update-index[1]).
--full-name::
When run from a subdirectory, the command usually
@@ -146,7 +152,7 @@ Exclude Patterns
'git-ls-files' can use a list of "exclude patterns" when
traversing the directory tree and finding files to show when the
-flags --others or --ignored are specified. gitlink:gitignore[5]
+flags --others or --ignored are specified. linkgit:gitignore[5]
specifies the format of exclude patterns.
These exclude patterns come from these places, in order:
@@ -173,7 +179,7 @@ pattern file appears in.
See Also
--------
-gitlink:git-read-tree[1], gitlink:gitignore[5]
+linkgit:git-read-tree[1], linkgit:gitignore[5]
Author
@@ -186,4 +192,4 @@ Documentation by David Greaves, Junio C Hamano, Josh Triplett, and the git-list
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt
index 93e9a60330..c5ba0aad13 100644
--- a/Documentation/git-ls-remote.txt
+++ b/Documentation/git-ls-remote.txt
@@ -27,10 +27,10 @@ OPTIONS
displayed.
-u <exec>, --upload-pack=<exec>::
- Specify the full path of gitlink:git-upload-pack[1] on the remote
+ Specify the full path of linkgit:git-upload-pack[1] on the remote
host. This allows listing references from repositories accessed via
SSH and where the SSH daemon does not use the PATH configured by the
- user. Also see the '--exec' option for gitlink:git-peek-remote[1].
+ user.
<repository>::
Location of the repository. The shorthand defined in
@@ -69,4 +69,4 @@ Written by Junio C Hamano <junkio@cox.net>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index 7b78599673..360c0a1b98 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -91,4 +91,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index 64aa6a1ea6..3846f0e6eb 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -18,7 +18,7 @@ writes the commit log message in <msg> file, and the patches in
<patch> file. The author name, e-mail and e-mail subject are
written out to the standard output to be used by git-am
to create a commit. It is usually not necessary to use this
-command directly. See gitlink:git-am[1] instead.
+command directly. See linkgit:git-am[1] instead.
OPTIONS
@@ -66,4 +66,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt
index c4f4cabbdc..8243f69113 100644
--- a/Documentation/git-mailsplit.txt
+++ b/Documentation/git-mailsplit.txt
@@ -55,4 +55,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 6b71880ec4..07f78b4ae0 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -39,4 +39,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt
index 31882abb87..c513184ba0 100644
--- a/Documentation/git-merge-file.txt
+++ b/Documentation/git-merge-file.txt
@@ -41,7 +41,7 @@ conflicts otherwise. If the merge was clean, the exit value is 0.
git-merge-file is designed to be a minimal clone of RCS merge, that is, it
implements all of RCS merge's functionality which is needed by
-gitlink:git[1].
+linkgit:git[1].
OPTIONS
@@ -89,4 +89,4 @@ with parts copied from the original documentation of RCS merge.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt
index b726ddfe12..19ee017aed 100644
--- a/Documentation/git-merge-index.txt
+++ b/Documentation/git-merge-index.txt
@@ -8,7 +8,7 @@ git-merge-index - Run a merge for files needing merging
SYNOPSIS
--------
-'git-merge-index' [-o] [-q] <merge-program> (-a | \-- | <file>\*)
+'git-merge-index' [-o] [-q] <merge-program> (-a | [--] <file>\*)
DESCRIPTION
-----------
@@ -84,4 +84,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-merge-one-file.txt b/Documentation/git-merge-one-file.txt
index f35d0e1b45..ee95df3bc0 100644
--- a/Documentation/git-merge-one-file.txt
+++ b/Documentation/git-merge-one-file.txt
@@ -26,4 +26,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt
index 6892fdac3d..4cc0964e78 100644
--- a/Documentation/git-merge-tree.txt
+++ b/Documentation/git-merge-tree.txt
@@ -33,4 +33,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index bca4212e56..0c9ad7f2bb 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -11,26 +11,27 @@ SYNOPSIS
[verse]
'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]...
[-m <msg>] <remote> <remote>...
+'git-merge' <msg> HEAD <remote>...
DESCRIPTION
-----------
This is the top-level interface to the merge machinery
which drives multiple merge strategy scripts.
+The second syntax (<msg> `HEAD` <remote>) 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> <remote>`.
+
OPTIONS
-------
include::merge-options.txt[]
-<msg>::
+-m <msg>::
The commit message to be used for the merge commit (in case
it is created). The `git-fmt-merge-msg` script can be used
to give a good default for automated `git-merge` invocations.
-<head>::
- Our branch head commit. This has to be `HEAD`, so new
- syntax does not require it
-
<remote>::
Other branch head merged into our branch. You need at
least one <remote>. Specifying more than one <remote>
@@ -41,7 +42,7 @@ include::merge-strategies.txt[]
If you tried a merge which resulted in a complex conflicts and
would want to start over, you can recover with
-gitlink:git-reset[1].
+linkgit:git-reset[1].
CONFIGURATION
-------------
@@ -73,14 +74,14 @@ it happens. In other words, `git-diff --cached HEAD` must
report no changes.
[NOTE]
-This is a bit of lie. In certain special cases, your index are
-allowed to be different from the tree of `HEAD` commit. The most
+This is a bit of a lie. In certain special cases, your index is
+allowed to be different from the tree of the `HEAD` commit. The most
notable case is when your `HEAD` commit is already ahead of what
is being merged, in which case your index can have arbitrary
-difference from your `HEAD` commit. Otherwise, your index entries
-are allowed have differences from your `HEAD` commit that match
-the result of trivial merge (e.g. you received the same patch
-from external source to produce the same result as what you are
+differences from your `HEAD` commit. Also, your index entries
+may have differences from your `HEAD` commit that match
+the result of a trivial merge (e.g. you received the same patch
+from an external source to produce the same result as what you are
merging). For example, if a path did not exist in the common
ancestor and your head commit but exists in the tree you are
merging into your repository, and if you already happen to have
@@ -162,7 +163,8 @@ After seeing a conflict, you can do two things:
SEE ALSO
--------
-gitlink:git-fmt-merge-msg[1], gitlink:git-pull[1]
+linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1],
+linkgit:gitattributes[5]
Author
@@ -176,4 +178,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt
index 6c32c6d18e..50f106ec5b 100644
--- a/Documentation/git-mergetool.txt
+++ b/Documentation/git-mergetool.txt
@@ -13,7 +13,7 @@ DESCRIPTION
-----------
Use 'git mergetool' to run one of several merge utilities to resolve
-merge conflicts. It is typically run after gitlink:git-merge[1].
+merge conflicts. It is typically run after linkgit:git-merge[1].
If one or more <file> parameters are given, the merge tool program will
be run to resolve differences on each file. If no <file> names are
@@ -25,12 +25,18 @@ OPTIONS
-t or --tool=<tool>::
Use the merge resolution program specified by <tool>.
Valid merge tools are:
- kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, and opendiff
+ kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff
+
If a merge resolution program is not specified, 'git mergetool'
will use the configuration variable merge.tool. If the
configuration variable merge.tool is not set, 'git mergetool'
will pick a suitable default.
++
+You can explicitly provide a full path to the tool by setting the
+configuration variable mergetool.<tool>.path. For example, you
+can configure the absolute path to kdiff3 by setting
+mergetool.kdiff3.path. Otherwise, 'git mergetool' assumes the tool
+is available in PATH.
Author
------
@@ -42,4 +48,4 @@ Documentation by Theodore Y Ts'o.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-mktag.txt b/Documentation/git-mktag.txt
index ea7a75234a..82db9f5d8f 100644
--- a/Documentation/git-mktag.txt
+++ b/Documentation/git-mktag.txt
@@ -43,4 +43,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-mktree.txt b/Documentation/git-mktree.txt
index 638abc7d0f..f312036ab5 100644
--- a/Documentation/git-mktree.txt
+++ b/Documentation/git-mktree.txt
@@ -31,4 +31,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt
index 2c9cf743c7..bff3fbe745 100644
--- a/Documentation/git-mv.txt
+++ b/Documentation/git-mv.txt
@@ -34,7 +34,7 @@ OPTIONS
condition. An error happens when a source is neither existing nor
controlled by GIT, or when it would overwrite an existing
file unless '-f' is given.
--n::
+-n, \--dry-run::
Do nothing; only show what would happen
@@ -50,4 +50,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt
index 306e1a4956..efcabdc272 100644
--- a/Documentation/git-name-rev.txt
+++ b/Documentation/git-name-rev.txt
@@ -38,7 +38,7 @@ OPTIONS
Instead of printing both the SHA-1 and the name, print only
the name. If given with --tags the usual tag prefix of
"tags/" is also omitted from the name, matching the output
- of gitlink::git-describe[1] more closely. This option
+ of linkgit:git-describe[1] more closely. This option
cannot be combined with --stdin.
EXAMPLE
@@ -75,4 +75,4 @@ Documentation by Johannes Schindelin.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index 5237ab0c04..8353be186f 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -58,7 +58,7 @@ base-name::
--revs::
Read the revision arguments from the standard input, instead of
individual object names. The revision arguments are processed
- the same way as gitlink:git-rev-list[1] with `--objects` flag
+ the same way as linkgit:git-rev-list[1] with `--objects` flag
uses its `commit` arguments to build the list of objects it
outputs. The objects on the resulting list are packed.
@@ -99,7 +99,8 @@ base-name::
--max-pack-size=<n>::
Maximum size of each output packfile, expressed in MiB.
If specified, multiple packfiles may be created.
- The default is unlimited.
+ The default is unlimited, unless the config variable
+ `pack.packSizeLimit` is set.
--incremental::
This flag causes an object already in a pack ignored
@@ -193,10 +194,10 @@ Documentation by Junio C Hamano
See Also
--------
-gitlink:git-rev-list[1]
-gitlink:git-repack[1]
-gitlink:git-prune-packed[1]
+linkgit:git-rev-list[1]
+linkgit:git-repack[1]
+linkgit:git-prune-packed[1]
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt
index f2ceebac4b..af4aa4a2e5 100644
--- a/Documentation/git-pack-redundant.txt
+++ b/Documentation/git-pack-redundant.txt
@@ -48,10 +48,10 @@ Documentation by Lukas Sandström <lukass@etek.chalmers.se>
See Also
--------
-gitlink:git-pack-objects[1]
-gitlink:git-repack[1]
-gitlink:git-prune-packed[1]
+linkgit:git-pack-objects[1]
+linkgit:git-repack[1]
+linkgit:git-prune-packed[1]
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt
index a20fc7de40..e4ff934711 100644
--- a/Documentation/git-pack-refs.txt
+++ b/Documentation/git-pack-refs.txt
@@ -63,4 +63,4 @@ Written by Linus Torvalds <torvalds@osdl.org>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-parse-remote.txt b/Documentation/git-parse-remote.txt
index 11b1f4d2e2..deb8b2f01e 100644
--- a/Documentation/git-parse-remote.txt
+++ b/Documentation/git-parse-remote.txt
@@ -47,4 +47,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt
index ad528a9224..894852a78b 100644
--- a/Documentation/git-patch-id.txt
+++ b/Documentation/git-patch-id.txt
@@ -39,4 +39,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt
index abc171266a..0001710072 100644
--- a/Documentation/git-peek-remote.txt
+++ b/Documentation/git-peek-remote.txt
@@ -12,8 +12,7 @@ SYNOPSIS
DESCRIPTION
-----------
-Lists the references the remote repository has, and optionally
-stores them in the local repository under the same name.
+This command is deprecated; use `git-ls-remote` instead.
OPTIONS
-------
@@ -29,9 +28,6 @@ OPTIONS
shells, but prefer having a lean .bashrc file (they set most of
the things up in .bash_profile).
-\--exec=<git-upload-pack>::
- Same \--upload-pack=<git-upload-pack>.
-
<host>::
A remote host that houses the repository. When this
part is specified, 'git-upload-pack' is invoked via
@@ -51,4 +47,4 @@ Documentation by Junio C Hamano.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt
index 9f85f3833e..93ee82ae57 100644
--- a/Documentation/git-prune-packed.txt
+++ b/Documentation/git-prune-packed.txt
@@ -44,9 +44,9 @@ Documentation by Ryan Anderson <ryan@michonline.com>
See Also
--------
-gitlink:git-pack-objects[1]
-gitlink:git-repack[1]
+linkgit:git-pack-objects[1]
+linkgit:git-repack[1]
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index 0ace233d18..f151cff5d9 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -8,7 +8,7 @@ git-prune - Prune all unreachable objects from the object database
SYNOPSIS
--------
-'git-prune' [-n] [--] [<head>...]
+'git-prune' [-n] [--expire <expire>] [--] [<head>...]
DESCRIPTION
-----------
@@ -31,6 +31,9 @@ OPTIONS
\--::
Do not interpret any more arguments as options.
+\--expire <time>::
+ Only expire loose objects older than <time>.
+
<head>...::
In addition to objects
reachable from any of our references, keep objects
@@ -57,4 +60,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index e1eb2c1d00..737894390d 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -15,6 +15,7 @@ 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
@@ -25,6 +26,24 @@ OPTIONS
-------
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`.
++
+*NOTE:* This is a potentially _dangerous_ mode of operation.
+It rewrites history, which does not bode well when you
+published that history already. Do *not* use this option
+unless you have read linkgit:git-rebase[1] carefully.
+
+\--no-rebase::
+ Override earlier \--rebase.
+
include::fetch-options.txt[]
include::pull-fetch-param.txt[]
@@ -96,7 +115,7 @@ git pull, git pull origin::
current branch. Normally the branch merged in is
the HEAD of the remote repository, but the choice is
determined by the branch.<name>.remote and
- branch.<name>.merge options; see gitlink:git-config[1]
+ branch.<name>.merge options; see linkgit:git-config[1]
for details.
git pull origin next::
@@ -143,12 +162,12 @@ The final command then merges the newly fetched `tmp` into master.
If you tried a pull which resulted in a complex conflicts and
would want to start over, you can recover with
-gitlink:git-reset[1].
+linkgit:git-reset[1].
SEE ALSO
--------
-gitlink:git-fetch[1], gitlink:git-merge[1], gitlink:git-config[1]
+linkgit:git-fetch[1], linkgit:git-merge[1], linkgit:git-config[1]
Author
@@ -164,4 +183,4 @@ Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index e5dd4c1066..3128170bcd 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git-push' [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>]
- [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]
+ [--repo=all] [-f | --force] [-v | --verbose] [<repository> <refspec>...]
DESCRIPTION
-----------
@@ -20,7 +20,7 @@ necessary to complete the given refs.
You can make interesting things happen to a repository
every time you push into it, by setting up 'hooks' there. See
-documentation for gitlink:git-receive-pack[1].
+documentation for linkgit:git-receive-pack[1].
OPTIONS
@@ -47,9 +47,9 @@ even if it does not result in a fast forward update.
+
Note: If no explicit refspec is found, (that is neither
on the command line nor in any Push line of the
-corresponding remotes file---see below), then all the
-heads that exist both on the local side and on the remote
-side are updated.
+corresponding remotes file---see below), then "matching" heads are
+pushed: for every head that exists on the local side, the remote side is
+updated if a head of the same name already exists on the remote side.
+
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
+
@@ -63,6 +63,14 @@ the remote repository.
Instead of naming each ref to push, specifies that all
refs under `$GIT_DIR/refs/heads/` be pushed.
+\--mirror::
+ Instead of naming each ref to push, specifies that all
+ refs under `$GIT_DIR/refs/heads/` and `$GIT_DIR/refs/tags/`
+ be mirrored to the remote repository. Newly created local
+ refs will be pushed to the remote end, locally updated refs
+ will be force updated on the remote end, and deleted refs
+ will be removed from the remote end.
+
\--dry-run::
Do everything except actually send the updates.
@@ -95,11 +103,60 @@ the remote repository.
transfer spends extra cycles to minimize the number of
objects to be sent and meant to be used on slower connection.
--v::
+-v, \--verbose::
Run verbosely.
include::urls-remotes.txt[]
+OUTPUT
+------
+
+The output of "git push" depends on the transport method used; this
+section describes the output when pushing over the git protocol (either
+locally or via ssh).
+
+The status of the push is output in tabular form, with each line
+representing the status of a single ref. Each line is of the form:
+
+-------------------------------
+ <flag> <summary> <from> -> <to> (<reason>)
+-------------------------------
+
+flag::
+ A single character indicating the status of the ref. This is
+ blank for a successfully pushed ref, `!` for a ref that was
+ rejected or failed to push, and '=' for a ref that was up to
+ date and did not need pushing (note that the status of up to
+ date refs is shown only when `git push` is running verbosely).
+
+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).
+
+from::
+ The name of the local ref being pushed, minus its
+ `refs/<type>/` prefix. In the case of deletion, the
+ name of the local ref is omitted.
+
+to::
+ The name of the remote ref being updated, minus its
+ `refs/<type>/` prefix.
+
+reason::
+ A human-readable explanation. In the case of successfully pushed
+ refs, no explanation is needed. For a failed ref, the reason for
+ failure is described.
Examples
--------
@@ -137,4 +194,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt
index 1c3ef4c593..0fc2b56c12 100644
--- a/Documentation/git-quiltimport.txt
+++ b/Documentation/git-quiltimport.txt
@@ -57,4 +57,4 @@ Documentation by Eric Biederman <ebiederm@lnxi.com>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index 74c5478ba1..8421d1fd78 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -15,7 +15,7 @@ DESCRIPTION
-----------
Reads the tree information given by <tree-ish> into the index,
but does not actually *update* any of the files it "caches". (see:
-gitlink:git-checkout-index[1])
+linkgit:git-checkout-index[1])
Optionally, it can merge a tree into the index, perform a
fast-forward (i.e. 2-way) merge, or a 3-way merge, with the `-m`
@@ -347,8 +347,8 @@ have finished your work-in-progress), attempt the merge again.
See Also
--------
-gitlink:git-write-tree[1]; gitlink:git-ls-files[1];
-gitlink:gitignore[5]
+linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
+linkgit:gitignore[5]
Author
@@ -361,4 +361,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index e4326d3322..c11c6453ea 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -235,7 +235,7 @@ OPTIONS
--whitespace=<nowarn|warn|error|error-all|strip>::
This flag is passed to the `git-apply` program
- (see gitlink:git-apply[1]) that applies the patch.
+ (see linkgit:git-apply[1]) that applies the patch.
-i, \--interactive::
Make a list of the commits which are about to be rebased. Let the
@@ -374,8 +374,8 @@ add other commits. This can be used to split a commit into two:
However, the working tree stays the same.
- Now add the changes to the index that you want to have in the first
- commit. You can use gitlink:git-add[1] (possibly interactively) and/or
- gitlink:git-gui[1] to do that.
+ commit. You can use linkgit:git-add[1] (possibly interactively) and/or
+ linkgit:git-gui[1] to do that.
- Commit the now-current index with whatever commit message is appropriate
now.
@@ -386,7 +386,7 @@ add other commits. This can be used to split a commit into two:
If you are not absolutely sure that the intermediate revisions are
consistent (they compile, pass the testsuite, etc.) you should use
-gitlink:git-stash[1] to stash away the not-yet-committed changes
+linkgit:git-stash[1] to stash away the not-yet-committed changes
after each commit, test, and amend the commit if fixes are necessary.
@@ -401,4 +401,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 2633d94c59..4111434bb6 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -149,7 +149,7 @@ if the repository is packed and is served via a dumb transport.
SEE ALSO
--------
-gitlink:git-send-pack[1]
+linkgit:git-send-pack[1]
Author
@@ -162,4 +162,4 @@ Documentation by Junio C Hamano.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index a0c7ceead2..5719039841 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -21,7 +21,7 @@ git reflog expire [--dry-run] [--stale-fix] [--verbose]
git reflog delete ref@\{specifier\}...
-git reflog [show] [log-options]
+git reflog [show] [log-options] [<ref>]
Reflog is a mechanism to record when the tip of branches are
updated. This command is to manage the information recorded in it.
@@ -30,13 +30,20 @@ The subcommand "expire" is used to prune older reflog entries.
Entries older than `expire` time, or entries older than
`expire-unreachable` time and are not reachable from the current
tip, are removed from the reflog. This is typically not used
-directly by the end users -- instead, see gitlink:git-gc[1].
+directly by the end users -- instead, see linkgit:git-gc[1].
The subcommand "show" (which is also the default, in the absence of any
subcommands) will take all the normal log options, and show the log of
-`HEAD`, which will cover all recent actions, including branch switches.
-It is basically an alias for 'git log -g --abbrev-commit
---pretty=oneline', see gitlink:git-log[1].
+the reference provided in the command-line (or `HEAD`, by default).
+The reflog will cover all recent actions (HEAD reflog records branch switching
+as well). It is an alias for 'git log -g --abbrev-commit --pretty=oneline';
+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
+more details.
To delete single entries from the reflog, use the subcommand "delete"
and specify the _exact_ entry (e.g. ``git reflog delete master@\{2\}'').
@@ -86,4 +93,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt
index fe631bb3dd..1b024ded33 100644
--- a/Documentation/git-relink.txt
+++ b/Documentation/git-relink.txt
@@ -7,12 +7,13 @@ git-relink - Hardlink common objects in local repositories
SYNOPSIS
--------
-'git-relink' [--safe] <dir> <dir> [<dir>]\*
+'git-relink' [--safe] <dir> [<dir>]\* <master_dir>
DESCRIPTION
-----------
-This will scan 2 or more object repositories and look for common objects, check
-if they are hardlinked, and replace one with a hardlink to the other if not.
+This will scan 1 or more object repositories and look for objects in common
+with a master repository. Objects not already hardlinked to the master
+repository will be replaced with a hardlink to the master repository.
OPTIONS
-------
@@ -33,4 +34,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 027ba11bdb..2cbd1f764b 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git-remote'
-'git-remote' add [-t <branch>] [-m <branch>] [-f] [--mirror] <name> <url>
+'git-remote' add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
'git-remote' rm <name>
'git-remote' show <name>
'git-remote' prune <name>
@@ -79,9 +79,9 @@ caution.
Fetch updates for a named set of remotes in the repository as defined by
remotes.<group>. If a named group is not specified on the command line,
the configuration parameter remotes.default will get used; if
-remotes.default is not defined, all remotes which do not the
+remotes.default is not defined, all remotes which do not have the
configuration parameter remote.<name>.skipDefaultUpdate set to true will
-be updated. (See gitlink:git-config[1]).
+be updated. (See linkgit:git-config[1]).
DISCUSSION
@@ -89,7 +89,7 @@ DISCUSSION
The remote configuration is achieved using the `remote.origin.url` and
`remote.origin.fetch` configuration variables. (See
-gitlink:git-config[1]).
+linkgit:git-config[1]).
Examples
--------
@@ -101,7 +101,7 @@ $ git remote
origin
$ git branch -r
origin/master
-$ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
+$ git remote add linux-nfs git://linux-nfs.org/pub/linux/nfs-2.6.git
$ git remote
linux-nfs
origin
@@ -128,9 +128,9 @@ $ git merge origin
See Also
--------
-gitlink:git-fetch[1]
-gitlink:git-branch[1]
-gitlink:git-config[1]
+linkgit:git-fetch[1]
+linkgit:git-branch[1]
+linkgit:git-config[1]
Author
------
@@ -144,4 +144,4 @@ Documentation by J. Bruce Fields and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index 12e2079a7c..3d957492f8 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -40,19 +40,19 @@ OPTIONS
-d::
After packing, if the newly created packs make some
existing packs redundant, remove the redundant packs.
- Also runs gitlink:git-prune-packed[1].
+ Also runs linkgit:git-prune-packed[1].
-l::
Pass the `--local` option to `git pack-objects`, see
- gitlink:git-pack-objects[1].
+ linkgit:git-pack-objects[1].
-f::
Pass the `--no-reuse-delta` option to `git pack-objects`, see
- gitlink:git-pack-objects[1].
+ linkgit:git-pack-objects[1].
-q::
Pass the `-q` option to `git pack-objects`, see
- gitlink:git-pack-objects[1].
+ linkgit:git-pack-objects[1].
-n::
Do not update the server information with
@@ -109,9 +109,9 @@ Documentation by Ryan Anderson <ryan@michonline.com>
See Also
--------
-gitlink:git-pack-objects[1]
-gitlink:git-prune-packed[1]
+linkgit:git-pack-objects[1]
+linkgit:git-prune-packed[1]
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index 2deba31763..2ca39946b7 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -14,5 +14,5 @@ SYNOPSIS
DESCRIPTION
-----------
-This is a synonym for gitlink:git-config[1]. Please refer to the
+This is a synonym for linkgit:git-config[1]. Please refer to the
documentation of that command.
diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt
index 087eeb7cc2..270df9b185 100644
--- a/Documentation/git-request-pull.txt
+++ b/Documentation/git-request-pull.txt
@@ -36,4 +36,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index c4d4263238..a53858e250 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -23,8 +23,8 @@ initial manual merge, and later by noticing the same automerge
results and applying the previously recorded hand resolution.
[NOTE]
-You need to set the config variable rerere.enabled to enable this
-command.
+You need to set the configuration variable rerere.enabled to
+enable this command.
COMMANDS
@@ -37,7 +37,7 @@ its working state.
'clear'::
This resets the metadata used by rerere if a merge resolution is to be
-is aborted. Calling gitlink:git-am[1] --skip or gitlink:git-rebase[1]
+is aborted. Calling linkgit:git-am[1] --skip or linkgit:git-rebase[1]
[--skip|--abort] will automatically invoke this command.
'diff'::
@@ -208,4 +208,4 @@ Written by Junio C Hamano <junkio@cox.net>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 15e3aca9a1..fac59c9726 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -8,8 +8,8 @@ git-reset - Reset current HEAD to the specified state
SYNOPSIS
--------
[verse]
-'git-reset' [--mixed | --soft | --hard] [<commit>]
-'git-reset' [--mixed] <commit> [--] <paths>...
+'git reset' [--mixed | --soft | --hard] [-q] [<commit>]
+'git reset' [-q] [<commit>] [--] <paths>...
DESCRIPTION
-----------
@@ -21,7 +21,7 @@ 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,
-gitlink:git-revert[1] is your friend.
+linkgit:git-revert[1] is your friend.
The second form with 'paths' is used to revert selected paths in
the index from a given commit, without moving HEAD.
@@ -37,7 +37,7 @@ OPTIONS
--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 "Added but not yet committed", as gitlink:git-status[1] would
+ files "Changes to be committed", as linkgit:git-status[1] would
put it.
--hard::
@@ -45,8 +45,11 @@ OPTIONS
switched to. Any changes to tracked files in the working tree
since <commit> are lost.
+-q::
+ Be quiet, only report errors.
+
<commit>::
- Commit to make the current HEAD.
+ Commit to make the current HEAD. If not given defaults to HEAD.
Examples
--------
@@ -68,7 +71,7 @@ message, or both. Leaves working tree as it was before "reset".
commit by starting with its log message. If you do not need to
edit the message further, you can give -C option instead.
+
-See also the --amend option to gitlink:git-commit[1].
+See also the --amend option to linkgit:git-commit[1].
Undo commits permanently::
+
@@ -157,7 +160,7 @@ need to get to the other branch for a quick bugfix.
------------
$ git checkout feature ;# you were working in "feature" branch and
$ work work work ;# got interrupted
-$ git commit -a -m 'snapshot WIP' <1>
+$ git commit -a -m "snapshot WIP" <1>
$ git checkout master
$ fix fix fix
$ git commit ;# commit with real log
@@ -173,6 +176,23 @@ $ git reset <3>
committed as 'snapshot WIP'. This updates the index to show your
WIP files as uncommitted.
+Reset a single file in the index::
++
+Suppose you have added a file to your index, but later decide you do not
+want to add it to your commit. You can remove the file from the index
+while keeping your changes with git reset.
++
+------------
+$ git reset -- frotz.c <1>
+$ git commit -m "Commit files in index" <2>
+$ git add frotz.c <3>
+------------
++
+<1> This removes the file from the index while keeping it in the working
+ directory.
+<2> This commits all other changes in the index.
+<3> Adds the file to the index again.
+
Author
------
Written by Junio C Hamano <junkio@cox.net> and Linus Torvalds <torvalds@osdl.org>
@@ -183,4 +203,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 7cd0e8913e..5b96eabfce 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -15,11 +15,13 @@ SYNOPSIS
[ \--min-age=timestamp ]
[ \--sparse ]
[ \--no-merges ]
+ [ \--first-parent ]
[ \--remove-empty ]
[ \--full-history ]
[ \--not ]
[ \--all ]
[ \--stdin ]
+ [ \--quiet ]
[ \--topo-order ]
[ \--parents ]
[ \--timestamp ]
@@ -34,6 +36,7 @@ SYNOPSIS
[ \--pretty | \--header ]
[ \--bisect ]
[ \--bisect-vars ]
+ [ \--bisect-all ]
[ \--merge ]
[ \--reverse ]
[ \--walk-reflogs ]
@@ -76,340 +79,17 @@ between the two operands. The following two commands are equivalent:
$ git-rev-list A...B
-----------------------------------------------------------------------
-gitlink:git-rev-list[1] is a very essential git program, since it
+linkgit:git-rev-list[1] is a very essential git program, since it
provides the ability to build and traverse commit ancestry graphs. For
this reason, it has a lot of different options that enables it to be
-used by commands as different as gitlink:git-bisect[1] and
-gitlink:git-repack[1].
+used by commands as different as linkgit:git-bisect[1] and
+linkgit:git-repack[1].
OPTIONS
-------
-Commit Formatting
-~~~~~~~~~~~~~~~~~
-
-Using these options, gitlink:git-rev-list[1] will act similar to the
-more specialized family of commit log tools: gitlink:git-log[1],
-gitlink:git-show[1], and gitlink:git-whatchanged[1]
-
-include::pretty-options.txt[]
-
---relative-date::
-
- Synonym for `--date=relative`.
-
---date={relative,local,default,iso,rfc}::
-
- Only takes effect for dates shown in human-readable format, such
- as when using "--pretty".
-+
-`--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=default` shows timestamps in the original timezone
-(either committer's or author's).
-
---header::
-
- Print the contents of the commit in raw-format; each record is
- separated with a NUL character.
-
---parents::
-
- Print the parents of the commit.
-
---timestamp::
- Print the raw commit timestamp.
-
---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 line 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
------------------------------------------------------------------------
-
-Diff Formatting
-~~~~~~~~~~~~~~~
-
-Below are listed options that control the formatting of diff output.
-Some of them are specific to gitlink:git-rev-list[1], however other diff
-options may be given. See gitlink:git-diff-files[1] for more options.
-
--c::
-
- This flag changes the way a merge commit is displayed. It 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 hunks that show differences from only
- one parent, or show the same change from all but one parent for
- an Octopus merge.
-
--r::
-
- Show recursive diffs.
-
--t::
-
- Show the tree objects in the diff output. This implies '-r'.
-
-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.
-
---
-
--n 'number', --max-count='number'::
-
- Limit the number of commits output.
-
---skip='number'::
-
- Skip 'number' commits before starting to show the commit output.
-
---since='date', --after='date'::
-
- Show commits more recent than a specific date.
-
---until='date', --before='date'::
-
- Show commits older than a specific date.
-
---max-age='timestamp', --min-age='timestamp'::
-
- Limit the commits output to specified time range.
-
---author='pattern', --committer='pattern'::
-
- Limit the commits output to ones with author/committer
- header lines that match the specified pattern (regular expression).
-
---grep='pattern'::
-
- Limit the commits output to ones with log message that
- matches the specified pattern (regular expression).
-
--i, --regexp-ignore-case::
-
- Match the regexp limiting patterns without regard to letters case.
-
--E, --extended-regexp::
-
- Consider the limiting patterns to be extended regular expressions
- instead of the default basic regular expressions.
-
---remove-empty::
-
- Stop when a given path disappears from the tree.
-
---full-history::
-
- Show also parts of history irrelevant to current state of a given
- path. This turns off history simplification, which removed merges
- which didn't change anything at all at some child. It will still actually
- simplify away merges that didn't change anything at all into either
- child.
-
---no-merges::
-
- Do not print commits with more than one parent.
-
---not::
-
- Reverses the meaning of the '{caret}' prefix (or lack thereof)
- for all following revision specifiers, up to the next '--not'.
-
---all::
-
- Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the
- command line as '<commit>'.
-
---stdin::
-
- In addition to the '<commit>' listed on the command
- line, read them from the standard input.
-
---cherry-pick::
-
- Omit any commit that introduces the same change as
- another commit on the "other side" when the set of
- commits are limited with symmetric difference.
-+
-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
-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.
-
--g, --walk-reflogs::
-
- Instead of walking the commit ancestry chain, walk
- 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).
-+
-With '\--pretty' format other than oneline (for obvious reasons),
-this causes the output to have two extra lines of information
-taken from the reflog. By default, 'commit@\{Nth}' notation is
-used in the output. When the starting commit is specified as
-'commit@{now}', output also uses 'commit@\{timestamp}' notation
-instead. Under '\--pretty=oneline', the commit message is
-prefixed with this information on the same line.
-
-Cannot be combined with '\--reverse'.
-
---merge::
-
- After a failed merge, show refs that touch files having a
- conflict and don't exist on all heads to merge.
-
---boundary::
-
- Output uninteresting commits at the boundary, which are usually
- not shown.
-
---dense, --sparse::
-
-When optional paths are given, the default behaviour ('--dense') is to
-only output commits that changes at least one of them, and also ignore
-merges that do not touch the given paths.
-
-Use the '--sparse' flag to makes the command output all eligible commits
-(still subject to count and age limitation), but apply merge
-simplification nevertheless.
-
---bisect::
-
-Limit output to the one commit object which is roughly halfway between
-the included and excluded commits. Thus, if
-
------------------------------------------------------------------------
- $ git-rev-list --bisect foo ^bar ^baz
------------------------------------------------------------------------
-
-outputs 'midpoint', the output of the two commands
-
------------------------------------------------------------------------
- $ git-rev-list foo ^midpoint
- $ git-rev-list midpoint ^bar ^baz
------------------------------------------------------------------------
-
-would be of roughly the same length. Finding the change which
-introduces a regression is thus reduced to a binary search: repeatedly
-generate and test new 'midpoint's until the commit chain is of length
-one.
-
---bisect-vars::
-
-This calculates the same as `--bisect`, but outputs text ready
-to be eval'ed by the shell. These lines will assign the name of
-the midpoint revision to the variable `bisect_rev`, and the
-expected number of commits to be tested after `bisect_rev` is
-tested to `bisect_nr`, the expected number of commits to be
-tested if `bisect_rev` turns out to be good to `bisect_good`,
-the expected number of commits to be tested if `bisect_rev`
-turns out to be bad to `bisect_bad`, and the number of commits
-we are bisecting right now to `bisect_all`.
-
---
-
-Commit Ordering
-~~~~~~~~~~~~~~~
-
-By default, the commits are shown in reverse chronological order.
-
---topo-order::
-
- This option makes them appear in topological order (i.e.
- descendant commits are shown before their parents).
-
---date-order::
-
- This option is similar to '--topo-order' in the sense that no
- parent comes before all of its children, but otherwise things
- are still ordered in the commit timestamp order.
-
---reverse::
-
- Output the commits in reverse order.
- Cannot be combined with '\--walk-reflogs'.
-
-Object Traversal
-~~~~~~~~~~~~~~~~
-
-These options are mostly targeted for packing of git repositories.
-
---objects::
-
- Print the object IDs of any object referenced by the listed
- commits. 'git-rev-list --objects foo ^bar' thus means "send me
- all object IDs which I need to download if I have the commit
- object 'bar', but not 'foo'".
-
---objects-edge::
-
- Similar to '--objects', but also print the IDs of excluded
- commits prefixed with a "-" character. This is used by
- gitlink:git-pack-objects[1] to build "thin" pack, which records
- objects in deltified form based on objects contained in these
- excluded commits to reduce network traffic.
-
---unpacked::
-
- Only useful with '--objects'; print the object IDs that are not
- in packs.
-
---no-walk::
-
- Only show the given revs, but do not traverse their ancestors.
-
---do-walk::
-
- Overrides a previous --no-walk.
-
+:git-rev-list: 1
+include::rev-list-options.txt[]
include::pretty-formats.txt[]
@@ -425,4 +105,4 @@ and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 4758c33dee..f02f6bbb49 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -23,6 +23,13 @@ distinguish between them.
OPTIONS
-------
+--parseopt::
+ Use `git-rev-parse` in option parsing mode (see PARSEOPT section below).
+
+--keep-dash-dash::
+ Only meaningful in `--parseopt` mode. Tells the option parser to echo
+ out the first `--` met instead of skipping it.
+
--revs-only::
Do not output flags and parameters not meant for
`git-rev-list` command.
@@ -63,6 +70,13 @@ OPTIONS
possible '{caret}' prefix); this option makes them output in a
form as close to the original input as possible.
+--symbolic-full-name::
+ This is similar to \--symbolic, but it omits input that
+ are not refs (i.e. branch or tag names; or more
+ explicitly disambiguating "heads/master" form, when you
+ want to name the "master" branch when there is an
+ unfortunately named tag "master"), and show them as full
+ refnames (e.g. "refs/heads/master").
--all::
Show all refs found in `$GIT_DIR/refs`.
@@ -215,13 +229,13 @@ blobs contained in a commit.
* A colon, optionally followed by a stage number (0 to 3) and a
colon, followed by a path; this names a blob object in the
index at the given path. Missing stage number (and the colon
- that follows it) names an stage 0 entry. During a merge, stage
+ 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 being merged.
-Here is an illustration, by Jon Loeliger. Both node B and C are
-a commit parents of commit node A. Parent commits are ordered
+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
@@ -277,7 +291,7 @@ and its parent commits exists. `r1{caret}@` notation means all
parents of `r1`. `r1{caret}!` includes commit `r1` but excludes
its all parents.
-Here are a handful examples:
+Here are a handful of examples:
D G H D
D F G H I J D F
@@ -288,10 +302,75 @@ Here are a handful examples:
C^@ I J F
F^! D G H D F
+PARSEOPT
+--------
+
+In `--parseopt` mode, `git-rev-parse` helps massaging options to bring to shell
+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`
+to replace the arguments with normalized ones. In case of error, it outputs
+usage on the standard error stream, and exits with code 129.
+
+Input Format
+~~~~~~~~~~~~
+
+`git-rev-parse --parseopt` input format is fully text based. It has two parts,
+separated by a line that contains only `--`. The lines before the separator
+(should be more than one) are used for the usage.
+The lines after the separator describe the options.
+
+Each line of options has this format:
+
+------------
+<opt_spec><arg_spec>? SP+ help LF
+------------
+
+`<opt_spec>`::
+ its format is the short option character, then the long option name
+ separated by a comma. Both parts are not required, though at least one
+ is necessary. `h,help`, `dry-run` and `f` are all three correct
+ `<opt_spec>`.
+
+`<arg_spec>`::
+ an `<arg_spec>` tells the option parser if the option has an argument
+ (`=`), an optional one (`?` though its use is discouraged) or none
+ (no `<arg_spec>` in that case).
+
+The remainder of the line, after stripping the spaces, is used
+as the help associated to the option.
+
+Blank lines are ignored, and lines that don't match this specification are used
+as option group headers (start the line with a space to create such
+lines on purpose).
+
+Example
+~~~~~~~
+
+------------
+OPTS_SPEC="\
+some-command [options] <args>...
+
+some-command does foo and bar!
+--
+h,help show the help
+
+foo some nifty option --foo
+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 $?`
+------------
+
+
Author
------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <junkio@cox.net>
+Written by Linus Torvalds <torvalds@osdl.org> .
+Junio C Hamano <junkio@cox.net> and Pierre Habouzit <madcoder@debian.org>
Documentation
--------------
@@ -299,4 +378,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index 69db498447..93e20f7752 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -7,7 +7,7 @@ git-revert - Revert an existing commit
SYNOPSIS
--------
-'git-revert' [--edit | --no-edit] [-n] <commit>
+'git-revert' [--edit | --no-edit] [-n] [-m parent-number] <commit>
DESCRIPTION
-----------
@@ -20,13 +20,20 @@ OPTIONS
<commit>::
Commit to revert.
For a more complete list of ways to spell commit names, see
- "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+ "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
-e|--edit::
With this option, `git-revert` will let you edit the commit
- message prior committing the revert. This is the default if
+ message prior to committing the revert. This is the default if
you run the command from a terminal.
+-m parent-number|--mainline parent-number::
+ Usually you cannot revert a merge because you do not know which
+ side of the merge should be considered the mainline. This
+ option specifies the parent number (starting from 1) of
+ the mainline and allows revert to reverse the change
+ relative to the specified parent.
+
--no-edit::
With this option, `git-revert` will not start the commit
message editor.
@@ -55,4 +62,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index be61a82164..dc36c662ae 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -30,7 +30,7 @@ OPTIONS
-f::
Override the up-to-date check.
--n::
+-n, \--dry-run::
Don't actually remove the file(s), just show if they exist in
the index.
@@ -51,7 +51,7 @@ OPTIONS
\--ignore-unmatch::
Exit with a zero status even if no files matched.
-\--quiet::
+-q, \--quiet::
git-rm normally outputs one line (in the form of an "rm" command)
for each file removed. This option suppresses that output.
@@ -83,7 +83,7 @@ git-rm -f git-*.sh::
See Also
--------
-gitlink:git-add[1]
+linkgit:git-add[1]
Author
------
@@ -95,4 +95,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-runstatus.txt b/Documentation/git-runstatus.txt
deleted file mode 100644
index dee5d0da9d..0000000000
--- a/Documentation/git-runstatus.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-git-runstatus(1)
-================
-
-NAME
-----
-git-runstatus - A helper for git-status and git-commit
-
-
-SYNOPSIS
---------
-'git-runstatus' [--color|--nocolor] [--amend] [--verbose] [--untracked]
-
-
-DESCRIPTION
------------
-Examines paths in the working tree that has changes unrecorded
-to the index file, and changes between the index file and the
-current HEAD commit. The former paths are what you _could_
-commit by running 'git add' (or 'git rm' if you are deleting) before running 'git
-commit', and the latter paths are what you _would_ commit by
-running 'git commit'.
-
-If there is no path that is different between the index file and
-the current HEAD commit, the command exits with non-zero status.
-
-Note that this is _not_ the user level command you would want to
-run from the command line. Use 'git-status' instead.
-
-
-OPTIONS
--------
---color::
- Show colored status, highlighting modified file names.
-
---nocolor::
- Turn off coloring.
-
---amend::
- Show status based on HEAD^1, not HEAD, i.e. show what
- 'git-commit --amend' would do.
-
---verbose::
- Show unified diff of all file changes.
-
---untracked::
- Show files in untracked directories, too. Without this
- option only its name and a trailing slash are displayed
- for each untracked directory.
-
-
-OUTPUT
-------
-The output from this command is designed to be used as a commit
-template comments, and all the output lines are prefixed with '#'.
-
-
-Author
-------
-Originally written by Linus Torvalds <torvalds@osdl.org> as part
-of git-commit, and later rewritten in C by Jeff King.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 3727776a0b..336d797e80 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -96,11 +96,40 @@ The --cc option must be repeated for each user you want on the cc list.
servers typically listen to smtp port 25 and ssmtp port
465).
---smtp-user, --smtp-pass::
- Username and password for SMTP-AUTH. Defaults are the values of
- the configuration values 'sendemail.smtpuser' and
- 'sendemail.smtppass', but see also 'sendemail.identity'.
- If not set, authentication is not attempted.
+--smtp-user::
+ Username for SMTP-AUTH. In place of this option, the following
+ configuration variables can be specified:
++
+--
+ * sendemail.smtpuser
+ * sendemail.<identity>.smtpuser (see sendemail.identity).
+--
++
+However, --smtp-user always overrides these variables.
++
+If a username is not specified (with --smtp-user or a
+configuration variable), then authentication is not attempted.
+
+--smtp-pass::
+ Password for SMTP-AUTH. The argument is optional: If no
+ argument is specified, then the empty string is used as
+ the password.
++
+In place of this option, the following configuration variables
+can be specified:
++
+--
+ * sendemail.smtppass
+ * sendemail.<identity>.smtppass (see sendemail.identity).
+--
++
+However, --smtp-pass always overrides these variables.
++
+Furthermore, passwords need not be specified in configuration files
+or on the command line. If a username has been specified (with
+--smtp-user or a configuration variable), but no password has been
+specified (with --smtp-pass or a configuration variable), then the
+user is prompted for a password while the input is masked for privacy.
--smtp-ssl::
If set, connects to the SMTP server using SSL.
@@ -113,11 +142,21 @@ The --cc option must be repeated for each user you want on the cc list.
is not set, this will be prompted for.
--suppress-from, --no-suppress-from::
- If this is set, do not add the From: address to the cc: list, if it
- shows up in a From: line.
+ If this is set, do not add the From: address to the cc: list.
Default is the value of 'sendemail.suppressfrom' configuration value;
if that is unspecified, default to --no-suppress-from.
+--suppress-cc::
+ Specify an additional category of recipients to suppress the
+ auto-cc of. 'self' will avoid including the sender, 'author' will
+ avoid including the patch author, 'cc' will avoid including anyone
+ mentioned in Cc lines in the patch, 'sob' will avoid including
+ anyone mentioned in Signed-off-by lines, and 'cccmd' will avoid
+ running the --cc-cmd. 'all' will suppress all auto cc values.
+ Default is the value of 'sendemail.suppresscc' configuration value;
+ if that is unspecified, default to 'self' if --suppress-from is
+ specified, as well as 'sob' if --no-signed-off-cc is specified.
+
--thread, --no-thread::
If this is set, the In-Reply-To header will be set on each email sent.
If disabled with "--no-thread", no emails will have the In-Reply-To
@@ -138,6 +177,8 @@ The --cc option must be repeated for each user you want on the cc list.
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.
+
The --to option must be repeated for each user you want on the to list.
@@ -149,7 +190,7 @@ sendemail.identity::
'sendemail.<identity>.<item>' will have higher precedence than
'sendemail.<item>'. This is useful to declare multiple SMTP
identities and to hoist sensitive authentication information
- out of the repository and into the global configuation file.
+ out of the repository and into the global configuration file.
sendemail.aliasesfile::
To avoid typing long email addresses, point this to one or more
@@ -159,6 +200,9 @@ sendemail.aliasfiletype::
Format of the file(s) specified in sendemail.aliasesfile. Must be
one of 'mutt', 'mailrc', 'pine', or 'gnus'.
+sendemail.to::
+ Email address (or alias) to always send to.
+
sendemail.cccmd::
Command to execute to generate per patch file specific "Cc:"s.
@@ -194,4 +238,4 @@ Documentation by Ryan Anderson
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 2fa01d4a3c..777515b12e 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -12,7 +12,7 @@ SYNOPSIS
DESCRIPTION
-----------
-Usually you would want to use gitlink:git-push[1] which is a
+Usually you would want to use linkgit:git-push[1] which is a
higher level wrapper of this command instead.
Invokes 'git-receive-pack' on a possibly remote repository, and
@@ -85,7 +85,9 @@ Each pattern pair consists of the source side (before the colon)
and the destination side (after the colon). The ref to be
pushed is determined by finding a match that matches the source
side, and where it is pushed is determined by using the
-destination side.
+destination side. The rules used to match a ref are the same
+rules used by linkgit:git-rev-parse[1] to resolve a symbolic ref
+name.
- It is an error if <src> does not match exactly one of the
local refs.
@@ -123,4 +125,4 @@ Documentation by Junio C Hamano.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt
index 1ea1faa1b5..16b8b75146 100644
--- a/Documentation/git-sh-setup.txt
+++ b/Documentation/git-sh-setup.txt
@@ -44,6 +44,11 @@ set_reflog_action::
end-user action in the reflog, when the script updates a
ref.
+git_editor::
+ runs an editor of user's choice (GIT_EDITOR, core.editor, VISUAL or
+ EDITOR) on a given file, but error out if no editor is specified
+ and the terminal is dumb.
+
is_bare_repository::
outputs `true` or `false` to the standard output stream
to indicate if the repository is a bare repository
@@ -57,6 +62,10 @@ require_work_tree::
if so. Used by scripts that require working tree
(e.g. `checkout`).
+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
------
@@ -68,4 +77,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt
index 48f2d57b7b..bc031e0cc2 100644
--- a/Documentation/git-shell.txt
+++ b/Documentation/git-shell.txt
@@ -31,4 +31,4 @@ Documentation by Petr Baudis and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index 2220ef6ea8..c7752575d8 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -8,8 +8,8 @@ git-shortlog - Summarize 'git log' output
SYNOPSIS
--------
[verse]
-git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s]
-git-shortlog [-n|--numbered] [-s|--summary] [<committish>...]
+git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] [-e]
+git-shortlog [-n|--numbered] [-s|--summary] [-e|--email] [<committish>...]
DESCRIPTION
-----------
@@ -32,18 +32,22 @@ OPTIONS
-s, \--summary::
Suppress commit description and provide a commit count summary only.
+-e, \--email::
+ Show the email address of each author.
+
FILES
-----
-.mailmap::
- If this file exists, it will be used for mapping author email
- addresses to a real author name. One mapping per line, first
- the author name followed by the email address enclosed by
- '<' and '>'. Use hash '#' for comments. Example:
+If the file `.mailmap` exists, it will be used for mapping author
+email addresses to a real author name. One mapping per line, first
+the author name followed by the email address enclosed by
+'<' and '>'. Use hash '#' for comments. Example:
- # Keep alphabetized
- Adam Morrow <adam@localhost.localdomain>
- Eve Jones <eve@laptop.(none)>
+------------
+# Keep alphabetized
+Adam Morrow <adam@localhost.localdomain>
+Eve Jones <eve@laptop.(none)>
+------------
Author
------
@@ -55,4 +59,4 @@ Documentation by Junio C Hamano.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt
index ba5313d51f..0bb8250b20 100644
--- a/Documentation/git-show-branch.txt
+++ b/Documentation/git-show-branch.txt
@@ -190,4 +190,4 @@ Documentation by Junio C Hamano.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt
index 764d99356b..535a884642 100644
--- a/Documentation/git-show-index.txt
+++ b/Documentation/git-show-index.txt
@@ -31,4 +31,4 @@ Documentation by Junio C Hamano
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 2355aa5e86..ce0e643fbe 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -130,7 +130,7 @@ When using the '--verify' flag, the command requires an exact path:
will only match the exact branch called "master".
-If nothing matches, gitlink:git-show-ref[1] will return an error code of 1,
+If nothing matches, linkgit:git-show-ref[1] will return an error code of 1,
and in the case of verification, it will show an error message.
For scripting, you can ask it to be quiet with the "--quiet" flag, which
@@ -160,7 +160,7 @@ to get a listing of all tags together with what they dereference.
SEE ALSO
--------
-gitlink:git-ls-remote[1], gitlink:git-peek-remote[1]
+linkgit:git-ls-remote[1]
AUTHORS
-------
@@ -169,4 +169,4 @@ Man page by Jonas Fonseca <fonseca@diku.dk>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
index a42e121150..dccf0e20ec 100644
--- a/Documentation/git-show.txt
+++ b/Documentation/git-show.txt
@@ -20,12 +20,12 @@ presents the merge commit in a special format as produced by
For tags, it shows the tag message and the referenced objects.
-For trees, it shows the names (equivalent to gitlink:git-ls-tree[1]
+For trees, it shows the names (equivalent to linkgit:git-ls-tree[1]
with \--name-only).
For plain blobs, it shows the plain contents.
-The command takes options applicable to the gitlink:git-diff-tree[1] command to
+The command takes options applicable to the linkgit:git-diff-tree[1] command to
control how the changes the commit introduces are shown.
This manual page describes only the most frequently used options.
@@ -36,7 +36,7 @@ OPTIONS
<object>::
The name of the object to show.
For a more complete list of ways to spell object names, see
- "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+ "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
include::pretty-options.txt[]
@@ -83,4 +83,4 @@ This manual page is a stub. You can help the git documentation by expanding it.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-ssh-fetch.txt b/Documentation/git-ssh-fetch.txt
deleted file mode 100644
index 8d3e2ffb2c..0000000000
--- a/Documentation/git-ssh-fetch.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-git-ssh-fetch(1)
-================
-
-NAME
-----
-git-ssh-fetch - Fetch from a remote repository over ssh connection
-
-
-
-SYNOPSIS
---------
-'git-ssh-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
-
-DESCRIPTION
------------
-THIS COMMAND IS DEPRECATED.
-
-Pulls from a remote repository over ssh connection, invoking
-git-ssh-upload on the other end. It functions identically to
-git-ssh-upload, aside from which end you run it on.
-
-
-OPTIONS
--------
-commit-id::
- Either the hash or the filename under [URL]/refs/ to
- pull.
-
--c::
- Get the commit objects.
--t::
- Get trees associated with the commit objects.
--a::
- Get all the objects.
--v::
- Report what is downloaded.
--w::
- Writes the commit-id into the filename under $GIT_DIR/refs/ on
- the local end after the transfer is complete.
-
-
-Author
-------
-Written by Daniel Barkalow <barkalow@iabervon.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/Documentation/git-ssh-upload.txt b/Documentation/git-ssh-upload.txt
deleted file mode 100644
index 5e2ca8dccf..0000000000
--- a/Documentation/git-ssh-upload.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-git-ssh-upload(1)
-=================
-
-NAME
-----
-git-ssh-upload - Push to a remote repository over ssh connection
-
-
-SYNOPSIS
---------
-'git-ssh-upload' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
-
-DESCRIPTION
------------
-THIS COMMAND IS DEPRECATED.
-
-Pushes from a remote repository over ssh connection, invoking
-git-ssh-fetch on the other end. It functions identically to
-git-ssh-fetch, aside from which end you run it on.
-
-OPTIONS
--------
-commit-id::
- Id of commit to push.
-
--c::
- Get the commit objects.
--t::
- Get tree associated with the requested commit object.
--a::
- Get all the objects.
--v::
- Report what is uploaded.
--w::
- Writes the commit-id into the filename under [URL]/refs/ on
- the remote end after the transfer is complete.
-
-Author
-------
-Written by Daniel Barkalow <barkalow@iabervon.org>
-
-Documentation
---------------
-Documentation by Daniel Barkalow
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index c0147b99a2..48e6f5a3f7 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git-stash' (list | show [<stash>] | apply [<stash>] | clear)
-'git-stash' [save] [message...]
+'git-stash' [save [<message>]]
DESCRIPTION
-----------
@@ -36,13 +36,14 @@ is also possible).
OPTIONS
-------
-save::
+save [<message>]::
Save your local modifications to a new 'stash', and run `git-reset
--hard` to revert them. This is the default action when no
- subcommand is given.
+ subcommand is given. The <message> part is optional and gives
+ the description along with the stashed state.
-list::
+list [<options>]::
List the stashes that you currently have. Each 'stash' is listed
with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1}` is
@@ -54,6 +55,9 @@ list::
stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
stash@{1}: On master: 9cc0589... Add git-stash
----------------------------------------------------------------
++
+The command takes options applicable to the linkgit:git-log[1]
+command to control what is shown and how.
show [<stash>]::
@@ -156,10 +160,10 @@ $ git stash apply
SEE ALSO
--------
-gitlink:git-checkout[1],
-gitlink:git-commit[1],
-gitlink:git-reflog[1],
-gitlink:git-reset[1]
+linkgit:git-checkout[1],
+linkgit:git-commit[1],
+linkgit:git-reflog[1],
+linkgit:git-reset[1]
AUTHOR
------
@@ -167,4 +171,4 @@ Written by Nanako Shiraishi <nanako3@bluebottle.com>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index 8fd0fc6236..3ea269aa7a 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -12,33 +12,32 @@ SYNOPSIS
DESCRIPTION
-----------
-Examines paths in the working tree that has changes unrecorded
-to the index file, and changes between the index file and the
-current HEAD commit. The former paths are what you _could_
-commit by running 'git add' before running 'git
-commit', and the latter paths are what you _would_ commit by
-running 'git commit'.
-
-If there is no path that is different between the index file and
-the current HEAD commit, the command exits with non-zero
-status.
+Displays paths that have differences between the index file and the
+current HEAD commit, paths that have differences between the working
+tree and the index file, and paths in the working tree that are not
+tracked by git (and are not ignored by linkgit:gitignore[5]). The first
+are what you _would_ commit by running `git commit`; the second and
+third are what you _could_ commit by running `git add` before running
+`git commit`.
The command takes the same set of options as `git-commit`; it
shows what would be committed if the same options are given to
`git-commit`.
-If any paths have been touched in the working tree (that is,
-their modification times have changed) but their contents and
-permissions are identical to those in the index file, the command
-updates the index file. Running `git-status` can thus speed up
-subsequent operations such as `git-diff` if the working tree
-contains many paths that have been touched but not modified.
+If there is no path that is different between the index file and
+the current HEAD commit (i.e., there is nothing to commit by running
+`git-commit`), the command exits with non-zero status.
OUTPUT
------
The output from this command is designed to be used as a commit
-template comments, and all the output lines are prefixed with '#'.
+template comment, and all the output lines are prefixed with '#'.
+
+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.
CONFIGURATION
@@ -49,9 +48,13 @@ mean the same thing and the latter is kept for backward
compatibility) and `color.status.<slot>` configuration variables
to colorize its output.
+If the config variable `status.relativePaths` is set to false, then all
+paths shown are relative to the repository root, not to the current
+directory.
+
See Also
--------
-gitlink:gitignore[5]
+linkgit:gitignore[5]
Author
------
@@ -64,4 +67,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt
index 5212358306..fc5687502e 100644
--- a/Documentation/git-stripspace.txt
+++ b/Documentation/git-stripspace.txt
@@ -16,7 +16,7 @@ Remove multiple empty lines, and empty lines at beginning and end.
OPTIONS
-------
--s\|--strip-comments::
+-s|--strip-comments::
In addition to empty lines, also strip lines starting with '#'.
<stream>::
@@ -32,4 +32,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index 335e973a6a..e818e6e789 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -9,8 +9,9 @@ git-submodule - Initialize, update or inspect submodules
SYNOPSIS
--------
[verse]
-'git-submodule' [--quiet] [-b branch] add <repository> [<path>]
-'git-submodule' [--quiet] [--cached] [status|init|update] [--] [<path>...]
+'git-submodule' [--quiet] add [-b branch] [--] <repository> [<path>]
+'git-submodule' [--quiet] status [--cached] [--] [<path>...]
+'git-submodule' [--quiet] [init|update] [--] [<path>...]
COMMANDS
@@ -28,7 +29,7 @@ add::
status::
Show the status of the submodules. This will print the SHA-1 of the
currently checked out commit for each submodule, along with the
- submodule path and the output of gitlink:git-describe[1] for the
+ submodule path and the output of linkgit:git-describe[1] 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
does not match the SHA-1 found in the index of the containing
@@ -67,8 +68,9 @@ FILES
-----
When initializing submodules, a .gitmodules file in the top-level directory
of the containing repository is used to find the url of each submodule.
-This file should be formatted in the same way as $GIR_DIR/config. The key
-to each submodule url is "submodule.$name.url".
+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
@@ -77,4 +79,4 @@ Written by Lars Hjemli <hjemli@gmail.com>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 488e4b1caf..340f1be02a 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -12,7 +12,7 @@ SYNOPSIS
DESCRIPTION
-----------
git-svn is a simple conduit for changesets between Subversion and git.
-It is not to be confused with gitlink:git-svnimport[1], which is
+It is not to be confused with linkgit:git-svnimport[1], which is
read-only.
git-svn was originally designed for an individual developer who wants a
@@ -161,6 +161,13 @@ New features:
+
Any other arguments are passed directly to `git log'
+'blame'::
+ Show what revision and author last modified each line of a file. This is
+ identical to `git blame', but SVN revision numbers are shown instead of git
+ commit hashes.
++
+All arguments are passed directly to `git blame'.
+
--
'find-rev'::
When given an SVN revision number of the form 'rN', returns the
@@ -193,6 +200,12 @@ Any other arguments are passed directly to `git log'
repository (that has been init-ed with git-svn).
The -r<revision> option is required for this.
+'info'::
+ Shows information about a file or directory similar to what
+ `svn info' provides. Does not currently support a -r/--revision
+ argument. Use the --url option to output only the value of the
+ 'URL:' field.
+
--
OPTIONS
@@ -202,7 +215,7 @@ OPTIONS
--shared[={false|true|umask|group|all|world|everybody}]::
--template=<template_directory>::
Only used with the 'init' command.
- These are passed directly to gitlink:git-init[1].
+ These are passed directly to linkgit:git-init[1].
-r <ARG>::
--revision <ARG>::
@@ -255,7 +268,7 @@ config key: svn.edit
Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
They are both passed directly to git-diff-tree see
-gitlink:git-diff-tree[1] for more information.
+linkgit:git-diff-tree[1] for more information.
[verse]
config key: svn.l
@@ -293,7 +306,7 @@ with many revisions.
to fetch before repacking. This defaults to repacking every
1000 commits fetched if no argument is specified.
---repack-flags are passed directly to gitlink:git-repack[1].
+--repack-flags are passed directly to linkgit:git-repack[1].
[verse]
config key: svn.repack
@@ -450,10 +463,13 @@ have each person clone that repository with 'git clone':
------------------------------------------------------------------------
# Do the initial import on a server
ssh server "cd /pub && git-svn clone http://svn.foo.org/project
-# Clone locally
- git clone server:/pub/project
-# Tell git-svn which branch contains the Subversion commits
- git update-ref refs/remotes/git-svn origin/master
+# Clone locally - make sure the refs/remotes/ space matches the server
+ mkdir project
+ cd project
+ git-init
+ git remote add origin server:/pub/project
+ git config --add remote.origin.fetch=+refs/remotes/*:refs/remotes/*
+ git fetch
# Initialize git-svn locally (be sure to use the same URL and -T/-b/-t options as were used on server)
git-svn init http://svn.foo.org/project
# Pull the latest changes from Subversion
@@ -555,11 +571,11 @@ however the remote wildcard may be anywhere as long as it's own
independent path component (surrounded by '/' or EOL). This
type of configuration is not automatically created by 'init' and
should be manually entered with a text-editor or using
-gitlink:git-config[1]
+linkgit:git-config[1]
SEE ALSO
--------
-gitlink:git-rebase[1]
+linkgit:git-rebase[1]
Author
------
diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
index a88f722860..a5b40f3e85 100644
--- a/Documentation/git-symbolic-ref.txt
+++ b/Documentation/git-symbolic-ref.txt
@@ -26,7 +26,7 @@ a regular file whose contents is `ref: refs/heads/master`.
OPTIONS
-------
--q::
+-q, --quiet::
Do not issue an error message if the <name> is not a
symbolic ref but a detached HEAD; instead exit with
non-zero status silently.
@@ -58,4 +58,4 @@ Written by Junio C Hamano <junkio@cox.net>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 10d3e3fa95..b62a3d1c58 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -65,7 +65,9 @@ OPTIONS
Typing "git tag" without arguments, also lists all tags.
-m <msg>::
- Use the given tag message (instead of prompting)
+ Use the given tag message (instead of prompting).
+ If multiple `-m` options are given, there values are
+ concatenated as separate paragraphs.
-F <file>::
Take the tag message from the given file. Use '-' to
@@ -246,4 +248,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt
index 434607bfb5..65c68176e5 100644
--- a/Documentation/git-tar-tree.txt
+++ b/Documentation/git-tar-tree.txt
@@ -86,4 +86,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt
index 10653ff898..a96403cb8c 100644
--- a/Documentation/git-tools.txt
+++ b/Documentation/git-tools.txt
@@ -22,6 +22,9 @@ Alternative/Augmentative Porcelains
providing generally smoother user experience than the "raw" Core GIT
itself and indeed many other version control systems.
+ Cogito is no longer maintained as most of its functionality
+ is now in core GIT.
+
- *pg* (http://www.spearce.org/category/projects/scm/pg/)
@@ -33,7 +36,7 @@ Alternative/Augmentative Porcelains
- *StGit* (http://www.procode.org/stgit/)
Stacked GIT provides a quilt-like patch management functionality in the
- GIT environment. You can easily manage your patches in the scope of GIT
+ GIT environment. You can easily manage your patches in the scope of GIT
until they get merged upstream.
diff --git a/Documentation/git-unpack-file.txt b/Documentation/git-unpack-file.txt
index 20bb6a7800..1864d13ed8 100644
--- a/Documentation/git-unpack-file.txt
+++ b/Documentation/git-unpack-file.txt
@@ -32,4 +32,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt
index d529a43f55..b79be3fd4c 100644
--- a/Documentation/git-unpack-objects.txt
+++ b/Documentation/git-unpack-objects.txt
@@ -51,4 +51,4 @@ Documentation by Junio C Hamano
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index 0a1953803e..66be18ef36 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -27,7 +27,7 @@ Modifies the index or directory cache. Each file mentioned is updated
into the index and any 'unmerged' or 'needs updating' state is
cleared.
-See also gitlink:git-add[1] for a more user-friendly way to do some of
+See also linkgit:git-add[1] for a more user-friendly way to do some of
the most common operations on the index.
The way "git-update-index" handles files it is told about can be modified
@@ -292,14 +292,14 @@ Configuration
The command honors `core.filemode` configuration variable. If
your repository is on an filesystem whose executable bits are
-unreliable, this should be set to 'false' (see gitlink:git-config[1]).
+unreliable, this should be set to 'false' (see linkgit:git-config[1]).
This causes the command to ignore differences in file modes recorded
in the index and the file mode on the filesystem if they differ only on
executable bit. On such an unfortunate filesystem, you may
need to use `git-update-index --chmod=`.
Quite similarly, if `core.symlinks` configuration variable is set
-to 'false' (see gitlink:git-config[1]), symbolic links are checked out
+to 'false' (see linkgit:git-config[1]), symbolic links are checked out
as plain files, and this command does not modify a recorded file mode
from symbolic link to regular file.
@@ -309,8 +309,8 @@ The command looks at `core.ignorestat` configuration variable. See
See Also
--------
-gitlink:git-config[1],
-gitlink:git-add[1]
+linkgit:git-config[1],
+linkgit:git-add[1]
Author
@@ -323,4 +323,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index f222616591..4dc475992e 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -90,4 +90,4 @@ Written by Linus Torvalds <torvalds@osdl.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-update-server-info.txt b/Documentation/git-update-server-info.txt
index e7e82a31ea..1cf89fd79e 100644
--- a/Documentation/git-update-server-info.txt
+++ b/Documentation/git-update-server-info.txt
@@ -54,4 +54,4 @@ Documentation by Junio C Hamano.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt
index 403871d7c6..c1ef1440bc 100644
--- a/Documentation/git-upload-archive.txt
+++ b/Documentation/git-upload-archive.txt
@@ -34,4 +34,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
index fd6519299a..2330d13814 100644
--- a/Documentation/git-upload-pack.txt
+++ b/Documentation/git-upload-pack.txt
@@ -43,4 +43,4 @@ Documentation by Junio C Hamano.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
index 813942368b..2980283905 100644
--- a/Documentation/git-var.txt
+++ b/Documentation/git-var.txt
@@ -47,9 +47,9 @@ Your sysadmin must hate you!::
See Also
--------
-gitlink:git-commit-tree[1]
-gitlink:git-tag[1]
-gitlink:git-config[1]
+linkgit:git-commit-tree[1]
+linkgit:git-tag[1]
+linkgit:git-config[1]
Author
------
@@ -61,4 +61,4 @@ Documentation by Eric Biederman and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt
index f4c540f39b..db019a2b8d 100644
--- a/Documentation/git-verify-pack.txt
+++ b/Documentation/git-verify-pack.txt
@@ -50,4 +50,4 @@ Documentation by Junio C Hamano
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt
index ac7fb19154..7e9c1ed15b 100644
--- a/Documentation/git-verify-tag.txt
+++ b/Documentation/git-verify-tag.txt
@@ -28,4 +28,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt
new file mode 100644
index 0000000000..df57d010e5
--- /dev/null
+++ b/Documentation/git-web--browse.txt
@@ -0,0 +1,78 @@
+git-web--browse(1)
+==================
+
+NAME
+----
+git-web--browse - git helper script to launch a web browser
+
+SYNOPSIS
+--------
+'git-web--browse' [OPTIONS] URL/FILE ...
+
+DESCRIPTION
+-----------
+
+This script tries, as much as possible, to display the URLs and FILEs
+that are passed as arguments, as HTML pages in new tabs on an already
+opened web browser.
+
+The following browsers (or commands) are currently supported:
+
+* firefox (this is the default under X Window when not using KDE)
+* iceweasel
+* konqueror (this is the default under KDE)
+* w3m (this is the default outside graphical environments)
+* links
+* lynx
+* dillo
+* open (this is the default under Mac OS X GUI)
+
+OPTIONS
+-------
+-b BROWSER|--browser=BROWSER::
+ Use the specified BROWSER. It must be in the list of supported
+ browsers.
+
+-t BROWSER|--tool=BROWSER::
+ Same as above.
+
+-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.
+
+CONFIGURATION VARIABLES
+-----------------------
+
+The web browser can be specified using a configuration variable passed
+with the -c (or --config) command line option, or the 'web.browser'
+configuration variable if the former is not used.
+
+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
+is available in PATH.
+
+Note that these configuration variables should probably be set using
+the '--global' flag, for example like this:
+
+------------------------------------------------
+$ 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[7] suite
diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt
index 607df48f09..54947b6769 100644
--- a/Documentation/git-whatchanged.txt
+++ b/Documentation/git-whatchanged.txt
@@ -77,4 +77,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt
index cb8d6aadeb..461c813f5a 100644
--- a/Documentation/git-write-tree.txt
+++ b/Documentation/git-write-tree.txt
@@ -46,4 +46,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git.txt b/Documentation/git.txt
index ce8f923a15..d57bed618f 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -28,7 +28,7 @@ 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
-as defined in the configuration file (see gitlink:git-config[1]).
+as defined in the configuration file (see linkgit:git-config[1]).
Formatted and hyperlinked version of the latest git
documentation can be viewed at
@@ -43,13 +43,25 @@ unreleased) version of git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.5.3/git.html[documentation for release 1.5.3]
+* link:v1.5.4.2/git.html[documentation for release 1.5.4.2]
* release notes for
+ 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.1.txt[1.5.3.1],
+ link:RelNotes-1.5.3.txt[1.5.3].
* release notes for
link:RelNotes-1.5.2.5.txt[1.5.2.5],
@@ -97,9 +109,14 @@ OPTIONS
--help::
Prints the synopsis and a list of the most commonly used
- commands. If a git command is named this option will bring up
- the man-page for that command. If the option '--all' or '-a' is
- given then all available commands are printed.
+ commands. If the option '--all' or '-a' is given then all
+ available commands are printed. If a git command is named this
+ option will bring up the manual page for that command.
++
+Other options are available to control how the manual page is
+displayed. See linkgit:git-help[1] for more information,
+because 'git --help ...' is converted internally into 'git
+help ...'.
--exec-path::
Path to wherever your core git programs are installed.
@@ -144,6 +161,8 @@ introductions to the underlying git architecture.
See also the link:howto-index.html[howto] documents for some useful
examples.
+The internals are documented link:technical/api-index.html[here].
+
GIT COMMANDS
------------
@@ -187,8 +206,8 @@ Low-level commands (plumbing)
Although git includes its
own porcelain layer, its low-level commands are sufficient to support
development of alternative porcelains. Developers of such porcelains
-might start by reading about gitlink:git-update-index[1] and
-gitlink:git-read-tree[1].
+might start by reading about linkgit:git-update-index[1] and
+linkgit:git-read-tree[1].
The interface (input, output, set of options and the semantics)
to these low-level commands are meant to be a lot more stable
@@ -320,7 +339,7 @@ HEAD::
(i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
For a more complete list of ways to spell object names, see
-"SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
File/Directory Structure
@@ -388,7 +407,7 @@ git Commits
'GIT_COMMITTER_EMAIL'::
'GIT_COMMITTER_DATE'::
'EMAIL'::
- see gitlink:git-commit-tree[1]
+ see linkgit:git-commit-tree[1]
git Diffs
~~~~~~~~~
@@ -428,7 +447,7 @@ other
'GIT_MERGE_VERBOSITY'::
A number controlling the amount of output shown by
the recursive merge strategy. Overrides merge.verbosity.
- See gitlink:git-merge[1]
+ See linkgit:git-merge[1]
'GIT_PAGER'::
This environment variable overrides `$PAGER`. If it is set
@@ -436,8 +455,8 @@ other
a pager.
'GIT_SSH'::
- If this environment variable is set then gitlink:git-fetch[1]
- and gitlink:git-push[1] will use this command instead
+ If this environment variable is set then linkgit:git-fetch[1]
+ and linkgit:git-push[1] will use this command instead
of `ssh` when they need to connect to a remote system.
The 'GIT_SSH' command will be given exactly two arguments:
the 'username@host' (or just 'host') from the URL and the
@@ -491,7 +510,7 @@ as tags and branch heads.
The object database contains objects of three main types: blobs, which
hold file data; trees, which point to blobs and other trees to build up
-directory heirarchies; and commits, which each reference a single tree
+directory hierarchies; and commits, which each reference a single tree
and some number of parent commits.
The commit, equivalent to what other systems call a "changeset" or
@@ -511,7 +530,7 @@ efficiency may later be compressed together into "pack files".
Named pointers called refs mark interesting points in history. A ref
may contain the SHA1 name of an object or the name of another ref. Refs
with names beginning `ref/head/` contain the SHA1 name of the most
-recent commit (or "head") of a branch under developement. SHA1 names of
+recent commit (or "head") of a branch under development. SHA1 names of
tags of interest are stored under `ref/tags/`. A special ref named
`HEAD` contains the name of the currently checked-out branch.
@@ -532,7 +551,7 @@ 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 Andres Ericsson <ae@op5.se>.
+* The git potty was written by Andreas Ericsson <ae@op5.se>.
* General upbringing is handled by the git-list <git@vger.kernel.org>.
Documentation
@@ -543,4 +562,4 @@ contributors on the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 20cf8ff816..84ec9623a2 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -133,6 +133,26 @@ When `core.autocrlf` is set to "input", line endings are
converted to LF upon checkin, but there is no conversion done
upon checkout.
+If `core.safecrlf` is set to "true" or "warn", git verifies if
+the conversion is reversible for the current setting of
+`core.autocrlf`. For "true", git rejects irreversible
+conversions; for "warn", git only prints a warning but accepts
+an irreversible conversion. The safety triggers to prevent such
+a conversion done to the files in the work tree, but there are a
+few exceptions. Even though...
+
+- "git add" itself does not touch the files in the work tree, the
+ next checkout would, so the safety triggers;
+
+- "git apply" to update a text file with a patch does touch the files
+ in the work tree, but the operation is about text files and CRLF
+ conversion is about fixing the line ending inconsistencies, so the
+ safety does not trigger;
+
+- "git diff" itself does not touch the files in the work tree, it is
+ often run to inspect the changes you intend to next "git add". To
+ catch potential problems early, safety triggers.
+
`ident`
^^^^^^^
@@ -148,22 +168,23 @@ with `$Id$` upon check-in.
`filter`
^^^^^^^^
-A `filter` attribute can be set to a string value. This names
+A `filter` attribute can be set to a string value that names a
filter driver specified in the configuration.
-A filter driver consists of `clean` command and `smudge`
+A filter driver consists of a `clean` command and a `smudge`
command, either of which can be left unspecified. Upon
-checkout, when `smudge` command is specified, the command is fed
-the blob object from its standard input, and its standard output
-is used to update the worktree file. Similarly, `clean` command
-is used to convert the contents of worktree file upon checkin.
+checkout, when the `smudge` command is specified, the command is
+fed the blob object from its standard input, and its standard
+output is used to update the worktree file. Similarly, the
+`clean` command is used to convert the contents of worktree file
+upon checkin.
-Missing filter driver definition in the config is not an error
+A missing filter driver definition in the config is not an error
but makes the filter a no-op passthru.
The content filtering is done to massage the content into a
shape that is more convenient for the platform, filesystem, and
-the user to use. The keyword here is "more convenient" and not
+the user to use. The key phrase here is "more convenient" and not
"turning something unusable into usable". In other words, the
intent is that if someone unsets the filter driver definition,
or does not have the appropriate filter program, the project
@@ -237,7 +258,7 @@ When git needs to show you a diff for the path with `diff`
attribute set to `jcdiff`, it calls the command you specified
with the above configuration, i.e. `j-c-diff`, with 7
parameters, just like `GIT_EXTERNAL_DIFF` program is called.
-See gitlink:git[7] for details.
+See linkgit:git[7] for details.
Defining a custom hunk-header
@@ -321,12 +342,43 @@ String::
requested with "binary".
+Built-in merge drivers
+^^^^^^^^^^^^^^^^^^^^^^
+
+There are a few built-in low-level merge drivers defined that
+can be asked for via the `merge` attribute.
+
+text::
+
+ Usual 3-way file level merge for text files. Conflicted
+ regions are marked with conflict markers `<<<<<<<`,
+ `=======` and `>>>>>>>`. The version from your branch
+ appears before the `=======` marker, and the version
+ from the merged branch appears after the `=======`
+ marker.
+
+binary::
+
+ Keep the version from your branch in the work tree, but
+ leave the path in the conflicted state for the user to
+ sort out.
+
+union::
+
+ Run 3-way file level merge for text files, but take
+ lines from both versions, instead of leaving conflict
+ markers. This tends to leave the added lines in the
+ resulting file in random order and the user should
+ verify the result. Do not use this if you do not
+ understand the implications.
+
+
Defining a custom merge driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The definition of a merge driver is done in `gitconfig` not
-`gitattributes` file, so strictly speaking this manual page is a
-wrong place to talk about it. However...
+The definition of a merge driver is done in the `.git/config`
+file, not in the `gitattributes` file, so strictly speaking this
+manual page is a wrong place to talk about it. However...
To define a custom merge driver `filfre`, add a section to your
`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
@@ -360,6 +412,37 @@ When left unspecified, the driver itself is used for both
internal merge and the final merge.
+Checking whitespace errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+`whitespace`
+^^^^^^^^^^^^
+
+The `core.whitespace` configuration variable allows you to define what
+`diff` and `apply` should consider whitespace errors for all paths in
+the project (See linkgit:git-config[1]). This attribute gives you finer
+control per path.
+
+Set::
+
+ Notice all types of potential whitespace errors known to git.
+
+Unset::
+
+ Do not notice anything as error.
+
+Unspecified::
+
+ Use the value of `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
+ variable.
+
+
EXAMPLE
-------
@@ -418,9 +501,9 @@ Creating an archive
If the attribute `export-subst` is set for a file then git will expand
several placeholders when adding this file to an archive. The
expansion depends on the availability of a commit ID, i.e. if
-gitlink:git-archive[1] has been given a tree instead of a commit or a
+linkgit:git-archive[1] has been given a tree instead of a commit or a
tag then no replacement will be done. The placeholders are the same
-as those for the option `--pretty=format:` of gitlink:git-log[1],
+as those for the option `--pretty=format:` of linkgit:git-log[1],
except that they need to be wrapped like this: `$Format:PLACEHOLDERS$`
in the file. E.g. the string `$Format:%H$` will be replaced by the
commit hash.
@@ -428,4 +511,4 @@ commit hash.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt
new file mode 100644
index 0000000000..7ee5ce386f
--- /dev/null
+++ b/Documentation/gitcli.txt
@@ -0,0 +1,113 @@
+gitcli(5)
+=========
+
+NAME
+----
+gitcli - git command line interface and conventions
+
+SYNOPSIS
+--------
+gitcli
+
+
+DESCRIPTION
+-----------
+
+This manual describes best practice in how to use git CLI. Here are
+the rules that you should follow when you are scripting git:
+
+ * it's preferred to use the non dashed form of git commands, which means that
+ you should prefer `"git foo"` to `"git-foo"`.
+
+ * splitting short options to separate words (prefer `"git foo -a -b"`
+ to `"git foo -ab"`, the latter may not even work).
+
+ * when a command line option takes an argument, use the 'sticked' form. In
+ other words, write `"git foo -oArg"` instead of `"git foo -o Arg"` for short
+ options, and `"git foo --long-opt=Arg"` instead of `"git foo --long-opt Arg"`
+ for long options. An option that takes optional option-argument must be
+ written in the 'sticked' form.
+
+ * when you give a revision parameter to a command, make sure the parameter is
+ not ambiguous with a name of a file in the work tree. E.g. do not write
+ `"git log -1 HEAD"` but write `"git log -1 HEAD --"`; the former will not work
+ if you happen to have a file called `HEAD` in the work tree.
+
+
+ENHANCED CLI
+------------
+From the git 1.5.4 series and further, many git commands (not all of them at the
+time of the writing though) come with an enhanced option parser.
+
+Here is an exhaustive list of the facilities provided by this option parser.
+
+
+Magic Options
+~~~~~~~~~~~~~
+Commands which have the enhanced option parser activated all understand a
+couple of magic command line options:
+
+-h::
+ gives a pretty printed usage of the command.
++
+---------------------------------------------
+$ git describe -h
+usage: git-describe [options] <committish>*
+
+ --contains find the tag that comes after the commit
+ --debug debug search strategy on stderr
+ --all use any ref in .git/refs
+ --tags use any tag in .git/refs/tags
+ --abbrev [<n>] use <n> digits to display SHA-1s
+ --candidates <n> consider <n> most recent tags (default: 10)
+---------------------------------------------
+
+--help-all::
+ Some git commands take options that are only used for plumbing or that
+ are deprecated, and such options are hidden from the default usage. This
+ option gives the full list of options.
+
+
+Negating options
+~~~~~~~~~~~~~~~~
+Options with long option names can be negated by prefixing `"--no-"`. For
+example, `"git branch"` has the option `"--track"` which is 'on' by default. You
+can use `"--no-track"` to override that behaviour. The same goes for `"--color"`
+and `"--no-color"`.
+
+
+Aggregating short options
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Commands that support the enhanced option parser allow you to aggregate short
+options. This means that you can for example use `"git rm -rf"` or
+`"git clean -fdx"`.
+
+
+Separating argument from the option
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+You can write the mandatory option parameter to an option as a separate
+word on the command line. That means that all the following uses work:
+
+----------------------------
+$ git foo --long-opt=Arg
+$ git foo --long-opt Arg
+$ git foo -oArg
+$ git foo -o Arg
+----------------------------
+
+However, this is *NOT* allowed for switches with an optional value, where the
+'sticked' form must be used:
+----------------------------
+$ git describe --abbrev HEAD # correct
+$ git describe --abbrev=10 HEAD # correct
+$ git describe --abbrev 10 HEAD # NOT WHAT YOU MEANT
+----------------------------
+
+
+Documentation
+-------------
+Documentation by Pierre Habouzit.
+
+GIT
+---
+Part of the linkgit:git[7] suite
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index e8b8581f52..e847b3ba63 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -39,10 +39,10 @@ precedence, the last matching pattern decides the outcome):
variable 'core.excludesfile'.
The underlying git plumbing tools, such as
-gitlink:git-ls-files[1] and gitlink:git-read-tree[1], read
+linkgit:git-ls-files[1] and linkgit:git-read-tree[1], read
`gitignore` patterns specified by command-line options, or from
files specified by command-line options. Higher-level git
-tools, such as gitlink:git-status[1] and gitlink:git-add[1],
+tools, such as linkgit:git-status[1] and linkgit:git-add[1],
use patterns from the sources specified above.
Patterns have the following format:
@@ -57,6 +57,13 @@ Patterns have the following format:
included again. If a negated pattern matches, this will
override lower precedence patterns sources.
+ - If the pattern ends with a slash, it is removed for the
+ purpose of the following description, but it would only find
+ a match with a directory. In other words, `foo/` will match a
+ directory `foo` and paths underneath it, but will not match a
+ regular file or a symbolic link `foo` (this is consistent
+ with the way how pathspec works in general in git).
+
- If the pattern does not contain a slash '/', git treats it as
a shell glob pattern and checks for a match against the
pathname without leading directories.
@@ -119,4 +126,4 @@ Frank Lichtenheld, and the git-list <git@vger.kernel.org>.
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt
index e9f82b97b9..29edafceda 100644
--- a/Documentation/gitk.txt
+++ b/Documentation/gitk.txt
@@ -22,7 +22,7 @@ git repository.
OPTIONS
-------
To control which revisions to shown, the command takes options applicable to
-the gitlink:git-rev-list[1] command. This manual page describes only the most
+the linkgit:git-rev-list[1] command. This manual page describes only the most
frequently used options.
-n <number>, --max-count=<number>::
@@ -48,7 +48,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 gitlink:git-rev-parse[1].
+ "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
<path>::
@@ -69,7 +69,7 @@ gitk --since="2 weeks ago" \-- gitk::
The "--" is necessary to avoid confusion with the *branch* named
'gitk'
-gitk --max-count=100 --all -- Makefile::
+gitk --max-count=100 --all \-- Makefile::
Show at most 100 changes made to the file 'Makefile'. Instead of only
looking for changes in the current branch look in all branches.
@@ -98,4 +98,4 @@ Documentation by Junio C Hamano, Jonas Fonseca, and the git-list
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt
index 035294e208..cc95b69f27 100644
--- a/Documentation/gitmodules.txt
+++ b/Documentation/gitmodules.txt
@@ -15,7 +15,7 @@ DESCRIPTION
The `.gitmodules` file, located in the top-level directory of a git
working tree, is a text file with a syntax matching the requirements
-of gitlink:git-config[1].
+of linkgit:git-config[1].
The file contains one subsection per submodule, and the subsection value
is the name of the submodule. Each submodule section also contains the
@@ -51,7 +51,7 @@ submodules an url is specified which can be used for cloning the submodules.
SEE ALSO
--------
-gitlink:git-submodule[1] gitlink:git-config[1]
+linkgit:git-submodule[1] linkgit:git-config[1]
DOCUMENTATION
-------------
@@ -59,4 +59,4 @@ Documentation by Lars Hjemli <hjemli@gmail.com>
GIT
---
-Part of the gitlink:git[7] suite
+Part of the linkgit:git[7] suite
diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt
index fc1874424e..ab4caf4e26 100644
--- a/Documentation/glossary.txt
+++ b/Documentation/glossary.txt
@@ -140,7 +140,7 @@ to point at the new commit.
branch's <<def_head_ref,head ref>> from a remote
<<def_repository,repository>>, to find out which objects are
missing from the local <<def_object_database,object database>>,
- and to get them, too. See also gitlink:git-fetch[1].
+ and to get them, too. See also linkgit:git-fetch[1].
[[def_file_system]]file system::
Linus Torvalds originally designed git to be a user space file system,
@@ -164,7 +164,7 @@ to point at the new commit.
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
- gitlink:git-pack-refs[1].)
+ linkgit:git-pack-refs[1].)
[[def_HEAD]]HEAD::
The current <<def_branch,branch>>. In more detail: Your <<def_working_tree,
@@ -244,8 +244,7 @@ This commit is referred to as a "merge commit", or sometimes just a
The unique identifier of an <<def_object,object>>. The <<def_hash,hash>>
of the object's contents using the Secure Hash Algorithm
1 and usually represented by the 40 character hexadecimal encoding of
- the <<def_hash,hash>> of the object (possibly followed by
- a white space).
+ the <<def_hash,hash>> of the object.
[[def_object_type]]object type::
One of the identifiers
@@ -283,7 +282,7 @@ This commit is referred to as a "merge commit", or sometimes just a
routines that help select changes that add or delete a given text
string. With the `--pickaxe-all` option, it can be used to view the full
<<def_changeset,changeset>> that introduced or removed, say, a
- particular line of text. See gitlink:git-diff[1].
+ particular line of text. See linkgit:git-diff[1].
[[def_plumbing]]plumbing::
Cute name for <<def_core_git,core git>>.
@@ -296,7 +295,7 @@ This commit is referred to as a "merge commit", or sometimes just a
[[def_pull]]pull::
Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and
- <<def_merge,merge>> it. See also gitlink:git-pull[1].
+ <<def_merge,merge>> it. See also linkgit:git-pull[1].
[[def_push]]push::
Pushing a <<def_branch,branch>> means to get the branch's
@@ -334,7 +333,7 @@ This commit is referred to as a "merge commit", or sometimes just a
A reflog shows the local "history" of a ref. In other words,
it can tell you what the 3rd last revision in _this_ repository
was, and what was the current state in _this_ repository,
- yesterday 9:14pm. See gitlink:git-reflog[1] for details.
+ yesterday 9:14pm. See linkgit:git-reflog[1] for details.
[[def_refspec]]refspec::
A "refspec" is used by <<def_fetch,fetch>> and
@@ -347,7 +346,7 @@ This commit is referred to as a "merge commit", or sometimes just a
it as my origin branch head". And `git push
$URL refs/heads/master:refs/heads/to-upstream` means "publish my
master branch head as to-upstream branch at $URL". See also
- gitlink:git-push[1].
+ linkgit:git-push[1].
[[def_repository]]repository::
A collection of <<def_ref,refs>> together with an
@@ -384,15 +383,15 @@ This commit is referred to as a "merge commit", or sometimes just a
object>>). This is sometimes useful when you are interested only in the
recent history of a project even though the real history recorded in the
upstream is much larger. A shallow repository
- is created by giving the `--depth` option to gitlink:git-clone[1], and
- its history can be later deepened with gitlink:git-fetch[1].
+ is created by giving the `--depth` option to linkgit:git-clone[1], and
+ its history can be later deepened with linkgit:git-fetch[1].
[[def_symref]]symref::
Symbolic reference: instead of containing the <<def_SHA1,SHA1>>
id itself, it is of the format 'ref: refs/some/thing' and when
referenced, it recursively dereferences to this reference.
'<<def_HEAD,HEAD>>' is a prime example of a symref. Symbolic
- references are manipulated with the gitlink:git-symbolic-ref[1]
+ references are manipulated with the linkgit:git-symbolic-ref[1]
command.
[[def_tag]]tag::
diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt
index f110162b01..76b8d77460 100644
--- a/Documentation/hooks.txt
+++ b/Documentation/hooks.txt
@@ -61,6 +61,35 @@ The default 'pre-commit' hook, when enabled, catches introduction
of lines with trailing whitespaces and aborts the commit when
such a line is found.
+All the `git-commit` hooks are invoked with the environment
+variable `GIT_EDITOR=:` if the command will not bring up an editor
+to modify the commit message.
+
+prepare-commit-msg
+------------------
+
+This hook is invoked by `git-commit` right after preparing the
+default log message, and before the editor is started.
+
+It takes one to three parameters. The first is the name of the file
+that the commit log message. The second is the source of the commit
+message, and can be: `message` (if a `\-m` or `\-F` option was
+given); `template` (if a `\-t` option was given or the
+configuration option `commit.template` is set); `merge` (if the
+commit is a merge or a `.git/MERGE_MSG` file exists); `squash`
+(if a `.git/SQUASH_MSG` file exists); or `commit`, followed by
+a commit SHA1 (if a `\-c`, `\-C` or `\--amend` option was given).
+
+If the exit status is non-zero, `git-commit` will abort.
+
+The purpose of the hook is to edit the message file in place, and
+it is not suppressed by the `\--no-verify` option. A non-zero exit
+means a failure of the hook and aborts the commit. It should not
+be used as replacement for pre-commit hook.
+
+The sample `prepare-commit-msg` hook that comes with git comments
+out the `Conflicts:` part of a merge's commit message.
+
commit-msg
----------
diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt
new file mode 100644
index 0000000000..4357e26913
--- /dev/null
+++ b/Documentation/howto/maintain-git.txt
@@ -0,0 +1,277 @@
+From: Junio C Hamano <gitster@pobox.com>
+Date: Wed, 21 Nov 2007 16:32:55 -0800
+Subject: Addendum to "MaintNotes"
+Abstract: Imagine that git development is racing along as usual, when our friendly
+ neighborhood maintainer is struck down by a wayward bus. Out of the
+ hordes of suckers (loyal developers), you have been tricked (chosen) to
+ step up as the new maintainer. This howto will show you "how to" do it.
+
+The maintainer's git time is spent on three activities.
+
+ - Communication (60%)
+
+ Mailing list discussions on general design, fielding user
+ questions, diagnosing bug reports; reviewing, commenting on,
+ suggesting alternatives to, and rejecting patches.
+
+ - Integration (30%)
+
+ Applying new patches from the contributors while spotting and
+ correcting minor mistakes, shuffling the integration and
+ testing branches, pushing the results out, cutting the
+ releases, and making announcements.
+
+ - Own development (10%)
+
+ Scratching my own itch and sending proposed patch series out.
+
+The policy on Integration is informally mentioned in "A Note
+from the maintainer" message, which is periodically posted to
+this mailing list after each feature release is made.
+
+The policy.
+
+ - Feature releases are numbered as vX.Y.Z and are meant to
+ contain bugfixes and enhancements in any area, including
+ functionality, performance and usability, without regression.
+
+ - Maintenance releases are numbered as vX.Y.Z.W and are meant
+ to contain only bugfixes for the corresponding vX.Y.Z feature
+ release and earlier maintenance releases vX.Y.Z.V (V < W).
+
+ - 'master' branch is used to prepare for the next feature
+ release. In other words, at some point, the tip of 'master'
+ branch is tagged with vX.Y.Z.
+
+ - 'maint' branch is used to prepare for the next maintenance
+ release. After the feature release vX.Y.Z is made, the tip
+ of 'maint' branch is set to that release, and bugfixes will
+ accumulate on the branch, and at some point, the tip of the
+ branch is tagged with vX.Y.Z.1, vX.Y.Z.2, and so on.
+
+ - 'next' branch is used to publish changes (both enhancements
+ and fixes) that (1) have worthwhile goal, (2) are in a fairly
+ good shape suitable for everyday use, (3) but have not yet
+ demonstrated to be regression free. New changes are tested
+ in 'next' before merged to 'master'.
+
+ - 'pu' branch is used to publish other proposed changes that do
+ not yet pass the criteria set for 'next'.
+
+ - The tips of 'master', 'maint' and 'next' branches will always
+ fast forward, to allow people to build their own
+ customization on top of them.
+
+ - Usually 'master' contains all of 'maint', 'next' contains all
+ of 'master' and 'pu' contains all of 'next'.
+
+ - The tip of 'master' is meant to be more stable than any
+ tagged releases, and the users are encouraged to follow it.
+
+ - The 'next' branch is where new action takes place, and the
+ users are encouraged to test it so that regressions and bugs
+ are found before new topics are merged to 'master'.
+
+
+A typical git day for the maintainer implements the above policy
+by doing the following:
+
+ - Scan mailing list and #git channel log. Respond with review
+ comments, suggestions etc. Kibitz. Collect potentially
+ usable patches from the mailing list. Patches about a single
+ topic go to one mailbox (I read my mail in Gnus, and type
+ \C-o to save/append messages in files in mbox format).
+
+ - Review the patches in the saved mailboxes. Edit proposed log
+ message for typofixes and clarifications, and add Acks
+ collected from the list. Edit patch to incorporate "Oops,
+ that should have been like this" fixes from the discussion.
+
+ - Classify the collected patches and handle 'master' and
+ 'maint' updates:
+
+ - Obviously correct fixes that pertain to the tip of 'maint'
+ are directly applied to 'maint'.
+
+ - Obviously correct fixes that pertain to the tip of 'master'
+ are directly applied to 'master'.
+
+ This step is done with "git am".
+
+ $ git checkout master ;# or "git checkout maint"
+ $ git am -3 -s mailbox
+ $ make test
+
+ - Merge downwards (maint->master):
+
+ $ git checkout master
+ $ git merge maint
+ $ make test
+
+ - Review the last issue of "What's cooking" message, review the
+ topics scheduled for merging upwards (topic->master and
+ topic->maint), and merge.
+
+ $ git checkout master ;# or "git checkout maint"
+ $ git merge ai/topic ;# or "git merge ai/maint-topic"
+ $ git log -p ORIG_HEAD.. ;# final review
+ $ git diff ORIG_HEAD.. ;# final review
+ $ make test ;# final review
+ $ git branch -d ai/topic ;# or "git branch -d ai/maint-topic"
+
+ - Merge downwards (maint->master) if needed:
+
+ $ git checkout master
+ $ git merge maint
+ $ make test
+
+ - Merge downwards (master->next) if needed:
+
+ $ git checkout next
+ $ git merge master
+ $ make test
+
+ - Handle the remaining patches:
+
+ - Anything unobvious that is applicable to 'master' (in other
+ words, does not depend on anything that is still in 'next'
+ and not in 'master') is applied to a new topic branch that
+ is forked from the tip of 'master'. This includes both
+ enhancements and unobvious fixes to 'master'. A topic
+ branch is named as ai/topic where "ai" is typically
+ author's initial and "topic" is a descriptive name of the
+ topic (in other words, "what's the series is about").
+
+ - An unobvious fix meant for 'maint' is applied to a new
+ topic branch that is forked from the tip of 'maint'. The
+ topic is named as ai/maint-topic.
+
+ - Changes that pertain to an existing topic are applied to
+ the branch, but:
+
+ - obviously correct ones are applied first;
+
+ - questionable ones are discarded or applied to near the tip;
+
+ - Replacement patches to an existing topic are accepted only
+ for commits not in 'next'.
+
+ The above except the "replacement" are all done with:
+
+ $ git am -3 -s mailbox
+
+ while patch replacement is often done by:
+
+ $ git format-patch ai/topic~$n..ai/topic ;# export existing
+
+ then replace some parts with the new patch, and reapplying:
+
+ $ git reset --hard ai/topic~$n
+ $ git am -3 -s 000*.txt
+
+ The full test suite is always run for 'maint' and 'master'
+ after patch application; for topic branches the tests are run
+ as time permits.
+
+ - Update "What's cooking" message to review the updates to
+ existing topics, newly added topics and graduated topics.
+
+ This step is helped with Meta/UWC script (where Meta/ contains
+ a checkout of the 'todo' branch).
+
+ - Merge topics to 'next'. For each branch whose tip is not
+ merged to 'next', one of three things can happen:
+
+ - The commits are all next-worthy; merge the topic to next:
+
+ $ git checkout next
+ $ git merge ai/topic ;# or "git merge ai/maint-topic"
+ $ make test
+
+ - The new parts are of mixed quality, but earlier ones are
+ next-worthy; merge the early parts to next:
+
+ $ git checkout next
+ $ git merge ai/topic~2 ;# the tip two are dubious
+ $ make test
+
+ - Nothing is next-worthy; do not do anything.
+
+ - Rebase topics that do not have any commit in next yet. This
+ step is optional but sometimes is worth doing when an old
+ series that is not in next can take advantage of low-level
+ framework change that is merged to 'master' already.
+
+ $ git rebase master ai/topic
+
+ This step is helped with Meta/git-topic.perl script to
+ identify which topic is rebaseable. There also is a
+ pre-rebase hook to make sure that topics that are already in
+ 'next' are not rebased beyond the merged commit.
+
+ - Rebuild "pu" to merge the tips of topics not in 'next'.
+
+ $ git checkout pu
+ $ git reset --hard next
+ $ git merge ai/topic ;# repeat for all remaining topics
+ $ make test
+
+ This step is helped with Meta/PU script
+
+ - Push four integration branches to a private repository at
+ k.org and run "make test" on all of them.
+
+ - Push four integration branches to /pub/scm/git/git.git at
+ k.org. This triggers its post-update hook which:
+
+ (1) runs "git pull" in $HOME/git-doc/ repository to pull
+ 'master' just pushed out;
+
+ (2) runs "make doc" in $HOME/git-doc/, install the generated
+ documentation in staging areas, which are separate
+ repositories that have html and man branches checked
+ out.
+
+ (3) runs "git commit" in the staging areas, and run "git
+ push" back to /pub/scm/git/git.git/ to update the html
+ and man branches.
+
+ (4) installs generated documentation to /pub/software/scm/git/docs/
+ to be viewed from http://www.kernel.org/
+
+ - Fetch html and man branches back from k.org, and push four
+ integration branches and the two documentation branches to
+ repo.or.cz
+
+
+Some observations to be made.
+
+ * Each topic is tested individually, and also together with
+ other topics cooking in 'next'. Until it matures, none part
+ of it is merged to 'master'.
+
+ * A topic already in 'next' can get fixes while still in
+ 'next'. Such a topic will have many merges to 'next' (in
+ other words, "git log --first-parent next" will show many
+ "Merge ai/topic to next" for the same topic.
+
+ * An unobvious fix for 'maint' is cooked in 'next' and then
+ merged to 'master' to make extra sure it is Ok and then
+ merged to 'maint'.
+
+ * Even when 'next' becomes empty (in other words, all topics
+ prove stable and are merged to 'master' and "git diff master
+ next" shows empty), it has tons of merge commits that will
+ never be in 'master'.
+
+ * In principle, "git log --first-parent master..next" should
+ show nothing but merges (in practice, there are fixup commits
+ and reverts that are not merges).
+
+ * Commits near the tip of a topic branch that are not in 'next'
+ are fair game to be discarded, replaced or rewritten.
+ Commits already merged to 'next' will not be.
+
+ * Being in the 'next' branch is not a guarantee for a topic to
+ be included in the next feature release. Being in the
+ 'master' branch typically is.
diff --git a/Documentation/howto/recover-corrupted-blob-object.txt b/Documentation/howto/recover-corrupted-blob-object.txt
new file mode 100644
index 0000000000..323b513ed0
--- /dev/null
+++ b/Documentation/howto/recover-corrupted-blob-object.txt
@@ -0,0 +1,134 @@
+Date: Fri, 9 Nov 2007 08:28:38 -0800 (PST)
+From: Linus Torvalds <torvalds@linux-foundation.org>
+Subject: corrupt object on git-gc
+Abstract: Some tricks to reconstruct blob objects in order to fix
+ a corrupted repository.
+
+On Fri, 9 Nov 2007, Yossi Leybovich wrote:
+>
+> Did not help still the repository look for this object?
+> Any one know how can I track this object and understand which file is it
+
+So exactly *because* the SHA1 hash is cryptographically secure, the hash
+itself doesn't actually tell you anything, in order to fix a corrupt
+object you basically have to find the "original source" for it.
+
+The easiest way to do that is almost always to have backups, and find the
+same object somewhere else. Backups really are a good idea, and git makes
+it pretty easy (if nothing else, just clone the repository somewhere else,
+and make sure that you do *not* use a hard-linked clone, and preferably
+not the same disk/machine).
+
+But since you don't seem to have backups right now, the good news is that
+especially with a single blob being corrupt, these things *are* somewhat
+debuggable.
+
+First off, move the corrupt object away, and *save* it. The most common
+cause of corruption so far has been memory corruption, but even so, there
+are people who would be interested in seeing the corruption - but it's
+basically impossible to judge the corruption until we can also see the
+original object, so right now the corrupt object is useless, but it's very
+interesting for the future, in the hope that you can re-create a
+non-corrupt version.
+
+So:
+
+> ib]$ mv .git/objects/4b/9458b3786228369c63936db65827de3cc06200 ../
+
+This is the right thing to do, although it's usually best to save it under
+it's full SHA1 name (you just dropped the "4b" from the result ;).
+
+Let's see what that tells us:
+
+> ib]$ git-fsck --full
+> broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
+> to blob 4b9458b3786228369c63936db65827de3cc06200
+> missing blob 4b9458b3786228369c63936db65827de3cc06200
+
+Ok, I removed the "dangling commit" messages, because they are just
+messages about the fact that you probably have rebased etc, so they're not
+at all interesting. But what remains is still very useful. In particular,
+we now know which tree points to it!
+
+Now you can do
+
+ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
+
+which will show something like
+
+ 100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore
+ 100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap
+ 100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING
+ 100644 blob ee909f2cc49e54f0799a4739d24c4cb9151ae453 CREDITS
+ 040000 tree 0f5f709c17ad89e72bdbbef6ea221c69807009f6 Documentation
+ 100644 blob 1570d248ad9237e4fa6e4d079336b9da62d9ba32 Kbuild
+ 100644 blob 1c7c229a092665b11cd46a25dbd40feeb31661d9 MAINTAINERS
+ ...
+
+and you should now have a line that looks like
+
+ 10064 blob 4b9458b3786228369c63936db65827de3cc06200 my-magic-file
+
+in the output. This already tells you a *lot* it tells you what file the
+corrupt blob came from!
+
+Now, it doesn't tell you quite enough, though: it doesn't tell what
+*version* of the file didn't get correctly written! You might be really
+lucky, and it may be the version that you already have checked out in your
+working tree, in which case fixing this problem is really simple, just do
+
+ git hash-object -w my-magic-file
+
+again, and if it outputs the missing SHA1 (4b945..) you're now all done!
+
+But that's the really lucky case, so let's assume that it was some older
+version that was broken. How do you tell which version it was?
+
+The easiest way to do it is to do
+
+ git log --raw --all --full-history -- subdirectory/my-magic-file
+
+and that will show you the whole log for that file (please realize that
+the tree you had may not be the top-level tree, so you need to figure out
+which subdirectory it was in on your own), and because you're asking for
+raw output, you'll now get something like
+
+ commit abc
+ Author:
+ Date:
+ ..
+ :100644 100644 4b9458b... newsha... M somedirectory/my-magic-file
+
+
+ commit xyz
+ Author:
+ Date:
+
+ ..
+ :100644 100644 oldsha... 4b9458b... M somedirectory/my-magic-file
+
+and this actually tells you what the *previous* and *subsequent* versions
+of that file were! So now you can look at those ("oldsha" and "newsha"
+respectively), and hopefully you have done commits often, and can
+re-create the missing my-magic-file version by looking at those older and
+newer versions!
+
+If you can do that, you can now recreate the missing object with
+
+ git hash-object -w <recreated-file>
+
+and your repository is good again!
+
+(Btw, you could have ignored the fsck, and started with doing a
+
+ git log --raw --all
+
+and just looked for the sha of the missing object (4b9458b..) in that
+whole thing. It's up to you - git does *have* a lot of information, it is
+just missing one particular blob version.
+
+Trying to recreate trees and especially commits is *much* harder. So you
+were lucky that it's a blob. It's quite possible that you can recreate the
+thing.
+
+ Linus
diff --git a/Documentation/howto/using-merge-subtree.txt b/Documentation/howto/using-merge-subtree.txt
new file mode 100644
index 0000000000..0953a50b69
--- /dev/null
+++ b/Documentation/howto/using-merge-subtree.txt
@@ -0,0 +1,75 @@
+Date: Sat, 5 Jan 2008 20:17:40 -0500
+From: Sean <seanlkml@sympatico.ca>
+To: Miklos Vajna <vmiklos@frugalware.org>
+Cc: git@vger.kernel.org
+Subject: how to use git merge -s subtree?
+Abstract: In this article, Sean demonstrates how one can use the subtree merge
+ strategy.
+Content-type: text/asciidoc
+Message-ID: <BAYC1-PASMTP12374B54BA370A1E1C6E78AE4E0@CEZ.ICE>
+
+How to use the subtree merge strategy
+=====================================
+
+There are situations where you want to include contents in your project
+from an independently developed project. You can just pull from the
+other project as long as there are no conflicting paths.
+
+The problematic case is when there are conflicting files. Potential
+candidates are Makefiles and other standard filenames. You could merge
+these files but probably you do not want to. A better solution for this
+problem can be to merge the project as its own subdirectory. This is not
+supported by the 'recursive' merge strategy, so just pulling won't work.
+
+What you want is the 'subtree' merge strategy, which helps you in such a
+situation.
+
+In this example, let's say you have the repository at `/path/to/B` (but
+it can be an URL as well, if you want). You want to merge the 'master'
+branch of that repository to the `dir-B` subdirectory in your current
+branch.
+
+Here is the command sequence you need:
+
+----------------
+$ git remote add -f Bproject /path/to/B <1>
+$ git merge -s ours --no-commit Bproject/master <2>
+$ git read-tree --prefix=dir-B/ -u Bproject/master <3>
+$ git commit -m "Merge B project as our subdirectory" <4>
+
+$ git pull -s subtree Bproject master <5>
+----------------
+<1> name the other project "Bproject", and fetch.
+<2> prepare for the later step to record the result as a merge.
+<3> read "master" branch of Bproject to the subdirectory "dir-B".
+<4> record the merge result.
+<5> maintain the result with subsequent merges using "subtree"
+
+The first four commands are used for the initial merge, while the last
+one is to merge updates from 'B project'.
+
+Comparing 'subtree' merge with submodules
+-----------------------------------------
+
+- The benefit of using subtree merge is that it requires less
+ administrative burden from the users of your repository. It works with
+ older (before Git v1.5.2) clients and you have the code right after
+ clone.
+
+- However if you use submodules then you can choose not to transfer the
+ submodule objects. This may be a problem with the subtree merge.
+
+- Also, in case you make changes to the other project, it is easier to
+ submit changes if you just use submodules.
+
+Additional tips
+---------------
+
+- If you made changes to the other project in your repository, they may
+ want to merge from your project. This is possible using subtree -- it
+ can shift up the paths in your tree and then they can merge only the
+ 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
+ to.
diff --git a/Documentation/i18n.txt b/Documentation/i18n.txt
index b95f99be6c..1e188e6e74 100644
--- a/Documentation/i18n.txt
+++ b/Documentation/i18n.txt
@@ -22,7 +22,7 @@ does not forbid it. However, there are a few things to keep in
mind.
. `git-commit-tree` (hence, `git-commit` which uses it) issues
- an warning if the commit log message given to it does not look
+ a warning if the commit log message given to it does not look
like a valid UTF-8 string, unless you explicitly say your
project uses a legacy encoding. The way to say this is to
have i18n.commitencoding in `.git/config` file, like this:
diff --git a/Documentation/install-webdoc.sh b/Documentation/install-webdoc.sh
index cd3a18eb7f..2135a8ee1f 100755
--- a/Documentation/install-webdoc.sh
+++ b/Documentation/install-webdoc.sh
@@ -2,9 +2,16 @@
T="$1"
-for h in *.html *.txt howto/*.txt howto/*.html RelNotes-*.txt *.css
+for h in \
+ *.txt *.html \
+ howto/*.txt howto/*.html \
+ technical/*.txt technical/*.html \
+ RelNotes-*.txt *.css
do
- if test -f "$T/$h" &&
+ 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"
then
:; # up to date
@@ -16,7 +23,10 @@ do
fi
done
strip_leading=`echo "$T/" | sed -e 's|.|.|g'`
-for th in "$T"/*.html "$T"/*.txt "$T"/howto/*.txt "$T"/howto/*.html
+for th in \
+ "$T"/*.html "$T"/*.txt \
+ "$T"/howto/*.txt "$T"/howto/*.html \
+ "$T"/technical/*.txt "$T"/technical/*.html
do
h=`expr "$th" : "$strip_leading"'\(.*\)'`
case "$h" in
diff --git a/Documentation/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl
new file mode 100644
index 0000000000..fe3cd72d6f
--- /dev/null
+++ b/Documentation/manpage-1.72.xsl
@@ -0,0 +1,17 @@
+<!-- callout.xsl: converts asciidoc callouts to man page format -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+<xsl:template match="co">
+ <xsl:value-of select="concat('&#x2593;fB(',substring-after(@id,'-'),')&#x2593;fR')"/>
+</xsl:template>
+<xsl:template match="calloutlist">
+ <xsl:text>&#x2302;sp&#10;</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;</xsl:text>
+</xsl:template>
+<xsl:template match="callout">
+ <xsl:value-of select="concat('&#x2593;fB',substring-after(@arearefs,'-'),'. &#x2593;fR')"/>
+ <xsl:apply-templates/>
+ <xsl:text>&#x2302;br&#10;</xsl:text>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt
index 4c92e375fe..6939130094 100644
--- a/Documentation/repository-layout.txt
+++ b/Documentation/repository-layout.txt
@@ -19,7 +19,7 @@ trees this way, for example. A repository with this kind of
incomplete object store is not suitable to be published to the
outside world but sometimes useful for private repository.
. You also could have an incomplete but locally usable repository
-by cloning shallowly. See gitlink:git-clone[1].
+by cloning shallowly. See linkgit:git-clone[1].
. You can be using `objects/info/alternates` mechanism, or
`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanism to 'borrow'
objects from other object stores. A repository with this kind
@@ -89,7 +89,7 @@ refs/remotes/`name`::
packed-refs::
records the same information as refs/heads/, refs/tags/,
and friends record in a more efficient way. See
- gitlink:git-pack-refs[1].
+ linkgit:git-pack-refs[1].
HEAD::
A symref (see glossary) to the `refs/heads/` namespace
@@ -106,7 +106,7 @@ HEAD::
HEAD can also record a specific commit directly, instead of
being a symref to point at the current branch. Such a state
is often called 'detached HEAD', and almost all commands work
-identically as normal. See gitlink:git-checkout[1] for
+identically as normal. See linkgit:git-checkout[1] for
details.
branches::
@@ -155,7 +155,7 @@ info/exclude::
exclude pattern list. `.gitignore` is the per-directory
ignore file. `git status`, `git add`, `git rm` and `git
clean` look at it but the core git commands do not look
- at it. See also: gitlink:gitignore[5].
+ at it. See also: linkgit:gitignore[5].
remotes::
Stores shorthands to be used to give URL and default
@@ -176,4 +176,4 @@ logs/refs/tags/`name`::
shallow::
This is similar to `info/grafts` but is internally used
and maintained by shallow clone mechanism. See `--depth`
- option to gitlink:git-clone[1] and gitlink:git-fetch[1].
+ option to linkgit:git-clone[1] and linkgit:git-fetch[1].
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
new file mode 100644
index 0000000000..a8138e27a1
--- /dev/null
+++ b/Documentation/rev-list-options.txt
@@ -0,0 +1,361 @@
+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}::
+
+ Only takes effect for dates shown in human-readable format, such
+ as when using "--pretty".
++
+`--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=default` shows timestamps in the original timezone
+(either committer's or author's).
+
+--header::
+
+ Print the contents of the commit in raw-format; each record is
+ separated with a NUL character.
+
+--parents::
+
+ Print the parents of the commit.
+
+--timestamp::
+ Print the raw commit timestamp.
+
+--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 line 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
+-----------------------------------------------------------------------
+
+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::
+
+ This flag changes the way a merge commit is displayed. It 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 hunks that show differences from only
+ one parent, or show the same change from all but one parent for
+ an Octopus merge.
+
+-r::
+
+ Show recursive diffs.
+
+-t::
+
+ Show the tree objects in the diff output. This implies '-r'.
+
+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.
+
+--
+
+-n 'number', --max-count='number'::
+
+ Limit the number of commits output.
+
+--skip='number'::
+
+ Skip 'number' commits before starting to show the commit output.
+
+--since='date', --after='date'::
+
+ Show commits more recent than a specific date.
+
+--until='date', --before='date'::
+
+ Show commits older than a specific date.
+
+--max-age='timestamp', --min-age='timestamp'::
+
+ Limit the commits output to specified time range.
+
+--author='pattern', --committer='pattern'::
+
+ Limit the commits output to ones with author/committer
+ header lines that match the specified pattern (regular expression).
+
+--grep='pattern'::
+
+ Limit the commits output to ones with log message that
+ matches the specified pattern (regular expression).
+
+-i, --regexp-ignore-case::
+
+ Match the regexp limiting patterns without regard to letters case.
+
+-E, --extended-regexp::
+
+ Consider the limiting patterns to be extended regular expressions
+ instead of the default basic regular expressions.
+
+--remove-empty::
+
+ Stop when a given path disappears from the tree.
+
+--full-history::
+
+ Show also parts of history irrelevant to current state of a given
+ path. This turns off history simplification, which removed merges
+ which didn't change anything at all at some child. It will still actually
+ simplify away merges that didn't change anything at all into either
+ child.
+
+--no-merges::
+
+ Do not print commits with more than one parent.
+
+--first-parent::
+ Follow only the first parent commit upon seeing a merge
+ commit. This option can give a better overview when
+ viewing the evolution of a particular topic branch,
+ because merges into a topic branch tend to be only about
+ adjusting to updated upstream from time to time, and
+ this option allows you to ignore the individual commits
+ brought in to your history by such a merge.
+
+--not::
+
+ Reverses the meaning of the '{caret}' prefix (or lack thereof)
+ for all following revision specifiers, up to the next '--not'.
+
+--all::
+
+ Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the
+ command line as '<commit>'.
+
+--stdin::
+
+ In addition to the '<commit>' listed on the command
+ line, read them from the standard input.
+
+--quiet::
+
+ Don't print anything to standard output. This form
+ is primarily meant to allow the caller to
+ test the exit status to see if a range of objects is fully
+ connected (or not). It is faster than redirecting stdout
+ to /dev/null as the output does not have to be formatted.
+
+--cherry-pick::
+
+ Omit any commit that introduces the same change as
+ another commit on the "other side" when the set of
+ commits are limited with symmetric difference.
++
+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
+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.
+
+-g, --walk-reflogs::
+
+ Instead of walking the commit ancestry chain, walk
+ 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).
++
+With '\--pretty' format other than oneline (for obvious reasons),
+this causes the output to have two extra lines of information
+taken from the reflog. By default, 'commit@\{Nth}' notation is
+used in the output. When the starting commit is specified as
+'commit@{now}', output also uses 'commit@\{timestamp}' notation
+instead. Under '\--pretty=oneline', the commit message is
+prefixed with this information on the same line.
+
+Cannot be combined with '\--reverse'.
+See also linkgit:git-reflog[1].
+
+--merge::
+
+ After a failed merge, show refs that touch files having a
+ conflict and don't exist on all heads to merge.
+
+--boundary::
+
+ Output uninteresting commits at the boundary, which are usually
+ not shown.
+
+--dense, --sparse::
+
+When optional paths are given, the default behaviour ('--dense') is to
+only output commits that changes at least one of them, and also ignore
+merges that do not touch the given paths.
+
+Use the '--sparse' flag to makes the command output all eligible commits
+(still subject to count and age limitation), but apply merge
+simplification nevertheless.
+
+ifdef::git-rev-list[]
+--bisect::
+
+Limit output to the one commit object which is roughly halfway between
+the included and excluded commits. Thus, if
+
+-----------------------------------------------------------------------
+ $ git-rev-list --bisect foo ^bar ^baz
+-----------------------------------------------------------------------
+
+outputs 'midpoint', the output of the two commands
+
+-----------------------------------------------------------------------
+ $ git-rev-list foo ^midpoint
+ $ git-rev-list midpoint ^bar ^baz
+-----------------------------------------------------------------------
+
+would be of roughly the same length. Finding the change which
+introduces a regression is thus reduced to a binary search: repeatedly
+generate and test new 'midpoint's until the commit chain is of length
+one.
+
+--bisect-vars::
+
+This calculates the same as `--bisect`, but outputs text ready
+to be eval'ed by the shell. These lines will assign the name of
+the midpoint revision to the variable `bisect_rev`, and the
+expected number of commits to be tested after `bisect_rev` is
+tested to `bisect_nr`, the expected number of commits to be
+tested if `bisect_rev` turns out to be good to `bisect_good`,
+the expected number of commits to be tested if `bisect_rev`
+turns out to be bad to `bisect_bad`, and the number of commits
+we are bisecting right now to `bisect_all`.
+
+--bisect-all::
+
+This outputs all the commit objects between the included and excluded
+commits, ordered by their distance to the included and excluded
+commits. The farthest from them is displayed first. (This is the only
+one displayed by `--bisect`.)
+
+This is useful because it makes it easy to choose a good commit to
+test when you want to avoid to test some of them for some reason (they
+may not compile for example).
+
+This option can be used along with `--bisect-vars`, in this case,
+after all the sorted commit objects, there will be the same text as if
+`--bisect-vars` had been used alone.
+endif::git-rev-list[]
+
+--
+
+Commit Ordering
+~~~~~~~~~~~~~~~
+
+By default, the commits are shown in reverse chronological order.
+
+--topo-order::
+
+ This option makes them appear in topological order (i.e.
+ descendant commits are shown before their parents).
+
+--date-order::
+
+ This option is similar to '--topo-order' in the sense that no
+ parent comes before all of its children, but otherwise things
+ are still ordered in the commit timestamp order.
+
+--reverse::
+
+ Output the commits in reverse order.
+ Cannot be combined with '\--walk-reflogs'.
+
+Object Traversal
+~~~~~~~~~~~~~~~~
+
+These options are mostly targeted for packing of git repositories.
+
+--objects::
+
+ Print the object IDs of any object referenced by the listed
+ commits. '--objects foo ^bar' thus means "send me
+ all object IDs which I need to download if I have the commit
+ object 'bar', but not 'foo'".
+
+--objects-edge::
+
+ Similar to '--objects', but also print the IDs of excluded
+ commits prefixed with a "-" character. This is used by
+ linkgit:git-pack-objects[1] to build "thin" pack, which records
+ objects in deltified form based on objects contained in these
+ excluded commits to reduce network traffic.
+
+--unpacked::
+
+ Only useful with '--objects'; print the object IDs that are not
+ in packs.
+
+--no-walk::
+
+ Only show the given revs, but do not traverse their ancestors.
+
+--do-walk::
+
+ Overrides a previous --no-walk.
diff --git a/Documentation/technical/.gitignore b/Documentation/technical/.gitignore
new file mode 100644
index 0000000000..8aa891daee
--- /dev/null
+++ b/Documentation/technical/.gitignore
@@ -0,0 +1 @@
+api-index.txt
diff --git a/Documentation/technical/api-allocation-growing.txt b/Documentation/technical/api-allocation-growing.txt
new file mode 100644
index 0000000000..43dbe09f73
--- /dev/null
+++ b/Documentation/technical/api-allocation-growing.txt
@@ -0,0 +1,34 @@
+allocation growing API
+======================
+
+Dynamically growing an array using realloc() is error prone and boring.
+
+Define your array with:
+
+* a pointer (`ary`) that points at the array, initialized to `NULL`;
+
+* an integer variable (`alloc`) that keeps track of how big the current
+ allocation is, initialized to `0`;
+
+* another integer variable (`nr`) to keep track of how many elements the
+ array currently has, initialized to `0`.
+
+Then before adding `n`th element to the array, call `ALLOC_GROW(ary, n,
+alloc)`. This ensures that the array can hold at least `n` elements by
+calling `realloc(3)` and adjusting `alloc` variable.
+
+------------
+sometype *ary;
+size_t nr;
+size_t alloc
+
+for (i = 0; i < nr; i++)
+ if (we like ary[i] already)
+ return;
+
+/* we did not like any existing one, so add one */
+ALLOC_GROW(ary, nr + 1, alloc);
+ary[nr++] = value you like;
+------------
+
+You are responsible for updating the `nr` variable.
diff --git a/Documentation/technical/api-builtin.txt b/Documentation/technical/api-builtin.txt
new file mode 100644
index 0000000000..52cdb4c520
--- /dev/null
+++ b/Documentation/technical/api-builtin.txt
@@ -0,0 +1,63 @@
+builtin API
+===========
+
+Adding a new built-in
+---------------------
+
+There are 4 things to do to add a bulit-in command implementation to
+git:
+
+. Define the implementation of the built-in command `foo` with
+ signature:
+
+ int cmd_foo(int argc, const char **argv, const char *prefix);
+
+. Add the external declaration for the function to `builtin.h`.
+
+. Add the command to `commands[]` table in `handle_internal_command()`,
+ defined in `git.c`. The entry should look like:
+
+ { "foo", cmd_foo, <options> },
+
+ where options is the bitwise-or of:
+
+`RUN_SETUP`::
+
+ Make sure there is a git directory to work on, and if there is a
+ work tree, chdir to the top of it if the command was invoked
+ in a subdirectory. If there is no work tree, no chdir() is
+ done.
+
+`USE_PAGER`::
+
+ If the standard output is connected to a tty, spawn a pager and
+ feed our output to it.
+
+. Add `builtin-foo.o` to `BUILTIN_OBJS` in `Makefile`.
+
+Additionally, if `foo` is a new command, there are 3 more things to do:
+
+. Add tests to `t/` directory.
+
+. Write documentation in `Documentation/git-foo.txt`.
+
+. Add an entry for `git-foo` to the list at the end of
+ `Documentation/cmd-list.perl`.
+
+
+How a built-in is called
+------------------------
+
+The implementation `cmd_foo()` takes three parameters, `argc`, `argv,
+and `prefix`. The first two are similar to what `main()` of a
+standalone command would be called with.
+
+When `RUN_SETUP` is specified in the `commands[]` table, and when you
+were started from a subdirectory of the work tree, `cmd_foo()` is called
+after chdir(2) to the top of the work tree, and `prefix` gets the path
+to the subdirectory the command started from. This allows you to
+convert a user-supplied pathname (typically relative to that directory)
+to a pathname relative to the top of the work tree.
+
+The return value from `cmd_foo()` becomes the exit status of the
+command.
diff --git a/Documentation/technical/api-decorate.txt b/Documentation/technical/api-decorate.txt
new file mode 100644
index 0000000000..1d52a6ce14
--- /dev/null
+++ b/Documentation/technical/api-decorate.txt
@@ -0,0 +1,6 @@
+decorate API
+============
+
+Talk about <decorate.h>
+
+(Linus)
diff --git a/Documentation/technical/api-diff.txt b/Documentation/technical/api-diff.txt
new file mode 100644
index 0000000000..83b007e708
--- /dev/null
+++ b/Documentation/technical/api-diff.txt
@@ -0,0 +1,166 @@
+diff API
+========
+
+The diff API is for programs that compare two sets of files (e.g. two
+trees, one tree and the index) and present the found difference in
+various ways. The calling program is responsible for feeding the API
+pairs of files, one from the "old" set and the corresponding one from
+"new" set, that are different. The library called through this API is
+called diffcore, and is responsible for two things.
+
+* finding total rewrites (`-B`), renames (`-M`) and copies (`-C`), and
+ changes that touch a string (`-S`), as specified by the caller.
+
+* outputting the differences in various formats, as specified by the
+ caller.
+
+Calling sequence
+----------------
+
+* Prepare `struct diff_options` to record the set of diff options, and
+ then call `diff_setup()` to initialize this structure. This sets up
+ the vanilla default.
+
+* Fill in the options structure to specify desired output format, rename
+ detection, etc. `diff_opt_parse()` can be used to parse options given
+ from the command line in a way consistent with existing git-diff
+ family of programs.
+
+* Call `diff_setup_done()`; this inspects the options set up so far for
+ internal consistency and make necessary tweaking to it (e.g. if
+ textual patch output was asked, recursive behaviour is turned on).
+
+* 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
+ API. These are thin wrappers to a lower-level `diff_queue()` function
+ that is flexible enough to record any of these kinds of changes.
+
+* Once you finish feeding the pairs of files, call `diffcore_std()`.
+ This will tell the diffcore library to go ahead and do its work.
+
+* Calling `diffcore_flush()` will produce the output.
+
+
+Data structures
+---------------
+
+* `struct diff_filespec`
+
+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
+feed `diff_queue()` function with.
+
+* `struct diff_filepair`
+
+This records a pair of `struct diff_filespec`; the filespec for a file
+in the "old" set (i.e. preimage) is called `one`, and the filespec for a
+file in the "new" set (i.e. postimage) is called `two`. A change that
+represents file creation has NULL in `one`, and file deletion has NULL
+in `two`.
+
+A `filepair` starts pointing at `one` and `two` that are from the same
+filename, but `diffcore_std()` can break pairs and match component
+filespecs with other filespecs from a different filepair to form new
+filepair. This is called 'rename detection'.
+
+* `struct diff_queue`
+
+This is a collection of filepairs. Notable members are:
+
+`queue`::
+
+ An array of pointers to `struct diff_filepair`. This
+ dynamically grows as you add filepairs;
+
+`alloc`::
+
+ The allocated size of the `queue` array;
+
+`nr`::
+
+ The number of elements in the `queue` array.
+
+
+* `struct diff_options`
+
+This describes the set of options the calling program wants to affect
+the operation of diffcore library with.
+
+Notable members are:
+
+`output_format`::
+ The output format used when `diff_flush()` is run.
+
+`context`::
+ Number of context lines to generate in patch output.
+
+`break_opt`, `detect_rename`, `rename-score`, `rename_limit`::
+ Affects the way detection logic for complete rewrites, renames
+ and copies.
+
+`abbrev`::
+ Number of hexdigits to abbreviate raw format output to.
+
+`pickaxe`::
+ A constant string (can and typically does contain newlines to
+ look for a block of text, not just a single line) to filter out
+ the filepairs that do not change the number of strings contained
+ in its preimage and postimage of the diff_queue.
+
+`flags`::
+ This is mostly a collection of boolean options that affects the
+ operation, but some do not have anything to do with the diffcore
+ library.
+
+BINARY, TEXT;;
+ Affects the way how a file that is seemingly binary is treated.
+
+FULL_INDEX;;
+ Tells the patch output format not to use abbreviated object
+ names on the "index" lines.
+
+FIND_COPIES_HARDER;;
+ Tells the diffcore library that the caller is feeding unchanged
+ filepairs to allow copies from unmodified files be detected.
+
+COLOR_DIFF;;
+ Output should be colored.
+
+COLOR_DIFF_WORDS;;
+ Output is a colored word-diff.
+
+NO_INDEX;;
+ Tells diff-files that the input is not tracked files but files
+ in random locations on the filesystem.
+
+ALLOW_EXTERNAL;;
+ Tells output routine that it is Ok to call user specified patch
+ output routine. Plumbing disables this to ensure stable output.
+
+QUIET;;
+ Do not show any output.
+
+REVERSE_DIFF;;
+ Tells the library that the calling program is feeding the
+ filepairs reversed; `one` is two, and `two` is one.
+
+EXIT_WITH_STATUS;;
+ For communication between the calling program and the options
+ parser; tell the calling program to signal the presence of
+ difference using program exit code.
+
+HAS_CHANGES;;
+ Internal; used for optimization to see if there is any change.
+
+SILENT_ON_REMOVE;;
+ Affects if diff-files shows removed files.
+
+RECURSIVE, TREE_IN_RECURSIVE;;
+ Tells if tree traversal done by tree-diff should recursively
+ descend into a tree object pair that are different in preimage
+ and postimage set.
+
+(JC)
diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
new file mode 100644
index 0000000000..5bbd18f020
--- /dev/null
+++ b/Documentation/technical/api-directory-listing.txt
@@ -0,0 +1,76 @@
+directory listing API
+=====================
+
+The directory listing API is used to enumerate paths in the work tree,
+optionally taking `.git/info/exclude` and `.gitignore` files per
+directory into account.
+
+Data structure
+--------------
+
+`struct dir_struct` structure is used to pass directory traversal
+options to the library and to record the paths discovered. The notable
+options are:
+
+`exclude_per_dir`::
+
+ The name of the file to be read in each directory for excluded
+ files (typically `.gitignore`).
+
+`collect_ignored`::
+
+ Include paths that are to be excluded in the result.
+
+`show_ignored`::
+
+ The traversal is for finding just ignored files, not unignored
+ files.
+
+`show_other_directories`::
+
+ Include a directory that is not tracked.
+
+`hide_empty_directories`::
+
+ Do not include a directory that is not tracked and is empty.
+
+`no_gitlinks`::
+
+ If set, recurse into a directory that looks like a git
+ directory. Otherwise it is shown as a directory.
+
+The result of the enumeration is left in these fields::
+
+`entries[]`::
+
+ An array of `struct dir_entry`, each element of which describes
+ a path.
+
+`nr`::
+
+ The number of members in `entries[]` array.
+
+`alloc`::
+
+ Internal use; keeps track of allocation of `entries[]` array.
+
+
+Calling sequence
+----------------
+
+* Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
+ sizeof(dir))`.
+
+* Call `add_exclude()` to add single exclude pattern,
+ `add_excludes_from_file()` to add patterns from a file
+ (e.g. `.git/info/exclude`), and/or set `dir.exclude_per_dir`. A
+ short-hand function `setup_standard_excludes()` can be used to set up
+ the standard set of exclude settings.
+
+* Set options described in the Data Structure section above.
+
+* Call `read_directory()`.
+
+* Use `dir.entries[]`.
+
+(JC)
diff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt
new file mode 100644
index 0000000000..9d97eaa9de
--- /dev/null
+++ b/Documentation/technical/api-gitattributes.txt
@@ -0,0 +1,111 @@
+gitattributes API
+=================
+
+gitattributes mechanism gives a uniform way to associate various
+attributes to set of paths.
+
+
+Data Structure
+--------------
+
+`struct git_attr`::
+
+ An attribute is an opaque object that is identified by its name.
+ Pass the name and its length to `git_attr()` function to obtain
+ the object of this type. The internal representation of this
+ structure is of no interest to the calling programs.
+
+`struct git_attr_check`::
+
+ This structure represents a set of attributes to check in a call
+ to `git_checkattr()` function, and receives the results.
+
+
+Calling Sequence
+----------------
+
+* Prepare an array of `struct git_attr_check` to define the list of
+ attributes you would want to check. To populate this array, you would
+ need to define necessary attributes by calling `git_attr()` function.
+
+* Call git_checkattr() to check the attributes for the path.
+
+* Inspect `git_attr_check` structure to see how each of the attribute in
+ the array is defined for the path.
+
+
+Attribute Values
+----------------
+
+An attribute for a path can be in one of four states: Set, Unset,
+Unspecified or set to a string, and `.value` member of `struct
+git_attr_check` records it. There are three macros to check these:
+
+`ATTR_TRUE()`::
+
+ Returns true if the attribute is Set for the path.
+
+`ATTR_FALSE()`::
+
+ Returns true if the attribute is Unset for the path.
+
+`ATTR_UNSET()`::
+
+ Returns true if the attribute is Unspecified for the path.
+
+If none of the above returns true, `.value` member points at a string
+value of the attribute for the path.
+
+
+Example
+-------
+
+To see how attributes "crlf" and "indent" are set for different paths.
+
+. Prepare an array of `struct git_attr_check` with two elements (because
+ we are checking two attributes). Initialize their `attr` member with
+ pointers to `struct git_attr` obtained by calling `git_attr()`:
+
+------------
+static struct git_attr_check check[2];
+static void setup_check(void)
+{
+ if (check[0].attr)
+ return; /* already done */
+ check[0].attr = git_attr("crlf", 4);
+ check[1].attr = git_attr("ident", 5);
+}
+------------
+
+. Call `git_checkattr()` with the prepared array of `struct git_attr_check`:
+
+------------
+ const char *path;
+
+ setup_check();
+ git_checkattr(path, ARRAY_SIZE(check), check);
+------------
+
+. Act on `.value` member of the result, left in `check[]`:
+
+------------
+ const char *value = check[0].value;
+
+ if (ATTR_TRUE(value)) {
+ The attribute is Set, by listing only the name of the
+ attribute in the gitattributes file for the path.
+ } else if (ATTR_FALSE(value)) {
+ The attribute is Unset, by listing the name of the
+ attribute prefixed with a dash - for the path.
+ } else if (ATTR_UNSET(value)) {
+ The attribute is not set nor unset for the path.
+ } else if (!strcmp(value, "input")) {
+ If none of ATTR_TRUE(), ATTR_FALSE(), or ATTR_UNSET() is
+ true, the value is a string set in the gitattributes
+ file for the path by saying "attr=value".
+ } else if (... other check using value as string ...) {
+ ...
+ }
+------------
+
+(JC)
diff --git a/Documentation/technical/api-grep.txt b/Documentation/technical/api-grep.txt
new file mode 100644
index 0000000000..a69cc8964d
--- /dev/null
+++ b/Documentation/technical/api-grep.txt
@@ -0,0 +1,8 @@
+grep API
+========
+
+Talk about <grep.h>, things like:
+
+* grep_buffer()
+
+(JC)
diff --git a/Documentation/technical/api-hash.txt b/Documentation/technical/api-hash.txt
new file mode 100644
index 0000000000..c784d3edcb
--- /dev/null
+++ b/Documentation/technical/api-hash.txt
@@ -0,0 +1,6 @@
+hash API
+========
+
+Talk about <hash.h>
+
+(Linus)
diff --git a/Documentation/technical/api-in-core-index.txt b/Documentation/technical/api-in-core-index.txt
new file mode 100644
index 0000000000..adbdbf5d75
--- /dev/null
+++ b/Documentation/technical/api-in-core-index.txt
@@ -0,0 +1,21 @@
+in-core index API
+=================
+
+Talk about <read-cache.c> and <cache-tree.c>, things like:
+
+* cache -> the_index macros
+* read_index()
+* write_index()
+* ie_match_stat() and ie_modified(); how they are different and when to
+ use which.
+* index_name_pos()
+* remove_index_entry_at()
+* remove_file_from_index()
+* add_file_to_index()
+* add_index_entry()
+* refresh_index()
+* discard_index()
+* cache_tree_invalidate_path()
+* cache_tree_update()
+
+(JC, Linus)
diff --git a/Documentation/technical/api-index-skel.txt b/Documentation/technical/api-index-skel.txt
new file mode 100644
index 0000000000..af7cc2e395
--- /dev/null
+++ b/Documentation/technical/api-index-skel.txt
@@ -0,0 +1,15 @@
+GIT API Documents
+=================
+
+GIT has grown a set of internal API over time. This collection
+documents them.
+
+////////////////////////////////////////////////////////////////
+// table of contents begin
+////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////
+// table of contents end
+////////////////////////////////////////////////////////////////
+
+2007-11-24
diff --git a/Documentation/technical/api-index.sh b/Documentation/technical/api-index.sh
new file mode 100755
index 0000000000..9c3f4131b8
--- /dev/null
+++ b/Documentation/technical/api-index.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+(
+ c=////////////////////////////////////////////////////////////////
+ skel=api-index-skel.txt
+ sed -e '/^\/\/ table of contents begin/q' "$skel"
+ echo "$c"
+
+ ls api-*.txt |
+ while read filename
+ do
+ case "$filename" in
+ api-index-skel.txt | api-index.txt) continue ;;
+ esac
+ title=$(sed -e 1q "$filename")
+ html=${filename%.txt}.html
+ echo "* link:$html[$title]"
+ done
+ echo "$c"
+ sed -n -e '/^\/\/ table of contents end/,$p' "$skel"
+) >api-index.txt+
+
+if test -f api-index.txt && cmp api-index.txt api-index.txt+ >/dev/null
+then
+ rm -f api-index.txt+
+else
+ mv api-index.txt+ api-index.txt
+fi
diff --git a/Documentation/technical/api-lockfile.txt b/Documentation/technical/api-lockfile.txt
new file mode 100644
index 0000000000..dd894043ae
--- /dev/null
+++ b/Documentation/technical/api-lockfile.txt
@@ -0,0 +1,74 @@
+lockfile API
+============
+
+The lockfile API serves two purposes:
+
+* Mutual exclusion. When we write out a new index file, first
+ we create a new file `$GIT_DIR/index.lock`, write the new
+ contents into it, and rename it to the final destination
+ `$GIT_DIR/index`. We try to create the `$GIT_DIR/index.lock`
+ file with O_EXCL so that we can notice and fail when somebody
+ else is already trying to update the index file.
+
+* Automatic cruft removal. After we create the "lock" file, we
+ may decide to `die()`, and we would want to make sure that we
+ remove the file that has not been committed to its final
+ destination. This is done by remembering the lockfiles we
+ created in a linked list and cleaning them up from an
+ `atexit(3)` handler. Outstanding lockfiles are also removed
+ when the program dies on a signal.
+
+
+The functions
+-------------
+
+hold_lock_file_for_update::
+
+ Take a pointer to `struct lock_file`, the filename of
+ the final destination (e.g. `$GIT_DIR/index`) and a flag
+ `die_on_error`. Attempt to create a lockfile for the
+ destination and return the file descriptor for writing
+ to the file. If `die_on_error` flag is true, it dies if
+ a lock is already taken for the file; otherwise it
+ returns a negative integer to the caller on failure.
+
+commit_lock_file::
+
+ Take a pointer to the `struct lock_file` initialized
+ with an earlier call to `hold_lock_file_for_update()`,
+ close the file descriptor and rename the lockfile to its
+ final destination. Returns 0 upon success, a negative
+ value on failure to close(2) or rename(2).
+
+rollback_lock_file::
+
+ Take a pointer to the `struct lock_file` initialized
+ with an earlier call to `hold_lock_file_for_update()`,
+ close the file descriptor and remove the lockfile.
+
+close_lock_file::
+ Take a pointer to the `struct lock_file` initialized
+ with an earlier call to `hold_lock_file_for_update()`,
+ and close the file descriptor. Returns 0 upon success,
+ a negative value on failure to close(2).
+
+Because the structure is used in an `atexit(3)` handler, its
+storage has to stay throughout the life of the program. It
+cannot be an auto variable allocated on the stack.
+
+Call `commit_lock_file()` or `rollback_lock_file()` when you are
+done writing to the file descriptor. If you do not call either
+and simply `exit(3)` from the program, an `atexit(3)` handler
+will close and remove the lockfile.
+
+If you need to close the file descriptor you obtained from
+`hold_lock_file_for_update` function yourself, do so by calling
+`close_lock_file()`. You should never call `close(2)` yourself!
+Otherwise the `struct
+lock_file` structure still remembers that the file descriptor
+needs to be closed, and a later call to `commit_lock_file()` or
+`rollback_lock_file()` will result in duplicate calls to
+`close(2)`. Worse yet, if you `close(2)`, open another file
+descriptor for completely different purpose, and then call
+`commit_lock_file()` or `rollback_lock_file()`, they may close
+that unrelated file descriptor.
diff --git a/Documentation/technical/api-object-access.txt b/Documentation/technical/api-object-access.txt
new file mode 100644
index 0000000000..03bb0e950d
--- /dev/null
+++ b/Documentation/technical/api-object-access.txt
@@ -0,0 +1,15 @@
+object access API
+=================
+
+Talk about <sha1_file.c> and <object.h> family, things like
+
+* read_sha1_file()
+* read_object_with_reference()
+* has_sha1_file()
+* write_sha1_file()
+* pretend_sha1_file()
+* lookup_{object,commit,tag,blob,tree}
+* parse_{object,commit,tag,blob,tree}
+* Use of object flags
+
+(JC, Shawn, Daniel, Dscho, Linus)
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
new file mode 100644
index 0000000000..b7cda94f54
--- /dev/null
+++ b/Documentation/technical/api-parse-options.txt
@@ -0,0 +1,6 @@
+parse-options API
+=================
+
+Talk about <parse-options.h>
+
+(Pierre)
diff --git a/Documentation/technical/api-path-list.txt b/Documentation/technical/api-path-list.txt
new file mode 100644
index 0000000000..d077683171
--- /dev/null
+++ b/Documentation/technical/api-path-list.txt
@@ -0,0 +1,9 @@
+path-list API
+=============
+
+Talk about <path-list.h>, things like
+
+* it is not just paths but strings in general;
+* the calling sequence.
+
+(Dscho)
diff --git a/Documentation/technical/api-quote.txt b/Documentation/technical/api-quote.txt
new file mode 100644
index 0000000000..e8a1bce94e
--- /dev/null
+++ b/Documentation/technical/api-quote.txt
@@ -0,0 +1,10 @@
+quote API
+=========
+
+Talk about <quote.h>, things like
+
+* sq_quote and unquote
+* c_style quote and unquote
+* quoting for foreign languages
+
+(JC)
diff --git a/Documentation/technical/api-remote.txt b/Documentation/technical/api-remote.txt
new file mode 100644
index 0000000000..073b22bd83
--- /dev/null
+++ b/Documentation/technical/api-remote.txt
@@ -0,0 +1,123 @@
+Remotes configuration API
+=========================
+
+The API in remote.h gives access to the configuration related to
+remotes. It handles all three configuration mechanisms historically
+and currently used by git, and presents the information in a uniform
+fashion. Note that the code also handles plain URLs without any
+configuration, giving them just the default information.
+
+struct remote
+-------------
+
+`name`::
+
+ The user's nickname for the remote
+
+`url`::
+
+ An array of all of the url_nr URLs configured for the remote
+
+`push`::
+
+ An array of refspecs configured for pushing, with
+ push_refspec being the literal strings, and push_refspec_nr
+ being the quantity.
+
+`fetch`::
+
+ An array of refspecs configured for fetching, with
+ fetch_refspec being the literal strings, and fetch_refspec_nr
+ being the quantity.
+
+`fetch_tags`::
+
+ The setting for whether to fetch tags (as a separate rule from
+ the configured refspecs); -1 means never to fetch tags, 0
+ means to auto-follow tags based on the default heuristic, 1
+ means to always auto-follow tags, and 2 means to fetch all
+ tags.
+
+`receivepack`, `uploadpack`::
+
+ The configured helper programs to run on the remote side, for
+ git-native protocols.
+
+`http_proxy`::
+
+ The proxy to use for curl (http, https, ftp, etc.) URLs.
+
+struct remotes can be found by name with remote_get(), and iterated
+through with for_each_remote(). remote_get(NULL) will return the
+default remote, given the current branch and configuration.
+
+struct refspec
+--------------
+
+A struct refspec holds the parsed interpretation of a refspec. If it
+will force updates (starts with a '+'), force is true. If it is a
+pattern (sides end with '*') pattern is true. src and dest are the two
+sides (if a pattern, only the part outside of the wildcards); if there
+is only one side, it is src, and dst is NULL; if sides exist but are
+empty (i.e., the refspec either starts or ends with ':'), the
+corresponding side is "".
+
+This parsing can be done to an array of strings to give an array of
+struct refpsecs with parse_ref_spec().
+
+remote_find_tracking(), given a remote and a struct refspec with
+either src or dst filled out, will fill out the other such that the
+result is in the "fetch" specification for the remote (note that this
+evaluates patterns and returns a single result).
+
+struct branch
+-------------
+
+Note that this may end up moving to branch.h
+
+struct branch holds the configuration for a branch. It can be looked
+up with branch_get(name) for "refs/heads/{name}", or with
+branch_get(NULL) for HEAD.
+
+It contains:
+
+`name`::
+
+ The short name of the branch.
+
+`refname`::
+
+ The full path for the branch ref.
+
+`remote_name`::
+
+ The name of the remote listed in the configuration.
+
+`remote`::
+
+ The struct remote for that remote.
+
+`merge_name`::
+
+ An array of the "merge" lines in the configuration.
+
+`merge`::
+
+ An array of the struct refspecs used for the merge lines. That
+ is, merge[i]->dst is a local tracking ref which should be
+ merged into this branch by default.
+
+`merge_nr`::
+
+ The number of merge configurations
+
+branch_has_merge_config() returns true if the given branch has merge
+configuration given.
+
+Other stuff
+-----------
+
+There is other stuff in remote.h that is related, in general, to the
+process of interacting with remotes.
+
+(Daniel Barkalow)
diff --git a/Documentation/technical/api-revision-walking.txt b/Documentation/technical/api-revision-walking.txt
new file mode 100644
index 0000000000..01a24551af
--- /dev/null
+++ b/Documentation/technical/api-revision-walking.txt
@@ -0,0 +1,9 @@
+revision walking API
+====================
+
+Talk about <revision.h>, things like:
+
+* two diff_options, one for path limiting, another for output;
+* calling sequence: init_revisions(), setup_revsions(), get_revision();
+
+(Linus, JC, Dscho)
diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt
new file mode 100644
index 0000000000..dfbf9ac5d0
--- /dev/null
+++ b/Documentation/technical/api-run-command.txt
@@ -0,0 +1,171 @@
+run-command API
+===============
+
+The run-command API offers a versatile tool to run sub-processes with
+redirected input and output as well as with a modified environment
+and an alternate current directory.
+
+A similar API offers the capability to run a function asynchronously,
+which is primarily used to capture the output that the function
+produces in the caller in order to process it.
+
+
+Functions
+---------
+
+`start_command`::
+
+ Start a sub-process. Takes a pointer to a `struct child_process`
+ that specifies the details and returns pipe FDs (if requested).
+ See below for details.
+
+`finish_command`::
+
+ Wait for the completion of a sub-process that was started with
+ start_command().
+
+`run_command`::
+
+ A convenience function that encapsulates a sequence of
+ start_command() followed by finish_command(). Takes a pointer
+ to a `struct child_process` that specifies the details.
+
+`run_command_v_opt`, `run_command_v_opt_dir`, `run_command_v_opt_cd_env`::
+
+ Convenience functions that encapsulate a sequence of
+ start_command() followed by finish_command(). The argument argv
+ specifies the program and its arguments. The argument opt is zero
+ or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`, or
+ `RUN_COMMAND_STDOUT_TO_STDERR` that correspond to the members
+ .no_stdin, .git_cmd, .stdout_to_stderr of `struct child_process`.
+ The argument dir corresponds the member .dir. The argument env
+ corresponds to the member .env.
+
+`start_async`::
+
+ Run a function asynchronously. Takes a pointer to a `struct
+ async` that specifies the details and returns a pipe FD
+ from which the caller reads. See below for details.
+
+`finish_async`::
+
+ Wait for the completeion of an asynchronous function that was
+ started with start_async().
+
+
+Data structures
+---------------
+
+* `struct child_process`
+
+This describes the arguments, redirections, and environment of a
+command to run in a sub-process.
+
+The caller:
+
+1. allocates and clears (memset(&chld, '0', sizeof(chld));) a
+ struct child_process variable;
+2. initializes the members;
+3. calls start_command();
+4. processes the data;
+5. closes file descriptors (if necessary; see below);
+6. calls finish_command().
+
+The .argv member is set up as an array of string pointers (NULL
+terminated), of which .argv[0] is the program name to run (usually
+without a path). If the command to run is a git command, set argv[0] to
+the command name without the 'git-' prefix and set .git_cmd = 1.
+
+The members .in, .out, .err are used to redirect stdin, stdout,
+stderr as follows:
+
+. Specify 0 to request no special redirection. No new file descriptor
+ is allocated. The child process simply inherits the channel from the
+ parent.
+
+. Specify -1 to have a pipe allocated; start_command() replaces -1
+ by the pipe FD in the following way:
+
+ .in: Returns the writable pipe end into which the caller writes;
+ the readable end of the pipe becomes the child's stdin.
+
+ .out, .err: Returns the readable pipe end from which the caller
+ reads; the writable end of the pipe end becomes child's
+ stdout/stderr.
+
+ The caller of start_command() must close the so returned FDs
+ after it has completed reading from/writing to it!
+
+. Specify a file descriptor > 0 to be used by the child:
+
+ .in: The FD must be readable; it becomes child's stdin.
+ .out: The FD must be writable; it becomes child's stdout.
+ .err > 0 is not supported.
+
+ The specified FD is closed by start_command(), even if it fails to
+ run the sub-process!
+
+. Special forms of redirection are available by setting these members
+ to 1:
+
+ .no_stdin, .no_stdout, .no_stderr: The respective channel is
+ redirected to /dev/null.
+
+ .stdout_to_stderr: stdout of the child is redirected to the
+ parent's stderr (i.e. *not* to what .err or
+ .no_stderr specify).
+
+To modify the environment of the sub-process, specify an array of
+string pointers (NULL terminated) in .env:
+
+. If the string is of the form "VAR=value", i.e. it contains '='
+ the variable is added to the child process's environment.
+
+. If the string does not contain '=', it names an environement
+ variable that will be removed from the child process's envionment.
+
+To specify a new initial working directory for the sub-process,
+specify it in the .dir member.
+
+
+* `struct async`
+
+This describes a function to run asynchronously, whose purpose is
+to produce output that the caller reads.
+
+The caller:
+
+1. allocates and clears (memset(&asy, '0', sizeof(asy));) a
+ struct async variable;
+2. initializes .proc and .data;
+3. calls start_async();
+4. processes the data by reading from the fd in .out;
+5. closes .out;
+6. calls finish_async().
+
+The function pointer in .proc has the following signature:
+
+ int proc(int fd, void *data);
+
+. fd specifies a writable file descriptor to which the function must
+ write the data that it produces. The function *must* close this
+ descriptor before it returns.
+
+. data is the value that the caller has specified in the .data member
+ of struct async.
+
+. The return value of the function is 0 on success and non-zero
+ on failure. If the function indicates failure, finish_async() will
+ report failure as well.
+
+
+There are serious restrictions on what the asynchronous function can do
+because this facility is implemented by a pipe to a forked process on
+UNIX, but by a thread in the same address space on Windows:
+
+. It cannot change the program's state (global variables, environment,
+ etc.) in a way that the caller notices; in other words, .out is the
+ only communication channel to the caller.
+
+. It must not change the program's state that the caller of the
+ facility also uses.
diff --git a/Documentation/technical/api-setup.txt b/Documentation/technical/api-setup.txt
new file mode 100644
index 0000000000..4f63a04d7d
--- /dev/null
+++ b/Documentation/technical/api-setup.txt
@@ -0,0 +1,13 @@
+setup API
+=========
+
+Talk about
+
+* setup_git_directory()
+* setup_git_directory_gently()
+* is_inside_git_dir()
+* is_inside_work_tree()
+* setup_work_tree()
+* get_pathspec()
+
+(Dscho)
diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt
new file mode 100644
index 0000000000..a52e4f36d5
--- /dev/null
+++ b/Documentation/technical/api-strbuf.txt
@@ -0,0 +1,6 @@
+strbuf API
+==========
+
+Talk about <strbuf.h>
+
+(Pierre, JC)
diff --git a/Documentation/technical/api-tree-walking.txt b/Documentation/technical/api-tree-walking.txt
new file mode 100644
index 0000000000..e3ddf91284
--- /dev/null
+++ b/Documentation/technical/api-tree-walking.txt
@@ -0,0 +1,12 @@
+tree walking API
+================
+
+Talk about <tree-walk.h>, things like
+
+* struct tree_desc
+* init_tree_desc
+* tree_entry_extract
+* update_tree_entry
+* get_tree_entry
+
+(JC, Linus)
diff --git a/Documentation/technical/api-xdiff-interface.txt b/Documentation/technical/api-xdiff-interface.txt
new file mode 100644
index 0000000000..6296ecad1d
--- /dev/null
+++ b/Documentation/technical/api-xdiff-interface.txt
@@ -0,0 +1,7 @@
+xdiff interface API
+===================
+
+Talk about our calling convention to xdiff library, including
+xdiff_emit_consume_fn.
+
+(Dscho, JC)
diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt
index e5b31c81fa..aa87756a55 100644
--- a/Documentation/technical/pack-format.txt
+++ b/Documentation/technical/pack-format.txt
@@ -1,9 +1,9 @@
GIT pack format
===============
-= pack-*.pack file has the following format:
+= pack-*.pack files have the following format:
- - The header appears at the beginning and consists of the following:
+ - A header appears at the beginning and consists of the following:
4-byte signature:
The signature is: {'P', 'A', 'C', 'K'}
@@ -34,18 +34,14 @@ GIT pack format
- The trailer records 20-byte SHA1 checksum of all of the above.
-= pack-*.idx file has the following format:
+= Original (version 1) pack-*.idx files have the following format:
- The header consists of 256 4-byte network byte order
integers. N-th entry of this table records the number of
objects in the corresponding pack, the first byte of whose
- object name are smaller than N. This is called the
+ object name is less than or equal to N. This is called the
'first-level fan-out' table.
- Observation: we would need to extend this to an array of
- 8-byte integers to go beyond 4G objects per pack, but it is
- not strictly necessary.
-
- The header is followed by sorted 24-byte entries, one entry
per object in the pack. Each entry is:
@@ -55,10 +51,6 @@ GIT pack format
20-byte object name.
- Observation: we would definitely need to extend this to
- 8-byte integer plus 20-byte object name to handle a packfile
- that is larger than 4GB.
-
- The file is concluded with a trailer:
A copy of the 20-byte SHA1 checksum at the end of
@@ -68,31 +60,30 @@ GIT pack format
Pack Idx file:
- idx
- +--------------------------------+
- | fanout[0] = 2 |-.
- +--------------------------------+ |
+ -- +--------------------------------+
+fanout | fanout[0] = 2 (for example) |-.
+table +--------------------------------+ |
| fanout[1] | |
+--------------------------------+ |
| fanout[2] | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
- | fanout[255] | |
- +--------------------------------+ |
-main | offset | |
-index | object name 00XXXXXXXXXXXXXXXX | |
-table +--------------------------------+ |
- | offset | |
- | object name 00XXXXXXXXXXXXXXXX | |
- +--------------------------------+ |
- .-| offset |<+
- | | object name 01XXXXXXXXXXXXXXXX |
- | +--------------------------------+
- | | offset |
- | | object name 01XXXXXXXXXXXXXXXX |
- | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- | | offset |
- | | object name FFXXXXXXXXXXXXXXXX |
- | +--------------------------------+
+ | fanout[255] = total objects |---.
+ -- +--------------------------------+ | |
+main | offset | | |
+index | object name 00XXXXXXXXXXXXXXXX | | |
+table +--------------------------------+ | |
+ | offset | | |
+ | object name 00XXXXXXXXXXXXXXXX | | |
+ +--------------------------------+<+ |
+ .-| offset | |
+ | | object name 01XXXXXXXXXXXXXXXX | |
+ | +--------------------------------+ |
+ | | offset | |
+ | | object name 01XXXXXXXXXXXXXXXX | |
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
+ | | offset | |
+ | | object name FFXXXXXXXXXXXXXXXX | |
+ --| +--------------------------------+<--+
trailer | | packfile checksum |
| +--------------------------------+
| | idxfile checksum |
@@ -116,3 +107,40 @@ Pack file entry: <+
20-byte base object name SHA1 (the size above is the
size of the delta data that follows).
delta data, deflated.
+
+
+= Version 2 pack-*.idx files support packs larger than 4 GiB, and
+ have some other reorganizations. They have the format:
+
+ - A 4-byte magic number '\377tOc' which is an unreasonable
+ fanout[0] value.
+
+ - A 4-byte version number (= 2)
+
+ - A 256-entry fan-out table just like v1.
+
+ - A table of sorted 20-byte SHA1 object names. These are
+ packed together without offset values to reduce the cache
+ footprint of the binary search for a specific object name.
+
+ - A table of 4-byte CRC32 values of the packed object data.
+ This is new in v2 so compressed data can be copied directly
+ from pack to pack during repacking without undetected
+ data corruption.
+
+ - A table of 4-byte offset values (in network byte order).
+ These are usually 31-bit pack file offsets, but large
+ offsets are encoded as an index into the next table with
+ the msbit set.
+
+ - A table of 8-byte offset entries (empty for pack files less
+ than 2 GiB). Pack files are organized with heavily used
+ objects toward the front, so most object references should
+ not need to refer to this table.
+
+ - The same trailer as a v1 pack file:
+
+ A copy of the 20-byte SHA1 checksum at the end of
+ corresponding packfile.
+
+ 20-byte SHA1-checksum of all of the above.
diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt
index 5030d9f2f8..6bdf034b3a 100644
--- a/Documentation/technical/racy-git.txt
+++ b/Documentation/technical/racy-git.txt
@@ -184,7 +184,7 @@ In a large project where raciness avoidance cost really matters,
however, the initial computation of all object names in the
index takes more than one second, and the index file is written
out after all that happens. Therefore the timestamp of the
-index file will be more than one seconds later than the the
+index file will be more than one seconds later than the
youngest file in the working tree. This means that in these
cases there actually will not be any racily clean entry in
the resulting index.
diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt
index 5c39a165f5..7fac47de8b 100644
--- a/Documentation/tutorial-2.txt
+++ b/Documentation/tutorial-2.txt
@@ -172,7 +172,7 @@ merge, with the parent references pointing to the heads of the merged
branches.
Besides blobs, trees, and commits, the only remaining type of object
-is a "tag", which we won't discuss here; refer to gitlink:git-tag[1]
+is a "tag", which we won't discuss here; refer to linkgit:git-tag[1]
for details.
So now we know how git uses the object database to represent a
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
index fff1068c54..e2bbda53f0 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -47,7 +47,7 @@ You've now initialized the working directory--you may notice a new
directory created, named ".git".
Next, tell git to take a snapshot of the contents of all files under the
-current directory (note the '.'), with gitlink:git-add[1]:
+current directory (note the '.'), with linkgit:git-add[1]:
------------------------------------------------
$ git add .
@@ -55,7 +55,7 @@ $ git add .
This snapshot is now stored in a temporary staging area which git calls
the "index". You can permanently store the contents of the index in the
-repository with gitlink:git-commit[1]:
+repository with linkgit:git-commit[1]:
------------------------------------------------
$ git commit
@@ -74,15 +74,15 @@ $ git add file1 file2 file3
------------------------------------------------
You are now ready to commit. You can see what is about to be committed
-using gitlink:git-diff[1] with the --cached option:
+using linkgit:git-diff[1] with the --cached option:
------------------------------------------------
$ git diff --cached
------------------------------------------------
-(Without --cached, gitlink:git-diff[1] will show you any changes that
+(Without --cached, linkgit:git-diff[1] will show you any changes that
you've made but not yet added to the index.) You can also get a brief
-summary of the situation with gitlink:git-status[1]:
+summary of the situation with linkgit:git-status[1]:
------------------------------------------------
$ git status
@@ -358,7 +358,7 @@ $ git config --get remote.origin.url
-------------------------------------
(The complete configuration created by git-clone is visible using
-"git config -l", and the gitlink:git-config[1] man page
+"git config -l", and the linkgit:git-config[1] man page
explains the meaning of each option.)
Git also keeps a pristine copy of Alice's master branch under the
@@ -377,10 +377,10 @@ $ git clone alice.org:/home/alice/project myrepo
-------------------------------------
Alternatively, git has a native protocol, or can use rsync or http;
-see gitlink:git-pull[1] for details.
+see linkgit:git-pull[1] for details.
Git can also be used in a CVS-like mode, with a central repository
-that various users push changes to; see gitlink:git-push[1] and
+that various users push changes to; see linkgit:git-push[1] and
link:cvs-migration.html[git for CVS users].
Exploring history
@@ -442,7 +442,7 @@ $ git-tag v2.5 1b2e1d63ff
you can refer to 1b2e1d63ff by the name "v2.5". If you intend to
share this name with other people (for example, to identify a release
version), you should create a "tag" object, and perhaps sign it; see
-gitlink:git-tag[1] for details.
+linkgit:git-tag[1] for details.
Any git command that needs to know a commit can take any of these
names. For example:
@@ -461,7 +461,7 @@ this branch. If this branch is the only branch containing those
commits, they will be lost. Also, don't use "git reset" on a
publicly-visible branch that other developers pull from, as it will
force needless merges on other developers to clean up the history.
-If you need to undo changes that you have pushed, use gitlink:git-revert[1]
+If you need to undo changes that you have pushed, use linkgit:git-revert[1]
instead.
The git grep command can search for strings in any version of your
@@ -567,12 +567,12 @@ need to make the most of git.
If you don't want to continue with that right away, a few other
digressions that may be interesting at this point are:
- * gitlink:git-format-patch[1], gitlink:git-am[1]: These convert
+ * linkgit:git-format-patch[1], linkgit:git-am[1]: These convert
series of git commits into emailed patches, and vice versa,
useful for projects such as the linux kernel which rely heavily
on emailed patches.
- * gitlink:git-bisect[1]: When there is a regression in your
+ * linkgit:git-bisect[1]: When there is a regression in your
project, one way to track down the bug is by searching through
the history to find the exact commit that's to blame. Git bisect
can help you perform a binary search for that commit. It is
diff --git a/Documentation/urls.txt b/Documentation/urls.txt
index e67f9140ab..81ac17f32a 100644
--- a/Documentation/urls.txt
+++ b/Documentation/urls.txt
@@ -36,5 +36,11 @@ To sync with a local directory, you can use:
- file:///path/to/repo.git/
===============================================================
+ifndef::git-clone[]
They are mostly equivalent, except when cloning. See
-gitlink:git-clone[1] for details.
+linkgit:git-clone[1] for details.
+endif::git-clone[]
+
+ifdef::git-clone[]
+They are equivalent, except the former implies --local option.
+endif::git-clone[]
diff --git a/Documentation/user-manual.conf b/Documentation/user-manual.conf
index 92b01ecf71..339b30919e 100644
--- a/Documentation/user-manual.conf
+++ b/Documentation/user-manual.conf
@@ -7,7 +7,7 @@ startsb=&#91;
endsb=&#93;
tilde=&#126;
-[gitlink-inlinemacro]
+[linkgit-inlinemacro]
<ulink url="{target}.html">{target}{0?({0})}</ulink>
ifdef::backend-docbook[]
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index d99adc6f72..40b0de0877 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -42,7 +42,7 @@ How to get a git repository
It will be useful to have a git repository to experiment with as you
read this manual.
-The best way to get one is by using the gitlink:git-clone[1] command to
+The best way to get one is by using the linkgit:git-clone[1] command to
download a copy of an existing repository. If you don't already have a
project in mind, here are some interesting examples:
@@ -56,11 +56,12 @@ $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
The initial clone may be time-consuming for a large project, but you
will only need to clone once.
-The clone command creates a new directory named after the project
-("git" or "linux-2.6" in the examples above). After you cd into this
+The clone command creates a new directory named after the project ("git"
+or "linux-2.6" in the examples above). After you cd into this
directory, you will see that it contains a copy of the project files,
-together with a special top-level directory named ".git", which
-contains all the information about the history of the project.
+called the <<def_working_tree,working tree>>, together with a special
+top-level directory named ".git", which contains all the information
+about the history of the project.
[[how-to-check-out]]
How to check out a different version of a project
@@ -71,9 +72,14 @@ of files. It stores the history as a compressed collection of
interrelated snapshots of the project's contents. In git each such
version is called a <<def_commit,commit>>.
-A single git repository may contain multiple branches. It keeps track
-of them by keeping a list of <<def_head,heads>> which reference the
-latest commit on each branch; the gitlink:git-branch[1] command shows
+Those snapshots aren't necessarily all arranged in a single line from
+oldest to newest; instead, work may simultaneously proceed along
+parallel lines of development, called <<def_branch,branches>>, which may
+merge and diverge.
+
+A single git repository can track development on multiple branches. It
+does this by keeping a list of <<def_head,heads>> which reference the
+latest commit on each branch; the linkgit:git-branch[1] command shows
you the list of branch heads:
------------------------------------------------
@@ -87,7 +93,7 @@ the project referred to by that branch head.
Most projects also use <<def_tag,tags>>. Tags, like heads, are
references into the project's history, and can be listed using the
-gitlink:git-tag[1] command:
+linkgit:git-tag[1] command:
------------------------------------------------
$ git tag -l
@@ -107,14 +113,14 @@ Tags are expected to always point at the same version of a project,
while heads are expected to advance as development progresses.
Create a new branch head pointing to one of these versions and check it
-out using gitlink:git-checkout[1]:
+out using linkgit:git-checkout[1]:
------------------------------------------------
$ git checkout -b new v2.6.13
------------------------------------------------
The working directory then reflects the contents that the project had
-when it was tagged v2.6.13, and gitlink:git-branch[1] shows two
+when it was tagged v2.6.13, and linkgit:git-branch[1] shows two
branches, with an asterisk marking the currently checked-out branch:
------------------------------------------------
@@ -140,7 +146,7 @@ Understanding History: Commits
------------------------------
Every change in the history of a project is represented by a commit.
-The gitlink:git-show[1] command shows the most recent commit on the
+The linkgit:git-show[1] command shows the most recent commit on the
current branch:
------------------------------------------------
@@ -202,7 +208,7 @@ representing a merge can therefore have more than one parent, with
each parent representing the most recent commit on one of the lines
of development leading to that point.
-The best way to see how this works is using the gitlink:gitk[1]
+The best way to see how this works is using the linkgit:gitk[1]
command; running gitk now on a git repository and looking for merge
commits will help understand how the git organizes history.
@@ -329,7 +335,7 @@ 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
-can view using the "-r" option to gitlink:git-branch[1]:
+can view using the "-r" option to linkgit:git-branch[1]:
------------------------------------------------
$ git branch -r
@@ -372,7 +378,7 @@ exists a tag and a branch with the same name.
(Newly created refs are actually stored in the .git/refs directory,
under the path given by their name. However, for efficiency reasons
they may also be packed together in a single file; see
-gitlink:git-pack-refs[1]).
+linkgit:git-pack-refs[1]).
As another useful shortcut, the "HEAD" of a repository can be referred
to just using the name of that repository. So, for example, "origin"
@@ -381,7 +387,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 gitlink:git-rev-parse[1].
+REVISIONS" section of linkgit:git-rev-parse[1].
[[Updating-a-repository-with-git-fetch]]
Updating a repository with git fetch
@@ -401,7 +407,7 @@ Fetching branches from other repositories
-----------------------------------------
You can also track branches from repositories other than the one you
-cloned from, using gitlink:git-remote[1]:
+cloned from, using linkgit:git-remote[1]:
-------------------------------------------------
$ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
@@ -437,7 +443,7 @@ $ cat .git/config
This is what causes git to track the remote's branches; you may modify
or delete these configuration options by editing .git/config with a
text editor. (See the "CONFIGURATION FILE" section of
-gitlink:git-config[1] for details.)
+linkgit:git-config[1] for details.)
[[exploring-git-history]]
Exploring git history
@@ -462,7 +468,7 @@ Suppose version 2.6.18 of your project worked, but the version at
"master" crashes. Sometimes the best way to find the cause of such a
regression is to perform a brute-force search through the project's
history to find the particular commit that caused the problem. The
-gitlink:git-bisect[1] command can help you do this:
+linkgit:git-bisect[1] command can help you do this:
-------------------------------------------------
$ git bisect start
@@ -475,7 +481,7 @@ Bisecting: 3537 revisions left to test after this
If you run "git branch" at this point, you'll see that git has
temporarily moved you to a new branch named "bisect". This branch
points to a commit (with commit id 65934...) that is reachable from
-v2.6.19 but not from v2.6.18. Compile and test it, and see whether
+"master" but not from v2.6.18. Compile and test it, and see whether
it crashes. Assume it does crash. Then:
-------------------------------------------------
@@ -491,7 +497,7 @@ half each time.
After about 13 tests (in this case), it will output the commit id of
the guilty commit. You can then examine the commit with
-gitlink:git-show[1], find out who wrote it, and mail them your bug
+linkgit:git-show[1], find out who wrote it, and mail them your bug
report with the commit id. Finally, run
-------------------------------------------------
@@ -537,7 +543,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
-gitlink:git-rev-parse[1] man page for the complete list of ways to
+linkgit:git-rev-parse[1] man page for the complete list of ways to
name revisions. Some examples:
-------------------------------------------------
@@ -578,7 +584,7 @@ When we discuss merges we'll also see the special name MERGE_HEAD,
which refers to the other branch that we're merging in to the current
branch.
-The gitlink:git-rev-parse[1] command is a low-level command that is
+The linkgit:git-rev-parse[1] command is a low-level command that is
occasionally useful for translating some name for a commit to the object
name for that commit:
@@ -602,14 +608,14 @@ You can use stable-1 to refer to the commit 1b2e1d63ff.
This creates a "lightweight" tag. If you would also like to include a
comment with the tag, and possibly sign it cryptographically, then you
-should create a tag object instead; see the gitlink:git-tag[1] man page
+should create a tag object instead; see the linkgit:git-tag[1] man page
for details.
[[browsing-revisions]]
Browsing revisions
------------------
-The gitlink:git-log[1] command can show lists of commits. On its
+The linkgit:git-log[1] command can show lists of commits. On its
own, it shows all commits reachable from the parent commit; but you
can also make more specific requests:
@@ -639,7 +645,7 @@ You can also ask git log to show patches:
$ git log -p
-------------------------------------------------
-See the "--pretty" option in the gitlink:git-log[1] man page for more
+See the "--pretty" option in the linkgit:git-log[1] man page for more
display options.
Note that git log starts with the most recent commit and works
@@ -652,22 +658,29 @@ Generating diffs
----------------
You can generate diffs between any two versions using
-gitlink:git-diff[1]:
+linkgit:git-diff[1]:
-------------------------------------------------
$ git diff master..test
-------------------------------------------------
-Sometimes what you want instead is a set of patches:
+That will produce the diff between the tips of the two branches. If
+you'd prefer to find the diff from their common ancestor to test, you
+can use three dots instead of two:
+
+-------------------------------------------------
+$ git diff master...test
+-------------------------------------------------
+
+Sometimes what you want instead is a set of patches; for this you can
+use linkgit:git-format-patch[1]:
-------------------------------------------------
$ git format-patch master..test
-------------------------------------------------
will generate a file with a patch for each commit reachable from test
-but not from master. Note that if master also has commits which are
-not reachable from test, then the combined result of these patches
-will not be the same as the diff produced by the git-diff example.
+but not from master.
[[viewing-old-file-versions]]
Viewing old file versions
@@ -701,7 +714,7 @@ $ git log --pretty=oneline origin..mybranch | wc -l
-------------------------------------------------
Alternatively, you may often see this sort of thing done with the
-lower-level command gitlink:git-rev-list[1], which just lists the SHA1's
+lower-level command linkgit:git-rev-list[1], which just lists the SHA1's
of all the given commits:
-------------------------------------------------
@@ -759,7 +772,7 @@ You could just visually inspect the commits since e05db0fd:
$ gitk e05db0fd..
-------------------------------------------------
-Or you can use gitlink:git-name-rev[1], which will give the commit a
+Or you can use linkgit:git-name-rev[1], which will give the commit a
name based on any tag it finds pointing to one of the commit's
descendants:
@@ -768,7 +781,7 @@ $ git name-rev --tags e05db0fd
e05db0fd tags/v1.5.0-rc1^0~23
-------------------------------------------------
-The gitlink:git-describe[1] command does the opposite, naming the
+The linkgit:git-describe[1] command does the opposite, naming the
revision using a tag on which the given commit is based:
-------------------------------------------------
@@ -780,7 +793,7 @@ but that may sometimes help you guess which tags might come after the
given commit.
If you just want to verify whether a given tagged version contains a
-given commit, you could use gitlink:git-merge-base[1]:
+given commit, you could use linkgit:git-merge-base[1]:
-------------------------------------------------
$ git merge-base e05db0fd v1.5.0-rc1
@@ -801,7 +814,7 @@ $ git log v1.5.0-rc1..e05db0fd
will produce empty output if and only if v1.5.0-rc1 includes e05db0fd,
because it outputs only commits that are not reachable from v1.5.0-rc1.
-As yet another alternative, the gitlink:git-show-branch[1] command lists
+As yet another alternative, the linkgit:git-show-branch[1] command lists
the commits reachable from its arguments with a display on the left-hand
side that indicates which arguments that commit is reachable from. So,
you can run something like
@@ -834,7 +847,7 @@ Suppose you would like to see all the commits reachable from the branch
head named "master" but not from any other head in your repository.
We can list all the heads in this repository with
-gitlink:git-show-ref[1]:
+linkgit:git-show-ref[1]:
-------------------------------------------------
$ git show-ref --heads
@@ -871,14 +884,14 @@ commits reachable from some head but not from any tag in the repository:
$ gitk $( git show-ref --heads ) --not $( git show-ref --tags )
-------------------------------------------------
-(See gitlink:git-rev-parse[1] for explanations of commit-selecting
+(See linkgit:git-rev-parse[1] for explanations of commit-selecting
syntax such as `--not`.)
[[making-a-release]]
Creating a changelog and tarball for a software release
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The gitlink:git-archive[1] command can create a tar or zip archive from
+The linkgit:git-archive[1] command can create a tar or zip archive from
any version of a project; for example:
-------------------------------------------------
@@ -931,8 +944,8 @@ $ git log --raw --abbrev=40 --pretty=oneline |
-------------------------------------------------
Figuring out why this works is left as an exercise to the (advanced)
-student. The gitlink:git-log[1], gitlink:git-diff-tree[1], and
-gitlink:git-hash-object[1] man pages may prove helpful.
+student. The linkgit:git-log[1], linkgit:git-diff-tree[1], and
+linkgit:git-hash-object[1] man pages may prove helpful.
[[Developing-with-git]]
Developing with git
@@ -952,7 +965,7 @@ file named .gitconfig in your home directory:
email = you@yourdomain.example.com
------------------------------------------------
-(See the "CONFIGURATION FILE" section of gitlink:git-config[1] for
+(See the "CONFIGURATION FILE" section of linkgit:git-config[1] for
details on the configuration file.)
@@ -1075,7 +1088,7 @@ $ git diff HEAD # difference between HEAD and working tree; what
$ git status # a brief per-file summary of the above.
-------------------------------------------------
-You can also use gitlink:git-gui[1] to create commits, view changes in
+You can also use linkgit:git-gui[1] to create commits, view changes in
the index and the working tree files, and individually select diff hunks
for inclusion in the index (by right-clicking on the diff hunk and
choosing "Stage Hunk For Commit").
@@ -1118,7 +1131,7 @@ foo.txt
*.[oa]
-------------------------------------------------
-See gitlink:gitignore[5] for a detailed explanation of the syntax. You can
+See linkgit:gitignore[5] for a detailed explanation of the syntax. You can
also place .gitignore files in other directories in your working tree, and they
will apply to those directories and their subdirectories. The `.gitignore`
files can be added to your repository like any other files (just run `git add
@@ -1131,14 +1144,14 @@ If you wish the exclude patterns to affect only certain repositories
them in a file in your repository named .git/info/exclude, or in any file
specified by the `core.excludesfile` configuration variable. Some git
commands can also take exclude patterns directly on the command line.
-See gitlink:gitignore[5] for the details.
+See linkgit:gitignore[5] for the details.
[[how-to-merge]]
How to merge
------------
You can rejoin two diverging branches of development using
-gitlink:git-merge[1]:
+linkgit:git-merge[1]:
-------------------------------------------------
$ git merge branchname
@@ -1175,7 +1188,7 @@ the working tree in a special state that gives you all the
information you need to help resolve the merge.
Files with conflicts are marked specially in the index, so until you
-resolve the problem and update the index, gitlink:git-commit[1] will
+resolve the problem and update the index, linkgit:git-commit[1] will
fail:
-------------------------------------------------
@@ -1183,7 +1196,7 @@ $ git commit
file.txt: needs merge
-------------------------------------------------
-Also, gitlink:git-status[1] will list those files as "unmerged", and the
+Also, linkgit:git-status[1] will list those files as "unmerged", and the
files with conflicts will have conflict markers added, like this:
-------------------------------------------------
@@ -1214,7 +1227,7 @@ Getting conflict-resolution help during a merge
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
All of the changes that git was able to merge automatically are
-already added to the index file, so gitlink:git-diff[1] shows only
+already added to the index file, so linkgit:git-diff[1] shows only
the conflicts. It uses an unusual syntax:
-------------------------------------------------
@@ -1249,7 +1262,7 @@ $ git show :3:file.txt # the version from MERGE_HEAD, but including any
Since the stage 2 and stage 3 versions have already been updated with
nonconflicting changes, the only remaining differences between them are
-the important ones; thus gitlink:git-diff[1] can use the information in
+the important ones; thus linkgit:git-diff[1] can use the information in
the index to show only those conflicts.
The diff above shows the differences between the working-tree version of
@@ -1258,7 +1271,7 @@ each line by a single "+" or "-", it now uses two columns: the first
column is used for differences between the first parent and the working
directory copy, and the second for differences between the second parent
and the working directory copy. (See the "COMBINED DIFF FORMAT" section
-of gitlink:git-diff-files[1] for a details of the format.)
+of linkgit:git-diff-files[1] for a details of the format.)
After resolving the conflict in the obvious way (but before updating the
index), the diff will look like:
@@ -1291,7 +1304,7 @@ $ git diff -3 file.txt # diff against stage 3
$ git diff --theirs file.txt # same as the above.
-------------------------------------------------
-The gitlink:git-log[1] and gitk[1] commands also provide special help
+The linkgit:git-log[1] and gitk[1] commands also provide special help
for merges:
-------------------------------------------------
@@ -1302,7 +1315,7 @@ $ gitk --merge
These will display all commits which exist only on HEAD or on
MERGE_HEAD, and which touch an unmerged file.
-You may also use gitlink:git-mergetool[1], which lets you merge the
+You may also use linkgit:git-mergetool[1], which lets you merge the
unmerged files using external tools such as emacs or kdiff3.
Each time you resolve the conflicts in a file and update the index:
@@ -1367,7 +1380,7 @@ If you make a commit that you later wish you hadn't, there are two
fundamentally different ways to fix the problem:
1. You can create a new commit that undoes whatever was done
- by the previous commit. This is the correct thing if your
+ by the old commit. This is the correct thing if your
mistake has already been made public.
2. You can go back and modify the old commit. You should
@@ -1381,7 +1394,7 @@ Fixing a mistake with a new commit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Creating a new commit that reverts an earlier change is very easy;
-just pass the gitlink:git-revert[1] command a reference to the bad
+just pass the linkgit:git-revert[1] command a reference to the bad
commit; for example, to revert the most recent commit:
-------------------------------------------------
@@ -1403,8 +1416,8 @@ with the changes to be reverted, then you will be asked to fix
conflicts manually, just as in the case of <<resolving-a-merge,
resolving a merge>>.
-[[fixing-a-mistake-by-editing-history]]
-Fixing a mistake by editing history
+[[fixing-a-mistake-by-rewriting-history]]
+Fixing a mistake by rewriting history
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the problematic commit is the most recent commit, and you have not
@@ -1424,10 +1437,10 @@ which will replace the old commit by a new commit incorporating your
changes, giving you a chance to edit the old commit message first.
Again, you should never do this to a commit that may already have
-been merged into another branch; use gitlink:git-revert[1] instead in
+been merged into another branch; use linkgit:git-revert[1] instead in
that case.
-It is also possible to edit commits further back in the history, but
+It is also possible to replace commits further back in the history, but
this is an advanced topic to be left for
<<cleaning-up-history,another chapter>>.
@@ -1437,7 +1450,7 @@ Checking out an old version of a file
In the process of undoing a previous bad change, you may find it
useful to check out an older version of a particular file using
-gitlink:git-checkout[1]. We've used git checkout before to switch
+linkgit:git-checkout[1]. We've used git checkout before to switch
branches, but it has quite different behavior if it is given a path
name: the command
@@ -1450,7 +1463,7 @@ also updates the index to match. It does not change branches.
If you just want to look at an old version of the file, without
modifying the working directory, you can do that with
-gitlink:git-show[1]:
+linkgit:git-show[1]:
-------------------------------------------------
$ git show HEAD^:path/to/file
@@ -1464,7 +1477,7 @@ Temporarily setting aside work in progress
While you are in the middle of working on something complicated, you
find an unrelated but obvious and trivial bug. You would like to fix it
-before continuing. You can use gitlink:git-stash[1] to save the current
+before continuing. You can use linkgit:git-stash[1] to save the current
state of your work, and after fixing the bug (or, optionally after doing
so on a different branch and then coming back), unstash the
work-in-progress changes.
@@ -1498,7 +1511,7 @@ On large repositories, git depends on compression to keep the history
information from taking up too much space on disk or in memory.
This compression is not performed automatically. Therefore you
-should occasionally run gitlink:git-gc[1]:
+should occasionally run linkgit:git-gc[1]:
-------------------------------------------------
$ git gc
@@ -1516,7 +1529,7 @@ Ensuring reliability
Checking the repository for corruption
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The gitlink:git-fsck[1] command runs a number of self-consistency checks
+The linkgit:git-fsck[1] command runs a number of self-consistency checks
on the repository, and reports on any problems. This may take some
time. The most common warning by far is about "dangling" objects:
@@ -1536,8 +1549,8 @@ dangling tree b24c2473f1fd3d91352a624795be026d64c8841f
Dangling objects are not a problem. At worst they may take up a little
extra disk space. They can sometimes provide a last-resort method for
recovering lost work--see <<dangling-objects>> for details. However, if
-you wish, you can remove them with gitlink:git-prune[1] or the `--prune`
-option to gitlink:git-gc[1]:
+you wish, you can remove them with linkgit:git-prune[1] or the `--prune`
+option to linkgit:git-gc[1]:
-------------------------------------------------
$ git gc --prune
@@ -1547,6 +1560,11 @@ This may be time-consuming. Unlike most other git operations (including
git-gc when run without any options), it is not safe to prune while
other git operations are in progress in the same repository.
+If linkgit:git-fsck[1] complains about sha1 mismatches or missing
+objects, you may have a much more serious problem; your best option is
+probably restoring from backups. See
+<<recovering-from-repository-corruption>> for a detailed discussion.
+
[[recovering-lost-changes]]
Recovering lost changes
~~~~~~~~~~~~~~~~~~~~~~~
@@ -1555,7 +1573,7 @@ Recovering lost changes
Reflogs
^^^^^^^
-Say you modify a branch with `gitlink:git-reset[1] --hard`, and then
+Say you modify a branch with `linkgit:git-reset[1] --hard`, and then
realize that the branch was the only reference you had to that point in
history.
@@ -1567,9 +1585,9 @@ old history using, for example,
$ git log master@{1}
-------------------------------------------------
-This lists the commits reachable from the previous version of the head.
-This syntax can be used to with any git command that accepts a commit,
-not just with git log. Some other examples:
+This lists the commits reachable from the previous version of the
+"master" branch head. This syntax can be used with any git command
+that accepts a commit, not just with git log. Some other examples:
-------------------------------------------------
$ git show master@{2} # See where the branch pointed 2,
@@ -1590,9 +1608,9 @@ pointed to one week ago. This allows you to see the history of what
you've checked out.
The reflogs are kept by default for 30 days, after which they may be
-pruned. See gitlink:git-reflog[1] and gitlink:git-gc[1] to learn
+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 gitlink:git-rev-parse[1] for details.
+section of linkgit:git-rev-parse[1] 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
@@ -1657,7 +1675,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 gitlink: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:
@@ -1666,7 +1684,7 @@ $ git fetch
$ git merge origin/master
-------------------------------------------------
-However, the gitlink:git-pull[1] command provides a way to do this in
+However, the linkgit:git-pull[1] command provides a way to do this in
one step:
-------------------------------------------------
@@ -1684,8 +1702,8 @@ $ git pull
More generally, a branch that is created from a remote branch will pull
by default from that branch. See the descriptions of the
branch.<name>.remote and branch.<name>.merge options in
-gitlink:git-config[1], and the discussion of the `--track` option in
-gitlink:git-checkout[1], to learn how to control these defaults.
+linkgit:git-config[1], and the discussion of the `--track` option in
+linkgit:git-checkout[1], to learn how to control these defaults.
In addition to saving you keystrokes, "git pull" also helps you by
producing a default commit message documenting the branch and
@@ -1713,7 +1731,7 @@ Submitting patches to a project
If you just have a few changes, the simplest way to submit them may
just be to send them as patches in email:
-First, use gitlink:git-format-patch[1]; for example:
+First, use linkgit:git-format-patch[1]; for example:
-------------------------------------------------
$ git format-patch origin
@@ -1724,7 +1742,7 @@ for each patch in the current branch but not in origin/HEAD.
You can then import these into your mail client and send them by
hand. However, if you have a lot to send at once, you may prefer to
-use the gitlink:git-send-email[1] script to automate the process.
+use the linkgit:git-send-email[1] script to automate the process.
Consult the mailing list for your project first to determine how they
prefer such patches be handled.
@@ -1732,7 +1750,7 @@ prefer such patches be handled.
Importing patches to a project
------------------------------
-Git also provides a tool called gitlink:git-am[1] (am stands for
+Git also provides a tool called linkgit:git-am[1] (am stands for
"apply mailbox"), for importing such an emailed series of patches.
Just save all of the patch-containing messages, in order, into a
single mailbox file, say "patches.mbox", then run
@@ -1767,7 +1785,7 @@ Public git repositories
Another way to submit changes to a project is to tell the maintainer
of that project to pull the changes from your repository using
-gitlink:git-pull[1]. In the section "<<getting-updates-with-git-pull,
+linkgit:git-pull[1]. In the section "<<getting-updates-with-git-pull,
Getting updates with git pull>>" we described this as a way to get
updates from the "main" repository, but it works just as well in the
other direction.
@@ -1848,14 +1866,14 @@ at. You can then skip to the section
"<<pushing-changes-to-a-public-repository,Pushing changes to a public
repository>>", below.
-Otherwise, all you need to do is start gitlink:git-daemon[1]; it will
+Otherwise, all you need to do is start linkgit:git-daemon[1]; it will
listen on port 9418. By default, it will allow access to any directory
that looks like a git directory and contains the magic file
git-daemon-export-ok. Passing some directory paths as git-daemon
arguments will further restrict the exports to those paths.
You can also run git-daemon as an inetd service; see the
-gitlink:git-daemon[1] man page for details. (See especially the
+linkgit:git-daemon[1] man page for details. (See especially the
examples section.)
[[exporting-via-http]]
@@ -1877,7 +1895,7 @@ $ chmod a+x hooks/post-update
-------------------------------------------------
(For an explanation of the last two lines, see
-gitlink:git-update-server-info[1], and the documentation
+linkgit:git-update-server-info[1], and the documentation
link:hooks.html[Hooks used by git].)
Advertise the URL of proj.git. Anybody else should then be able to
@@ -1902,7 +1920,7 @@ maintainers to fetch your latest changes, but they do not allow write
access, which you will need to update the public repository with the
latest changes created in your private repository.
-The simplest way to do this is using gitlink:git-push[1] and ssh; to
+The simplest way to do this is using linkgit:git-push[1] and ssh; to
update the remote branch named "master" with the latest state of your
branch named "master", run
@@ -1916,15 +1934,9 @@ or just
$ git push ssh://yourserver.com/~you/proj.git master
-------------------------------------------------
-As with git-fetch, git-push will complain if this does not result in
-a <<fast-forwards,fast forward>>. Normally this is a sign of
-something wrong. However, if you are sure you know what you're
-doing, you may force git-push to perform the update anyway by
-preceding the branch name by a plus sign:
-
--------------------------------------------------
-$ git push ssh://yourserver.com/~you/proj.git +master
--------------------------------------------------
+As with git-fetch, git-push will complain if this does not result in a
+<<fast-forwards,fast forward>>; see the following section for details on
+handling this case.
Note that the target of a "push" is normally a
<<def_bare_repository,bare>> repository. You can also push to a
@@ -1949,9 +1961,55 @@ $ git push public-repo master
-------------------------------------------------
See the explanations of the remote.<name>.url, branch.<name>.remote,
-and remote.<name>.push options in gitlink:git-config[1] for
+and remote.<name>.push options in linkgit:git-config[1] for
details.
+[[forcing-push]]
+What to do when a push fails
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If a push would not result in a <<fast-forwards,fast forward>> of the
+remote branch, then it will fail with an error like:
+
+-------------------------------------------------
+error: remote 'refs/heads/master' is not an ancestor of
+ local 'refs/heads/master'.
+ Maybe you are not up-to-date and need to pull first?
+error: failed to push to 'ssh://yourserver.com/~you/proj.git'
+-------------------------------------------------
+
+This can happen, for example, if you:
+
+ - use `git reset --hard` to remove already-published commits, or
+ - use `git commit --amend` to replace already-published commits
+ (as in <<fixing-a-mistake-by-rewriting-history>>), or
+ - use `git rebase` to rebase any already-published commits (as
+ in <<using-git-rebase>>).
+
+You may force git-push to perform the update anyway by preceding the
+branch name with a plus sign:
+
+-------------------------------------------------
+$ git push ssh://yourserver.com/~you/proj.git +master
+-------------------------------------------------
+
+Normally whenever a branch head in a public repository is modified, it
+is modified to point to a descendant of the commit that it pointed to
+before. By forcing a push in this situation, you break that convention.
+(See <<problems-with-rewriting-history>>.)
+
+Nevertheless, this is a common practice for people that need a simple
+way to publish a work-in-progress patch series, and it is an acceptable
+compromise as long as you warn other developers that this is how you
+intend to manage the branch.
+
+It's also possible for a push to fail in this way when other people have
+the right to push to the same repository. In that case, the correct
+solution is to retry the push after first updating your work by either a
+pull or a fetch followed by a rebase; see the
+<<setting-up-a-shared-repository,next section>> and
+link:cvs-migration.html[git for CVS users] for more.
+
[[setting-up-a-shared-repository]]
Setting up a shared repository
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2025,14 +2083,14 @@ $ cd work
-------------------------------------------------
Linus's tree will be stored in the remote branch named origin/master,
-and can be updated using gitlink:git-fetch[1]; you can track other
-public trees using gitlink:git-remote[1] to set up a "remote" and
-gitlink:git-fetch[1] to keep them up-to-date; see
+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
<<repositories-and-branches>>.
Now create the branches in which you are going to work; these start out
at the current tip of origin/master branch, and should be set up (using
-the --track option to gitlink:git-branch[1]) to merge changes in from
+the --track option to linkgit:git-branch[1]) to merge changes in from
Linus by default.
-------------------------------------------------
@@ -2040,7 +2098,7 @@ $ git branch --track test origin/master
$ git branch --track release origin/master
-------------------------------------------------
-These can be easily kept up to date using gitlink:git-pull[1].
+These can be easily kept up to date using linkgit:git-pull[1].
-------------------------------------------------
$ git checkout test && git pull
@@ -2055,7 +2113,7 @@ doing this capriciously in the "release" branch, as these noisy commits
will become part of the permanent history when you ask Linus to pull
from the release branch.
-A few configuration variables (see gitlink:git-config[1]) can
+A few configuration variables (see linkgit:git-config[1]) can
make it easy to push both branches to your public tree. (See
<<setting-up-a-public-repository>>.)
@@ -2069,7 +2127,7 @@ EOF
-------------------------------------------------
Then you can push both the test and release trees using
-gitlink:git-push[1]:
+linkgit:git-push[1]:
-------------------------------------------------
$ git push mytree
@@ -2382,7 +2440,7 @@ the result would create a new merge commit, like this:
However, if you prefer to keep the history in mywork a simple series of
commits without any merges, you may instead choose to use
-gitlink:git-rebase[1]:
+linkgit:git-rebase[1]:
-------------------------------------------------
$ git checkout mywork
@@ -2419,11 +2477,11 @@ return mywork to the state it had before you started the rebase:
$ git rebase --abort
-------------------------------------------------
-[[modifying-one-commit]]
-Modifying a single commit
+[[rewriting-one-commit]]
+Rewriting a single commit
-------------------------
-We saw in <<fixing-a-mistake-by-editing-history>> that you can replace the
+We saw in <<fixing-a-mistake-by-rewriting-history>> that you can replace the
most recent commit using
-------------------------------------------------
@@ -2433,8 +2491,10 @@ $ git commit --amend
which will replace the old commit by a new commit incorporating your
changes, giving you a chance to edit the old commit message first.
-You can also use a combination of this and gitlink:git-rebase[1] to edit
-commits further back in your history. First, tag the problematic commit with
+You can also use a combination of this and linkgit:git-rebase[1] to
+replace a commit further back in your history and recreate the
+intervening changes on top of it. First, tag the problematic commit
+with
-------------------------------------------------
$ git tag bad mywork~5
@@ -2469,7 +2529,7 @@ new commits having new object names.
Reordering or selecting from a patch series
-------------------------------------------
-Given one existing commit, the gitlink:git-cherry-pick[1] command
+Given one existing commit, the linkgit:git-cherry-pick[1] command
allows you to apply the change introduced by that commit and create a
new commit that records it. So, for example, if "mywork" points to a
series of patches on top of "origin", you might do something like:
@@ -2482,7 +2542,7 @@ $ gitk origin..mywork &
and browse through the list of patches in the mywork branch using gitk,
applying them (possibly in a different order) to mywork-new using
cherry-pick, and possibly modifying them as you go using `commit --amend`.
-The gitlink:git-gui[1] command may also help as it allows you to
+The linkgit:git-gui[1] command may also help as it allows you to
individually select diff hunks for inclusion in the index (by
right-clicking on the diff hunk and choosing "Stage Hunk for Commit").
@@ -2495,7 +2555,7 @@ $ git reset --hard origin
-------------------------------------------------
Then modify, reorder, or eliminate patches as preferred before applying
-them again with gitlink:git-am[1].
+them again with linkgit:git-am[1].
[[patch-series-tools]]
Other tools
@@ -2554,6 +2614,72 @@ branches into their own work.
For true distributed development that supports proper merging,
published branches should never be rewritten.
+[[bisect-merges]]
+Why bisecting merge commits can be harder than bisecting linear history
+-----------------------------------------------------------------------
+
+The linkgit:git-bisect[1] command correctly handles history that
+includes merge commits. However, when the commit that it finds is a
+merge commit, the user may need to work harder than usual to figure out
+why that commit introduced a problem.
+
+Imagine this history:
+
+................................................
+ ---Z---o---X---...---o---A---C---D
+ \ /
+ o---o---Y---...---o---B
+................................................
+
+Suppose that on the upper line of development, the meaning of one
+of the functions that exists at Z is changed at commit X. The
+commits from Z leading to A change both the function's
+implementation and all calling sites that exist at Z, as well
+as new calling sites they add, to be consistent. There is no
+bug at A.
+
+Suppose that in the meantime on the lower line of development somebody
+adds a new calling site for that function at commit Y. The
+commits from Z leading to B all assume the old semantics of that
+function and the callers and the callee are consistent with each
+other. There is no bug at B, either.
+
+Suppose further that the two development lines merge cleanly at C,
+so no conflict resolution is required.
+
+Nevertheless, the code at C is broken, because the callers added
+on the lower line of development have not been converted to the new
+semantics introduced on the upper line of development. So if all
+you know is that D is bad, that Z is good, and that
+linkgit:git-bisect[1] identifies C as the culprit, how will you
+figure out that the problem is due to this change in semantics?
+
+When the result of a git-bisect is a non-merge commit, you should
+normally be able to discover the problem by examining just that commit.
+Developers can make this easy by breaking their changes into small
+self-contained commits. That won't help in the case above, however,
+because the problem isn't obvious from examination of any single
+commit; instead, a global view of the development is required. To
+make matters worse, the change in semantics in the problematic
+function may be just one small part of the changes in the upper
+line of development.
+
+On the other hand, if instead of merging at C you had rebased the
+history between Z to B on top of A, you would have gotten this
+linear history:
+
+................................................................
+ ---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*
+................................................................
+
+Bisecting between Z and D* would hit a single culprit commit Y*,
+and understanding why Y* was broken would probably be easier.
+
+Partly for this reason, many experienced git users, even when
+working on an otherwise merge-heavy project, keep the history
+linear by rebasing against the latest upstream version before
+publishing.
+
[[advanced-branch-management]]
Advanced branch management
==========================
@@ -2562,7 +2688,7 @@ Advanced branch management
Fetching individual branches
----------------------------
-Instead of using gitlink:git-remote[1], you can also choose just
+Instead of using linkgit:git-remote[1], you can also choose just
to update one branch at a time, and to store it locally under an
arbitrary name:
@@ -2653,7 +2779,7 @@ Configuring remote branches
We saw above that "origin" is just a shortcut to refer to the
repository that you originally cloned from. This information is
stored in git configuration variables, which you can see using
-gitlink:git-config[1]:
+linkgit:git-config[1]:
-------------------------------------------------
$ git config -l
@@ -2706,9 +2832,9 @@ throwing away commits on mybranch.
Also note that all of the above configuration can be performed by
directly editing the file .git/config instead of using
-gitlink:git-config[1].
+linkgit:git-config[1].
-See gitlink:git-config[1] for more details on the configuration
+See linkgit:git-config[1] for more details on the configuration
options mentioned above.
@@ -2774,7 +2900,7 @@ Commit Object
The "commit" object links a physical state of a tree with a description
of how we got there and why. Use the --pretty=raw option to
-gitlink:git-show[1] or gitlink:git-log[1] to examine your favorite
+linkgit:git-show[1] or linkgit:git-log[1] to examine your favorite
commit:
------------------------------------------------
@@ -2795,7 +2921,7 @@ As you can see, a commit is defined by:
- a tree: The SHA1 name of a tree object (as defined below), representing
the contents of a directory at a certain point in time.
- parent(s): The SHA1 name of some number of commits which represent the
- immediately prevoius step(s) in the history of the project. The
+ immediately previous step(s) in the history of the project. The
example above has one parent; merge commits may have more than
one. A commit with no parents is called a "root" commit, and
represents the initial revision of a project. Each project must have
@@ -2815,9 +2941,9 @@ of the tree referred to by this commit with the trees associated with
its parents. In particular, git does not attempt to record file renames
explicitly, though it can identify cases where the existence of the same
file data at changing paths suggests a rename. (See, for example, the
--M option to gitlink:git-diff[1]).
+-M option to linkgit:git-diff[1]).
-A commit is usually created by gitlink:git-commit[1], which creates a
+A commit is usually created by linkgit:git-commit[1], which creates a
commit whose parent is normally the current HEAD, and whose tree is
taken from the content currently stored in the index.
@@ -2825,8 +2951,8 @@ taken from the content currently stored in the index.
Tree Object
~~~~~~~~~~~
-The ever-versatile gitlink:git-show[1] command can also be used to
-examine tree objects, but gitlink:git-ls-tree[1] will give you more
+The ever-versatile linkgit:git-show[1] command can also be used to
+examine tree objects, but linkgit:git-ls-tree[1] will give you more
details:
------------------------------------------------
@@ -2865,7 +2991,7 @@ attention to the executable bit.
Blob Object
~~~~~~~~~~~
-You can use gitlink:git-show[1] to examine the contents of a blob; take,
+You can use linkgit:git-show[1] to examine the contents of a blob; take,
for example, the blob in the entry for "COPYING" from the tree above:
------------------------------------------------
@@ -2887,7 +3013,7 @@ is totally independent of its location in the directory tree, and
renaming a file does not change the object that file is associated with.
Note that any tree or blob object can be examined using
-gitlink:git-show[1] with the <revision>:<path> syntax. This can
+linkgit:git-show[1] with the <revision>:<path> syntax. This can
sometimes be useful for browsing the contents of a tree that is not
currently checked out.
@@ -2927,7 +3053,7 @@ Tag Object
A tag object contains an object, object type, tag name, the name of the
person ("tagger") who created the tag, and a message, which may contain
-a signature, as can be seen using the gitlink:git-cat-file[1]:
+a signature, as can be seen using the linkgit:git-cat-file[1]:
------------------------------------------------
$ git cat-file tag v1.5.0
@@ -2946,8 +3072,8 @@ nLE/L9aUXdWeTFPron96DLA=
-----END PGP SIGNATURE-----
------------------------------------------------
-See the gitlink:git-tag[1] command to learn how to create and verify tag
-objects. (Note that gitlink:git-tag[1] can also be used to create
+See the linkgit:git-tag[1] command to learn how to create and verify tag
+objects. (Note that linkgit:git-tag[1] can also be used to create
"lightweight tags", which are not tag objects at all, but just simple
references whose names begin with "refs/tags/").
@@ -3009,14 +3135,14 @@ $ git count-objects
Although the object files are gone, any commands that refer to those
objects will work exactly as they did before.
-The gitlink:git-gc[1] command performs packing, pruning, and more for
+The linkgit:git-gc[1] command performs packing, pruning, and more for
you, so is normally the only high-level command you need.
[[dangling-objects]]
Dangling objects
~~~~~~~~~~~~~~~~
-The gitlink:git-fsck[1] command will sometimes complain about dangling
+The linkgit:git-fsck[1] command will sometimes complain about dangling
objects. They are not a problem.
The most common cause of dangling objects is that you've rebased a
@@ -3099,13 +3225,134 @@ confusing and scary messages, but it won't actually do anything bad. In
contrast, running "git prune" while somebody is actively changing the
repository is a *BAD* idea).
+[[recovering-from-repository-corruption]]
+Recovering from repository corruption
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By design, git treats data trusted to it with caution. However, even in
+the absence of bugs in git itself, it is still possible that hardware or
+operating system errors could corrupt data.
+
+The first defense against such problems is backups. You can back up a
+git directory using clone, or just using cp, tar, or any other backup
+mechanism.
+
+As a last resort, you can search for the corrupted objects and attempt
+to replace them by hand. Back up your repository before attempting this
+in case you corrupt things even more in the process.
+
+We'll assume that the problem is a single missing or corrupted blob,
+which is sometimes a solvable problem. (Recovering missing trees and
+especially commits is *much* harder).
+
+Before starting, verify that there is corruption, and figure out where
+it is with linkgit:git-fsck[1]; this may be time-consuming.
+
+Assume the output looks like this:
+
+------------------------------------------------
+$ git-fsck --full
+broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
+ to blob 4b9458b3786228369c63936db65827de3cc06200
+missing blob 4b9458b3786228369c63936db65827de3cc06200
+------------------------------------------------
+
+(Typically there will be some "dangling object" messages too, but they
+aren't interesting.)
+
+Now you know that blob 4b9458b3 is missing, and that the tree 2d9263c6
+points to it. If you could find just one copy of that missing blob
+object, possibly in some other repository, you could move it into
+.git/objects/4b/9458b3... and be done. Suppose you can't. You can
+still examine the tree that pointed to it with linkgit:git-ls-tree[1],
+which might output something like:
+
+------------------------------------------------
+$ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
+100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore
+100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap
+100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING
+...
+100644 blob 4b9458b3786228369c63936db65827de3cc06200 myfile
+...
+------------------------------------------------
+
+So now you know that the missing blob was the data for a file named
+"myfile". And chances are you can also identify the directory--let's
+say it's in "somedirectory". If you're lucky the missing copy might be
+the same as the copy you have checked out in your working tree at
+"somedirectory/myfile"; you can test whether that's right with
+linkgit:git-hash-object[1]:
+
+------------------------------------------------
+$ git hash-object -w somedirectory/myfile
+------------------------------------------------
+
+which will create and store a blob object with the contents of
+somedirectory/myfile, and output the sha1 of that object. if you're
+extremely lucky it might be 4b9458b3786228369c63936db65827de3cc06200, in
+which case you've guessed right, and the corruption is fixed!
+
+Otherwise, you need more information. How do you tell which version of
+the file has been lost?
+
+The easiest way to do this is with:
+
+------------------------------------------------
+$ git log --raw --all --full-history -- somedirectory/myfile
+------------------------------------------------
+
+Because you're asking for raw output, you'll now get something like
+
+------------------------------------------------
+commit abc
+Author:
+Date:
+...
+:100644 100644 4b9458b... newsha... M somedirectory/myfile
+
+
+commit xyz
+Author:
+Date:
+
+...
+:100644 100644 oldsha... 4b9458b... M somedirectory/myfile
+------------------------------------------------
+
+This tells you that the immediately preceding version of the file was
+"newsha", and that the immediately following version was "oldsha".
+You also know the commit messages that went with the change from oldsha
+to 4b9458b and with the change from 4b9458b to newsha.
+
+If you've been committing small enough changes, you may now have a good
+shot at reconstructing the contents of the in-between state 4b9458b.
+
+If you can do that, you can now recreate the missing object with
+
+------------------------------------------------
+$ git hash-object -w <recreated-file>
+------------------------------------------------
+
+and your repository is good again!
+
+(Btw, you could have ignored the fsck, and started with doing a
+
+------------------------------------------------
+$ git log --raw --all
+------------------------------------------------
+
+and just looked for the sha of the missing object (4b9458b..) in that
+whole thing. It's up to you - git does *have* a lot of information, it is
+just missing one particular blob version.
+
[[the-index]]
The index
-----------
The index is a binary file (generally kept in .git/index) containing a
sorted list of path names, each with permissions and the SHA1 of a blob
-object; gitlink:git-ls-files[1] can show you the contents of the index:
+object; linkgit:git-ls-files[1] can show you the contents of the index:
-------------------------------------------------
$ git ls-files --stage
@@ -3127,7 +3374,7 @@ properties:
1. The index contains all the information necessary to generate a single
(uniquely determined) tree object.
+
-For example, running gitlink:git-commit[1] generates this tree object
+For example, running linkgit:git-commit[1] generates this tree object
from the index, stores it in the object database, and uses it as the
tree object associated with the new commit.
@@ -3148,7 +3395,7 @@ you can create a three-way merge between them.
+
We saw in <<conflict-resolution>> that during a merge the index can
store multiple versions of a single file (called "stages"). The third
-column in the gitlink:git-ls-files[1] output above is the stage
+column in the linkgit:git-ls-files[1] output above is the stage
number, and will take on values other than 0 for files with merge
conflicts.
@@ -3197,7 +3444,7 @@ commit ID, so other developers who clone the containing project
Partial checkouts of the superproject are possible: you can tell Git to
clone none, some or all of the submodules.
-The gitlink:git-submodule[1] command is available since Git 1.5.3. Users
+The linkgit:git-submodule[1] command is available since Git 1.5.3. Users
with Git 1.5.2 can look up the submodule commits in the repository and
manually check them out; earlier versions won't recognize the submodules at
all.
@@ -3245,7 +3492,7 @@ The `git submodule add` command does a couple of things:
- It clones the submodule under the current directory and by default checks out
the master branch.
-- It adds the submodule's clone path to the gitlink:gitmodules[5] file and
+- It adds the submodule's clone path to the linkgit:gitmodules[5] file and
adds this file to the index, ready to be committed.
- It adds the submodule's current commit ID to the index, ready to be
committed.
@@ -3406,26 +3653,26 @@ understand its inner workings.
Object access and manipulation
------------------------------
-The gitlink:git-cat-file[1] command can show the contents of any object,
-though the higher-level gitlink:git-show[1] is usually more useful.
+The linkgit:git-cat-file[1] command can show the contents of any object,
+though the higher-level linkgit:git-show[1] is usually more useful.
-The gitlink:git-commit-tree[1] command allows constructing commits with
+The linkgit:git-commit-tree[1] command allows constructing commits with
arbitrary parents and trees.
-A tree can be created with gitlink:git-write-tree[1] and its data can be
-accessed by gitlink:git-ls-tree[1]. Two trees can be compared with
-gitlink:git-diff-tree[1].
+A tree can be created with linkgit:git-write-tree[1] and its data can be
+accessed by linkgit:git-ls-tree[1]. Two trees can be compared with
+linkgit:git-diff-tree[1].
-A tag is created with gitlink:git-mktag[1], and the signature can be
-verified by gitlink:git-verify-tag[1], though it is normally simpler to
-use gitlink:git-tag[1] for both.
+A tag is created with linkgit:git-mktag[1], and the signature can be
+verified by linkgit:git-verify-tag[1], though it is normally simpler to
+use linkgit:git-tag[1] for both.
[[the-workflow]]
The Workflow
------------
-High-level operations such as gitlink:git-commit[1],
-gitlink:git-checkout[1] and gitlink:git-reset[1] work by moving data
+High-level operations such as linkgit:git-commit[1],
+linkgit:git-checkout[1] and linkgit:git-reset[1] work by moving data
between the working tree, the index, and the object database. Git
provides low-level operations which perform each of these steps
individually.
@@ -3440,7 +3687,7 @@ combinations:
working directory -> index
~~~~~~~~~~~~~~~~~~~~~~~~~~
-The gitlink:git-update-index[1] command updates the index with
+The linkgit:git-update-index[1] command updates the index with
information from the working directory. You generally update the
index information by just specifying the filename you want to update,
like so:
@@ -3460,7 +3707,7 @@ should use the `--remove` and `--add` flags respectively.
NOTE! A `--remove` flag does 'not' mean that subsequent filenames will
necessarily be removed: if the files still exist in your directory
structure, the index will be updated with their new status, not
-removed. The only thing `--remove` means is that update-cache will be
+removed. The only thing `--remove` means is that update-index will be
considering a removed file to be a valid thing, and if the file really
does not exist any more, it will update the index accordingly.
@@ -3470,8 +3717,8 @@ stat information. It will 'not' update the object status itself, and
it will only update the fields that are used to quickly test whether
an object still matches its old backing store object.
-The previously introduced gitlink:git-add[1] is just a wrapper for
-gitlink:git-update-index[1].
+The previously introduced linkgit:git-add[1] is just a wrapper for
+linkgit:git-update-index[1].
[[index-to-object-database]]
index -> object database
@@ -3618,7 +3865,7 @@ Examining the data
You can examine the data represented in the object database and the
index with various helper tools. For every object, you can use
-gitlink:git-cat-file[1] to examine details about the
+linkgit:git-cat-file[1] to examine details about the
object:
-------------------------------------------------
@@ -4281,7 +4528,7 @@ Scan Documentation/ for other stuff left out; in particular:
- howto's
- some of technical/?
- hooks
-- list of commands in gitlink:git[1]
+- list of commands in linkgit:git[1]
Scan email archives for other stuff left out
@@ -4309,4 +4556,7 @@ Write a chapter on using plumbing and writing scripts.
Alternates, clone -reference, etc.
-git unpack-objects -r for recovery
+More on recovery from repository corruption. See:
+ http://marc.theaimsgroup.com/?l=git&m=117263864820799&w=2
+ http://marc.theaimsgroup.com/?l=git&m=117147855503798&w=2
+ http://marc.theaimsgroup.com/?l=git&m=117147855503798&w=2
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 3c0032cec5..1ad324e236 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.5.3.GIT
+DEF_VER=v1.5.4.GIT
LF='
'
@@ -15,7 +15,8 @@ elif test -d .git &&
VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
case "$VN" in
*$LF*) (exit 1) ;;
- v[0-9]*) : happy ;;
+ v[0-9]*)
+ git diff-index --quiet HEAD || VN="$VN-dirty" ;;
esac
then
VN=$(echo "$VN" | sed -e 's/-/./g');
@@ -25,14 +26,6 @@ fi
VN=$(expr "$VN" : v*'\(.*\)')
-dirty=$(sh -c 'git diff-index --name-only HEAD' 2>/dev/null) || dirty=
-case "$dirty" in
-'')
- ;;
-*)
- VN="$VN-dirty" ;;
-esac
-
if test -r $GVF
then
VC=$(sed -e 's/^GIT_VERSION = //' <$GVF)
diff --git a/INSTALL b/INSTALL
index f1eb4049b9..6f3bcb4525 100644
--- a/INSTALL
+++ b/INSTALL
@@ -79,8 +79,8 @@ Issues of note:
- "perl" and POSIX-compliant shells are needed to use most of
the barebone Porcelainish scripts.
- - "cpio" is used by git-merge for saving and restoring the index,
- and by git-clone when doing a local (possibly hardlinked) clone.
+ - "cpio" is used by git-clone when doing a local (possibly
+ hardlinked) clone.
- Some platform specific issues are dealt with Makefile rules,
but depending on your specific installation, you may not
diff --git a/Makefile b/Makefile
index 9f81c73af7..d33a556ffe 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,9 @@ all::
# Define V=1 to have a more verbose compile.
#
+# Define FREAD_READS_DIRECTORIES if your are on a system which succeeds
+# when attempting to read from an fopen'ed directory.
+#
# Define NO_OPENSSL environment variable if you do not have OpenSSL.
# This also implies MOZILLA_SHA1.
#
@@ -38,6 +41,12 @@ all::
#
# Define NO_SETENV if you don't have setenv in the C library.
#
+# Define NO_UNSETENV if you don't have unsetenv in the C library.
+#
+# Define NO_MKDTEMP if you don't have mkdtemp in the C library.
+#
+# Define NO_SYS_SELECT_H if you don't have sys/select.h.
+#
# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
# Enable it on Windows. By default, symrefs are still used.
#
@@ -96,6 +105,8 @@ all::
# Define OLD_ICONV if your library has an old iconv(), where the second
# (input buffer pointer) parameter is declared with type (const char **).
#
+# Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.
+#
# Define NO_R_TO_GCC_LINKER if your gcc does not like "-R/path/lib"
# that tells runtime paths to dynamic libraries;
# "-Wl,-rpath=/path/lib" is used instead.
@@ -107,10 +118,12 @@ all::
# times (my ext3 doesn't).
#
# Define USE_STDEV below if you want git to care about the underlying device
-# change being considered an inode change from the update-cache perspective.
+# change being considered an inode change from the update-index perspective.
#
# Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
#
+# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72.
+#
# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
# MakeMaker (e.g. using ActiveState under Cygwin).
#
@@ -127,6 +140,10 @@ all::
# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit
# parallel delta searching when packing objects.
#
+# Define INTERNAL_QSORT to use Git's implementation of qsort(), which
+# is a simplified version of the merge sort used in glibc. This is
+# recommended if Git triggers O(n^2) behavior in your platform's qsort().
+#
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -148,9 +165,12 @@ STRIP ?= strip
prefix = $(HOME)
bindir = $(prefix)/bin
+mandir = $(prefix)/share/man
+infodir = $(prefix)/share/info
gitexecdir = $(bindir)
sharedir = $(prefix)/share
template_dir = $(sharedir)/git-core/templates
+htmldir=$(sharedir)/doc/git-doc
ifeq ($(prefix),/usr)
sysconfdir = /etc
else
@@ -177,7 +197,7 @@ GITWEB_FAVICON = git-favicon.png
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
-export prefix bindir gitexecdir sharedir template_dir sysconfdir
+export prefix bindir gitexecdir sharedir template_dir htmldir sysconfdir
CC = gcc
AR = ar
@@ -207,42 +227,40 @@ BASIC_LDFLAGS =
SCRIPT_SH = \
git-bisect.sh git-checkout.sh \
- git-clean.sh git-clone.sh git-commit.sh \
- git-fetch.sh \
- git-ls-remote.sh \
+ git-clone.sh \
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
git-pull.sh git-rebase.sh git-rebase--interactive.sh \
git-repack.sh git-request-pull.sh \
git-sh-setup.sh \
git-am.sh \
git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
- git-merge-resolve.sh git-merge-ours.sh \
+ git-merge-resolve.sh \
git-lost-found.sh git-quiltimport.sh git-submodule.sh \
git-filter-branch.sh \
- git-stash.sh
+ git-stash.sh \
+ git-web--browse.sh
SCRIPT_PERL = \
git-add--interactive.perl \
git-archimport.perl git-cvsimport.perl git-relink.perl \
- git-cvsserver.perl git-remote.perl \
- git-svnimport.perl git-cvsexportcommit.perl \
+ git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \
git-send-email.perl git-svn.perl
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
- git-status git-instaweb
+ git-instaweb
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS = \
git-fetch-pack$X \
- git-hash-object$X git-index-pack$X git-local-fetch$X \
+ git-hash-object$X git-index-pack$X \
git-fast-import$X \
git-daemon$X \
git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
- git-peek-remote$X git-receive-pack$X \
+ git-receive-pack$X \
git-send-pack$X git-shell$X \
- git-show-index$X git-ssh-fetch$X \
- git-ssh-upload$X git-unpack-file$X \
+ git-show-index$X \
+ git-unpack-file$X \
git-update-server-info$X \
git-upload-pack$X \
git-pack-redundant$X git-var$X \
@@ -256,7 +274,7 @@ EXTRA_PROGRAMS =
BUILT_INS = \
git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
git-get-tar-commit-id$X git-init$X git-repo-config$X \
- git-fsck-objects$X git-cherry-pick$X \
+ git-fsck-objects$X git-cherry-pick$X git-peek-remote$X git-status$X \
$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
# what 'all' will build and 'install' will install, in gitexecdir
@@ -266,12 +284,6 @@ ALL_PROGRAMS += git-merge-subtree$X
# what 'all' will build but not install in gitexecdir
OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
-ifndef NO_TCLTK
-OTHER_PROGRAMS += gitk-wish
-endif
-
-# Backward compatibility -- to be removed after 1.0
-PROGRAMS += git-ssh-pull$X git-ssh-push$X
# Set paths to tools early so that they can be used for version tests.
ifndef SHELL_PATH
@@ -292,7 +304,7 @@ LIB_H = \
run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
- mailmap.h remote.h
+ mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h
DIFF_OBJS = \
diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -302,7 +314,7 @@ DIFF_OBJS = \
LIB_OBJS = \
blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
date.o diff-delta.o entry.o exec_cmd.o ident.o \
- interpolate.o \
+ pretty.o interpolate.o hash.o \
lockfile.o \
patch-ids.o \
object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \
@@ -314,7 +326,8 @@ LIB_OBJS = \
write_or_die.o trace.o list-objects.o grep.o match-trees.o \
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
- convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o
+ convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
+ transport.o bundle.o walker.o parse-options.o ws.o archive.o
BUILTIN_OBJS = \
builtin-add.o \
@@ -328,6 +341,8 @@ BUILTIN_OBJS = \
builtin-check-attr.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
+ builtin-clean.o \
+ builtin-commit.o \
builtin-commit-tree.o \
builtin-count-objects.o \
builtin-describe.o \
@@ -335,6 +350,9 @@ BUILTIN_OBJS = \
builtin-diff-files.o \
builtin-diff-index.o \
builtin-diff-tree.o \
+ builtin-fast-export.o \
+ builtin-fetch.o \
+ builtin-fetch-pack.o \
builtin-fetch--tool.o \
builtin-fmt-merge-msg.o \
builtin-for-each-ref.o \
@@ -345,10 +363,12 @@ BUILTIN_OBJS = \
builtin-log.o \
builtin-ls-files.o \
builtin-ls-tree.o \
+ builtin-ls-remote.o \
builtin-mailinfo.o \
builtin-mailsplit.o \
builtin-merge-base.o \
builtin-merge-file.o \
+ builtin-merge-ours.o \
builtin-mv.o \
builtin-name-rev.o \
builtin-pack-objects.o \
@@ -357,6 +377,7 @@ BUILTIN_OBJS = \
builtin-push.o \
builtin-read-tree.o \
builtin-reflog.o \
+ builtin-send-pack.o \
builtin-config.o \
builtin-rerere.o \
builtin-reset.o \
@@ -364,7 +385,6 @@ BUILTIN_OBJS = \
builtin-rev-parse.o \
builtin-revert.o \
builtin-rm.o \
- builtin-runstatus.o \
builtin-shortlog.o \
builtin-show-branch.o \
builtin-stripspace.o \
@@ -401,7 +421,9 @@ endif
ifeq ($(uname_S),Darwin)
NEEDS_SSL_WITH_CRYPTO = YesPlease
NEEDS_LIBICONV = YesPlease
- OLD_ICONV = UnfortunatelyYes
+ ifneq ($(shell expr "$(uname_R)" : '9\.'),2)
+ OLD_ICONV = UnfortunatelyYes
+ endif
NO_STRLCPY = YesPlease
NO_MEMMEM = YesPlease
endif
@@ -412,6 +434,7 @@ ifeq ($(uname_S),SunOS)
NO_STRCASESTR = YesPlease
NO_MEMMEM = YesPlease
NO_HSTRERROR = YesPlease
+ NO_MKDTEMP = YesPlease
ifeq ($(uname_R),5.8)
NEEDS_LIBICONV = YesPlease
NO_UNSETENV = YesPlease
@@ -438,6 +461,7 @@ ifeq ($(uname_O),Cygwin)
NEEDS_LIBICONV = YesPlease
NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
NO_TRUSTABLE_FILEMODE = UnfortunatelyYes
+ OLD_ICONV = UnfortunatelyYes
# There are conflicting reports about this.
# On some boxes NO_MMAP is needed, and not so elsewhere.
# Try commenting this out if you suspect MMAP is more efficient
@@ -488,6 +512,17 @@ ifeq ($(uname_S),IRIX64)
# for now, build 32-bit version
BASIC_LDFLAGS += -L/usr/lib32
endif
+ifeq ($(uname_S),HP-UX)
+ NO_IPV6=YesPlease
+ NO_SETENV=YesPlease
+ NO_STRCASESTR=YesPlease
+ NO_MEMMEM = YesPlease
+ NO_STRLCPY = YesPlease
+ NO_MKDTEMP = YesPlease
+ NO_UNSETENV = YesPlease
+ NO_HSTRERROR = YesPlease
+ NO_SYS_SELECT_H = YesPlease
+endif
ifneq (,$(findstring arm,$(uname_M)))
ARM_SHA1 = YesPlease
endif
@@ -518,7 +553,9 @@ else
CC_LD_DYNPATH = -R
endif
-ifndef NO_CURL
+ifdef NO_CURL
+ BASIC_CFLAGS += -DNO_CURL
+else
ifdef CURLDIR
# Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
BASIC_CFLAGS += -I$(CURLDIR)/include
@@ -526,7 +563,9 @@ ifndef NO_CURL
else
CURL_LIBCURL = -lcurl
endif
- PROGRAMS += git-http-fetch$X
+ BUILTIN_OBJS += builtin-http-fetch.o
+ EXTLIBS += $(CURL_LIBCURL)
+ LIB_OBJS += http.o http-walker.o
curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
ifeq "$(curl_check)" "070908"
ifndef NO_EXPAT
@@ -586,6 +625,10 @@ endif
ifdef NO_C99_FORMAT
BASIC_CFLAGS += -DNO_C99_FORMAT
endif
+ifdef FREAD_READS_DIRECTORIES
+ COMPAT_CFLAGS += -DFREAD_READS_DIRECTORIES
+ COMPAT_OBJS += compat/fopen.o
+endif
ifdef NO_SYMLINK_HEAD
BASIC_CFLAGS += -DNO_SYMLINK_HEAD
endif
@@ -608,10 +651,17 @@ ifdef NO_SETENV
COMPAT_CFLAGS += -DNO_SETENV
COMPAT_OBJS += compat/setenv.o
endif
+ifdef NO_MKDTEMP
+ COMPAT_CFLAGS += -DNO_MKDTEMP
+ COMPAT_OBJS += compat/mkdtemp.o
+endif
ifdef NO_UNSETENV
COMPAT_CFLAGS += -DNO_UNSETENV
COMPAT_OBJS += compat/unsetenv.o
endif
+ifdef NO_SYS_SELECT_H
+ BASIC_CFLAGS += -DNO_SYS_SELECT_H
+endif
ifdef NO_MMAP
COMPAT_CFLAGS += -DNO_MMAP
COMPAT_OBJS += compat/mmap.o
@@ -651,6 +701,10 @@ ifdef OLD_ICONV
BASIC_CFLAGS += -DOLD_ICONV
endif
+ifdef NO_DEFLATE_BOUND
+ BASIC_CFLAGS += -DNO_DEFLATE_BOUND
+endif
+
ifdef PPC_SHA1
SHA1_HEADER = "ppc/sha1.h"
LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
@@ -679,6 +733,10 @@ ifdef NO_MEMMEM
COMPAT_CFLAGS += -DNO_MEMMEM
COMPAT_OBJS += compat/memmem.o
endif
+ifdef INTERNAL_QSORT
+ COMPAT_CFLAGS += -DINTERNAL_QSORT
+ COMPAT_OBJS += compat/qsort.o
+endif
ifdef THREADED_DELTA_SEARCH
BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
@@ -725,8 +783,11 @@ ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
bindir_SQ = $(subst ','\'',$(bindir))
+mandir_SQ = $(subst ','\'',$(mandir))
+infodir_SQ = $(subst ','\'',$(infodir))
gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
template_dir_SQ = $(subst ','\'',$(template_dir))
+htmldir_SQ = $(subst ','\'',$(htmldir))
prefix_SQ = $(subst ','\'',$(prefix))
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
@@ -736,7 +797,7 @@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
LIBS = $(GITLIBS) $(EXTLIBS)
BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
- -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $(COMPAT_CFLAGS)
+ $(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS)
ALL_CFLAGS += $(BASIC_CFLAGS)
@@ -755,6 +816,7 @@ endif
all::
ifndef NO_TCLTK
$(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all
+ $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
endif
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
@@ -762,12 +824,6 @@ endif
strip: $(PROGRAMS) git$X
$(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
-gitk-wish: gitk GIT-GUI-VARS
- $(QUIET_GEN)$(RM) $@ $@+ && \
- sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
- chmod +x $@+ && \
- mv -f $@+ $@
-
git.o: git.c common-cmds.h GIT-CFLAGS
$(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
$(ALL_CFLAGS) -c $(filter %.c,$^)
@@ -776,7 +832,11 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
-help.o: common-cmds.h
+help.o: help.c common-cmds.h GIT-CFLAGS
+ $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
+ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
+ '-DGIT_MAN_PATH="$(mandir_SQ)"' \
+ '-DGIT_INFO_PATH="$(infodir_SQ)"' $<
git-merge-subtree$X: git-merge-recursive$X
$(QUIET_BUILT_IN)$(RM) $@ && ln git-merge-recursive$X $@
@@ -784,7 +844,7 @@ git-merge-subtree$X: git-merge-recursive$X
$(BUILT_INS): git$X
$(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
-common-cmds.h: ./generate-cmdlist.sh
+common-cmds.h: ./generate-cmdlist.sh command-list.txt
common-cmds.h: $(wildcard Documentation/git-*.txt)
$(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
@@ -801,7 +861,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
-perl/perl.mak: GIT-CFLAGS
+perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
@@ -820,9 +880,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
chmod +x $@+ && \
mv $@+ $@
-git-status: git-commit
- $(QUIET_GEN)cp $< $@+ && mv $@+ $@
-
gitweb/gitweb.cgi: gitweb/gitweb.perl
$(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
@@ -856,6 +913,7 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
-e '/@@GITWEB_CGI@@/d' \
-e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
-e '/@@GITWEB_CSS@@/d' \
+ -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
$@.sh > $@+ && \
chmod +x $@+ && \
mv $@+ $@
@@ -885,39 +943,31 @@ exec_cmd.o: exec_cmd.c GIT-CFLAGS
builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
+config.o: config.c GIT-CFLAGS
+ $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $<
+
http.o: http.c GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
ifdef NO_EXPAT
-http-fetch.o: http-fetch.c http.h GIT-CFLAGS
+http-walker.o: http-walker.c http.h GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
endif
git-%$X: %.o $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-ssh-pull.o: ssh-fetch.c
-ssh-push.o: ssh-upload.c
-git-local-fetch$X: fetch.o
-git-ssh-fetch$X: rsh.o fetch.o
-git-ssh-upload$X: rsh.o
-git-ssh-pull$X: rsh.o fetch.o
-git-ssh-push$X: rsh.o
-
git-imap-send$X: imap-send.o $(LIB_FILE)
-http.o http-fetch.o http-push.o: http.h
-git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS)
- $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
- $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+http.o http-walker.o http-push.o transport.o: http.h
git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
-$(LIB_OBJS) $(BUILTIN_OBJS) fetch.o: $(LIB_H)
+$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
-$(DIFF_OBJS): diffcore.h
+builtin-revert.o wt-status.o: wt-status.h
$(LIB_FILE): $(LIB_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
@@ -931,10 +981,6 @@ $(XDIFF_LIB): $(XDIFF_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
-perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
- (cd perl && $(PERL_PATH) Makefile.PL \
- PREFIX='$(prefix_SQ)')
-
doc:
$(MAKE) -C Documentation all
@@ -980,7 +1026,7 @@ endif
### Testing rules
-TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X test-parse-options$X
all:: $(TEST_PROGRAMS)
@@ -997,6 +1043,8 @@ test-date$X: date.o ctype.o
test-delta$X: diff-delta.o patch-delta.o
+test-parse-options$X: parse-options.o
+
.PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
test-%$X: test-%.o $(GITLIBS)
@@ -1014,14 +1062,14 @@ remove-dashes:
### Installation rules
install: all
- $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(bindir_SQ)'
- $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(INSTALL) git$X '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
- $(MAKE) -C perl prefix='$(prefix_SQ)' install
+ $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
ifndef NO_TCLTK
- $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
+ $(MAKE) -C gitk-git install
$(MAKE) -C git-gui install
endif
if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
@@ -1114,7 +1162,7 @@ clean:
$(MAKE) -C templates/ clean
$(MAKE) -C t/ clean
ifndef NO_TCLTK
- $(RM) gitk-wish
+ $(MAKE) -C gitk-git clean
$(MAKE) -C git-gui clean
endif
$(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
@@ -1125,26 +1173,51 @@ endif
### Check documentation
#
check-docs::
- @for v in $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk; \
+ @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \
do \
case "$$v" in \
git-merge-octopus | git-merge-ours | git-merge-recursive | \
- git-merge-resolve | git-merge-stupid | \
- git-add--interactive | git-fsck-objects | git-init-db | \
- git-repo-config | git-fetch--tool | \
- git-ssh-pull | git-ssh-push ) continue ;; \
+ git-merge-resolve | git-merge-stupid | git-merge-subtree | \
+ git-fsck-objects | git-init-db | \
+ git-?*--?* ) continue ;; \
esac ; \
test -f "Documentation/$$v.txt" || \
echo "no doc: $$v"; \
- sed -e '1,/^__DATA__/d' Documentation/cmd-list.perl | \
+ sed -e '/^#/d' command-list.txt | \
grep -q "^$$v[ ]" || \
case "$$v" in \
git) ;; \
*) echo "no link: $$v";; \
esac ; \
- done | sort
+ done; \
+ ( \
+ sed -e '/^#/d' \
+ -e 's/[ ].*//' \
+ -e 's/^/listed /' command-list.txt; \
+ ls -1 Documentation/git*txt | \
+ sed -e 's|Documentation/|documented |' \
+ -e 's/\.txt//'; \
+ ) | while read how cmd; \
+ do \
+ case "$$how,$$cmd" in \
+ *,git-citool | \
+ *,git-gui | \
+ *,git-help | \
+ documented,gitattributes | \
+ documented,gitignore | \
+ documented,gitmodules | \
+ documented,gitcli | \
+ documented,git-tools | \
+ sentinel,not,matching,is,ok ) continue ;; \
+ esac; \
+ case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \
+ *" $$cmd "*) ;; \
+ *) echo "removed but $$how: $$cmd" ;; \
+ esac; \
+ done ) | sort
### Make sure built-ins do not have dups and listed in git.c
#
check-builtins::
./check-builtins.sh
+
diff --git a/RelNotes b/RelNotes
index 46308cee0b..3e77358f70 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.5.4.txt \ No newline at end of file
+Documentation/RelNotes-1.5.5.txt \ No newline at end of file
diff --git a/archive-tar.c b/archive-tar.c
index e1bced5609..30aa2e23fd 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -222,7 +222,7 @@ static void write_global_extended_header(const unsigned char *sha1)
static int git_tar_config(const char *var, const char *value)
{
if (!strcmp(var, "tar.umask")) {
- if (!strcmp(value, "user")) {
+ if (value && !strcmp(value, "user")) {
tar_umask = umask(0);
umask(tar_umask);
} else {
diff --git a/archive.c b/archive.c
new file mode 100644
index 0000000000..fb159fe59e
--- /dev/null
+++ b/archive.c
@@ -0,0 +1,84 @@
+#include "cache.h"
+#include "commit.h"
+#include "attr.h"
+
+static void format_subst(const struct commit *commit,
+ const char *src, size_t len,
+ struct strbuf *buf)
+{
+ char *to_free = NULL;
+ struct strbuf fmt;
+
+ if (src == buf->buf)
+ to_free = strbuf_detach(buf, NULL);
+ strbuf_init(&fmt, 0);
+ for (;;) {
+ const char *b, *c;
+
+ b = memmem(src, len, "$Format:", 8);
+ if (!b || src + len < b + 9)
+ break;
+ c = memchr(b + 8, '$', len - 8);
+ if (!c)
+ break;
+
+ strbuf_reset(&fmt);
+ strbuf_add(&fmt, b + 8, c - b - 8);
+
+ strbuf_add(buf, src, b - src);
+ format_commit_message(commit, fmt.buf, buf);
+ len -= c + 1 - src;
+ src = c + 1;
+ }
+ strbuf_add(buf, src, len);
+ strbuf_release(&fmt);
+ free(to_free);
+}
+
+static int convert_to_archive(const char *path,
+ const void *src, size_t len,
+ struct strbuf *buf,
+ const struct commit *commit)
+{
+ static struct git_attr *attr_export_subst;
+ struct git_attr_check check[1];
+
+ if (!commit)
+ return 0;
+
+ if (!attr_export_subst)
+ attr_export_subst = git_attr("export-subst", 12);
+
+ check[0].attr = attr_export_subst;
+ if (git_checkattr(path, ARRAY_SIZE(check), check))
+ return 0;
+ if (!ATTR_TRUE(check[0].value))
+ return 0;
+
+ format_subst(commit, src, len, buf);
+ return 1;
+}
+
+void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
+ unsigned int mode, enum object_type *type,
+ unsigned long *sizep,
+ const struct commit *commit)
+{
+ void *buffer;
+
+ buffer = read_sha1_file(sha1, type, sizep);
+ if (buffer && S_ISREG(mode)) {
+ struct strbuf buf;
+ size_t size = 0;
+
+ strbuf_init(&buf, 0);
+ strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
+ convert_to_working_tree(path, buf.buf, buf.len, &buf);
+ convert_to_archive(path, buf.buf, buf.len, &buf, commit);
+ buffer = strbuf_detach(&buf, &size);
+ *sizep = size;
+ }
+
+ return buffer;
+}
+
diff --git a/attr.c b/attr.c
index 92704a3f61..64b77b1663 100644
--- a/attr.c
+++ b/attr.c
@@ -209,8 +209,11 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
num_attr = 0;
cp = name + namelen;
cp = cp + strspn(cp, blank);
- while (*cp)
+ while (*cp) {
cp = parse_attr(src, lineno, cp, &num_attr, res);
+ if (!cp)
+ return NULL;
+ }
if (pass)
break;
res = xcalloc(1,
@@ -403,7 +406,7 @@ static void debug_info(const char *what, struct attr_stack *elem)
{
fprintf(stderr, "%s: %s\n", what, elem->origin ? elem->origin : "()");
}
-static void debug_set(const char *what, const char *match, struct git_attr *attr, void *v)
+static void debug_set(const char *what, const char *match, struct git_attr *attr, const void *v)
{
const char *value = v;
@@ -540,10 +543,10 @@ static int path_matches(const char *pathname, int pathlen,
if (*pattern == '/')
pattern++;
if (pathlen < baselen ||
- (baselen && pathname[baselen - 1] != '/') ||
+ (baselen && pathname[baselen] != '/') ||
strncmp(pathname, base, baselen))
return 0;
- return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+ return fnmatch(pattern, pathname + baselen + 1, FNM_PATHNAME) == 0;
}
static int fill_one(const char *what, struct match_attr *a, int rem)
diff --git a/builtin-add.c b/builtin-add.c
index f9a65803d8..820110e085 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -12,12 +12,15 @@
#include "diffcore.h"
#include "commit.h"
#include "revision.h"
-
-static const char builtin_add_usage[] =
-"git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] <filepattern>...";
-
+#include "run-command.h"
+#include "parse-options.h"
+
+static const char * const builtin_add_usage[] = {
+ "git-add [options] [--] <filepattern>...",
+ NULL
+};
+static int patch_interactive = 0, add_interactive = 0;
static int take_worktree_changes;
-static const char *excludes_file;
static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
{
@@ -44,6 +47,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
die("pathspec '%s' did not match any files",
pathspec[i]);
}
+ free(seen);
}
static void fill_directory(struct dir_struct *dir, const char **pathspec,
@@ -56,12 +60,7 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec,
memset(dir, 0, sizeof(*dir));
if (!ignored_too) {
dir->collect_ignored = 1;
- dir->exclude_per_dir = ".gitignore";
- path = git_path("info/exclude");
- if (!access(path, R_OK))
- add_excludes_from_file(dir, path);
- if (excludes_file != NULL && !access(excludes_file, R_OK))
- add_excludes_from_file(dir, excludes_file);
+ setup_standard_excludes(dir);
}
/*
@@ -106,18 +105,16 @@ static void update_callback(struct diff_queue_struct *q,
}
}
-static void update(int verbose, const char *prefix, const char **files)
+void add_files_to_cache(int verbose, const char *prefix, const char **pathspec)
{
struct rev_info rev;
init_revisions(&rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
- rev.prune_data = get_pathspec(prefix, files);
+ rev.prune_data = pathspec;
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
rev.diffopt.format_callback_data = &verbose;
- if (read_cache() < 0)
- die("index file corrupt");
- run_diff_files(&rev, 0);
+ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
}
static void refresh(int verbose, const char **pathspec)
@@ -135,18 +132,43 @@ static void refresh(int verbose, const char **pathspec)
if (!seen[i])
die("pathspec '%s' did not match any files", pathspec[i]);
}
+ free(seen);
}
-static int git_add_config(const char *var, const char *value)
+static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
{
- if (!strcmp(var, "core.excludesfile")) {
- if (!value)
- die("core.excludesfile without value");
- excludes_file = xstrdup(value);
- return 0;
+ const char **pathspec = get_pathspec(prefix, argv);
+
+ return pathspec;
+}
+
+int interactive_add(int argc, const char **argv, const char *prefix)
+{
+ int status, ac;
+ const char **args;
+ const char **pathspec = NULL;
+
+ if (argc) {
+ pathspec = validate_pathspec(argc, argv, prefix);
+ if (!pathspec)
+ return -1;
+ }
+
+ args = xcalloc(sizeof(const char *), (argc + 4));
+ ac = 0;
+ args[ac++] = "add--interactive";
+ if (patch_interactive)
+ args[ac++] = "--patch";
+ args[ac++] = "--";
+ if (argc) {
+ memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);
+ ac += argc;
}
+ args[ac] = NULL;
- return git_default_config(var, value);
+ status = run_command_v_opt(args, RUN_GIT_CMD);
+ free(args);
+ return status;
}
static struct lock_file lock_file;
@@ -154,81 +176,70 @@ static struct lock_file lock_file;
static const char ignore_error[] =
"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 struct option builtin_add_options[] = {
+ OPT__DRY_RUN(&show_only),
+ OPT__VERBOSE(&verbose),
+ OPT_GROUP(""),
+ OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
+ OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
+ OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"),
+ OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"),
+ OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
+ OPT_END(),
+};
+
int cmd_add(int argc, const char **argv, const char *prefix)
{
int i, newfd;
- int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
const char **pathspec;
struct dir_struct dir;
- int add_interactive = 0;
- for (i = 1; i < argc; i++) {
- if (!strcmp("--interactive", argv[i]) ||
- !strcmp("-i", argv[i]))
- add_interactive++;
- }
- if (add_interactive) {
- const char *args[] = { "add--interactive", NULL };
+ argc = parse_options(argc, argv, builtin_add_options,
+ builtin_add_usage, 0);
+ if (patch_interactive)
+ add_interactive = 1;
+ if (add_interactive)
+ exit(interactive_add(argc, argv, prefix));
- if (add_interactive != 1 || argc != 2)
- die("add --interactive does not take any parameters");
- execv_git_cmd(args);
- exit(1);
- }
-
- git_config(git_add_config);
+ git_config(git_default_config);
newfd = hold_locked_index(&lock_file, 1);
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (arg[0] != '-')
- break;
- if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- if (!strcmp(arg, "-n")) {
- show_only = 1;
- continue;
- }
- if (!strcmp(arg, "-f")) {
- ignored_too = 1;
- continue;
- }
- if (!strcmp(arg, "-v")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(arg, "-u")) {
- take_worktree_changes = 1;
- continue;
- }
- if (!strcmp(arg, "--refresh")) {
- refresh_only = 1;
- continue;
- }
- usage(builtin_add_usage);
- }
-
if (take_worktree_changes) {
- update(verbose, prefix, argv + i);
+ const char **pathspec;
+ if (read_cache() < 0)
+ die("index file corrupt");
+ pathspec = get_pathspec(prefix, argv);
+ add_files_to_cache(verbose, prefix, pathspec);
goto finish;
}
- if (argc <= i) {
+ if (argc == 0) {
fprintf(stderr, "Nothing specified, nothing added.\n");
fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
return 0;
}
- pathspec = get_pathspec(prefix, argv + i);
+ pathspec = get_pathspec(prefix, argv);
if (refresh_only) {
refresh(verbose, pathspec);
goto finish;
}
+ if (*argv) {
+ /* Was there an invalid path? */
+ if (pathspec) {
+ int num;
+ for (num = 0; pathspec[num]; num++)
+ ; /* just counting */
+ if (argc != num)
+ exit(1); /* error message already given */
+ } else
+ exit(1); /* error message already given */
+ }
+
fill_directory(&dir, pathspec, ignored_too);
if (show_only) {
@@ -260,7 +271,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
finish:
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
- close(newfd) || commit_locked_index(&lock_file))
+ commit_locked_index(&lock_file))
die("Unable to write new index file");
}
diff --git a/builtin-apply.c b/builtin-apply.c
index 05c6bc3592..6a88ff018d 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -45,14 +45,14 @@ static const char *fake_ancestor;
static int line_termination = '\n';
static unsigned long p_context = ULONG_MAX;
static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
-
-static enum whitespace_eol {
- nowarn_whitespace,
- warn_on_whitespace,
- error_on_whitespace,
- strip_whitespace,
-} new_whitespace = warn_on_whitespace;
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|fix|error|error-all>] <patch>...";
+
+static enum ws_error_action {
+ nowarn_ws_error,
+ warn_on_ws_error,
+ die_on_ws_error,
+ correct_ws_error,
+} ws_error_action = warn_on_ws_error;
static int whitespace_error;
static int squelch_whitespace_errors = 5;
static int applied_after_fixing_ws;
@@ -61,28 +61,28 @@ static const char *patch_input_file;
static void parse_whitespace_option(const char *option)
{
if (!option) {
- new_whitespace = warn_on_whitespace;
+ ws_error_action = warn_on_ws_error;
return;
}
if (!strcmp(option, "warn")) {
- new_whitespace = warn_on_whitespace;
+ ws_error_action = warn_on_ws_error;
return;
}
if (!strcmp(option, "nowarn")) {
- new_whitespace = nowarn_whitespace;
+ ws_error_action = nowarn_ws_error;
return;
}
if (!strcmp(option, "error")) {
- new_whitespace = error_on_whitespace;
+ ws_error_action = die_on_ws_error;
return;
}
if (!strcmp(option, "error-all")) {
- new_whitespace = error_on_whitespace;
+ ws_error_action = die_on_ws_error;
squelch_whitespace_errors = 0;
return;
}
- if (!strcmp(option, "strip")) {
- new_whitespace = strip_whitespace;
+ if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
+ ws_error_action = correct_ws_error;
return;
}
die("unrecognized whitespace option '%s'", option);
@@ -90,11 +90,8 @@ static void parse_whitespace_option(const char *option)
static void set_default_whitespace_mode(const char *whitespace_option)
{
- if (!whitespace_option && !apply_default_whitespace) {
- new_whitespace = (apply
- ? warn_on_whitespace
- : nowarn_whitespace);
- }
+ if (!whitespace_option && !apply_default_whitespace)
+ ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error);
}
/*
@@ -137,11 +134,17 @@ struct fragment {
#define BINARY_DELTA_DEFLATED 1
#define BINARY_LITERAL_DEFLATED 2
+/*
+ * This represents a "patch" to a file, both metainfo changes
+ * such as creation/deletion, filemode and content changes represented
+ * as a series of fragments.
+ */
struct patch {
char *new_name, *old_name, *def_name;
unsigned int old_mode, new_mode;
int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
int rejected;
+ unsigned ws_rule;
unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
@@ -152,13 +155,14 @@ struct patch {
unsigned int is_rename:1;
struct fragment *fragments;
char *result;
- unsigned long resultsize;
+ size_t resultsize;
char old_sha1_prefix[41];
char new_sha1_prefix[41];
struct patch *next;
};
-static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post)
+static void say_patch_name(FILE *output, const char *pre,
+ struct patch *patch, const char *post)
{
fputs(pre, output);
if (patch->old_name && patch->new_name &&
@@ -229,7 +233,8 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
if (*line == '"') {
struct strbuf name;
- /* Proposed "new-style" GNU patch/diff format; see
+ /*
+ * Proposed "new-style" GNU patch/diff format; see
* http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
*/
strbuf_init(&name, 0);
@@ -499,7 +504,8 @@ static int gitdiff_dissimilarity(const char *line, struct patch *patch)
static int gitdiff_index(const char *line, struct patch *patch)
{
- /* index line is N hexadecimal, "..", N hexadecimal,
+ /*
+ * index line is N hexadecimal, "..", N hexadecimal,
* and optional space with octal mode.
*/
const char *ptr, *eol;
@@ -550,7 +556,8 @@ static const char *stop_at_slash(const char *line, int llen)
return NULL;
}
-/* This is to extract the same name that appears on "diff --git"
+/*
+ * This is to extract the same name that appears on "diff --git"
* line. We do not find and return anything if it is a rename
* patch, and it is OK because we will find the name elsewhere.
* We need to reliably find name only when it is mode-change only,
@@ -584,7 +591,8 @@ static char *git_header_name(char *line, int llen)
goto free_and_fail1;
strbuf_remove(&first, 0, cp + 1 - first.buf);
- /* second points at one past closing dq of name.
+ /*
+ * second points at one past closing dq of name.
* find the second name.
*/
while ((second < line + llen) && isspace(*second))
@@ -627,7 +635,8 @@ static char *git_header_name(char *line, int llen)
return NULL;
name++;
- /* since the first name is unquoted, a dq if exists must be
+ /*
+ * since the first name is unquoted, a dq if exists must be
* the beginning of the second name.
*/
for (second = name; second < line + llen; second++) {
@@ -683,7 +692,6 @@ static char *git_header_name(char *line, int llen)
}
}
}
- return NULL;
}
/* Verify that we recognize the lines following a git header */
@@ -759,7 +767,7 @@ static int parse_num(const char *line, unsigned long *p)
}
static int parse_range(const char *line, int len, int offset, const char *expect,
- unsigned long *p1, unsigned long *p2)
+ unsigned long *p1, unsigned long *p2)
{
int digits, ex;
@@ -868,14 +876,14 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
return offset;
}
- /** --- followed by +++ ? */
+ /* --- followed by +++ ? */
if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4))
continue;
/*
* We only accept unified patches, so we want it to
* at least have "@@ -a,b +c,d @@\n", which is 14 chars
- * minimum
+ * minimum ("@@ -0,0 +1 @@\n" is the shortest).
*/
nextlen = linelen(line + len, size - len);
if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
@@ -890,56 +898,34 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
return -1;
}
-static void check_whitespace(const char *line, int len)
+static void check_whitespace(const char *line, int len, unsigned ws_rule)
{
- const char *err = "Adds trailing whitespace";
- int seen_space = 0;
- int i;
-
- /*
- * We know len is at least two, since we have a '+' and we
- * checked that the last character was a '\n' before calling
- * this function. That is, an addition of an empty line would
- * check the '+' here. Sneaky...
- */
- if (isspace(line[len-2]))
- goto error;
-
- /*
- * Make sure that there is no space followed by a tab in
- * indentation.
- */
- err = "Space in indent is followed by a tab";
- for (i = 1; i < len; i++) {
- if (line[i] == '\t') {
- if (seen_space)
- goto error;
- }
- else if (line[i] == ' ')
- seen_space = 1;
- else
- break;
- }
- return;
+ char *err;
+ unsigned result = check_and_emit_line(line + 1, len - 1, ws_rule,
+ NULL, NULL, NULL, NULL);
+ if (!result)
+ return;
- error:
whitespace_error++;
if (squelch_whitespace_errors &&
squelch_whitespace_errors < whitespace_error)
;
- else
- fprintf(stderr, "%s.\n%s:%d:%.*s\n",
- err, patch_input_file, linenr, len-2, line+1);
+ else {
+ err = whitespace_error_string(result);
+ fprintf(stderr, "%s:%d: %s.\n%.*s\n",
+ patch_input_file, linenr, err, len - 2, line + 1);
+ free(err);
+ }
}
-
/*
* Parse a unified diff. Note that this really needs to parse each
* fragment separately, since the only way to know the difference
* between a "---" that is part of a patch, and a "---" that starts
* the next patch is to look at the line counts..
*/
-static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
+static int parse_fragment(char *line, unsigned long size,
+ struct patch *patch, struct fragment *fragment)
{
int added, deleted;
int len = linelen(line, size), offset;
@@ -980,22 +966,23 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
break;
case '-':
if (apply_in_reverse &&
- new_whitespace != nowarn_whitespace)
- check_whitespace(line, len);
+ ws_error_action != nowarn_ws_error)
+ check_whitespace(line, len, patch->ws_rule);
deleted++;
oldlines--;
trailing = 0;
break;
case '+':
if (!apply_in_reverse &&
- new_whitespace != nowarn_whitespace)
- check_whitespace(line, len);
+ ws_error_action != nowarn_ws_error)
+ check_whitespace(line, len, patch->ws_rule);
added++;
newlines--;
trailing = 0;
break;
- /* We allow "\ No newline at end of file". Depending
+ /*
+ * We allow "\ No newline at end of file". Depending
* on locale settings when the patch was produced we
* don't know what this line looks like. The only
* thing we do know is that it begins with "\ ".
@@ -1013,7 +1000,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
fragment->leading = leading;
fragment->trailing = trailing;
- /* If a fragment ends with an incomplete line, we failed to include
+ /*
+ * If a fragment ends with an incomplete line, we failed to include
* it in the above loop because we hit oldlines == newlines == 0
* before seeing it.
*/
@@ -1141,7 +1129,8 @@ static struct fragment *parse_binary_hunk(char **buf_p,
int *status_p,
int *used_p)
{
- /* Expect a line that begins with binary patch method ("literal"
+ /*
+ * Expect a line that begins with binary patch method ("literal"
* or "delta"), followed by the length of data before deflating.
* a sequence of 'length-byte' followed by base-85 encoded data
* should follow, terminated by a newline.
@@ -1190,7 +1179,8 @@ static struct fragment *parse_binary_hunk(char **buf_p,
size--;
break;
}
- /* Minimum line is "A00000\n" which is 7-byte long,
+ /*
+ * Minimum line is "A00000\n" which is 7-byte long,
* and the line length must be multiple of 5 plus 2.
*/
if ((llen < 7) || (llen-2) % 5)
@@ -1241,7 +1231,8 @@ static struct fragment *parse_binary_hunk(char **buf_p,
static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
{
- /* We have read "GIT binary patch\n"; what follows is a line
+ /*
+ * We have read "GIT binary patch\n"; what follows is a line
* that says the patch method (currently, either "literal" or
* "delta") and the length of data before deflating; a
* sequence of 'length-byte' followed by base-85 encoded data
@@ -1271,7 +1262,8 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
if (reverse)
used += used_1;
else if (status) {
- /* not having reverse hunk is not an error, but having
+ /*
+ * Not having reverse hunk is not an error, but having
* a corrupt reverse hunk is.
*/
free((void*) forward->patch);
@@ -1292,7 +1284,12 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
if (offset < 0)
return offset;
- patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch);
+ patch->ws_rule = whitespace_rule(patch->new_name
+ ? patch->new_name
+ : patch->old_name);
+
+ patchsize = parse_single_patch(buffer + offset + hdrsize,
+ size - offset - hdrsize, patch);
if (!patchsize) {
static const char *binhdr[] = {
@@ -1368,8 +1365,10 @@ static void reverse_patches(struct patch *p)
}
}
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
+static const char pluses[] =
+"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]=
+"----------------------------------------------------------------------";
static void show_stats(struct patch *patch)
{
@@ -1431,14 +1430,16 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
case S_IFREG:
if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
return error("unable to open or read %s", path);
- convert_to_git(path, buf->buf, buf->len, buf);
+ convert_to_git(path, buf->buf, buf->len, buf, 0);
return 0;
default:
return -1;
}
}
-static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line, int *lines)
+static int find_offset(const char *buf, unsigned long size,
+ const char *fragment, unsigned long fragsize,
+ int line, int *lines)
{
int i;
unsigned long start, backwards, forwards;
@@ -1537,9 +1538,11 @@ static void remove_last_line(const char **rbuf, int *rsize)
*rsize = offset + 1;
}
-static int apply_line(char *output, const char *patch, int plen)
+static int apply_line(char *output, const char *patch, int plen,
+ unsigned ws_rule)
{
- /* plen is number of bytes to be copied from patch,
+ /*
+ * plen is number of bytes to be copied from patch,
* starting at patch+1 (patch[0] is '+'). Typically
* patch[plen] is '\n', unless this is the incomplete
* last line.
@@ -1547,18 +1550,22 @@ static int apply_line(char *output, const char *patch, int plen)
int i;
int add_nl_to_tail = 0;
int fixed = 0;
- int last_tab_in_indent = -1;
- int last_space_in_indent = -1;
+ int last_tab_in_indent = 0;
+ int last_space_in_indent = 0;
int need_fix_leading_space = 0;
char *buf;
- if ((new_whitespace != strip_whitespace) || !whitespace_error ||
+ if ((ws_error_action != correct_ws_error) || !whitespace_error ||
*patch != '+') {
memcpy(output, patch + 1, plen);
return plen;
}
- if (1 < plen && isspace(patch[plen-1])) {
+ /*
+ * Strip trailing whitespace
+ */
+ if ((ws_rule & WS_TRAILING_SPACE) &&
+ (1 < plen && isspace(patch[plen-1]))) {
if (patch[plen] == '\n')
add_nl_to_tail = 1;
plen--;
@@ -1567,15 +1574,22 @@ static int apply_line(char *output, const char *patch, int plen)
fixed = 1;
}
+ /*
+ * Check leading whitespaces (indent)
+ */
for (i = 1; i < plen; i++) {
char ch = patch[i];
if (ch == '\t') {
last_tab_in_indent = i;
- if (0 <= last_space_in_indent)
+ if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
+ 0 < last_space_in_indent)
+ need_fix_leading_space = 1;
+ } else if (ch == ' ') {
+ last_space_in_indent = i;
+ if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
+ 8 <= i - last_tab_in_indent)
need_fix_leading_space = 1;
}
- else if (ch == ' ')
- last_space_in_indent = i;
else
break;
}
@@ -1583,10 +1597,21 @@ static int apply_line(char *output, const char *patch, int plen)
buf = output;
if (need_fix_leading_space) {
int consecutive_spaces = 0;
- /* between patch[1..last_tab_in_indent] strip the
- * funny spaces, updating them to tab as needed.
+ int last = last_tab_in_indent + 1;
+
+ if (ws_rule & WS_INDENT_WITH_NON_TAB) {
+ /* have "last" point at one past the indent */
+ if (last_tab_in_indent < last_space_in_indent)
+ last = last_space_in_indent + 1;
+ else
+ last = last_tab_in_indent + 1;
+ }
+
+ /*
+ * between patch[1..last], strip the funny spaces,
+ * updating them to tab as needed.
*/
- for (i = 1; i < last_tab_in_indent; i++, plen--) {
+ for (i = 1; i < last; i++, plen--) {
char ch = patch[i];
if (ch != ' ') {
consecutive_spaces = 0;
@@ -1599,8 +1624,10 @@ static int apply_line(char *output, const char *patch, int plen)
}
}
}
+ while (0 < consecutive_spaces--)
+ *output++ = ' ';
fixed = 1;
- i = last_tab_in_indent;
+ i = last;
}
else
i = 1;
@@ -1613,7 +1640,8 @@ static int apply_line(char *output, const char *patch, int plen)
return output + plen - buf;
}
-static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int inaccurate_eof)
+static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
+ int inaccurate_eof, unsigned ws_rule)
{
int match_beginning, match_end;
const char *patch = frag->patch;
@@ -1672,7 +1700,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int ina
case '+':
if (first != '+' || !no_add) {
int added = apply_line(new + newsize, patch,
- plen);
+ plen, ws_rule);
newsize += added;
if (first == '+' &&
added == 1 && new[newsize-1] == '\n')
@@ -1695,8 +1723,9 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int ina
size -= len;
}
- if (inaccurate_eof && oldsize > 0 && old[oldsize - 1] == '\n' &&
- newsize > 0 && new[newsize - 1] == '\n') {
+ if (inaccurate_eof &&
+ oldsize > 0 && old[oldsize - 1] == '\n' &&
+ newsize > 0 && new[newsize - 1] == '\n') {
oldsize--;
newsize--;
}
@@ -1733,7 +1762,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int ina
if (match_beginning && offset)
offset = -1;
if (offset >= 0) {
- if (new_whitespace == strip_whitespace &&
+ if (ws_error_action == correct_ws_error &&
(buf->len - oldsize - offset == 0)) /* end of file? */
newsize -= new_blank_lines_at_end;
@@ -1758,9 +1787,10 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int ina
match_beginning = match_end = 0;
continue;
}
- /* Reduce the number of context lines
- * Reduce both leading and trailing if they are equal
- * otherwise just reduce the larger context.
+ /*
+ * Reduce the number of context lines; reduce both
+ * leading and trailing if they are equal otherwise
+ * just reduce the larger context.
*/
if (leading >= trailing) {
remove_first_line(&oldlines, &oldsize);
@@ -1820,7 +1850,8 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
const char *name = patch->old_name ? patch->old_name : patch->new_name;
unsigned char sha1[20];
- /* For safety, we require patch index line to contain
+ /*
+ * For safety, we require patch index line to contain
* full 40-byte textual SHA1 for old and new, at least for now.
*/
if (strlen(patch->old_sha1_prefix) != 40 ||
@@ -1831,7 +1862,8 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
"without full index line", name);
if (patch->old_name) {
- /* See if the old one matches what the patch
+ /*
+ * See if the old one matches what the patch
* applies to.
*/
hash_sha1_file(buf->buf, buf->len, blob_type, sha1);
@@ -1868,7 +1900,8 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
/* XXX read_sha1_file NUL-terminates */
strbuf_attach(buf, result, size, size + 1);
} else {
- /* We have verified buf matches the preimage;
+ /*
+ * We have verified buf matches the preimage;
* apply the patch data to it, which is stored
* in the patch->fragments->{patch,size}.
*/
@@ -1890,12 +1923,14 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch)
{
struct fragment *frag = patch->fragments;
const char *name = patch->old_name ? patch->old_name : patch->new_name;
+ unsigned ws_rule = patch->ws_rule;
+ unsigned inaccurate_eof = patch->inaccurate_eof;
if (patch->is_binary)
return apply_binary(buf, patch);
while (frag) {
- if (apply_one_fragment(buf, frag, patch->inaccurate_eof)) {
+ if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) {
error("patch failed: %s:%ld", name, frag->oldpos);
if (!apply_with_reject)
return -1;
@@ -1911,7 +1946,7 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
if (!ce)
return 0;
- if (S_ISGITLINK(ntohl(ce->ce_mode))) {
+ if (S_ISGITLINK(ce->ce_mode)) {
strbuf_grow(buf, 100);
strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
} else {
@@ -1988,12 +2023,12 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists)
static int verify_index_match(struct cache_entry *ce, struct stat *st)
{
- if (S_ISGITLINK(ntohl(ce->ce_mode))) {
+ if (S_ISGITLINK(ce->ce_mode)) {
if (!S_ISDIR(st->st_mode))
return -1;
return 0;
}
- return ce_match_stat(ce, st, 1);
+ return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID);
}
static int check_patch(struct patch *patch, struct patch *prev_patch)
@@ -2047,12 +2082,12 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
return error("%s: does not match index",
old_name);
if (cached)
- st_mode = ntohl(ce->ce_mode);
+ st_mode = ce->ce_mode;
} else if (stat_ret < 0)
return error("%s: %s", old_name, strerror(errno));
if (!cached)
- st_mode = ntohl(ce_mode_from_stat(ce, st.st_mode));
+ st_mode = ce_mode_from_stat(ce, st.st_mode);
if (patch->is_new < 0)
patch->is_new = 0;
@@ -2067,7 +2102,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
if (new_name && prev_patch && 0 < prev_patch->is_delete &&
!strcmp(prev_patch->old_name, new_name))
- /* A type-change diff is always split into a patch to
+ /*
+ * A type-change diff is always split into a patch to
* delete old, immediately followed by a patch to
* create new (see diff.c::run_diff()); in such a case
* it is Ok that the entry to be deleted by the
@@ -2352,7 +2388,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
ce = xcalloc(1, ce_size);
memcpy(ce->name, path, namelen);
ce->ce_mode = create_ce_mode(mode);
- ce->ce_flags = htons(namelen);
+ ce->ce_flags = namelen;
if (S_ISGITLINK(mode)) {
const char *s = buf;
@@ -2671,7 +2707,7 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
offset += nr;
}
- if (whitespace_error && (new_whitespace == error_on_whitespace))
+ if (whitespace_error && (ws_error_action == die_on_ws_error))
apply = 0;
update_index = check_index && apply;
@@ -2710,6 +2746,8 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
static int git_apply_config(const char *var, const char *value)
{
if (!strcmp(var, "apply.whitespace")) {
+ if (!value)
+ return config_error_nonbool(var);
apply_default_whitespace = xstrdup(value);
return 0;
}
@@ -2866,12 +2904,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
squelched,
squelched == 1 ? "" : "s");
}
- if (new_whitespace == error_on_whitespace)
+ if (ws_error_action == die_on_ws_error)
die("%d line%s add%s whitespace errors.",
whitespace_error,
whitespace_error == 1 ? "" : "s",
whitespace_error == 1 ? "s" : "");
- if (applied_after_fixing_ws)
+ if (applied_after_fixing_ws && apply)
fprintf(stderr, "warning: %d line%s applied after"
" fixing whitespace errors.\n",
applied_after_fixing_ws,
@@ -2885,7 +2923,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
if (update_index) {
if (write_cache(newfd, active_cache, active_nr) ||
- close(newfd) || commit_locked_index(&lock_file))
+ commit_locked_index(&lock_file))
die("Unable to write new index file");
}
diff --git a/builtin-archive.c b/builtin-archive.c
index 04385dea05..c2e0c1ea5a 100644
--- a/builtin-archive.c
+++ b/builtin-archive.c
@@ -30,7 +30,7 @@ static int run_remote_archiver(const char *remote, int argc,
{
char *url, buf[LARGE_PACKET_MAX];
int fd[2], i, len, rv;
- pid_t pid;
+ struct child_process *conn;
const char *exec = "git-upload-archive";
int exec_at = 0;
@@ -46,9 +46,7 @@ static int run_remote_archiver(const char *remote, int argc,
}
url = xstrdup(remote);
- pid = git_connect(fd, url, exec, 0);
- if (pid < 0)
- return pid;
+ conn = git_connect(fd, url, exec, 0);
for (i = 1; i < argc; i++) {
if (i == exec_at)
@@ -76,89 +74,11 @@ static int run_remote_archiver(const char *remote, int argc,
rv = recv_sideband("archive", fd[0], 1, 2);
close(fd[0]);
close(fd[1]);
- rv |= finish_connect(pid);
+ rv |= finish_connect(conn);
return !!rv;
}
-static void format_subst(const struct commit *commit,
- const char *src, size_t len,
- struct strbuf *buf)
-{
- char *to_free = NULL;
- struct strbuf fmt;
-
- if (src == buf->buf)
- to_free = strbuf_detach(buf, NULL);
- strbuf_init(&fmt, 0);
- for (;;) {
- const char *b, *c;
-
- b = memmem(src, len, "$Format:", 8);
- if (!b || src + len < b + 9)
- break;
- c = memchr(b + 8, '$', len - 8);
- if (!c)
- break;
-
- strbuf_reset(&fmt);
- strbuf_add(&fmt, b + 8, c - b - 8);
-
- strbuf_add(buf, src, b - src);
- format_commit_message(commit, fmt.buf, buf);
- len -= c + 1 - src;
- src = c + 1;
- }
- strbuf_add(buf, src, len);
- strbuf_release(&fmt);
- free(to_free);
-}
-
-static int convert_to_archive(const char *path,
- const void *src, size_t len,
- struct strbuf *buf,
- const struct commit *commit)
-{
- static struct git_attr *attr_export_subst;
- struct git_attr_check check[1];
-
- if (!commit)
- return 0;
-
- if (!attr_export_subst)
- attr_export_subst = git_attr("export-subst", 12);
-
- check[0].attr = attr_export_subst;
- if (git_checkattr(path, ARRAY_SIZE(check), check))
- return 0;
- if (!ATTR_TRUE(check[0].value))
- return 0;
-
- format_subst(commit, src, len, buf);
- return 1;
-}
-
-void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
- unsigned int mode, enum object_type *type,
- unsigned long *sizep,
- const struct commit *commit)
-{
- void *buffer;
-
- buffer = read_sha1_file(sha1, type, sizep);
- if (buffer && S_ISREG(mode)) {
- struct strbuf buf;
-
- strbuf_init(&buf, 0);
- strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
- convert_to_working_tree(path, buf.buf, buf.len, &buf);
- convert_to_archive(path, buf.buf, buf.len, &buf, commit);
- buffer = strbuf_detach(&buf, sizep);
- }
-
- return buffer;
-}
-
static int init_archiver(const char *name, struct archiver *ar)
{
int rv = -1, i;
diff --git a/builtin-blame.c b/builtin-blame.c
index e3112a2d5b..59d7237f21 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -125,11 +125,18 @@ static void origin_decref(struct origin *o)
if (o && --o->refcnt <= 0) {
if (o->file.ptr)
free(o->file.ptr);
- memset(o, 0, sizeof(*o));
free(o);
}
}
+static void drop_origin_blob(struct origin *o)
+{
+ if (o->file.ptr) {
+ free(o->file.ptr);
+ o->file.ptr = NULL;
+ }
+}
+
/*
* Each group of lines is described by a blame_entry; it can be split
* as we pass blame to the parents. They form a linked list in the
@@ -335,7 +342,7 @@ static struct origin *find_origin(struct scoreboard *sb,
* same and diff-tree is fairly efficient about this.
*/
diff_setup(&diff_opts);
- diff_opts.recursive = 1;
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = 0;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
paths[0] = origin->path;
@@ -380,6 +387,7 @@ static struct origin *find_origin(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
if (porigin) {
/*
* Create a freestanding copy that is not part of
@@ -409,7 +417,7 @@ static struct origin *find_rename(struct scoreboard *sb,
const char *paths[2];
diff_setup(&diff_opts);
- diff_opts.recursive = 1;
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = origin->path;
@@ -436,6 +444,7 @@ static struct origin *find_rename(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
return porigin;
}
@@ -532,7 +541,7 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
state.ret->chunks = NULL;
state.ret->num = 0;
- xdl_diff(file_p, file_o, &xpp, &xecfg, &ecb);
+ xdi_diff(file_p, file_o, &xpp, &xecfg, &ecb);
if (state.ret->num) {
struct chunk *chunk;
@@ -597,7 +606,7 @@ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
/*
* src typically is on-stack; we want to copy the information in it to
- * an malloced blame_entry that is already on the linked list of the
+ * a malloced blame_entry that is already on the linked list of the
* scoreboard. The origin of dst loses a refcnt while the origin of src
* gains one.
*/
@@ -1075,7 +1084,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
return 1; /* nothing remains for this target */
diff_setup(&diff_opts);
- diff_opts.recursive = 1;
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
paths[0] = NULL;
@@ -1093,7 +1102,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
if ((opt & PICKAXE_BLAME_COPY_HARDEST)
|| ((opt & PICKAXE_BLAME_COPY_HARDER)
&& (!porigin || strcmp(target->path, porigin->path))))
- diff_opts.find_copies_harder = 1;
+ DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
if (is_null_sha1(target->commit->object.sha1))
do_diff_cache(parent->tree->object.sha1, &diff_opts);
@@ -1102,7 +1111,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
target->commit->tree->object.sha1,
"", &diff_opts);
- if (!diff_opts.find_copies_harder)
+ if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
diffcore_std(&diff_opts);
retval = 0;
@@ -1157,7 +1166,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
-
+ diff_tree_release_paths(&diff_opts);
return retval;
}
@@ -1274,8 +1283,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
}
finish:
- for (i = 0; i < MAXPARENT; i++)
- origin_decref(parent_origin[i]);
+ for (i = 0; i < MAXPARENT; i++) {
+ if (parent_origin[i]) {
+ drop_origin_blob(parent_origin[i]);
+ origin_decref(parent_origin[i]);
+ }
+ }
+ drop_origin_blob(origin);
}
/*
@@ -1880,9 +1894,7 @@ static unsigned parse_score(const char *arg)
static const char *add_prefix(const char *prefix, const char *path)
{
- if (!prefix || !prefix[0])
- return path;
- return prefix_path(prefix, strlen(prefix), path);
+ return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
/*
@@ -2059,6 +2071,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
if (strbuf_read(&buf, 0, 0) < 0)
die("read error %s from stdin", strerror(errno));
}
+ convert_to_git(path, buf.buf, buf.len, &buf, 0);
origin->file.ptr = buf.buf;
origin->file.size = buf.len;
pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
@@ -2077,7 +2090,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
if (!mode) {
int pos = cache_name_pos(path, len);
if (0 <= pos)
- mode = ntohl(active_cache[pos]->ce_mode);
+ mode = active_cache[pos]->ce_mode;
else
/* Let's not bother reading from HEAD tree */
mode = S_IFREG | 0644;
@@ -2214,9 +2227,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
argv[unk++] = arg;
}
- if (!incremental)
- setup_pager();
-
if (!blame_move_score)
blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
if (!blame_copy_score)
@@ -2297,6 +2307,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
else if (i != argc - 1)
usage(blame_usage); /* garbage at end */
+ setup_work_tree();
if (!has_path_in_work_tree(path))
die("cannot stat path %s: %s",
path, strerror(errno));
@@ -2344,6 +2355,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
* do not default to HEAD, but use the working tree
* or "--contents".
*/
+ setup_work_tree();
sb.final = fake_working_tree_commit(path, contents_from);
add_pending_object(&revs, &(sb.final->object), ":");
}
@@ -2355,7 +2367,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
* bottom commits we would reach while traversing as
* uninteresting.
*/
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
if (is_null_sha1(sb.final->object.sha1)) {
char *buf;
@@ -2410,6 +2423,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
read_mailmap(&mailmap, ".mailmap", NULL);
+ if (!incremental)
+ setup_pager();
+
assign_blame(&sb, &revs, opt);
if (incremental)
diff --git a/builtin-branch.c b/builtin-branch.c
index 3da8b55b8f..9edf2eb816 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -11,9 +11,15 @@
#include "commit.h"
#include "builtin.h"
#include "remote.h"
-
-static const char builtin_branch_usage[] =
- "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]";
+#include "parse-options.h"
+
+static const char * const builtin_branch_usage[] = {
+ "git-branch [options] [-r | -a]",
+ "git-branch [options] [-l] [-f] <branchname> [<start-point>]",
+ "git-branch [options] [-r] (-d | -D) <branchname>",
+ "git-branch [options] (-m | -M) [<oldbranch>] <newbranch>",
+ NULL
+};
#define REF_UNKNOWN_TYPE 0x00
#define REF_LOCAL_BRANCH 0x01
@@ -25,7 +31,7 @@ static unsigned char head_sha1[20];
static int branch_track = 1;
-static int branch_use_color;
+static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
"\033[m", /* reset */
"", /* PLAIN (normal) */
@@ -59,23 +65,26 @@ static int parse_branch_color_slot(const char *var, int ofs)
static int git_branch_config(const char *var, const char *value)
{
if (!strcmp(var, "color.branch")) {
- branch_use_color = git_config_colorbool(var, value);
+ branch_use_color = git_config_colorbool(var, value, -1);
return 0;
}
if (!prefixcmp(var, "color.branch.")) {
int slot = parse_branch_color_slot(var, 13);
+ if (!value)
+ return config_error_nonbool(var);
color_parse(value, var, branch_colors[slot]);
return 0;
}
- if (!strcmp(var, "branch.autosetupmerge"))
- branch_track = git_config_bool(var, value);
-
- return git_default_config(var, value);
+ if (!strcmp(var, "branch.autosetupmerge")) {
+ branch_track = git_config_bool(var, value);
+ return 0;
+ }
+ return git_color_default_config(var, value);
}
static const char *branch_get_color(enum color_branch ix)
{
- if (branch_use_color)
+ if (branch_use_color > 0)
return branch_colors[ix];
return "";
}
@@ -142,7 +151,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
if (!force &&
!in_merge_bases(rev, &head_rev, 1)) {
- error("The branch '%s' is not a strict subset of "
+ error("The branch '%s' is not an ancestor of "
"your current HEAD.\n"
"If you are sure you want to delete it, "
"run 'git branch -D %s'.", argv[i], argv[i]);
@@ -178,9 +187,30 @@ struct ref_item {
struct ref_list {
int index, alloc, maxwidth;
struct ref_item *list;
+ struct commit_list *with_commit;
int kinds;
};
+static int has_commit(const unsigned char *sha1, struct commit_list *with_commit)
+{
+ struct commit *commit;
+
+ if (!with_commit)
+ return 1;
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit)
+ return 0;
+ while (with_commit) {
+ struct commit *other;
+
+ other = with_commit->item;
+ with_commit = with_commit->next;
+ if (in_merge_bases(other, &commit, 1))
+ return 1;
+ }
+ return 0;
+}
+
static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
{
struct ref_list *ref_list = (struct ref_list*)(cb_data);
@@ -200,6 +230,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
refname += 10;
}
+ /* Filter with with_commit if specified */
+ if (!has_commit(sha1, ref_list->with_commit))
+ return 0;
+
/* Don't add types the caller doesn't want */
if ((kind & ref_list->kinds) == 0)
return 0;
@@ -276,7 +310,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
commit = lookup_commit(item->sha1);
if (commit && !parse_commit(commit)) {
pretty_print_commit(CMIT_FMT_ONELINE, commit,
- &subject, 0, NULL, NULL, 0);
+ &subject, 0, NULL, NULL, 0, 0);
sub = subject.buf;
}
printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color),
@@ -290,19 +324,20 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
}
}
-static void print_ref_list(int kinds, int detached, int verbose, int abbrev)
+static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
{
int i;
struct ref_list ref_list;
memset(&ref_list, 0, sizeof(ref_list));
ref_list.kinds = kinds;
+ ref_list.with_commit = with_commit;
for_each_ref(append_ref, &ref_list);
qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
detached = (detached && (kinds & REF_LOCAL_BRANCH));
- if (detached) {
+ if (detached && has_commit(head_sha1, with_commit)) {
struct ref_item item;
item.name = xstrdup("(no branch)");
item.kind = REF_LOCAL_BRANCH;
@@ -499,99 +534,68 @@ static void rename_branch(const char *oldname, const char *newname, int force)
die("Branch is renamed, but update of config-file failed");
}
+static int opt_parse_with_commit(const struct option *opt, const char *arg, int unset)
+{
+ unsigned char sha1[20];
+ struct commit *commit;
+
+ if (!arg)
+ return -1;
+ if (get_sha1(arg, sha1))
+ die("malformed object name %s", arg);
+ commit = lookup_commit_reference(sha1);
+ if (!commit)
+ die("no such commit %s", arg);
+ commit_list_insert(commit, opt->value);
+ return 0;
+}
+
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, force_delete = 0, force_create = 0;
- int rename = 0, force_rename = 0;
+ int delete = 0, rename = 0, force_create = 0;
int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
int reflog = 0, track;
int kinds = REF_LOCAL_BRANCH;
- int i;
+ struct commit_list *with_commit = NULL;
+
+ struct option options[] = {
+ OPT_GROUP("Generic options"),
+ OPT__VERBOSE(&verbose),
+ OPT_BOOLEAN( 0 , "track", &track, "set up tracking mode (see git-pull(1))"),
+ OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"),
+ OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
+ REF_REMOTE_BRANCH),
+ OPT_CALLBACK(0, "contains", &with_commit, "commit",
+ "print only branches that contain the commit",
+ opt_parse_with_commit),
+ {
+ OPTION_CALLBACK, 0, "with", &with_commit, "commit",
+ "print only branches that contain the commit",
+ PARSE_OPT_HIDDEN, opt_parse_with_commit,
+ },
+ OPT__ABBREV(&abbrev),
+
+ OPT_GROUP("Specific git-branch actions:"),
+ OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+ REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
+ OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
+ OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
+ 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', NULL, &force_create, "force creation (when already exists)"),
+ OPT_END(),
+ };
git_config(git_branch_config);
- track = branch_track;
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (arg[0] != '-')
- break;
- if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- if (!strcmp(arg, "--track")) {
- track = 1;
- continue;
- }
- if (!strcmp(arg, "--no-track")) {
- track = 0;
- continue;
- }
- if (!strcmp(arg, "-d")) {
- delete = 1;
- continue;
- }
- if (!strcmp(arg, "-D")) {
- delete = 1;
- force_delete = 1;
- continue;
- }
- if (!strcmp(arg, "-f")) {
- force_create = 1;
- continue;
- }
- if (!strcmp(arg, "-m")) {
- rename = 1;
- continue;
- }
- if (!strcmp(arg, "-M")) {
- rename = 1;
- force_rename = 1;
- continue;
- }
- if (!strcmp(arg, "-r")) {
- kinds = REF_REMOTE_BRANCH;
- continue;
- }
- if (!strcmp(arg, "-a")) {
- kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH;
- continue;
- }
- if (!strcmp(arg, "-l")) {
- reflog = 1;
- continue;
- }
- if (!prefixcmp(arg, "--no-abbrev")) {
- abbrev = 0;
- continue;
- }
- if (!prefixcmp(arg, "--abbrev=")) {
- abbrev = strtoul(arg + 9, NULL, 10);
- if (abbrev < MINIMUM_ABBREV)
- abbrev = MINIMUM_ABBREV;
- else if (abbrev > 40)
- abbrev = 40;
- continue;
- }
- if (!strcmp(arg, "-v")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(arg, "--color")) {
- branch_use_color = 1;
- continue;
- }
- if (!strcmp(arg, "--no-color")) {
- branch_use_color = 0;
- continue;
- }
- usage(builtin_branch_usage);
- }
+ if (branch_use_color == -1)
+ branch_use_color = git_use_color_default;
- if ((delete && rename) || (delete && force_create) ||
- (rename && force_create))
- usage(builtin_branch_usage);
+ track = branch_track;
+ argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
+ if (!!delete + !!rename + !!force_create > 1)
+ usage_with_options(builtin_branch_usage, options);
head = resolve_ref("HEAD", head_sha1, 0, NULL);
if (!head)
@@ -599,26 +603,25 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
head = xstrdup(head);
if (!strcmp(head, "HEAD")) {
detached = 1;
- }
- else {
+ } else {
if (prefixcmp(head, "refs/heads/"))
die("HEAD not found below refs/heads!");
head += 11;
}
if (delete)
- return delete_branches(argc - i, argv + i, force_delete, kinds);
- else if (i == argc)
- print_ref_list(kinds, detached, verbose, abbrev);
- else if (rename && (i == argc - 1))
- rename_branch(head, argv[i], force_rename);
- else if (rename && (i == argc - 2))
- rename_branch(argv[i], argv[i + 1], force_rename);
- else if (i == argc - 1 || i == argc - 2)
- create_branch(argv[i], (i == argc - 2) ? argv[i+1] : head,
+ return delete_branches(argc, argv, delete > 1, kinds);
+ else if (argc == 0)
+ print_ref_list(kinds, detached, verbose, abbrev, with_commit);
+ else if (rename && (argc == 1))
+ rename_branch(head, argv[0], rename > 1);
+ else if (rename && (argc == 2))
+ rename_branch(argv[0], argv[1], rename > 1);
+ else if (argc <= 2)
+ create_branch(argv[0], (argc == 2) ? argv[1] : head,
force_create, reflog, track);
else
- usage(builtin_branch_usage);
+ usage_with_options(builtin_branch_usage, options);
return 0;
}
diff --git a/builtin-bundle.c b/builtin-bundle.c
index 1b650069c9..9f38e2176a 100644
--- a/builtin-bundle.c
+++ b/builtin-bundle.c
@@ -1,11 +1,6 @@
#include "builtin.h"
#include "cache.h"
-#include "object.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "list-objects.h"
-#include "run-command.h"
+#include "bundle.h"
/*
* Basic handler for bundle files to connect repositories via sneakernet.
@@ -16,355 +11,6 @@
static const char *bundle_usage="git-bundle (create <bundle> <git-rev-list args> | verify <bundle> | list-heads <bundle> [refname]... | unbundle <bundle> [refname]... )";
-static const char bundle_signature[] = "# v2 git bundle\n";
-
-struct ref_list {
- unsigned int nr, alloc;
- struct ref_list_entry {
- unsigned char sha1[20];
- char *name;
- } *list;
-};
-
-static void add_to_ref_list(const unsigned char *sha1, const char *name,
- struct ref_list *list)
-{
- if (list->nr + 1 >= list->alloc) {
- list->alloc = alloc_nr(list->nr + 1);
- list->list = xrealloc(list->list,
- list->alloc * sizeof(list->list[0]));
- }
- memcpy(list->list[list->nr].sha1, sha1, 20);
- list->list[list->nr].name = xstrdup(name);
- list->nr++;
-}
-
-struct bundle_header {
- struct ref_list prerequisites;
- struct ref_list references;
-};
-
-/* returns an fd */
-static int read_header(const char *path, struct bundle_header *header) {
- char buffer[1024];
- int fd;
- long fpos;
- FILE *ffd = fopen(path, "rb");
-
- if (!ffd)
- return error("could not open '%s'", path);
- if (!fgets(buffer, sizeof(buffer), ffd) ||
- strcmp(buffer, bundle_signature)) {
- fclose(ffd);
- return error("'%s' does not look like a v2 bundle file", path);
- }
- while (fgets(buffer, sizeof(buffer), ffd)
- && buffer[0] != '\n') {
- int is_prereq = buffer[0] == '-';
- int offset = is_prereq ? 1 : 0;
- int len = strlen(buffer);
- unsigned char sha1[20];
- struct ref_list *list = is_prereq ? &header->prerequisites
- : &header->references;
- char delim;
-
- if (buffer[len - 1] == '\n')
- buffer[len - 1] = '\0';
- if (get_sha1_hex(buffer + offset, sha1)) {
- warning("unrecognized header: %s", buffer);
- continue;
- }
- delim = buffer[40 + offset];
- if (!isspace(delim) && (delim != '\0' || !is_prereq))
- die ("invalid header: %s", buffer);
- add_to_ref_list(sha1, isspace(delim) ?
- buffer + 41 + offset : "", list);
- }
- fpos = ftell(ffd);
- fclose(ffd);
- fd = open(path, O_RDONLY);
- if (fd < 0)
- return error("could not open '%s'", path);
- lseek(fd, fpos, SEEK_SET);
- return fd;
-}
-
-static int list_refs(struct ref_list *r, int argc, const char **argv)
-{
- int i;
-
- for (i = 0; i < r->nr; i++) {
- if (argc > 1) {
- int j;
- for (j = 1; j < argc; j++)
- if (!strcmp(r->list[i].name, argv[j]))
- break;
- if (j == argc)
- continue;
- }
- printf("%s %s\n", sha1_to_hex(r->list[i].sha1),
- r->list[i].name);
- }
- return 0;
-}
-
-#define PREREQ_MARK (1u<<16)
-
-static int verify_bundle(struct bundle_header *header, int verbose)
-{
- /*
- * Do fast check, then if any prereqs are missing then go line by line
- * to be verbose about the errors
- */
- struct ref_list *p = &header->prerequisites;
- struct rev_info revs;
- const char *argv[] = {NULL, "--all"};
- struct object_array refs;
- struct commit *commit;
- int i, ret = 0, req_nr;
- const char *message = "Repository lacks these prerequisite commits:";
-
- init_revisions(&revs, NULL);
- for (i = 0; i < p->nr; i++) {
- struct ref_list_entry *e = p->list + i;
- struct object *o = parse_object(e->sha1);
- if (o) {
- o->flags |= PREREQ_MARK;
- add_pending_object(&revs, o, e->name);
- continue;
- }
- if (++ret == 1)
- error(message);
- error("%s %s", sha1_to_hex(e->sha1), e->name);
- }
- if (revs.pending.nr != p->nr)
- return ret;
- req_nr = revs.pending.nr;
- setup_revisions(2, argv, &revs, NULL);
-
- memset(&refs, 0, sizeof(struct object_array));
- for (i = 0; i < revs.pending.nr; i++) {
- struct object_array_entry *e = revs.pending.objects + i;
- add_object_array(e->item, e->name, &refs);
- }
-
- prepare_revision_walk(&revs);
-
- i = req_nr;
- while (i && (commit = get_revision(&revs)))
- if (commit->object.flags & PREREQ_MARK)
- i--;
-
- for (i = 0; i < req_nr; i++)
- if (!(refs.objects[i].item->flags & SHOWN)) {
- if (++ret == 1)
- error(message);
- error("%s %s", sha1_to_hex(refs.objects[i].item->sha1),
- refs.objects[i].name);
- }
-
- for (i = 0; i < refs.nr; i++)
- clear_commit_marks((struct commit *)refs.objects[i].item, -1);
-
- if (verbose) {
- struct ref_list *r;
-
- r = &header->references;
- printf("The bundle contains %d ref%s\n",
- r->nr, (1 < r->nr) ? "s" : "");
- list_refs(r, 0, NULL);
- r = &header->prerequisites;
- printf("The bundle requires these %d ref%s\n",
- r->nr, (1 < r->nr) ? "s" : "");
- list_refs(r, 0, NULL);
- }
- return ret;
-}
-
-static int list_heads(struct bundle_header *header, int argc, const char **argv)
-{
- return list_refs(&header->references, argc, argv);
-}
-
-static int create_bundle(struct bundle_header *header, const char *path,
- int argc, const char **argv)
-{
- static struct lock_file lock;
- 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 *));
- int i, ref_count = 0;
- char buffer[1024];
- struct rev_info revs;
- struct child_process rls;
- FILE *rls_fout;
-
- bundle_to_stdout = !strcmp(path, "-");
- if (bundle_to_stdout)
- bundle_fd = 1;
- else
- bundle_fd = hold_lock_file_for_update(&lock, path, 1);
-
- /* write signature */
- write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature));
-
- /* init revs to list objects for pack-objects later */
- save_commit_buffer = 0;
- init_revisions(&revs, NULL);
-
- /* write prerequisites */
- memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *));
- argv_boundary[0] = "rev-list";
- argv_boundary[1] = "--boundary";
- argv_boundary[2] = "--pretty=oneline";
- argv_boundary[argc + 2] = NULL;
- memset(&rls, 0, sizeof(rls));
- rls.argv = argv_boundary;
- rls.out = -1;
- rls.git_cmd = 1;
- if (start_command(&rls))
- return -1;
- rls_fout = fdopen(rls.out, "r");
- while (fgets(buffer, sizeof(buffer), rls_fout)) {
- unsigned char sha1[20];
- if (buffer[0] == '-') {
- write_or_die(bundle_fd, buffer, strlen(buffer));
- if (!get_sha1_hex(buffer + 1, sha1)) {
- struct object *object = parse_object(sha1);
- object->flags |= UNINTERESTING;
- add_pending_object(&revs, object, buffer);
- }
- } else if (!get_sha1_hex(buffer, sha1)) {
- struct object *object = parse_object(sha1);
- object->flags |= SHOWN;
- }
- }
- fclose(rls_fout);
- if (finish_command(&rls))
- return error("rev-list died");
-
- /* write references */
- argc = setup_revisions(argc, argv, &revs, NULL);
- if (argc > 1)
- return error("unrecognized argument: %s'", argv[1]);
-
- for (i = 0; i < revs.pending.nr; i++) {
- struct object_array_entry *e = revs.pending.objects + i;
- unsigned char sha1[20];
- char *ref;
-
- if (e->item->flags & UNINTERESTING)
- continue;
- if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
- continue;
- /*
- * Make sure the refs we wrote out is correct; --max-count and
- * other limiting options could have prevented all the tips
- * from getting output.
- *
- * Non commit objects such as tags and blobs do not have
- * this issue as they are not affected by those extra
- * constraints.
- */
- if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) {
- warning("ref '%s' is excluded by the rev-list options",
- e->name);
- free(ref);
- continue;
- }
- /*
- * If you run "git bundle create bndl v1.0..v2.0", the
- * name of the positive ref is "v2.0" but that is the
- * commit that is referenced by the tag, and not the tag
- * itself.
- */
- if (hashcmp(sha1, e->item->sha1)) {
- /*
- * Is this the positive end of a range expressed
- * in terms of a tag (e.g. v2.0 from the range
- * "v1.0..v2.0")?
- */
- struct commit *one = lookup_commit_reference(sha1);
- struct object *obj;
-
- if (e->item == &(one->object)) {
- /*
- * Need to include e->name as an
- * independent ref to the pack-objects
- * input, so that the tag is included
- * in the output; otherwise we would
- * end up triggering "empty bundle"
- * error.
- */
- obj = parse_object(sha1);
- obj->flags |= SHOWN;
- add_pending_object(&revs, obj, e->name);
- }
- free(ref);
- continue;
- }
-
- ref_count++;
- write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40);
- write_or_die(bundle_fd, " ", 1);
- write_or_die(bundle_fd, ref, strlen(ref));
- write_or_die(bundle_fd, "\n", 1);
- free(ref);
- }
- if (!ref_count)
- die ("Refusing to create empty bundle.");
-
- /* end header */
- write_or_die(bundle_fd, "\n", 1);
-
- /* write pack */
- argv_pack[0] = "pack-objects";
- argv_pack[1] = "--all-progress";
- argv_pack[2] = "--stdout";
- argv_pack[3] = "--thin";
- argv_pack[4] = NULL;
- memset(&rls, 0, sizeof(rls));
- rls.argv = argv_pack;
- rls.in = -1;
- rls.out = bundle_fd;
- rls.git_cmd = 1;
- if (start_command(&rls))
- return error("Could not spawn pack-objects");
- for (i = 0; i < revs.pending.nr; i++) {
- struct object *object = revs.pending.objects[i].item;
- if (object->flags & UNINTERESTING)
- write(rls.in, "^", 1);
- write(rls.in, sha1_to_hex(object->sha1), 40);
- write(rls.in, "\n", 1);
- }
- if (finish_command(&rls))
- return error ("pack-objects died");
- close(bundle_fd);
- if (!bundle_to_stdout)
- commit_lock_file(&lock);
- return 0;
-}
-
-static int unbundle(struct bundle_header *header, int bundle_fd,
- int argc, const char **argv)
-{
- const char *argv_index_pack[] = {"index-pack",
- "--fix-thin", "--stdin", NULL};
- struct child_process ip;
-
- if (verify_bundle(header, 0))
- return -1;
- memset(&ip, 0, sizeof(ip));
- ip.argv = argv_index_pack;
- ip.in = bundle_fd;
- ip.no_stdout = 1;
- ip.git_cmd = 1;
- if (run_command(&ip))
- return error("index-pack died");
- return list_heads(header, argc, argv);
-}
-
int cmd_bundle(int argc, const char **argv, const char *prefix)
{
struct bundle_header header;
@@ -388,8 +34,8 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
}
memset(&header, 0, sizeof(header));
- if (strcmp(cmd, "create") &&
- (bundle_fd = read_header(bundle_file, &header)) < 0)
+ if (strcmp(cmd, "create") && (bundle_fd =
+ read_bundle_header(bundle_file, &header)) < 0)
return 1;
if (!strcmp(cmd, "verify")) {
@@ -401,7 +47,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
}
if (!strcmp(cmd, "list-heads")) {
close(bundle_fd);
- return !!list_heads(&header, argc, argv);
+ return !!list_bundle_refs(&header, argc, argv);
}
if (!strcmp(cmd, "create")) {
if (nongit)
@@ -410,7 +56,8 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
} else if (!strcmp(cmd, "unbundle")) {
if (nongit)
die("Need a repository to unbundle.");
- return !!unbundle(&header, bundle_fd, argc, argv);
+ return !!unbundle(&header, bundle_fd) ||
+ list_bundle_refs(&header, argc, argv);
} else
usage(bundle_usage);
}
diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c
index 70d619da8d..7e42024c67 100644
--- a/builtin-checkout-index.c
+++ b/builtin-checkout-index.c
@@ -246,8 +246,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
* want to update cache.
*/
if (state.refresh_cache) {
- close(newfd); newfd = -1;
rollback_lock_file(&lock_file);
+ newfd = -1;
}
state.refresh_cache = 0;
}
@@ -297,7 +297,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
if (0 <= newfd &&
(write_cache(newfd, active_cache, active_nr) ||
- close(newfd) || commit_locked_index(&lock_file)))
+ commit_locked_index(&lock_file)))
die("Unable to write new index file");
return 0;
}
diff --git a/builtin-clean.c b/builtin-clean.c
new file mode 100644
index 0000000000..3b220d5060
--- /dev/null
+++ b/builtin-clean.c
@@ -0,0 +1,178 @@
+/*
+ * "git clean" builtin command
+ *
+ * Copyright (C) 2007 Shawn Bohrer
+ *
+ * Based on git-clean.sh by Pavel Roskin
+ */
+
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "parse-options.h"
+
+static int force = -1; /* unset */
+
+static const char *const builtin_clean_usage[] = {
+ "git-clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...",
+ NULL
+};
+
+static int git_clean_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "clean.requireforce"))
+ force = !git_config_bool(var, value);
+ return git_default_config(var, value);
+}
+
+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;
+ struct strbuf directory;
+ struct dir_struct dir;
+ const char *path, *base;
+ static const char **pathspec;
+ int prefix_offset = 0;
+ char *seen = NULL;
+ struct option options[] = {
+ OPT__QUIET(&quiet),
+ OPT__DRY_RUN(&show_only),
+ OPT_BOOLEAN('f', NULL, &force, "force"),
+ OPT_BOOLEAN('d', NULL, &remove_directories,
+ "remove whole directories"),
+ OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
+ OPT_BOOLEAN('X', NULL, &ignored_only,
+ "remove only ignored files"),
+ OPT_END()
+ };
+
+ git_config(git_clean_config);
+ if (force < 0)
+ force = 0;
+ else
+ config_set = 1;
+
+ argc = parse_options(argc, argv, options, builtin_clean_usage, 0);
+
+ memset(&dir, 0, sizeof(dir));
+ if (ignored_only)
+ dir.show_ignored = 1;
+
+ if (ignored && ignored_only)
+ die("-x and -X cannot be used together");
+
+ if (!show_only && !force)
+ die("clean.requireForce%s set and -n or -f not given; "
+ "refusing to clean", config_set ? "" : " not");
+
+ dir.show_other_directories = 1;
+
+ if (!ignored)
+ setup_standard_excludes(&dir);
+
+ if (prefix)
+ prefix_offset = strlen(prefix);
+ pathspec = get_pathspec(prefix, argv);
+ read_cache();
+
+ /*
+ * Calculate common prefix for the pathspec, and
+ * use that to optimize the directory walk
+ */
+ baselen = common_prefix(pathspec);
+ path = ".";
+ base = "";
+ if (baselen)
+ path = base = xmemdupz(*pathspec, baselen);
+ read_directory(&dir, path, base, baselen, pathspec);
+ strbuf_init(&directory, 0);
+
+ if (pathspec)
+ seen = xmalloc(argc > 0 ? argc : 1);
+
+ for (i = 0; i < dir.nr; i++) {
+ struct dir_entry *ent = dir.entries[i];
+ int len, pos, matches;
+ struct cache_entry *ce;
+ struct stat st;
+
+ /*
+ * Remove the '/' at the end that directory
+ * walking adds for directory entries.
+ */
+ len = ent->len;
+ if (len && ent->name[len-1] == '/')
+ len--;
+ pos = cache_name_pos(ent->name, len);
+ if (0 <= pos)
+ continue; /* exact match */
+ pos = -pos - 1;
+ if (pos < active_nr) {
+ ce = active_cache[pos];
+ if (ce_namelen(ce) == len &&
+ !memcmp(ce->name, ent->name, len))
+ continue; /* Yup, this one exists unmerged */
+ }
+
+ /*
+ * we might have removed this as part of earlier
+ * recursive directory removal, so lstat() here could
+ * fail with ENOENT.
+ */
+ if (lstat(ent->name, &st))
+ continue;
+
+ if (pathspec) {
+ memset(seen, 0, argc > 0 ? argc : 1);
+ matches = match_pathspec(pathspec, ent->name, ent->len,
+ baselen, seen);
+ } else {
+ matches = 0;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ strbuf_addstr(&directory, ent->name);
+ if (show_only && (remove_directories || matches)) {
+ printf("Would remove %s\n",
+ directory.buf + prefix_offset);
+ } else if (remove_directories || matches) {
+ if (!quiet)
+ printf("Removing %s\n",
+ directory.buf + prefix_offset);
+ if (remove_dir_recursively(&directory, 0) != 0) {
+ warning("failed to remove '%s'",
+ directory.buf + prefix_offset);
+ errors++;
+ }
+ } else if (show_only) {
+ printf("Would not remove %s\n",
+ directory.buf + prefix_offset);
+ } else {
+ printf("Not removing %s\n",
+ directory.buf + prefix_offset);
+ }
+ strbuf_reset(&directory);
+ } else {
+ if (pathspec && !matches)
+ continue;
+ if (show_only) {
+ printf("Would remove %s\n",
+ ent->name + prefix_offset);
+ continue;
+ } else if (!quiet) {
+ printf("Removing %s\n",
+ ent->name + prefix_offset);
+ }
+ if (unlink(ent->name) != 0) {
+ warning("failed to remove '%s'", ent->name);
+ errors++;
+ }
+ }
+ }
+ free(seen);
+
+ strbuf_release(&directory);
+ return (errors != 0);
+}
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 88b0ab36eb..6610d18358 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -98,8 +98,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
strbuf_addf(&buffer, "parent %s\n", sha1_to_hex(parent_sha1[i]));
/* Person/date information */
- strbuf_addf(&buffer, "author %s\n", git_author_info(1));
- strbuf_addf(&buffer, "committer %s\n", git_committer_info(1));
+ strbuf_addf(&buffer, "author %s\n", git_author_info(IDENT_ERROR_ON_NO_NAME));
+ strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
if (!encoding_is_utf8)
strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
strbuf_addch(&buffer, '\n');
diff --git a/builtin-commit.c b/builtin-commit.c
new file mode 100644
index 0000000000..065e1f7b7f
--- /dev/null
+++ b/builtin-commit.c
@@ -0,0 +1,992 @@
+/*
+ * Builtin "git commit"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ */
+
+#include "cache.h"
+#include "cache-tree.h"
+#include "color.h"
+#include "dir.h"
+#include "builtin.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "commit.h"
+#include "revision.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "refs.h"
+#include "log-tree.h"
+#include "strbuf.h"
+#include "utf8.h"
+#include "parse-options.h"
+#include "path-list.h"
+#include "unpack-trees.h"
+
+static const char * const builtin_commit_usage[] = {
+ "git-commit [options] [--] <filepattern>...",
+ NULL
+};
+
+static const char * const builtin_status_usage[] = {
+ "git-status [options] [--] <filepattern>...",
+ NULL
+};
+
+static unsigned char head_sha1[20], merge_head_sha1[20];
+static 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 */
+static enum {
+ COMMIT_AS_IS = 1,
+ COMMIT_NORMAL,
+ COMMIT_PARTIAL,
+} commit_style;
+
+static char *logfile, *force_author, *template_file;
+static char *edit_message, *use_message;
+static int all, edit_flag, also, interactive, only, amend, signoff;
+static int quiet, verbose, untracked_files, no_verify, allow_empty;
+/*
+ * The default commit message cleanup mode will remove the lines
+ * beginning with # (shell comments) and leading and trailing
+ * whitespaces (empty lines or containing only whitespaces)
+ * if editor is used, and only the whitespaces if the message
+ * is specified explicitly.
+ */
+static enum {
+ CLEANUP_SPACE,
+ CLEANUP_NONE,
+ CLEANUP_ALL,
+} cleanup_mode;
+static char *cleanup_arg;
+
+static int use_editor = 1, initial_commit, in_merge;
+const char *only_include_assumed;
+struct strbuf message;
+
+static int opt_parse_m(const struct option *opt, const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+ if (unset)
+ strbuf_setlen(buf, 0);
+ else {
+ strbuf_addstr(buf, arg);
+ strbuf_addch(buf, '\n');
+ strbuf_addch(buf, '\n');
+ }
+ return 0;
+}
+
+static struct option builtin_commit_options[] = {
+ OPT__QUIET(&quiet),
+ OPT__VERBOSE(&verbose),
+ OPT_GROUP("Commit message options"),
+
+ OPT_STRING('F', "file", &logfile, "FILE", "read log from file"),
+ OPT_STRING(0, "author", &force_author, "AUTHOR", "override author 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_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by: header"),
+ OPT_STRING('t', "template", &template_file, "FILE", "use specified template file"),
+ OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+
+ OPT_GROUP("Commit contents 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('o', "only", &only, ""),
+ OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
+ OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+ OPT_BOOLEAN(0, "untracked-files", &untracked_files, "show all untracked files"),
+ OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
+ OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+
+ OPT_END()
+};
+
+static void rollback_index_files(void)
+{
+ switch (commit_style) {
+ case COMMIT_AS_IS:
+ break; /* nothing to do */
+ case COMMIT_NORMAL:
+ rollback_lock_file(&index_lock);
+ break;
+ case COMMIT_PARTIAL:
+ rollback_lock_file(&index_lock);
+ rollback_lock_file(&false_lock);
+ break;
+ }
+}
+
+static int commit_index_files(void)
+{
+ int err = 0;
+
+ switch (commit_style) {
+ case COMMIT_AS_IS:
+ break; /* nothing to do */
+ case COMMIT_NORMAL:
+ err = commit_lock_file(&index_lock);
+ break;
+ case COMMIT_PARTIAL:
+ err = commit_lock_file(&index_lock);
+ rollback_lock_file(&false_lock);
+ break;
+ }
+
+ return err;
+}
+
+/*
+ * Take a union of paths in the index and the named tree (typically, "HEAD"),
+ * and return the paths that match the given pattern in list.
+ */
+static int list_paths(struct path_list *list, const char *with_tree,
+ const char *prefix, const char **pattern)
+{
+ int i;
+ char *m;
+
+ for (i = 0; pattern[i]; i++)
+ ;
+ m = xcalloc(1, i);
+
+ if (with_tree)
+ overlay_tree_on_cache(with_tree, prefix);
+
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ce->ce_flags & CE_UPDATE)
+ continue;
+ if (!pathspec_match(pattern, m, ce->name, 0))
+ continue;
+ path_list_insert(ce->name, list);
+ }
+
+ return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
+}
+
+static void add_remove_files(struct path_list *list)
+{
+ int i;
+ for (i = 0; i < list->nr; i++) {
+ struct path_list_item *p = &(list->items[i]);
+ if (file_exists(p->path))
+ add_file_to_cache(p->path, 0);
+ else
+ remove_file_from_cache(p->path);
+ }
+}
+
+static void create_base_index(void)
+{
+ struct tree *tree;
+ struct unpack_trees_options opts;
+ struct tree_desc t;
+
+ if (initial_commit) {
+ discard_cache();
+ return;
+ }
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.index_only = 1;
+ opts.merge = 1;
+
+ opts.fn = oneway_merge;
+ tree = parse_tree_indirect(head_sha1);
+ if (!tree)
+ die("failed to unpack HEAD tree object");
+ parse_tree(tree);
+ init_tree_desc(&t, tree->buffer, tree->size);
+ unpack_trees(1, &t, &opts);
+}
+
+static char *prepare_index(int argc, const char **argv, const char *prefix)
+{
+ int fd;
+ struct path_list partial;
+ const char **pathspec = NULL;
+
+ if (interactive) {
+ interactive_add(argc, argv, prefix);
+ commit_style = COMMIT_AS_IS;
+ return get_index_file();
+ }
+
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ if (*argv)
+ pathspec = get_pathspec(prefix, argv);
+
+ /*
+ * Non partial, non as-is commit.
+ *
+ * (1) get the real index;
+ * (2) update the_index as necessary;
+ * (3) write the_index out to the real index (still locked);
+ * (4) return the name of the locked index file.
+ *
+ * The caller should run hooks on the locked real index, and
+ * (A) if all goes well, commit the real index;
+ * (B) on failure, rollback the real index.
+ */
+ if (all || (also && pathspec && *pathspec)) {
+ int fd = hold_locked_index(&index_lock, 1);
+ add_files_to_cache(0, also ? prefix : NULL, pathspec);
+ refresh_cache(REFRESH_QUIET);
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&index_lock))
+ die("unable to write new_index file");
+ commit_style = COMMIT_NORMAL;
+ return index_lock.filename;
+ }
+
+ /*
+ * As-is commit.
+ *
+ * (1) return the name of the real index file.
+ *
+ * The caller should run hooks on the real index, and run
+ * hooks on the real index, and create commit from the_index.
+ * We still need to refresh the index here.
+ */
+ if (!pathspec || !*pathspec) {
+ fd = hold_locked_index(&index_lock, 1);
+ refresh_cache(REFRESH_QUIET);
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(&index_lock))
+ die("unable to write new_index file");
+ commit_style = COMMIT_AS_IS;
+ return get_index_file();
+ }
+
+ /*
+ * A partial commit.
+ *
+ * (0) find the set of affected paths;
+ * (1) get lock on the real index file;
+ * (2) update the_index with the given paths;
+ * (3) write the_index out to the real index (still locked);
+ * (4) get lock on the false index file;
+ * (5) reset the_index from HEAD;
+ * (6) update the_index the same way as (2);
+ * (7) write the_index out to the false index file;
+ * (8) return the name of the false index file (still locked);
+ *
+ * The caller should run hooks on the locked false index, and
+ * create commit from it. Then
+ * (A) if all goes well, commit the real index;
+ * (B) on failure, rollback the real index;
+ * In either case, rollback the false index.
+ */
+ commit_style = COMMIT_PARTIAL;
+
+ if (file_exists(git_path("MERGE_HEAD")))
+ die("cannot do a partial commit during a merge.");
+
+ memset(&partial, 0, sizeof(partial));
+ partial.strdup_paths = 1;
+ if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
+ exit(1);
+
+ discard_cache();
+ if (read_cache() < 0)
+ 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");
+
+ fd = hold_lock_file_for_update(&false_lock,
+ git_path("next-index-%d", getpid()), 1);
+
+ create_base_index();
+ add_remove_files(&partial);
+ refresh_cache(REFRESH_QUIET);
+
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&false_lock))
+ die("unable to write temporary index file");
+
+ discard_cache();
+ read_cache_from(false_lock.filename);
+
+ return false_lock.filename;
+}
+
+static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn)
+{
+ struct wt_status s;
+
+ wt_status_prepare(&s);
+ if (wt_status_relative_paths)
+ s.prefix = prefix;
+
+ if (amend) {
+ s.amend = 1;
+ s.reference = "HEAD^1";
+ }
+ s.verbose = verbose;
+ s.untracked = untracked_files;
+ s.index_file = index_file;
+ s.fp = fp;
+ s.nowarn = nowarn;
+
+ wt_status_print(&s);
+
+ return s.commitable;
+}
+
+static int run_hook(const char *index_file, const char *name, ...)
+{
+ struct child_process hook;
+ const char *argv[10], *env[2];
+ char index[PATH_MAX];
+ va_list args;
+ int i;
+
+ va_start(args, name);
+ argv[0] = git_path("hooks/%s", name);
+ i = 0;
+ do {
+ if (++i >= ARRAY_SIZE(argv))
+ die ("run_hook(): too many arguments");
+ argv[i] = va_arg(args, const char *);
+ } while (argv[i]);
+ va_end(args);
+
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+ env[0] = index;
+ env[1] = NULL;
+
+ if (access(argv[0], X_OK) < 0)
+ return 0;
+
+ memset(&hook, 0, sizeof(hook));
+ hook.argv = argv;
+ hook.no_stdin = 1;
+ hook.stdout_to_stderr = 1;
+ hook.env = env;
+
+ return run_command(&hook);
+}
+
+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");
+ return !!(commit->parents && commit->parents->next);
+}
+
+static const char sign_off_header[] = "Signed-off-by: ";
+
+static int prepare_to_commit(const char *index_file, const char *prefix)
+{
+ struct stat statbuf;
+ int commitable, saved_color_setting;
+ struct strbuf sb;
+ char *buffer;
+ FILE *fp;
+ const char *hook_arg1 = NULL;
+ const char *hook_arg2 = NULL;
+
+ if (!no_verify && run_hook(index_file, "pre-commit", NULL))
+ return 0;
+
+ strbuf_init(&sb, 0);
+ 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");
+ if (strbuf_read(&sb, 0, 0) < 0)
+ die("could not read log from standard input");
+ hook_arg1 = "message";
+ } else if (logfile) {
+ if (strbuf_read_file(&sb, logfile, 0) < 0)
+ die("could not read log file '%s': %s",
+ logfile, strerror(errno));
+ hook_arg1 = "message";
+ } else if (use_message) {
+ buffer = strstr(use_message_buffer, "\n\n");
+ if (!buffer || buffer[2] == '\0')
+ die("commit has empty message");
+ strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+ hook_arg1 = "commit";
+ hook_arg2 = use_message;
+ } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
+ die("could not read MERGE_MSG: %s", strerror(errno));
+ hook_arg1 = "merge";
+ } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
+ die("could not read SQUASH_MSG: %s", strerror(errno));
+ hook_arg1 = "squash";
+ } else if (template_file && !stat(template_file, &statbuf)) {
+ if (strbuf_read_file(&sb, template_file, 0) < 0)
+ die("could not read %s: %s",
+ template_file, strerror(errno));
+ hook_arg1 = "template";
+ }
+
+ /*
+ * This final case does not modify the template message,
+ * it just sets the argument to the prepare-commit-msg hook.
+ */
+ else if (in_merge)
+ hook_arg1 = "merge";
+
+ fp = fopen(git_path(commit_editmsg), "w");
+ if (fp == NULL)
+ die("could not open %s", git_path(commit_editmsg));
+
+ if (cleanup_mode != CLEANUP_NONE)
+ stripspace(&sb, 0);
+
+ if (signoff) {
+ struct strbuf sob;
+ int i;
+
+ strbuf_init(&sob, 0);
+ strbuf_addstr(&sob, sign_off_header);
+ strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL")));
+ strbuf_addch(&sob, '\n');
+ for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
+ ; /* do nothing */
+ if (prefixcmp(sb.buf + i, sob.buf)) {
+ if (prefixcmp(sb.buf + i, sign_off_header))
+ strbuf_addch(&sb, '\n');
+ strbuf_addbuf(&sb, &sob);
+ }
+ strbuf_release(&sob);
+ }
+
+ if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
+ die("could not write commit template: %s", strerror(errno));
+
+ strbuf_release(&sb);
+
+ if (use_editor) {
+ 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.\n"
+ "# (Comment lines starting with '#' will ");
+ if (cleanup_mode == CLEANUP_ALL)
+ fprintf(fp, "not be included)\n");
+ else /* CLEANUP_SPACE, that is. */
+ fprintf(fp, "be kept.\n"
+ "# You can remove them yourself if you want to)\n");
+ if (only_include_assumed)
+ fprintf(fp, "# %s\n", only_include_assumed);
+
+ saved_color_setting = wt_status_use_color;
+ wt_status_use_color = 0;
+ commitable = run_status(fp, index_file, prefix, 1);
+ wt_status_use_color = saved_color_setting;
+ } else {
+ struct rev_info rev;
+ unsigned char sha1[20];
+ const char *parent = "HEAD";
+
+ if (!active_nr && read_cache() < 0)
+ die("Cannot read index");
+
+ if (amend)
+ parent = "HEAD^1";
+
+ if (get_sha1(parent, sha1))
+ commitable = !!active_nr;
+ else {
+ init_revisions(&rev, "");
+ rev.abbrev = 0;
+ setup_revisions(0, NULL, &rev, parent);
+ DIFF_OPT_SET(&rev.diffopt, QUIET);
+ DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
+ run_diff_index(&rev, 1 /* cached */);
+
+ commitable = !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
+ }
+ }
+
+ fclose(fp);
+
+ if (!commitable && !in_merge && !allow_empty &&
+ !(amend && is_a_merge(head_sha1))) {
+ run_status(stdout, index_file, prefix, 0);
+ unlink(commit_editmsg);
+ return 0;
+ }
+
+ /*
+ * Re-read the index as pre-commit hook could have updated it,
+ * and write it out as a tree. We must do this before we invoke
+ * the editor and after we invoke run_status above.
+ */
+ discard_cache();
+ read_cache_from(index_file);
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+ if (cache_tree_update(active_cache_tree,
+ active_cache, active_nr, 0, 0) < 0) {
+ error("Error building trees");
+ return 0;
+ }
+
+ if (run_hook(index_file, "prepare-commit-msg",
+ git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
+ return 0;
+
+ if (use_editor) {
+ char index[PATH_MAX];
+ const char *env[2] = { index, NULL };
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+ launch_editor(git_path(commit_editmsg), NULL, env);
+ }
+
+ if (!no_verify &&
+ run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Find out if the message starting at position 'start' in the strbuf
+ * contains only whitespace and Signed-off-by lines.
+ */
+static int message_is_empty(struct strbuf *sb, int start)
+{
+ struct strbuf tmpl;
+ const char *nl;
+ int eol, i;
+
+ if (cleanup_mode == CLEANUP_NONE && sb->len)
+ return 0;
+
+ /* See if the template is just a prefix of the message. */
+ strbuf_init(&tmpl, 0);
+ if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
+ stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
+ if (start + tmpl.len <= sb->len &&
+ memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
+ start += tmpl.len;
+ }
+ strbuf_release(&tmpl);
+
+ /* Check if the rest is just whitespace and Signed-of-by's. */
+ for (i = start; i < sb->len; i++) {
+ nl = memchr(sb->buf + i, '\n', sb->len - i);
+ if (nl)
+ eol = nl - sb->buf;
+ else
+ eol = sb->len;
+
+ if (strlen(sign_off_header) <= eol - i &&
+ !prefixcmp(sb->buf + i, sign_off_header)) {
+ i = eol;
+ continue;
+ }
+ while (i < eol)
+ if (!isspace(sb->buf[i++]))
+ return 0;
+ }
+
+ return 1;
+}
+
+static void determine_author_info(struct strbuf *sb)
+{
+ char *name, *email, *date;
+
+ name = getenv("GIT_AUTHOR_NAME");
+ email = getenv("GIT_AUTHOR_EMAIL");
+ date = getenv("GIT_AUTHOR_DATE");
+
+ if (use_message) {
+ const char *a, *lb, *rb, *eol;
+
+ a = strstr(use_message_buffer, "\nauthor ");
+ if (!a)
+ die("invalid commit: %s", use_message);
+
+ lb = strstr(a + 8, " <");
+ rb = strstr(a + 8, "> ");
+ eol = strchr(a + 8, '\n');
+ if (!lb || !rb || !eol)
+ die("invalid commit: %s", use_message);
+
+ name = xstrndup(a + 8, lb - (a + 8));
+ email = xstrndup(lb + 2, rb - (lb + 2));
+ date = xstrndup(rb + 2, eol - (rb + 2));
+ }
+
+ if (force_author) {
+ const char *lb = strstr(force_author, " <");
+ const char *rb = strchr(force_author, '>');
+
+ if (!lb || !rb)
+ die("malformed --author parameter");
+ name = xstrndup(force_author, lb - force_author);
+ email = xstrndup(lb + 2, rb - (lb + 2));
+ }
+
+ strbuf_addf(sb, "author %s\n", fmt_ident(name, email, date, IDENT_ERROR_ON_NO_NAME));
+}
+
+static int parse_and_validate_options(int argc, const char *argv[],
+ const char * const usage[])
+{
+ int f = 0;
+
+ argc = parse_options(argc, argv, builtin_commit_options, usage, 0);
+
+ if (logfile || message.len || use_message)
+ use_editor = 0;
+ if (edit_flag)
+ use_editor = 1;
+ if (!use_editor)
+ setenv("GIT_EDITOR", ":", 1);
+
+ if (get_sha1("HEAD", head_sha1))
+ initial_commit = 1;
+
+ if (!get_sha1("MERGE_HEAD", merge_head_sha1))
+ in_merge = 1;
+
+ /* 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.");
+
+ if (use_message)
+ f++;
+ if (edit_message)
+ f++;
+ if (logfile)
+ f++;
+ if (f > 1)
+ die("Only one of -c/-C/-F can be used.");
+ if (message.len && f > 0)
+ die("Option -m cannot be combined with -c/-C/-F.");
+ if (edit_message)
+ use_message = edit_message;
+ if (amend && !use_message)
+ use_message = "HEAD";
+ 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;
+ }
+ 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 (!!also + !!only + !!all + !!interactive > 1)
+ die("Only one of --include/--only/--all/--interactive can be used.");
+ if (argc == 0 && (also || (only && !amend)))
+ 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.";
+ if (argc > 0 && !also && !only) {
+ only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
+ also = 0;
+ }
+ if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
+ cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
+ else if (!strcmp(cleanup_arg, "verbatim"))
+ cleanup_mode = CLEANUP_NONE;
+ else if (!strcmp(cleanup_arg, "whitespace"))
+ cleanup_mode = CLEANUP_SPACE;
+ else if (!strcmp(cleanup_arg, "strip"))
+ cleanup_mode = CLEANUP_ALL;
+ else
+ die("Invalid cleanup mode %s", cleanup_arg);
+
+ 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.");
+
+ return argc;
+}
+
+int cmd_status(int argc, const char **argv, const char *prefix)
+{
+ const char *index_file;
+ int commitable;
+
+ git_config(git_status_config);
+
+ if (wt_status_use_color == -1)
+ wt_status_use_color = git_use_color_default;
+
+ argc = parse_and_validate_options(argc, argv, builtin_status_usage);
+
+ index_file = prepare_index(argc, argv, prefix);
+
+ commitable = run_status(stdout, index_file, prefix, 0);
+
+ rollback_index_files();
+
+ return commitable ? 0 : 1;
+}
+
+static void print_summary(const char *prefix, const unsigned char *sha1)
+{
+ struct rev_info rev;
+ struct commit *commit;
+
+ commit = lookup_commit(sha1);
+ if (!commit)
+ die("couldn't look up newly created commit");
+ if (!commit || parse_commit(commit))
+ die("could not parse newly created commit");
+
+ 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;
+
+ rev.verbose_header = 1;
+ rev.show_root_diff = 1;
+ rev.commit_format = get_commit_format("format:%h: %s");
+ 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);
+
+ printf("Created %scommit ", initial_commit ? "initial " : "");
+
+ if (!log_tree_commit(&rev, commit)) {
+ struct strbuf buf = STRBUF_INIT;
+ format_commit_message(commit, "%h: %s", &buf);
+ printf("%s\n", buf.buf);
+ strbuf_release(&buf);
+ }
+}
+
+int git_commit_config(const char *k, const char *v)
+{
+ if (!strcmp(k, "commit.template")) {
+ if (!v)
+ return config_error_nonbool(v);
+ template_file = xstrdup(v);
+ return 0;
+ }
+
+ return git_status_config(k, v);
+}
+
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
+static void add_parent(struct strbuf *sb, const unsigned char *sha1)
+{
+ struct object *obj = parse_object(sha1);
+ const char *parent = sha1_to_hex(sha1);
+ if (!obj)
+ die("Unable to find commit parent %s", parent);
+ if (obj->type != OBJ_COMMIT)
+ die("Parent %s isn't a proper commit", parent);
+ strbuf_addf(sb, "parent %s\n", parent);
+}
+
+int cmd_commit(int argc, const char **argv, const char *prefix)
+{
+ int header_len;
+ struct strbuf sb;
+ const char *index_file, *reflog_msg;
+ char *nl, *p;
+ unsigned char commit_sha1[20];
+ struct ref_lock *ref_lock;
+
+ git_config(git_commit_config);
+
+ argc = parse_and_validate_options(argc, argv, builtin_commit_usage);
+
+ index_file = prepare_index(argc, argv, 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)) {
+ rollback_index_files();
+ return 1;
+ }
+
+ /*
+ * The commit object
+ */
+ strbuf_init(&sb, 0);
+ strbuf_addf(&sb, "tree %s\n",
+ sha1_to_hex(active_cache_tree->sha1));
+
+ /* Determine parents */
+ if (initial_commit) {
+ reflog_msg = "commit (initial)";
+ } else if (amend) {
+ struct commit_list *c;
+ struct commit *commit;
+
+ reflog_msg = "commit (amend)";
+ commit = lookup_commit(head_sha1);
+ if (!commit || parse_commit(commit))
+ die("could not parse HEAD commit");
+
+ for (c = commit->parents; c; c = c->next)
+ add_parent(&sb, c->item->object.sha1);
+ } else if (in_merge) {
+ struct strbuf m;
+ FILE *fp;
+
+ reflog_msg = "commit (merge)";
+ add_parent(&sb, head_sha1);
+ strbuf_init(&m, 0);
+ fp = fopen(git_path("MERGE_HEAD"), "r");
+ if (fp == NULL)
+ die("could not open %s for reading: %s",
+ git_path("MERGE_HEAD"), strerror(errno));
+ 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);
+ add_parent(&sb, sha1);
+ }
+ fclose(fp);
+ strbuf_release(&m);
+ } else {
+ reflog_msg = "commit";
+ strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+ }
+
+ determine_author_info(&sb);
+ strbuf_addf(&sb, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
+ if (!is_encoding_utf8(git_commit_encoding))
+ strbuf_addf(&sb, "encoding %s\n", git_commit_encoding);
+ strbuf_addch(&sb, '\n');
+
+ /* Finally, get the commit message */
+ header_len = sb.len;
+ if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
+ rollback_index_files();
+ die("could not read commit message");
+ }
+
+ /* Truncate the message just before the diff, if any. */
+ p = strstr(sb.buf, "\ndiff --git a/");
+ if (p != NULL)
+ strbuf_setlen(&sb, p - sb.buf + 1);
+
+ if (cleanup_mode != CLEANUP_NONE)
+ stripspace(&sb, cleanup_mode == CLEANUP_ALL);
+ if (sb.len < header_len || message_is_empty(&sb, header_len)) {
+ rollback_index_files();
+ die("no commit message? aborting commit.");
+ }
+ strbuf_addch(&sb, '\0');
+ if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))
+ fprintf(stderr, commit_utf8_warn);
+
+ if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1)) {
+ rollback_index_files();
+ die("failed to write commit object");
+ }
+
+ ref_lock = lock_any_ref_for_update("HEAD",
+ initial_commit ? NULL : head_sha1,
+ 0);
+
+ nl = strchr(sb.buf + header_len, '\n');
+ if (nl)
+ strbuf_setlen(&sb, nl + 1 - sb.buf);
+ else
+ strbuf_addch(&sb, '\n');
+ strbuf_remove(&sb, 0, header_len);
+ strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
+ strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
+
+ if (!ref_lock) {
+ rollback_index_files();
+ die("cannot lock HEAD ref");
+ }
+ if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+ rollback_index_files();
+ die("cannot update HEAD ref");
+ }
+
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+ unlink(git_path("SQUASH_MSG"));
+
+ if (commit_index_files())
+ 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.");
+
+ rerere();
+ run_hook(get_index_file(), "post-commit", NULL);
+ if (!quiet)
+ print_summary(prefix, commit_sha1);
+
+ return 0;
+}
diff --git a/builtin-config.c b/builtin-config.c
index d98b6c2c4c..2b9a4261d4 100644
--- a/builtin-config.c
+++ b/builtin-config.c
@@ -1,8 +1,9 @@
#include "builtin.h"
#include "cache.h"
+#include "color.h"
static const char git_config_set_usage[] =
-"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
+"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty]";
static char *key;
static regex_t *key_regexp;
@@ -37,8 +38,7 @@ static int show_config(const char* key_, const char* value_)
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
return 0;
if (regexp != NULL &&
- (do_not_match ^
- regexec(regexp, (value_?value_:""), 0, NULL, 0)))
+ (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
return 0;
if (show_keys) {
@@ -79,9 +79,10 @@ static int get_value(const char* key_, const char* regex_)
local = getenv(CONFIG_LOCAL_ENVIRONMENT);
if (!local)
local = repo_config = xstrdup(git_path("config"));
- if (home)
+ if (git_config_global() && home)
global = xstrdup(mkpath("%s/.gitconfig", home));
- system_wide = ETC_GITCONFIG;
+ if (git_config_system())
+ system_wide = git_etc_gitconfig();
}
key = xstrdup(key_);
@@ -161,6 +162,106 @@ char *normalize_value(const char *key, const char *value)
return normalized;
}
+static int get_color_found;
+static const char *get_color_slot;
+static char parsed_color[COLOR_MAXLEN];
+
+static int git_get_color_config(const char *var, const char *value)
+{
+ if (!strcmp(var, get_color_slot)) {
+ if (!value)
+ config_error_nonbool(var);
+ color_parse(value, var, parsed_color);
+ get_color_found = 1;
+ }
+ return 0;
+}
+
+static int get_color(int argc, const char **argv)
+{
+ /*
+ * grab the color setting for the given slot from the configuration,
+ * or parse the default value if missing, and return ANSI color
+ * escape sequence.
+ *
+ * e.g.
+ * git config --get-color color.diff.whitespace "blue reverse"
+ */
+ const char *def_color = NULL;
+
+ switch (argc) {
+ default:
+ usage(git_config_set_usage);
+ case 2:
+ def_color = argv[1];
+ /* fallthru */
+ case 1:
+ get_color_slot = argv[0];
+ break;
+ }
+
+ get_color_found = 0;
+ parsed_color[0] = '\0';
+ git_config(git_get_color_config);
+
+ if (!get_color_found && def_color)
+ color_parse(def_color, "command line", parsed_color);
+
+ fputs(parsed_color, stdout);
+ return 0;
+}
+
+static int stdout_is_tty;
+static int get_colorbool_found;
+static int get_diff_color_found;
+static int git_get_colorbool_config(const char *var, const char *value)
+{
+ if (!strcmp(var, get_color_slot)) {
+ get_colorbool_found =
+ git_config_colorbool(var, value, stdout_is_tty);
+ }
+ if (!strcmp(var, "diff.color")) {
+ get_diff_color_found =
+ git_config_colorbool(var, value, stdout_is_tty);
+ }
+ return 0;
+}
+
+static int get_colorbool(int argc, const char **argv)
+{
+ /*
+ * git config --get-colorbool <slot> [<stdout-is-tty>]
+ *
+ * returns "true" or "false" depending on how <slot>
+ * is configured.
+ */
+
+ if (argc == 2)
+ stdout_is_tty = git_config_bool("command line", argv[1]);
+ else if (argc == 1)
+ stdout_is_tty = isatty(1);
+ else
+ usage(git_config_set_usage);
+ get_colorbool_found = -1;
+ get_diff_color_found = -1;
+ get_color_slot = argv[0];
+ git_config(git_get_colorbool_config);
+
+ if (get_colorbool_found < 0) {
+ if (!strcmp(get_color_slot, "color.diff"))
+ get_colorbool_found = get_diff_color_found;
+ if (get_colorbool_found < 0)
+ get_colorbool_found = 0;
+ }
+
+ if (argc == 1) {
+ return get_colorbool_found ? 0 : 1;
+ } else {
+ printf("%s\n", get_colorbool_found ? "true" : "false");
+ return 0;
+ }
+}
+
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = 0;
@@ -175,7 +276,10 @@ int cmd_config(int argc, const char **argv, const char *prefix)
else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) {
if (argc != 2)
usage(git_config_set_usage);
- return git_config(show_all_config);
+ if (git_config(show_all_config) < 0 && file && errno)
+ die("unable to read config file %s: %s", file,
+ strerror(errno));
+ return 0;
}
else if (!strcmp(argv[1], "--global")) {
char *home = getenv("HOME");
@@ -188,7 +292,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
}
else if (!strcmp(argv[1], "--system"))
- setenv(CONFIG_ENVIRONMENT, ETC_GITCONFIG, 1);
+ setenv(CONFIG_ENVIRONMENT, git_etc_gitconfig(), 1);
else if (!strcmp(argv[1], "--file") || !strcmp(argv[1], "-f")) {
if (argc < 3)
usage(git_config_set_usage);
@@ -231,8 +335,11 @@ int cmd_config(int argc, const char **argv, const char *prefix)
return 1;
}
return 0;
- }
- else
+ } else if (!strcmp(argv[1], "--get-color")) {
+ return get_color(argc-2, argv+2);
+ } else if (!strcmp(argv[1], "--get-colorbool")) {
+ return get_colorbool(argc-2, argv+2);
+ } else
break;
argc--;
argv++;
diff --git a/builtin-count-objects.c b/builtin-count-objects.c
index 4274ec1950..f00306fb67 100644
--- a/builtin-count-objects.c
+++ b/builtin-count-objects.c
@@ -6,8 +6,7 @@
#include "cache.h"
#include "builtin.h"
-
-static const char count_objects_usage[] = "git-count-objects [-v]";
+#include "parse-options.h"
static void count_objects(DIR *d, char *path, int len, int verbose,
unsigned long *loose,
@@ -67,29 +66,28 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
}
}
-int cmd_count_objects(int ac, const char **av, const char *prefix)
+static char const * const count_objects_usage[] = {
+ "git-count-objects [-v]",
+ NULL
+};
+
+int cmd_count_objects(int argc, const char **argv, const char *prefix)
{
- int i;
- int verbose = 0;
+ int i, verbose = 0;
const char *objdir = get_object_directory();
int len = strlen(objdir);
char *path = xmalloc(len + 50);
unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
unsigned long loose_size = 0;
+ struct option opts[] = {
+ OPT__VERBOSE(&verbose),
+ OPT_END(),
+ };
- for (i = 1; i < ac; i++) {
- const char *arg = av[i];
- if (*arg != '-')
- break;
- else if (!strcmp(arg, "-v"))
- verbose = 1;
- else
- usage(count_objects_usage);
- }
-
+ argc = parse_options(argc, argv, opts, count_objects_usage, 0);
/* we do not take arguments other than flags for now */
- if (i < ac)
- usage(count_objects_usage);
+ if (argc)
+ usage_with_options(count_objects_usage, opts);
memcpy(path, objdir, len);
if (len && objdir[len-1] != '/')
path[len++] = '/';
diff --git a/builtin-describe.c b/builtin-describe.c
index 669110cb06..3428483134 100644
--- a/builtin-describe.c
+++ b/builtin-describe.c
@@ -4,18 +4,22 @@
#include "refs.h"
#include "builtin.h"
#include "exec_cmd.h"
+#include "parse-options.h"
#define SEEN (1u<<0)
#define MAX_TAGS (FLAG_BITS - 1)
-static const char describe_usage[] =
-"git-describe [--all] [--tags] [--abbrev=<n>] <committish>*";
+static const char * const describe_usage[] = {
+ "git-describe [options] <committish>*",
+ NULL
+};
static int debug; /* Display lots of verbose info */
static int all; /* Default to annotated tags only */
static int tags; /* But allow any tags if --tags is specified */
static int abbrev = DEFAULT_ABBREV;
static int max_candidates = 10;
+const char *pattern = NULL;
struct commit_name {
int prio; /* annotated tag = 2, tag = 1, head = 0 */
@@ -54,9 +58,11 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
* Otherwise only annotated tags are used.
*/
if (!prefixcmp(path, "refs/tags/")) {
- if (object->type == OBJ_TAG)
+ if (object->type == OBJ_TAG) {
prio = 2;
- else
+ if (pattern && fnmatch(pattern, path + 10, 0))
+ prio = 0;
+ } else
prio = 1;
}
else
@@ -242,57 +248,53 @@ static void describe(const char *arg, int last_one)
int cmd_describe(int argc, const char **argv, const char *prefix)
{
- int i;
int contains = 0;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"),
+ OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"),
+ OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"),
+ OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"),
+ OPT__ABBREV(&abbrev),
+ OPT_INTEGER(0, "candidates", &max_candidates,
+ "consider <n> most recent tags (default: 10)"),
+ OPT_STRING(0, "match", &pattern, "pattern",
+ "only consider tags matching <pattern>"),
+ OPT_END(),
+ };
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (*arg != '-')
- break;
- else if (!strcmp(arg, "--contains"))
- contains = 1;
- else if (!strcmp(arg, "--debug"))
- debug = 1;
- else if (!strcmp(arg, "--all"))
- all = 1;
- else if (!strcmp(arg, "--tags"))
- tags = 1;
- else if (!prefixcmp(arg, "--abbrev=")) {
- abbrev = strtoul(arg + 9, NULL, 10);
- if (abbrev != 0 && (abbrev < MINIMUM_ABBREV || 40 < abbrev))
- abbrev = DEFAULT_ABBREV;
- }
- else if (!prefixcmp(arg, "--candidates=")) {
- max_candidates = strtoul(arg + 13, NULL, 10);
- if (max_candidates < 1)
- max_candidates = 1;
- else if (max_candidates > MAX_TAGS)
- max_candidates = MAX_TAGS;
- }
- else
- usage(describe_usage);
- }
+ argc = parse_options(argc, argv, options, describe_usage, 0);
+ if (max_candidates < 1)
+ max_candidates = 1;
+ else if (max_candidates > MAX_TAGS)
+ max_candidates = MAX_TAGS;
save_commit_buffer = 0;
if (contains) {
- const char **args = xmalloc((4 + argc - i) * sizeof(char*));
- args[0] = "name-rev";
- args[1] = "--name-only";
- args[2] = "--tags";
- memcpy(args + 3, argv + i, (argc - i) * sizeof(char*));
- args[3 + argc - i] = NULL;
- return cmd_name_rev(3 + argc - i, args, prefix);
+ const char **args = xmalloc((6 + argc) * sizeof(char*));
+ int i = 0;
+ args[i++] = "name-rev";
+ args[i++] = "--name-only";
+ args[i++] = "--no-undefined";
+ if (!all) {
+ args[i++] = "--tags";
+ if (pattern) {
+ char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
+ sprintf(s, "--refs=refs/tags/%s", pattern);
+ args[i++] = s;
+ }
+ }
+ memcpy(args + i, argv, argc * sizeof(char*));
+ args[i + argc] = NULL;
+ return cmd_name_rev(i + argc, args, prefix);
}
- if (argc <= i)
+ if (argc == 0) {
describe("HEAD", 1);
- else
- while (i < argc) {
- describe(argv[i], (i == argc - 1));
- i++;
+ } else {
+ while (argc-- > 0) {
+ describe(*argv++, argc == 0);
}
-
+ }
return 0;
}
diff --git a/builtin-diff-files.c b/builtin-diff-files.c
index 6cb30c8e12..4abe3c28fb 100644
--- a/builtin-diff-files.c
+++ b/builtin-diff-files.c
@@ -21,7 +21,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
prefix = setup_git_directory_gently(&nongit);
init_revisions(&rev, prefix);
- git_config(git_default_config); /* no "diff" UI options */
+ git_config(git_diff_basic_config); /* no "diff" UI options */
rev.abbrev = 0;
if (!setup_diff_no_index(&rev, argc, argv, nongit, prefix))
@@ -31,5 +31,5 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_RAW;
result = run_diff_files_cmd(&rev, argc, argv);
- return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result;
+ return diff_result_code(&rev.diffopt, result);
}
diff --git a/builtin-diff-index.c b/builtin-diff-index.c
index 81e7167438..2b955deb91 100644
--- a/builtin-diff-index.c
+++ b/builtin-diff-index.c
@@ -17,7 +17,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
int result;
init_revisions(&rev, prefix);
- git_config(git_default_config); /* no "diff" UI options */
+ git_config(git_diff_basic_config); /* no "diff" UI options */
rev.abbrev = 0;
argc = setup_revisions(argc, argv, &rev, NULL);
@@ -44,5 +44,5 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
return -1;
}
result = run_diff_index(&rev, cached);
- return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result;
+ return diff_result_code(&rev.diffopt, result);
}
diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c
index 0b591c8716..832797ff3b 100644
--- a/builtin-diff-tree.c
+++ b/builtin-diff-tree.c
@@ -68,7 +68,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
int read_stdin = 0;
init_revisions(opt, prefix);
- git_config(git_default_config); /* no "diff" UI options */
+ git_config(git_diff_basic_config); /* no "diff" UI options */
nr_sha1 = 0;
opt->abbrev = 0;
opt->diff = 1;
@@ -117,22 +117,21 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
break;
}
- if (!read_stdin)
- return opt->diffopt.exit_with_status ?
- opt->diffopt.has_changes: 0;
+ if (read_stdin) {
+ if (opt->diffopt.detect_rename)
+ opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
+ DIFF_SETUP_USE_CACHE);
+ while (fgets(line, sizeof(line), stdin)) {
+ unsigned char sha1[20];
- if (opt->diffopt.detect_rename)
- opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
- DIFF_SETUP_USE_CACHE);
- while (fgets(line, sizeof(line), stdin)) {
- unsigned char sha1[20];
-
- if (get_sha1_hex(line, sha1)) {
- fputs(line, stdout);
- fflush(stdout);
+ if (get_sha1_hex(line, sha1)) {
+ fputs(line, stdout);
+ fflush(stdout);
+ }
+ else
+ diff_tree_stdin(line);
}
- else
- diff_tree_stdin(line);
}
- return opt->diffopt.exit_with_status ? opt->diffopt.has_changes: 0;
+
+ return diff_result_code(&opt->diffopt, 0);
}
diff --git a/builtin-diff.c b/builtin-diff.c
index f77352b40d..8f53f52dcb 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -4,6 +4,7 @@
* Copyright (c) 2006 Junio C Hamano
*/
#include "cache.h"
+#include "color.h"
#include "commit.h"
#include "blob.h"
#include "tag.h"
@@ -35,7 +36,7 @@ static void stuff_change(struct diff_options *opt,
!hashcmp(old_sha1, new_sha1) && (old_mode == new_mode))
return;
- if (opt->reverse_diff) {
+ if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
unsigned tmp;
const unsigned char *tmp_u;
const char *tmp_c;
@@ -176,18 +177,6 @@ static int builtin_diff_combined(struct rev_info *revs,
return 0;
}
-void add_head(struct rev_info *revs)
-{
- unsigned char sha1[20];
- struct object *obj;
- if (get_sha1("HEAD", sha1))
- return;
- obj = parse_object(sha1);
- if (!obj)
- return;
- add_pending_object(revs, obj, "HEAD");
-}
-
static void refresh_index_quietly(void)
{
struct lock_file *lock_file;
@@ -200,15 +189,11 @@ static void refresh_index_quietly(void)
discard_cache();
read_cache();
refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
- if (active_cache_changed) {
- if (write_cache(fd, active_cache, active_nr) ||
- close(fd) ||
- commit_locked_index(lock_file))
- ; /*
- * silently ignore it -- we haven't mucked
- * with the real index.
- */
- }
+
+ if (active_cache_changed &&
+ !write_cache(fd, active_cache, active_nr))
+ commit_locked_index(lock_file);
+
rollback_lock_file(lock_file);
}
@@ -245,6 +230,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
prefix = setup_git_directory_gently(&nongit);
git_config(git_diff_ui_config);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
init_revisions(&rev, prefix);
rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
@@ -257,13 +246,14 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
if (diff_setup_done(&rev.diffopt) < 0)
die("diff_setup_done failed");
}
- rev.diffopt.allow_external = 1;
- rev.diffopt.recursive = 1;
+ DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
+ DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
- /* If the user asked for our exit code then don't start a
+ /*
+ * If the user asked for our exit code then don't start a
* pager or we would end up reporting its exit code instead.
*/
- if (!rev.diffopt.exit_with_status)
+ if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS))
setup_pager();
/* Do we have --cached and not have a pending object, then
@@ -276,7 +266,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--"))
break;
else if (!strcmp(arg, "--cached")) {
- add_head(&rev);
+ add_head_to_pending(&rev);
if (!rev.pending.nr)
die("No HEAD commit to compare with (yet)");
break;
@@ -367,9 +357,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
else
result = builtin_diff_combined(&rev, argc, argv,
ent, ents);
- if (rev.diffopt.exit_with_status)
- result = rev.diffopt.has_changes;
-
+ result = diff_result_code(&rev.diffopt, result);
if (1 < rev.diffopt.skip_stat_unmatch)
refresh_index_quietly();
return result;
diff --git a/builtin-fast-export.c b/builtin-fast-export.c
new file mode 100755
index 0000000000..f741df5220
--- /dev/null
+++ b/builtin-fast-export.c
@@ -0,0 +1,407 @@
+/*
+ * "git fast-export" builtin command
+ *
+ * Copyright (C) 2007 Johannes E. Schindelin
+ */
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "object.h"
+#include "tag.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "log-tree.h"
+#include "revision.h"
+#include "decorate.h"
+#include "path-list.h"
+#include "utf8.h"
+#include "parse-options.h"
+
+static const char *fast_export_usage[] = {
+ "git-fast-export [rev-list-opts]",
+ NULL
+};
+
+static int progress;
+static enum { VERBATIM, WARN, STRIP, ABORT } signed_tag_mode = ABORT;
+
+static int parse_opt_signed_tag_mode(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset || !strcmp(arg, "abort"))
+ signed_tag_mode = ABORT;
+ else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
+ signed_tag_mode = VERBATIM;
+ else if (!strcmp(arg, "warn"))
+ signed_tag_mode = WARN;
+ else if (!strcmp(arg, "strip"))
+ signed_tag_mode = STRIP;
+ else
+ return error("Unknown signed-tag mode: %s", arg);
+ return 0;
+}
+
+static struct decoration idnums;
+static uint32_t last_idnum;
+
+static int has_unshown_parent(struct commit *commit)
+{
+ struct commit_list *parent;
+
+ for (parent = commit->parents; parent; parent = parent->next)
+ if (!(parent->item->object.flags & SHOWN) &&
+ !(parent->item->object.flags & UNINTERESTING))
+ return 1;
+ return 0;
+}
+
+/* Since intptr_t is C99, we do not use it here */
+static void mark_object(struct object *object)
+{
+ last_idnum++;
+ add_decoration(&idnums, object, ((uint32_t *)NULL) + last_idnum);
+}
+
+static int get_object_mark(struct object *object)
+{
+ void *decoration = lookup_decoration(&idnums, object);
+ if (!decoration)
+ return 0;
+ return (uint32_t *)decoration - (uint32_t *)NULL;
+}
+
+static void show_progress(void)
+{
+ static int counter = 0;
+ if (!progress)
+ return;
+ if ((++counter % progress) == 0)
+ printf("progress %d objects\n", counter);
+}
+
+static void handle_object(const unsigned char *sha1)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buf;
+ struct object *object;
+
+ if (is_null_sha1(sha1))
+ return;
+
+ object = parse_object(sha1);
+ if (!object)
+ die ("Could not read blob %s", sha1_to_hex(sha1));
+
+ if (object->flags & SHOWN)
+ return;
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ die ("Could not read blob %s", sha1_to_hex(sha1));
+
+ mark_object(object);
+
+ printf("blob\nmark :%d\ndata %lu\n", last_idnum, size);
+ if (size && fwrite(buf, size, 1, stdout) != 1)
+ die ("Could not write blob %s", sha1_to_hex(sha1));
+ printf("\n");
+
+ show_progress();
+
+ object->flags |= SHOWN;
+ free(buf);
+}
+
+static void show_filemodify(struct diff_queue_struct *q,
+ struct diff_options *options, void *data)
+{
+ int i;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filespec *spec = q->queue[i]->two;
+ if (is_null_sha1(spec->sha1))
+ printf("D %s\n", spec->path);
+ else {
+ struct object *object = lookup_object(spec->sha1);
+ printf("M 0%06o :%d %s\n", spec->mode,
+ get_object_mark(object), spec->path);
+ }
+ }
+}
+
+static const char *find_encoding(const char *begin, const char *end)
+{
+ const char *needle = "\nencoding ";
+ char *bol, *eol;
+
+ bol = memmem(begin, end ? end - begin : strlen(begin),
+ needle, strlen(needle));
+ if (!bol)
+ return git_commit_encoding;
+ bol += strlen(needle);
+ eol = strchrnul(bol, '\n');
+ *eol = '\0';
+ return bol;
+}
+
+static void handle_commit(struct commit *commit, struct rev_info *rev)
+{
+ int saved_output_format = rev->diffopt.output_format;
+ const char *author, *author_end, *committer, *committer_end;
+ const char *encoding, *message;
+ char *reencoded = NULL;
+ struct commit_list *p;
+ int i;
+
+ rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
+
+ parse_commit(commit);
+ author = strstr(commit->buffer, "\nauthor ");
+ if (!author)
+ die ("Could not find author in commit %s",
+ sha1_to_hex(commit->object.sha1));
+ author++;
+ author_end = strchrnul(author, '\n');
+ committer = strstr(author_end, "\ncommitter ");
+ if (!committer)
+ die ("Could not find committer in commit %s",
+ sha1_to_hex(commit->object.sha1));
+ committer++;
+ committer_end = strchrnul(committer, '\n');
+ message = strstr(committer_end, "\n\n");
+ encoding = find_encoding(committer_end, message);
+ if (message)
+ message += 2;
+
+ if (commit->parents) {
+ parse_commit(commit->parents->item);
+ diff_tree_sha1(commit->parents->item->tree->object.sha1,
+ commit->tree->object.sha1, "", &rev->diffopt);
+ }
+ else
+ diff_root_tree_sha1(commit->tree->object.sha1,
+ "", &rev->diffopt);
+
+ for (i = 0; i < diff_queued_diff.nr; i++)
+ handle_object(diff_queued_diff.queue[i]->two->sha1);
+
+ mark_object(&commit->object);
+ if (!is_encoding_utf8(encoding))
+ reencoded = reencode_string(message, "UTF-8", encoding);
+ printf("commit %s\nmark :%d\n%.*s\n%.*s\ndata %u\n%s",
+ (const char *)commit->util, last_idnum,
+ (int)(author_end - author), author,
+ (int)(committer_end - committer), committer,
+ (unsigned)(reencoded
+ ? strlen(reencoded) : message
+ ? strlen(message) : 0),
+ reencoded ? reencoded : message ? message : "");
+ if (reencoded)
+ free(reencoded);
+
+ for (i = 0, p = commit->parents; p; p = p->next) {
+ int mark = get_object_mark(&p->item->object);
+ if (!mark)
+ continue;
+ if (i == 0)
+ printf("from :%d\n", mark);
+ else if (i == 1)
+ printf("merge :%d", mark);
+ else
+ printf(" :%d", mark);
+ i++;
+ }
+ if (i > 1)
+ printf("\n");
+
+ log_tree_diff_flush(rev);
+ rev->diffopt.output_format = saved_output_format;
+
+ printf("\n");
+
+ show_progress();
+}
+
+static void handle_tail(struct object_array *commits, struct rev_info *revs)
+{
+ struct commit *commit;
+ while (commits->nr) {
+ commit = (struct commit *)commits->objects[commits->nr - 1].item;
+ if (has_unshown_parent(commit))
+ return;
+ handle_commit(commit, revs);
+ commits->nr--;
+ }
+}
+
+static void handle_tag(const char *name, struct tag *tag)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buf;
+ const char *tagger, *tagger_end, *message;
+ size_t message_size = 0;
+
+ buf = read_sha1_file(tag->object.sha1, &type, &size);
+ if (!buf)
+ die ("Could not read tag %s", sha1_to_hex(tag->object.sha1));
+ message = memmem(buf, size, "\n\n", 2);
+ if (message) {
+ message += 2;
+ message_size = strlen(message);
+ }
+ tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8);
+ if (!tagger)
+ die ("No tagger for tag %s", sha1_to_hex(tag->object.sha1));
+ tagger++;
+ tagger_end = strchrnul(tagger, '\n');
+
+ /* handle signed tags */
+ if (message) {
+ const char *signature = strstr(message,
+ "\n-----BEGIN PGP SIGNATURE-----\n");
+ if (signature)
+ switch(signed_tag_mode) {
+ case ABORT:
+ die ("Encountered signed tag %s; use "
+ "--signed-tag=<mode> to handle it.",
+ sha1_to_hex(tag->object.sha1));
+ case WARN:
+ warning ("Exporting signed tag %s",
+ sha1_to_hex(tag->object.sha1));
+ /* fallthru */
+ case VERBATIM:
+ break;
+ case STRIP:
+ message_size = signature + 1 - message;
+ break;
+ }
+ }
+
+ if (!prefixcmp(name, "refs/tags/"))
+ name += 10;
+ printf("tag %s\nfrom :%d\n%.*s\ndata %d\n%.*s\n",
+ name, get_object_mark(tag->tagged),
+ (int)(tagger_end - tagger), tagger,
+ (int)message_size, (int)message_size, message ? message : "");
+}
+
+static void get_tags_and_duplicates(struct object_array *pending,
+ struct path_list *extra_refs)
+{
+ struct tag *tag;
+ int i;
+
+ for (i = 0; i < pending->nr; i++) {
+ struct object_array_entry *e = pending->objects + i;
+ unsigned char sha1[20];
+ struct commit *commit = commit;
+ char *full_name;
+
+ if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
+ continue;
+
+ switch (e->item->type) {
+ case OBJ_COMMIT:
+ commit = (struct commit *)e->item;
+ break;
+ case OBJ_TAG:
+ tag = (struct tag *)e->item;
+ while (tag && tag->object.type == OBJ_TAG) {
+ path_list_insert(full_name, extra_refs)->util = tag;
+ tag = (struct tag *)tag->tagged;
+ }
+ if (!tag)
+ die ("Tag %s points nowhere?", e->name);
+ switch(tag->object.type) {
+ case OBJ_COMMIT:
+ commit = (struct commit *)tag;
+ break;
+ case OBJ_BLOB:
+ handle_object(tag->object.sha1);
+ continue;
+ }
+ break;
+ default:
+ die ("Unexpected object of type %s",
+ typename(e->item->type));
+ }
+ if (commit->util)
+ /* more than one name for the same object */
+ path_list_insert(full_name, extra_refs)->util = commit;
+ else
+ commit->util = full_name;
+ }
+}
+
+static void handle_tags_and_duplicates(struct path_list *extra_refs)
+{
+ struct commit *commit;
+ int i;
+
+ for (i = extra_refs->nr - 1; i >= 0; i--) {
+ const char *name = extra_refs->items[i].path;
+ struct object *object = extra_refs->items[i].util;
+ switch (object->type) {
+ case OBJ_TAG:
+ handle_tag(name, (struct tag *)object);
+ break;
+ case OBJ_COMMIT:
+ /* create refs pointing to already seen commits */
+ commit = (struct commit *)object;
+ printf("reset %s\nfrom :%d\n\n", name,
+ get_object_mark(&commit->object));
+ show_progress();
+ break;
+ }
+ }
+}
+
+int cmd_fast_export(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ struct object_array commits = { 0, 0, NULL };
+ struct path_list extra_refs = { NULL, 0, 0, 0 };
+ struct commit *commit;
+ struct option options[] = {
+ OPT_INTEGER(0, "progress", &progress,
+ "show progress after <n> objects"),
+ OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode",
+ "select handling of signed tags",
+ parse_opt_signed_tag_mode),
+ OPT_END()
+ };
+
+ /* we handle encodings */
+ git_config(git_default_config);
+
+ init_revisions(&revs, prefix);
+ argc = setup_revisions(argc, argv, &revs, NULL);
+ argc = parse_options(argc, argv, options, fast_export_usage, 0);
+ if (argc > 1)
+ usage_with_options (fast_export_usage, options);
+
+ get_tags_and_duplicates(&revs.pending, &extra_refs);
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ revs.diffopt.format_callback = show_filemodify;
+ DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+ while ((commit = get_revision(&revs))) {
+ if (has_unshown_parent(commit)) {
+ struct commit_list *parent = commit->parents;
+ add_object_array(&commit->object, NULL, &commits);
+ for (; parent; parent = parent->next)
+ if (!parent->item->util)
+ parent->item->util = commit->util;
+ }
+ else {
+ handle_commit(commit, &revs);
+ handle_tail(&commits, &revs);
+ }
+ }
+
+ handle_tags_and_duplicates(&extra_refs);
+
+ return 0;
+}
diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c
index 1e43d79221..7460ab7fce 100644
--- a/builtin-fetch--tool.c
+++ b/builtin-fetch--tool.c
@@ -25,7 +25,7 @@ static int update_ref_env(const char *action,
unsigned char *oldval)
{
char msg[1024];
- char *rla = getenv("GIT_REFLOG_ACTION");
+ const char *rla = getenv("GIT_REFLOG_ACTION");
if (!rla)
rla = "(reflog update)";
@@ -61,7 +61,7 @@ static int update_local_ref(const char *name,
}
if (get_sha1(name, sha1_old)) {
- char *msg;
+ const char *msg;
just_store:
/* new ref */
if (!strncmp(name, "refs/tags/", 10))
@@ -131,7 +131,7 @@ static int append_fetch_head(FILE *fp,
if (get_sha1(head, sha1))
return error("Not a valid object name: %s", head);
- commit = lookup_commit_reference(sha1);
+ commit = lookup_commit_reference_gently(sha1, 1);
if (!commit)
not_for_merge = 1;
@@ -435,9 +435,7 @@ static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_resu
cp++;
if (!*cp)
break;
- np = strchr(cp, '\n');
- if (!np)
- np = cp + strlen(cp);
+ np = strchrnul(cp, '\n');
if (pass) {
lrr_list[i].line = cp;
lrr_list[i].name = cp + 41;
@@ -461,9 +459,7 @@ static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_resu
rref++;
if (!*rref)
break;
- next = strchr(rref, '\n');
- if (!next)
- next = rref + strlen(rref);
+ next = strchrnul(rref, '\n');
rreflen = next - rref;
for (i = 0; i < lrr_count; i++) {
@@ -515,10 +511,14 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
if (!strcmp("append-fetch-head", argv[1])) {
int result;
FILE *fp;
+ char *filename;
if (argc != 8)
return error("append-fetch-head takes 6 args");
- fp = fopen(git_path("FETCH_HEAD"), "a");
+ filename = git_path("FETCH_HEAD");
+ fp = fopen(filename, "a");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
result = append_fetch_head(fp, argv[2], argv[3],
argv[4], argv[5],
argv[6], !!argv[7][0],
@@ -529,10 +529,14 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
if (!strcmp("native-store", argv[1])) {
int result;
FILE *fp;
+ char *filename;
if (argc != 5)
return error("fetch-native-store takes 3 args");
- fp = fopen(git_path("FETCH_HEAD"), "a");
+ filename = git_path("FETCH_HEAD");
+ fp = fopen(filename, "a");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
result = fetch_native_store(fp, argv[2], argv[3], argv[4],
verbose, force);
fclose(fp);
diff --git a/fetch-pack.c b/builtin-fetch-pack.c
index 9c81305be5..f40135248a 100644
--- a/fetch-pack.c
+++ b/builtin-fetch-pack.c
@@ -6,19 +6,19 @@
#include "exec_cmd.h"
#include "pack.h"
#include "sideband.h"
+#include "fetch-pack.h"
+#include "remote.h"
+#include "run-command.h"
-static int keep_pack;
static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
static int unpack_limit = 100;
-static int quiet;
-static int verbose;
-static int fetch_all;
-static int depth;
-static int no_progress;
+static struct fetch_pack_args args = {
+ /* .uploadpack = */ "git-upload-pack",
+};
+
static const char fetch_pack_usage[] =
"git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
-static const char *uploadpack = "git-upload-pack";
#define COMPLETE (1U << 0)
#define COMMON (1U << 1)
@@ -33,7 +33,7 @@ static const char *uploadpack = "git-upload-pack";
#define MAX_IN_VAIN 256
static struct commit_list *rev_list;
-static int non_common_revs, multi_ack, use_thin_pack, use_sideband;
+static int non_common_revs, multi_ack, use_sideband;
static void rev_list_push(struct commit *commit, int mark)
{
@@ -179,8 +179,8 @@ static int find_common(int fd[2], unsigned char *result_sha1,
(multi_ack ? " multi_ack" : ""),
(use_sideband == 2 ? " side-band-64k" : ""),
(use_sideband == 1 ? " side-band" : ""),
- (use_thin_pack ? " thin-pack" : ""),
- (no_progress ? " no-progress" : ""),
+ (args.use_thin_pack ? " thin-pack" : ""),
+ (args.no_progress ? " no-progress" : ""),
" ofs-delta");
else
packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
@@ -188,13 +188,13 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
if (is_repository_shallow())
write_shallow_commits(fd[1], 1);
- if (depth > 0)
- packet_write(fd[1], "deepen %d", depth);
+ if (args.depth > 0)
+ packet_write(fd[1], "deepen %d", args.depth);
packet_flush(fd[1]);
if (!fetching)
return 1;
- if (depth > 0) {
+ if (args.depth > 0) {
char line[1024];
unsigned char sha1[20];
int len;
@@ -225,7 +225,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
retval = -1;
while ((sha1 = get_rev())) {
packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
- if (verbose)
+ if (args.verbose)
fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
in_vain++;
if (!(31 & ++count)) {
@@ -243,7 +243,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
do {
ack = get_ack(fd[0], result_sha1);
- if (verbose && ack)
+ if (args.verbose && ack)
fprintf(stderr, "got ack %d %s\n", ack,
sha1_to_hex(result_sha1));
if (ack == 1) {
@@ -262,7 +262,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
} while (ack);
flushes--;
if (got_continue && MAX_IN_VAIN < in_vain) {
- if (verbose)
+ if (args.verbose)
fprintf(stderr, "giving up\n");
break; /* give up */
}
@@ -270,7 +270,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
done:
packet_write(fd[1], "done\n");
- if (verbose)
+ if (args.verbose)
fprintf(stderr, "done\n");
if (retval != 0) {
multi_ack = 0;
@@ -279,7 +279,7 @@ done:
while (flushes || multi_ack) {
int ack = get_ack(fd[0], result_sha1);
if (ack) {
- if (verbose)
+ if (args.verbose)
fprintf(stderr, "got ack (%d) %s\n", ack,
sha1_to_hex(result_sha1));
if (ack == 1)
@@ -316,7 +316,7 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag,
static void mark_recent_complete_commits(unsigned long cutoff)
{
while (complete && cutoff <= complete->item->date) {
- if (verbose)
+ if (args.verbose)
fprintf(stderr, "Marking %s as complete\n",
sha1_to_hex(complete->item->object.sha1));
pop_most_recent_commit(&complete, COMPLETE);
@@ -331,7 +331,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
struct ref *ref, *next;
struct ref *fastarray[32];
- if (nr_match && !fetch_all) {
+ if (nr_match && !args.fetch_all) {
if (ARRAY_SIZE(fastarray) < nr_match)
return_refs = xcalloc(nr_match, sizeof(struct ref *));
else {
@@ -347,8 +347,8 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
if (!memcmp(ref->name, "refs/", 5) &&
check_ref_format(ref->name + 5))
; /* trash */
- else if (fetch_all &&
- (!depth || prefixcmp(ref->name, "refs/tags/") )) {
+ else if (args.fetch_all &&
+ (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
*newtail = ref;
ref->next = NULL;
newtail = &ref->next;
@@ -364,7 +364,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
free(ref);
}
- if (!fetch_all) {
+ if (!args.fetch_all) {
int i;
for (i = 0; i < nr_match; i++) {
ref = return_refs[i];
@@ -407,7 +407,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
}
}
- if (!depth) {
+ if (!args.depth) {
for_each_ref(mark_complete, NULL);
if (cutoff)
mark_recent_complete_commits(cutoff);
@@ -441,7 +441,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
o = lookup_object(remote);
if (!o || !(o->flags & COMPLETE)) {
retval = 0;
- if (!verbose)
+ if (!args.verbose)
continue;
fprintf(stderr,
"want %s (%s)\n", sha1_to_hex(remote),
@@ -450,7 +450,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
}
hashcpy(ref->new_sha1, local);
- if (!verbose)
+ if (!args.verbose)
continue;
fprintf(stderr,
"already have %s (%s)\n", sha1_to_hex(remote),
@@ -459,58 +459,46 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
return retval;
}
-static pid_t setup_sideband(int fd[2], int xd[2])
+static int sideband_demux(int fd, void *data)
{
- pid_t side_pid;
+ int *xd = data;
- if (!use_sideband) {
- fd[0] = xd[0];
- fd[1] = xd[1];
- return 0;
- }
- /* xd[] is talking with upload-pack; subprocess reads from
- * xd[0], spits out band#2 to stderr, and feeds us band#1
- * through our fd[0].
- */
- if (pipe(fd) < 0)
- die("fetch-pack: unable to set up pipe");
- side_pid = fork();
- if (side_pid < 0)
- die("fetch-pack: unable to fork off sideband demultiplexer");
- if (!side_pid) {
- /* subprocess */
- close(fd[0]);
- if (xd[0] != xd[1])
- close(xd[1]);
- if (recv_sideband("fetch-pack", xd[0], fd[1], 2))
- exit(1);
- exit(0);
- }
- close(xd[0]);
- close(fd[1]);
- fd[1] = xd[1];
- return side_pid;
+ return recv_sideband("fetch-pack", xd[0], fd, 2);
}
-static int get_pack(int xd[2])
+static int get_pack(int xd[2], char **pack_lockfile)
{
- int status;
- pid_t pid, side_pid;
- int fd[2];
+ struct async demux;
const char *argv[20];
char keep_arg[256];
char hdr_arg[256];
const char **av;
- int do_keep = keep_pack;
-
- side_pid = setup_sideband(fd, xd);
+ int do_keep = args.keep_pack;
+ struct child_process cmd;
+
+ memset(&demux, 0, sizeof(demux));
+ if (use_sideband) {
+ /* xd[] is talking with upload-pack; subprocess reads from
+ * xd[0], spits out band#2 to stderr, and feeds us band#1
+ * through demux->out.
+ */
+ demux.proc = sideband_demux;
+ demux.data = xd;
+ if (start_async(&demux))
+ die("fetch-pack: unable to fork off sideband"
+ " demultiplexer");
+ }
+ else
+ demux.out = xd[0];
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.argv = argv;
av = argv;
*hdr_arg = 0;
- if (unpack_limit) {
+ if (!args.keep_pack && unpack_limit) {
struct pack_header header;
- if (read_pack_header(fd[0], &header))
+ if (read_pack_header(demux.out, &header))
die("protocol error: bad pack header");
snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u",
ntohl(header.hdr_version), ntohl(header.hdr_entries));
@@ -521,13 +509,15 @@ static int get_pack(int xd[2])
}
if (do_keep) {
+ if (pack_lockfile)
+ cmd.out = -1;
*av++ = "index-pack";
*av++ = "--stdin";
- if (!quiet && !no_progress)
+ if (!args.quiet && !args.no_progress)
*av++ = "-v";
- if (use_thin_pack)
+ if (args.use_thin_pack)
*av++ = "--fix-thin";
- if (keep_pack > 1 || unpack_limit) {
+ if (args.lock_pack || unpack_limit) {
int s = sprintf(keep_arg,
"--keep=fetch-pack %d on ", getpid());
if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
@@ -537,90 +527,69 @@ static int get_pack(int xd[2])
}
else {
*av++ = "unpack-objects";
- if (quiet)
+ if (args.quiet)
*av++ = "-q";
}
if (*hdr_arg)
*av++ = hdr_arg;
*av++ = NULL;
- pid = fork();
- if (pid < 0)
+ cmd.in = demux.out;
+ cmd.git_cmd = 1;
+ if (start_command(&cmd))
die("fetch-pack: unable to fork off %s", argv[0]);
- if (!pid) {
- dup2(fd[0], 0);
- close(fd[0]);
- close(fd[1]);
- execv_git_cmd(argv);
- die("%s exec failed", argv[0]);
- }
- close(fd[0]);
- close(fd[1]);
- while (waitpid(pid, &status, 0) < 0) {
- if (errno != EINTR)
- die("waiting for %s: %s", argv[0], strerror(errno));
- }
- if (WIFEXITED(status)) {
- int code = WEXITSTATUS(status);
- if (code)
- die("%s died with error code %d", argv[0], code);
- return 0;
- }
- if (WIFSIGNALED(status)) {
- int sig = WTERMSIG(status);
- die("%s died of signal %d", argv[0], sig);
- }
- die("%s died of unnatural causes %d", argv[0], status);
+ if (do_keep && pack_lockfile)
+ *pack_lockfile = index_pack_lockfile(cmd.out);
+
+ if (finish_command(&cmd))
+ die("%s failed", argv[0]);
+ if (use_sideband && finish_async(&demux))
+ die("error in sideband demultiplexer");
+ return 0;
}
-static int fetch_pack(int fd[2], int nr_match, char **match)
+static struct ref *do_fetch_pack(int fd[2],
+ const struct ref *orig_ref,
+ int nr_match,
+ char **match,
+ char **pack_lockfile)
{
- struct ref *ref;
+ struct ref *ref = copy_ref_list(orig_ref);
unsigned char sha1[20];
- get_remote_heads(fd[0], &ref, 0, NULL, 0);
if (is_repository_shallow() && !server_supports("shallow"))
die("Server does not support shallow clients");
if (server_supports("multi_ack")) {
- if (verbose)
+ if (args.verbose)
fprintf(stderr, "Server supports multi_ack\n");
multi_ack = 1;
}
if (server_supports("side-band-64k")) {
- if (verbose)
+ if (args.verbose)
fprintf(stderr, "Server supports side-band-64k\n");
use_sideband = 2;
}
else if (server_supports("side-band")) {
- if (verbose)
+ if (args.verbose)
fprintf(stderr, "Server supports side-band\n");
use_sideband = 1;
}
- if (!ref) {
- packet_flush(fd[1]);
- die("no matching remote head");
- }
if (everything_local(&ref, nr_match, match)) {
packet_flush(fd[1]);
goto all_done;
}
if (find_common(fd, sha1, ref) < 0)
- if (keep_pack != 1)
+ if (!args.keep_pack)
/* When cloning, it is not unusual to have
* no common commit.
*/
fprintf(stderr, "warning: no common commits\n");
- if (get_pack(fd))
+ if (get_pack(fd, pack_lockfile))
die("git-fetch-pack: fetch failed.");
all_done:
- while (ref) {
- printf("%s %s\n",
- sha1_to_hex(ref->old_sha1), ref->name);
- ref = ref->next;
- }
- return 0;
+ return ref;
}
static int remove_duplicates(int nr_heads, char **heads)
@@ -642,7 +611,6 @@ static int remove_duplicates(int nr_heads, char **heads)
heads[dst] = heads[src];
dst++;
}
- heads[dst] = 0;
return dst;
}
@@ -663,85 +631,94 @@ static int fetch_pack_config(const char *var, const char *value)
static struct lock_file lock;
-int main(int argc, char **argv)
+static void fetch_pack_setup(void)
{
- int i, ret, nr_heads;
- char *dest = NULL, **heads;
- int fd[2];
- pid_t pid;
- struct stat st;
-
- setup_git_directory();
+ static int did_setup;
+ if (did_setup)
+ return;
git_config(fetch_pack_config);
-
if (0 <= transfer_unpack_limit)
unpack_limit = transfer_unpack_limit;
else if (0 <= fetch_unpack_limit)
unpack_limit = fetch_unpack_limit;
+ did_setup = 1;
+}
+
+int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
+{
+ int i, ret, nr_heads;
+ struct ref *ref = NULL;
+ char *dest = NULL, **heads;
+ int fd[2];
+ struct child_process *conn;
nr_heads = 0;
heads = NULL;
for (i = 1; i < argc; i++) {
- char *arg = argv[i];
+ const char *arg = argv[i];
if (*arg == '-') {
if (!prefixcmp(arg, "--upload-pack=")) {
- uploadpack = arg + 14;
+ args.uploadpack = arg + 14;
continue;
}
if (!prefixcmp(arg, "--exec=")) {
- uploadpack = arg + 7;
+ args.uploadpack = arg + 7;
continue;
}
if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
- quiet = 1;
+ args.quiet = 1;
continue;
}
if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
- keep_pack++;
- unpack_limit = 0;
+ args.lock_pack = args.keep_pack;
+ args.keep_pack = 1;
continue;
}
if (!strcmp("--thin", arg)) {
- use_thin_pack = 1;
+ args.use_thin_pack = 1;
continue;
}
if (!strcmp("--all", arg)) {
- fetch_all = 1;
+ args.fetch_all = 1;
continue;
}
if (!strcmp("-v", arg)) {
- verbose = 1;
+ args.verbose = 1;
continue;
}
if (!prefixcmp(arg, "--depth=")) {
- depth = strtol(arg + 8, NULL, 0);
- if (stat(git_path("shallow"), &st))
- st.st_mtime = 0;
+ args.depth = strtol(arg + 8, NULL, 0);
continue;
}
if (!strcmp("--no-progress", arg)) {
- no_progress = 1;
+ args.no_progress = 1;
continue;
}
usage(fetch_pack_usage);
}
- dest = arg;
- heads = argv + i + 1;
+ dest = (char *)arg;
+ heads = (char **)(argv + i + 1);
nr_heads = argc - i - 1;
break;
}
if (!dest)
usage(fetch_pack_usage);
- pid = git_connect(fd, dest, uploadpack, verbose ? CONNECT_VERBOSE : 0);
- if (pid < 0)
- return 1;
- if (heads && nr_heads)
- nr_heads = remove_duplicates(nr_heads, heads);
- ret = fetch_pack(fd, nr_heads, heads);
- close(fd[0]);
- close(fd[1]);
- ret |= finish_connect(pid);
+
+ conn = git_connect(fd, (char *)dest, args.uploadpack,
+ args.verbose ? CONNECT_VERBOSE : 0);
+ if (conn) {
+ get_remote_heads(fd[0], &ref, 0, NULL, 0);
+
+ ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL);
+ close(fd[0]);
+ close(fd[1]);
+ if (finish_connect(conn))
+ ref = NULL;
+ } else {
+ ref = NULL;
+ }
+ ret = !ref;
if (!ret && nr_heads) {
/* If the heads to pull were given, we should have
@@ -755,8 +732,42 @@ int main(int argc, char **argv)
ret = 1;
}
}
+ while (ref) {
+ printf("%s %s\n",
+ sha1_to_hex(ref->old_sha1), ref->name);
+ ref = ref->next;
+ }
+
+ return ret;
+}
+
+struct ref *fetch_pack(struct fetch_pack_args *my_args,
+ int fd[], struct child_process *conn,
+ const struct ref *ref,
+ const char *dest,
+ int nr_heads,
+ char **heads,
+ char **pack_lockfile)
+{
+ struct stat st;
+ struct ref *ref_cpy;
+
+ fetch_pack_setup();
+ memcpy(&args, my_args, sizeof(args));
+ if (args.depth > 0) {
+ if (stat(git_path("shallow"), &st))
+ st.st_mtime = 0;
+ }
+
+ if (heads && nr_heads)
+ nr_heads = remove_duplicates(nr_heads, heads);
+ if (!ref) {
+ packet_flush(fd[1]);
+ die("no matching remote head");
+ }
+ ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile);
- if (!ret && depth > 0) {
+ if (args.depth > 0) {
struct cache_time mtime;
char *shallow = git_path("shallow");
int fd;
@@ -780,10 +791,9 @@ int main(int argc, char **argv)
unlink(shallow);
rollback_lock_file(&lock);
} else {
- close(fd);
commit_lock_file(&lock);
}
}
- return !!ret;
+ return ref_cpy;
}
diff --git a/builtin-fetch.c b/builtin-fetch.c
new file mode 100644
index 0000000000..ac335f20fe
--- /dev/null
+++ b/builtin-fetch.c
@@ -0,0 +1,634 @@
+/*
+ * "git fetch"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+#include "path-list.h"
+#include "remote.h"
+#include "transport.h"
+#include "run-command.h"
+#include "parse-options.h"
+
+static const char * const builtin_fetch_usage[] = {
+ "git-fetch [options] [<repository> <refspec>...]",
+ NULL
+};
+
+enum {
+ TAGS_UNSET = 0,
+ TAGS_DEFAULT = 1,
+ TAGS_SET = 2
+};
+
+static int append, force, keep, update_head_ok, verbose, quiet;
+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 struct option builtin_fetch_options[] = {
+ OPT__QUIET(&quiet),
+ OPT__VERBOSE(&verbose),
+ OPT_BOOLEAN('a', "append", &append,
+ "append to .git/FETCH_HEAD instead of overwriting"),
+ 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_SET_INT('t', "tags", &tags,
+ "fetch all tags and associated objects", TAGS_SET),
+ OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
+ OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+ "allow updating of HEAD ref"),
+ OPT_STRING(0, "depth", &depth, "DEPTH",
+ "deepen history of shallow clone"),
+ OPT_END()
+};
+
+static void unlock_pack(void)
+{
+ if (transport)
+ transport_unlock_pack(transport);
+}
+
+static void unlock_pack_on_signal(int signo)
+{
+ unlock_pack();
+ signal(SIGINT, SIG_DFL);
+ raise(signo);
+}
+
+static void add_merge_config(struct ref **head,
+ const struct ref *remote_refs,
+ struct branch *branch,
+ struct ref ***tail)
+{
+ int i;
+
+ for (i = 0; i < branch->merge_nr; i++) {
+ struct ref *rm, **old_tail = *tail;
+ struct refspec refspec;
+
+ for (rm = *head; rm; rm = rm->next) {
+ if (branch_merge_matches(branch, i, rm->name)) {
+ rm->merge = 1;
+ break;
+ }
+ }
+ if (rm)
+ continue;
+
+ /*
+ * Not fetched to a 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
+ * at a nonexisting branch. If we were indeed called by
+ * git-pull, it will notice the misconfiguration because
+ * there is no entry in the resulting FETCH_HEAD marked
+ * for merging.
+ */
+ refspec.src = branch->merge[i]->src;
+ refspec.dst = NULL;
+ refspec.pattern = 0;
+ refspec.force = 0;
+ get_fetch_map(remote_refs, &refspec, tail, 1);
+ for (rm = *old_tail; rm; rm = rm->next)
+ rm->merge = 1;
+ }
+}
+
+static struct ref *get_ref_map(struct transport *transport,
+ struct refspec *refs, int ref_count, int tags,
+ int *autotags)
+{
+ int i;
+ struct ref *rm;
+ struct ref *ref_map = NULL;
+ struct ref **tail = &ref_map;
+
+ const struct ref *remote_refs = transport_get_remote_refs(transport);
+
+ if (ref_count || tags == TAGS_SET) {
+ for (i = 0; i < ref_count; i++) {
+ get_fetch_map(remote_refs, &refs[i], &tail, 0);
+ if (refs[i].dst && refs[i].dst[0])
+ *autotags = 1;
+ }
+ /* Merge everything on the command line, but not --tags */
+ for (rm = ref_map; rm; rm = rm->next)
+ rm->merge = 1;
+ if (tags == TAGS_SET) {
+ struct refspec refspec;
+ refspec.src = "refs/tags/";
+ refspec.dst = "refs/tags/";
+ refspec.pattern = 1;
+ refspec.force = 0;
+ get_fetch_map(remote_refs, &refspec, &tail, 0);
+ }
+ } else {
+ /* Use the defaults */
+ 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)) {
+ for (i = 0; i < remote->fetch_refspec_nr; i++) {
+ get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
+ if (remote->fetch[i].dst &&
+ remote->fetch[i].dst[0])
+ *autotags = 1;
+ if (!i && !has_merge && ref_map &&
+ !remote->fetch[0].pattern)
+ ref_map->merge = 1;
+ }
+ /*
+ * 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.
+ */
+ if (has_merge &&
+ !strcmp(branch->remote_name, remote->name))
+ add_merge_config(&ref_map, remote_refs, branch, &tail);
+ } else {
+ ref_map = get_remote_ref(remote_refs, "HEAD");
+ if (!ref_map)
+ die("Couldn't find remote ref HEAD");
+ ref_map->merge = 1;
+ }
+ }
+ ref_remove_duplicates(ref_map);
+
+ return ref_map;
+}
+
+static int s_update_ref(const char *action,
+ struct ref *ref,
+ int check_old)
+{
+ char msg[1024];
+ char *rla = getenv("GIT_REFLOG_ACTION");
+ static struct ref_lock *lock;
+
+ if (!rla)
+ rla = default_rla.buf;
+ snprintf(msg, sizeof(msg), "%s: %s", rla, action);
+ lock = lock_any_ref_for_update(ref->name,
+ check_old ? ref->old_sha1 : NULL, 0);
+ if (!lock)
+ return 1;
+ if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
+ return 1;
+ return 0;
+}
+
+#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+#define REFCOL_WIDTH 10
+
+static int update_local_ref(struct ref *ref,
+ const char *remote,
+ int verbose,
+ char *display)
+{
+ struct commit *current = NULL, *updated;
+ enum object_type type;
+ struct branch *current_branch = branch_get(NULL);
+ const char *pretty_ref = ref->name + (
+ !prefixcmp(ref->name, "refs/heads/") ? 11 :
+ !prefixcmp(ref->name, "refs/tags/") ? 10 :
+ !prefixcmp(ref->name, "refs/remotes/") ? 13 :
+ 0);
+
+ *display = 0;
+ type = sha1_object_info(ref->new_sha1, NULL);
+ if (type < 0)
+ die("object %s not found", sha1_to_hex(ref->new_sha1));
+
+ if (!*ref->name) {
+ /* Not storing */
+ if (verbose)
+ sprintf(display, "* branch %s -> FETCH_HEAD", remote);
+ return 0;
+ }
+
+ if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
+ if (verbose)
+ sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH,
+ "[up to date]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 0;
+ }
+
+ if (current_branch &&
+ !strcmp(ref->name, current_branch->name) &&
+ !(update_head_ok || is_bare_repository()) &&
+ !is_null_sha1(ref->old_sha1)) {
+ /*
+ * 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)",
+ SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 1;
+ }
+
+ if (!is_null_sha1(ref->old_sha1) &&
+ !prefixcmp(ref->name, "refs/tags/")) {
+ sprintf(display, "- %-*s %-*s -> %s",
+ SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return s_update_ref("updating tag", ref, 0);
+ }
+
+ current = lookup_commit_reference_gently(ref->old_sha1, 1);
+ updated = lookup_commit_reference_gently(ref->new_sha1, 1);
+ if (!current || !updated) {
+ const char *msg;
+ const char *what;
+ if (!strncmp(ref->name, "refs/tags/", 10)) {
+ msg = "storing tag";
+ what = "[new tag]";
+ }
+ else {
+ msg = "storing head";
+ what = "[new branch]";
+ }
+
+ sprintf(display, "* %-*s %-*s -> %s", SUMMARY_WIDTH, what,
+ REFCOL_WIDTH, remote, pretty_ref);
+ return s_update_ref(msg, ref, 0);
+ }
+
+ if (in_merge_bases(current, &updated, 1)) {
+ char quickref[83];
+ strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+ strcat(quickref, "..");
+ strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ sprintf(display, " %-*s %-*s -> %s", SUMMARY_WIDTH, quickref,
+ REFCOL_WIDTH, remote, pretty_ref);
+ return s_update_ref("fast forward", ref, 1);
+ } else if (force || ref->force) {
+ char quickref[84];
+ strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+ strcat(quickref, "...");
+ strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ sprintf(display, "+ %-*s %-*s -> %s (forced update)",
+ SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref);
+ return s_update_ref("forced-update", ref, 1);
+ } else {
+ sprintf(display, "! %-*s %-*s -> %s (non fast forward)",
+ SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 1;
+ }
+}
+
+static int store_updated_refs(const char *url, struct ref *ref_map)
+{
+ FILE *fp;
+ struct commit *commit;
+ int url_len, i, note_len, shown_url = 0;
+ char note[1024];
+ const char *what, *kind;
+ struct ref *rm;
+ char *filename = git_path("FETCH_HEAD");
+
+ fp = fopen(filename, "a");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
+ for (rm = ref_map; rm; rm = rm->next) {
+ struct ref *ref = NULL;
+
+ if (rm->peer_ref) {
+ ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
+ strcpy(ref->name, rm->peer_ref->name);
+ hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
+ hashcpy(ref->new_sha1, rm->old_sha1);
+ ref->force = rm->peer_ref->force;
+ }
+
+ commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+ if (!commit)
+ rm->merge = 0;
+
+ if (!strcmp(rm->name, "HEAD")) {
+ kind = "";
+ what = "";
+ }
+ else if (!prefixcmp(rm->name, "refs/heads/")) {
+ kind = "branch";
+ what = rm->name + 11;
+ }
+ else if (!prefixcmp(rm->name, "refs/tags/")) {
+ kind = "tag";
+ what = rm->name + 10;
+ }
+ else if (!prefixcmp(rm->name, "refs/remotes/")) {
+ kind = "remote branch";
+ what = rm->name + 13;
+ }
+ else {
+ kind = "";
+ what = rm->name;
+ }
+
+ url_len = strlen(url);
+ for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
+ ;
+ url_len = i + 1;
+ if (4 < i && !strncmp(".git", url + i - 3, 4))
+ url_len = i - 3;
+
+ note_len = 0;
+ if (*what) {
+ if (*kind)
+ note_len += sprintf(note + note_len, "%s ",
+ kind);
+ note_len += sprintf(note + note_len, "'%s' of ", what);
+ }
+ note_len += sprintf(note + note_len, "%.*s", url_len, url);
+ fprintf(fp, "%s\t%s\t%s\n",
+ sha1_to_hex(commit ? commit->object.sha1 :
+ rm->old_sha1),
+ rm->merge ? "" : "not-for-merge",
+ note);
+
+ if (ref) {
+ update_local_ref(ref, what, verbose, note);
+ if (*note) {
+ if (!shown_url) {
+ fprintf(stderr, "From %.*s\n",
+ url_len, url);
+ shown_url = 1;
+ }
+ fprintf(stderr, " %s\n", note);
+ }
+ }
+ }
+ fclose(fp);
+ return 0;
+}
+
+/*
+ * We would want to bypass the object transfer altogether if
+ * everything we are going to fetch already exists and connected
+ * locally.
+ *
+ * The refs we are going to fetch are in to_fetch (nr_heads in
+ * total). If running
+ *
+ * $ git-rev-list --objects to_fetch[0] to_fetch[1] ... --not --all
+ *
+ * does not error out, that means everything reachable from the
+ * refs we are going to fetch exists and is connected to some of
+ * our existing refs.
+ */
+static int quickfetch(struct ref *ref_map)
+{
+ struct child_process revlist;
+ struct ref *ref;
+ char **argv;
+ int i, err;
+
+ /*
+ * If we are deepening a shallow clone we already have these
+ * objects reachable. Running rev-list here will return with
+ * a good (0) exit status and we'll bypass the fetch that we
+ * really need to perform. Claiming failure now will ensure
+ * we perform the network exchange to deepen our history.
+ */
+ if (depth)
+ return -1;
+
+ for (i = 0, ref = ref_map; ref; ref = ref->next)
+ i++;
+ if (!i)
+ return 0;
+
+ argv = xmalloc(sizeof(*argv) * (i + 6));
+ i = 0;
+ argv[i++] = xstrdup("rev-list");
+ argv[i++] = xstrdup("--quiet");
+ argv[i++] = xstrdup("--objects");
+ for (ref = ref_map; ref; ref = ref->next)
+ argv[i++] = xstrdup(sha1_to_hex(ref->old_sha1));
+ argv[i++] = xstrdup("--not");
+ argv[i++] = xstrdup("--all");
+ argv[i++] = NULL;
+
+ memset(&revlist, 0, sizeof(revlist));
+ revlist.argv = (const char**)argv;
+ revlist.git_cmd = 1;
+ revlist.no_stdin = 1;
+ revlist.no_stdout = 1;
+ revlist.no_stderr = 1;
+ err = run_command(&revlist);
+
+ for (i = 0; argv[i]; i++)
+ free(argv[i]);
+ free(argv);
+ return err;
+}
+
+static int fetch_refs(struct transport *transport, struct ref *ref_map)
+{
+ int ret = quickfetch(ref_map);
+ if (ret)
+ ret = transport_fetch_refs(transport, ref_map);
+ if (!ret)
+ ret |= store_updated_refs(transport->url, ref_map);
+ transport_unlock_pack(transport);
+ return ret;
+}
+
+static int add_existing(const char *refname, const unsigned char *sha1,
+ int flag, void *cbdata)
+{
+ struct path_list *list = (struct path_list *)cbdata;
+ path_list_insert(refname, list);
+ return 0;
+}
+
+static struct ref *find_non_local_tags(struct transport *transport,
+ struct ref *fetch_map)
+{
+ static struct path_list existing_refs = { NULL, 0, 0, 0 };
+ struct path_list new_refs = { NULL, 0, 0, 1 };
+ char *ref_name;
+ int ref_name_len;
+ const unsigned char *ref_sha1;
+ const struct ref *tag_ref;
+ struct ref *rm = NULL;
+ struct ref *ref_map = NULL;
+ struct ref **tail = &ref_map;
+ const struct ref *ref;
+
+ for_each_ref(add_existing, &existing_refs);
+ for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
+ if (prefixcmp(ref->name, "refs/tags"))
+ continue;
+
+ ref_name = xstrdup(ref->name);
+ ref_name_len = strlen(ref_name);
+ ref_sha1 = ref->old_sha1;
+
+ if (!strcmp(ref_name + ref_name_len - 3, "^{}")) {
+ ref_name[ref_name_len - 3] = 0;
+ tag_ref = transport_get_remote_refs(transport);
+ while (tag_ref) {
+ if (!strcmp(tag_ref->name, ref_name)) {
+ ref_sha1 = tag_ref->old_sha1;
+ break;
+ }
+ tag_ref = tag_ref->next;
+ }
+ }
+
+ if (!path_list_has_path(&existing_refs, ref_name) &&
+ !path_list_has_path(&new_refs, ref_name) &&
+ has_sha1_file(ref->old_sha1)) {
+ path_list_insert(ref_name, &new_refs);
+
+ rm = alloc_ref(strlen(ref_name) + 1);
+ strcpy(rm->name, ref_name);
+ rm->peer_ref = alloc_ref(strlen(ref_name) + 1);
+ strcpy(rm->peer_ref->name, ref_name);
+ hashcpy(rm->old_sha1, ref_sha1);
+
+ *tail = rm;
+ tail = &rm->next;
+ }
+ free(ref_name);
+ }
+
+ return ref_map;
+}
+
+static int do_fetch(struct transport *transport,
+ struct refspec *refs, int ref_count)
+{
+ struct ref *ref_map, *fetch_map;
+ struct ref *rm;
+ int autotags = (transport->remote->fetch_tags == 1);
+ if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
+ 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);
+
+ /* if not appending, truncate FETCH_HEAD */
+ if (!append) {
+ char *filename = git_path("FETCH_HEAD");
+ FILE *fp = fopen(filename, "w");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
+ fclose(fp);
+ }
+
+ ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
+
+ for (rm = ref_map; rm; rm = rm->next) {
+ if (rm->peer_ref)
+ read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
+ }
+
+ if (fetch_refs(transport, ref_map)) {
+ free_refs(ref_map);
+ return 1;
+ }
+
+ fetch_map = ref_map;
+
+ /* if neither --no-tags nor --tags was specified, do automated tag
+ * following ... */
+ if (tags == TAGS_DEFAULT && autotags) {
+ ref_map = find_non_local_tags(transport, fetch_map);
+ if (ref_map) {
+ transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+ fetch_refs(transport, ref_map);
+ }
+ free_refs(ref_map);
+ }
+
+ free_refs(fetch_map);
+
+ transport_disconnect(transport);
+
+ return 0;
+}
+
+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\n",
+ name, value, transport->url);
+ if (r > 0)
+ warning("Option \"%s\" is ignored for %s\n",
+ name, transport->url);
+}
+
+int cmd_fetch(int argc, const char **argv, const char *prefix)
+{
+ struct remote *remote;
+ int i;
+ static const char **refs = NULL;
+ int ref_nr = 0;
+
+ /* Record the command line for the reflog */
+ strbuf_addstr(&default_rla, "fetch");
+ for (i = 1; i < argc; i++)
+ strbuf_addf(&default_rla, " %s", argv[i]);
+
+ argc = parse_options(argc, argv,
+ builtin_fetch_options, builtin_fetch_usage, 0);
+
+ if (argc == 0)
+ remote = remote_get(NULL);
+ else
+ remote = remote_get(argv[0]);
+
+ transport = transport_get(remote, remote->url[0]);
+ if (verbose >= 2)
+ transport->verbose = 1;
+ if (quiet)
+ transport->verbose = -1;
+ if (upload_pack)
+ set_option(TRANS_OPT_UPLOADPACK, upload_pack);
+ if (keep)
+ set_option(TRANS_OPT_KEEP, "yes");
+ if (depth)
+ set_option(TRANS_OPT_DEPTH, depth);
+
+ if (!transport->url)
+ die("Where do you want to fetch from today?");
+
+ if (argc > 1) {
+ int j = 0;
+ refs = xcalloc(argc + 1, sizeof(const char *));
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "tag")) {
+ char *ref;
+ i++;
+ ref = xmalloc(strlen(argv[i]) * 2 + 22);
+ strcpy(ref, "refs/tags/");
+ strcat(ref, argv[i]);
+ strcat(ref, ":refs/tags/");
+ strcat(ref, argv[i]);
+ refs[j++] = ref;
+ } else
+ refs[j++] = argv[i];
+ }
+ refs[j] = NULL;
+ ref_nr = j;
+ }
+
+ signal(SIGINT, unlock_pack_on_signal);
+ atexit(unlock_pack);
+ return do_fetch(transport, parse_ref_spec(ref_nr, refs), ref_nr);
+}
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
index 8a3c962f89..ebb3f37cf1 100644
--- a/builtin-fmt-merge-msg.c
+++ b/builtin-fmt-merge-msg.c
@@ -176,7 +176,7 @@ static void shortlog(const char *name, unsigned char *sha1,
struct commit *commit;
struct object *branch;
struct list subjects = { NULL, NULL, 0, 0 };
- int flags = UNINTERESTING | TREECHANGE | SEEN | SHOWN | ADDED;
+ int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
if (!branch || branch->type != OBJ_COMMIT)
@@ -187,7 +187,8 @@ static void shortlog(const char *name, unsigned char *sha1,
add_pending_object(rev, branch, name);
add_pending_object(rev, &head->object, "^HEAD");
head->object.flags |= UNINTERESTING;
- prepare_revision_walk(rev);
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
while ((commit = get_revision(rev)) != NULL) {
char *oneline, *bol, *eol;
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
index c74ef2800c..f36a43c264 100644
--- a/builtin-for-each-ref.c
+++ b/builtin-for-each-ref.c
@@ -7,13 +7,14 @@
#include "tree.h"
#include "blob.h"
#include "quote.h"
+#include "parse-options.h"
/* Quoting styles */
#define QUOTE_NONE 0
#define QUOTE_SHELL 1
#define QUOTE_PERL 2
-#define QUOTE_PYTHON 3
-#define QUOTE_TCL 4
+#define QUOTE_PYTHON 4
+#define QUOTE_TCL 8
typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
@@ -158,17 +159,18 @@ static const char *find_next(const char *cp)
* Make sure the format string is well formed, and parse out
* the used atoms.
*/
-static void verify_format(const char *format)
+static int verify_format(const char *format)
{
const char *cp, *sp;
for (cp = format; *cp && (sp = find_next(cp)); ) {
const char *ep = strchr(sp, ')');
if (!ep)
- die("malformatted format string %s", sp);
+ return error("malformatted format string %s", sp);
/* sp points at "%(" and ep points at the closing ")" */
parse_atom(sp + 2, ep);
cp = ep + 1;
}
+ return 0;
}
/*
@@ -302,7 +304,7 @@ static const char *find_wholine(const char *who, int wholen, const char *buf, un
if (!eol)
return "";
eol++;
- if (eol[1] == '\n')
+ if (*eol == '\n')
return ""; /* end of header */
buf = eol;
}
@@ -800,94 +802,77 @@ static struct ref_sort *default_sort(void)
return sort;
}
-int cmd_for_each_ref(int ac, const char **av, const char *prefix)
+int opt_parse_sort(const struct option *opt, const char *arg, int unset)
+{
+ struct ref_sort **sort_tail = opt->value;
+ struct ref_sort *s;
+ int len;
+
+ if (!arg) /* should --no-sort void the list ? */
+ return -1;
+
+ *sort_tail = s = xcalloc(1, sizeof(*s));
+ sort_tail = &s->next;
+
+ if (*arg == '-') {
+ s->reverse = 1;
+ arg++;
+ }
+ len = strlen(arg);
+ s->atom = parse_atom(arg, arg+len);
+ return 0;
+}
+
+static char const * const for_each_ref_usage[] = {
+ "git-for-each-ref [options] [<pattern>]",
+ NULL
+};
+
+int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
{
int i, num_refs;
- const char *format = NULL;
+ const char *format = "%(objectname) %(objecttype)\t%(refname)";
struct ref_sort *sort = NULL, **sort_tail = &sort;
- int maxcount = 0;
- int quote_style = -1; /* unspecified yet */
+ int maxcount = 0, quote_style = 0;
struct refinfo **refs;
struct grab_ref_cbdata cbdata;
- for (i = 1; i < ac; i++) {
- const char *arg = av[i];
- if (arg[0] != '-')
- break;
- if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- if (!prefixcmp(arg, "--format=")) {
- if (format)
- die("more than one --format?");
- format = arg + 9;
- continue;
- }
- if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) {
- if (0 <= quote_style)
- die("more than one quoting style?");
- quote_style = QUOTE_SHELL;
- continue;
- }
- if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) {
- if (0 <= quote_style)
- die("more than one quoting style?");
- quote_style = QUOTE_PERL;
- continue;
- }
- if (!strcmp(arg, "--python") ) {
- if (0 <= quote_style)
- die("more than one quoting style?");
- quote_style = QUOTE_PYTHON;
- continue;
- }
- if (!strcmp(arg, "--tcl") ) {
- if (0 <= quote_style)
- die("more than one quoting style?");
- quote_style = QUOTE_TCL;
- continue;
- }
- if (!prefixcmp(arg, "--count=")) {
- if (maxcount)
- die("more than one --count?");
- maxcount = atoi(arg + 8);
- if (maxcount <= 0)
- die("The number %s did not parse", arg);
- continue;
- }
- if (!prefixcmp(arg, "--sort=")) {
- struct ref_sort *s = xcalloc(1, sizeof(*s));
- int len;
-
- s->next = NULL;
- *sort_tail = s;
- sort_tail = &s->next;
-
- arg += 7;
- if (*arg == '-') {
- s->reverse = 1;
- arg++;
- }
- len = strlen(arg);
- sort->atom = parse_atom(arg, arg+len);
- continue;
- }
- break;
+ struct option opts[] = {
+ OPT_BIT('s', "shell", &quote_style,
+ "quote placeholders suitably for shells", QUOTE_SHELL),
+ OPT_BIT('p', "perl", &quote_style,
+ "quote placeholders suitably for perl", QUOTE_PERL),
+ OPT_BIT(0 , "python", &quote_style,
+ "quote placeholders suitably for python", QUOTE_PYTHON),
+ OPT_BIT(0 , "tcl", &quote_style,
+ "quote placeholders suitably for tcl", QUOTE_TCL),
+
+ OPT_GROUP(""),
+ OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
+ OPT_STRING( 0 , "format", &format, "format", "format to use for the output"),
+ OPT_CALLBACK(0 , "sort", sort_tail, "key",
+ "field name to sort on", &opt_parse_sort),
+ OPT_END(),
+ };
+
+ parse_options(argc, argv, opts, for_each_ref_usage, 0);
+ if (maxcount < 0) {
+ error("invalid --count argument: `%d'", maxcount);
+ usage_with_options(for_each_ref_usage, opts);
}
- if (quote_style < 0)
- quote_style = QUOTE_NONE;
+ if (HAS_MULTI_BITS(quote_style)) {
+ error("more than one quoting style?");
+ usage_with_options(for_each_ref_usage, opts);
+ }
+ if (verify_format(format))
+ usage_with_options(for_each_ref_usage, opts);
if (!sort)
sort = default_sort();
sort_atom_limit = used_atom_cnt;
- if (!format)
- format = "%(objectname) %(objecttype)\t%(refname)";
-
- verify_format(format);
memset(&cbdata, 0, sizeof(cbdata));
- cbdata.grab_pattern = av + i;
+ cbdata.grab_pattern = argv;
for_each_ref(grab_single_ref, &cbdata);
refs = cbdata.grab_array;
num_refs = cbdata.grab_cnt;
diff --git a/builtin-fsck.c b/builtin-fsck.c
index 8d12287f03..cc7524be80 100644
--- a/builtin-fsck.c
+++ b/builtin-fsck.c
@@ -8,6 +8,7 @@
#include "pack.h"
#include "cache-tree.h"
#include "tree-walk.h"
+#include "parse-options.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
@@ -359,6 +360,9 @@ static int fsck_commit(struct commit *commit)
fprintf(stderr, "Checking commit %s\n",
sha1_to_hex(commit->object.sha1));
+ if (!commit->date)
+ return objerror(&commit->object, "invalid author/committer line");
+
if (memcmp(buffer, "tree ", 5))
return objerror(&commit->object, "invalid format - expected 'tree' line");
if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
@@ -377,9 +381,6 @@ static int fsck_commit(struct commit *commit)
return objerror(&commit->object, "could not load commit's tree %s", tree_sha1);
if (!commit->parents && show_root)
printf("root %s\n", sha1_to_hex(commit->object.sha1));
- if (!commit->date)
- printf("bad commit date in %s\n",
- sha1_to_hex(commit->object.sha1));
return 0;
}
@@ -554,20 +555,23 @@ static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, in
return 0;
}
+static int is_branch(const char *refname)
+{
+ return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
+}
+
static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *obj;
- obj = lookup_object(sha1);
+ obj = parse_object(sha1);
if (!obj) {
- if (has_sha1_file(sha1)) {
- default_refs++;
- return 0; /* it is in a pack */
- }
error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
/* We'll continue with the rest despite the error.. */
return 0;
}
+ if (obj->type != OBJ_COMMIT && is_branch(refname))
+ error("%s: not a commit", refname);
default_refs++;
obj->used = 1;
mark_reachable(obj, REACHABLE);
@@ -666,9 +670,24 @@ static int fsck_cache_tree(struct cache_tree *it)
return err;
}
-static const char fsck_usage[] =
-"git-fsck [--tags] [--root] [[--unreachable] [--cache] [--full] "
-"[--strict] [--verbose] <head-sha1>*]";
+static char const * const fsck_usage[] = {
+ "git-fsck [options] [<object>...]",
+ NULL
+};
+
+static struct option fsck_opts[] = {
+ OPT__VERBOSE(&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"),
+ OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"),
+ OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"),
+ OPT_BOOLEAN(0, "full", &check_full, "also consider alternate objects"),
+ OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
+ OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
+ "write dangling objects in .git/lost-found"),
+ OPT_END(),
+};
int cmd_fsck(int argc, const char **argv, const char *prefix)
{
@@ -677,49 +696,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
track_object_refs = 1;
errors_found = 0;
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (!strcmp(arg, "--unreachable")) {
- show_unreachable = 1;
- continue;
- }
- if (!strcmp(arg, "--tags")) {
- show_tags = 1;
- continue;
- }
- if (!strcmp(arg, "--root")) {
- show_root = 1;
- continue;
- }
- if (!strcmp(arg, "--cache")) {
- keep_cache_objects = 1;
- continue;
- }
- if (!strcmp(arg, "--no-reflogs")) {
- include_reflogs = 0;
- continue;
- }
- if (!strcmp(arg, "--full")) {
- check_full = 1;
- continue;
- }
- if (!strcmp(arg, "--strict")) {
- check_strict = 1;
- continue;
- }
- if (!strcmp(arg, "--verbose")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(arg, "--lost-found")) {
- check_full = 1;
- include_reflogs = 0;
- write_lost_and_found = 1;
- continue;
- }
- if (*arg == '-')
- usage(fsck_usage);
+ argc = parse_options(argc, argv, fsck_opts, fsck_usage, 0);
+ if (write_lost_and_found) {
+ check_full = 1;
+ include_reflogs = 0;
}
fsck_head_link();
@@ -741,22 +721,18 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
verify_pack(p, 0);
for (p = packed_git; p; p = p->next) {
- uint32_t i, num;
+ uint32_t j, num;
if (open_pack_index(p))
continue;
num = p->num_objects;
- for (i = 0; i < num; i++)
- fsck_sha1(nth_packed_object_sha1(p, i));
+ for (j = 0; j < num; j++)
+ fsck_sha1(nth_packed_object_sha1(p, j));
}
}
heads = 0;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
-
- if (*arg == '-')
- continue;
-
if (!get_sha1(arg, head_sha1)) {
struct object *obj = lookup_object(head_sha1);
@@ -783,14 +759,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
}
if (keep_cache_objects) {
- int i;
read_cache();
for (i = 0; i < active_nr; i++) {
unsigned int mode;
struct blob *blob;
struct object *obj;
- mode = ntohl(active_cache[i]->ce_mode);
+ mode = active_cache[i]->ce_mode;
if (S_ISGITLINK(mode))
continue;
blob = lookup_blob(active_cache[i]->sha1);
diff --git a/builtin-gc.c b/builtin-gc.c
index 956c32d1af..ad4a75eedd 100644
--- a/builtin-gc.c
+++ b/builtin-gc.c
@@ -12,11 +12,15 @@
#include "builtin.h"
#include "cache.h"
+#include "parse-options.h"
#include "run-command.h"
#define FAILED_RUN "failed to run %s"
-static const char builtin_gc_usage[] = "git-gc [--prune] [--aggressive]";
+static const char * const builtin_gc_usage[] = {
+ "git-gc [options]",
+ NULL
+};
static int pack_refs = 1;
static int aggressive_window = -1;
@@ -33,7 +37,7 @@ static const char *argv_rerere[] = {"rerere", "gc", NULL};
static int gc_config(const char *var, const char *value)
{
if (!strcmp(var, "gc.packrefs")) {
- if (!strcmp(value, "notbare"))
+ if (value && !strcmp(value, "notbare"))
pack_refs = -1;
else
pack_refs = git_config_bool(var, value);
@@ -165,38 +169,34 @@ static int need_to_gc(void)
int cmd_gc(int argc, const char **argv, const char *prefix)
{
- int i;
int prune = 0;
+ int aggressive = 0;
int auto_gc = 0;
char buf[80];
+ struct option builtin_gc_options[] = {
+ OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects"),
+ OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
+ OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
+ OPT_END()
+ };
+
git_config(gc_config);
if (pack_refs < 0)
pack_refs = !is_bare_repository();
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--prune")) {
- prune = 1;
- continue;
- }
- if (!strcmp(arg, "--aggressive")) {
- append_option(argv_repack, "-f", MAX_ADD);
- if (aggressive_window > 0) {
- sprintf(buf, "--window=%d", aggressive_window);
- append_option(argv_repack, buf, MAX_ADD);
- }
- continue;
- }
- if (!strcmp(arg, "--auto")) {
- auto_gc = 1;
- continue;
+ argc = parse_options(argc, argv, builtin_gc_options, builtin_gc_usage, 0);
+ if (argc > 0)
+ usage_with_options(builtin_gc_usage, builtin_gc_options);
+
+ if (aggressive) {
+ append_option(argv_repack, "-f", MAX_ADD);
+ if (aggressive_window > 0) {
+ sprintf(buf, "--window=%d", aggressive_window);
+ append_option(argv_repack, buf, MAX_ADD);
}
- break;
}
- if (i != argc)
- usage(builtin_gc_usage);
if (auto_gc) {
/*
@@ -205,6 +205,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
prune = 0;
if (!need_to_gc())
return 0;
+ fprintf(stderr, "Auto packing your repository for optimum "
+ "performance. You may also\n"
+ "run \"git gc\" manually. See "
+ "\"git help gc\" for more information.\n");
} else {
/*
* Use safer (for shared repos) "-A" option to
diff --git a/builtin-grep.c b/builtin-grep.c
index c7b45c4d58..f4f4ecb11b 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -294,7 +294,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
if (opt->pre_context) {
push_arg("-B");
len += snprintf(argptr, sizeof(randarg)-len,
- "%u", opt->pre_context);
+ "%u", opt->pre_context) + 1;
if (sizeof(randarg) <= len)
die("maximum length of args exceeded");
push_arg(argptr);
@@ -303,7 +303,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
if (opt->post_context) {
push_arg("-A");
len += snprintf(argptr, sizeof(randarg)-len,
- "%u", opt->post_context);
+ "%u", opt->post_context) + 1;
if (sizeof(randarg) <= len)
die("maximum length of args exceeded");
push_arg(argptr);
@@ -313,7 +313,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
else {
push_arg("-C");
len += snprintf(argptr, sizeof(randarg)-len,
- "%u", opt->post_context);
+ "%u", opt->post_context) + 1;
if (sizeof(randarg) <= len)
die("maximum length of args exceeded");
push_arg(argptr);
@@ -331,7 +331,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
struct cache_entry *ce = active_cache[i];
char *name;
int kept;
- if (!S_ISREG(ntohl(ce->ce_mode)))
+ if (!S_ISREG(ce->ce_mode))
continue;
if (!pathspec_matches(paths, ce->name))
continue;
@@ -343,12 +343,12 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
memcpy(name + 2, ce->name, len + 1);
}
argv[argc++] = name;
- if (argc < MAXARGS && !ce_stage(ce))
- continue;
- status = flush_grep(opt, argc, nr, argv, &kept);
- if (0 < status)
- hit = 1;
- argc = nr + kept;
+ if (MAXARGS <= argc) {
+ status = flush_grep(opt, argc, nr, argv, &kept);
+ if (0 < status)
+ hit = 1;
+ argc = nr + kept;
+ }
if (ce_stage(ce)) {
do {
i++;
@@ -387,7 +387,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
for (nr = 0; nr < active_nr; nr++) {
struct cache_entry *ce = active_cache[nr];
- if (!S_ISREG(ntohl(ce->ce_mode)))
+ if (!S_ISREG(ce->ce_mode))
continue;
if (!pathspec_matches(paths, ce->name))
continue;
@@ -578,6 +578,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp("-l", arg) ||
+ !strcmp("--name-only", arg) ||
!strcmp("--files-with-matches", arg)) {
opt.name_only = 1;
continue;
@@ -644,7 +645,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
die("'%s': %s", argv[1], strerror(errno));
while (fgets(buf, sizeof(buf), patterns)) {
int len = strlen(buf);
- if (buf[len-1] == '\n')
+ if (len && buf[len-1] == '\n')
buf[len-1] = 0;
/* ignore empty line like grep does */
if (!buf[0])
diff --git a/builtin-http-fetch.c b/builtin-http-fetch.c
new file mode 100644
index 0000000000..7f450c61d9
--- /dev/null
+++ b/builtin-http-fetch.c
@@ -0,0 +1,87 @@
+#include "cache.h"
+#include "walker.h"
+
+int cmd_http_fetch(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;
+ int arg = 1;
+ int rc = 0;
+ int get_tree = 0;
+ int get_history = 0;
+ int get_all = 0;
+ int get_verbosely = 0;
+ int get_recover = 0;
+
+ git_config(git_default_config);
+
+ while (arg < argc && argv[arg][0] == '-') {
+ if (argv[arg][1] == 't') {
+ get_tree = 1;
+ } else if (argv[arg][1] == 'c') {
+ get_history = 1;
+ } else if (argv[arg][1] == 'a') {
+ get_all = 1;
+ get_tree = 1;
+ get_history = 1;
+ } else if (argv[arg][1] == 'v') {
+ get_verbosely = 1;
+ } else if (argv[arg][1] == 'w') {
+ write_ref = &argv[arg + 1];
+ arg++;
+ } else if (!strcmp(argv[arg], "--recover")) {
+ get_recover = 1;
+ } else if (!strcmp(argv[arg], "--stdin")) {
+ commits_on_stdin = 1;
+ }
+ arg++;
+ }
+ if (argc < arg + 2 - commits_on_stdin) {
+ usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
+ return 1;
+ }
+ if (commits_on_stdin) {
+ commits = walker_targets_stdin(&commit_id, &write_ref);
+ } else {
+ commit_id = (char **) &argv[arg++];
+ commits = 1;
+ }
+ url = argv[arg];
+ if (url && url[strlen(url)-1] != '/') {
+ rewritten_url = malloc(strlen(url)+2);
+ strcpy(rewritten_url, url);
+ strcat(rewritten_url, "/");
+ url = rewritten_url;
+ }
+
+ walker = get_http_walker(url);
+ walker->get_tree = get_tree;
+ walker->get_history = get_history;
+ walker->get_all = get_all;
+ walker->get_verbosely = get_verbosely;
+ walker->get_recover = get_recover;
+
+ rc = walker_fetch(walker, commits, commit_id, write_ref, url);
+
+ if (commits_on_stdin)
+ walker_targets_free(commits, commit_id, write_ref);
+
+ if (walker->corrupt_object_found) {
+ fprintf(stderr,
+"Some loose object were found to be corrupt, but they might be just\n"
+"a false '404 Not Found' error message sent with incorrect HTTP\n"
+"status code. Suggest running git-fsck.\n");
+ }
+
+ walker_free(walker);
+
+ if (rewritten_url)
+ free(rewritten_url);
+
+ return rc;
+}
diff --git a/builtin-init-db.c b/builtin-init-db.c
index 763fa55e76..5d7cdda933 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -5,6 +5,7 @@
*/
#include "cache.h"
#include "builtin.h"
+#include "exec_cmd.h"
#ifndef DEFAULT_GIT_TEMPLATE_DIR
#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
@@ -131,10 +132,19 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
int template_len;
DIR *dir;
- if (!template_dir) {
+ if (!template_dir)
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
- if (!template_dir)
- template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+ if (!template_dir) {
+ /*
+ * if the hard-coded template is relative, it is
+ * interpreted relative to the exec_dir
+ */
+ template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+ if (!is_absolute_path(template_dir)) {
+ struct strbuf d = STRBUF_INIT;
+ strbuf_addf(&d, "%s/%s", git_exec_path(), template_dir);
+ template_dir = strbuf_detach(&d, NULL);
+ }
}
strcpy(template_path, template_dir);
template_len = strlen(template_path);
diff --git a/builtin-log.c b/builtin-log.c
index e8b982db7c..c67d63cb1c 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -5,6 +5,7 @@
* 2006 Junio Hamano
*/
#include "cache.h"
+#include "color.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
@@ -18,9 +19,6 @@
static int default_show_root = 1;
static const char *fmt_patch_subject_prefix = "PATCH";
-/* this is in builtin-diff.c */
-void add_head(struct rev_info *revs);
-
static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
{
int plen = strlen(prefix);
@@ -55,13 +53,13 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
rev->abbrev = DEFAULT_ABBREV;
rev->commit_format = CMIT_FMT_DEFAULT;
rev->verbose_header = 1;
- rev->diffopt.recursive = 1;
+ DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
rev->show_root_diff = default_show_root;
rev->subject_prefix = fmt_patch_subject_prefix;
argc = setup_revisions(argc, argv, rev, "HEAD");
if (rev->diffopt.pickaxe || rev->diffopt.filter)
rev->always_show_header = 0;
- if (rev->diffopt.follow_renames) {
+ if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
rev->always_show_header = 0;
if (rev->diffopt.nr_paths != 1)
usage("git logs can only follow renames on one pathname at a time");
@@ -77,11 +75,135 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
}
}
+/*
+ * This gives a rough estimate for how many commits we
+ * will print out in the list.
+ */
+static int estimate_commit_count(struct rev_info *rev, struct commit_list *list)
+{
+ int n = 0;
+
+ while (list) {
+ struct commit *commit = list->item;
+ unsigned int flags = commit->object.flags;
+ list = list->next;
+ if (!(flags & (TREESAME | UNINTERESTING)))
+ n++;
+ }
+ return n;
+}
+
+static void show_early_header(struct rev_info *rev, const char *stage, int nr)
+{
+ if (rev->shown_one) {
+ rev->shown_one = 0;
+ if (rev->commit_format != CMIT_FMT_ONELINE)
+ putchar(rev->diffopt.line_termination);
+ }
+ printf("Final output: %d %s\n", nr, stage);
+}
+
+struct itimerval early_output_timer;
+
+static void log_show_early(struct rev_info *revs, struct commit_list *list)
+{
+ int i = revs->early_output;
+ int show_header = 1;
+
+ sort_in_topological_order(&list, revs->lifo);
+ while (list && i) {
+ struct commit *commit = list->item;
+ switch (simplify_commit(revs, commit)) {
+ case commit_show:
+ if (show_header) {
+ int n = estimate_commit_count(revs, list);
+ show_early_header(revs, "incomplete", n);
+ show_header = 0;
+ }
+ log_tree_commit(revs, commit);
+ i--;
+ break;
+ case commit_ignore:
+ break;
+ case commit_error:
+ return;
+ }
+ list = list->next;
+ }
+
+ /* Did we already get enough commits for the early output? */
+ if (!i)
+ return;
+
+ /*
+ * ..if no, then repeat it twice a second until we
+ * do.
+ *
+ * NOTE! We don't use "it_interval", because if the
+ * reader isn't listening, we want our output to be
+ * throttled by the writing, and not have the timer
+ * trigger every second even if we're blocked on a
+ * reader!
+ */
+ early_output_timer.it_value.tv_sec = 0;
+ early_output_timer.it_value.tv_usec = 500000;
+ setitimer(ITIMER_REAL, &early_output_timer, NULL);
+}
+
+static void early_output(int signal)
+{
+ show_early_output = log_show_early;
+}
+
+static void setup_early_output(struct rev_info *rev)
+{
+ struct sigaction sa;
+
+ /*
+ * Set up the signal handler, minimally intrusively:
+ * we only set a single volatile integer word (not
+ * using sigatomic_t - trying to avoid unnecessary
+ * system dependencies and headers), and using
+ * SA_RESTART.
+ */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = early_output;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGALRM, &sa, NULL);
+
+ /*
+ * If we can get the whole output in less than a
+ * tenth of a second, don't even bother doing the
+ * early-output thing..
+ *
+ * This is a one-time-only trigger.
+ */
+ early_output_timer.it_value.tv_sec = 0;
+ early_output_timer.it_value.tv_usec = 100000;
+ setitimer(ITIMER_REAL, &early_output_timer, NULL);
+}
+
+static void finish_early_output(struct rev_info *rev)
+{
+ int n = estimate_commit_count(rev, rev->commits);
+ signal(SIGALRM, SIG_IGN);
+ show_early_header(rev, "done", n);
+}
+
static int cmd_log_walk(struct rev_info *rev)
{
struct commit *commit;
- prepare_revision_walk(rev);
+ if (rev->early_output)
+ setup_early_output(rev);
+
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
+
+ if (rev->early_output)
+ finish_early_output(rev);
+
while ((commit = get_revision(rev)) != NULL) {
log_tree_commit(rev, commit);
if (!rev->reflog_info) {
@@ -99,7 +221,7 @@ static int git_log_config(const char *var, const char *value)
{
if (!strcmp(var, "format.subjectprefix")) {
if (!value)
- die("format.subjectprefix without value");
+ config_error_nonbool(var);
fmt_patch_subject_prefix = xstrdup(value);
return 0;
}
@@ -115,6 +237,10 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
struct rev_info rev;
git_config(git_log_config);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
init_revisions(&rev, prefix);
rev.diff = 1;
rev.simplify_history = 0;
@@ -124,7 +250,28 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
return cmd_log_walk(&rev);
}
-static int show_object(const unsigned char *sha1, int suppress_header)
+static void show_tagger(char *buf, int len, struct rev_info *rev)
+{
+ char *email_end, *p;
+ unsigned long date;
+ int tz;
+
+ email_end = memchr(buf, '>', len);
+ if (!email_end)
+ return;
+ p = ++email_end;
+ while (isspace(*p))
+ p++;
+ date = strtoul(p, &p, 10);
+ while (isspace(*p))
+ p++;
+ tz = (int)strtol(p, NULL, 10);
+ printf("Tagger: %.*s\nDate: %s\n", (int)(email_end - buf), buf,
+ show_date(date, tz, rev->date_mode));
+}
+
+static int show_object(const unsigned char *sha1, int show_tag_object,
+ struct rev_info *rev)
{
unsigned long size;
enum object_type type;
@@ -134,11 +281,14 @@ static int show_object(const unsigned char *sha1, int suppress_header)
if (!buf)
return error("Could not read object %s", sha1_to_hex(sha1));
- if (suppress_header)
- while (offset < size && buf[offset++] != '\n') {
- int new_offset = offset;
+ if (show_tag_object)
+ while (offset < size && buf[offset] != '\n') {
+ int new_offset = offset + 1;
while (new_offset < size && buf[new_offset++] != '\n')
; /* do nothing */
+ if (!prefixcmp(buf + offset, "tagger "))
+ show_tagger(buf + offset + 7,
+ new_offset - offset - 7, rev);
offset = new_offset;
}
@@ -163,6 +313,10 @@ int cmd_show(int argc, const char **argv, const char *prefix)
int i, count, ret = 0;
git_config(git_log_config);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
init_revisions(&rev, prefix);
rev.diff = 1;
rev.combine_merges = 1;
@@ -179,29 +333,25 @@ int cmd_show(int argc, const char **argv, const char *prefix)
const char *name = objects[i].name;
switch (o->type) {
case OBJ_BLOB:
- ret = show_object(o->sha1, 0);
+ ret = show_object(o->sha1, 0, NULL);
break;
case OBJ_TAG: {
struct tag *t = (struct tag *)o;
- printf("%stag %s%s\n\n",
- diff_get_color(rev.diffopt.color_diff,
- DIFF_COMMIT),
+ printf("%stag %s%s\n",
+ diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
t->tag,
- diff_get_color(rev.diffopt.color_diff,
- DIFF_RESET));
- ret = show_object(o->sha1, 1);
+ diff_get_color_opt(&rev.diffopt, DIFF_RESET));
+ ret = show_object(o->sha1, 1, &rev);
objects[i].item = (struct object *)t->tagged;
i--;
break;
}
case OBJ_TREE:
printf("%stree %s%s\n\n",
- diff_get_color(rev.diffopt.color_diff,
- DIFF_COMMIT),
+ diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
name,
- diff_get_color(rev.diffopt.color_diff,
- DIFF_RESET));
+ diff_get_color_opt(&rev.diffopt, DIFF_RESET));
read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
show_tree_object);
break;
@@ -227,6 +377,10 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
struct rev_info rev;
git_config(git_log_config);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
init_revisions(&rev, prefix);
init_reflog_walk(&rev.reflog_info);
rev.abbrev_commit = 1;
@@ -255,6 +409,10 @@ int cmd_log(int argc, const char **argv, const char *prefix)
struct rev_info rev;
git_config(git_log_config);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
init_revisions(&rev, prefix);
rev.always_show_header = 1;
cmd_log_init(argc, argv, prefix, &rev);
@@ -273,6 +431,8 @@ static int istitlechar(char c)
static char *extra_headers = NULL;
static int extra_headers_size = 0;
static const char *fmt_patch_suffix = ".patch";
+static int numbered = 0;
+static int auto_number = 0;
static int git_format_config(const char *var, const char *value)
{
@@ -290,13 +450,21 @@ static int git_format_config(const char *var, const char *value)
}
if (!strcmp(var, "format.suffix")) {
if (!value)
- die("format.suffix without value");
+ return config_error_nonbool(var);
fmt_patch_suffix = xstrdup(value);
return 0;
}
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
return 0;
}
+ if (!strcmp(var, "format.numbered")) {
+ if (value && !strcasecmp(value, "auto")) {
+ auto_number = 1;
+ return 0;
+ }
+ numbered = git_config_bool(var, value);
+ return 0;
+ }
return git_log_config(var, value);
}
@@ -406,7 +574,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
o2->flags ^= UNINTERESTING;
add_pending_object(&check_rev, o1, "o1");
add_pending_object(&check_rev, o2, "o2");
- prepare_revision_walk(&check_rev);
+ if (prepare_revision_walk(&check_rev))
+ die("revision walk setup failed");
while ((commit = get_revision(&check_rev)) != NULL) {
/* ignore merges */
@@ -427,7 +596,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
static void gen_message_id(char *dest, unsigned int length, char *base)
{
- const char *committer = git_committer_info(-1);
+ const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
const char *email_start = strrchr(committer, '<');
const char *email_end = strrchr(committer, '>');
if(!email_start || !email_end || email_start > email_end - 1)
@@ -466,7 +635,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
struct rev_info rev;
int nr = 0, total, i, j;
int use_stdout = 0;
- int numbered = 0;
int start_number = -1;
int keep_subject = 0;
int numbered_files = 0; /* _just_ numbers */
@@ -487,7 +655,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.combine_merges = 0;
rev.ignore_merges = 1;
rev.diffopt.msg_sep = "";
- rev.diffopt.recursive = 1;
+ DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
rev.subject_prefix = fmt_patch_subject_prefix;
rev.extra_headers = extra_headers;
@@ -503,6 +671,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
else if (!strcmp(argv[i], "-n") ||
!strcmp(argv[i], "--numbered"))
numbered = 1;
+ else if (!strcmp(argv[i], "-N") ||
+ !strcmp(argv[i], "--no-numbered")) {
+ numbered = 0;
+ auto_number = 0;
+ }
else if (!prefixcmp(argv[i], "--start-number="))
start_number = strtol(argv[i] + 15, NULL, 10);
else if (!strcmp(argv[i], "--numbered-files"))
@@ -531,7 +704,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
!strcmp(argv[i], "-s")) {
const char *committer;
const char *endpos;
- committer = git_committer_info(1);
+ committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
endpos = strchr(committer, '>');
if (!endpos)
die("bogos committer info %s\n", committer);
@@ -590,8 +763,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH;
- if (!rev.diffopt.text)
- rev.diffopt.binary = 1;
+ if (!DIFF_OPT_TST(&rev.diffopt, TEXT))
+ DIFF_OPT_SET(&rev.diffopt, BINARY);
if (!output_directory && !use_stdout)
output_directory = prefix;
@@ -612,7 +785,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
* does not have.
*/
rev.pending.objects[0].item->flags |= UNINTERESTING;
- add_head(&rev);
+ add_head_to_pending(&rev);
}
/*
* Otherwise, it is "format-patch -22 HEAD", and/or
@@ -627,7 +800,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (!use_stdout)
realstdout = xfdopen(xdup(1), "w");
- prepare_revision_walk(&rev);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
while ((commit = get_revision(&rev)) != NULL) {
/* ignore merges */
if (commit->parents && commit->parents->next)
@@ -642,6 +816,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
list[nr - 1] = commit;
}
total = nr;
+ if (!keep_subject && auto_number && total > 1)
+ numbered = 1;
if (numbered)
rev.total = total + start_number - 1;
rev.add_signoff = add_signoff;
@@ -747,7 +923,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
revs.diff = 1;
revs.combine_merges = 0;
revs.ignore_merges = 1;
- revs.diffopt.recursive = 1;
+ DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
if (add_pending_commit(head, &revs, 0))
die("Unknown commit %s", head);
@@ -767,7 +943,8 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
die("Unknown commit %s", limit);
/* reverse the list of commits */
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
while ((commit = get_revision(&revs)) != NULL) {
/* ignore merges */
if (commit->parents && commit->parents->next)
@@ -787,7 +964,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
struct strbuf buf;
strbuf_init(&buf, 0);
pretty_print_commit(CMIT_FMT_ONELINE, commit,
- &buf, 0, NULL, NULL, 0);
+ &buf, 0, NULL, NULL, 0, 0);
printf("%c %s %s\n", sign,
sha1_to_hex(commit->object.sha1), buf.buf);
strbuf_release(&buf);
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index b70da1863b..25dbfb4499 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -38,28 +38,28 @@ static const char *tag_modified = "";
/*
- * Match a pathspec against a filename. The first "len" characters
+ * Match a pathspec against a filename. The first "skiplen" characters
* are the common prefix
*/
-static int match(const char **spec, char *ps_matched,
- const char *filename, int len)
+int pathspec_match(const char **spec, char *ps_matched,
+ const char *filename, int skiplen)
{
const char *m;
while ((m = *spec++) != NULL) {
- int matchlen = strlen(m + len);
+ int matchlen = strlen(m + skiplen);
if (!matchlen)
goto matched;
- if (!strncmp(m + len, filename + len, matchlen)) {
- if (m[len + matchlen - 1] == '/')
+ if (!strncmp(m + skiplen, filename + skiplen, matchlen)) {
+ if (m[skiplen + matchlen - 1] == '/')
goto matched;
- switch (filename[len + matchlen]) {
+ switch (filename[skiplen + matchlen]) {
case '/': case '\0':
goto matched;
}
}
- if (!fnmatch(m + len, filename + len, 0))
+ if (!fnmatch(m + skiplen, filename + skiplen, 0))
goto matched;
if (ps_matched)
ps_matched++;
@@ -80,7 +80,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
if (len >= ent->len)
die("git-ls-files: internal error - directory entry not superset of prefix");
- if (pathspec && !match(pathspec, ps_matched, ent->name, len))
+ if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len))
return;
fputs(tag, stdout);
@@ -185,11 +185,11 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
if (len >= ce_namelen(ce))
die("git-ls-files: internal error - cache entry not superset of prefix");
- if (pathspec && !match(pathspec, ps_matched, ce->name, len))
+ if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len))
return;
if (tag && *tag && show_valid_bit &&
- (ce->ce_flags & htons(CE_VALID))) {
+ (ce->ce_flags & CE_VALID)) {
static char alttag[4];
memcpy(alttag, tag, 3);
if (isalpha(tag[0]))
@@ -210,7 +210,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
} else {
printf("%s%06o %s %d\t",
tag,
- ntohl(ce->ce_mode),
+ ce->ce_mode,
abbrev ? find_unique_abbrev(ce->sha1,abbrev)
: sha1_to_hex(ce->sha1),
ce_stage(ce));
@@ -238,11 +238,12 @@ static void show_files(struct dir_struct *dir, const char *prefix)
if (show_cached | show_stage) {
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
- if (excluded(dir, ce->name) != dir->show_ignored)
+ int dtype = ce_to_dtype(ce);
+ if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
continue;
if (show_unmerged && !ce_stage(ce))
continue;
- if (ce->ce_flags & htons(CE_UPDATE))
+ if (ce->ce_flags & CE_UPDATE)
continue;
show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
}
@@ -252,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
struct cache_entry *ce = active_cache[i];
struct stat st;
int err;
- if (excluded(dir, ce->name) != dir->show_ignored)
+ int dtype = ce_to_dtype(ce);
+ if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
continue;
err = lstat(ce->name, &st);
if (show_deleted && err)
@@ -331,7 +333,7 @@ static const char *verify_pathspec(const char *prefix)
* that were given from the command line. We are not
* going to write this index out.
*/
-static void overlay_tree(const char *tree_name, const char *prefix)
+void overlay_tree_on_cache(const char *tree_name, const char *prefix)
{
struct tree *tree;
unsigned char sha1[20];
@@ -350,7 +352,7 @@ static void overlay_tree(const char *tree_name, const char *prefix)
struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
continue;
- ce->ce_flags |= htons(CE_STAGEMASK);
+ ce->ce_flags |= CE_STAGEMASK;
}
if (prefix) {
@@ -379,16 +381,52 @@ static void overlay_tree(const char *tree_name, const char *prefix)
*/
if (last_stage0 &&
!strcmp(last_stage0->name, ce->name))
- ce->ce_flags |= htons(CE_UPDATE);
+ ce->ce_flags |= CE_UPDATE;
}
}
}
+int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset)
+{
+ /*
+ * Make sure all pathspec matched; otherwise it is an error.
+ */
+ int num, errors = 0;
+ for (num = 0; pathspec[num]; num++) {
+ int other, found_dup;
+
+ if (ps_matched[num])
+ continue;
+ /*
+ * The caller might have fed identical pathspec
+ * twice. Do not barf on such a mistake.
+ */
+ for (found_dup = other = 0;
+ !found_dup && pathspec[other];
+ other++) {
+ if (other == num || !ps_matched[other])
+ continue;
+ if (!strcmp(pathspec[other], pathspec[num]))
+ /*
+ * Ok, we have a match already.
+ */
+ found_dup = 1;
+ }
+ if (found_dup)
+ continue;
+
+ error("pathspec '%s' did not match any file(s) known to git.",
+ pathspec[num] + prefix_offset);
+ errors++;
+ }
+ return errors;
+}
+
static const char ls_files_usage[] =
"git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
- "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
- "[--] [<file>]*";
+ "[ --exclude-per-directory=<filename> ] [--exclude-standard] "
+ "[--full-name] [--abbrev] [--] [<file>]*";
int cmd_ls_files(int argc, const char **argv, const char *prefix)
{
@@ -496,6 +534,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
dir.exclude_per_dir = arg + 24;
continue;
}
+ if (!strcmp(arg, "--exclude-standard")) {
+ exc_given = 1;
+ setup_standard_excludes(&dir);
+ continue;
+ }
if (!strcmp(arg, "--full-name")) {
prefix_offset = 0;
continue;
@@ -525,17 +568,23 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
break;
}
- if (require_work_tree && !is_inside_work_tree()) {
- const char *work_tree = get_git_work_tree();
- if (!work_tree || chdir(work_tree))
- die("This operation must be run in a work tree");
- }
+ if (require_work_tree && !is_inside_work_tree())
+ setup_work_tree();
pathspec = get_pathspec(prefix, argv + i);
/* Verify that the pathspec matches the prefix */
- if (pathspec)
+ if (pathspec) {
+ if (argc != i) {
+ int cnt;
+ for (cnt = 0; pathspec[cnt]; cnt++)
+ ;
+ if (cnt != (argc - i))
+ exit(1); /* error message already given */
+ }
prefix = verify_pathspec(prefix);
+ } else if (argc != i)
+ exit(1); /* error message already given */
/* Treat unmatching pathspec elements as errors */
if (pathspec && error_unmatch) {
@@ -566,47 +615,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
*/
if (show_stage || show_unmerged)
die("ls-files --with-tree is incompatible with -s or -u");
- overlay_tree(with_tree, prefix);
+ overlay_tree_on_cache(with_tree, prefix);
}
show_files(&dir, prefix);
if (ps_matched) {
- /* We need to make sure all pathspec matched otherwise
- * it is an error.
- */
- int num, errors = 0;
- for (num = 0; pathspec[num]; num++) {
- int other, found_dup;
-
- if (ps_matched[num])
- continue;
- /*
- * The caller might have fed identical pathspec
- * twice. Do not barf on such a mistake.
- */
- for (found_dup = other = 0;
- !found_dup && pathspec[other];
- other++) {
- if (other == num || !ps_matched[other])
- continue;
- if (!strcmp(pathspec[other], pathspec[num]))
- /*
- * Ok, we have a match already.
- */
- found_dup = 1;
- }
- if (found_dup)
- continue;
-
- error("pathspec '%s' did not match any file(s) known to git.",
- pathspec[num] + prefix_offset);
- errors++;
- }
-
- if (errors)
+ int bad;
+ bad = report_path_error(ps_matched, pathspec, prefix_offset);
+ if (bad)
fprintf(stderr, "Did you forget to 'git add'?\n");
- return errors ? 1 : 0;
+ return bad ? 1 : 0;
}
return 0;
diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c
new file mode 100644
index 0000000000..023754986e
--- /dev/null
+++ b/builtin-ls-remote.c
@@ -0,0 +1,110 @@
+#include "builtin.h"
+#include "cache.h"
+#include "transport.h"
+#include "remote.h"
+
+static const char ls_remote_usage[] =
+"git-ls-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>";
+
+/*
+ * Is there one among the list of patterns that match the tail part
+ * of the path?
+ */
+static int tail_match(const char **pattern, const char *path)
+{
+ const char *p;
+ char pathbuf[PATH_MAX];
+
+ if (!pattern)
+ return 1; /* no restriction */
+
+ if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf))
+ return error("insanely long ref %.*s...", 20, path);
+ while ((p = *(pattern++)) != NULL) {
+ if (!fnmatch(p, pathbuf, 0))
+ return 1;
+ }
+ return 0;
+}
+
+int cmd_ls_remote(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ const char *dest = NULL;
+ int nongit = 0;
+ unsigned flags = 0;
+ const char *uploadpack = NULL;
+ const char **pattern = NULL;
+
+ struct remote *remote;
+ struct transport *transport;
+ const struct ref *ref;
+
+ setup_git_directory_gently(&nongit);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!prefixcmp(arg, "--upload-pack=")) {
+ uploadpack = arg + 14;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ uploadpack = arg + 7;
+ continue;
+ }
+ if (!strcmp("--tags", arg) || !strcmp("-t", arg)) {
+ flags |= REF_TAGS;
+ continue;
+ }
+ if (!strcmp("--heads", arg) || !strcmp("-h", arg)) {
+ flags |= REF_HEADS;
+ continue;
+ }
+ if (!strcmp("--refs", arg)) {
+ flags |= REF_NORMAL;
+ continue;
+ }
+ usage(ls_remote_usage);
+ }
+ dest = arg;
+ i++;
+ break;
+ }
+
+ if (!dest)
+ usage(ls_remote_usage);
+
+ if (argv[i]) {
+ int j;
+ pattern = xcalloc(sizeof(const char *), argc - i + 1);
+ for (j = i; j < argc; j++) {
+ int len = strlen(argv[j]);
+ char *p = xmalloc(len + 3);
+ sprintf(p, "*/%s", argv[j]);
+ pattern[j - i] = p;
+ }
+ }
+ remote = nongit ? NULL : remote_get(dest);
+ if (remote && !remote->url_nr)
+ die("remote %s has no configured URL", dest);
+ transport = transport_get(remote, remote ? remote->url[0] : dest);
+ if (uploadpack != NULL)
+ transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
+
+ ref = transport_get_remote_refs(transport);
+ transport_disconnect(transport);
+
+ if (!ref)
+ return 1;
+
+ for ( ; ref; ref = ref->next) {
+ if (!check_ref_type(ref, flags))
+ continue;
+ if (!tail_match(pattern, ref->name))
+ continue;
+ printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+ }
+ return 0;
+}
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index d7cb11dc0d..11f154b31f 100644
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
@@ -288,7 +288,7 @@ static void cleanup_space(char *buf)
}
static void decode_header(char *it, unsigned itsize);
-static char *header[MAX_HDR_PARSED] = {
+static const char *header[MAX_HDR_PARSED] = {
"From","Subject","Date",
};
@@ -818,6 +818,7 @@ static void handle_body(void)
switch (transfer_encoding) {
case TE_BASE64:
+ case TE_QP:
{
char *op = line;
@@ -915,6 +916,7 @@ static void handle_info(void)
static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
const char *msg, const char *patch)
{
+ int peek;
keep_subject = ks;
metainfo_charset = encoding;
fin = in;
@@ -935,6 +937,11 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *));
s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *));
+ do {
+ peek = fgetc(in);
+ } while (isspace(peek));
+ ungetc(peek, in);
+
/* process the email header */
while (read_one_header_line(line, sizeof(line), fin))
check_header(line, sizeof(line), p_hdr_data, 1);
diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c
index 43fc373a15..46b27cdaea 100644
--- a/builtin-mailsplit.c
+++ b/builtin-mailsplit.c
@@ -101,20 +101,29 @@ static int populate_maildir_list(struct path_list *list, const char *path)
{
DIR *dir;
struct dirent *dent;
+ char name[PATH_MAX];
+ char *subs[] = { "cur", "new", NULL };
+ char **sub;
+
+ for (sub = subs; *sub; ++sub) {
+ snprintf(name, sizeof(name), "%s/%s", path, *sub);
+ if ((dir = opendir(name)) == NULL) {
+ if (errno == ENOENT)
+ continue;
+ error("cannot opendir %s (%s)", name, strerror(errno));
+ return -1;
+ }
- if ((dir = opendir(path)) == NULL) {
- error("cannot opendir %s (%s)", path, strerror(errno));
- return -1;
- }
+ while ((dent = readdir(dir)) != NULL) {
+ if (dent->d_name[0] == '.')
+ continue;
+ snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
+ path_list_insert(name, list);
+ }
- while ((dent = readdir(dir)) != NULL) {
- if (dent->d_name[0] == '.')
- continue;
- path_list_insert(dent->d_name, list);
+ closedir(dir);
}
- closedir(dir);
-
return 0;
}
@@ -122,19 +131,17 @@ static int split_maildir(const char *maildir, const char *dir,
int nr_prec, int skip)
{
char file[PATH_MAX];
- char curdir[PATH_MAX];
char name[PATH_MAX];
int ret = -1;
int i;
struct path_list list = {NULL, 0, 0, 1};
- snprintf(curdir, sizeof(curdir), "%s/cur", maildir);
- if (populate_maildir_list(&list, curdir) < 0)
+ if (populate_maildir_list(&list, maildir) < 0)
goto out;
for (i = 0; i < list.nr; i++) {
FILE *f;
- snprintf(file, sizeof(file), "%s/%s", curdir, list.items[i].path);
+ snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].path);
f = fopen(file, "r");
if (!f) {
error("cannot open mail %s (%s)", file, strerror(errno));
@@ -152,10 +159,9 @@ static int split_maildir(const char *maildir, const char *dir,
fclose(f);
}
- path_list_clear(&list, 1);
-
ret = skip;
out:
+ path_list_clear(&list, 1);
return ret;
}
@@ -164,6 +170,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
{
char name[PATH_MAX];
int ret = -1;
+ int peek;
FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
int file_done = 0;
@@ -173,6 +180,11 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
goto out;
}
+ do {
+ peek = fgetc(f);
+ } while (isspace(peek));
+ ungetc(peek, f);
+
if (fgets(buf, sizeof(buf), f) == NULL) {
/* empty stdin is OK */
if (f != stdin) {
diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c
new file mode 100644
index 0000000000..8f5bbaf402
--- /dev/null
+++ b/builtin-merge-ours.c
@@ -0,0 +1,28 @@
+/*
+ * Implementation of git-merge-ours.sh as builtin
+ *
+ * Copyright (c) 2007 Thomas Harning Jr
+ * Original:
+ * Original Copyright (c) 2005 Junio C Hamano
+ *
+ * Pretend we resolved the heads, but declare our tree trumps everybody else.
+ */
+#include "git-compat-util.h"
+#include "builtin.h"
+
+static const char *diff_index_args[] = {
+ "diff-index", "--quiet", "--cached", "HEAD", "--", NULL
+};
+#define NARGS (ARRAY_SIZE(diff_index_args) - 1)
+
+int cmd_merge_ours(int argc, const char **argv, const char *prefix)
+{
+ /*
+ * We need to exit with 2 if the index does not match our HEAD tree,
+ * because the current index is what we will be committing as the
+ * merge result.
+ */
+ if (cmd_diff_index(NARGS, diff_index_args, prefix))
+ exit(2);
+ exit(0);
+}
diff --git a/builtin-mv.c b/builtin-mv.c
index b944651691..68aa2a68bb 100644
--- a/builtin-mv.c
+++ b/builtin-mv.c
@@ -8,14 +8,18 @@
#include "dir.h"
#include "cache-tree.h"
#include "path-list.h"
+#include "parse-options.h"
-static const char builtin_mv_usage[] =
-"git-mv [-n] [-f] (<source> <destination> | [-k] <source>... <destination>)";
+static const char * const builtin_mv_usage[] = {
+ "git-mv [options] <source>... <destination>",
+ NULL
+};
static const char **copy_pathspec(const char *prefix, const char **pathspec,
int count, int base_name)
{
int i;
+ int len = prefix ? strlen(prefix) : 0;
const char **result = xmalloc((count + 1) * sizeof(const char *));
memcpy(result, pathspec, count * sizeof(const char *));
result[count] = NULL;
@@ -29,8 +33,11 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
if (last_slash)
result[i] = last_slash + 1;
}
+ result[i] = prefix_path(prefix, len, result[i]);
+ if (!result[i])
+ exit(1); /* error already given */
}
- return get_pathspec(prefix, result);
+ return result;
}
static void show_list(const char *label, struct path_list *list)
@@ -61,8 +68,14 @@ static struct lock_file lock_file;
int cmd_mv(int argc, const char **argv, const char *prefix)
{
- int i, newfd, count;
+ 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', NULL, &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;
@@ -78,52 +91,29 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die("index file corrupt");
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
+ argc = parse_options(argc, argv, builtin_mv_options, builtin_mv_usage, 0);
+ if (--argc < 1)
+ usage_with_options(builtin_mv_usage, builtin_mv_options);
- if (arg[0] != '-')
- break;
- if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- if (!strcmp(arg, "-n")) {
- show_only = 1;
- continue;
- }
- if (!strcmp(arg, "-f")) {
- force = 1;
- continue;
- }
- if (!strcmp(arg, "-k")) {
- ignore_errors = 1;
- continue;
- }
- usage(builtin_mv_usage);
- }
- count = argc - i - 1;
- if (count < 1)
- usage(builtin_mv_usage);
-
- source = copy_pathspec(prefix, argv + i, count, 0);
- modes = xcalloc(count, sizeof(enum update_mode));
- dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0);
+ source = copy_pathspec(prefix, argv, argc, 0);
+ modes = xcalloc(argc, sizeof(enum update_mode));
+ dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
if (dest_path[0][0] == '\0')
/* special case: "." was normalized to "" */
- destination = copy_pathspec(dest_path[0], argv + i, count, 1);
+ destination = copy_pathspec(dest_path[0], argv, argc, 1);
else if (!lstat(dest_path[0], &st) &&
S_ISDIR(st.st_mode)) {
dest_path[0] = add_slash(dest_path[0]);
- destination = copy_pathspec(dest_path[0], argv + i, count, 1);
+ destination = copy_pathspec(dest_path[0], argv, argc, 1);
} else {
- if (count != 1)
- usage(builtin_mv_usage);
+ if (argc != 1)
+ usage_with_options(builtin_mv_usage, builtin_mv_options);
destination = dest_path;
}
/* Checking */
- for (i = 0; i < count; i++) {
+ for (i = 0; i < argc; i++) {
const char *src = source[i], *dst = destination[i];
int length, src_is_dir;
const char *bad = NULL;
@@ -167,29 +157,29 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (last - first > 0) {
source = xrealloc(source,
- (count + last - first)
+ (argc + last - first)
* sizeof(char *));
destination = xrealloc(destination,
- (count + last - first)
+ (argc + last - first)
* sizeof(char *));
modes = xrealloc(modes,
- (count + last - first)
+ (argc + last - first)
* sizeof(enum update_mode));
}
dst = add_slash(dst);
- dst_len = strlen(dst) - 1;
+ dst_len = strlen(dst);
for (j = 0; j < last - first; j++) {
const char *path =
active_cache[first + j]->name;
- source[count + j] = path;
- destination[count + j] =
+ source[argc + j] = path;
+ destination[argc + j] =
prefix_path(dst, dst_len,
- path + length);
- modes[count + j] = INDEX;
+ path + length + 1);
+ modes[argc + j] = INDEX;
}
- count += last - first;
+ argc += last - first;
}
} else if (lstat(dst, &st) == 0) {
bad = "destination exists";
@@ -216,12 +206,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (bad) {
if (ignore_errors) {
- if (--count > 0) {
+ if (--argc > 0) {
memmove(source + i, source + i + 1,
- (count - i) * sizeof(char *));
+ (argc - i) * sizeof(char *));
memmove(destination + i,
destination + i + 1,
- (count - i) * sizeof(char *));
+ (argc - i) * sizeof(char *));
}
} else
die ("%s, source=%s, destination=%s",
@@ -229,7 +219,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
}
}
- for (i = 0; i < count; i++) {
+ for (i = 0; i < argc; i++) {
const char *src = source[i], *dst = destination[i];
enum update_mode mode = modes[i];
if (show_only || verbose)
@@ -253,7 +243,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
path_list_insert(dst, &added);
}
- if (show_only) {
+ if (show_only) {
show_list("Changed : ", &changed);
show_list("Adding : ", &added);
show_list("Deleting : ", &deleted);
@@ -278,7 +268,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
- close(newfd) ||
commit_locked_index(&lock_file))
die("Unable to write new index file");
}
diff --git a/builtin-name-rev.c b/builtin-name-rev.c
index 03083e9477..f22c8b5f5d 100644
--- a/builtin-name-rev.c
+++ b/builtin-name-rev.c
@@ -3,12 +3,10 @@
#include "commit.h"
#include "tag.h"
#include "refs.h"
+#include "parse-options.h"
#define CUTOFF_DATE_SLOP 86400 /* one day */
-static const char name_rev_usage[] =
- "git-name-rev [--tags | --refs=<pattern>] ( --all | --stdin | committish [committish...] )\n";
-
typedef struct rev_name {
const char *tip_name;
int generation;
@@ -127,18 +125,18 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
}
/* returns a static buffer */
-static const char* get_rev_name(struct object *o)
+static const char *get_rev_name(struct object *o)
{
static char buffer[1024];
struct rev_name *n;
struct commit *c;
if (o->type != OBJ_COMMIT)
- return "undefined";
+ return NULL;
c = (struct commit *) o;
n = c->util;
if (!n)
- return "undefined";
+ return NULL;
if (!n->generation)
return n->tip_name;
@@ -153,51 +151,42 @@ static const char* get_rev_name(struct object *o)
}
}
+static char const * const name_rev_usage[] = {
+ "git-name-rev [options] ( --all | --stdin | <commit>... )",
+ NULL
+};
+
int cmd_name_rev(int argc, const char **argv, const char *prefix)
{
struct object_array revs = { 0, 0, NULL };
- int as_is = 0, all = 0, transform_stdin = 0;
+ int all = 0, transform_stdin = 0, allow_undefined = 1;
struct name_ref_data data = { 0, 0, NULL };
+ struct option opts[] = {
+ OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"),
+ OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"),
+ OPT_STRING(0, "refs", &data.ref_filter, "pattern",
+ "only use refs matching <pattern>"),
+ OPT_GROUP(""),
+ OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"),
+ OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"),
+ OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"),
+ OPT_END(),
+ };
git_config(git_default_config);
+ argc = parse_options(argc, argv, opts, name_rev_usage, 0);
+ if (!!all + !!transform_stdin + !!argc > 1) {
+ error("Specify either a list, or --all, not both!");
+ usage_with_options(name_rev_usage, opts);
+ }
+ if (all || transform_stdin)
+ cutoff = 0;
- if (argc < 2)
- usage(name_rev_usage);
-
- for (--argc, ++argv; argc; --argc, ++argv) {
+ for (; argc; argc--, argv++) {
unsigned char sha1[20];
struct object *o;
struct commit *commit;
- if (!as_is && (*argv)[0] == '-') {
- if (!strcmp(*argv, "--")) {
- as_is = 1;
- continue;
- } else if (!strcmp(*argv, "--name-only")) {
- data.name_only = 1;
- continue;
- } else if (!strcmp(*argv, "--tags")) {
- data.tags_only = 1;
- continue;
- } else if (!prefixcmp(*argv, "--refs=")) {
- data.ref_filter = *argv + 7;
- continue;
- } else if (!strcmp(*argv, "--all")) {
- if (argc > 1)
- die("Specify either a list, or --all, not both!");
- all = 1;
- cutoff = 0;
- continue;
- } else if (!strcmp(*argv, "--stdin")) {
- if (argc > 1)
- die("Specify either a list, or --stdin, not both!");
- transform_stdin = 1;
- cutoff = 0;
- continue;
- }
- usage(name_rev_usage);
- }
-
if (get_sha1(*argv, sha1)) {
fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
*argv);
@@ -212,10 +201,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
}
commit = (struct commit *)o;
-
if (cutoff > commit->date)
cutoff = commit->date;
-
add_object_array((struct object *)commit, *argv, &revs);
}
@@ -240,7 +227,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
else if (++forty == 40 &&
!ishex(*(p+1))) {
unsigned char sha1[40];
- const char *name = "undefined";
+ const char *name = NULL;
char c = *(p+1);
forty = 0;
@@ -254,11 +241,10 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
}
*(p+1) = c;
- if (!strcmp(name, "undefined"))
+ if (!name)
continue;
- fwrite(p_start, p - p_start + 1, 1,
- stdout);
+ fwrite(p_start, p - p_start + 1, 1, stdout);
printf(" (%s)", name);
p_start = p + 1;
}
@@ -274,18 +260,32 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
max = get_max_object_index();
for (i = 0; i < max; i++) {
struct object * obj = get_indexed_object(i);
+ const char *name;
if (!obj)
continue;
if (!data.name_only)
printf("%s ", sha1_to_hex(obj->sha1));
- printf("%s\n", get_rev_name(obj));
+ name = get_rev_name(obj);
+ if (name)
+ printf("%s\n", name);
+ else if (allow_undefined)
+ printf("undefined\n");
+ else
+ die("cannot describe '%s'", sha1_to_hex(obj->sha1));
}
} else {
int i;
for (i = 0; i < revs.nr; i++) {
+ const char *name;
if (!data.name_only)
printf("%s ", revs.objects[i].name);
- printf("%s\n", get_rev_name(revs.objects[i].item));
+ name = get_rev_name(revs.objects[i].item);
+ if (name)
+ printf("%s\n", name);
+ else if (allow_undefined)
+ printf("undefined\n");
+ else
+ die("cannot describe '%s'", sha1_to_hex(revs.objects[i].item->sha1));
}
}
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 0be539ed7f..d2bb12e574 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -57,7 +57,7 @@ struct object_entry {
* nice "minimum seek" order.
*/
static struct object_entry *objects;
-static struct object_entry **written_list;
+static struct pack_idx_entry **written_list;
static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
static int non_empty;
@@ -65,17 +65,15 @@ static int no_reuse_delta, no_reuse_object, keep_unreachable;
static int local;
static int incremental;
static int allow_ofs_delta;
-static const char *pack_tmp_name, *idx_tmp_name;
-static char tmpname[PATH_MAX];
static const char *base_name;
static int progress = 1;
static int window = 10;
-static uint32_t pack_size_limit;
+static uint32_t pack_size_limit, pack_size_limit_cfg;
static int depth = 50;
static int delta_search_threads = 1;
static int pack_to_stdout;
static int num_preferred_base;
-static struct progress progress_state;
+static struct progress *progress_state;
static int pack_compression_level = Z_DEFAULT_COMPRESSION;
static int pack_compression_seen;
@@ -447,7 +445,7 @@ static unsigned long write_object(struct sha1file *f,
/* nothing */;
deflateEnd(&stream);
datalen = stream.total_out;
- deflateEnd(&stream);
+
/*
* The object header is a byte of 'type' followed by zero or
* more bytes of length.
@@ -579,7 +577,7 @@ static off_t write_one(struct sha1file *f,
e->idx.offset = 0;
return 0;
}
- written_list[nr_written++] = e;
+ written_list[nr_written++] = &e->idx;
/* make sure off_t is sufficiently large not to wrap */
if (offset > offset + size)
@@ -587,12 +585,6 @@ static off_t write_one(struct sha1file *f,
return offset + size;
}
-static int open_object_dir_tmp(const char *path)
-{
- snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path);
- return xmkstemp(tmpname);
-}
-
/* forward declaration for write_pack_file */
static int adjust_perm(const char *path, mode_t mode);
@@ -606,16 +598,21 @@ static void write_pack_file(void)
uint32_t nr_remaining = nr_result;
if (do_progress)
- start_progress(&progress_state, "Writing %u objects...", "", nr_result);
- written_list = xmalloc(nr_objects * sizeof(struct object_entry *));
+ progress_state = start_progress("Writing objects", nr_result);
+ written_list = xmalloc(nr_objects * sizeof(*written_list));
do {
unsigned char sha1[20];
+ char *pack_tmp_name = NULL;
if (pack_to_stdout) {
- f = sha1fd(1, "<stdout>");
+ f = sha1fd_throughput(1, "<stdout>", progress_state);
} else {
- int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
+ char tmpname[PATH_MAX];
+ int fd;
+ snprintf(tmpname, sizeof(tmpname),
+ "%s/tmp_pack_XXXXXX", get_object_directory());
+ fd = xmkstemp(tmpname);
pack_tmp_name = xstrdup(tmpname);
f = sha1fd(fd, pack_tmp_name);
}
@@ -632,8 +629,7 @@ static void write_pack_file(void)
if (!offset_one)
break;
offset = offset_one;
- if (do_progress)
- display_progress(&progress_state, written);
+ display_progress(progress_state, written);
}
/*
@@ -643,19 +639,20 @@ static void write_pack_file(void)
if (pack_to_stdout || nr_written == nr_remaining) {
sha1close(f, sha1, 1);
} else {
- sha1close(f, sha1, 0);
- fixup_pack_header_footer(f->fd, sha1, pack_tmp_name, nr_written);
- close(f->fd);
+ int fd = sha1close(f, NULL, 0);
+ fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written);
+ close(fd);
}
if (!pack_to_stdout) {
mode_t mode = umask(0);
+ char *idx_tmp_name, tmpname[PATH_MAX];
umask(mode);
mode = 0444 & ~mode;
- idx_tmp_name = write_idx_file(NULL,
- (struct pack_idx_entry **) written_list, nr_written, sha1);
+ idx_tmp_name = write_idx_file(NULL, written_list,
+ nr_written, sha1);
snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
base_name, sha1_to_hex(sha1));
if (adjust_perm(pack_tmp_name, mode))
@@ -672,19 +669,20 @@ static void write_pack_file(void)
if (rename(idx_tmp_name, tmpname))
die("unable to rename temporary index file: %s",
strerror(errno));
+ free(idx_tmp_name);
+ free(pack_tmp_name);
puts(sha1_to_hex(sha1));
}
/* mark written objects as written to previous pack */
for (j = 0; j < nr_written; j++) {
- written_list[j]->idx.offset = (off_t)-1;
+ written_list[j]->offset = (off_t)-1;
}
nr_remaining -= nr_written;
} while (nr_remaining && i < nr_objects);
free(written_list);
- if (do_progress)
- stop_progress(&progress_state);
+ stop_progress(&progress_state);
if (written != nr_result)
die("wrote %u objects while expecting %u", written, nr_result);
/*
@@ -852,8 +850,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
else
object_ix[-1 - ix] = nr_objects;
- if (progress)
- display_progress(&progress_state, nr_objects);
+ display_progress(progress_state, nr_objects);
if (name && no_try_delta(name))
entry->no_try_delta = 1;
@@ -993,7 +990,7 @@ static void add_pbase_object(struct tree_desc *tree,
return;
if (name[cmplen] != '/') {
add_object_entry(entry.sha1,
- S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB,
+ object_type(entry.mode),
fullname, 1);
return;
}
@@ -1248,28 +1245,37 @@ static void get_object_details(void)
free(sorted_by_offset);
}
+/*
+ * We search for deltas in a list sorted by type, by filename hash, and then
+ * by size, so that we see progressively smaller and smaller files.
+ * That's because we prefer deltas to be from the bigger file
+ * to the smaller -- deletes are potentially cheaper, but perhaps
+ * more importantly, the bigger file is likely the more recent
+ * one. The deepest deltas are therefore the oldest objects which are
+ * less susceptible to be accessed often.
+ */
static int type_size_sort(const void *_a, const void *_b)
{
const struct object_entry *a = *(struct object_entry **)_a;
const struct object_entry *b = *(struct object_entry **)_b;
- if (a->type < b->type)
- return -1;
if (a->type > b->type)
- return 1;
- if (a->hash < b->hash)
return -1;
- if (a->hash > b->hash)
+ if (a->type < b->type)
return 1;
- if (a->preferred_base < b->preferred_base)
+ if (a->hash > b->hash)
return -1;
- if (a->preferred_base > b->preferred_base)
+ if (a->hash < b->hash)
return 1;
- if (a->size < b->size)
+ if (a->preferred_base > b->preferred_base)
return -1;
+ if (a->preferred_base < b->preferred_base)
+ return 1;
if (a->size > b->size)
+ return -1;
+ if (a->size < b->size)
return 1;
- return a > b ? -1 : (a < b); /* newest last */
+ return a < b ? -1 : (a > b); /* newest first */
}
struct unpacked {
@@ -1320,14 +1326,6 @@ static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
-/*
- * We search for deltas _backwards_ in a list sorted by type and
- * by size, so that we see progressively smaller and smaller files.
- * That's because we prefer deltas to be from the bigger file
- * to the smaller - deletes are potentially cheaper, but perhaps
- * more importantly, the bigger file is likely the more recent
- * one.
- */
static int try_delta(struct unpacked *trg, struct unpacked *src,
unsigned max_depth, unsigned long *mem_usage)
{
@@ -1425,10 +1423,6 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
}
}
- trg_entry->delta = src_entry;
- trg_entry->delta_size = delta_size;
- trg->depth = src->depth + 1;
-
/*
* Handle memory allocation outside of the cache
* accounting lock. Compiler will optimize the strangeness
@@ -1442,7 +1436,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
trg_entry->delta_data = NULL;
}
if (delta_cacheable(src_size, trg_size, delta_size)) {
- delta_cache_size += trg_entry->delta_size;
+ delta_cache_size += delta_size;
cache_unlock();
trg_entry->delta_data = xrealloc(delta_buf, delta_size);
} else {
@@ -1450,6 +1444,10 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
free(delta_buf);
}
+ trg_entry->delta = src_entry;
+ trg_entry->delta_size = delta_size;
+ trg->depth = src->depth + 1;
+
return 1;
}
@@ -1481,10 +1479,10 @@ static unsigned long free_unpacked(struct unpacked *n)
return freed_mem;
}
-static void find_deltas(struct object_entry **list, unsigned list_size,
+static void find_deltas(struct object_entry **list, unsigned *list_size,
int window, int depth, unsigned *processed)
{
- uint32_t i = list_size, idx = 0, count = 0;
+ uint32_t i, idx = 0, count = 0;
unsigned int array_size = window * sizeof(struct unpacked);
struct unpacked *array;
unsigned long mem_usage = 0;
@@ -1492,11 +1490,23 @@ static void find_deltas(struct object_entry **list, unsigned list_size,
array = xmalloc(array_size);
memset(array, 0, array_size);
- do {
- struct object_entry *entry = list[--i];
+ for (;;) {
+ struct object_entry *entry = *list++;
struct unpacked *n = array + idx;
int j, max_depth, best_base = -1;
+ progress_lock();
+ if (!*list_size) {
+ progress_unlock();
+ break;
+ }
+ (*list_size)--;
+ if (!entry->preferred_base) {
+ (*processed)++;
+ display_progress(progress_state, *processed);
+ }
+ progress_unlock();
+
mem_usage -= free_unpacked(n);
n->entry = entry;
@@ -1514,12 +1524,6 @@ static void find_deltas(struct object_entry **list, unsigned list_size,
if (entry->preferred_base)
goto next;
- progress_lock();
- (*processed)++;
- if (progress)
- display_progress(&progress_state, *processed);
- progress_unlock();
-
/*
* If the current object is at pack edge, take the depth the
* objects that depend on the current object into account
@@ -1579,7 +1583,7 @@ static void find_deltas(struct object_entry **list, unsigned list_size,
count++;
if (idx >= window)
idx = 0;
- } while (i > 0);
+ }
for (i = 0; i < window; ++i) {
free_delta_index(array[i].index);
@@ -1590,94 +1594,180 @@ static void find_deltas(struct object_entry **list, unsigned list_size,
#ifdef THREADED_DELTA_SEARCH
+/*
+ * The main thread waits on the condition that (at least) one of the workers
+ * has stopped working (which is indicated in the .working member of
+ * struct thread_params).
+ * When a work thread has completed its work, it sets .working to 0 and
+ * signals the main thread and waits on the condition that .data_ready
+ * becomes 1.
+ */
+
struct thread_params {
pthread_t thread;
struct object_entry **list;
unsigned list_size;
+ unsigned remaining;
int window;
int depth;
+ int working;
+ int data_ready;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
unsigned *processed;
};
-static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER;
-static struct thread_params *data_requester;
+static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER;
static void *threaded_find_deltas(void *arg)
{
struct thread_params *me = arg;
- for (;;) {
- pthread_mutex_lock(&data_request);
- data_requester = me;
- pthread_mutex_unlock(&data_provider);
- pthread_mutex_lock(&data_ready);
- pthread_mutex_unlock(&data_request);
+ while (me->remaining) {
+ find_deltas(me->list, &me->remaining,
+ me->window, me->depth, me->processed);
- if (!me->list_size)
- return NULL;
+ progress_lock();
+ me->working = 0;
+ pthread_cond_signal(&progress_cond);
+ progress_unlock();
- find_deltas(me->list, me->list_size,
- me->window, me->depth, me->processed);
+ /*
+ * We must not set ->data_ready before we wait on the
+ * condition because the main thread may have set it to 1
+ * before we get here. In order to be sure that new
+ * work is available if we see 1 in ->data_ready, it
+ * was initialized to 0 before this thread was spawned
+ * and we reset it to 0 right away.
+ */
+ pthread_mutex_lock(&me->mutex);
+ while (!me->data_ready)
+ pthread_cond_wait(&me->cond, &me->mutex);
+ me->data_ready = 0;
+ pthread_mutex_unlock(&me->mutex);
}
+ /* leave ->working 1 so that this doesn't get more work assigned */
+ return NULL;
}
static void ll_find_deltas(struct object_entry **list, unsigned list_size,
int window, int depth, unsigned *processed)
{
- struct thread_params *target, p[delta_search_threads];
- int i, ret;
- unsigned chunk_size;
+ struct thread_params p[delta_search_threads];
+ int i, ret, active_threads = 0;
if (delta_search_threads <= 1) {
- find_deltas(list, list_size, window, depth, processed);
+ find_deltas(list, &list_size, window, depth, processed);
return;
}
- pthread_mutex_lock(&data_provider);
- pthread_mutex_lock(&data_ready);
-
+ /* Partition the work amongst work threads. */
for (i = 0; i < delta_search_threads; i++) {
+ unsigned sub_size = list_size / (delta_search_threads - i);
+
p[i].window = window;
p[i].depth = depth;
p[i].processed = processed;
+ p[i].working = 1;
+ p[i].data_ready = 0;
+
+ /* try to split chunks on "path" boundaries */
+ while (sub_size && sub_size < list_size &&
+ list[sub_size]->hash &&
+ list[sub_size]->hash == list[sub_size-1]->hash)
+ sub_size++;
+
+ p[i].list = list;
+ p[i].list_size = sub_size;
+ p[i].remaining = sub_size;
+
+ list += sub_size;
+ list_size -= sub_size;
+ }
+
+ /* Start work threads. */
+ for (i = 0; i < delta_search_threads; i++) {
+ if (!p[i].list_size)
+ continue;
+ pthread_mutex_init(&p[i].mutex, NULL);
+ pthread_cond_init(&p[i].cond, NULL);
ret = pthread_create(&p[i].thread, NULL,
threaded_find_deltas, &p[i]);
if (ret)
die("unable to create thread: %s", strerror(ret));
+ active_threads++;
}
- /* this should be auto-tuned somehow */
- chunk_size = window * 1000;
+ /*
+ * Now let's wait for work completion. Each time a thread is done
+ * with its work, we steal half of the remaining work from the
+ * thread with the largest number of unprocessed objects and give
+ * it to that newly idle thread. This ensure good load balancing
+ * until the remaining object list segments are simply too short
+ * to be worth splitting anymore.
+ */
+ while (active_threads) {
+ struct thread_params *target = NULL;
+ struct thread_params *victim = NULL;
+ unsigned sub_size = 0;
- do {
- unsigned sublist_size = chunk_size;
- if (sublist_size > list_size)
- sublist_size = list_size;
+ progress_lock();
+ for (;;) {
+ for (i = 0; !target && i < delta_search_threads; i++)
+ if (!p[i].working)
+ target = &p[i];
+ if (target)
+ break;
+ pthread_cond_wait(&progress_cond, &progress_mutex);
+ }
- /* try to split chunks on "path" boundaries */
- while (sublist_size < list_size && list[sublist_size]->hash &&
- list[sublist_size]->hash == list[sublist_size-1]->hash)
- sublist_size++;
-
- pthread_mutex_lock(&data_provider);
- target = data_requester;
- target->list = list;
- target->list_size = sublist_size;
- pthread_mutex_unlock(&data_ready);
-
- list += sublist_size;
- list_size -= sublist_size;
- if (!sublist_size) {
+ for (i = 0; i < delta_search_threads; i++)
+ if (p[i].remaining > 2*window &&
+ (!victim || victim->remaining < p[i].remaining))
+ victim = &p[i];
+ if (victim) {
+ sub_size = victim->remaining / 2;
+ list = victim->list + victim->list_size - sub_size;
+ while (sub_size && list[0]->hash &&
+ list[0]->hash == list[-1]->hash) {
+ list++;
+ sub_size--;
+ }
+ if (!sub_size) {
+ /*
+ * It is possible for some "paths" to have
+ * so many objects that no hash boundary
+ * might be found. Let's just steal the
+ * exact half in that case.
+ */
+ sub_size = victim->remaining / 2;
+ list -= sub_size;
+ }
+ target->list = list;
+ victim->list_size -= sub_size;
+ victim->remaining -= sub_size;
+ }
+ target->list_size = sub_size;
+ target->remaining = sub_size;
+ target->working = 1;
+ progress_unlock();
+
+ pthread_mutex_lock(&target->mutex);
+ target->data_ready = 1;
+ pthread_cond_signal(&target->cond);
+ pthread_mutex_unlock(&target->mutex);
+
+ if (!sub_size) {
pthread_join(target->thread, NULL);
- i--;
+ pthread_cond_destroy(&target->cond);
+ pthread_mutex_destroy(&target->mutex);
+ active_threads--;
}
- } while (i);
+ }
}
#else
-#define ll_find_deltas find_deltas
+#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p)
#endif
static void prepare_pack(int window, int depth)
@@ -1714,16 +1804,14 @@ static void prepare_pack(int window, int depth)
delta_list[n++] = entry;
}
- if (nr_deltas) {
+ if (nr_deltas && n > 1) {
unsigned nr_done = 0;
if (progress)
- start_progress(&progress_state,
- "Deltifying %u objects...", "",
- nr_deltas);
+ progress_state = start_progress("Compressing objects",
+ nr_deltas);
qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
- if (progress)
- stop_progress(&progress_state);
+ stop_progress(&progress_state);
if (nr_done != nr_deltas)
die("inconsistency with delta count");
}
@@ -1773,6 +1861,16 @@ static int git_pack_config(const char *k, const char *v)
#endif
return 0;
}
+ if (!strcmp(k, "pack.indexversion")) {
+ pack_idx_default_version = git_config_int(k, v);
+ if (pack_idx_default_version > 2)
+ die("bad pack.indexversion=%d", pack_idx_default_version);
+ return 0;
+ }
+ if (!strcmp(k, "pack.packsizelimit")) {
+ pack_size_limit_cfg = git_config_ulong(k, v);
+ return 0;
+ }
return git_default_config(k, v);
}
@@ -1920,7 +2018,7 @@ static void get_object_list(int ac, const char **av)
while (fgets(line, sizeof(line), stdin) != NULL) {
int len = strlen(line);
- if (line[len - 1] == '\n')
+ if (len && line[len - 1] == '\n')
line[--len] = 0;
if (!len)
break;
@@ -1935,7 +2033,8 @@ static void get_object_list(int ac, const char **av)
die("bad revision '%s'", line);
}
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
mark_edges_uninteresting(revs.commits, &revs, show_edge);
traverse_commit_list(&revs, show_commit, show_object);
@@ -2002,6 +2101,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
}
if (!prefixcmp(arg, "--max-pack-size=")) {
char *end;
+ pack_size_limit_cfg = 0;
pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024;
if (!arg[16] || *end)
usage(pack_usage);
@@ -2126,6 +2226,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (pack_to_stdout != !base_name)
usage(pack_usage);
+ if (!pack_to_stdout && !pack_size_limit)
+ pack_size_limit = pack_size_limit_cfg;
+
if (pack_to_stdout && pack_size_limit)
die("--max-pack-size cannot be used to build a pack for transfer.");
@@ -2135,23 +2238,17 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
prepare_packed_git();
if (progress)
- start_progress(&progress_state, "Generating pack...",
- "Counting objects: ", 0);
+ progress_state = start_progress("Counting objects", 0);
if (!use_internal_rev_list)
read_object_list_from_stdin();
else {
rp_av[rp_ac] = NULL;
get_object_list(rp_ac, rp_av);
}
- if (progress) {
- stop_progress(&progress_state);
- fprintf(stderr, "Done counting %u objects.\n", nr_objects);
- }
+ stop_progress(&progress_state);
if (non_empty && !nr_result)
return 0;
- if (progress && (nr_objects != nr_result))
- fprintf(stderr, "Result has %u objects.\n", nr_result);
if (nr_result)
prepare_pack(window, depth);
write_pack_file();
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
index 09df4e11a8..1aaa76dd1f 100644
--- a/builtin-pack-refs.c
+++ b/builtin-pack-refs.c
@@ -3,9 +3,7 @@
#include "refs.h"
#include "object.h"
#include "tag.h"
-
-static const char builtin_pack_refs_usage[] =
-"git-pack-refs [--all] [--prune | --no-prune]";
+#include "parse-options.h"
struct ref_to_prune {
struct ref_to_prune *next;
@@ -110,6 +108,12 @@ static int pack_refs(unsigned int flags)
die("failed to write ref-pack file");
if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
die("failed to write ref-pack file (%s)", strerror(errno));
+ /*
+ * Since the lock file was fdopen()'ed and then fclose()'ed above,
+ * assign -1 to the lock file descriptor so that commit_lock_file()
+ * won't try to close() it.
+ */
+ packed.fd = -1;
if (commit_lock_file(&packed) < 0)
die("unable to overwrite old ref-pack file (%s)", strerror(errno));
if (cbdata.flags & PACK_REFS_PRUNE)
@@ -117,31 +121,20 @@ static int pack_refs(unsigned int flags)
return 0;
}
+static char const * const pack_refs_usage[] = {
+ "git-pack-refs [options]",
+ NULL
+};
+
int cmd_pack_refs(int argc, const char **argv, const char *prefix)
{
- int i;
- unsigned int flags;
-
- flags = PACK_REFS_PRUNE;
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--prune")) {
- flags |= PACK_REFS_PRUNE; /* now the default */
- continue;
- }
- if (!strcmp(arg, "--no-prune")) {
- flags &= ~PACK_REFS_PRUNE;
- continue;
- }
- if (!strcmp(arg, "--all")) {
- flags |= PACK_REFS_ALL;
- continue;
- }
- /* perhaps other parameters later... */
- break;
- }
- if (i != argc)
- usage(builtin_pack_refs_usage);
-
+ unsigned int flags = PACK_REFS_PRUNE;
+ struct option opts[] = {
+ OPT_BIT(0, "all", &flags, "pack everything", PACK_REFS_ALL),
+ OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE),
+ OPT_END(),
+ };
+ if (parse_options(argc, argv, opts, pack_refs_usage, 0))
+ usage_with_options(pack_refs_usage, opts);
return pack_refs(flags);
}
diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c
index 977730064b..23faf3129f 100644
--- a/builtin-prune-packed.c
+++ b/builtin-prune-packed.c
@@ -1,5 +1,6 @@
#include "builtin.h"
#include "cache.h"
+#include "progress.h"
static const char prune_packed_usage[] =
"git-prune-packed [-n] [-q]";
@@ -7,6 +8,8 @@ static const char prune_packed_usage[] =
#define DRY_RUN 01
#define VERBOSE 02
+static struct progress *progress;
+
static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
{
struct dirent *de;
@@ -27,6 +30,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
printf("rm -f %s\n", pathname);
else if (unlink(pathname) < 0)
error("unable to unlink %s", pathname);
+ display_progress(progress, i + 1);
}
pathname[len] = 0;
rmdir(pathname);
@@ -39,6 +43,10 @@ void prune_packed_objects(int opts)
const char *dir = get_object_directory();
int len = strlen(dir);
+ if (opts == VERBOSE)
+ progress = start_progress_delay("Removing duplicate objects",
+ 256, 95, 2);
+
if (len > PATH_MAX - 42)
die("impossible object directory");
memcpy(pathname, dir, len);
@@ -49,16 +57,12 @@ void prune_packed_objects(int opts)
sprintf(pathname + len, "%02x/", i);
d = opendir(pathname);
- if (opts == VERBOSE && (d || i == 255))
- fprintf(stderr, "Removing unused objects %d%%...\015",
- ((i+1) * 100) / 256);
if (!d)
continue;
prune_dir(i, d, pathname, len + 3, opts);
closedir(d);
}
- if (opts == VERBOSE)
- fprintf(stderr, "\nDone.\n");
+ stop_progress(&progress);
}
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
diff --git a/builtin-prune.c b/builtin-prune.c
index 44df59e4a7..bb8ead92cf 100644
--- a/builtin-prune.c
+++ b/builtin-prune.c
@@ -7,15 +7,24 @@
static const char prune_usage[] = "git-prune [-n]";
static int show_only;
+static unsigned long expire;
static int prune_object(char *path, const char *filename, const unsigned char *sha1)
{
+ const char *fullpath = mkpath("%s/%s", path, filename);
+ if (expire) {
+ struct stat st;
+ if (lstat(fullpath, &st))
+ return error("Could not stat '%s'", fullpath);
+ if (st.st_mtime > expire)
+ return 0;
+ }
if (show_only) {
enum object_type type = sha1_object_info(sha1, NULL);
printf("%s %s\n", sha1_to_hex(sha1),
(type > 0) ? typename(type) : "unknown");
} else
- unlink(mkpath("%s/%s", path, filename));
+ unlink(fullpath);
return 0;
}
@@ -74,6 +83,44 @@ static void prune_object_dir(const char *path)
}
}
+/*
+ * Write errors (particularly out of space) can result in
+ * failed temporary packs (and more rarely indexes and other
+ * files begining with "tmp_") accumulating in the
+ * object directory.
+ */
+static void remove_temporary_files(void)
+{
+ DIR *dir;
+ struct dirent *de;
+ char* dirname=get_object_directory();
+
+ dir = opendir(dirname);
+ if (!dir) {
+ fprintf(stderr, "Unable to open object directory %s\n",
+ dirname);
+ return;
+ }
+ while ((de = readdir(dir)) != NULL) {
+ if (!prefixcmp(de->d_name, "tmp_")) {
+ char name[PATH_MAX];
+ int c = snprintf(name, PATH_MAX, "%s/%s",
+ dirname, de->d_name);
+ if (c < 0 || c >= PATH_MAX)
+ continue;
+ if (expire) {
+ struct stat st;
+ if (stat(name, &st) != 0 || st.st_mtime >= expire)
+ continue;
+ }
+ printf("Removing stale temporary file %s\n", name);
+ if (!show_only)
+ unlink(name);
+ }
+ }
+ closedir(dir);
+}
+
int cmd_prune(int argc, const char **argv, const char *prefix)
{
int i;
@@ -85,6 +132,16 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
show_only = 1;
continue;
}
+ if (!strcmp(arg, "--expire")) {
+ if (++i < argc) {
+ expire = approxidate(argv[i]);
+ continue;
+ }
+ }
+ else if (!prefixcmp(arg, "--expire=")) {
+ expire = approxidate(arg + 9);
+ continue;
+ }
usage(prune_usage);
}
@@ -96,5 +153,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
sync();
prune_packed_objects(show_only);
+ remove_temporary_files();
return 0;
}
diff --git a/builtin-push.c b/builtin-push.c
index 141380b852..9f727c00f6 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -6,10 +6,15 @@
#include "run-command.h"
#include "builtin.h"
#include "remote.h"
+#include "transport.h"
+#include "parse-options.h"
-static const char push_usage[] = "git-push [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
+static const char * const push_usage[] = {
+ "git-push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]",
+ NULL,
+};
-static int all, dry_run, force, thin, verbose;
+static int thin, verbose;
static const char *receivepack;
static const char **refspec;
@@ -39,86 +44,53 @@ static void set_refspecs(const char **refs, int nr)
strcat(tag, refs[i]);
ref = tag;
}
+ if (!strcmp("HEAD", ref)) {
+ unsigned char sha1_dummy[20];
+ ref = resolve_ref(ref, sha1_dummy, 1, NULL);
+ if (!ref)
+ die("HEAD cannot be resolved.");
+ if (prefixcmp(ref, "refs/heads/"))
+ die("HEAD cannot be resolved to branch.");
+ ref = xstrdup(ref + 11);
+ }
add_refspec(ref);
}
}
-static int do_push(const char *repo)
+static int do_push(const char *repo, int flags)
{
int i, errs;
- int common_argc;
- const char **argv;
- int argc;
struct remote *remote = remote_get(repo);
if (!remote)
die("bad repository '%s'", repo);
- if (remote->receivepack) {
- char *rp = xmalloc(strlen(remote->receivepack) + 16);
- sprintf(rp, "--receive-pack=%s", remote->receivepack);
- receivepack = rp;
- }
- if (!refspec && !all && remote->push_refspec_nr) {
+ if (!refspec
+ && !(flags & TRANSPORT_PUSH_ALL)
+ && remote->push_refspec_nr) {
refspec = remote->push_refspec;
refspec_nr = remote->push_refspec_nr;
}
-
- argv = xmalloc((refspec_nr + 10) * sizeof(char *));
- argv[0] = "dummy-send-pack";
- argc = 1;
- if (all)
- argv[argc++] = "--all";
- if (dry_run)
- argv[argc++] = "--dry-run";
- if (force)
- argv[argc++] = "--force";
- if (receivepack)
- argv[argc++] = receivepack;
- common_argc = argc;
-
errs = 0;
- for (i = 0; i < remote->uri_nr; i++) {
+ for (i = 0; i < remote->url_nr; i++) {
+ struct transport *transport =
+ transport_get(remote, remote->url[i]);
int err;
- int dest_argc = common_argc;
- int dest_refspec_nr = refspec_nr;
- const char **dest_refspec = refspec;
- const char *dest = remote->uri[i];
- const char *sender = "send-pack";
- if (!prefixcmp(dest, "http://") ||
- !prefixcmp(dest, "https://"))
- sender = "http-push";
- else {
- char *rem = xmalloc(strlen(remote->name) + 10);
- sprintf(rem, "--remote=%s", remote->name);
- argv[dest_argc++] = rem;
- if (thin)
- argv[dest_argc++] = "--thin";
- }
- argv[0] = sender;
- argv[dest_argc++] = dest;
- while (dest_refspec_nr--)
- argv[dest_argc++] = *dest_refspec++;
- argv[dest_argc] = NULL;
+ if (receivepack)
+ transport_set_option(transport,
+ TRANS_OPT_RECEIVEPACK, receivepack);
+ if (thin)
+ transport_set_option(transport, TRANS_OPT_THIN, "yes");
+
if (verbose)
- fprintf(stderr, "Pushing to %s\n", dest);
- err = run_command_v_opt(argv, RUN_GIT_CMD);
+ fprintf(stderr, "Pushing to %s\n", remote->url[i]);
+ err = transport_push(transport, refspec_nr, refspec, flags);
+ err |= transport_disconnect(transport);
+
if (!err)
continue;
- error("failed to push to '%s'", remote->uri[i]);
- switch (err) {
- case -ERR_RUN_COMMAND_FORK:
- error("unable to fork for %s", sender);
- case -ERR_RUN_COMMAND_EXEC:
- error("unable to exec %s", sender);
- break;
- case -ERR_RUN_COMMAND_WAITPID:
- case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
- case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
- case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
- error("%s died with strange error", sender);
- }
+ error("failed to push some refs to '%s'", remote->url[i]);
errs++;
}
return !!errs;
@@ -126,62 +98,55 @@ static int do_push(const char *repo)
int cmd_push(int argc, const char **argv, const char *prefix)
{
- int i;
+ int flags = 0;
+ int all = 0;
+ int mirror = 0;
+ int dry_run = 0;
+ int force = 0;
+ int tags = 0;
const char *repo = NULL; /* default repository */
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
+ struct option options[] = {
+ OPT__VERBOSE(&verbose),
+ OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
+ OPT_BOOLEAN( 0 , "all", &all, "push all refs"),
+ OPT_BOOLEAN( 0 , "mirror", &mirror, "mirror all refs"),
+ OPT_BOOLEAN( 0 , "tags", &tags, "push tags"),
+ OPT_BOOLEAN( 0 , "dry-run", &dry_run, "dry run"),
+ OPT_BOOLEAN('f', "force", &force, "force updates"),
+ OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
+ OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
+ OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+ OPT_END()
+ };
- if (arg[0] != '-') {
- repo = arg;
- i++;
- break;
- }
- if (!strcmp(arg, "-v")) {
- verbose=1;
- continue;
- }
- if (!prefixcmp(arg, "--repo=")) {
- repo = arg+7;
- continue;
- }
- if (!strcmp(arg, "--all")) {
- all = 1;
- continue;
- }
- if (!strcmp(arg, "--dry-run")) {
- dry_run = 1;
- continue;
- }
- if (!strcmp(arg, "--tags")) {
- add_refspec("refs/tags/*");
- continue;
- }
- if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) {
- force = 1;
- continue;
- }
- if (!strcmp(arg, "--thin")) {
- thin = 1;
- continue;
- }
- if (!strcmp(arg, "--no-thin")) {
- thin = 0;
- continue;
- }
- if (!prefixcmp(arg, "--receive-pack=")) {
- receivepack = arg;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- receivepack = arg;
- continue;
- }
- usage(push_usage);
+ argc = parse_options(argc, argv, options, push_usage, 0);
+
+ if (force)
+ flags |= TRANSPORT_PUSH_FORCE;
+ if (dry_run)
+ flags |= TRANSPORT_PUSH_DRY_RUN;
+ if (verbose)
+ flags |= TRANSPORT_PUSH_VERBOSE;
+ if (tags)
+ add_refspec("refs/tags/*");
+ if (all)
+ flags |= TRANSPORT_PUSH_ALL;
+ if (mirror)
+ flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+
+ if (argc > 0) {
+ repo = argv[0];
+ set_refspecs(argv + 1, argc - 1);
+ }
+ if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec)
+ usage_with_options(push_usage, options);
+
+ if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
+ (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
+ error("--all and --mirror are incompatible");
+ usage_with_options(push_usage, options);
}
- set_refspecs(argv + i, argc - i);
- if (all && refspec)
- usage(push_usage);
- return do_push(repo);
+ return do_push(repo, flags);
}
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 43cd56a3b5..726fb0b588 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -45,8 +45,7 @@ static int read_cache_unmerged(void)
continue;
cache_tree_invalidate_path(active_cache_tree, ce->name);
last = ce;
- ce->ce_mode = 0;
- ce->ce_flags &= ~htons(CE_STAGEMASK);
+ continue;
}
*dst++ = ce;
}
@@ -283,7 +282,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
}
if (write_cache(newfd, active_cache, active_nr) ||
- close(newfd) || commit_locked_index(&lock_file))
+ commit_locked_index(&lock_file))
die("unable to write new index file");
return 0;
}
diff --git a/builtin-reflog.c b/builtin-reflog.c
index 5e5498945f..8af75bc3e1 100644
--- a/builtin-reflog.c
+++ b/builtin-reflog.c
@@ -35,6 +35,16 @@ struct expire_reflog_cb {
struct cmd_reflog_expire_cb *cmd;
};
+struct collected_reflog {
+ unsigned char sha1[20];
+ char reflog[FLEX_ARRAY];
+};
+struct collect_reflog_cb {
+ struct collected_reflog **e;
+ int alloc;
+ int nr;
+};
+
#define INCOMPLETE (1u<<10)
#define STUDYING (1u<<11)
@@ -270,10 +280,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
for_each_reflog_ent(ref, expire_reflog_ent, &cb);
finish:
if (cb.newlog) {
- if (fclose(cb.newlog))
+ if (fclose(cb.newlog)) {
status |= error("%s: %s", strerror(errno),
newlog_path);
- if (rename(newlog_path, log_file)) {
+ unlink(newlog_path);
+ } else if (rename(newlog_path, log_file)) {
status |= error("cannot rename %s to %s",
newlog_path, log_file);
unlink(newlog_path);
@@ -285,15 +296,35 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
return status;
}
+static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+{
+ struct collected_reflog *e;
+ struct collect_reflog_cb *cb = cb_data;
+ size_t namelen = strlen(ref);
+
+ e = xmalloc(sizeof(*e) + namelen + 1);
+ hashcpy(e->sha1, sha1);
+ memcpy(e->reflog, ref, namelen + 1);
+ ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
+ cb->e[cb->nr++] = e;
+ return 0;
+}
+
static int reflog_expire_config(const char *var, const char *value)
{
- if (!strcmp(var, "gc.reflogexpire"))
+ if (!strcmp(var, "gc.reflogexpire")) {
+ if (!value)
+ config_error_nonbool(var);
default_reflog_expire = approxidate(value);
- else if (!strcmp(var, "gc.reflogexpireunreachable"))
+ return 0;
+ }
+ if (!strcmp(var, "gc.reflogexpireunreachable")) {
+ if (!value)
+ config_error_nonbool(var);
default_reflog_expire_unreachable = approxidate(value);
- else
- return git_default_config(var, value);
- return 0;
+ return 0;
+ }
+ return git_default_config(var, value);
}
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
@@ -353,8 +384,20 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
putchar('\n');
}
- if (do_all)
- status |= for_each_reflog(expire_reflog, &cb);
+ if (do_all) {
+ struct collect_reflog_cb collected;
+ int i;
+
+ memset(&collected, 0, sizeof(collected));
+ for_each_reflog(collect_reflog, &collected);
+ for (i = 0; i < collected.nr; i++) {
+ struct collected_reflog *e = collected.e[i];
+ status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
+ free(e);
+ }
+ free(collected.e);
+ }
+
while (i < argc) {
const char *ref = argv[i++];
unsigned char sha1[20];
diff --git a/builtin-rerere.c b/builtin-rerere.c
index b8206744c1..b0c17bde87 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -61,9 +61,9 @@ static int write_rr(struct path_list *rr, int out_fd)
write_in_full(out_fd, path, length) != length)
die("unable to write rerere record");
}
- if (close(out_fd) != 0)
+ if (commit_lock_file(&write_lock) != 0)
die("unable to write rerere record");
- return commit_lock_file(&write_lock);
+ return 0;
}
static int handle_file(const char *path,
@@ -149,8 +149,8 @@ static int find_conflict(struct path_list *conflict)
if (ce_stage(e2) == 2 &&
ce_stage(e3) == 3 &&
ce_same_name(e2, e3) &&
- S_ISREG(ntohl(e2->ce_mode)) &&
- S_ISREG(ntohl(e3->ce_mode))) {
+ S_ISREG(e2->ce_mode) &&
+ S_ISREG(e3->ce_mode)) {
path_list_insert((const char *)e2->name, conflict);
i++; /* skip over both #2 and #3 */
}
@@ -260,7 +260,7 @@ static int diff_two(const char *file1, const char *label1,
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
ecb.outf = outf;
- xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+ xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
free(minus.ptr);
free(plus.ptr);
@@ -389,18 +389,39 @@ static int is_rerere_enabled(void)
return 1;
}
-int cmd_rerere(int argc, const char **argv, const char *prefix)
+static int setup_rerere(struct path_list *merge_rr)
{
- struct path_list merge_rr = { NULL, 0, 0, 1 };
- int i, fd = -1;
+ int fd;
git_config(git_rerere_config);
if (!is_rerere_enabled())
- return 0;
+ return -1;
merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
- read_rr(&merge_rr);
+ read_rr(merge_rr);
+ return fd;
+}
+
+int rerere(void)
+{
+ struct path_list merge_rr = { NULL, 0, 0, 1 };
+ int fd;
+
+ fd = setup_rerere(&merge_rr);
+ if (fd < 0)
+ return 0;
+ return do_plain_rerere(&merge_rr, fd);
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+ struct path_list merge_rr = { NULL, 0, 0, 1 };
+ int i, fd;
+
+ fd = setup_rerere(&merge_rr);
+ if (fd < 0)
+ return 0;
if (argc < 2)
return do_plain_rerere(&merge_rr, fd);
diff --git a/builtin-reset.c b/builtin-reset.c
index 99d5c082a6..7ee811f0b8 100644
--- a/builtin-reset.c
+++ b/builtin-reset.c
@@ -18,7 +18,7 @@
#include "tree.h"
static const char builtin_reset_usage[] =
-"git-reset [--mixed | --soft | --hard] [<commit-ish>] [ [--] <paths>...]";
+"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]";
static char *args_to_str(const char **argv)
{
@@ -46,26 +46,14 @@ static inline int is_merge(void)
static int unmerged_files(void)
{
- char b;
- ssize_t len;
- struct child_process cmd;
- const char *argv_ls_files[] = {"ls-files", "--unmerged", NULL};
-
- memset(&cmd, 0, sizeof(cmd));
- cmd.argv = argv_ls_files;
- cmd.git_cmd = 1;
- cmd.out = -1;
-
- if (start_command(&cmd))
- die("Could not run sub-command: git ls-files");
-
- len = xread(cmd.out, &b, 1);
- if (len < 0)
- die("Could not read output from git ls-files: %s",
- strerror(errno));
- finish_command(&cmd);
-
- return len;
+ int i;
+ read_cache();
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ce_stage(ce))
+ return 1;
+ }
+ return 0;
}
static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
@@ -107,19 +95,33 @@ static void print_new_head_line(struct commit *commit)
printf("\n");
}
-static int update_index_refresh(void)
+static int update_index_refresh(int fd, struct lock_file *index_lock)
{
- const char *argv_update_index[] = {"update-index", "--refresh", NULL};
- return run_command_v_opt(argv_update_index, RUN_GIT_CMD);
+ int result;
+
+ if (!index_lock) {
+ index_lock = xcalloc(1, sizeof(struct lock_file));
+ fd = hold_locked_index(index_lock, 1);
+ }
+
+ if (read_cache() < 0)
+ return error("Could not read index");
+ result = refresh_cache(0) ? 1 : 0;
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(index_lock))
+ return error ("Could not refresh index");
+ return result;
}
static void update_index_from_diff(struct diff_queue_struct *q,
struct diff_options *opt, void *data)
{
int i;
+ int *discard_flag = data;
/* do_diff_cache() mangled the index */
discard_cache();
+ *discard_flag = 1;
read_cache();
for (i = 0; i < q->nr; i++) {
@@ -138,24 +140,29 @@ static void update_index_from_diff(struct diff_queue_struct *q,
static int read_from_tree(const char *prefix, const char **argv,
unsigned char *tree_sha1)
{
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
- int index_fd;
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ int index_fd, index_was_discarded = 0;
struct diff_options opt;
memset(&opt, 0, sizeof(opt));
diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.format_callback = update_index_from_diff;
+ opt.format_callback_data = &index_was_discarded;
index_fd = hold_locked_index(lock, 1);
+ index_was_discarded = 0;
read_cache();
if (do_diff_cache(tree_sha1, &opt))
return 1;
diffcore_std(&opt);
diff_flush(&opt);
- return write_cache(index_fd, active_cache, active_nr) ||
- close(index_fd) ||
- commit_locked_index(lock);
+ diff_tree_release_paths(&opt);
+
+ if (!index_was_discarded)
+ /* The index is still clobbered from do_diff_cache() */
+ discard_cache();
+ return update_index_refresh(index_fd, lock);
}
static void prepend_reflog_action(const char *action, char *buf, size_t size)
@@ -169,11 +176,11 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
}
enum reset_type { MIXED, SOFT, HARD, NONE };
-static char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
+static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
int cmd_reset(int argc, const char **argv, const char *prefix)
{
- int i = 1, reset_type = NONE, update_ref_status = 0;
+ int i = 1, reset_type = NONE, update_ref_status = 0, quiet = 0;
const char *rev = "HEAD";
unsigned char sha1[20], *orig = NULL, sha1_orig[20],
*old_orig = NULL, sha1_old_orig[20];
@@ -185,7 +192,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
reflog_action = args_to_str(argv);
setenv("GIT_REFLOG_ACTION", reflog_action, 0);
- if (i < argc) {
+ while (i < argc) {
if (!strcmp(argv[i], "--mixed")) {
reset_type = MIXED;
i++;
@@ -198,6 +205,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
reset_type = HARD;
i++;
}
+ else if (!strcmp(argv[i], "-q")) {
+ quiet = 1;
+ i++;
+ }
+ else
+ break;
}
if (i < argc && argv[i][0] != '-')
@@ -225,13 +238,14 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
else if (reset_type != NONE)
die("Cannot do %s reset with paths.",
reset_type_names[reset_type]);
- if (read_from_tree(prefix, argv + i, sha1))
- return 1;
- return update_index_refresh() ? 1 : 0;
+ return read_from_tree(prefix, argv + i, sha1);
}
if (reset_type == NONE)
reset_type = MIXED; /* by default */
+ if (reset_type == HARD && is_bare_repository())
+ die("hard reset makes no sense in a bare repository");
+
/* Soft reset does not touch the index file nor the working tree
* at all, but requires them in a good order. Other resets reset
* the index file to the tree object we are switching to. */
@@ -258,13 +272,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
switch (reset_type) {
case HARD:
- if (!update_ref_status)
+ if (!update_ref_status && !quiet)
print_new_head_line(commit);
break;
case SOFT: /* Nothing else to do. */
break;
case MIXED: /* Report what has not been updated. */
- update_index_refresh();
+ update_index_refresh(0, NULL);
break;
}
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index 33726b8d84..6f7d5f8214 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -9,6 +9,7 @@
#include "revision.h"
#include "list-objects.h"
#include "builtin.h"
+#include "log-tree.h"
/* bits #0-15 in revision.h */
@@ -25,6 +26,7 @@ static const char rev_list_usage[] =
" --remove-empty\n"
" --all\n"
" --stdin\n"
+" --quiet\n"
" ordering output:\n"
" --topo-order\n"
" --date-order\n"
@@ -38,7 +40,8 @@ static const char rev_list_usage[] =
" --left-right\n"
" special purpose:\n"
" --bisect\n"
-" --bisect-vars"
+" --bisect-vars\n"
+" --bisect-all"
;
static struct rev_info revs;
@@ -48,6 +51,7 @@ static int show_timestamp;
static int hdr_termination;
static const char *header_prefix;
+static void finish_commit(struct commit *commit);
static void show_commit(struct commit *commit)
{
if (show_timestamp)
@@ -56,6 +60,8 @@ static void show_commit(struct commit *commit)
fputs(header_prefix, stdout);
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('<');
@@ -74,21 +80,28 @@ static void show_commit(struct commit *commit)
parents = parents->next;
}
}
+ show_decorations(commit);
if (revs.commit_format == CMIT_FMT_ONELINE)
putchar(' ');
else
putchar('\n');
- if (revs.verbose_header) {
+ if (revs.verbose_header && commit->buffer) {
struct strbuf buf;
strbuf_init(&buf, 0);
pretty_print_commit(revs.commit_format, commit,
- &buf, revs.abbrev, NULL, NULL, revs.date_mode);
+ &buf, revs.abbrev, NULL, NULL,
+ revs.date_mode, 0);
if (buf.len)
printf("%s%c", buf.buf, hdr_termination);
strbuf_release(&buf);
}
maybe_flush_or_die(stdout, "stdout");
+ finish_commit(commit);
+}
+
+static void finish_commit(struct commit *commit)
+{
if (commit->parents) {
free_commit_list(commit->parents);
commit->parents = NULL;
@@ -97,6 +110,12 @@ static void show_commit(struct commit *commit)
commit->buffer = NULL;
}
+static void finish_object(struct object_array_entry *p)
+{
+ if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1))
+ die("missing blob object '%s'", sha1_to_hex(p->item->sha1));
+}
+
static void show_object(struct object_array_entry *p)
{
/* An object with name "foo\n0000000..." can be used to
@@ -104,9 +123,7 @@ static void show_object(struct object_array_entry *p)
*/
const char *ep = strchr(p->name, '\n');
- if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1))
- die("missing blob object '%s'", sha1_to_hex(p->item->sha1));
-
+ finish_object(p);
if (ep) {
printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
(int) (ep - p->name),
@@ -138,7 +155,7 @@ static int count_distance(struct commit_list *entry)
if (commit->object.flags & (UNINTERESTING | COUNTED))
break;
- if (!revs.prune_fn || (commit->object.flags & TREECHANGE))
+ if (!(commit->object.flags & TREESAME))
nr++;
commit->object.flags |= COUNTED;
p = commit->parents;
@@ -194,7 +211,7 @@ static inline int halfway(struct commit_list *p, int nr)
/*
* Don't short-cut something we are not going to return!
*/
- if (revs.prune_fn && !(p->item->object.flags & TREECHANGE))
+ if (p->item->object.flags & TREESAME)
return 0;
if (DEBUG_BISECT)
return 0;
@@ -230,7 +247,7 @@ static void show_list(const char *debug, int counted, int nr,
char *ep, *sp;
fprintf(stderr, "%c%c%c ",
- (flags & TREECHANGE) ? 'T' : ' ',
+ (flags & TREESAME) ? ' ' : 'T',
(flags & UNINTERESTING) ? 'U' : ' ',
(flags & COUNTED) ? 'C' : ' ');
if (commit->util)
@@ -264,7 +281,7 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
int distance;
unsigned flags = p->item->object.flags;
- if (revs.prune_fn && !(flags & TREECHANGE))
+ if (flags & TREESAME)
continue;
distance = weight(p);
if (nr - distance < distance)
@@ -278,6 +295,57 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
return best;
}
+struct commit_dist {
+ struct commit *commit;
+ int distance;
+};
+
+static int compare_commit_dist(const void *a_, const void *b_)
+{
+ struct commit_dist *a, *b;
+
+ a = (struct commit_dist *)a_;
+ b = (struct commit_dist *)b_;
+ if (a->distance != b->distance)
+ return b->distance - a->distance; /* desc sort */
+ return hashcmp(a->commit->object.sha1, b->commit->object.sha1);
+}
+
+static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
+{
+ struct commit_list *p;
+ struct commit_dist *array = xcalloc(nr, sizeof(*array));
+ int cnt, i;
+
+ for (p = list, cnt = 0; p; p = p->next) {
+ int distance;
+ unsigned flags = p->item->object.flags;
+
+ if (flags & TREESAME)
+ continue;
+ distance = weight(p);
+ if (nr - distance < distance)
+ distance = nr - distance;
+ array[cnt].commit = p->item;
+ array[cnt].distance = distance;
+ cnt++;
+ }
+ qsort(array, cnt, sizeof(*array), compare_commit_dist);
+ for (p = list, i = 0; i < cnt; i++) {
+ struct name_decoration *r = xmalloc(sizeof(*r) + 100);
+ struct object *obj = &(array[i].commit->object);
+
+ sprintf(r->name, "dist=%d", array[i].distance);
+ r->next = add_decoration(&name_decoration, obj, r);
+ p->item = array[i].commit;
+ p = p->next;
+ }
+ if (p)
+ p->next = NULL;
+ free(array);
+ return list;
+}
+
/*
* zero or positive weight is the number of interesting commits it can
* reach, including itself. Especially, weight = 0 means it does not
@@ -292,7 +360,8 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
* or positive distance.
*/
static struct commit_list *do_find_bisection(struct commit_list *list,
- int nr, int *weights)
+ int nr, int *weights,
+ int find_all)
{
int n, counted;
struct commit_list *p;
@@ -306,7 +375,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
p->item->util = &weights[n++];
switch (count_interesting_parents(commit)) {
case 0:
- if (!revs.prune_fn || (flags & TREECHANGE)) {
+ if (!(flags & TREESAME)) {
weight_set(p, 1);
counted++;
show_list("bisection 2 count one",
@@ -351,7 +420,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
clear_distance(list);
/* Does it happen to be at exactly half-way? */
- if (halfway(p, nr))
+ if (!find_all && halfway(p, nr))
return p;
counted++;
}
@@ -379,7 +448,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
* add one for p itself if p is to be counted,
* otherwise inherit it from q directly.
*/
- if (!revs.prune_fn || (flags & TREECHANGE)) {
+ if (!(flags & TREESAME)) {
weight_set(p, weight(q)+1);
counted++;
show_list("bisection 2 count one",
@@ -389,19 +458,22 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
weight_set(p, weight(q));
/* Does it happen to be at exactly half-way? */
- if (halfway(p, nr))
+ if (!find_all && halfway(p, nr))
return p;
}
}
show_list("bisection 2 counted all", counted, nr, list);
- /* Then find the best one */
- return best_bisection(list, nr);
+ if (!find_all)
+ return best_bisection(list, nr);
+ else
+ return best_bisection_sorted(list, nr);
}
static struct commit_list *find_bisection(struct commit_list *list,
- int *reaches, int *all)
+ int *reaches, int *all,
+ int find_all)
{
int nr, on_list;
struct commit_list *p, *best, *next, *last;
@@ -423,7 +495,7 @@ static struct commit_list *find_bisection(struct commit_list *list,
continue;
p->next = last;
last = p;
- if (!revs.prune_fn || (flags & TREECHANGE))
+ if (!(flags & TREESAME))
nr++;
on_list++;
}
@@ -434,14 +506,13 @@ static struct commit_list *find_bisection(struct commit_list *list,
weights = xcalloc(on_list, sizeof(*weights));
/* Do the real work of finding bisection commit. */
- best = do_find_bisection(list, nr, weights);
-
+ best = do_find_bisection(list, nr, weights, find_all);
if (best) {
- best->next = NULL;
+ if (!find_all)
+ best->next = NULL;
*reaches = weight(best);
}
free(weights);
-
return best;
}
@@ -451,7 +522,7 @@ static void read_revisions_from_stdin(struct rev_info *revs)
while (fgets(line, sizeof(line), stdin) != NULL) {
int len = strlen(line);
- if (line[len - 1] == '\n')
+ if (len && line[len - 1] == '\n')
line[--len] = 0;
if (!len)
break;
@@ -468,6 +539,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
int i;
int read_from_stdin = 0;
int bisect_show_vars = 0;
+ int bisect_find_all = 0;
+ int quiet = 0;
git_config(git_default_config);
init_revisions(&revs, prefix);
@@ -490,6 +563,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
bisect_list = 1;
continue;
}
+ if (!strcmp(arg, "--bisect-all")) {
+ bisect_list = 1;
+ bisect_find_all = 1;
+ continue;
+ }
if (!strcmp(arg, "--bisect-vars")) {
bisect_list = 1;
bisect_show_vars = 1;
@@ -501,6 +579,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
read_revisions_from_stdin(&revs);
continue;
}
+ if (!strcmp(arg, "--quiet")) {
+ quiet = 1;
+ continue;
+ }
usage(rev_list_usage);
}
@@ -529,16 +611,19 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (bisect_list)
revs.limited = 1;
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
if (revs.tree_objects)
mark_edges_uninteresting(revs.commits, &revs, show_edge);
if (bisect_list) {
int reaches = reaches, all = all;
- revs.commits = find_bisection(revs.commits, &reaches, &all);
+ revs.commits = find_bisection(revs.commits, &reaches, &all,
+ bisect_find_all);
if (bisect_show_vars) {
int cnt;
+ char hex[41];
if (!revs.commits)
return 1;
/*
@@ -550,15 +635,22 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
* A bisect set of size N has (N-1) commits further
* to test, as we already know one bad one.
*/
- cnt = all-reaches;
+ cnt = all - reaches;
if (cnt < reaches)
cnt = reaches;
+ strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1));
+
+ if (bisect_find_all) {
+ traverse_commit_list(&revs, show_commit, show_object);
+ printf("------\n");
+ }
+
printf("bisect_rev=%s\n"
"bisect_nr=%d\n"
"bisect_good=%d\n"
"bisect_bad=%d\n"
"bisect_all=%d\n",
- sha1_to_hex(revs.commits->item->object.sha1),
+ hex,
cnt - 1,
all - reaches - 1,
reaches - 1,
@@ -567,7 +659,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
}
}
- traverse_commit_list(&revs, show_commit, show_object);
+ traverse_commit_list(&revs,
+ quiet ? finish_commit : show_commit,
+ quiet ? finish_object : show_object);
return 0;
}
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 8d78b69c90..b9af1a5a55 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -8,6 +8,7 @@
#include "refs.h"
#include "quote.h"
#include "builtin.h"
+#include "parse-options.h"
#define DO_REVS 1
#define DO_NOREV 2
@@ -20,6 +21,9 @@ static const char *def;
#define NORMAL 0
#define REVERSED 1
static int show_type = NORMAL;
+
+#define SHOW_SYMBOLIC_ASIS 1
+#define SHOW_SYMBOLIC_FULL 2
static int symbolic;
static int abbrev;
static int output_sq;
@@ -102,8 +106,32 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
if (type != show_type)
putchar('^');
- if (symbolic && name)
- show(name);
+ if (symbolic && name) {
+ if (symbolic == SHOW_SYMBOLIC_FULL) {
+ unsigned char discard[20];
+ char *full;
+
+ switch (dwim_ref(name, strlen(name), discard, &full)) {
+ case 0:
+ /*
+ * Not found -- not a ref. We could
+ * emit "name" here, but symbolic-full
+ * users are interested in finding the
+ * refs spelled in full, and they would
+ * need to filter non-refs if we did so.
+ */
+ break;
+ case 1: /* happy */
+ show(full);
+ break;
+ default: /* ambiguous */
+ error("refname '%s' is ambiguous", name);
+ break;
+ }
+ } else {
+ show(name);
+ }
+ }
else if (abbrev)
show(find_unique_abbrev(sha1, abbrev));
else
@@ -209,13 +237,138 @@ static int try_difference(const char *arg)
return 0;
}
+static int parseopt_dump(const struct option *o, const char *arg, int unset)
+{
+ struct strbuf *parsed = o->value;
+ if (unset)
+ strbuf_addf(parsed, " --no-%s", o->long_name);
+ else if (o->short_name)
+ strbuf_addf(parsed, " -%c", o->short_name);
+ else
+ strbuf_addf(parsed, " --%s", o->long_name);
+ if (arg) {
+ strbuf_addch(parsed, ' ');
+ sq_quote_buf(parsed, arg);
+ }
+ return 0;
+}
+
+static const char *skipspaces(const char *s)
+{
+ while (isspace(*s))
+ s++;
+ return s;
+}
+
+static int cmd_parseopt(int argc, const char **argv, const char *prefix)
+{
+ static int keep_dashdash = 0;
+ static char const * const parseopt_usage[] = {
+ "git-rev-parse --parseopt [options] -- [<args>...]",
+ NULL
+ };
+ static struct option parseopt_opts[] = {
+ OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
+ "keep the `--` passed as an arg"),
+ OPT_END(),
+ };
+
+ struct strbuf sb, parsed;
+ const char **usage = NULL;
+ struct option *opts = NULL;
+ int onb = 0, osz = 0, unb = 0, usz = 0;
+
+ strbuf_init(&parsed, 0);
+ strbuf_addstr(&parsed, "set --");
+ argc = parse_options(argc, argv, parseopt_opts, parseopt_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+ if (argc < 1 || strcmp(argv[0], "--"))
+ usage_with_options(parseopt_usage, parseopt_opts);
+
+ strbuf_init(&sb, 0);
+ /* get the usage up to the first line with a -- on it */
+ for (;;) {
+ if (strbuf_getline(&sb, stdin, '\n') == EOF)
+ die("premature end of input");
+ ALLOC_GROW(usage, unb + 1, usz);
+ if (!strcmp("--", sb.buf)) {
+ if (unb < 1)
+ die("no usage string given before the `--' separator");
+ usage[unb] = NULL;
+ break;
+ }
+ usage[unb++] = strbuf_detach(&sb, NULL);
+ }
+
+ /* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */
+ while (strbuf_getline(&sb, stdin, '\n') != EOF) {
+ const char *s;
+ struct option *o;
+
+ if (!sb.len)
+ continue;
+
+ ALLOC_GROW(opts, onb + 1, osz);
+ memset(opts + onb, 0, sizeof(opts[onb]));
+
+ o = &opts[onb++];
+ s = strchr(sb.buf, ' ');
+ if (!s || *sb.buf == ' ') {
+ o->type = OPTION_GROUP;
+ o->help = xstrdup(skipspaces(s));
+ continue;
+ }
+
+ o->type = OPTION_CALLBACK;
+ o->help = xstrdup(skipspaces(s));
+ o->value = &parsed;
+ o->callback = &parseopt_dump;
+ switch (s[-1]) {
+ case '=':
+ s--;
+ break;
+ case '?':
+ o->flags = PARSE_OPT_OPTARG;
+ s--;
+ break;
+ default:
+ o->flags = PARSE_OPT_NOARG;
+ break;
+ }
+
+ if (s - sb.buf == 1) /* short option only */
+ o->short_name = *sb.buf;
+ else if (sb.buf[1] != ',') /* long option only */
+ o->long_name = xmemdupz(sb.buf, s - sb.buf);
+ else {
+ o->short_name = *sb.buf;
+ o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
+ }
+ }
+ strbuf_release(&sb);
+
+ /* put an OPT_END() */
+ ALLOC_GROW(opts, onb + 1, osz);
+ memset(opts + onb, 0, sizeof(opts[onb]));
+ argc = parse_options(argc, argv, opts, usage,
+ keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0);
+
+ strbuf_addf(&parsed, " --");
+ sq_quote_argv(&parsed, argv, 0);
+ puts(parsed.buf);
+ return 0;
+}
+
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
{
int i, as_is = 0, verify = 0;
unsigned char sha1[20];
- git_config(git_default_config);
+ if (argc > 1 && !strcmp("--parseopt", argv[1]))
+ return cmd_parseopt(argc - 1, argv + 1, prefix);
+ prefix = setup_git_directory();
+ git_config(git_default_config);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -295,7 +448,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--symbolic")) {
- symbolic = 1;
+ symbolic = SHOW_SYMBOLIC_ASIS;
+ continue;
+ }
+ if (!strcmp(arg, "--symbolic-full-name")) {
+ symbolic = SHOW_SYMBOLIC_FULL;
continue;
}
if (!strcmp(arg, "--all")) {
diff --git a/builtin-revert.c b/builtin-revert.c
index a655c8ee2a..e219859f9b 100644
--- a/builtin-revert.c
+++ b/builtin-revert.c
@@ -7,6 +7,8 @@
#include "run-command.h"
#include "exec_cmd.h"
#include "utf8.h"
+#include "parse-options.h"
+#include "cache-tree.h"
/*
* This implements the builtins revert and cherry-pick.
@@ -19,51 +21,44 @@
* Copyright (c) 2005 Junio C Hamano
*/
-static const char *revert_usage = "git-revert [--edit | --no-edit] [-n] <commit-ish>";
+static const char * const revert_usage[] = {
+ "git-revert [options] <commit-ish>",
+ NULL
+};
-static const char *cherry_pick_usage = "git-cherry-pick [--edit] [-n] [-r] [-x] <commit-ish>";
+static const char * const cherry_pick_usage[] = {
+ "git-cherry-pick [options] <commit-ish>",
+ NULL
+};
-static int edit;
-static int replay;
+static int edit, no_replay, no_commit, mainline;
static enum { REVERT, CHERRY_PICK } action;
-static int no_commit;
static struct commit *commit;
-static int needed_deref;
static const char *me;
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-static void parse_options(int argc, const char **argv)
+static void parse_args(int argc, const char **argv)
{
- const char *usage_str = action == REVERT ?
- revert_usage : cherry_pick_usage;
+ const char * const * usage_str =
+ action == REVERT ? revert_usage : cherry_pick_usage;
unsigned char sha1[20];
const char *arg;
- int i;
+ int noop;
+ 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('x', NULL, &no_replay, "append commit name when cherry-picking"),
+ OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
+ OPT_INTEGER('m', "mainline", &mainline, "parent number"),
+ OPT_END(),
+ };
+
+ if (parse_options(argc, argv, options, usage_str, 0) != 1)
+ usage_with_options(usage_str, options);
+ arg = argv[0];
- if (argc < 2)
- usage(usage_str);
-
- for (i = 1; i < argc; i++) {
- arg = argv[i];
- if (arg[0] != '-')
- break;
- if (!strcmp(arg, "-n") || !strcmp(arg, "--no-commit"))
- no_commit = 1;
- else if (!strcmp(arg, "-e") || !strcmp(arg, "--edit"))
- edit = 1;
- else if (!strcmp(arg, "--no-edit"))
- edit = 0;
- else if (!strcmp(arg, "-x") || !strcmp(arg, "--i-really-want-"
- "to-expose-my-private-commit-object-name"))
- replay = 0;
- else if (strcmp(arg, "-r"))
- usage(usage_str);
- }
- if (i != argc - 1)
- usage(usage_str);
- arg = argv[argc - 1];
if (get_sha1(arg, sha1))
die ("Cannot find '%s'", arg);
commit = (struct commit *)parse_object(sha1);
@@ -72,7 +67,6 @@ static void parse_options(int argc, const char **argv)
if (commit->object.type == OBJ_TAG) {
commit = (struct commit *)
deref_tag((struct object *)commit, arg, strlen(arg));
- needed_deref = 1;
}
if (commit->object.type != OBJ_COMMIT)
die ("'%s' does not point to a commit", arg);
@@ -231,10 +225,31 @@ static int merge_recursive(const char *base_sha1,
return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD);
}
+static char *help_msg(const unsigned char *sha1)
+{
+ static char helpbuf[1024];
+ char *msg = getenv("GIT_CHERRY_PICK_HELP");
+
+ if (msg)
+ return msg;
+
+ strcpy(helpbuf, " After resolving the conflicts,\n"
+ "mark the corrected paths with 'git add <paths>' "
+ "or 'git rm <paths>' and commit the result.");
+
+ if (action == CHERRY_PICK) {
+ sprintf(helpbuf + strlen(helpbuf),
+ "\nWhen commiting, use the option "
+ "'-c %s' to retain authorship and message.",
+ find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ }
+ return helpbuf;
+}
+
static int revert_or_cherry_pick(int argc, const char **argv)
{
unsigned char head[20];
- struct commit *base, *next;
+ struct commit *base, *next, *parent;
int i;
char *oneline, *reencoded_message = NULL;
const char *message, *encoding;
@@ -243,18 +258,20 @@ static int revert_or_cherry_pick(int argc, const char **argv)
git_config(git_default_config);
me = action == REVERT ? "revert" : "cherry-pick";
setenv(GIT_REFLOG_ACTION, me, 0);
- parse_options(argc, argv);
+ parse_args(argc, argv);
/* this is copied from the shell script, but it's never triggered... */
- if (action == REVERT && replay)
+ if (action == REVERT && !no_replay)
die("revert is incompatible with replay");
if (no_commit) {
/*
* We do not intend to commit immediately. We just want to
- * merge the differences in.
+ * merge the differences in, so let's compute the tree
+ * that represents the "current" state for merge-recursive
+ * to work on.
*/
- if (write_tree(head, 0, NULL))
+ if (write_cache_as_tree(head, 0, NULL))
die ("Your index file is unmerged.");
} else {
struct wt_status s;
@@ -262,15 +279,36 @@ static int revert_or_cherry_pick(int argc, const char **argv)
if (get_sha1("HEAD", head))
die ("You do not have a valid HEAD");
wt_status_prepare(&s);
- if (s.commitable || s.workdir_dirty)
+ if (s.commitable)
die ("Dirty index: cannot %s", me);
discard_cache();
}
if (!commit->parents)
die ("Cannot %s a root commit", me);
- if (commit->parents->next)
- die ("Cannot %s a multi-parent commit.", me);
+ if (commit->parents->next) {
+ /* Reverting or cherry-picking a merge commit */
+ int cnt;
+ struct commit_list *p;
+
+ if (!mainline)
+ die("Commit %s is a merge but no -m option was given.",
+ sha1_to_hex(commit->object.sha1));
+
+ for (cnt = 1, p = commit->parents;
+ cnt != mainline && p;
+ cnt++)
+ p = p->next;
+ if (cnt != mainline || !p)
+ 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.",
+ sha1_to_hex(commit->object.sha1));
+ else
+ parent = commit->parents->item;
+
if (!(message = commit->buffer))
die ("Cannot get commit message for %s",
sha1_to_hex(commit->object.sha1));
@@ -299,39 +337,28 @@ static int revert_or_cherry_pick(int argc, const char **argv)
char *oneline_body = strchr(oneline, ' ');
base = commit;
- next = commit->parents->item;
+ next = parent;
add_to_msg("Revert \"");
add_to_msg(oneline_body + 1);
add_to_msg("\"\n\nThis reverts commit ");
add_to_msg(sha1_to_hex(commit->object.sha1));
add_to_msg(".\n");
} else {
- base = commit->parents->item;
+ base = parent;
next = commit;
set_author_ident_env(message);
add_message_to_msg(message);
- if (!replay) {
+ if (no_replay) {
add_to_msg("(cherry picked from commit ");
add_to_msg(sha1_to_hex(commit->object.sha1));
add_to_msg(")\n");
}
}
- if (needed_deref) {
- add_to_msg("(original 'git ");
- add_to_msg(me);
- add_to_msg("' arguments: ");
- for (i = 0; i < argc; i++) {
- if (i)
- add_to_msg(" ");
- add_to_msg(argv[i]);
- }
- add_to_msg(")\n");
- }
if (merge_recursive(sha1_to_hex(base->object.sha1),
sha1_to_hex(head), "HEAD",
sha1_to_hex(next->object.sha1), oneline) ||
- write_tree(head, 0, NULL)) {
+ write_cache_as_tree(head, 0, NULL)) {
add_to_msg("\nConflicts:\n\n");
read_cache();
for (i = 0; i < active_nr;) {
@@ -345,21 +372,13 @@ static int revert_or_cherry_pick(int argc, const char **argv)
i++;
}
}
- if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
+ if (commit_lock_file(&msg_file) < 0)
die ("Error wrapping up %s", defmsg);
- fprintf(stderr, "Automatic %s failed. "
- "After resolving the conflicts,\n"
- "mark the corrected paths with 'git-add <paths>'\n"
- "and commit the result.\n", me);
- if (action == CHERRY_PICK) {
- fprintf(stderr, "When commiting, use the option "
- "'-c %s' to retain authorship and message.\n",
- find_unique_abbrev(commit->object.sha1,
- DEFAULT_ABBREV));
- }
+ fprintf(stderr, "Automatic %s failed.%s\n",
+ me, help_msg(commit->object.sha1));
exit(1);
}
- if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
+ if (commit_lock_file(&msg_file) < 0)
die ("Error wrapping up %s", defmsg);
fprintf(stderr, "Finished one %s.\n", me);
@@ -388,13 +407,14 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
{
if (isatty(0))
edit = 1;
+ no_replay = 1;
action = REVERT;
return revert_or_cherry_pick(argc, argv);
}
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
{
- replay = 1;
+ no_replay = 0;
action = CHERRY_PICK;
return revert_or_cherry_pick(argc, argv);
}
diff --git a/builtin-rm.c b/builtin-rm.c
index 3b0677e44b..c0a8bb6cf5 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -8,9 +8,12 @@
#include "dir.h"
#include "cache-tree.h"
#include "tree-walk.h"
+#include "parse-options.h"
-static const char builtin_rm_usage[] =
-"git-rm [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...";
+static const char * const builtin_rm_usage[] = {
+ "git-rm [options] [--] <file>...",
+ NULL
+};
static struct {
int nr, alloc;
@@ -121,11 +124,23 @@ static int check_local_mod(unsigned char *head, int index_only)
static struct lock_file lock_file;
+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_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"),
+ OPT_BOOLEAN('f', NULL, &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"),
+ OPT_END(),
+};
+
int cmd_rm(int argc, const char **argv, const char *prefix)
{
int i, newfd;
- int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
- int ignore_unmatch = 0;
const char **pathspec;
char *seen;
@@ -136,34 +151,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die("index file corrupt");
- for (i = 1 ; i < argc ; i++) {
- const char *arg = argv[i];
+ argc = parse_options(argc, argv, builtin_rm_options, builtin_rm_usage, 0);
+ if (!argc)
+ usage_with_options(builtin_rm_usage, builtin_rm_options);
- if (*arg != '-')
- break;
- else if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- else if (!strcmp(arg, "-n"))
- show_only = 1;
- else if (!strcmp(arg, "--cached"))
- index_only = 1;
- else if (!strcmp(arg, "-f"))
- force = 1;
- else if (!strcmp(arg, "-r"))
- recursive = 1;
- else if (!strcmp(arg, "--quiet"))
- quiet = 1;
- else if (!strcmp(arg, "--ignore-unmatch"))
- ignore_unmatch = 1;
- else
- usage(builtin_rm_usage);
- }
- if (argc <= i)
- usage(builtin_rm_usage);
+ if (!index_only)
+ setup_work_tree();
- pathspec = get_pathspec(prefix, argv + i);
+ pathspec = get_pathspec(prefix, argv);
seen = NULL;
for (i = 0; pathspec[i] ; i++)
/* nothing */;
@@ -255,7 +250,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
- close(newfd) || commit_locked_index(&lock_file))
+ commit_locked_index(&lock_file))
die("Unable to write new index file");
}
diff --git a/builtin-runstatus.c b/builtin-runstatus.c
deleted file mode 100644
index 2db25c88bf..0000000000
--- a/builtin-runstatus.c
+++ /dev/null
@@ -1,37 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "wt-status.h"
-
-extern int wt_status_use_color;
-
-static const char runstatus_usage[] =
-"git-runstatus [--color|--nocolor] [--amend] [--verbose] [--untracked]";
-
-int cmd_runstatus(int argc, const char **argv, const char *prefix)
-{
- struct wt_status s;
- int i;
-
- git_config(git_status_config);
- wt_status_prepare(&s);
-
- for (i = 1; i < argc; i++) {
- if (!strcmp(argv[i], "--color"))
- wt_status_use_color = 1;
- else if (!strcmp(argv[i], "--nocolor"))
- wt_status_use_color = 0;
- else if (!strcmp(argv[i], "--amend")) {
- s.amend = 1;
- s.reference = "HEAD^1";
- }
- else if (!strcmp(argv[i], "--verbose"))
- s.verbose = 1;
- else if (!strcmp(argv[i], "--untracked"))
- s.untracked = 1;
- else
- usage(runstatus_usage);
- }
-
- wt_status_print(&s);
- return s.commitable ? 0 : 1;
-}
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
new file mode 100644
index 0000000000..8afb1d0bca
--- /dev/null
+++ b/builtin-send-pack.c
@@ -0,0 +1,654 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "run-command.h"
+#include "remote.h"
+#include "send-pack.h"
+
+static const char send_pack_usage[] =
+"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+" --all and explicit <ref> specification are mutually exclusive.";
+
+static struct send_pack_args args = {
+ /* .receivepack = */ "git-receive-pack",
+};
+
+/*
+ * Make a pack stream and spit it out into file descriptor fd
+ */
+static int pack_objects(int fd, struct ref *refs)
+{
+ /*
+ * The child becomes pack-objects --revs; we feed
+ * the revision parameters to it via its stdin and
+ * let its stdout go back to the other end.
+ */
+ const char *argv[] = {
+ "pack-objects",
+ "--all-progress",
+ "--revs",
+ "--stdout",
+ NULL,
+ NULL,
+ };
+ struct child_process po;
+
+ if (args.use_thin_pack)
+ argv[4] = "--thin";
+ memset(&po, 0, sizeof(po));
+ po.argv = argv;
+ po.in = -1;
+ po.out = fd;
+ po.git_cmd = 1;
+ if (start_command(&po))
+ die("git-pack-objects failed (%s)", strerror(errno));
+
+ /*
+ * We feed the pack-objects we just spawned with revision
+ * parameters by writing to the pipe.
+ */
+ while (refs) {
+ char buf[42];
+
+ if (!is_null_sha1(refs->old_sha1) &&
+ has_sha1_file(refs->old_sha1)) {
+ memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
+ buf[0] = '^';
+ buf[41] = '\n';
+ if (!write_or_whine(po.in, buf, 42,
+ "send-pack: send refs"))
+ break;
+ }
+ if (!is_null_sha1(refs->new_sha1)) {
+ memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
+ buf[40] = '\n';
+ if (!write_or_whine(po.in, buf, 41,
+ "send-pack: send refs"))
+ break;
+ }
+ refs = refs->next;
+ }
+
+ if (finish_command(&po))
+ return error("pack-objects died with strange error");
+ return 0;
+}
+
+static void unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+ while (list) {
+ struct commit_list *temp = list;
+ temp->item->object.flags &= ~mark;
+ list = temp->next;
+ free(temp);
+ }
+}
+
+static int ref_newer(const unsigned char *new_sha1,
+ const unsigned char *old_sha1)
+{
+ struct object *o;
+ struct commit *old, *new;
+ struct commit_list *list, *used;
+ int found = 0;
+
+ /* Both new and old must be commit-ish and new is descendant of
+ * old. Otherwise we require --force.
+ */
+ o = deref_tag(parse_object(old_sha1), NULL, 0);
+ if (!o || o->type != OBJ_COMMIT)
+ return 0;
+ old = (struct commit *) o;
+
+ o = deref_tag(parse_object(new_sha1), NULL, 0);
+ if (!o || o->type != OBJ_COMMIT)
+ return 0;
+ new = (struct commit *) o;
+
+ if (parse_commit(new) < 0)
+ return 0;
+
+ used = list = NULL;
+ commit_list_insert(new, &list);
+ while (list) {
+ new = pop_most_recent_commit(&list, 1);
+ commit_list_insert(new, &used);
+ if (new == old) {
+ found = 1;
+ break;
+ }
+ }
+ unmark_and_free(list, 1);
+ unmark_and_free(used, 1);
+ return found;
+}
+
+static struct ref *local_refs, **local_tail;
+static struct ref *remote_refs, **remote_tail;
+
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct ref *ref;
+ int len = strlen(refname) + 1;
+ ref = xcalloc(1, sizeof(*ref) + len);
+ hashcpy(ref->new_sha1, sha1);
+ memcpy(ref->name, refname, len);
+ *local_tail = ref;
+ local_tail = &ref->next;
+ return 0;
+}
+
+static void get_local_heads(void)
+{
+ local_tail = &local_refs;
+ for_each_ref(one_local_ref, NULL);
+}
+
+static int receive_status(int in, struct ref *refs)
+{
+ struct ref *hint;
+ char line[1000];
+ int ret = 0;
+ int len = packet_read_line(in, line, sizeof(line));
+ if (len < 10 || memcmp(line, "unpack ", 7))
+ return error("did not receive remote status");
+ if (memcmp(line, "unpack ok\n", 10)) {
+ char *p = line + strlen(line) - 1;
+ if (*p == '\n')
+ *p = '\0';
+ error("unpack failed: %s", line + 7);
+ ret = -1;
+ }
+ hint = NULL;
+ while (1) {
+ char *refname;
+ char *msg;
+ len = packet_read_line(in, line, sizeof(line));
+ if (!len)
+ break;
+ if (len < 3 ||
+ (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
+ fprintf(stderr, "protocol error: %s\n", line);
+ ret = -1;
+ break;
+ }
+
+ line[strlen(line)-1] = '\0';
+ refname = line + 3;
+ msg = strchr(refname, ' ');
+ if (msg)
+ *msg++ = '\0';
+
+ /* first try searching at our hint, falling back to all refs */
+ if (hint)
+ hint = find_ref_by_name(hint, refname);
+ if (!hint)
+ hint = find_ref_by_name(refs, refname);
+ if (!hint) {
+ warning("remote reported status on unknown ref: %s",
+ refname);
+ continue;
+ }
+ if (hint->status != REF_STATUS_EXPECTING_REPORT) {
+ warning("remote reported status on unexpected ref: %s",
+ refname);
+ continue;
+ }
+
+ if (line[0] == 'o' && line[1] == 'k')
+ hint->status = REF_STATUS_OK;
+ else {
+ hint->status = REF_STATUS_REMOTE_REJECT;
+ ret = -1;
+ }
+ if (msg)
+ hint->remote_status = xstrdup(msg);
+ /* start our next search from the next ref */
+ hint = hint->next;
+ }
+ return ret;
+}
+
+static void update_tracking_ref(struct remote *remote, struct ref *ref)
+{
+ struct refspec rs;
+
+ if (ref->status != REF_STATUS_OK)
+ return;
+
+ rs.src = ref->name;
+ rs.dst = NULL;
+
+ if (!remote_find_tracking(remote, &rs)) {
+ if (args.verbose)
+ fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
+ if (ref->deletion) {
+ if (delete_ref(rs.dst, NULL))
+ error("Failed to delete");
+ } else
+ update_ref("update by push", rs.dst,
+ ref->new_sha1, NULL, 0, 0);
+ free(rs.dst);
+ }
+}
+
+static const char *prettify_ref(const struct ref *ref)
+{
+ const char *name = ref->name;
+ return name + (
+ !prefixcmp(name, "refs/heads/") ? 11 :
+ !prefixcmp(name, "refs/tags/") ? 10 :
+ !prefixcmp(name, "refs/remotes/") ? 13 :
+ 0);
+}
+
+#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+
+static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
+{
+ fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
+ if (from)
+ fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to));
+ else
+ fputs(prettify_ref(to), stderr);
+ if (msg) {
+ fputs(" (", stderr);
+ fputs(msg, stderr);
+ fputc(')', stderr);
+ }
+ fputc('\n', stderr);
+}
+
+static const char *status_abbrev(unsigned char sha1[20])
+{
+ const char *abbrev;
+ abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
+ return abbrev ? abbrev : sha1_to_hex(sha1);
+}
+
+static void print_ok_ref_status(struct ref *ref)
+{
+ if (ref->deletion)
+ print_ref_status('-', "[deleted]", ref, NULL, NULL);
+ else if (is_null_sha1(ref->old_sha1))
+ print_ref_status('*',
+ (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
+ "[new branch]"),
+ ref, ref->peer_ref, NULL);
+ else {
+ char quickref[84];
+ char type;
+ const char *msg;
+
+ strcpy(quickref, status_abbrev(ref->old_sha1));
+ if (ref->nonfastforward) {
+ strcat(quickref, "...");
+ type = '+';
+ msg = "forced update";
+ } else {
+ strcat(quickref, "..");
+ type = ' ';
+ msg = NULL;
+ }
+ strcat(quickref, status_abbrev(ref->new_sha1));
+
+ print_ref_status(type, quickref, ref, ref->peer_ref, msg);
+ }
+}
+
+static int print_one_push_status(struct ref *ref, const char *dest, int count)
+{
+ if (!count)
+ fprintf(stderr, "To %s\n", dest);
+
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ print_ref_status('X', "[no match]", ref, NULL, NULL);
+ break;
+ case REF_STATUS_REJECT_NODELETE:
+ print_ref_status('!', "[rejected]", ref, NULL,
+ "remote does not support deleting refs");
+ break;
+ case REF_STATUS_UPTODATE:
+ print_ref_status('=', "[up to date]", ref,
+ ref->peer_ref, NULL);
+ break;
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+ "non-fast forward");
+ break;
+ case REF_STATUS_REMOTE_REJECT:
+ print_ref_status('!', "[remote rejected]", ref,
+ ref->deletion ? NULL : ref->peer_ref,
+ ref->remote_status);
+ break;
+ case REF_STATUS_EXPECTING_REPORT:
+ print_ref_status('!', "[remote failure]", ref,
+ ref->deletion ? NULL : ref->peer_ref,
+ "remote failed to report status");
+ break;
+ case REF_STATUS_OK:
+ print_ok_ref_status(ref);
+ break;
+ }
+
+ return 1;
+}
+
+static void print_push_status(const char *dest, struct ref *refs)
+{
+ struct ref *ref;
+ int n = 0;
+
+ if (args.verbose) {
+ for (ref = refs; ref; ref = ref->next)
+ if (ref->status == REF_STATUS_UPTODATE)
+ n += print_one_push_status(ref, dest, n);
+ }
+
+ for (ref = refs; ref; ref = ref->next)
+ if (ref->status == REF_STATUS_OK)
+ n += print_one_push_status(ref, dest, n);
+
+ for (ref = refs; ref; ref = ref->next) {
+ if (ref->status != REF_STATUS_NONE &&
+ ref->status != REF_STATUS_UPTODATE &&
+ ref->status != REF_STATUS_OK)
+ n += print_one_push_status(ref, dest, n);
+ }
+}
+
+static int refs_pushed(struct ref *ref)
+{
+ for (; ref; ref = ref->next) {
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_UPTODATE:
+ break;
+ default:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec)
+{
+ struct ref *ref;
+ int new_refs;
+ int ask_for_status_report = 0;
+ int allow_deleting_refs = 0;
+ int expect_status_report = 0;
+ int flags = MATCH_REFS_NONE;
+ int ret;
+
+ if (args.send_all)
+ flags |= MATCH_REFS_ALL;
+ if (args.send_mirror)
+ flags |= MATCH_REFS_MIRROR;
+
+ /* No funny business with the matcher */
+ remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
+ get_local_heads();
+
+ /* Does the other end support the reporting? */
+ if (server_supports("report-status"))
+ ask_for_status_report = 1;
+ if (server_supports("delete-refs"))
+ allow_deleting_refs = 1;
+
+ /* match them up */
+ if (!remote_tail)
+ remote_tail = &remote_refs;
+ if (match_refs(local_refs, remote_refs, &remote_tail,
+ nr_refspec, refspec, flags))
+ return -1;
+
+ if (!remote_refs) {
+ fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+ "Perhaps you should specify a branch such as 'master'.\n");
+ return 0;
+ }
+
+ /*
+ * Finally, tell the other end!
+ */
+ new_refs = 0;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ const unsigned char *new_sha1;
+
+ if (!ref->peer_ref) {
+ if (!args.send_mirror)
+ continue;
+ new_sha1 = null_sha1;
+ }
+ else
+ new_sha1 = ref->peer_ref->new_sha1;
+
+
+ ref->deletion = is_null_sha1(new_sha1);
+ if (ref->deletion && !allow_deleting_refs) {
+ ref->status = REF_STATUS_REJECT_NODELETE;
+ continue;
+ }
+ if (!ref->deletion &&
+ !hashcmp(ref->old_sha1, new_sha1)) {
+ ref->status = REF_STATUS_UPTODATE;
+ continue;
+ }
+
+ /* This part determines what can overwrite what.
+ * The rules are:
+ *
+ * (0) you can always use --force or +A:B notation to
+ * selectively force individual ref pairs.
+ *
+ * (1) if the old thing does not exist, it is OK.
+ *
+ * (2) if you do not have the old thing, you are not allowed
+ * to overwrite it; you would not know what you are losing
+ * otherwise.
+ *
+ * (3) if both new and old are commit-ish, and new is a
+ * descendant of old, it is OK.
+ *
+ * (4) regardless of all of the above, removing :B is
+ * always allowed.
+ */
+
+ ref->nonfastforward =
+ !ref->deletion &&
+ !is_null_sha1(ref->old_sha1) &&
+ (!has_sha1_file(ref->old_sha1)
+ || !ref_newer(new_sha1, ref->old_sha1));
+
+ if (ref->nonfastforward && !ref->force && !args.force_update) {
+ ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+ continue;
+ }
+
+ hashcpy(ref->new_sha1, new_sha1);
+ if (!ref->deletion)
+ new_refs++;
+
+ if (!args.dry_run) {
+ char *old_hex = sha1_to_hex(ref->old_sha1);
+ char *new_hex = sha1_to_hex(ref->new_sha1);
+
+ if (ask_for_status_report) {
+ packet_write(out, "%s %s %s%c%s",
+ old_hex, new_hex, ref->name, 0,
+ "report-status");
+ ask_for_status_report = 0;
+ expect_status_report = 1;
+ }
+ else
+ packet_write(out, "%s %s %s",
+ old_hex, new_hex, ref->name);
+ }
+ ref->status = expect_status_report ?
+ REF_STATUS_EXPECTING_REPORT :
+ REF_STATUS_OK;
+ }
+
+ packet_flush(out);
+ if (new_refs && !args.dry_run) {
+ if (pack_objects(out, remote_refs) < 0) {
+ close(out);
+ return -1;
+ }
+ }
+ close(out);
+
+ if (expect_status_report)
+ ret = receive_status(in, remote_refs);
+ else
+ ret = 0;
+
+ print_push_status(dest, remote_refs);
+
+ if (!args.dry_run && remote) {
+ for (ref = remote_refs; ref; ref = ref->next)
+ update_tracking_ref(remote, ref);
+ }
+
+ if (!refs_pushed(remote_refs))
+ fprintf(stderr, "Everything up-to-date\n");
+ if (ret < 0)
+ return ret;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ switch (ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_UPTODATE:
+ case REF_STATUS_OK:
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void verify_remote_names(int nr_heads, const char **heads)
+{
+ int i;
+
+ for (i = 0; i < nr_heads; i++) {
+ const char *remote = strchr(heads[i], ':');
+
+ remote = remote ? (remote + 1) : heads[i];
+ switch (check_ref_format(remote)) {
+ case 0: /* ok */
+ case CHECK_REF_FORMAT_ONELEVEL:
+ /* ok but a single level -- that is fine for
+ * a match pattern.
+ */
+ case CHECK_REF_FORMAT_WILDCARD:
+ /* ok but ends with a pattern-match character */
+ continue;
+ }
+ die("remote part of refspec is not a valid name in %s",
+ heads[i]);
+ }
+}
+
+int cmd_send_pack(int argc, const char **argv, const char *prefix)
+{
+ int i, nr_heads = 0;
+ const char **heads = NULL;
+ const char *remote_name = NULL;
+ struct remote *remote = NULL;
+ const char *dest = NULL;
+
+ argv++;
+ for (i = 1; i < argc; i++, argv++) {
+ const char *arg = *argv;
+
+ if (*arg == '-') {
+ if (!prefixcmp(arg, "--receive-pack=")) {
+ args.receivepack = arg + 15;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ args.receivepack = arg + 7;
+ continue;
+ }
+ if (!prefixcmp(arg, "--remote=")) {
+ remote_name = arg + 9;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ args.send_all = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--dry-run")) {
+ args.dry_run = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--mirror")) {
+ args.send_mirror = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--force")) {
+ args.force_update = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--verbose")) {
+ args.verbose = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--thin")) {
+ args.use_thin_pack = 1;
+ continue;
+ }
+ usage(send_pack_usage);
+ }
+ if (!dest) {
+ dest = arg;
+ continue;
+ }
+ heads = (const char **) argv;
+ nr_heads = argc - i;
+ break;
+ }
+ if (!dest)
+ usage(send_pack_usage);
+ /*
+ * --all and --mirror are incompatible; neither makes sense
+ * with any refspecs.
+ */
+ if ((heads && (args.send_all || args.send_mirror)) ||
+ (args.send_all && args.send_mirror))
+ usage(send_pack_usage);
+
+ if (remote_name) {
+ remote = remote_get(remote_name);
+ if (!remote_has_url(remote, dest)) {
+ die("Destination %s is not a uri for %s",
+ dest, remote_name);
+ }
+ }
+
+ return send_pack(&args, dest, remote, nr_heads, heads);
+}
+
+int send_pack(struct send_pack_args *my_args,
+ const char *dest, struct remote *remote,
+ int nr_heads, const char **heads)
+{
+ int fd[2], ret;
+ struct child_process *conn;
+
+ memcpy(&args, my_args, sizeof(args));
+
+ verify_remote_names(nr_heads, heads);
+
+ conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
+ ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
+ close(fd[0]);
+ close(fd[1]);
+ ret |= finish_connect(conn);
+ return !!ret;
+}
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
index 3fe754677d..0055a57aeb 100644
--- a/builtin-shortlog.c
+++ b/builtin-shortlog.c
@@ -8,9 +8,10 @@
#include "mailmap.h"
static const char shortlog_usage[] =
-"git-shortlog [-n] [-s] [<commit-id>... ]";
+"git-shortlog [-n] [-s] [-e] [<commit-id>... ]";
static char *common_repo_prefix;
+static int email;
static int compare_by_number(const void *a1, const void *a2)
{
@@ -27,45 +28,68 @@ static int compare_by_number(const void *a1, const void *a2)
static struct path_list mailmap = {NULL, 0, 0, 0};
-static void insert_author_oneline(struct path_list *list,
- const char *author, int authorlen,
- const char *oneline, int onelinelen)
+static void insert_one_record(struct path_list *list,
+ const char *author,
+ const char *oneline)
{
const char *dot3 = common_repo_prefix;
char *buffer, *p;
struct path_list_item *item;
struct path_list *onelines;
+ char namebuf[1024];
+ size_t len;
+ const char *eol;
+ const char *boemail, *eoemail;
+
+ boemail = strchr(author, '<');
+ if (!boemail)
+ return;
+ eoemail = strchr(boemail, '>');
+ if (!eoemail)
+ return;
+ if (!map_email(&mailmap, boemail+1, namebuf, sizeof(namebuf))) {
+ while (author < boemail && isspace(*author))
+ author++;
+ for (len = 0;
+ len < sizeof(namebuf) - 1 && author + len < boemail;
+ len++)
+ namebuf[len] = author[len];
+ while (0 < len && isspace(namebuf[len-1]))
+ len--;
+ namebuf[len] = '\0';
+ }
+ else
+ len = strlen(namebuf);
- while (authorlen > 0 && isspace(author[authorlen - 1]))
- authorlen--;
+ if (email) {
+ size_t room = sizeof(namebuf) - len - 1;
+ int maillen = eoemail - boemail + 1;
+ snprintf(namebuf + len, room, " %.*s", maillen, boemail);
+ }
- buffer = xmemdupz(author, authorlen);
+ buffer = xstrdup(namebuf);
item = path_list_insert(buffer, list);
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct path_list));
else
free(buffer);
+ eol = strchr(oneline, '\n');
+ if (!eol)
+ eol = oneline + strlen(oneline);
+ while (*oneline && isspace(*oneline) && *oneline != '\n')
+ oneline++;
if (!prefixcmp(oneline, "[PATCH")) {
char *eob = strchr(oneline, ']');
-
- if (eob) {
- while (isspace(eob[1]) && eob[1] != '\n')
- eob++;
- if (eob - oneline < onelinelen) {
- onelinelen -= eob - oneline;
- oneline = eob;
- }
- }
+ if (eob && (!eol || eob < eol))
+ oneline = eob + 1;
}
-
- while (onelinelen > 0 && isspace(oneline[0])) {
+ while (*oneline && isspace(*oneline) && *oneline != '\n')
oneline++;
- onelinelen--;
- }
- while (onelinelen > 0 && isspace(oneline[onelinelen - 1]))
- onelinelen--;
- buffer = xmemdupz(oneline, onelinelen);
+ len = eol - oneline;
+ while (len && isspace(oneline[len-1]))
+ len--;
+ buffer = xmemdupz(oneline, len);
if (dot3) {
int dot3len = strlen(dot3);
@@ -92,55 +116,33 @@ static void insert_author_oneline(struct path_list *list,
static void read_from_stdin(struct path_list *list)
{
- char buffer[1024];
-
- while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
- char *bob;
- if ((buffer[0] == 'A' || buffer[0] == 'a') &&
- !prefixcmp(buffer + 1, "uthor: ") &&
- (bob = strchr(buffer + 7, '<')) != NULL) {
- char buffer2[1024], offset = 0;
-
- if (map_email(&mailmap, bob + 1, buffer, sizeof(buffer)))
- bob = buffer + strlen(buffer);
- else {
- offset = 8;
- while (buffer + offset < bob &&
- isspace(bob[-1]))
- bob--;
- }
-
- while (fgets(buffer2, sizeof(buffer2), stdin) &&
- buffer2[0] != '\n')
- ; /* chomp input */
- if (fgets(buffer2, sizeof(buffer2), stdin)) {
- int l2 = strlen(buffer2);
- int i;
- for (i = 0; i < l2; i++)
- if (!isspace(buffer2[i]))
- break;
- insert_author_oneline(list,
- buffer + offset,
- bob - buffer - offset,
- buffer2 + i, l2 - i);
- }
- }
+ char author[1024], oneline[1024];
+
+ while (fgets(author, sizeof(author), stdin) != NULL) {
+ if (!(author[0] == 'A' || author[0] == 'a') ||
+ prefixcmp(author + 1, "uthor: "))
+ continue;
+ while (fgets(oneline, sizeof(oneline), stdin) &&
+ oneline[0] != '\n')
+ ; /* discard headers */
+ while (fgets(oneline, sizeof(oneline), stdin) &&
+ oneline[0] == '\n')
+ ; /* discard blanks */
+ insert_one_record(list, author + 8, oneline);
}
}
static void get_from_rev(struct rev_info *rev, struct path_list *list)
{
- char scratch[1024];
struct commit *commit;
- prepare_revision_walk(rev);
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
while ((commit = get_revision(rev)) != NULL) {
- const char *author = NULL, *oneline, *buffer;
- int authorlen = authorlen, onelinelen;
+ const char *author = NULL, *buffer;
- /* get author and oneline */
- for (buffer = commit->buffer; buffer && *buffer != '\0' &&
- *buffer != '\n'; ) {
+ buffer = commit->buffer;
+ while (*buffer && *buffer != '\n') {
const char *eol = strchr(buffer, '\n');
if (eol == NULL)
@@ -148,50 +150,17 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list)
else
eol++;
- if (!prefixcmp(buffer, "author ")) {
- char *bracket = strchr(buffer, '<');
-
- if (bracket == NULL || bracket > eol)
- die("Invalid commit buffer: %s",
- sha1_to_hex(commit->object.sha1));
-
- if (map_email(&mailmap, bracket + 1, scratch,
- sizeof(scratch))) {
- author = scratch;
- authorlen = strlen(scratch);
- } else {
- if (bracket[-1] == ' ')
- bracket--;
-
- author = buffer + 7;
- authorlen = bracket - buffer - 7;
- }
- }
+ if (!prefixcmp(buffer, "author "))
+ author = buffer + 7;
buffer = eol;
}
-
- if (author == NULL)
- die ("Missing author: %s",
- sha1_to_hex(commit->object.sha1));
-
- if (buffer == NULL || *buffer == '\0') {
- oneline = "<none>";
- onelinelen = sizeof(oneline) + 1;
- } else {
- char *eol;
-
- oneline = buffer + 1;
- eol = strchr(oneline, '\n');
- if (eol == NULL)
- onelinelen = strlen(oneline);
- else
- onelinelen = eol - oneline;
- }
-
- insert_author_oneline(list,
- author, authorlen, oneline, onelinelen);
+ if (!author)
+ die("Missing author: %s",
+ sha1_to_hex(commit->object.sha1));
+ if (*buffer)
+ buffer++;
+ insert_one_record(list, author, !*buffer ? "<none>" : buffer);
}
-
}
static int parse_uint(char const **arg, int comma)
@@ -260,6 +229,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
else if (!strcmp(argv[1], "-s") ||
!strcmp(argv[1], "--summary"))
summary = 1;
+ else if (!strcmp(argv[1], "-e") ||
+ !strcmp(argv[1], "--email"))
+ email = 1;
else if (!prefixcmp(argv[1], "-w")) {
wrap_lines = 1;
parse_wrap_args(argv[1], &in1, &in2, &wrap);
@@ -278,9 +250,10 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
read_mailmap(&mailmap, ".mailmap", &common_repo_prefix);
+ /* assume HEAD if from a tty */
+ if (!rev.pending.nr && isatty(0))
+ add_head_to_pending(&rev);
if (rev.pending.nr == 0) {
- if (isatty(0))
- fprintf(stderr, "(reading log to summarize from standard input)\n");
read_from_stdin(&list);
}
else
@@ -294,7 +267,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
struct path_list *onelines = list.items[i].util;
if (summary) {
- printf("%s: %d\n", list.items[i].path, onelines->nr);
+ printf("%6d\t%s\n", onelines->nr, list.items[i].path);
} else {
printf("%s (%d):\n", list.items[i].path, onelines->nr);
for (j = onelines->nr - 1; j >= 0; j--) {
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
index 07a0c2316b..019abd3527 100644
--- a/builtin-show-branch.c
+++ b/builtin-show-branch.c
@@ -266,7 +266,7 @@ static void show_one_commit(struct commit *commit, int no_name)
strbuf_init(&pretty, 0);
if (commit->object.parsed) {
pretty_print_commit(CMIT_FMT_ONELINE, commit,
- &pretty, 0, NULL, NULL, 0);
+ &pretty, 0, NULL, NULL, 0, 0);
pretty_str = pretty.buf;
}
if (!prefixcmp(pretty_str, "[PATCH] "))
@@ -536,6 +536,8 @@ static void append_one_rev(const char *av)
static int git_show_branch_config(const char *var, const char *value)
{
if (!strcmp(var, "showbranch.default")) {
+ if (!value)
+ return config_error_nonbool(var);
if (default_alloc <= default_num + 1) {
default_alloc = default_alloc * 3 / 2 + 20;
default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
index 65051d14fd..a323633e29 100644
--- a/builtin-show-ref.c
+++ b/builtin-show-ref.c
@@ -86,6 +86,9 @@ match:
sha1_to_hex(sha1));
if (obj->type == OBJ_TAG) {
obj = deref_tag(obj, refname, 0);
+ if (!obj)
+ die("git-show-ref: bad tag at ref %s (%s)", refname,
+ sha1_to_hex(sha1));
hex = find_unique_abbrev(obj->sha1, abbrev);
printf("%s %s^{}\n", hex, refname);
}
diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c
index 9eb95e50da..d33982b967 100644
--- a/builtin-symbolic-ref.c
+++ b/builtin-symbolic-ref.c
@@ -1,9 +1,12 @@
#include "builtin.h"
#include "cache.h"
#include "refs.h"
+#include "parse-options.h"
-static const char git_symbolic_ref_usage[] =
-"git-symbolic-ref [-q] [-m <reason>] name [ref]";
+static const char * const git_symbolic_ref_usage[] = {
+ "git-symbolic-ref [options] name [ref]",
+ NULL
+};
static void check_symref(const char *HEAD, int quiet)
{
@@ -26,44 +29,25 @@ 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_STRING('m', NULL, &msg, "reason", "reason of the update"),
+ OPT_END(),
+ };
git_config(git_default_config);
-
- while (1 < argc) {
- const char *arg = argv[1];
- if (arg[0] != '-')
- break;
- else if (!strcmp("-q", arg))
- quiet = 1;
- else if (!strcmp("-m", arg)) {
- argc--;
- argv++;
- if (argc <= 1)
- break;
- msg = argv[1];
- if (!*msg)
- die("Refusing to perform update with empty message");
- }
- else if (!strcmp("--", arg)) {
- argc--;
- argv++;
- break;
- }
- else
- die("unknown option %s", arg);
- argc--;
- argv++;
- }
-
+ argc = parse_options(argc, argv, options, git_symbolic_ref_usage, 0);
+ if (msg &&!*msg)
+ die("Refusing to perform update with empty message");
switch (argc) {
- case 2:
- check_symref(argv[1], quiet);
+ case 1:
+ check_symref(argv[0], quiet);
break;
- case 3:
- create_symref(argv[1], argv[2], msg);
+ case 2:
+ create_symref(argv[0], argv[1], msg);
break;
default:
- usage(git_symbolic_ref_usage);
+ usage_with_options(git_symbolic_ref_usage, options);
}
return 0;
}
diff --git a/builtin-tag.c b/builtin-tag.c
index 66e5a58307..716b4fff32 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -11,17 +11,21 @@
#include "refs.h"
#include "tag.h"
#include "run-command.h"
-
-static const char builtin_tag_usage[] =
- "git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg> | -F <file>] <tagname> [<head>]";
+#include "parse-options.h"
+
+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 -v <tagname>...",
+ NULL
+};
static char signingkey[1000];
-static void launch_editor(const char *path, struct strbuf *buffer)
+void launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
{
const char *editor, *terminal;
- struct child_process child;
- const char *args[3];
editor = getenv("GIT_EDITOR");
if (!editor && editor_program)
@@ -42,15 +46,27 @@ static void launch_editor(const char *path, struct strbuf *buffer)
if (!editor)
editor = "vi";
- memset(&child, 0, sizeof(child));
- child.argv = args;
- args[0] = editor;
- args[1] = path;
- args[2] = NULL;
+ if (strcmp(editor, ":")) {
+ size_t len = strlen(editor);
+ int i = 0;
+ const char *args[6];
+
+ if (strcspn(editor, "$ \t'") != len) {
+ /* there are specials */
+ args[i++] = "sh";
+ args[i++] = "-c";
+ args[i++] = "$0 \"$@\"";
+ }
+ args[i++] = editor;
+ args[i++] = path;
+ args[i] = NULL;
- if (run_command(&child))
- die("There was a problem with the editor %s.", editor);
+ if (run_command_v_opt_cd_env(args, 0, NULL, env))
+ die("There was a problem with the editor %s.", editor);
+ }
+ if (!buffer)
+ return;
if (strbuf_read_file(buffer, path, 0) < 0)
die("could not read message file '%s': %s",
path, strerror(errno));
@@ -81,17 +97,16 @@ static int show_reference(const char *refname, const unsigned char *sha1,
}
printf("%-15s ", refname);
- sp = buf = read_sha1_file(sha1, &type, &size);
- if (!buf)
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf || !size)
return 0;
- if (!size) {
+
+ /* skip header */
+ sp = strstr(buf, "\n\n");
+ if (!sp) {
free(buf);
return 0;
}
- /* skip header */
- while (sp + 1 < buf + size &&
- !(sp[0] == '\n' && sp[1] == '\n'))
- sp++;
/* only take up to "lines" lines, and strip the signature */
for (i = 0, sp += 2;
i < filter->lines && sp < buf + size &&
@@ -185,7 +200,7 @@ static int do_sign(struct strbuf *buffer)
int len;
if (!*signingkey) {
- if (strlcpy(signingkey, git_committer_info(1),
+ if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
sizeof(signingkey)) > sizeof(signingkey) - 1)
return error("committer info too long.");
bracket = strchr(signingkey, '>');
@@ -221,9 +236,6 @@ static int do_sign(struct strbuf *buffer)
if (finish_command(&gpg) || !len || len < 0)
return error("gpg failed to sign the tag");
- if (len < 0)
- return error("could not read the entire signature from gpg.");
-
return 0;
}
@@ -233,23 +245,55 @@ static const char tag_template[] =
"# Write a tag message\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);
+}
+
static int git_tag_config(const char *var, const char *value)
{
if (!strcmp(var, "user.signingkey")) {
if (!value)
- die("user.signingkey without value");
- if (strlcpy(signingkey, value, sizeof(signingkey))
- >= sizeof(signingkey))
- die("user.signingkey value too long");
+ return config_error_nonbool(value);
+ set_signingkey(value);
return 0;
}
return git_default_config(var, value);
}
+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;
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ return;
+ /* skip header */
+ sp = strstr(buf, "\n\n");
+
+ if (!sp || !size || type != OBJ_TAG) {
+ free(buf);
+ 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);
+
+ free(buf);
+}
+
static void create_tag(const unsigned char *object, const char *tag,
struct strbuf *buf, int message, int sign,
- unsigned char *result)
+ unsigned char *prev, unsigned char *result)
{
enum object_type type;
char header_buf[1024];
@@ -267,7 +311,7 @@ static void create_tag(const unsigned char *object, const char *tag,
sha1_to_hex(object),
typename(type),
tag,
- git_committer_info(1));
+ git_committer_info(IDENT_ERROR_ON_NO_NAME));
if (header_len > sizeof(header_buf) - 1)
die("tag header too big.");
@@ -282,10 +326,14 @@ static void create_tag(const unsigned char *object, const char *tag,
if (fd < 0)
die("could not create file '%s': %s",
path, strerror(errno));
- write_or_die(fd, tag_template, strlen(tag_template));
+
+ if (!is_null_sha1(prev))
+ write_tag_body(fd, prev);
+ else
+ write_or_die(fd, tag_template, strlen(tag_template));
close(fd);
- launch_editor(path, buf);
+ launch_editor(path, buf, NULL);
unlink(path);
free(path);
@@ -304,105 +352,103 @@ static void create_tag(const unsigned char *object, const char *tag,
die("unable to write tag file");
}
+struct msg_arg {
+ int given;
+ struct strbuf buf;
+};
+
+static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
+{
+ struct msg_arg *msg = opt->value;
+
+ if (!arg)
+ return -1;
+ if (msg->buf.len)
+ strbuf_addstr(&(msg->buf), "\n\n");
+ strbuf_addstr(&(msg->buf), arg);
+ msg->given = 1;
+ return 0;
+}
+
int cmd_tag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf;
unsigned char object[20], prev[20];
- int annotate = 0, sign = 0, force = 0, lines = 0, message = 0;
char ref[PATH_MAX];
const char *object_ref, *tag;
- int i;
struct ref_lock *lock;
+ int annotate = 0, sign = 0, force = 0, lines = 0,
+ list = 0, delete = 0, verify = 0;
+ char *msgfile = NULL, *keyid = NULL;
+ struct msg_arg msg = { 0, STRBUF_INIT };
+ struct option options[] = {
+ OPT_BOOLEAN('l', NULL, &list, "list tag names"),
+ { OPTION_INTEGER, 'n', NULL, &lines, NULL,
+ "print n lines of each tag message",
+ PARSE_OPT_OPTARG, NULL, 1 },
+ OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
+ OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
+
+ 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_STRING('F', NULL, &msgfile, "file", "message in a 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', NULL, &force, "replace the tag if exists"),
+ OPT_END()
+ };
+
git_config(git_tag_config);
- strbuf_init(&buf, 0);
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
+ argc = parse_options(argc, argv, options, git_tag_usage, 0);
- if (arg[0] != '-')
- break;
- if (!strcmp(arg, "-a")) {
- annotate = 1;
- continue;
- }
- if (!strcmp(arg, "-s")) {
- annotate = 1;
- sign = 1;
- continue;
- }
- if (!strcmp(arg, "-f")) {
- force = 1;
- continue;
- }
- if (!strcmp(arg, "-n")) {
- if (i + 1 == argc || *argv[i + 1] == '-')
- /* no argument */
- lines = 1;
- else
- lines = isdigit(*argv[++i]) ?
- atoi(argv[i]) : 1;
- continue;
- }
- if (!strcmp(arg, "-m")) {
- annotate = 1;
- i++;
- if (i == argc)
- die("option -m needs an argument.");
- if (message)
- die("only one -F or -m option is allowed.");
- strbuf_addstr(&buf, argv[i]);
- message = 1;
- continue;
- }
- if (!strcmp(arg, "-F")) {
- annotate = 1;
- i++;
- if (i == argc)
- die("option -F needs an argument.");
- if (message)
- die("only one -F or -m option is allowed.");
-
- if (!strcmp(argv[i], "-")) {
+ if (keyid) {
+ sign = 1;
+ set_signingkey(keyid);
+ }
+ if (sign)
+ annotate = 1;
+
+ if (list)
+ return list_tags(argv[0], lines);
+ if (delete)
+ return for_each_tag_name(argv, delete_tag);
+ if (verify)
+ return for_each_tag_name(argv, verify_tag);
+
+ strbuf_init(&buf, 0);
+ if (msg.given || msgfile) {
+ if (msg.given && msgfile)
+ 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("cannot read %s", argv[i]);
+ die("cannot read %s", msgfile);
} else {
- if (strbuf_read_file(&buf, argv[i], 1024) < 0)
+ if (strbuf_read_file(&buf, msgfile, 1024) < 0)
die("could not open or read '%s': %s",
- argv[i], strerror(errno));
+ msgfile, strerror(errno));
}
- message = 1;
- continue;
- }
- if (!strcmp(arg, "-u")) {
- annotate = 1;
- sign = 1;
- i++;
- if (i == argc)
- die("option -u needs an argument.");
- if (strlcpy(signingkey, argv[i], sizeof(signingkey))
- >= sizeof(signingkey))
- die("argument to option -u too long");
- continue;
}
- if (!strcmp(arg, "-l"))
- return list_tags(argv[i + 1], lines);
- if (!strcmp(arg, "-d"))
- return for_each_tag_name(argv + i + 1, delete_tag);
- if (!strcmp(arg, "-v"))
- return for_each_tag_name(argv + i + 1, verify_tag);
- usage(builtin_tag_usage);
}
- if (i == argc) {
+ if (argc == 0) {
if (annotate)
- usage(builtin_tag_usage);
+ usage_with_options(git_tag_usage, options);
return list_tags(NULL, lines);
}
- tag = argv[i++];
+ tag = argv[0];
- object_ref = i < argc ? argv[i] : "HEAD";
- if (i + 1 < argc)
+ object_ref = argc == 2 ? argv[1] : "HEAD";
+ if (argc > 2)
die("too many params");
if (get_sha1(object_ref, object))
@@ -419,7 +465,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
die("tag '%s' already exists", tag);
if (annotate)
- create_tag(object, tag, &buf, message, sign, object);
+ create_tag(object, tag, &buf, msg.given || msgfile,
+ sign, prev, object);
lock = lock_any_ref_for_update(ref, prev, 0);
if (!lock)
diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c
index a6ff62fd8c..1e51865c52 100644
--- a/builtin-unpack-objects.c
+++ b/builtin-unpack-objects.c
@@ -311,7 +311,7 @@ static void unpack_one(unsigned nr)
static void unpack_all(void)
{
int i;
- struct progress progress;
+ struct progress *progress = NULL;
struct pack_header *hdr = fill(sizeof(struct pack_header));
unsigned nr_objects = ntohl(hdr->hdr_entries);
@@ -322,15 +322,13 @@ static void unpack_all(void)
use(sizeof(struct pack_header));
if (!quiet)
- start_progress(&progress, "Unpacking %u objects...", "", nr_objects);
+ progress = start_progress("Unpacking objects", nr_objects);
obj_list = xmalloc(nr_objects * sizeof(*obj_list));
for (i = 0; i < nr_objects; i++) {
unpack_one(i);
- if (!quiet)
- display_progress(&progress, i + 1);
+ display_progress(progress, i + 1);
}
- if (!quiet)
- stop_progress(&progress);
+ stop_progress(&progress);
if (delta_list)
die("unresolved deltas left after unpacking");
diff --git a/builtin-update-index.c b/builtin-update-index.c
index e1a938d897..a8795d3d5f 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -47,10 +47,10 @@ static int mark_valid(const char *path)
if (0 <= pos) {
switch (mark_valid_only) {
case MARK_VALID:
- active_cache[pos]->ce_flags |= htons(CE_VALID);
+ active_cache[pos]->ce_flags |= CE_VALID;
break;
case UNMARK_VALID:
- active_cache[pos]->ce_flags &= ~htons(CE_VALID);
+ active_cache[pos]->ce_flags &= ~CE_VALID;
break;
}
cache_tree_invalidate_path(active_cache_tree, path);
@@ -95,7 +95,7 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru
size = cache_entry_size(len);
ce = xcalloc(1, size);
memcpy(ce->name, path, len);
- ce->ce_flags = htons(len);
+ ce->ce_flags = len;
fill_stat_cache_info(ce, st);
ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
@@ -139,7 +139,7 @@ static int process_directory(const char *path, int len, struct stat *st)
/* Exact match: file or existing gitlink */
if (pos >= 0) {
struct cache_entry *ce = active_cache[pos];
- if (S_ISGITLINK(ntohl(ce->ce_mode))) {
+ if (S_ISGITLINK(ce->ce_mode)) {
/* Do nothing to the index if there is no HEAD! */
if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
@@ -183,7 +183,7 @@ static int process_file(const char *path, int len, struct stat *st)
int pos = cache_name_pos(path, len);
struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
- if (ce && S_ISGITLINK(ntohl(ce->ce_mode)))
+ if (ce && S_ISGITLINK(ce->ce_mode))
return error("%s is already a gitlink, not replacing", path);
return add_one_path(ce, path, len, st);
@@ -226,7 +226,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
ce->ce_flags = create_ce_flags(len, stage);
ce->ce_mode = create_ce_mode(mode);
if (assume_unchanged)
- ce->ce_flags |= htons(CE_VALID);
+ ce->ce_flags |= CE_VALID;
option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
if (add_cache_entry(ce, option))
@@ -246,14 +246,14 @@ static void chmod_path(int flip, const char *path)
if (pos < 0)
goto fail;
ce = active_cache[pos];
- mode = ntohl(ce->ce_mode);
+ mode = ce->ce_mode;
if (!S_ISREG(mode))
goto fail;
switch (flip) {
case '+':
- ce->ce_mode |= htonl(0111); break;
+ ce->ce_mode |= 0111; break;
case '-':
- ce->ce_mode &= htonl(~0111); break;
+ ce->ce_mode &= ~0111; break;
default:
goto fail;
}
@@ -738,7 +738,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
get_index_file(), strerror(lock_error));
}
if (write_cache(newfd, active_cache, active_nr) ||
- close(newfd) || commit_locked_index(lock_file))
+ commit_locked_index(lock_file))
die("Unable to write new index file");
}
diff --git a/builtin-update-ref.c b/builtin-update-ref.c
index fe1f74c9f3..e90737c350 100644
--- a/builtin-update-ref.c
+++ b/builtin-update-ref.c
@@ -1,59 +1,44 @@
#include "cache.h"
#include "refs.h"
#include "builtin.h"
+#include "parse-options.h"
-static const char git_update_ref_usage[] =
-"git-update-ref [-m <reason>] (-d <refname> <value> | [--no-deref] <refname> <value> [<oldval>])";
+static const char * const git_update_ref_usage[] = {
+ "git-update-ref [options] -d <refname> <oldval>",
+ "git-update-ref [options] <refname> <newval> [<oldval>]",
+ NULL
+};
int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
- const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
+ const char *refname, *value, *oldval, *msg=NULL;
unsigned char sha1[20], oldsha1[20];
- int i, delete, ref_flags;
+ int delete = 0, no_deref = 0;
+ struct option options[] = {
+ OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"),
+ OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"),
+ OPT_BOOLEAN( 0 , "no-deref", &no_deref,
+ "update <refname> not the one it points to"),
+ OPT_END(),
+ };
- delete = 0;
- ref_flags = 0;
git_config(git_default_config);
+ argc = parse_options(argc, argv, options, git_update_ref_usage, 0);
+ if (msg && !*msg)
+ die("Refusing to perform update with empty message.");
- for (i = 1; i < argc; i++) {
- if (!strcmp("-m", argv[i])) {
- if (i+1 >= argc)
- usage(git_update_ref_usage);
- msg = argv[++i];
- if (!*msg)
- die("Refusing to perform update with empty message.");
- continue;
- }
- if (!strcmp("-d", argv[i])) {
- delete = 1;
- continue;
- }
- if (!strcmp("--no-deref", argv[i])) {
- ref_flags |= REF_NODEREF;
- continue;
- }
- if (!refname) {
- refname = argv[i];
- continue;
- }
- if (!value) {
- value = argv[i];
- continue;
- }
- if (!oldval) {
- oldval = argv[i];
- continue;
- }
- }
- if (!refname || !value)
- usage(git_update_ref_usage);
+ if (argc < 2 || argc > 3)
+ usage_with_options(git_update_ref_usage, options);
+ refname = argv[0];
+ value = argv[1];
+ oldval = argv[2];
if (get_sha1(value, sha1))
die("%s: not a valid SHA1", value);
if (delete) {
if (oldval)
- usage(git_update_ref_usage);
+ usage_with_options(git_update_ref_usage, options);
return delete_ref(refname, sha1);
}
@@ -62,5 +47,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("%s: not a valid old SHA1", oldval);
return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
- ref_flags, DIE_ON_ERR);
+ no_deref ? REF_NODEREF : 0, DIE_ON_ERR);
}
diff --git a/builtin-write-tree.c b/builtin-write-tree.c
index b89d02efec..e838d01233 100644
--- a/builtin-write-tree.c
+++ b/builtin-write-tree.c
@@ -11,66 +11,12 @@
static const char write_tree_usage[] =
"git-write-tree [--missing-ok] [--prefix=<prefix>/]";
-int write_tree(unsigned char *sha1, int missing_ok, const char *prefix)
-{
- int entries, was_valid, newfd;
-
- /* We can't free this memory, it becomes part of a linked list parsed atexit() */
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-
- newfd = hold_locked_index(lock_file, 1);
-
- entries = read_cache();
- if (entries < 0)
- die("git-write-tree: error reading cache");
-
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
-
- was_valid = cache_tree_fully_valid(active_cache_tree);
-
- if (!was_valid) {
- if (cache_tree_update(active_cache_tree,
- active_cache, active_nr,
- missing_ok, 0) < 0)
- die("git-write-tree: error building trees");
- if (0 <= newfd) {
- if (!write_cache(newfd, active_cache, active_nr)
- && !close(newfd)) {
- commit_lock_file(lock_file);
- newfd = -1;
- }
- }
- /* Not being able to write is fine -- we are only interested
- * in updating the cache-tree part, and if the next caller
- * ends up using the old index with unupdated cache-tree part
- * it misses the work we did here, but that is just a
- * performance penalty and not a big deal.
- */
- }
-
- if (prefix) {
- struct cache_tree *subtree =
- cache_tree_find(active_cache_tree, prefix);
- if (!subtree)
- die("git-write-tree: prefix %s not found", prefix);
- hashcpy(sha1, subtree->sha1);
- }
- else
- hashcpy(sha1, active_cache_tree->sha1);
-
- if (0 <= newfd)
- close(newfd);
- rollback_lock_file(lock_file);
-
- return 0;
-}
-
int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
{
int missing_ok = 0, ret;
const char *prefix = NULL;
unsigned char sha1[20];
+ const char *me = "git-write-tree";
git_config(git_default_config);
while (1 < argc) {
@@ -87,8 +33,20 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
if (argc > 2)
die("too many options");
- ret = write_tree(sha1, missing_ok, prefix);
- printf("%s\n", sha1_to_hex(sha1));
-
+ ret = write_cache_as_tree(sha1, missing_ok, prefix);
+ switch (ret) {
+ case 0:
+ printf("%s\n", sha1_to_hex(sha1));
+ break;
+ case WRITE_TREE_UNREADABLE_INDEX:
+ die("%s: error reading the index", me);
+ break;
+ case WRITE_TREE_UNMERGED_INDEX:
+ die("%s: error building trees; the index is unmerged?", me);
+ break;
+ case WRITE_TREE_PREFIX_ERROR:
+ die("%s: prefix %s not found", me, prefix);
+ break;
+ }
return ret;
}
diff --git a/builtin.h b/builtin.h
index d6f2c76b86..3d1628c597 100644
--- a/builtin.h
+++ b/builtin.h
@@ -6,8 +6,8 @@
extern const char git_version_string[];
extern const char git_usage_string[];
+extern void list_common_cmds_help(void);
extern void help_unknown_cmd(const char *cmd);
-extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
extern void prune_packed_objects(int);
extern int cmd_add(int argc, const char **argv, const char *prefix);
@@ -23,6 +23,8 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
extern int cmd_cherry(int argc, const char **argv, const char *prefix);
extern int cmd_cherry_pick(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_count_objects(int argc, const char **argv, const char *prefix);
extern int cmd_describe(int argc, const char **argv, const char *prefix);
@@ -30,6 +32,9 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
extern int cmd_diff(int argc, const char **argv, const char *prefix);
extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
+extern int cmd_fetch(int argc, const char **argv, const char *prefix);
+extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix);
extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
@@ -39,14 +44,17 @@ extern int cmd_gc(int argc, const char **argv, const char *prefix);
extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
extern int cmd_grep(int argc, const char **argv, const char *prefix);
extern int cmd_help(int argc, const char **argv, const char *prefix);
+extern int cmd_http_fetch(int argc, const char **argv, const char *prefix);
extern int cmd_init_db(int argc, const char **argv, const char *prefix);
extern int cmd_log(int argc, const char **argv, const char *prefix);
extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
extern int cmd_ls_files(int argc, const char **argv, const char *prefix);
extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
extern int cmd_mv(int argc, const char **argv, const char *prefix);
extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
@@ -64,10 +72,11 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_revert(int argc, const char **argv, const char *prefix);
extern int cmd_rm(int argc, const char **argv, const char *prefix);
-extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
+extern int cmd_send_pack(int argc, const char **argv, const char *prefix);
extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
extern int cmd_show(int argc, const char **argv, const char *prefix);
extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
+extern int cmd_status(int argc, const char **argv, const char *prefix);
extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
extern int cmd_tag(int argc, const char **argv, const char *prefix);
diff --git a/bundle.c b/bundle.c
new file mode 100644
index 0000000000..bd12ec8537
--- /dev/null
+++ b/bundle.c
@@ -0,0 +1,358 @@
+#include "cache.h"
+#include "bundle.h"
+#include "object.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "run-command.h"
+#include "refs.h"
+
+static const char bundle_signature[] = "# v2 git bundle\n";
+
+static void add_to_ref_list(const unsigned char *sha1, const char *name,
+ struct ref_list *list)
+{
+ if (list->nr + 1 >= list->alloc) {
+ list->alloc = alloc_nr(list->nr + 1);
+ list->list = xrealloc(list->list,
+ list->alloc * sizeof(list->list[0]));
+ }
+ memcpy(list->list[list->nr].sha1, sha1, 20);
+ list->list[list->nr].name = xstrdup(name);
+ list->nr++;
+}
+
+/* returns an fd */
+int read_bundle_header(const char *path, struct bundle_header *header)
+{
+ char buffer[1024];
+ int fd;
+ long fpos;
+ FILE *ffd = fopen(path, "rb");
+
+ if (!ffd)
+ return error("could not open '%s'", path);
+ if (!fgets(buffer, sizeof(buffer), ffd) ||
+ strcmp(buffer, bundle_signature)) {
+ fclose(ffd);
+ return error("'%s' does not look like a v2 bundle file", path);
+ }
+ while (fgets(buffer, sizeof(buffer), ffd)
+ && buffer[0] != '\n') {
+ int is_prereq = buffer[0] == '-';
+ int offset = is_prereq ? 1 : 0;
+ int len = strlen(buffer);
+ unsigned char sha1[20];
+ struct ref_list *list = is_prereq ? &header->prerequisites
+ : &header->references;
+ char delim;
+
+ if (len && buffer[len - 1] == '\n')
+ buffer[len - 1] = '\0';
+ if (get_sha1_hex(buffer + offset, sha1)) {
+ warning("unrecognized header: %s", buffer);
+ continue;
+ }
+ delim = buffer[40 + offset];
+ if (!isspace(delim) && (delim != '\0' || !is_prereq))
+ die ("invalid header: %s", buffer);
+ add_to_ref_list(sha1, isspace(delim) ?
+ buffer + 41 + offset : "", list);
+ }
+ fpos = ftell(ffd);
+ fclose(ffd);
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return error("could not open '%s'", path);
+ lseek(fd, fpos, SEEK_SET);
+ return fd;
+}
+
+static int list_refs(struct ref_list *r, int argc, const char **argv)
+{
+ int i;
+
+ for (i = 0; i < r->nr; i++) {
+ if (argc > 1) {
+ int j;
+ for (j = 1; j < argc; j++)
+ if (!strcmp(r->list[i].name, argv[j]))
+ break;
+ if (j == argc)
+ continue;
+ }
+ printf("%s %s\n", sha1_to_hex(r->list[i].sha1),
+ r->list[i].name);
+ }
+ return 0;
+}
+
+#define PREREQ_MARK (1u<<16)
+
+int verify_bundle(struct bundle_header *header, int verbose)
+{
+ /*
+ * Do fast check, then if any prereqs are missing then go line by line
+ * to be verbose about the errors
+ */
+ struct ref_list *p = &header->prerequisites;
+ struct rev_info revs;
+ const char *argv[] = {NULL, "--all"};
+ struct object_array refs;
+ struct commit *commit;
+ int i, ret = 0, req_nr;
+ const char *message = "Repository lacks these prerequisite commits:";
+
+ init_revisions(&revs, NULL);
+ for (i = 0; i < p->nr; i++) {
+ struct ref_list_entry *e = p->list + i;
+ struct object *o = parse_object(e->sha1);
+ if (o) {
+ o->flags |= PREREQ_MARK;
+ add_pending_object(&revs, o, e->name);
+ continue;
+ }
+ if (++ret == 1)
+ error(message);
+ error("%s %s", sha1_to_hex(e->sha1), e->name);
+ }
+ if (revs.pending.nr != p->nr)
+ return ret;
+ req_nr = revs.pending.nr;
+ setup_revisions(2, argv, &revs, NULL);
+
+ memset(&refs, 0, sizeof(struct object_array));
+ for (i = 0; i < revs.pending.nr; i++) {
+ struct object_array_entry *e = revs.pending.objects + i;
+ add_object_array(e->item, e->name, &refs);
+ }
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+
+ i = req_nr;
+ while (i && (commit = get_revision(&revs)))
+ if (commit->object.flags & PREREQ_MARK)
+ i--;
+
+ for (i = 0; i < req_nr; i++)
+ if (!(refs.objects[i].item->flags & SHOWN)) {
+ if (++ret == 1)
+ error(message);
+ error("%s %s", sha1_to_hex(refs.objects[i].item->sha1),
+ refs.objects[i].name);
+ }
+
+ for (i = 0; i < refs.nr; i++)
+ clear_commit_marks((struct commit *)refs.objects[i].item, -1);
+
+ if (verbose) {
+ struct ref_list *r;
+
+ r = &header->references;
+ printf("The bundle contains %d ref%s\n",
+ r->nr, (1 < r->nr) ? "s" : "");
+ list_refs(r, 0, NULL);
+ r = &header->prerequisites;
+ printf("The bundle requires these %d ref%s\n",
+ r->nr, (1 < r->nr) ? "s" : "");
+ list_refs(r, 0, NULL);
+ }
+ return ret;
+}
+
+int list_bundle_refs(struct bundle_header *header, int argc, const char **argv)
+{
+ return list_refs(&header->references, argc, argv);
+}
+
+int create_bundle(struct bundle_header *header, const char *path,
+ int argc, const char **argv)
+{
+ static struct lock_file lock;
+ 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 *));
+ int i, ref_count = 0;
+ char buffer[1024];
+ struct rev_info revs;
+ struct child_process rls;
+ FILE *rls_fout;
+
+ bundle_to_stdout = !strcmp(path, "-");
+ if (bundle_to_stdout)
+ bundle_fd = 1;
+ else
+ bundle_fd = hold_lock_file_for_update(&lock, path, 1);
+
+ /* write signature */
+ write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature));
+
+ /* init revs to list objects for pack-objects later */
+ save_commit_buffer = 0;
+ init_revisions(&revs, NULL);
+
+ /* write prerequisites */
+ memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *));
+ argv_boundary[0] = "rev-list";
+ argv_boundary[1] = "--boundary";
+ argv_boundary[2] = "--pretty=oneline";
+ argv_boundary[argc + 2] = NULL;
+ memset(&rls, 0, sizeof(rls));
+ rls.argv = argv_boundary;
+ rls.out = -1;
+ rls.git_cmd = 1;
+ if (start_command(&rls))
+ return -1;
+ rls_fout = fdopen(rls.out, "r");
+ while (fgets(buffer, sizeof(buffer), rls_fout)) {
+ unsigned char sha1[20];
+ if (buffer[0] == '-') {
+ write_or_die(bundle_fd, buffer, strlen(buffer));
+ if (!get_sha1_hex(buffer + 1, sha1)) {
+ struct object *object = parse_object(sha1);
+ object->flags |= UNINTERESTING;
+ add_pending_object(&revs, object, buffer);
+ }
+ } else if (!get_sha1_hex(buffer, sha1)) {
+ struct object *object = parse_object(sha1);
+ object->flags |= SHOWN;
+ }
+ }
+ fclose(rls_fout);
+ if (finish_command(&rls))
+ return error("rev-list died");
+
+ /* write references */
+ argc = setup_revisions(argc, argv, &revs, NULL);
+ if (argc > 1)
+ return error("unrecognized argument: %s'", argv[1]);
+
+ for (i = 0; i < revs.pending.nr; i++) {
+ struct object_array_entry *e = revs.pending.objects + i;
+ unsigned char sha1[20];
+ char *ref;
+ const char *display_ref;
+ int flag;
+
+ if (e->item->flags & UNINTERESTING)
+ continue;
+ if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
+ continue;
+ if (!resolve_ref(e->name, sha1, 1, &flag))
+ flag = 0;
+ display_ref = (flag & REF_ISSYMREF) ? e->name : ref;
+
+ /*
+ * Make sure the refs we wrote out is correct; --max-count and
+ * other limiting options could have prevented all the tips
+ * from getting output.
+ *
+ * Non commit objects such as tags and blobs do not have
+ * this issue as they are not affected by those extra
+ * constraints.
+ */
+ if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) {
+ warning("ref '%s' is excluded by the rev-list options",
+ e->name);
+ free(ref);
+ continue;
+ }
+ /*
+ * If you run "git bundle create bndl v1.0..v2.0", the
+ * name of the positive ref is "v2.0" but that is the
+ * commit that is referenced by the tag, and not the tag
+ * itself.
+ */
+ if (hashcmp(sha1, e->item->sha1)) {
+ /*
+ * Is this the positive end of a range expressed
+ * in terms of a tag (e.g. v2.0 from the range
+ * "v1.0..v2.0")?
+ */
+ struct commit *one = lookup_commit_reference(sha1);
+ struct object *obj;
+
+ if (e->item == &(one->object)) {
+ /*
+ * Need to include e->name as an
+ * independent ref to the pack-objects
+ * input, so that the tag is included
+ * in the output; otherwise we would
+ * end up triggering "empty bundle"
+ * error.
+ */
+ obj = parse_object(sha1);
+ obj->flags |= SHOWN;
+ add_pending_object(&revs, obj, e->name);
+ }
+ free(ref);
+ continue;
+ }
+
+ ref_count++;
+ write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40);
+ write_or_die(bundle_fd, " ", 1);
+ write_or_die(bundle_fd, display_ref, strlen(display_ref));
+ write_or_die(bundle_fd, "\n", 1);
+ free(ref);
+ }
+ if (!ref_count)
+ die ("Refusing to create empty bundle.");
+
+ /* end header */
+ write_or_die(bundle_fd, "\n", 1);
+
+ /* write pack */
+ argv_pack[0] = "pack-objects";
+ argv_pack[1] = "--all-progress";
+ argv_pack[2] = "--stdout";
+ argv_pack[3] = "--thin";
+ argv_pack[4] = NULL;
+ memset(&rls, 0, sizeof(rls));
+ rls.argv = argv_pack;
+ rls.in = -1;
+ rls.out = bundle_fd;
+ rls.git_cmd = 1;
+ if (start_command(&rls))
+ return error("Could not spawn pack-objects");
+
+ /*
+ * start_command closed bundle_fd if it was > 1
+ * so set the lock fd to -1 so commit_lock_file()
+ * won't fail trying to close it.
+ */
+ lock.fd = -1;
+
+ for (i = 0; i < revs.pending.nr; i++) {
+ struct object *object = revs.pending.objects[i].item;
+ if (object->flags & UNINTERESTING)
+ write_or_die(rls.in, "^", 1);
+ write_or_die(rls.in, sha1_to_hex(object->sha1), 40);
+ write_or_die(rls.in, "\n", 1);
+ }
+ if (finish_command(&rls))
+ return error ("pack-objects died");
+
+ return bundle_to_stdout ? close(bundle_fd) : commit_lock_file(&lock);
+}
+
+int unbundle(struct bundle_header *header, int bundle_fd)
+{
+ const char *argv_index_pack[] = {"index-pack",
+ "--fix-thin", "--stdin", NULL};
+ struct child_process ip;
+
+ if (verify_bundle(header, 0))
+ return -1;
+ memset(&ip, 0, sizeof(ip));
+ ip.argv = argv_index_pack;
+ ip.in = bundle_fd;
+ ip.no_stdout = 1;
+ ip.git_cmd = 1;
+ if (run_command(&ip))
+ return error("index-pack died");
+ return 0;
+}
diff --git a/bundle.h b/bundle.h
new file mode 100644
index 0000000000..e2aedd60d6
--- /dev/null
+++ b/bundle.h
@@ -0,0 +1,25 @@
+#ifndef BUNDLE_H
+#define BUNDLE_H
+
+struct ref_list {
+ unsigned int nr, alloc;
+ struct ref_list_entry {
+ unsigned char sha1[20];
+ char *name;
+ } *list;
+};
+
+struct bundle_header {
+ struct ref_list prerequisites;
+ struct ref_list references;
+};
+
+int read_bundle_header(const char *path, struct bundle_header *header);
+int create_bundle(struct bundle_header *header, const char *path,
+ int argc, const char **argv);
+int verify_bundle(struct bundle_header *header, int verbose);
+int unbundle(struct bundle_header *header, int bundle_fd);
+int list_bundle_refs(struct bundle_header *header,
+ int argc, const char **argv);
+
+#endif
diff --git a/cache-tree.c b/cache-tree.c
index 50b35264fd..39da54d1e5 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -320,13 +320,13 @@ static int update_one(struct cache_tree *it,
}
else {
sha1 = ce->sha1;
- mode = ntohl(ce->ce_mode);
+ mode = ce->ce_mode;
entlen = pathlen - baselen;
}
if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1))
return error("invalid object %s", sha1_to_hex(sha1));
- if (!ce->ce_mode)
+ if (ce->ce_flags & CE_REMOVE)
continue; /* entry being removed */
strbuf_grow(&buffer, entlen + 100);
@@ -529,3 +529,58 @@ struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
}
return it;
}
+
+int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix)
+{
+ int entries, was_valid, newfd;
+
+ /*
+ * We can't free this memory, it becomes part of a linked list
+ * parsed atexit()
+ */
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ newfd = hold_locked_index(lock_file, 1);
+
+ entries = read_cache();
+ if (entries < 0)
+ return WRITE_TREE_UNREADABLE_INDEX;
+
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+
+ was_valid = cache_tree_fully_valid(active_cache_tree);
+
+ if (!was_valid) {
+ if (cache_tree_update(active_cache_tree,
+ active_cache, active_nr,
+ missing_ok, 0) < 0)
+ return WRITE_TREE_UNMERGED_INDEX;
+ if (0 <= newfd) {
+ if (!write_cache(newfd, active_cache, active_nr) &&
+ !commit_lock_file(lock_file))
+ newfd = -1;
+ }
+ /* Not being able to write is fine -- we are only interested
+ * in updating the cache-tree part, and if the next caller
+ * ends up using the old index with unupdated cache-tree part
+ * it misses the work we did here, but that is just a
+ * performance penalty and not a big deal.
+ */
+ }
+
+ if (prefix) {
+ struct cache_tree *subtree =
+ cache_tree_find(active_cache_tree, prefix);
+ if (!subtree)
+ return WRITE_TREE_PREFIX_ERROR;
+ hashcpy(sha1, subtree->sha1);
+ }
+ else
+ hashcpy(sha1, active_cache_tree->sha1);
+
+ if (0 <= newfd)
+ rollback_lock_file(lock_file);
+
+ return 0;
+}
diff --git a/cache-tree.h b/cache-tree.h
index 8243228e49..44aad426d3 100644
--- a/cache-tree.h
+++ b/cache-tree.h
@@ -30,4 +30,9 @@ int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int)
struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
+#define WRITE_TREE_UNREADABLE_INDEX (-1)
+#define WRITE_TREE_UNMERGED_INDEX (-2)
+#define WRITE_TREE_PREFIX_ERROR (-3)
+
+int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix);
#endif
diff --git a/cache.h b/cache.h
index e0abcd697c..e1000bccb2 100644
--- a/cache.h
+++ b/cache.h
@@ -3,11 +3,12 @@
#include "git-compat-util.h"
#include "strbuf.h"
+#include "hash.h"
#include SHA1_HEADER
#include <zlib.h>
-#if ZLIB_VERNUM < 0x1200
+#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
#endif
@@ -94,66 +95,116 @@ struct cache_time {
* We save the fields in big-endian order to allow using the
* index file over NFS transparently.
*/
+struct ondisk_cache_entry {
+ struct cache_time ctime;
+ struct cache_time mtime;
+ unsigned int dev;
+ unsigned int ino;
+ unsigned int mode;
+ unsigned int uid;
+ unsigned int gid;
+ unsigned int size;
+ unsigned char sha1[20];
+ unsigned short flags;
+ char name[FLEX_ARRAY]; /* more */
+};
+
struct cache_entry {
- struct cache_time ce_ctime;
- struct cache_time ce_mtime;
+ struct cache_entry *next;
+ unsigned int ce_ctime;
+ unsigned int ce_mtime;
unsigned int ce_dev;
unsigned int ce_ino;
unsigned int ce_mode;
unsigned int ce_uid;
unsigned int ce_gid;
unsigned int ce_size;
+ unsigned int ce_flags;
unsigned char sha1[20];
- unsigned short ce_flags;
char name[FLEX_ARRAY]; /* more */
};
#define CE_NAMEMASK (0x0fff)
#define CE_STAGEMASK (0x3000)
-#define CE_UPDATE (0x4000)
#define CE_VALID (0x8000)
#define CE_STAGESHIFT 12
-#define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
-#define ce_namelen(ce) (CE_NAMEMASK & ntohs((ce)->ce_flags))
+/* In-memory only */
+#define CE_UPDATE (0x10000)
+#define CE_REMOVE (0x20000)
+#define CE_UPTODATE (0x40000)
+#define CE_UNHASHED (0x80000)
+
+static inline unsigned create_ce_flags(size_t len, unsigned stage)
+{
+ if (len >= CE_NAMEMASK)
+ len = CE_NAMEMASK;
+ return (len | (stage << CE_STAGESHIFT));
+}
+
+static inline size_t ce_namelen(const struct cache_entry *ce)
+{
+ size_t len = ce->ce_flags & CE_NAMEMASK;
+ if (len < CE_NAMEMASK)
+ return len;
+ return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK;
+}
+
#define ce_size(ce) cache_entry_size(ce_namelen(ce))
-#define ce_stage(ce) ((CE_STAGEMASK & ntohs((ce)->ce_flags)) >> CE_STAGESHIFT)
+#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce))
+#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
+#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
+#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
static inline unsigned int create_ce_mode(unsigned int mode)
{
if (S_ISLNK(mode))
- return htonl(S_IFLNK);
+ return S_IFLNK;
if (S_ISDIR(mode) || S_ISGITLINK(mode))
- return htonl(S_IFGITLINK);
- return htonl(S_IFREG | ce_permissions(mode));
+ return S_IFGITLINK;
+ return S_IFREG | ce_permissions(mode);
}
static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
{
extern int trust_executable_bit, has_symlinks;
if (!has_symlinks && S_ISREG(mode) &&
- ce && S_ISLNK(ntohl(ce->ce_mode)))
+ ce && S_ISLNK(ce->ce_mode))
return ce->ce_mode;
if (!trust_executable_bit && S_ISREG(mode)) {
- if (ce && S_ISREG(ntohl(ce->ce_mode)))
+ if (ce && S_ISREG(ce->ce_mode))
return ce->ce_mode;
return create_ce_mode(0666);
}
return create_ce_mode(mode);
}
+static inline int ce_to_dtype(const struct cache_entry *ce)
+{
+ unsigned ce_mode = ntohl(ce->ce_mode);
+ if (S_ISREG(ce_mode))
+ return DT_REG;
+ else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))
+ return DT_DIR;
+ else if (S_ISLNK(ce_mode))
+ return DT_LNK;
+ 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)
#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
+#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7)
struct index_state {
struct cache_entry **cache;
unsigned int cache_nr, cache_alloc, cache_changed;
struct cache_tree *cache_tree;
time_t timestamp;
- void *mmap;
- size_t mmap_size;
+ void *alloc;
+ unsigned name_hash_initialized : 1;
+ struct hash_table name_hash;
};
extern struct index_state the_index;
@@ -175,8 +226,9 @@ extern struct index_state the_index;
#define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
#define add_file_to_cache(path, verbose) add_file_to_index(&the_index, (path), (verbose))
#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
-#define ce_match_stat(ce, st, really) ie_match_stat(&the_index, (ce), (st), (really))
-#define ce_modified(ce, st, really) ie_modified(&the_index, (ce), (st), (really))
+#define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
+#define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
+#define cache_name_exists(name, namelen) index_name_exists(&the_index, (name), (namelen))
#endif
enum object_type {
@@ -192,6 +244,13 @@ enum object_type {
OBJ_MAX,
};
+static inline enum object_type object_type(unsigned int mode)
+{
+ return S_ISDIR(mode) ? OBJ_TREE :
+ S_ISGITLINK(mode) ? OBJ_COMMIT :
+ OBJ_BLOB;
+}
+
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
@@ -222,6 +281,7 @@ extern const char *get_git_work_tree(void);
#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
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);
@@ -255,6 +315,7 @@ extern int read_index_from(struct index_state *, const char *path);
extern int write_index(struct index_state *, int newfd);
extern int discard_index(struct index_state *);
extern int verify_path(const char *path);
+extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
extern int index_name_pos(struct index_state *, const char *name, int namelen);
#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */
#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */
@@ -267,8 +328,14 @@ extern int remove_file_from_index(struct index_state *, const char *path);
extern int add_file_to_index(struct index_state *, const char *path, int verbose);
extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
-extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, int);
-extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, int);
+
+/* do stat comparison even if CE_VALID is true */
+#define CE_MATCH_IGNORE_VALID 01
+/* do not check the contents but report dirty on racily-clean entries */
+#define CE_MATCH_RACY_IS_DIRTY 02
+extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, unsigned int);
+extern int ie_modified(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_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
@@ -283,6 +350,7 @@ extern int refresh_index(struct index_state *, unsigned int flags, const char **
struct lock_file {
struct lock_file *next;
+ int fd;
pid_t owner;
char on_list;
char filename[PATH_MAX];
@@ -293,7 +361,7 @@ extern int commit_lock_file(struct lock_file *);
extern int hold_locked_index(struct lock_file *, int);
extern int commit_locked_index(struct lock_file *);
extern void set_alternate_index_output(const char *);
-
+extern int close_lock_file(struct lock_file *);
extern void rollback_lock_file(struct lock_file *);
extern int delete_ref(const char *, const unsigned char *sha1);
@@ -315,6 +383,14 @@ extern size_t packed_git_limit;
extern size_t delta_base_cache_limit;
extern int auto_crlf;
+enum safe_crlf {
+ SAFE_CRLF_FALSE = 0,
+ SAFE_CRLF_FAIL = 1,
+ SAFE_CRLF_WARN = 2,
+};
+
+extern enum safe_crlf safe_crlf;
+
#define GIT_REPO_VERSION 0
extern int repository_format_version;
extern int check_repository_format(void);
@@ -408,6 +484,10 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
+extern const char *ref_rev_parse_rules[];
+extern const char *ref_fetch_rules[];
+
extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
extern int validate_headref(const char *ref);
@@ -434,9 +514,13 @@ void datestamp(char *buf, int bufsize);
unsigned long approxidate(const char *);
enum date_mode parse_date_format(const char *format);
+#define IDENT_WARN_ON_NO_NAME 1
+#define IDENT_ERROR_ON_NO_NAME 2
+#define IDENT_NO_DATE 4
extern const char *git_author_info(int);
extern const char *git_committer_info(int);
extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
+extern const char *fmt_name(const char *name, const char *email);
struct checkout {
const char *base_dir;
@@ -492,7 +576,20 @@ struct ref {
struct ref *next;
unsigned char old_sha1[20];
unsigned char new_sha1[20];
- unsigned char force;
+ unsigned int force:1,
+ merge:1,
+ nonfastforward:1,
+ deletion:1;
+ enum {
+ REF_STATUS_NONE = 0,
+ REF_STATUS_OK,
+ REF_STATUS_REJECT_NONFASTFORWARD,
+ REF_STATUS_REJECT_NODELETE,
+ REF_STATUS_UPTODATE,
+ REF_STATUS_REMOTE_REJECT,
+ REF_STATUS_EXPECTING_REPORT,
+ } status;
+ char *remote_status;
struct ref *peer_ref; /* when renaming */
char name[FLEX_ARRAY]; /* more */
};
@@ -501,9 +598,11 @@ struct ref {
#define REF_HEADS (1u << 1)
#define REF_TAGS (1u << 2)
+extern struct ref *find_ref_by_name(struct ref *list, const char *name);
+
#define CONNECT_VERBOSE (1u << 0)
-extern pid_t git_connect(int fd[2], char *url, const char *prog, int flags);
-extern int finish_connect(pid_t pid);
+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 path_match(const char *path, int nr, char **match);
extern int get_ack(int fd, unsigned char *result_sha1);
extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
@@ -523,6 +622,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 unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
+extern void close_pack_windows(struct packed_git *);
extern void unuse_pack(struct pack_window **);
extern struct packed_git *add_packed_git(const char *, int, int);
extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
@@ -545,10 +645,16 @@ 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 *);
extern int git_config_bool(const char *, const char *);
+extern int git_config_string(const char **, const char *, const char *);
extern int git_config_set(const char *, const char *);
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);
+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 *);
#define MAX_GITNAME (1000)
extern char git_default_email[MAX_GITNAME];
@@ -568,11 +674,12 @@ extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char
/* pager.c */
extern void setup_pager(void);
-extern char *pager_program;
-extern int pager_in_use;
+extern const char *pager_program;
+extern int pager_in_use(void);
extern int pager_use_color;
-extern char *editor_program;
+extern const char *editor_program;
+extern const char *excludes_file;
/* base85 */
int decode_85(char *dst, const char *line, int linelen);
@@ -588,17 +695,42 @@ extern void alloc_report(void);
/* trace.c */
extern void trace_printf(const char *format, ...);
-extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
+extern void trace_argv_printf(const char **argv, const char *format, ...);
/* 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);
+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);
+/* add */
+void add_files_to_cache(int verbose, const char *prefix, const char **pathspec);
+
/* diff.c */
extern int diff_auto_refresh_index;
/* match-trees.c */
void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
+/*
+ * whitespace rules.
+ * used by both diff and apply
+ */
+#define WS_TRAILING_SPACE 01
+#define WS_SPACE_BEFORE_TAB 02
+#define WS_INDENT_WITH_NON_TAB 04
+#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
+extern unsigned whitespace_rule_cfg;
+extern unsigned whitespace_rule(const char *);
+extern unsigned parse_whitespace_rule(const char *);
+extern unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
+ FILE *stream, const char *set,
+ const char *reset, const char *ws);
+extern char *whitespace_error_string(unsigned ws);
+
+/* ls-files */
+int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
+int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
+void overlay_tree_on_cache(const char *tree_name, const char *prefix);
+
#endif /* CACHE_H */
diff --git a/check-racy.c b/check-racy.c
index d6a08b4a55..00d92a1663 100644
--- a/check-racy.c
+++ b/check-racy.c
@@ -18,7 +18,7 @@ int main(int ac, char **av)
if (ce_match_stat(ce, &st, 0))
dirty++;
- else if (ce_match_stat(ce, &st, 2))
+ else if (ce_match_stat(ce, &st, CE_MATCH_RACY_IS_DIRTY))
racy++;
else
clean++;
diff --git a/color.c b/color.c
index 09d82eec3d..12a6453f90 100644
--- a/color.c
+++ b/color.c
@@ -3,6 +3,8 @@
#define COLOR_RESET "\033[m"
+int git_use_color_default = 0;
+
static int parse_color(const char *name, int len)
{
static const char * const color_names[] = {
@@ -17,7 +19,7 @@ static int parse_color(const char *name, int len)
return i - 1;
}
i = strtol(name, &end, 10);
- if (*name && !*end && i >= -1 && i <= 255)
+ if (end - name == len && i >= -1 && i <= 255)
return i;
return -2;
}
@@ -116,58 +118,76 @@ bad:
die("bad config value '%s' for variable '%s'", value, var);
}
-int git_config_colorbool(const char *var, const char *value)
+int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
{
- if (!value)
- return 1;
- if (!strcasecmp(value, "auto")) {
- if (isatty(1) || (pager_in_use && pager_use_color)) {
- char *term = getenv("TERM");
- if (term && strcmp(term, "dumb"))
- return 1;
- }
+ if (value) {
+ if (!strcasecmp(value, "never"))
+ return 0;
+ if (!strcasecmp(value, "always"))
+ return 1;
+ if (!strcasecmp(value, "auto"))
+ goto auto_color;
+ }
+
+ /* Missing or explicit false to turn off colorization */
+ if (!git_config_bool(var, value))
return 0;
+
+ /* any normal truth value defaults to 'auto' */
+ auto_color:
+ if (stdout_is_tty < 0)
+ stdout_is_tty = isatty(1);
+ if (stdout_is_tty || (pager_in_use() && pager_use_color)) {
+ char *term = getenv("TERM");
+ if (term && strcmp(term, "dumb"))
+ return 1;
}
- if (!strcasecmp(value, "never"))
+ return 0;
+}
+
+int git_color_default_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "color.ui")) {
+ git_use_color_default = git_config_colorbool(var, value, -1);
return 0;
- if (!strcasecmp(value, "always"))
- return 1;
- return git_config_bool(var, value);
+ }
+
+ return git_default_config(var, value);
}
-static int color_vprintf(const char *color, const char *fmt,
+static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
va_list args, const char *trail)
{
int r = 0;
if (*color)
- r += printf("%s", color);
- r += vprintf(fmt, args);
+ r += fprintf(fp, "%s", color);
+ r += vfprintf(fp, fmt, args);
if (*color)
- r += printf("%s", COLOR_RESET);
+ r += fprintf(fp, "%s", COLOR_RESET);
if (trail)
- r += printf("%s", trail);
+ r += fprintf(fp, "%s", trail);
return r;
}
-int color_printf(const char *color, const char *fmt, ...)
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
- r = color_vprintf(color, fmt, args, NULL);
+ r = color_vfprintf(fp, color, fmt, args, NULL);
va_end(args);
return r;
}
-int color_printf_ln(const char *color, const char *fmt, ...)
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
- r = color_vprintf(color, fmt, args, "\n");
+ r = color_vfprintf(fp, color, fmt, args, "\n");
va_end(args);
return r;
}
diff --git a/color.h b/color.h
index 88bb8ff1bd..ecda5569a2 100644
--- a/color.h
+++ b/color.h
@@ -4,9 +4,20 @@
/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
#define COLOR_MAXLEN 24
-int git_config_colorbool(const char *var, const char *value);
+/*
+ * This variable stores the value of color.ui
+ */
+extern int git_use_color_default;
+
+
+/*
+ * Use this instead of git_default_config if you need the value of color.ui.
+ */
+int git_color_default_config(const char *var, const char *value);
+
+int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
void color_parse(const char *var, const char *value, char *dst);
-int color_printf(const char *color, const char *fmt, ...);
-int color_printf_ln(const char *color, const char *fmt, ...);
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
#endif /* COLOR_H */
diff --git a/combine-diff.c b/combine-diff.c
index fe5a2a1953..0e19cbaacc 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -226,7 +226,7 @@ static void combine_diff(const unsigned char *parent, mmfile_t *result_file,
state.num_parent = num_parent;
state.n = n;
- xdl_diff(&parent_file, result_file, &xpp, &xecfg, &ecb);
+ xdi_diff(&parent_file, result_file, &xpp, &xecfg, &ecb);
free(parent_file.ptr);
/* Assign line numbers for this parent.
@@ -646,12 +646,19 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt,
sline->p_lno[i] = sline->p_lno[j];
}
-static void dump_quoted_path(const char *prefix, const char *path,
+static void dump_quoted_path(const char *head,
+ const char *prefix,
+ const char *path,
const char *c_meta, const char *c_reset)
{
- printf("%s%s", c_meta, prefix);
- quote_c_style(path, NULL, stdout, 0);
- printf("%s\n", c_reset);
+ static struct strbuf buf = STRBUF_INIT;
+
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, c_meta);
+ strbuf_addstr(&buf, head);
+ quote_two_c_style(&buf, prefix, path, 0);
+ strbuf_addstr(&buf, c_reset);
+ puts(buf.buf);
}
static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
@@ -664,7 +671,7 @@ 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 = opt->full_index ? 40 : DEFAULT_ABBREV;
+ int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
mmfile_t result_file;
context = opt->context;
@@ -784,7 +791,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
if (show_hunks || mode_differs || working_tree_file) {
const char *abb;
- int use_color = opt->color_diff;
+ 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;
@@ -793,7 +800,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
if (rev->loginfo && !rev->no_commit_id)
show_log(rev, opt->msg_sep);
dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
- elem->path, c_meta, c_reset);
+ "", 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,
@@ -829,14 +836,19 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
printf("%s\n", c_reset);
}
if (added)
- dump_quoted_path("--- /dev/", "null", c_meta, c_reset);
+ dump_quoted_path("--- ", "", "/dev/null",
+ c_meta, c_reset);
else
- dump_quoted_path("--- a/", elem->path, c_meta, c_reset);
+ dump_quoted_path("--- ", opt->a_prefix, elem->path,
+ c_meta, c_reset);
if (deleted)
- dump_quoted_path("+++ /dev/", "null", c_meta, c_reset);
+ dump_quoted_path("+++ ", "", "/dev/null",
+ c_meta, c_reset);
else
- dump_quoted_path("+++ b/", elem->path, c_meta, c_reset);
- dump_sline(sline, cnt, num_parent, opt->color_diff);
+ dump_quoted_path("+++ ", opt->b_prefix, elem->path,
+ c_meta, c_reset);
+ dump_sline(sline, cnt, num_parent,
+ DIFF_OPT_TST(opt, COLOR_DIFF));
}
free(result);
@@ -929,8 +941,8 @@ void diff_tree_combined(const unsigned char *sha1,
diffopts = *opt;
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diffopts.recursive = 1;
- diffopts.allow_external = 0;
+ DIFF_OPT_SET(&diffopts, RECURSIVE);
+ DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL);
show_log_first = !!rev->loginfo && !rev->no_commit_id;
needsep = 0;
diff --git a/command-list.txt b/command-list.txt
new file mode 100644
index 0000000000..3583a33ee9
--- /dev/null
+++ b/command-list.txt
@@ -0,0 +1,130 @@
+# List of known git commands.
+# command name category [deprecated] [common]
+git-add mainporcelain common
+git-am mainporcelain
+git-annotate ancillaryinterrogators
+git-apply plumbingmanipulators
+git-archimport foreignscminterface
+git-archive mainporcelain
+git-bisect mainporcelain common
+git-blame ancillaryinterrogators
+git-branch mainporcelain common
+git-bundle mainporcelain
+git-cat-file plumbinginterrogators
+git-check-attr purehelpers
+git-checkout mainporcelain common
+git-checkout-index plumbingmanipulators
+git-check-ref-format purehelpers
+git-cherry ancillaryinterrogators
+git-cherry-pick mainporcelain
+git-citool mainporcelain
+git-clean mainporcelain
+git-clone mainporcelain common
+git-commit mainporcelain common
+git-commit-tree plumbingmanipulators
+git-config ancillarymanipulators
+git-count-objects ancillaryinterrogators
+git-cvsexportcommit foreignscminterface
+git-cvsimport foreignscminterface
+git-cvsserver foreignscminterface
+git-daemon synchingrepositories
+git-describe mainporcelain
+git-diff mainporcelain common
+git-diff-files plumbinginterrogators
+git-diff-index plumbinginterrogators
+git-diff-tree plumbinginterrogators
+git-fast-export ancillarymanipulators
+git-fast-import ancillarymanipulators
+git-fetch mainporcelain common
+git-fetch-pack synchingrepositories
+git-filter-branch ancillarymanipulators
+git-fmt-merge-msg purehelpers
+git-for-each-ref plumbinginterrogators
+git-format-patch mainporcelain
+git-fsck ancillaryinterrogators
+git-gc mainporcelain
+git-get-tar-commit-id ancillaryinterrogators
+git-grep mainporcelain common
+git-gui mainporcelain
+git-hash-object plumbingmanipulators
+git-help ancillaryinterrogators
+git-http-fetch synchelpers
+git-http-push synchelpers
+git-imap-send foreignscminterface
+git-index-pack plumbingmanipulators
+git-init mainporcelain common
+git-instaweb ancillaryinterrogators
+gitk mainporcelain
+git-log mainporcelain common
+git-lost-found ancillarymanipulators deprecated
+git-ls-files plumbinginterrogators
+git-ls-remote plumbinginterrogators
+git-ls-tree plumbinginterrogators
+git-mailinfo purehelpers
+git-mailsplit purehelpers
+git-merge mainporcelain common
+git-merge-base plumbinginterrogators
+git-merge-file plumbingmanipulators
+git-merge-index plumbingmanipulators
+git-merge-one-file purehelpers
+git-mergetool ancillarymanipulators
+git-merge-tree ancillaryinterrogators
+git-mktag plumbingmanipulators
+git-mktree plumbingmanipulators
+git-mv mainporcelain common
+git-name-rev plumbinginterrogators
+git-pack-objects plumbingmanipulators
+git-pack-redundant plumbinginterrogators
+git-pack-refs ancillarymanipulators
+git-parse-remote synchelpers
+git-patch-id purehelpers
+git-peek-remote purehelpers deprecated
+git-prune ancillarymanipulators
+git-prune-packed plumbingmanipulators
+git-pull mainporcelain common
+git-push mainporcelain common
+git-quiltimport foreignscminterface
+git-read-tree plumbingmanipulators
+git-rebase mainporcelain common
+git-receive-pack synchelpers
+git-reflog ancillarymanipulators
+git-relink ancillarymanipulators
+git-remote ancillarymanipulators
+git-repack ancillarymanipulators
+git-repo-config ancillarymanipulators deprecated
+git-request-pull foreignscminterface
+git-rerere ancillaryinterrogators
+git-reset mainporcelain common
+git-revert mainporcelain
+git-rev-list plumbinginterrogators
+git-rev-parse ancillaryinterrogators
+git-rm mainporcelain common
+git-send-email foreignscminterface
+git-send-pack synchingrepositories
+git-shell synchelpers
+git-shortlog mainporcelain
+git-show mainporcelain common
+git-show-branch ancillaryinterrogators
+git-show-index plumbinginterrogators
+git-show-ref plumbinginterrogators
+git-sh-setup purehelpers
+git-stash mainporcelain
+git-status mainporcelain common
+git-stripspace purehelpers
+git-submodule mainporcelain
+git-svn foreignscminterface
+git-symbolic-ref plumbingmanipulators
+git-tag mainporcelain common
+git-tar-tree plumbinginterrogators deprecated
+git-unpack-file plumbinginterrogators
+git-unpack-objects plumbingmanipulators
+git-update-index plumbingmanipulators
+git-update-ref plumbingmanipulators
+git-update-server-info synchingrepositories
+git-upload-archive synchelpers
+git-upload-pack synchelpers
+git-var plumbinginterrogators
+git-verify-pack plumbinginterrogators
+git-verify-tag ancillaryinterrogators
+git-whatchanged ancillaryinterrogators
+git-write-tree plumbingmanipulators
diff --git a/commit.c b/commit.c
index ac24266e93..22ce776863 100644
--- a/commit.c
+++ b/commit.c
@@ -3,70 +3,13 @@
#include "commit.h"
#include "pkt-line.h"
#include "utf8.h"
-#include "interpolate.h"
#include "diff.h"
#include "revision.h"
int save_commit_buffer = 1;
-struct sort_node
-{
- /*
- * the number of children of the associated commit
- * that also occur in the list being sorted.
- */
- unsigned int indegree;
-
- /*
- * reference to original list item that we will re-use
- * on output.
- */
- struct commit_list * list_item;
-
-};
-
const char *commit_type = "commit";
-static struct cmt_fmt_map {
- const char *n;
- size_t cmp_len;
- enum cmit_fmt v;
-} cmt_fmts[] = {
- { "raw", 1, CMIT_FMT_RAW },
- { "medium", 1, CMIT_FMT_MEDIUM },
- { "short", 1, CMIT_FMT_SHORT },
- { "email", 1, CMIT_FMT_EMAIL },
- { "full", 5, CMIT_FMT_FULL },
- { "fuller", 5, CMIT_FMT_FULLER },
- { "oneline", 1, CMIT_FMT_ONELINE },
- { "format:", 7, CMIT_FMT_USERFORMAT},
-};
-
-static char *user_format;
-
-enum cmit_fmt get_commit_format(const char *arg)
-{
- int i;
-
- if (!arg || !*arg)
- return CMIT_FMT_DEFAULT;
- if (*arg == '=')
- arg++;
- if (!prefixcmp(arg, "format:")) {
- if (user_format)
- free(user_format);
- user_format = xstrdup(arg + 7);
- return CMIT_FMT_USERFORMAT;
- }
- for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
- if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
- !strncmp(arg, cmt_fmts[i].n, strlen(arg)))
- return cmt_fmts[i].v;
- }
-
- die("invalid --pretty format: %s", arg);
-}
-
static struct commit *check_commit(struct object *obj,
const unsigned char *sha1,
int quiet)
@@ -105,19 +48,32 @@ struct commit *lookup_commit(const unsigned char *sha1)
return check_commit(obj, sha1, 0);
}
-static unsigned long parse_commit_date(const char *buf)
+static unsigned long parse_commit_date(const char *buf, const char *tail)
{
unsigned long date;
+ const char *dateptr;
+ if (buf + 6 >= tail)
+ return 0;
if (memcmp(buf, "author", 6))
return 0;
- while (*buf++ != '\n')
+ while (buf < tail && *buf++ != '\n')
/* nada */;
+ if (buf + 9 >= tail)
+ return 0;
if (memcmp(buf, "committer", 9))
return 0;
- while (*buf++ != '>')
+ while (buf < tail && *buf++ != '>')
/* nada */;
- date = strtoul(buf, NULL, 10);
+ if (buf >= tail)
+ return 0;
+ dateptr = buf;
+ while (buf < tail && *buf++ != '\n')
+ /* nada */;
+ if (buf >= tail)
+ return 0;
+ /* dateptr < buf && buf[-1] == '\n', so strtoul will stop at buf-1 */
+ date = strtoul(dateptr, NULL, 10);
if (date == ULONG_MAX)
date = 0;
return date;
@@ -293,9 +249,9 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
return 0;
item->object.parsed = 1;
tail += size;
- if (tail <= bufptr + 5 || memcmp(bufptr, "tree ", 5))
+ if (tail <= bufptr + 46 || memcmp(bufptr, "tree ", 5) || bufptr[45] != '\n')
return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
- if (tail <= bufptr + 45 || get_sha1_hex(bufptr + 5, parent) < 0)
+ if (get_sha1_hex(bufptr + 5, parent) < 0)
return error("bad tree pointer in commit %s",
sha1_to_hex(item->object.sha1));
item->tree = lookup_tree(parent);
@@ -332,7 +288,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
n_refs++;
}
}
- item->date = parse_commit_date(bufptr);
+ item->date = parse_commit_date(bufptr, tail);
if (track_object_refs) {
unsigned i = 0;
@@ -355,6 +311,8 @@ int parse_commit(struct commit *item)
unsigned long size;
int ret;
+ if (!item)
+ return -1;
if (item->object.parsed)
return 0;
buffer = read_sha1_file(item->object.sha1, &type, &size);
@@ -429,8 +387,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
while (parents) {
struct commit *commit = parents->item;
- parse_commit(commit);
- if (!(commit->object.flags & mark)) {
+ if (!parse_commit(commit) && !(commit->object.flags & mark)) {
commit->object.flags |= mark;
insert_by_date(commit, list);
}
@@ -460,684 +417,6 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
}
}
-/*
- * Generic support for pretty-printing the header
- */
-static int get_one_line(const char *msg)
-{
- int ret = 0;
-
- for (;;) {
- char c = *msg++;
- if (!c)
- break;
- ret++;
- if (c == '\n')
- break;
- }
- return ret;
-}
-
-/* High bit set, or ISO-2022-INT */
-static int non_ascii(int ch)
-{
- ch = (ch & 0xff);
- return ((ch & 0x80) || (ch == 0x1b));
-}
-
-static int is_rfc2047_special(char ch)
-{
- return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
-}
-
-static void add_rfc2047(struct strbuf *sb, const char *line, int len,
- const char *encoding)
-{
- int i, last;
-
- for (i = 0; i < len; i++) {
- int ch = line[i];
- if (non_ascii(ch))
- goto needquote;
- if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
- goto needquote;
- }
- strbuf_add(sb, 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++) {
- unsigned ch = line[i] & 0xFF;
- /*
- * 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);
- strbuf_addf(sb, "=%02X", ch);
- last = i + 1;
- }
- }
- strbuf_add(sb, line + last, len - last);
- strbuf_addstr(sb, "?=");
-}
-
-static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
- const char *line, enum date_mode dmode,
- const char *encoding)
-{
- char *date;
- int namelen;
- unsigned long time;
- int tz;
- const char *filler = " ";
-
- if (fmt == CMIT_FMT_ONELINE)
- return;
- date = strchr(line, '>');
- if (!date)
- return;
- namelen = ++date - line;
- time = strtoul(date, &date, 10);
- tz = strtol(date, NULL, 10);
-
- if (fmt == CMIT_FMT_EMAIL) {
- char *name_tail = strchr(line, '<');
- int display_name_length;
- if (!name_tail)
- return;
- while (line < name_tail && isspace(name_tail[-1]))
- name_tail--;
- display_name_length = name_tail - line;
- filler = "";
- strbuf_addstr(sb, "From: ");
- add_rfc2047(sb, line, display_name_length, encoding);
- 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,
- filler, namelen, line);
- }
- switch (fmt) {
- case CMIT_FMT_MEDIUM:
- strbuf_addf(sb, "Date: %s\n", show_date(time, tz, dmode));
- 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));
- break;
- default:
- /* notin' */
- break;
- }
-}
-
-static int is_empty_line(const char *line, int *len_p)
-{
- int len = *len_p;
- while (len && isspace(line[len-1]))
- len--;
- *len_p = len;
- return !len;
-}
-
-static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
- const struct commit *commit, int abbrev)
-{
- struct commit_list *parent = commit->parents;
-
- if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
- !parent || !parent->next)
- return;
-
- strbuf_addstr(sb, "Merge:");
-
- while (parent) {
- struct commit *p = parent->item;
- const char *hex = NULL;
- const char *dots;
- if (abbrev)
- hex = find_unique_abbrev(p->object.sha1, abbrev);
- if (!hex)
- hex = sha1_to_hex(p->object.sha1);
- dots = (abbrev && strlen(hex) != 40) ? "..." : "";
- parent = parent->next;
-
- strbuf_addf(sb, " %s%s", hex, dots);
- }
- strbuf_addch(sb, '\n');
-}
-
-static char *get_header(const struct commit *commit, const char *key)
-{
- int key_len = strlen(key);
- const char *line = commit->buffer;
-
- for (;;) {
- const char *eol = strchr(line, '\n'), *next;
-
- if (line == eol)
- return NULL;
- if (!eol) {
- eol = line + strlen(line);
- next = NULL;
- } else
- next = eol + 1;
- if (eol - line > key_len &&
- !strncmp(line, key, key_len) &&
- line[key_len] == ' ') {
- return xmemdupz(line + key_len + 1, eol - line - key_len - 1);
- }
- line = next;
- }
-}
-
-static char *replace_encoding_header(char *buf, const char *encoding)
-{
- struct strbuf tmp;
- size_t start, len;
- char *cp = buf;
-
- /* guess if there is an encoding header before a \n\n */
- while (strncmp(cp, "encoding ", strlen("encoding "))) {
- cp = strchr(cp, '\n');
- if (!cp || *++cp == '\n')
- return buf;
- }
- start = cp - buf;
- cp = strchr(cp, '\n');
- if (!cp)
- return buf; /* should not happen but be defensive */
- len = cp + 1 - (buf + start);
-
- strbuf_init(&tmp, 0);
- strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);
- if (is_encoding_utf8(encoding)) {
- /* we have re-coded to UTF-8; drop the header */
- strbuf_remove(&tmp, start, len);
- } else {
- /* just replaces XXXX in 'encoding XXXX\n' */
- strbuf_splice(&tmp, start + strlen("encoding "),
- len - strlen("encoding \n"),
- encoding, strlen(encoding));
- }
- return strbuf_detach(&tmp, NULL);
-}
-
-static char *logmsg_reencode(const struct commit *commit,
- const char *output_encoding)
-{
- static const char *utf8 = "utf-8";
- const char *use_encoding;
- char *encoding;
- char *out;
-
- if (!*output_encoding)
- return NULL;
- encoding = get_header(commit, "encoding");
- use_encoding = encoding ? encoding : utf8;
- if (!strcmp(use_encoding, output_encoding))
- if (encoding) /* we'll strip encoding header later */
- out = xstrdup(commit->buffer);
- else
- return NULL; /* nothing to do */
- else
- out = reencode_string(commit->buffer,
- output_encoding, use_encoding);
- if (out)
- out = replace_encoding_header(out, output_encoding);
-
- free(encoding);
- return out;
-}
-
-static void fill_person(struct interp *table, const char *msg, int len)
-{
- int start, end, tz = 0;
- unsigned long date;
- char *ep;
-
- /* parse name */
- for (end = 0; end < len && msg[end] != '<'; end++)
- ; /* do nothing */
- start = end + 1;
- while (end > 0 && isspace(msg[end - 1]))
- end--;
- table[0].value = xmemdupz(msg, end);
-
- if (start >= len)
- return;
-
- /* parse email */
- for (end = start + 1; end < len && msg[end] != '>'; end++)
- ; /* do nothing */
-
- if (end >= len)
- return;
-
- table[1].value = xmemdupz(msg + start, end - start);
-
- /* parse date */
- for (start = end + 1; start < len && isspace(msg[start]); start++)
- ; /* do nothing */
- if (start >= len)
- return;
- date = strtoul(msg + start, &ep, 10);
- if (msg + start == ep)
- return;
-
- table[5].value = xmemdupz(msg + start, ep - (msg + start));
-
- /* parse tz */
- for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
- ; /* do nothing */
- if (start + 1 < len) {
- tz = strtoul(msg + start + 1, NULL, 10);
- if (msg[start] == '-')
- tz = -tz;
- }
-
- interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));
- interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));
- interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));
- interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
-}
-
-void format_commit_message(const struct commit *commit,
- const void *format, struct strbuf *sb)
-{
- struct interp table[] = {
- { "%H" }, /* commit hash */
- { "%h" }, /* abbreviated commit hash */
- { "%T" }, /* tree hash */
- { "%t" }, /* abbreviated tree hash */
- { "%P" }, /* parent hashes */
- { "%p" }, /* abbreviated parent hashes */
- { "%an" }, /* author name */
- { "%ae" }, /* author email */
- { "%ad" }, /* author date */
- { "%aD" }, /* author date, RFC2822 style */
- { "%ar" }, /* author date, relative */
- { "%at" }, /* author date, UNIX timestamp */
- { "%ai" }, /* author date, ISO 8601 */
- { "%cn" }, /* committer name */
- { "%ce" }, /* committer email */
- { "%cd" }, /* committer date */
- { "%cD" }, /* committer date, RFC2822 style */
- { "%cr" }, /* committer date, relative */
- { "%ct" }, /* committer date, UNIX timestamp */
- { "%ci" }, /* committer date, ISO 8601 */
- { "%e" }, /* encoding */
- { "%s" }, /* subject */
- { "%b" }, /* body */
- { "%Cred" }, /* red */
- { "%Cgreen" }, /* green */
- { "%Cblue" }, /* blue */
- { "%Creset" }, /* reset color */
- { "%n" }, /* newline */
- { "%m" }, /* left/right/bottom */
- };
- enum interp_index {
- IHASH = 0, IHASH_ABBREV,
- ITREE, ITREE_ABBREV,
- IPARENTS, IPARENTS_ABBREV,
- IAUTHOR_NAME, IAUTHOR_EMAIL,
- IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
- IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,
- ICOMMITTER_NAME, ICOMMITTER_EMAIL,
- ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
- ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
- ICOMMITTER_ISO8601,
- IENCODING,
- ISUBJECT,
- IBODY,
- IRED, IGREEN, IBLUE, IRESET_COLOR,
- INEWLINE,
- ILEFT_RIGHT,
- };
- struct commit_list *p;
- char parents[1024];
- unsigned long len;
- int i;
- enum { HEADER, SUBJECT, BODY } state;
- const char *msg = commit->buffer;
-
- if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
- die("invalid interp table!");
-
- /* these are independent of the commit */
- interp_set_entry(table, IRED, "\033[31m");
- interp_set_entry(table, IGREEN, "\033[32m");
- interp_set_entry(table, IBLUE, "\033[34m");
- interp_set_entry(table, IRESET_COLOR, "\033[m");
- interp_set_entry(table, INEWLINE, "\n");
-
- /* these depend on the commit */
- if (!commit->object.parsed)
- parse_object(commit->object.sha1);
- interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
- interp_set_entry(table, IHASH_ABBREV,
- find_unique_abbrev(commit->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
- interp_set_entry(table, ITREE_ABBREV,
- find_unique_abbrev(commit->tree->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, ILEFT_RIGHT,
- (commit->object.flags & BOUNDARY)
- ? "-"
- : (commit->object.flags & SYMMETRIC_LEFT)
- ? "<"
- : ">");
-
- parents[1] = 0;
- for (i = 0, p = commit->parents;
- p && i < sizeof(parents) - 1;
- p = p->next)
- i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
- sha1_to_hex(p->item->object.sha1));
- interp_set_entry(table, IPARENTS, parents + 1);
-
- parents[1] = 0;
- for (i = 0, p = commit->parents;
- p && i < sizeof(parents) - 1;
- p = p->next)
- i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
- find_unique_abbrev(p->item->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, IPARENTS_ABBREV, parents + 1);
-
- for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
- int eol;
- for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
- ; /* do nothing */
-
- if (state == SUBJECT) {
- table[ISUBJECT].value = xmemdupz(msg + i, eol - i);
- i = eol;
- }
- if (i == eol) {
- state++;
- /* strip empty lines */
- while (msg[eol + 1] == '\n')
- eol++;
- } else if (!prefixcmp(msg + i, "author "))
- fill_person(table + IAUTHOR_NAME,
- msg + i + 7, eol - i - 7);
- else if (!prefixcmp(msg + i, "committer "))
- fill_person(table + ICOMMITTER_NAME,
- msg + i + 10, eol - i - 10);
- else if (!prefixcmp(msg + i, "encoding "))
- table[IENCODING].value =
- xmemdupz(msg + i + 9, eol - i - 9);
- i = eol;
- }
- if (msg[i])
- table[IBODY].value = xstrdup(msg + i);
-
- len = interpolate(sb->buf + sb->len, strbuf_avail(sb),
- format, table, ARRAY_SIZE(table));
- if (len > strbuf_avail(sb)) {
- strbuf_grow(sb, len);
- interpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,
- format, table, ARRAY_SIZE(table));
- }
- strbuf_setlen(sb, sb->len + len);
- interp_clear_table(table, ARRAY_SIZE(table));
-}
-
-static void pp_header(enum cmit_fmt fmt,
- int abbrev,
- enum date_mode dmode,
- const char *encoding,
- const struct commit *commit,
- const char **msg_p,
- struct strbuf *sb)
-{
- int parents_shown = 0;
-
- for (;;) {
- const char *line = *msg_p;
- int linelen = get_one_line(*msg_p);
-
- if (!linelen)
- return;
- *msg_p += linelen;
-
- if (linelen == 1)
- /* End of header */
- return;
-
- if (fmt == CMIT_FMT_RAW) {
- strbuf_add(sb, line, linelen);
- continue;
- }
-
- if (!memcmp(line, "parent ", 7)) {
- if (linelen != 48)
- die("bad parent line in commit");
- continue;
- }
-
- if (!parents_shown) {
- struct commit_list *parent;
- int num;
- for (parent = commit->parents, num = 0;
- parent;
- parent = parent->next, num++)
- ;
- /* with enough slop */
- strbuf_grow(sb, num * 50 + 20);
- add_merge_info(fmt, sb, commit, abbrev);
- parents_shown = 1;
- }
-
- /*
- * MEDIUM == DEFAULT shows only author with dates.
- * FULL shows both authors but not dates.
- * FULLER shows both authors and dates.
- */
- if (!memcmp(line, "author ", 7)) {
- strbuf_grow(sb, linelen + 80);
- add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
- }
- if (!memcmp(line, "committer ", 10) &&
- (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
- strbuf_grow(sb, linelen + 80);
- add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
- }
- }
-}
-
-static void pp_title_line(enum cmit_fmt fmt,
- const char **msg_p,
- struct strbuf *sb,
- const char *subject,
- const char *after_subject,
- const char *encoding,
- int plain_non_ascii)
-{
- struct strbuf title;
-
- strbuf_init(&title, 80);
-
- for (;;) {
- const char *line = *msg_p;
- int linelen = get_one_line(line);
-
- *msg_p += linelen;
- if (!linelen || is_empty_line(line, &linelen))
- break;
-
- strbuf_grow(&title, linelen + 2);
- if (title.len) {
- if (fmt == CMIT_FMT_EMAIL) {
- strbuf_addch(&title, '\n');
- }
- strbuf_addch(&title, ' ');
- }
- strbuf_add(&title, line, linelen);
- }
-
- strbuf_grow(sb, title.len + 1024);
- if (subject) {
- strbuf_addstr(sb, subject);
- add_rfc2047(sb, title.buf, title.len, encoding);
- } else {
- strbuf_addbuf(sb, &title);
- }
- strbuf_addch(sb, '\n');
-
- if (plain_non_ascii) {
- const char *header_fmt =
- "MIME-Version: 1.0\n"
- "Content-Type: text/plain; charset=%s\n"
- "Content-Transfer-Encoding: 8bit\n";
- strbuf_addf(sb, header_fmt, encoding);
- }
- if (after_subject) {
- strbuf_addstr(sb, after_subject);
- }
- if (fmt == CMIT_FMT_EMAIL) {
- strbuf_addch(sb, '\n');
- }
- strbuf_release(&title);
-}
-
-static void pp_remainder(enum cmit_fmt fmt,
- const char **msg_p,
- struct strbuf *sb,
- int indent)
-{
- int first = 1;
- for (;;) {
- const char *line = *msg_p;
- int linelen = get_one_line(line);
- *msg_p += linelen;
-
- if (!linelen)
- break;
-
- if (is_empty_line(line, &linelen)) {
- if (first)
- continue;
- if (fmt == CMIT_FMT_SHORT)
- break;
- }
- first = 0;
-
- strbuf_grow(sb, linelen + indent + 20);
- if (indent) {
- memset(sb->buf + sb->len, ' ', indent);
- strbuf_setlen(sb, sb->len + indent);
- }
- strbuf_add(sb, line, linelen);
- strbuf_addch(sb, '\n');
- }
-}
-
-void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
- struct strbuf *sb, int abbrev,
- const char *subject, const char *after_subject,
- enum date_mode dmode)
-{
- unsigned long beginning_of_body;
- int indent = 4;
- const char *msg = commit->buffer;
- int plain_non_ascii = 0;
- char *reencoded;
- const char *encoding;
-
- if (fmt == CMIT_FMT_USERFORMAT) {
- format_commit_message(commit, user_format, sb);
- return;
- }
-
- encoding = (git_log_output_encoding
- ? git_log_output_encoding
- : git_commit_encoding);
- if (!encoding)
- encoding = "utf-8";
- reencoded = logmsg_reencode(commit, encoding);
- if (reencoded) {
- msg = reencoded;
- }
-
- if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
- indent = 0;
-
- /* After-subject is used to pass in Content-Type: multipart
- * MIME header; in that case we do not have to do the
- * plaintext content type even if the commit message has
- * non 7-bit ASCII character. Otherwise, check if we need
- * to say this is not a 7-bit ASCII.
- */
- if (fmt == CMIT_FMT_EMAIL && !after_subject) {
- int i, ch, in_body;
-
- for (in_body = i = 0; (ch = msg[i]); i++) {
- if (!in_body) {
- /* author could be non 7-bit ASCII but
- * the log may be so; skip over the
- * header part first.
- */
- if (ch == '\n' && msg[i+1] == '\n')
- in_body = 1;
- }
- else if (non_ascii(ch)) {
- plain_non_ascii = 1;
- break;
- }
- }
- }
-
- pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
- if (fmt != CMIT_FMT_ONELINE && !subject) {
- strbuf_addch(sb, '\n');
- }
-
- /* Skip excess blank lines at the beginning of body, if any... */
- for (;;) {
- int linelen = get_one_line(msg);
- int ll = linelen;
- if (!linelen)
- break;
- if (!is_empty_line(msg, &ll))
- break;
- msg += linelen;
- }
-
- /* These formats treat the title line specially. */
- if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
- pp_title_line(fmt, &msg, sb, subject,
- after_subject, encoding, plain_non_ascii);
-
- beginning_of_body = sb->len;
- if (fmt != CMIT_FMT_ONELINE)
- pp_remainder(fmt, &msg, sb, indent);
- strbuf_rtrim(sb);
-
- /* Make sure there is an EOLN for the non-oneline case */
- if (fmt != CMIT_FMT_ONELINE)
- strbuf_addch(sb, '\n');
-
- /*
- * The caller may append additional body text in e-mail
- * 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)
- strbuf_addch(sb, '\n');
- free(reencoded);
-}
-
struct commit *pop_commit(struct commit_list **stack)
{
struct commit_list *top = *stack;
@@ -1150,69 +429,38 @@ struct commit *pop_commit(struct commit_list **stack)
return item;
}
-void topo_sort_default_setter(struct commit *c, void *data)
-{
- c->util = data;
-}
-
-void *topo_sort_default_getter(struct commit *c)
-{
- return c->util;
-}
-
/*
* Performs an in-place topological sort on the list supplied.
*/
void sort_in_topological_order(struct commit_list ** list, int lifo)
{
- sort_in_topological_order_fn(list, lifo, topo_sort_default_setter,
- topo_sort_default_getter);
-}
-
-void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
- topo_sort_set_fn_t setter,
- topo_sort_get_fn_t getter)
-{
- struct commit_list * next = *list;
- struct commit_list * work = NULL, **insert;
- struct commit_list ** pptr = list;
- struct sort_node * nodes;
- struct sort_node * next_nodes;
- int count = 0;
-
- /* determine the size of the list */
- while (next) {
- next = next->next;
- count++;
- }
+ struct commit_list *next, *orig = *list;
+ struct commit_list *work, **insert;
+ struct commit_list **pptr;
- if (!count)
+ if (!orig)
return;
- /* allocate an array to help sort the list */
- nodes = xcalloc(count, sizeof(*nodes));
- /* link the list to the array */
- next_nodes = nodes;
- next=*list;
- while (next) {
- next_nodes->list_item = next;
- setter(next->item, next_nodes);
- next_nodes++;
- next = next->next;
+ *list = NULL;
+
+ /* Mark them and clear the indegree */
+ for (next = orig; next; next = next->next) {
+ struct commit *commit = next->item;
+ commit->object.flags |= TOPOSORT;
+ commit->indegree = 0;
}
+
/* update the indegree */
- next=*list;
- while (next) {
+ for (next = orig; next; next = next->next) {
struct commit_list * parents = next->item->parents;
while (parents) {
- struct commit * parent=parents->item;
- struct sort_node * pn = (struct sort_node *) getter(parent);
+ struct commit *parent = parents->item;
- if (pn)
- pn->indegree++;
- parents=parents->next;
+ if (parent->object.flags & TOPOSORT)
+ parent->indegree++;
+ parents = parents->next;
}
- next=next->next;
}
+
/*
* find the tips
*
@@ -1220,55 +468,56 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
*
* the tips serve as a starting set for the work queue.
*/
- next=*list;
+ work = NULL;
insert = &work;
- while (next) {
- struct sort_node * node = (struct sort_node *) getter(next->item);
+ for (next = orig; next; next = next->next) {
+ struct commit *commit = next->item;
- if (node->indegree == 0) {
- insert = &commit_list_insert(next->item, insert)->next;
- }
- next=next->next;
+ if (!commit->indegree)
+ insert = &commit_list_insert(commit, insert)->next;
}
/* process the list in topological order */
if (!lifo)
sort_by_date(&work);
+
+ pptr = list;
+ *list = NULL;
while (work) {
- struct commit * work_item = pop_commit(&work);
- struct sort_node * work_node = (struct sort_node *) getter(work_item);
- struct commit_list * parents = work_item->parents;
+ struct commit *commit;
+ struct commit_list *parents, *work_item;
- while (parents) {
- struct commit * parent=parents->item;
- struct sort_node * pn = (struct sort_node *) getter(parent);
-
- if (pn) {
- /*
- * parents are only enqueued for emission
- * when all their children have been emitted thereby
- * guaranteeing topological order.
- */
- pn->indegree--;
- if (!pn->indegree) {
- if (!lifo)
- insert_by_date(parent, &work);
- else
- commit_list_insert(parent, &work);
- }
+ work_item = work;
+ work = work_item->next;
+ work_item->next = NULL;
+
+ commit = work_item->item;
+ for (parents = commit->parents; parents ; parents = parents->next) {
+ struct commit *parent=parents->item;
+
+ if (!(parent->object.flags & TOPOSORT))
+ continue;
+
+ /*
+ * parents are only enqueued for emission
+ * when all their children have been emitted thereby
+ * guaranteeing topological order.
+ */
+ if (!--parent->indegree) {
+ if (!lifo)
+ insert_by_date(parent, &work);
+ else
+ commit_list_insert(parent, &work);
}
- parents=parents->next;
}
/*
* work_item is a commit all of whose children
* have already been emitted. we can emit it now.
*/
- *pptr = work_node->list_item;
- pptr = &(*pptr)->next;
- *pptr = NULL;
- setter(work_item, NULL);
+ commit->object.flags &= ~TOPOSORT;
+ *pptr = work_item;
+ pptr = &work_item->next;
}
- free(nodes);
}
/* merge-base stuff */
@@ -1304,8 +553,10 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
*/
return commit_list_insert(one, &result);
- parse_commit(one);
- parse_commit(two);
+ if (parse_commit(one))
+ return NULL;
+ if (parse_commit(two))
+ return NULL;
one->object.flags |= PARENT1;
two->object.flags |= PARENT2;
@@ -1338,7 +589,8 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
parents = parents->next;
if ((p->object.flags & flags) == flags)
continue;
- parse_commit(p);
+ if (parse_commit(p))
+ return NULL;
p->object.flags |= flags;
insert_by_date(p, &list);
}
diff --git a/commit.h b/commit.h
index b779de8cbc..10e2b5d4cf 100644
--- a/commit.h
+++ b/commit.h
@@ -14,6 +14,7 @@ struct commit_list {
struct commit {
struct object object;
void *util;
+ unsigned int indegree;
unsigned long date;
struct commit_list *parents;
struct tree *tree;
@@ -61,13 +62,15 @@ enum cmit_fmt {
CMIT_FMT_UNSPECIFIED,
};
+extern int non_ascii(int);
extern enum cmit_fmt get_commit_format(const char *arg);
extern void format_commit_message(const struct commit *commit,
const void *format, struct strbuf *sb);
extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
struct strbuf *,
int abbrev, const char *subject,
- const char *after_subject, enum date_mode);
+ const char *after_subject, enum date_mode,
+ int non_ascii_present);
/** Removes the first commit from a list sorted by date, and adds all
* of its parents.
@@ -82,31 +85,12 @@ void clear_commit_marks(struct commit *commit, unsigned int mark);
/*
* Performs an in-place topological sort of list supplied.
*
- * Pre-conditions for sort_in_topological_order:
- * all commits in input list and all parents of those
- * commits must have object.util == NULL
- *
- * Pre-conditions for sort_in_topological_order_fn:
- * all commits in input list and all parents of those
- * commits must have getter(commit) == NULL
- *
- * Post-conditions:
* invariant of resulting list is:
* a reachable from b => ord(b) < ord(a)
* in addition, when lifo == 0, commits on parallel tracks are
* sorted in the dates order.
*/
-
-typedef void (*topo_sort_set_fn_t)(struct commit*, void *data);
-typedef void* (*topo_sort_get_fn_t)(struct commit*);
-
-void topo_sort_default_setter(struct commit *c, void *data);
-void *topo_sort_default_getter(struct commit *c);
-
void sort_in_topological_order(struct commit_list ** list, int lifo);
-void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
- topo_sort_set_fn_t setter,
- topo_sort_get_fn_t getter);
struct commit_graft {
unsigned char sha1[20];
@@ -128,4 +112,13 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
int depth, int shallow_flag, int not_shallow_flag);
int in_merge_bases(struct commit *, struct commit **, int);
+
+extern int interactive_add(int argc, const char **argv, const char *prefix);
+extern int rerere(void);
+
+static inline int single_parent(struct commit *commit)
+{
+ return commit->parents && !commit->parents->next;
+}
+
#endif /* COMMIT_H */
diff --git a/compat/fopen.c b/compat/fopen.c
new file mode 100644
index 0000000000..ccb9e89fa4
--- /dev/null
+++ b/compat/fopen.c
@@ -0,0 +1,26 @@
+#include "../git-compat-util.h"
+#undef fopen
+FILE *git_fopen(const char *path, const char *mode)
+{
+ FILE *fp;
+ struct stat st;
+
+ if (mode[0] == 'w' || mode[0] == 'a')
+ return fopen(path, mode);
+
+ if (!(fp = fopen(path, mode)))
+ return NULL;
+
+ if (fstat(fileno(fp), &st)) {
+ fclose(fp);
+ return NULL;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fclose(fp);
+ errno = EISDIR;
+ return NULL;
+ }
+
+ return fp;
+}
diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c
index 4d7ab9d975..f44498258d 100644
--- a/compat/inet_ntop.c
+++ b/compat/inet_ntop.c
@@ -18,7 +18,6 @@
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
-#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
diff --git a/compat/inet_pton.c b/compat/inet_pton.c
index 5704e0d2b6..4078fc0877 100644
--- a/compat/inet_pton.c
+++ b/compat/inet_pton.c
@@ -18,7 +18,6 @@
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
-#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
diff --git a/compat/mkdtemp.c b/compat/mkdtemp.c
new file mode 100644
index 0000000000..34d4b49818
--- /dev/null
+++ b/compat/mkdtemp.c
@@ -0,0 +1,8 @@
+#include "../git-compat-util.h"
+
+char *gitmkdtemp(char *template)
+{
+ if (!mktemp(template) || mkdir(template, 0700))
+ return NULL;
+ return template;
+}
diff --git a/compat/qsort.c b/compat/qsort.c
new file mode 100644
index 0000000000..d93dce2cf8
--- /dev/null
+++ b/compat/qsort.c
@@ -0,0 +1,62 @@
+#include "../git-compat-util.h"
+
+/*
+ * A merge sort implementation, simplified from the qsort implementation
+ * by Mike Haertel, which is a part of the GNU C Library.
+ */
+
+static void msort_with_tmp(void *b, size_t n, size_t s,
+ int (*cmp)(const void *, const void *),
+ char *t)
+{
+ char *tmp;
+ char *b1, *b2;
+ size_t n1, n2;
+
+ if (n <= 1)
+ return;
+
+ n1 = n / 2;
+ n2 = n - n1;
+ b1 = b;
+ b2 = (char *)b + (n1 * s);
+
+ msort_with_tmp(b1, n1, s, cmp, t);
+ msort_with_tmp(b2, n2, s, cmp, t);
+
+ tmp = t;
+
+ while (n1 > 0 && n2 > 0) {
+ if (cmp(b1, b2) <= 0) {
+ memcpy(tmp, b1, s);
+ tmp += s;
+ b1 += s;
+ --n1;
+ } else {
+ memcpy(tmp, b2, s);
+ tmp += s;
+ b2 += s;
+ --n2;
+ }
+ }
+ if (n1 > 0)
+ memcpy(tmp, b1, n1 * s);
+ memcpy(b, t, (n - n2) * s);
+}
+
+void git_qsort(void *b, size_t n, size_t s,
+ int (*cmp)(const void *, const void *))
+{
+ const size_t size = n * s;
+ char buf[1024];
+
+ if (size < sizeof(buf)) {
+ /* The temporary array fits on the small on-stack buffer. */
+ msort_with_tmp(b, n, s, cmp, buf);
+ } else {
+ /* It's somewhat large, so malloc it. */
+ char *tmp = malloc(size);
+ msort_with_tmp(b, n, s, cmp, tmp);
+ free(tmp);
+ }
+}
diff --git a/config.c b/config.c
index dc3148d456..cba2bcfb67 100644
--- a/config.c
+++ b/config.c
@@ -6,12 +6,14 @@
*
*/
#include "cache.h"
+#include "exec_cmd.h"
#define MAXNAME (256)
static FILE *config_file;
static const char *config_file_name;
static int config_linenr;
+static int config_file_eof;
static int zlib_compression_seen;
static int get_next_char(void)
@@ -33,7 +35,7 @@ static int get_next_char(void)
if (c == '\n')
config_linenr++;
if (c == EOF) {
- config_file = NULL;
+ config_file_eof = 1;
c = '\n';
}
}
@@ -117,7 +119,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
/* Get the full name */
for (;;) {
c = get_next_char();
- if (c == EOF)
+ if (config_file_eof)
break;
if (!iskeychar(c))
break;
@@ -181,7 +183,7 @@ static int get_base_var(char *name)
for (;;) {
int c = get_next_char();
- if (c == EOF)
+ if (config_file_eof)
return -1;
if (c == ']')
return baselen;
@@ -204,8 +206,7 @@ static int git_parse_file(config_fn_t fn)
for (;;) {
int c = get_next_char();
if (c == '\n') {
- /* EOF? */
- if (!config_file)
+ if (config_file_eof)
return 0;
comment = 0;
continue;
@@ -233,17 +234,23 @@ static int git_parse_file(config_fn_t fn)
die("bad config file line %d in %s", config_linenr, config_file_name);
}
-static unsigned long get_unit_factor(const char *end)
+static int parse_unit_factor(const char *end, unsigned long *val)
{
if (!*end)
return 1;
- else if (!strcasecmp(end, "k"))
- return 1024;
- else if (!strcasecmp(end, "m"))
- return 1024 * 1024;
- else if (!strcasecmp(end, "g"))
- return 1024 * 1024 * 1024;
- die("unknown unit: '%s'", end);
+ else if (!strcasecmp(end, "k")) {
+ *val *= 1024;
+ return 1;
+ }
+ else if (!strcasecmp(end, "m")) {
+ *val *= 1024 * 1024;
+ return 1;
+ }
+ else if (!strcasecmp(end, "g")) {
+ *val *= 1024 * 1024 * 1024;
+ return 1;
+ }
+ return 0;
}
int git_parse_long(const char *value, long *ret)
@@ -251,7 +258,10 @@ int git_parse_long(const char *value, long *ret)
if (value && *value) {
char *end;
long val = strtol(value, &end, 0);
- *ret = val * get_unit_factor(end);
+ unsigned long factor = 1;
+ if (!parse_unit_factor(end, &factor))
+ return 0;
+ *ret = val * factor;
return 1;
}
return 0;
@@ -262,17 +272,26 @@ int git_parse_ulong(const char *value, unsigned long *ret)
if (value && *value) {
char *end;
unsigned long val = strtoul(value, &end, 0);
- *ret = val * get_unit_factor(end);
+ if (!parse_unit_factor(end, &val))
+ return 0;
+ *ret = val;
return 1;
}
return 0;
}
+static void die_bad_config(const char *name)
+{
+ if (config_file_name)
+ die("bad config value for '%s' in %s", name, config_file_name);
+ die("bad config value for '%s'", name);
+}
+
int git_config_int(const char *name, const char *value)
{
long ret;
if (!git_parse_long(value, &ret))
- die("bad config value for '%s' in %s", name, config_file_name);
+ die_bad_config(name);
return ret;
}
@@ -280,7 +299,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
{
unsigned long ret;
if (!git_parse_ulong(value, &ret))
- die("bad config value for '%s' in %s", name, config_file_name);
+ die_bad_config(name);
return ret;
}
@@ -297,6 +316,14 @@ int git_config_bool(const char *name, const char *value)
return git_config_int(name, value) != 0;
}
+int git_config_string(const char **dest, const char *var, const char *value)
+{
+ if (!value)
+ return config_error_nonbool(var);
+ *dest = xstrdup(value);
+ return 0;
+}
+
int git_default_config(const char *var, const char *value)
{
/* This needs a better name */
@@ -395,39 +422,53 @@ int git_default_config(const char *var, const char *value)
return 0;
}
+ if (!strcmp(var, "core.safecrlf")) {
+ if (value && !strcasecmp(value, "warn")) {
+ safe_crlf = SAFE_CRLF_WARN;
+ return 0;
+ }
+ safe_crlf = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "user.name")) {
+ if (!value)
+ return config_error_nonbool(var);
strlcpy(git_default_name, value, sizeof(git_default_name));
return 0;
}
if (!strcmp(var, "user.email")) {
+ if (!value)
+ return config_error_nonbool(var);
strlcpy(git_default_email, value, sizeof(git_default_email));
return 0;
}
- if (!strcmp(var, "i18n.commitencoding")) {
- git_commit_encoding = xstrdup(value);
- return 0;
- }
-
- if (!strcmp(var, "i18n.logoutputencoding")) {
- git_log_output_encoding = xstrdup(value);
- return 0;
- }
+ if (!strcmp(var, "i18n.commitencoding"))
+ return git_config_string(&git_commit_encoding, var, value);
+ if (!strcmp(var, "i18n.logoutputencoding"))
+ return git_config_string(&git_log_output_encoding, var, value);
if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
pager_use_color = git_config_bool(var,value);
return 0;
}
- if (!strcmp(var, "core.pager")) {
- pager_program = xstrdup(value);
- return 0;
- }
+ if (!strcmp(var, "core.pager"))
+ return git_config_string(&pager_program, var, value);
+
+ if (!strcmp(var, "core.editor"))
+ return git_config_string(&editor_program, var, value);
- if (!strcmp(var, "core.editor")) {
- editor_program = xstrdup(value);
+ if (!strcmp(var, "core.excludesfile"))
+ return git_config_string(&excludes_file, var, value);
+
+ if (!strcmp(var, "core.whitespace")) {
+ if (!value)
+ return config_error_nonbool(var);
+ whitespace_rule_cfg = parse_whitespace_rule(value);
return 0;
}
@@ -445,6 +486,7 @@ int git_config_from_file(config_fn_t fn, const char *filename)
config_file = f;
config_file_name = filename;
config_linenr = 1;
+ config_file_eof = 0;
ret = git_parse_file(fn);
fclose(f);
config_file_name = NULL;
@@ -452,6 +494,37 @@ int git_config_from_file(config_fn_t fn, const char *filename)
return ret;
}
+const char *git_etc_gitconfig(void)
+{
+ static const char *system_wide;
+ if (!system_wide) {
+ system_wide = ETC_GITCONFIG;
+ if (!is_absolute_path(system_wide)) {
+ /* interpret path relative to exec-dir */
+ struct strbuf d = STRBUF_INIT;
+ strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide);
+ system_wide = strbuf_detach(&d, NULL);
+ }
+ }
+ return system_wide;
+}
+
+int git_env_bool(const char *k, int def)
+{
+ const char *v = getenv(k);
+ return v ? git_config_bool(k, v) : def;
+}
+
+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(config_fn_t fn)
{
int ret = 0;
@@ -464,15 +537,15 @@ int git_config(config_fn_t fn)
* config file otherwise. */
filename = getenv(CONFIG_ENVIRONMENT);
if (!filename) {
- if (!access(ETC_GITCONFIG, R_OK))
- ret += git_config_from_file(fn, ETC_GITCONFIG);
+ if (git_config_system() && !access(git_etc_gitconfig(), R_OK))
+ ret += git_config_from_file(fn, git_etc_gitconfig());
home = getenv("HOME");
filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
if (!filename)
filename = repo_config = xstrdup(git_path("config"));
}
- if (home) {
+ if (git_config_global() && home) {
char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
if (!access(user_config, R_OK))
ret = git_config_from_file(fn, user_config);
@@ -582,86 +655,77 @@ static int write_error(void)
static int store_write_section(int fd, const char* key)
{
- const char *dot = strchr(key, '.');
- int len1 = store.baselen, len2 = -1;
+ const char *dot;
+ int i, success;
+ struct strbuf sb;
- dot = strchr(key, '.');
+ strbuf_init(&sb, 0);
+ dot = memchr(key, '.', store.baselen);
if (dot) {
- int dotlen = dot - key;
- if (dotlen < len1) {
- len2 = len1 - dotlen - 1;
- len1 = dotlen;
+ strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key);
+ for (i = dot - key + 1; i < store.baselen; i++) {
+ if (key[i] == '"')
+ strbuf_addch(&sb, '\\');
+ strbuf_addch(&sb, key[i]);
}
+ strbuf_addstr(&sb, "\"]\n");
+ } else {
+ strbuf_addf(&sb, "[%.*s]\n", store.baselen, key);
}
- if (write_in_full(fd, "[", 1) != 1 ||
- write_in_full(fd, key, len1) != len1)
- return 0;
- if (len2 >= 0) {
- if (write_in_full(fd, " \"", 2) != 2)
- return 0;
- while (--len2 >= 0) {
- unsigned char c = *++dot;
- if (c == '"')
- if (write_in_full(fd, "\\", 1) != 1)
- return 0;
- if (write_in_full(fd, &c, 1) != 1)
- return 0;
- }
- if (write_in_full(fd, "\"", 1) != 1)
- return 0;
- }
- if (write_in_full(fd, "]\n", 2) != 2)
- return 0;
+ success = write_in_full(fd, sb.buf, sb.len) == sb.len;
+ strbuf_release(&sb);
- return 1;
+ return success;
}
static int store_write_pair(int fd, const char* key, const char* value)
{
- int i;
- int length = strlen(key+store.baselen+1);
- int quote = 0;
+ int i, success;
+ int length = strlen(key + store.baselen + 1);
+ const char *quote = "";
+ struct strbuf sb;
- /* Check to see if the value needs to be quoted. */
+ /*
+ * Check to see if the value needs to be surrounded with a dq pair.
+ * Note that problematic characters are always backslash-quoted; this
+ * check is about not losing leading or trailing SP and strings that
+ * follow beginning-of-comment characters (i.e. ';' and '#') by the
+ * configuration parser.
+ */
if (value[0] == ' ')
- quote = 1;
+ quote = "\"";
for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#')
- quote = 1;
- if (value[i-1] == ' ')
- quote = 1;
+ quote = "\"";
+ if (i && value[i - 1] == ' ')
+ quote = "\"";
+
+ strbuf_init(&sb, 0);
+ strbuf_addf(&sb, "\t%.*s = %s",
+ length, key + store.baselen + 1, quote);
- if (write_in_full(fd, "\t", 1) != 1 ||
- write_in_full(fd, key+store.baselen+1, length) != length ||
- write_in_full(fd, " = ", 3) != 3)
- return 0;
- if (quote && write_in_full(fd, "\"", 1) != 1)
- return 0;
for (i = 0; value[i]; i++)
switch (value[i]) {
case '\n':
- if (write_in_full(fd, "\\n", 2) != 2)
- return 0;
+ strbuf_addstr(&sb, "\\n");
break;
case '\t':
- if (write_in_full(fd, "\\t", 2) != 2)
- return 0;
+ strbuf_addstr(&sb, "\\t");
break;
case '"':
case '\\':
- if (write_in_full(fd, "\\", 1) != 1)
- return 0;
+ strbuf_addch(&sb, '\\');
default:
- if (write_in_full(fd, value+i, 1) != 1)
- return 0;
+ strbuf_addch(&sb, value[i]);
break;
}
- if (quote && write_in_full(fd, "\"", 1) != 1)
- return 0;
- if (write_in_full(fd, "\n", 1) != 1)
- return 0;
- return 1;
+ strbuf_addf(&sb, "%s\n", quote);
+
+ success = write_in_full(fd, sb.buf, sb.len) == sb.len;
+ strbuf_release(&sb);
+
+ return success;
}
static ssize_t find_beginning_of_line(const char* contents, size_t size,
@@ -670,12 +734,17 @@ static ssize_t find_beginning_of_line(const char* contents, size_t size,
size_t equal_offset = size, bracket_offset = size;
ssize_t offset;
+contline:
for (offset = offset_-2; offset > 0
&& contents[offset] != '\n'; offset--)
switch (contents[offset]) {
case '=': equal_offset = offset; break;
case ']': bracket_offset = offset; break;
}
+ if (offset > 0 && contents[offset-1] == '\\') {
+ offset_ = offset;
+ goto contline;
+ }
if (bracket_offset < equal_offset) {
*found_bracket = 1;
offset = bracket_offset+1;
@@ -888,6 +957,9 @@ int git_config_set_multivar(const char* key, const char* value,
contents, contents_sz,
store.offset[i]-2, &new_line);
+ if (copy_end > 0 && contents[copy_end-1] != '\n')
+ new_line = 1;
+
/* write the first part of the config */
if (copy_end > copy_begin) {
if (write_in_full(fd, contents + copy_begin,
@@ -921,14 +993,12 @@ int git_config_set_multivar(const char* key, const char* value,
munmap(contents, contents_sz);
}
- if (close(fd) || commit_lock_file(lock) < 0) {
+ if (commit_lock_file(lock) < 0) {
fprintf(stderr, "Cannot commit config file!\n");
ret = 4;
goto out_free;
}
- /* fd is closed, so don't try to close it below. */
- fd = -1;
/*
* lock is committed, so don't try to roll it back below.
* NOTE: Since lockfile.c keeps a linked list of all created
@@ -939,8 +1009,6 @@ int git_config_set_multivar(const char* key, const char* value,
ret = 0;
out_free:
- if (0 <= fd)
- close(fd);
if (lock)
rollback_lock_file(lock);
free(config_filename);
@@ -1038,9 +1106,18 @@ int git_config_rename_section(const char *old_name, const char *new_name)
}
fclose(config_file);
unlock_and_out:
- if (close(out_fd) || commit_lock_file(lock) < 0)
+ if (commit_lock_file(lock) < 0)
ret = error("Cannot commit config file!");
out:
free(config_filename);
return ret;
}
+
+/*
+ * Call this to report error for your variable that should not
+ * get a boolean value (i.e. "[my] var" means "true").
+ */
+int config_error_nonbool(const char *var)
+{
+ return error("Missing value for '%s'", var);
+}
diff --git a/config.mak.in b/config.mak.in
index a3032e389f..ee6c33df03 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -23,18 +23,26 @@ VPATH = @srcdir@
export exec_prefix mandir
export srcdir VPATH
+ASCIIDOC8=@ASCIIDOC8@
NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
NO_OPENSSL=@NO_OPENSSL@
NO_CURL=@NO_CURL@
NO_EXPAT=@NO_EXPAT@
NEEDS_LIBICONV=@NEEDS_LIBICONV@
NEEDS_SOCKET=@NEEDS_SOCKET@
+NO_SYS_SELECT_H=@NO_SYS_SELECT_H@
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_STRCASESTR=@NO_STRCASESTR@
+NO_MEMMEM=@NO_MEMMEM@
NO_STRLCPY=@NO_STRLCPY@
+NO_STRTOUMAX=@NO_STRTOUMAX@
NO_SETENV=@NO_SETENV@
+NO_UNSETENV=@NO_UNSETENV@
+NO_MKDTEMP=@NO_MKDTEMP@
NO_ICONV=@NO_ICONV@
+OLD_ICONV=@OLD_ICONV@
+NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
diff --git a/configure.ac b/configure.ac
index ed7cc895d2..85d7ef570d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -73,7 +73,7 @@ fi \
AC_ARG_WITH([lib],
[AS_HELP_STRING([--with-lib=ARG],
[ARG specifies alternative name for lib directory])],
- [if test "$withval" = "no" -o "$withval" = "yes"; then \
+ [if test "$withval" = "no" || test "$withval" = "yes"; then \
AC_MSG_WARN([You should provide name for --with-lib=ARG]); \
else \
GIT_CONF_APPEND_LINE(lib=$withval); \
@@ -122,6 +122,27 @@ if test -z "$NO_TCLTK"; then
AC_SUBST(TCLTK_PATH)
fi
fi
+AC_CHECK_PROGS(ASCIIDOC, [asciidoc])
+if test -n "$ASCIIDOC"; then
+ AC_MSG_CHECKING([for asciidoc version])
+ asciidoc_version=`$ASCIIDOC --version 2>&1`
+ case "${asciidoc_version}" in
+ asciidoc' '8*)
+ ASCIIDOC8=YesPlease
+ AC_MSG_RESULT([${asciidoc_version} > 7])
+ ;;
+ asciidoc' '7*)
+ ASCIIDOC8=
+ AC_MSG_RESULT([${asciidoc_version}])
+ ;;
+ *)
+ ASCIIDOC8=
+ AC_MSG_RESULT([${asciidoc_version} (unknown)])
+ ;;
+ esac
+fi
+AC_SUBST(ASCIIDOC8)
+
## Checks for libraries.
AC_MSG_NOTICE([CHECKS for libraries])
@@ -182,6 +203,26 @@ AC_SUBST(NEEDS_LIBICONV)
AC_SUBST(NO_ICONV)
test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv"
#
+# Define NO_DEFLATE_BOUND if deflateBound is missing from zlib.
+AC_DEFUN([ZLIBTEST_SRC], [
+#include <zlib.h>
+
+int main(void)
+{
+ deflateBound(0, 0);
+ return 0;
+}
+])
+AC_MSG_CHECKING([for deflateBound in -lz])
+old_LIBS="$LIBS"
+LIBS="$LIBS -lz"
+AC_LINK_IFELSE(ZLIBTEST_SRC,
+ [AC_MSG_RESULT([yes])],
+ [AC_MSG_RESULT([no])
+ NO_DEFLATE_BOUND=yes])
+LIBS="$old_LIBS"
+AC_SUBST(NO_DEFLATE_BOUND)
+#
# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
# Patrick Mauritz).
AC_CHECK_LIB([c], [socket],
@@ -192,6 +233,34 @@ test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket"
## Checks for header files.
+AC_MSG_NOTICE([CHECKS for header files])
+#
+# Define NO_SYS_SELECT_H if you don't have sys/select.h.
+AC_CHECK_HEADER([sys/select.h],
+[NO_SYS_SELECT_H=],
+[NO_SYS_SELECT_H=UnfortunatelyYes])
+AC_SUBST(NO_SYS_SELECT_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], [[
+#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;
+}
+]])
+AC_MSG_CHECKING([for old iconv()])
+AC_COMPILE_IFELSE(OLDICONVTEST_SRC,
+ [AC_MSG_RESULT([no])],
+ [AC_MSG_RESULT([yes])
+ OLD_ICONV=UnfortunatelyYes])
+AC_SUBST(OLD_ICONV)
## Checks for typedefs, structures, and compiler characteristics.
@@ -245,9 +314,9 @@ 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)
- exit(1);
+ return 1;
else if (strcmp(buf, "12345"))
- exit(2);]])],
+ return 2;]])],
[ac_cv_c_c99_format=yes],
[ac_cv_c_c99_format=no])
])
@@ -269,18 +338,42 @@ AC_CHECK_FUNC(strcasestr,
[NO_STRCASESTR=YesPlease])
AC_SUBST(NO_STRCASESTR)
#
+# Define NO_MEMMEM if you don't have memmem.
+AC_CHECK_FUNC(memmem,
+[NO_MEMMEM=],
+[NO_MEMMEM=YesPlease])
+AC_SUBST(NO_MEMMEM)
+#
# Define NO_STRLCPY if you don't have strlcpy.
AC_CHECK_FUNC(strlcpy,
[NO_STRLCPY=],
[NO_STRLCPY=YesPlease])
AC_SUBST(NO_STRLCPY)
#
+# Define NO_STRTOUMAX if you don't have strtoumax in the C library.
+AC_CHECK_FUNC(strtoumax,
+[NO_STRTOUMAX=],
+[NO_STRTOUMAX=YesPlease])
+AC_SUBST(NO_STRTOUMAX)
+#
# Define NO_SETENV if you don't have setenv in the C library.
AC_CHECK_FUNC(setenv,
[NO_SETENV=],
[NO_SETENV=YesPlease])
AC_SUBST(NO_SETENV)
#
+# Define NO_UNSETENV if you don't have unsetenv in the C library.
+AC_CHECK_FUNC(unsetenv,
+[NO_UNSETENV=],
+[NO_UNSETENV=YesPlease])
+AC_SUBST(NO_UNSETENV)
+#
+# Define NO_MKDTEMP if you don't have mkdtemp in the C library.
+AC_CHECK_FUNC(mkdtemp,
+[NO_MKDTEMP=],
+[NO_MKDTEMP=YesPlease])
+AC_SUBST(NO_MKDTEMP)
+#
# Define NO_MMAP if you want to avoid mmap.
#
# Define NO_ICONV if your libc does not properly support iconv.
@@ -377,7 +470,7 @@ GIT_PARSE_WITH(iconv))
# times (my ext3 doesn't).
#
# Define USE_STDEV below if you want git to care about the underlying device
-# change being considered an inode change from the update-cache perspective.
+# change being considered an inode change from the update-index perspective.
## Output files
diff --git a/connect.c b/connect.c
index 06d279e37c..5ac3572784 100644
--- a/connect.c
+++ b/connect.c
@@ -36,6 +36,11 @@ static int check_ref(const char *name, int len, unsigned int flags)
return !(flags & ~REF_NORMAL);
}
+int check_ref_type(const struct ref *ref, int flags)
+{
+ return check_ref(ref->name, strlen(ref->name), flags);
+}
+
/*
* Read all the refs from the other end
*/
@@ -72,9 +77,9 @@ struct ref **get_remote_heads(int in, struct ref **list,
continue;
if (nr_match && !path_match(name, nr_match, match))
continue;
- ref = alloc_ref(len - 40);
+ ref = alloc_ref(name_len + 1);
hashcpy(ref->old_sha1, old_sha1);
- memcpy(ref->name, buffer + 41, len - 40);
+ memcpy(ref->name, buffer + 41, name_len + 1);
*list = ref;
list = &ref->next;
}
@@ -365,6 +370,8 @@ static int git_proxy_command_options(const char *var, const char *value)
if (git_proxy_command)
return 0;
+ if (!value)
+ return config_error_nonbool(var);
/* [core]
* ;# matches www.kernel.org as well
* gitproxy = netcatter-1 for kernel.org
@@ -467,25 +474,32 @@ char *get_port(char *host)
return NULL;
}
+static struct child_process no_fork;
+
/*
- * This returns 0 if the transport protocol does not need fork(2),
- * or a process id if it does. Once done, finish the connection
- * with finish_connect() with the value returned from this function
- * (it is safe to call finish_connect() with 0 to support the former
- * case).
+ * This returns a dummy child_process if the transport protocol does not
+ * need fork(2), or a struct child_process object if it does. Once done,
+ * finish the connection with finish_connect() with the value returned from
+ * this function (it is safe to call finish_connect() with NULL to support
+ * the former case).
*
- * Does not return a negative value on error; it just dies.
+ * If it returns, the connect is successful; it just dies on errors (this
+ * will hopefully be changed in a libification effort, to return NULL when
+ * the connection failed).
*/
-pid_t git_connect(int fd[2], char *url, const char *prog, int flags)
+struct child_process *git_connect(int fd[2], const char *url_orig,
+ const char *prog, int flags)
{
+ char *url = xstrdup(url_orig);
char *host, *path = url;
char *end;
int c;
- int pipefd[2][2];
- pid_t pid;
+ struct child_process *conn;
enum protocol protocol = PROTO_LOCAL;
int free_path = 0;
char *port = NULL;
+ const char **arg;
+ struct strbuf cmd;
/* Without this we cannot rely on waitpid() to tell
* what happened to our children.
@@ -566,76 +580,72 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags)
prog, path, 0,
target_host, 0);
free(target_host);
+ free(url);
if (free_path)
free(path);
- return 0;
+ return &no_fork;
}
- if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
- die("unable to create pipe pair for communication");
- pid = fork();
- if (pid < 0)
- die("unable to fork");
- if (!pid) {
- struct strbuf cmd;
-
- strbuf_init(&cmd, MAX_CMD_LEN);
- strbuf_addstr(&cmd, prog);
- strbuf_addch(&cmd, ' ');
- sq_quote_buf(&cmd, path);
- if (cmd.len >= MAX_CMD_LEN)
- die("command line too long");
-
- dup2(pipefd[1][0], 0);
- dup2(pipefd[0][1], 1);
- close(pipefd[0][0]);
- close(pipefd[0][1]);
- close(pipefd[1][0]);
- close(pipefd[1][1]);
- if (protocol == PROTO_SSH) {
- const char *ssh, *ssh_basename;
- ssh = getenv("GIT_SSH");
- if (!ssh) ssh = "ssh";
- ssh_basename = strrchr(ssh, '/');
- if (!ssh_basename)
- ssh_basename = ssh;
- else
- ssh_basename++;
-
- if (!port)
- execlp(ssh, ssh_basename, host, cmd.buf, NULL);
- else
- execlp(ssh, ssh_basename, "-p", port, host,
- cmd.buf, NULL);
+ conn = xcalloc(1, sizeof(*conn));
+
+ strbuf_init(&cmd, MAX_CMD_LEN);
+ strbuf_addstr(&cmd, prog);
+ strbuf_addch(&cmd, ' ');
+ sq_quote_buf(&cmd, path);
+ if (cmd.len >= MAX_CMD_LEN)
+ die("command line too long");
+
+ conn->in = conn->out = -1;
+ conn->argv = arg = xcalloc(6, sizeof(*arg));
+ if (protocol == PROTO_SSH) {
+ const char *ssh = getenv("GIT_SSH");
+ if (!ssh) ssh = "ssh";
+
+ *arg++ = ssh;
+ if (port) {
+ *arg++ = "-p";
+ *arg++ = port;
}
- else {
- unsetenv(ALTERNATE_DB_ENVIRONMENT);
- unsetenv(DB_ENVIRONMENT);
- unsetenv(GIT_DIR_ENVIRONMENT);
- unsetenv(GIT_WORK_TREE_ENVIRONMENT);
- unsetenv(GRAFT_ENVIRONMENT);
- unsetenv(INDEX_ENVIRONMENT);
- execlp("sh", "sh", "-c", cmd.buf, NULL);
- }
- die("exec failed");
+ *arg++ = host;
}
- fd[0] = pipefd[0][0];
- fd[1] = pipefd[1][1];
- close(pipefd[0][1]);
- close(pipefd[1][0]);
+ else {
+ /* remove these from the environment */
+ const char *env[] = {
+ ALTERNATE_DB_ENVIRONMENT,
+ DB_ENVIRONMENT,
+ GIT_DIR_ENVIRONMENT,
+ GIT_WORK_TREE_ENVIRONMENT,
+ GRAFT_ENVIRONMENT,
+ INDEX_ENVIRONMENT,
+ NULL
+ };
+ conn->env = env;
+ *arg++ = "sh";
+ *arg++ = "-c";
+ }
+ *arg++ = cmd.buf;
+ *arg = NULL;
+
+ if (start_command(conn))
+ die("unable to fork");
+
+ fd[0] = conn->out; /* read from child's stdout */
+ fd[1] = conn->in; /* write to child's stdin */
+ strbuf_release(&cmd);
+ free(url);
if (free_path)
free(path);
- return pid;
+ return conn;
}
-int finish_connect(pid_t pid)
+int finish_connect(struct child_process *conn)
{
- if (pid == 0)
+ int code;
+ if (!conn || conn == &no_fork)
return 0;
- while (waitpid(pid, NULL, 0) < 0) {
- if (errno != EINTR)
- return -1;
- }
- return 0;
+ code = finish_command(conn);
+ free(conn->argv);
+ free(conn);
+ return code;
}
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e760930740..4ea727b143 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -64,12 +64,41 @@ __gitdir ()
__git_ps1 ()
{
- local b="$(git symbolic-ref HEAD 2>/dev/null)"
- if [ -n "$b" ]; then
+ local g="$(git rev-parse --git-dir 2>/dev/null)"
+ if [ -n "$g" ]; then
+ local r
+ local b
+ if [ -d "$g/../.dotest" ]
+ then
+ r="|AM/REBASE"
+ b="$(git symbolic-ref HEAD 2>/dev/null)"
+ elif [ -f "$g/.dotest-merge/interactive" ]
+ then
+ r="|REBASE-i"
+ b="$(cat $g/.dotest-merge/head-name)"
+ elif [ -d "$g/.dotest-merge" ]
+ then
+ r="|REBASE-m"
+ b="$(cat $g/.dotest-merge/head-name)"
+ elif [ -f "$g/MERGE_HEAD" ]
+ then
+ r="|MERGING"
+ b="$(git symbolic-ref HEAD 2>/dev/null)"
+ else
+ if [ -f $g/BISECT_LOG ]
+ then
+ r="|BISECTING"
+ fi
+ if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
+ then
+ b="$(cut -c1-7 $g/HEAD)..."
+ fi
+ fi
+
if [ -n "$1" ]; then
- printf "$1" "${b##refs/heads/}"
+ printf "$1" "${b##refs/heads/}$r"
else
- printf " (%s)" "${b##refs/heads/}"
+ printf " (%s)" "${b##refs/heads/}$r"
fi
fi
}
@@ -291,7 +320,7 @@ __git_commands ()
for i in $(git help -a|egrep '^ ')
do
case $i in
- add--interactive) : plumbing;;
+ *--*) : helper pattern;;
applymbox) : ask gittus;;
applypatch) : ask gittus;;
archimport) : import;;
@@ -308,7 +337,6 @@ __git_commands ()
diff-tree) : plumbing;;
fast-import) : import;;
fsck-objects) : plumbing;;
- fetch--tool) : plumbing;;
fetch-pack) : plumbing;;
fmt-merge-msg) : plumbing;;
for-each-ref) : plumbing;;
@@ -334,7 +362,7 @@ __git_commands ()
read-tree) : plumbing;;
receive-pack) : plumbing;;
reflog) : plumbing;;
- repo-config) : plumbing;;
+ repo-config) : deprecated;;
rerere) : plumbing;;
rev-list) : plumbing;;
rev-parse) : plumbing;;
@@ -346,7 +374,6 @@ __git_commands ()
ssh-*) : transport;;
stripspace) : plumbing;;
svn) : import export;;
- svnimport) : import;;
symbolic-ref) : plumbing;;
tar-tree) : deprecated;;
unpack-file) : plumbing;;
@@ -552,6 +579,20 @@ _git_describe ()
_git_diff ()
{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ --*)
+ __gitcomp "--cached --stat --numstat --shortstat --summary
+ --patch-with-stat --name-only --name-status --color
+ --no-color --color-words --no-renames --check
+ --full-index --binary --abbrev --diff-filter
+ --find-copies-harder --pickaxe-all --pickaxe-regex
+ --text --ignore-space-at-eol --ignore-space-change
+ --ignore-all-space --exit-code --quiet --ext-diff
+ --no-ext-diff"
+ return
+ ;;
+ esac
__git_complete_file
}
@@ -958,18 +999,18 @@ _git_remote ()
while [ $c -lt $COMP_CWORD ]; do
i="${COMP_WORDS[c]}"
case "$i" in
- add|show|prune|update) command="$i"; break ;;
+ add|rm|show|prune|update) command="$i"; break ;;
esac
c=$((++c))
done
if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
- __gitcomp "add show prune update"
+ __gitcomp "add rm show prune update"
return
fi
case "$command" in
- show|prune)
+ rm|show|prune)
__gitcomp "$(__git_remotes)"
;;
update)
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
index bb671d561e..9f92cd250b 100644
--- a/contrib/emacs/git-blame.el
+++ b/contrib/emacs/git-blame.el
@@ -105,6 +105,13 @@ selected element from l."
(setq ,l (remove e ,l))
e))
+(defvar git-blame-log-oneline-format
+ "format:[%cr] %cn: %s"
+ "*Formatting option used for describing current line in the minibuffer.
+
+This option is used to pass to git log --pretty= command-line option,
+and describe which commit the current line was made.")
+
(defvar git-blame-dark-colors
(git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c")
"*List of colors (format #RGB) to use in a dark environment.
@@ -371,7 +378,8 @@ See also function `git-blame-mode'."
(defun git-describe-commit (hash)
(with-temp-buffer
(call-process "git" nil t nil
- "log" "-1" "--pretty=oneline"
+ "log" "-1"
+ (concat "--pretty=" git-blame-log-oneline-format)
hash)
(buffer-substring (point-min) (1- (point-max)))))
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 4286d160a0..f69b697f8d 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -35,7 +35,6 @@
;;
;; TODO
;; - portability to XEmacs
-;; - better handling of subprocess errors
;; - diff against other branch
;; - renaming files from the status buffer
;; - creating tags
@@ -49,6 +48,7 @@
(eval-when-compile (require 'cl))
(require 'ewoc)
(require 'log-edit)
+(require 'easymenu)
;;;; Customizations
@@ -185,14 +185,25 @@ if there is already one that displays the same directory."
(defun git-call-process-env (buffer env &rest args)
"Wrapper for call-process that sets environment strings."
- (if env
- (apply #'call-process "env" nil buffer nil
- (append (git-get-env-strings env) (list "git") args))
+ (let ((process-environment (append (git-get-env-strings env)
+ process-environment)))
(apply #'call-process "git" nil buffer nil args)))
+(defun git-call-process-display-error (&rest args)
+ "Wrapper for call-process that displays error messages."
+ (let* ((dir default-directory)
+ (buffer (get-buffer-create "*Git Command Output*"))
+ (ok (with-current-buffer buffer
+ (let ((default-directory dir)
+ (buffer-read-only nil))
+ (erase-buffer)
+ (eq 0 (apply 'call-process "git" nil (list buffer t) nil args))))))
+ (unless ok (display-message-or-buffer buffer))
+ ok))
+
(defun git-call-process-env-string (env &rest args)
"Wrapper for call-process that sets environment strings,
-and returns the process output as a string."
+and returns the process output as a string, or nil if the git failed."
(with-temp-buffer
(and (eq 0 (apply #' git-call-process-env t env args))
(buffer-string))))
@@ -376,7 +387,7 @@ and returns the process output as a string."
(when reason
(push reason args)
(push "-m" args))
- (eq 0 (apply #'git-call-process-env nil nil "update-ref" args))))
+ (apply 'git-call-process-display-error "update-ref" args)))
(defun git-read-tree (tree &optional index-file)
"Read a tree into the index file."
@@ -488,8 +499,7 @@ and returns the process output as a string."
"Set the state of a file info."
(unless (eq (git-fileinfo->state info) state)
(setf (git-fileinfo->state info) state
- (git-fileinfo->old-perm info) 0
- (git-fileinfo->new-perm info) 0
+ (git-fileinfo->new-perm info) (git-fileinfo->old-perm info)
(git-fileinfo->rename-state info) nil
(git-fileinfo->orig-name info) nil
(git-fileinfo->needs-refresh info) t)))
@@ -523,6 +533,7 @@ and returns the process output as a string."
(?A 'added)
(?D 'deleted)
(?U 'unmerged)
+ (?T 'modified)
(t nil)))
(defun git-status-code-as-string (code)
@@ -537,6 +548,36 @@ and returns the process output as a string."
('ignored (propertize "Ignored " 'face 'git-ignored-face))
(t "? ")))
+(defun git-file-type-as-string (old-perm new-perm)
+ "Return a string describing the file type based on its permissions."
+ (let* ((old-type (lsh (or old-perm 0) -9))
+ (new-type (lsh (or new-perm 0) -9))
+ (str (case new-type
+ (?\100 ;; file
+ (case old-type
+ (?\100 nil)
+ (?\120 " (type change symlink -> file)")
+ (?\160 " (type change subproject -> file)")))
+ (?\120 ;; symlink
+ (case old-type
+ (?\100 " (type change file -> symlink)")
+ (?\160 " (type change subproject -> symlink)")
+ (t " (symlink)")))
+ (?\160 ;; subproject
+ (case old-type
+ (?\100 " (type change file -> subproject)")
+ (?\120 " (type change symlink -> subproject)")
+ (t " (subproject)")))
+ (?\110 nil) ;; directory (internal, not a real git state)
+ (?\000 ;; deleted or unknown
+ (case old-type
+ (?\120 " (symlink)")
+ (?\160 " (subproject)")))
+ (t (format " (unknown type %o)" new-type)))))
+ (cond (str (propertize str 'face 'git-status-face))
+ ((eq new-type ?\110) "/")
+ (t ""))))
+
(defun git-rename-as-string (info)
"Return a string describing the copy or rename associated with INFO, or an empty string if none."
(let ((state (git-fileinfo->rename-state info)))
@@ -562,11 +603,14 @@ and returns the process output as a string."
(defun git-fileinfo-prettyprint (info)
"Pretty-printer for the git-fileinfo structure."
- (insert (concat " " (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ")
- " " (git-status-code-as-string (git-fileinfo->state info))
- " " (git-permissions-as-string (git-fileinfo->old-perm info) (git-fileinfo->new-perm info))
- " " (git-escape-file-name (git-fileinfo->name info))
- (git-rename-as-string info))))
+ (let ((old-perm (git-fileinfo->old-perm info))
+ (new-perm (git-fileinfo->new-perm info)))
+ (insert (concat " " (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ")
+ " " (git-status-code-as-string (git-fileinfo->state info))
+ " " (git-permissions-as-string old-perm new-perm)
+ " " (git-escape-file-name (git-fileinfo->name info))
+ (git-file-type-as-string old-perm new-perm)
+ (git-rename-as-string info)))))
(defun git-insert-info-list (status infolist)
"Insert a list of file infos in the status buffer, replacing existing ones if any."
@@ -577,9 +621,8 @@ and returns the process output as a string."
(let ((info (pop infolist))
(node (ewoc-nth status 0)))
(while info
- (setf (git-fileinfo->needs-refresh info) t)
(cond ((not node)
- (ewoc-enter-last status info)
+ (setq node (ewoc-enter-last status info))
(setq info (pop infolist)))
((string-lessp (git-fileinfo->name (ewoc-data node))
(git-fileinfo->name info))
@@ -588,21 +631,23 @@ and returns the process output as a string."
(git-fileinfo->name info))
;; preserve the marked flag
(setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node)))
+ (setf (git-fileinfo->needs-refresh info) t)
(setf (ewoc-data node) info)
(setq info (pop infolist)))
(t
- (ewoc-enter-before status node info)
+ (setq node (ewoc-enter-before status node info))
(setq info (pop infolist)))))))
(defun git-run-diff-index (status files)
"Run git-diff-index on FILES and parse the results into STATUS.
Return the list of files that haven't been handled."
- (let (infolist)
+ (let ((remaining (copy-sequence files))
+ infolist)
(with-temp-buffer
(apply #'git-call-process-env t nil "diff-index" "-z" "-M" "HEAD" "--" files)
(goto-char (point-min))
(while (re-search-forward
- ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
+ ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMUT]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
nil t 1)
(let ((old-perm (string-to-number (match-string 1) 8))
(new-perm (string-to-number (match-string 2) 8))
@@ -615,10 +660,10 @@ Return the list of files that haven't been handled."
(push (git-create-fileinfo 'deleted name 0 0 'rename new-name) infolist)
(push (git-create-fileinfo 'added new-name old-perm new-perm 'rename name) infolist))
(push (git-create-fileinfo (git-state-code state) name old-perm new-perm) infolist))
- (setq files (delete name files))
- (when new-name (setq files (delete new-name files))))))
+ (setq remaining (delete name remaining))
+ (when new-name (setq remaining (delete new-name remaining))))))
(git-insert-info-list status infolist)
- files))
+ remaining))
(defun git-find-status-file (status file)
"Find a given file in the status ewoc and return its node."
@@ -634,13 +679,32 @@ Return the list of files that haven't been handled."
(with-temp-buffer
(apply #'git-call-process-env t nil "ls-files" "-z" (append options (list "--") files))
(goto-char (point-min))
- (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
+ (while (re-search-forward "\\([^\0]*?\\)\\(/?\\)\0" nil t 1)
(let ((name (match-string 1)))
- (push (git-create-fileinfo default-state name) infolist)
+ (push (git-create-fileinfo default-state name 0
+ (if (string-equal "/" (match-string 2)) (lsh ?\110 9) 0))
+ infolist)
(setq files (delete name files)))))
(git-insert-info-list status infolist)
files))
+(defun git-run-ls-files-cached (status files default-state)
+ "Run git-ls-files -c on FILES and parse the results into STATUS.
+Return the list of files that haven't been handled."
+ (let ((remaining (copy-sequence files))
+ infolist)
+ (with-temp-buffer
+ (apply #'git-call-process-env t nil "ls-files" "-z" "-s" "-c" "--" files)
+ (goto-char (point-min))
+ (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
+ (let* ((new-perm (string-to-number (match-string 1) 8))
+ (old-perm (if (eq default-state 'added) 0 new-perm))
+ (name (match-string 2)))
+ (push (git-create-fileinfo default-state name old-perm new-perm) infolist)
+ (setq remaining (delete name remaining)))))
+ (git-insert-info-list status infolist)
+ remaining))
+
(defun git-run-ls-unmerged (status files)
"Run git-ls-files -u on FILES and parse the results into STATUS."
(with-temp-buffer
@@ -664,18 +728,18 @@ Return the list of files that haven't been handled."
(defun git-run-ls-files-with-excludes (status files default-state &rest options)
"Run git-ls-files on FILES with appropriate --exclude-from options."
(let ((exclude-files (git-get-exclude-files)))
- (apply #'git-run-ls-files status files default-state
+ (apply #'git-run-ls-files status files default-state "--directory"
(concat "--exclude-per-directory=" git-per-dir-ignore-file)
(append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
(defun git-update-status-files (files &optional default-state)
"Update the status of FILES from the index."
(unless git-status (error "Not in git-status buffer."))
- (unless files
- (when git-show-uptodate (git-run-ls-files git-status nil 'uptodate "-c")))
+ (when (or git-show-uptodate files)
+ (git-run-ls-files-cached git-status files 'uptodate))
(let* ((remaining-files
(if (git-empty-db-p) ; we need some special handling for an empty db
- (git-run-ls-files git-status files 'added "-c")
+ (git-run-ls-files-cached git-status files 'added)
(git-run-diff-index git-status files))))
(git-run-ls-unmerged git-status files)
(when (or remaining-files (and git-show-unknown (not files)))
@@ -686,6 +750,27 @@ Return the list of files that haven't been handled."
(git-refresh-files)
(git-refresh-ewoc-hf git-status)))
+(defun git-mark-files (status files)
+ "Mark all the specified FILES, and unmark the others."
+ (setq files (sort files #'string-lessp))
+ (let ((file (and files (pop files)))
+ (node (ewoc-nth status 0)))
+ (while node
+ (let ((info (ewoc-data node)))
+ (if (and file (string-equal (git-fileinfo->name info) file))
+ (progn
+ (unless (git-fileinfo->marked info)
+ (setf (git-fileinfo->marked info) t)
+ (setf (git-fileinfo->needs-refresh info) t))
+ (setq file (pop files))
+ (setq node (ewoc-next status node)))
+ (when (git-fileinfo->marked info)
+ (setf (git-fileinfo->marked info) nil)
+ (setf (git-fileinfo->needs-refresh info) t))
+ (if (and file (string-lessp file (git-fileinfo->name info)))
+ (setq file (pop files))
+ (setq node (ewoc-next status node))))))))
+
(defun git-marked-files ()
"Return a list of all marked files, or if none a list containing just the file at cursor position."
(unless git-status (error "Not in git-status buffer."))
@@ -791,15 +876,17 @@ Return the list of files that haven't been handled."
(if (or (not (string-equal tree head-tree))
(yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
(let ((commit (git-commit-tree buffer tree head)))
- (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
- (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
- (with-current-buffer buffer (erase-buffer))
- (dolist (info files) (git-set-fileinfo-state info 'uptodate))
- (git-call-process-env nil nil "rerere")
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)
- (message "Committed %s." commit)
- (git-run-hook "post-commit" nil))
+ (when commit
+ (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
+ (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
+ (with-current-buffer buffer (erase-buffer))
+ (git-update-status-files (git-get-filenames files) 'uptodate)
+ (git-call-process-env nil nil "rerere")
+ (git-call-process-env nil nil "gc" "--auto")
+ (git-refresh-files)
+ (git-refresh-ewoc-hf git-status)
+ (message "Committed %s." commit)
+ (git-run-hook "post-commit" nil)))
(message "Commit aborted."))))
(message "No files to commit.")))
(delete-file index-file))))))
@@ -842,7 +929,8 @@ Return the list of files that haven't been handled."
"Mark all files."
(interactive)
(unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) t) t) git-status)
+ (ewoc-map (lambda (info) (unless (git-fileinfo->marked info)
+ (setf (git-fileinfo->marked info) t))) git-status)
; move back to goal column after invalidate
(when goal-column (move-to-column goal-column)))
@@ -850,7 +938,9 @@ Return the list of files that haven't been handled."
"Unmark all files."
(interactive)
(unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) nil) t) git-status)
+ (ewoc-map (lambda (info) (when (git-fileinfo->marked info)
+ (setf (git-fileinfo->marked info) nil)
+ t)) git-status)
; move back to goal column after invalidate
(when goal-column (move-to-column goal-column)))
@@ -904,11 +994,12 @@ Return the list of files that haven't been handled."
"Add marked file(s) to the index cache."
(interactive)
(let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored))))
+ ;; FIXME: add support for directories
(unless files
(push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
- (apply #'git-call-process-env nil nil "update-index" "--add" "--" files)
- (git-update-status-files files 'uptodate)
- (git-success-message "Added" files)))
+ (when (apply 'git-call-process-display-error "update-index" "--add" "--" files)
+ (git-update-status-files files 'uptodate)
+ (git-success-message "Added" files))))
(defun git-ignore-file ()
"Add marked file(s) to the ignore list."
@@ -930,16 +1021,19 @@ Return the list of files that haven't been handled."
(format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
(progn
(dolist (name files)
- (when (file-exists-p name) (delete-file name)))
- (apply #'git-call-process-env nil nil "update-index" "--remove" "--" files)
- (git-update-status-files files nil)
- (git-success-message "Removed" files))
+ (ignore-errors
+ (if (file-directory-p name)
+ (delete-directory name)
+ (delete-file name))))
+ (when (apply 'git-call-process-display-error "update-index" "--remove" "--" files)
+ (git-update-status-files files nil)
+ (git-success-message "Removed" files)))
(message "Aborting"))))
(defun git-revert-file ()
"Revert changes to the marked file(s)."
(interactive)
- (let ((files (git-marked-files))
+ (let ((files (git-marked-files-state 'added 'deleted 'modified 'unmerged))
added modified)
(when (and files
(yes-or-no-p
@@ -950,21 +1044,31 @@ Return the list of files that haven't been handled."
('deleted (push (git-fileinfo->name info) modified))
('unmerged (push (git-fileinfo->name info) modified))
('modified (push (git-fileinfo->name info) modified))))
- (when added
- (apply #'git-call-process-env nil nil "update-index" "--force-remove" "--" added))
- (when modified
- (apply #'git-call-process-env nil nil "checkout" "HEAD" modified))
- (git-update-status-files (append added modified) 'uptodate)
- (git-success-message "Reverted" files))))
+ ;; check if a buffer contains one of the files and isn't saved
+ (dolist (file modified)
+ (let ((buffer (get-file-buffer file)))
+ (when (and buffer (buffer-modified-p buffer))
+ (error "Buffer %s is modified. Please kill or save modified buffers before reverting." (buffer-name buffer)))))
+ (let ((ok (and
+ (or (not added)
+ (apply 'git-call-process-display-error "update-index" "--force-remove" "--" added))
+ (or (not modified)
+ (apply 'git-call-process-display-error "checkout" "HEAD" modified)))))
+ (git-update-status-files (append added modified) 'uptodate)
+ (when ok
+ (dolist (file modified)
+ (let ((buffer (get-file-buffer file)))
+ (when buffer (with-current-buffer buffer (revert-buffer t t t)))))
+ (git-success-message "Reverted" (git-get-filenames files)))))))
(defun git-resolve-file ()
"Resolve conflicts in marked file(s)."
(interactive)
(let ((files (git-get-filenames (git-marked-files-state 'unmerged))))
(when files
- (apply #'git-call-process-env nil nil "update-index" "--" files)
- (git-update-status-files files 'uptodate)
- (git-success-message "Resolved" files))))
+ (when (apply 'git-call-process-display-error "update-index" "--" files)
+ (git-update-status-files files 'uptodate)
+ (git-success-message "Resolved" files)))))
(defun git-remove-handled ()
"Remove handled files from the status list."
@@ -1010,6 +1114,16 @@ Return the list of files that haven't been handled."
(message "Inserting unknown files...done"))
(git-remove-handled)))
+(defun git-expand-directory (info)
+ "Expand the directory represented by INFO to list its files."
+ (when (eq (lsh (git-fileinfo->new-perm info) -9) ?\110)
+ (let ((dir (git-fileinfo->name info)))
+ (git-set-filenames-state git-status (list dir) nil)
+ (git-run-ls-files-with-excludes git-status (list (concat dir "/")) 'unknown "-o")
+ (git-refresh-files)
+ (git-refresh-ewoc-hf git-status)
+ t)))
+
(defun git-setup-diff-buffer (buffer)
"Setup a buffer for displaying a diff."
(let ((dir default-directory))
@@ -1019,7 +1133,9 @@ Return the list of files that haven't been handled."
(setq default-directory dir)
(setq buffer-read-only t)))
(display-buffer buffer)
- (shrink-window-if-larger-than-buffer))
+ ; shrink window only if it displays the status buffer
+ (when (eq (window-buffer) (current-buffer))
+ (shrink-window-if-larger-than-buffer)))
(defun git-diff-file ()
"Diff the marked file(s) against HEAD."
@@ -1092,6 +1208,11 @@ Return the list of files that haven't been handled."
(with-current-buffer log-edit-parent-buffer
(git-get-filenames (git-marked-files-state 'added 'deleted 'modified))))
+(defun git-log-edit-diff ()
+ "Run a diff of the current files being committed from a log-edit buffer."
+ (with-current-buffer log-edit-parent-buffer
+ (git-diff-file)))
+
(defun git-append-sign-off (name email)
"Append a Signed-off-by entry to the current buffer, avoiding duplicates."
(let ((sign-off (format "Signed-off-by: %s <%s>" name email))
@@ -1139,7 +1260,8 @@ Return the list of files that haven't been handled."
(goto-char (point-min))
(when (re-search-forward "\n+\\'" nil t)
(replace-match "\n" t t))
- (when sign-off (git-append-sign-off committer-name committer-email)))))
+ (when sign-off (git-append-sign-off committer-name committer-email)))
+ buffer))
(defun git-commit-file ()
"Commit the marked file(s), asking for a commit message."
@@ -1164,19 +1286,69 @@ Return the list of files that haven't been handled."
(when (re-search-forward "^Date: \\(.*\\)$" nil t)
(setq date (match-string 1)))))
(git-setup-log-buffer buffer author-name author-email subject date))
- (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
+ (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))
(setq buffer-file-coding-system coding-system)
(re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
+(defun git-setup-commit-buffer (commit)
+ "Setup the commit buffer with the contents of COMMIT."
+ (let (author-name author-email subject date msg)
+ (with-temp-buffer
+ (let ((coding-system (git-get-logoutput-coding-system)))
+ (git-call-process-env t nil "log" "-1" commit)
+ (goto-char (point-min))
+ (when (re-search-forward "^Author: *\\(.*\\) <\\(.*\\)>$" nil t)
+ (setq author-name (match-string 1))
+ (setq author-email (match-string 2)))
+ (when (re-search-forward "^Date: *\\(.*\\)$" nil t)
+ (setq date (match-string 1)))
+ (while (re-search-forward "^ \\(.*\\)$" nil t)
+ (push (match-string 1) msg))
+ (setq msg (nreverse msg))
+ (setq subject (pop msg))
+ (while (and msg (zerop (length (car msg))) (pop msg)))))
+ (git-setup-log-buffer (get-buffer-create "*git-commit*")
+ author-name author-email subject date
+ (mapconcat #'identity msg "\n"))))
+
+(defun git-get-commit-files (commit)
+ "Retrieve the list of files modified by COMMIT."
+ (let (files)
+ (with-temp-buffer
+ (git-call-process-env t nil "diff-tree" "-r" "-z" "--name-only" "--no-commit-id" commit)
+ (goto-char (point-min))
+ (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
+ (push (match-string 1) files)))
+ files))
+
+(defun git-amend-commit ()
+ "Undo the last commit on HEAD, and set things up to commit an
+amended version of it."
+ (interactive)
+ (unless git-status (error "Not in git-status buffer."))
+ (when (git-empty-db-p) (error "No commit to amend."))
+ (let* ((commit (git-rev-parse "HEAD"))
+ (files (git-get-commit-files commit)))
+ (when (git-call-process-display-error "reset" "--soft" "HEAD^")
+ (git-update-status-files (copy-sequence files) 'uptodate)
+ (git-mark-files git-status files)
+ (git-refresh-files)
+ (git-setup-commit-buffer commit)
+ (git-commit-file))))
+
(defun git-find-file ()
"Visit the current file in its own buffer."
(interactive)
(unless git-status (error "Not in git-status buffer."))
(let ((info (ewoc-data (ewoc-locate git-status))))
- (find-file (git-fileinfo->name info))
- (when (eq 'unmerged (git-fileinfo->state info))
- (smerge-mode 1))))
+ (unless (git-expand-directory info)
+ (find-file (git-fileinfo->name info))
+ (when (eq 'unmerged (git-fileinfo->state info))
+ (smerge-mode 1)))))
(defun git-find-file-other-window ()
"Visit the current file in its own buffer in another window."
@@ -1246,6 +1418,7 @@ Return the list of files that haven't been handled."
(unless git-status-mode-map
(let ((map (make-keymap))
+ (commit-map (make-sparse-keymap))
(diff-map (make-sparse-keymap))
(toggle-map (make-sparse-keymap)))
(suppress-keymap map)
@@ -1254,6 +1427,7 @@ Return the list of files that haven't been handled."
(define-key map " " 'git-next-file)
(define-key map "a" 'git-add-file)
(define-key map "c" 'git-commit-file)
+ (define-key map "\C-c" commit-map)
(define-key map "d" diff-map)
(define-key map "=" 'git-diff-file)
(define-key map "f" 'git-find-file)
@@ -1279,6 +1453,8 @@ Return the list of files that haven't been handled."
(define-key map "x" 'git-remove-handled)
(define-key map "\C-?" 'git-unmark-file-up)
(define-key map "\M-\C-?" 'git-unmark-all)
+ ; the commit submap
+ (define-key commit-map "\C-a" 'git-amend-commit)
; the diff submap
(define-key diff-map "b" 'git-diff-file-base)
(define-key diff-map "c" 'git-diff-file-combined)
@@ -1293,7 +1469,47 @@ Return the list of files that haven't been handled."
(define-key toggle-map "i" 'git-toggle-show-ignored)
(define-key toggle-map "k" 'git-toggle-show-unknown)
(define-key toggle-map "m" 'git-toggle-all-marks)
- (setq git-status-mode-map map)))
+ (setq git-status-mode-map map))
+ (easy-menu-define git-menu git-status-mode-map
+ "Git Menu"
+ `("Git"
+ ["Refresh" git-refresh-status t]
+ ["Commit" git-commit-file t]
+ ("Merge"
+ ["Next Unmerged File" git-next-unmerged-file t]
+ ["Prev Unmerged File" git-prev-unmerged-file t]
+ ["Mark as Resolved" git-resolve-file t]
+ ["Interactive Merge File" git-find-file-imerge t]
+ ["Diff Against Common Base File" git-diff-file-base t]
+ ["Diff Combined" git-diff-file-combined t]
+ ["Diff Against Merge Head" git-diff-file-merge-head t]
+ ["Diff Against Mine" git-diff-file-mine t]
+ ["Diff Against Other" git-diff-file-other t])
+ "--------"
+ ["Add File" git-add-file t]
+ ["Revert File" git-revert-file t]
+ ["Ignore File" git-ignore-file t]
+ ["Remove File" git-remove-file t]
+ "--------"
+ ["Find File" git-find-file t]
+ ["View File" git-view-file t]
+ ["Diff File" git-diff-file t]
+ ["Interactive Diff File" git-diff-file-idiff t]
+ ["Log" git-log-file t]
+ "--------"
+ ["Mark" git-mark-file t]
+ ["Mark All" git-mark-all t]
+ ["Unmark" git-unmark-file t]
+ ["Unmark All" git-unmark-all t]
+ ["Toggle All Marks" git-toggle-all-marks t]
+ ["Hide Handled Files" git-remove-handled t]
+ "--------"
+ ["Show Uptodate Files" git-toggle-show-uptodate :style toggle :selected git-show-uptodate]
+ ["Show Ignored Files" git-toggle-show-ignored :style toggle :selected git-show-ignored]
+ ["Show Unknown Files" git-toggle-show-unknown :style toggle :selected git-show-unknown]
+ "--------"
+ ["Quit" git-status-quit t])))
+
;; git mode should only run in the *git status* buffer
(put 'git-status-mode 'mode-class 'special)
@@ -1353,7 +1569,7 @@ Commands:
"Update the corresponding git-status buffer when a file is saved.
Meant to be used in `after-save-hook'."
(let* ((file (expand-file-name buffer-file-name))
- (dir (condition-case nil (git-get-top-dir (file-name-directory file))))
+ (dir (condition-case nil (git-get-top-dir (file-name-directory file)) (error nil)))
(buffer (and dir (git-find-status-buffer dir))))
(when buffer
(with-current-buffer buffer
diff --git a/contrib/examples/git-clean.sh b/contrib/examples/git-clean.sh
new file mode 100755
index 0000000000..01c95e9fe8
--- /dev/null
+++ b/contrib/examples/git-clean.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+#
+# Copyright (c) 2005-2006 Pavel Roskin
+#
+
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-clean [options] <paths>...
+
+Clean untracked files from the working directory
+
+When optional <paths>... arguments are given, the paths
+affected are further limited to those that match them.
+--
+d remove directories as well
+f override clean.requireForce and clean anyway
+n don't remove anything, just show what would be done
+q be quiet, only report errors
+x remove ignored files as well
+X remove only ignored files"
+
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+
+ignored=
+ignoredonly=
+cleandir=
+rmf="rm -f --"
+rmrf="rm -rf --"
+rm_refuse="echo Not removing"
+echo1="echo"
+
+disabled=$(git config --bool clean.requireForce)
+
+while test $# != 0
+do
+ case "$1" in
+ -d)
+ cleandir=1
+ ;;
+ -f)
+ disabled=false
+ ;;
+ -n)
+ disabled=false
+ rmf="echo Would remove"
+ rmrf="echo Would remove"
+ rm_refuse="echo Would not remove"
+ echo1=":"
+ ;;
+ -q)
+ echo1=":"
+ ;;
+ -x)
+ ignored=1
+ ;;
+ -X)
+ ignoredonly=1
+ ;;
+ --)
+ shift
+ break
+ ;;
+ *)
+ usage # should not happen
+ ;;
+ esac
+ shift
+done
+
+# requireForce used to default to false but now it defaults to true.
+# IOW, lack of explicit "clean.requireForce = false" is taken as
+# "clean.requireForce = true".
+case "$disabled" in
+"")
+ die "clean.requireForce not set and -n or -f not given; refusing to clean"
+ ;;
+"true")
+ die "clean.requireForce set and -n or -f not given; refusing to clean"
+ ;;
+esac
+
+if [ "$ignored,$ignoredonly" = "1,1" ]; then
+ die "-x and -X cannot be set together"
+fi
+
+if [ -z "$ignored" ]; then
+ excl="--exclude-per-directory=.gitignore"
+ excl_info= excludes_file=
+ if [ -f "$GIT_DIR/info/exclude" ]; then
+ excl_info="--exclude-from=$GIT_DIR/info/exclude"
+ fi
+ if cfg_excl=$(git config core.excludesfile) && test -f "$cfg_excl"
+ then
+ excludes_file="--exclude-from=$cfg_excl"
+ fi
+ if [ "$ignoredonly" ]; then
+ excl="$excl --ignored"
+ fi
+fi
+
+git ls-files --others --directory \
+ $excl ${excl_info:+"$excl_info"} ${excludes_file:+"$excludes_file"} \
+ -- "$@" |
+while read -r file; do
+ if [ -d "$file" -a ! -L "$file" ]; then
+ if [ -z "$cleandir" ]; then
+ $rm_refuse "$file"
+ continue
+ fi
+ $echo1 "Removing $file"
+ $rmrf "$file"
+ else
+ $echo1 "Removing $file"
+ $rmf "$file"
+ fi
+done
diff --git a/git-commit.sh b/contrib/examples/git-commit.sh
index fcb8443bdf..2c4a4062a5 100755
--- a/git-commit.sh
+++ b/contrib/examples/git-commit.sh
@@ -5,6 +5,7 @@
USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
. git-sh-setup
require_work_tree
@@ -26,7 +27,7 @@ refuse_partial () {
}
TMP_INDEX=
-THIS_INDEX="$GIT_DIR/index"
+THIS_INDEX="${GIT_INDEX_FILE:-$GIT_DIR/index}"
NEXT_INDEX="$GIT_DIR/next-index$$"
rm -f "$NEXT_INDEX"
save_index () {
@@ -73,6 +74,7 @@ trap '
all=
also=
+allow_empty=f
interactive=
only=
logfile=
@@ -113,6 +115,10 @@ do
-a|--a|--al|--all)
all=t
;;
+ --allo|--allow|--allow-|--allow-e|--allow-em|--allow-emp|\
+ --allow-empt|--allow-empty)
+ allow_empty=t
+ ;;
--au=*|--aut=*|--auth=*|--autho=*|--author=*)
force_author="${1#*=}"
;;
@@ -282,9 +288,9 @@ unset only
case "$all,$interactive,$also,$#" in
*t,*t,*)
die "Cannot use -a, --interactive or -i at the same time." ;;
-t,,[1-9]*)
+t,,,[1-9]*)
die "Paths with -a does not make sense." ;;
-,t,[1-9]*)
+,t,,[1-9]*)
die "Paths with --interactive does not make sense." ;;
,,t,0)
die "No paths with -i does not make sense." ;;
@@ -514,13 +520,18 @@ else
# we need to check if there is anything to commit
run_status >/dev/null
fi
-if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ]
-then
+case "$allow_empty,$?,$PARENTS" in
+t,* | ?,0,* | ?,*,-p' '?*-p' '?*)
+ # an explicit --allow-empty, or a merge commit can record the
+ # same tree as its parent. Otherwise having commitable paths
+ # is required.
+ ;;
+*)
rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
use_status_color=t
run_status
exit 1
-fi
+esac
case "$no_edit" in
'')
diff --git a/git-fetch.sh b/contrib/examples/git-fetch.sh
index e44af2c86d..e44af2c86d 100755
--- a/git-fetch.sh
+++ b/contrib/examples/git-fetch.sh
diff --git a/git-ls-remote.sh b/contrib/examples/git-ls-remote.sh
index d56cf92ebf..fec70bbf88 100755
--- a/git-ls-remote.sh
+++ b/contrib/examples/git-ls-remote.sh
@@ -54,9 +54,10 @@ tmpdir=$tmp-d
case "$peek_repo" in
http://* | https://* | ftp://* )
- if [ -n "$GIT_SSL_NO_VERIFY" ]; then
- curl_extra_args="-k"
- fi
+ if [ -n "$GIT_SSL_NO_VERIFY" -o \
+ "`git config --bool http.sslVerify`" = false ]; then
+ curl_extra_args="-k"
+ fi
if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
"`git config --bool http.noEPSV`" = true ]; then
curl_extra_args="${curl_extra_args} --disable-epsv"
diff --git a/git-merge-ours.sh b/contrib/examples/git-merge-ours.sh
index c81a790aa6..29dba4ba3a 100755
--- a/git-merge-ours.sh
+++ b/contrib/examples/git-merge-ours.sh
@@ -9,6 +9,6 @@
# because the current index is what we will be committing as the
# merge result.
-git diff-index --quiet --cached HEAD || exit 2
+git diff-index --quiet --cached HEAD -- || exit 2
exit 0
diff --git a/contrib/examples/git-revert.sh b/contrib/examples/git-revert.sh
new file mode 100755
index 0000000000..49f00321b2
--- /dev/null
+++ b/contrib/examples/git-revert.sh
@@ -0,0 +1,197 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+#
+
+case "$0" in
+*-revert* )
+ test -t 0 && edit=-e
+ replay=
+ me=revert
+ USAGE='[--edit | --no-edit] [-n] <commit-ish>' ;;
+*-cherry-pick* )
+ replay=t
+ edit=
+ me=cherry-pick
+ USAGE='[--edit] [-n] [-r] [-x] <commit-ish>' ;;
+* )
+ echo >&2 "What are you talking about?"
+ exit 1 ;;
+esac
+
+SUBDIRECTORY_OK=Yes ;# we will cd up
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+no_commit=
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
+ --no-commi|--no-commit)
+ no_commit=t
+ ;;
+ -e|--e|--ed|--edi|--edit)
+ edit=-e
+ ;;
+ --n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit)
+ edit=
+ ;;
+ -r)
+ : no-op ;;
+ -x|--i-really-want-to-expose-my-private-commit-object-name)
+ replay=
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+set_reflog_action "$me"
+
+test "$me,$replay" = "revert,t" && usage
+
+case "$no_commit" in
+t)
+ # We do not intend to commit immediately. We just want to
+ # merge the differences in.
+ head=$(git-write-tree) ||
+ die "Your index file is unmerged."
+ ;;
+*)
+ head=$(git-rev-parse --verify HEAD) ||
+ die "You do not have a valid HEAD"
+ files=$(git-diff-index --cached --name-only $head) || exit
+ if [ "$files" ]; then
+ die "Dirty index: cannot $me (dirty: $files)"
+ fi
+ ;;
+esac
+
+rev=$(git-rev-parse --verify "$@") &&
+commit=$(git-rev-parse --verify "$rev^0") ||
+ die "Not a single commit $@"
+prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
+ die "Cannot run $me a root commit"
+git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
+ die "Cannot run $me a multi-parent commit."
+
+encoding=$(git config i18n.commitencoding || echo UTF-8)
+
+# "commit" is an existing commit. We would want to apply
+# the difference it introduces since its first parent "prev"
+# on top of the current HEAD if we are cherry-pick. Or the
+# reverse of it if we are revert.
+
+case "$me" in
+revert)
+ git show -s --pretty=oneline --encoding="$encoding" $commit |
+ sed -e '
+ s/^[^ ]* /Revert "/
+ s/$/"/
+ '
+ echo
+ echo "This reverts commit $commit."
+ test "$rev" = "$commit" ||
+ echo "(original 'git revert' arguments: $@)"
+ base=$commit next=$prev
+ ;;
+
+cherry-pick)
+ 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
+ }'
+
+ logmsg=`git show -s --pretty=raw --encoding="$encoding" "$commit"`
+ set_author_env=`echo "$logmsg" |
+ LANG=C LC_ALL=C sed -ne "$pick_author_script"`
+ eval "$set_author_env"
+ export GIT_AUTHOR_NAME
+ export GIT_AUTHOR_EMAIL
+ export GIT_AUTHOR_DATE
+
+ echo "$logmsg" |
+ sed -e '1,/^$/d' -e 's/^ //'
+ case "$replay" in
+ '')
+ echo "(cherry picked from commit $commit)"
+ test "$rev" = "$commit" ||
+ echo "(original 'git cherry-pick' arguments: $@)"
+ ;;
+ esac
+ base=$prev next=$commit
+ ;;
+
+esac >.msg
+
+eval GITHEAD_$head=HEAD
+eval GITHEAD_$next='`git show -s \
+ --pretty=oneline --encoding="$encoding" "$commit" |
+ sed -e "s/^[^ ]* //"`'
+export GITHEAD_$head GITHEAD_$next
+
+# This three way merge is an interesting one. We are at
+# $head, and would want to apply the change between $commit
+# 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 &&
+result=$(git-write-tree 2>/dev/null) || {
+ mv -f .msg "$GIT_DIR/MERGE_MSG"
+ {
+ echo '
+Conflicts:
+'
+ git ls-files --unmerged |
+ sed -e 's/^[^ ]* / /' |
+ uniq
+ } >>"$GIT_DIR/MERGE_MSG"
+ echo >&2 "Automatic $me failed. After resolving the conflicts,"
+ echo >&2 "mark the corrected paths with 'git-add <paths>'"
+ echo >&2 "and commit the result."
+ case "$me" in
+ cherry-pick)
+ echo >&2 "You may choose to use the following when making"
+ echo >&2 "the commit:"
+ echo >&2 "$set_author_env"
+ 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
+# 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.
+
+case "$no_commit" in
+'')
+ git-commit -n -F .msg $edit
+ rm -f .msg
+ ;;
+esac
diff --git a/git-svnimport.perl b/contrib/examples/git-svnimport.perl
index ea8c1b2f60..ea8c1b2f60 100755
--- a/git-svnimport.perl
+++ b/contrib/examples/git-svnimport.perl
diff --git a/Documentation/git-svnimport.txt b/contrib/examples/git-svnimport.txt
index 71aad8b45b..71aad8b45b 100644
--- a/Documentation/git-svnimport.txt
+++ b/contrib/examples/git-svnimport.txt
diff --git a/contrib/examples/git-tag.sh b/contrib/examples/git-tag.sh
index ae7c531666..e9f3a228af 100755
--- a/contrib/examples/git-tag.sh
+++ b/contrib/examples/git-tag.sh
@@ -167,7 +167,7 @@ type=$(git cat-file -t $object) || exit 1
tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
test -n "$username" ||
- username=$(git repo-config user.signingkey) ||
+ username=$(git config user.signingkey) ||
username=$(expr "z$tagger" : 'z\(.*>\)')
trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index 52cd2a46ba..781a0cbbbc 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -71,6 +71,79 @@ def isP4Exec(kind):
a plus sign, it is also executable"""
return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
+def setP4ExecBit(file, mode):
+ # Reopens an already open file and changes the execute bit to match
+ # the execute bit setting in the passed in mode.
+
+ p4Type = "+x"
+
+ if not isModeExec(mode):
+ p4Type = getP4OpenedType(file)
+ p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
+ p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
+ if p4Type[-1] == "+":
+ p4Type = p4Type[0:-1]
+
+ system("p4 reopen -t %s %s" % (p4Type, file))
+
+def getP4OpenedType(file):
+ # Returns the perforce file type for the given file.
+
+ result = read_pipe("p4 opened %s" % file)
+ match = re.match(".*\((.+)\)$", result)
+ if match:
+ return match.group(1)
+ else:
+ die("Could not determine file type for %s" % file)
+
+def diffTreePattern():
+ # This is a simple generator for the diff tree regex pattern. This could be
+ # a class variable if this and parseDiffTreeEntry were a part of a class.
+ pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
+ while True:
+ yield pattern
+
+def parseDiffTreeEntry(entry):
+ """Parses a single diff tree entry into its component elements.
+
+ See git-diff-tree(1) manpage for details about the format of the diff
+ output. This method returns a dictionary with the following elements:
+
+ src_mode - The mode of the source file
+ dst_mode - The mode of the destination file
+ src_sha1 - The sha1 for the source file
+ dst_sha1 - The sha1 fr the destination file
+ status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
+ status_score - The score for the status (applicable for 'C' and 'R'
+ statuses). This is None if there is no score.
+ src - The path for the source file.
+ dst - The path for the destination file. This is only present for
+ copy or renames. If it is not present, this is None.
+
+ If the pattern is not matched, None is returned."""
+
+ match = diffTreePattern().next().match(entry)
+ if match:
+ return {
+ 'src_mode': match.group(1),
+ 'dst_mode': match.group(2),
+ 'src_sha1': match.group(3),
+ 'dst_sha1': match.group(4),
+ 'status': match.group(5),
+ 'status_score': match.group(6),
+ 'src': match.group(7),
+ 'dst': match.group(10)
+ }
+ return None
+
+def isModeExec(mode):
+ # Returns True if the given git mode represents an executable file,
+ # otherwise False.
+ return mode[-3:] == "755"
+
+def isModeExecChanged(src_mode, dst_mode):
+ return isModeExec(src_mode) != isModeExec(dst_mode)
+
def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
cmd = "p4 -G %s" % cmd
if verbose:
@@ -396,21 +469,19 @@ class P4Submit(Command):
optparse.make_option("--origin", dest="origin"),
optparse.make_option("--reset", action="store_true", dest="reset"),
optparse.make_option("--log-substitutions", dest="substFile"),
- optparse.make_option("--dry-run", action="store_true"),
optparse.make_option("--direct", dest="directSubmit", action="store_true"),
- optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"),
+ optparse.make_option("-M", dest="detectRename", 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.firstTime = True
self.reset = False
self.interactive = True
- self.dryRun = False
self.substFile = ""
self.firstTime = True
self.origin = ""
self.directSubmit = False
- self.trustMeLikeAFool = False
+ self.detectRename = False
self.verbose = False
self.isWindows = (platform.system() == "Windows")
@@ -491,24 +562,39 @@ class P4Submit(Command):
diff = self.diffStatus
else:
print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
- diff = read_pipe_lines("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id))
+ diffOpts = ("", "-M")[self.detectRename]
+ diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
filesToAdd = set()
filesToDelete = set()
editedFiles = set()
+ filesToChangeExecBit = {}
for line in diff:
- modifier = line[0]
- path = line[1:].strip()
+ diff = parseDiffTreeEntry(line)
+ modifier = diff['status']
+ path = diff['src']
if modifier == "M":
system("p4 edit \"%s\"" % path)
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ filesToChangeExecBit[path] = diff['dst_mode']
editedFiles.add(path)
elif modifier == "A":
filesToAdd.add(path)
+ filesToChangeExecBit[path] = diff['dst_mode']
if path in filesToDelete:
filesToDelete.remove(path)
elif modifier == "D":
filesToDelete.add(path)
if path in filesToAdd:
filesToAdd.remove(path)
+ elif modifier == "R":
+ src, dest = diff['src'], diff['dst']
+ system("p4 integrate -Dt \"%s\" \"%s\"" % (src, dest))
+ system("p4 edit \"%s\"" % (dest))
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ filesToChangeExecBit[dest] = diff['dst_mode']
+ os.unlink(dest)
+ editedFiles.add(dest)
+ filesToDelete.add(src)
else:
die("unknown modifier %s for %s" % (modifier, path))
@@ -529,6 +615,10 @@ class P4Submit(Command):
"and with .rej files / [w]rite the patch to a file (patch.txt) ")
if response == "s":
print "Skipping! Good luck with the next patches..."
+ for f in editedFiles:
+ system("p4 revert \"%s\"" % f);
+ for f in filesToAdd:
+ system("rm %s" %f)
return
elif response == "a":
os.system(applyPatchCmd)
@@ -554,6 +644,11 @@ class P4Submit(Command):
system("p4 revert \"%s\"" % f)
system("p4 delete \"%s\"" % f)
+ # Set/clear executable bits
+ for f in filesToChangeExecBit.keys():
+ mode = filesToChangeExecBit[f]
+ setP4ExecBit(f, mode)
+
logMessage = ""
if not self.directSubmit:
logMessage = extractLogMessageFromGitCommit(id)
@@ -582,57 +677,30 @@ class P4Submit(Command):
separatorLine += "\r"
separatorLine += "\n"
- response = "e"
- if self.trustMeLikeAFool:
- response = "y"
-
- firstIteration = True
- while response == "e":
- if not firstIteration:
- response = raw_input("Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip ")
- firstIteration = False
- if response == "e":
- [handle, fileName] = tempfile.mkstemp()
- tmpFile = os.fdopen(handle, "w+")
- tmpFile.write(submitTemplate + separatorLine + diff)
- tmpFile.close()
- defaultEditor = "vi"
- if platform.system() == "Windows":
- defaultEditor = "notepad"
- editor = os.environ.get("EDITOR", defaultEditor);
- system(editor + " " + fileName)
- tmpFile = open(fileName, "rb")
- message = tmpFile.read()
- tmpFile.close()
- os.remove(fileName)
- submitTemplate = message[:message.index(separatorLine)]
- if self.isWindows:
- submitTemplate = submitTemplate.replace("\r\n", "\n")
-
- if response == "y" or response == "yes":
- if self.dryRun:
- print submitTemplate
- raw_input("Press return to continue...")
- else:
- if self.directSubmit:
- print "Submitting to git first"
- os.chdir(self.oldWorkingDirectory)
- write_pipe("git commit -a -F -", submitTemplate)
- os.chdir(self.clientPath)
-
- write_pipe("p4 submit -i", submitTemplate)
- elif response == "s":
- for f in editedFiles:
- system("p4 revert \"%s\"" % f);
- for f in filesToAdd:
- system("p4 revert \"%s\"" % f);
- system("rm %s" %f)
- for f in filesToDelete:
- system("p4 delete \"%s\"" % f);
- return
- else:
- print "Not submitting!"
- self.interactive = False
+ [handle, fileName] = tempfile.mkstemp()
+ tmpFile = os.fdopen(handle, "w+")
+ tmpFile.write(submitTemplate + separatorLine + diff)
+ tmpFile.close()
+ defaultEditor = "vi"
+ if platform.system() == "Windows":
+ defaultEditor = "notepad"
+ editor = os.environ.get("EDITOR", defaultEditor);
+ system(editor + " " + fileName)
+ tmpFile = open(fileName, "rb")
+ message = tmpFile.read()
+ tmpFile.close()
+ os.remove(fileName)
+ submitTemplate = message[:message.index(separatorLine)]
+ if self.isWindows:
+ submitTemplate = submitTemplate.replace("\r\n", "\n")
+
+ if self.directSubmit:
+ print "Submitting to git first"
+ os.chdir(self.oldWorkingDirectory)
+ write_pipe("git commit -a -F -", submitTemplate)
+ os.chdir(self.clientPath)
+
+ write_pipe("p4 submit -i", submitTemplate)
else:
fileName = "submit.txt"
file = open(fileName, "w+")
@@ -729,10 +797,8 @@ class P4Submit(Command):
sync = P4Sync()
sync.run([])
- response = raw_input("Do you want to rebase current HEAD from Perforce now using git-p4 rebase? [y]es/[n]o ")
- if response == "y" or response == "yes":
- rebase = P4Rebase()
- rebase.rebase()
+ rebase = P4Rebase()
+ rebase.rebase()
os.remove(self.configFile)
return True
@@ -865,9 +931,13 @@ class P4Sync(Command):
stat = filedata[j]
j += 1
text = ''
- while j < len(filedata) and filedata[j]['code'] in ('text',
- 'binary'):
- text += filedata[j]['data']
+ while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
+ tmp = filedata[j]['data']
+ if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
+ tmp = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', tmp)
+ elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
+ tmp = re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', tmp)
+ text += tmp
j += 1
@@ -1042,7 +1112,7 @@ class P4Sync(Command):
l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
if len(l) > 0 and not self.silent:
- print "Finding files belonging to labels in %s" % `self.depotPath`
+ print "Finding files belonging to labels in %s" % `self.depotPaths`
for output in l:
label = output["label"]
@@ -1108,6 +1178,15 @@ class P4Sync(Command):
for branch in lostAndFoundBranches:
self.knownBranches[branch] = branch
+ def getBranchMappingFromGitBranches(self):
+ branches = p4BranchesInGit(self.importIntoRemotes)
+ for branch in branches.keys():
+ if branch == "master":
+ branch = "main"
+ else:
+ branch = branch[len(self.projectName):]
+ self.knownBranches[branch] = branch
+
def listExistingP4GitBranches(self):
# branches holds mapping from name to commit
branches = p4BranchesInGit(self.importIntoRemotes)
@@ -1442,8 +1521,10 @@ class P4Sync(Command):
## FIXME - what's a P4 projectName ?
self.projectName = self.guessProjectName()
- if not self.hasOrigin:
- self.getBranchMapping();
+ if self.hasOrigin:
+ self.getBranchMappingFromGitBranches()
+ else:
+ self.getBranchMapping()
if self.verbose:
print "p4-git branches: %s" % self.p4BranchesInGit
print "initial parents: %s" % self.initialParents
@@ -1530,6 +1611,11 @@ class P4Rebase(Command):
return self.rebase()
def rebase(self):
+ if os.system("git update-index --refresh") != 0:
+ die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
+ if len(read_pipe("git diff-index HEAD --")) > 0:
+ die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
+
[upstream, settings] = findUpstreamBranchPoint()
if len(upstream) == 0:
die("Cannot find upstream branchpoint for rebase")
@@ -1560,7 +1646,7 @@ class P4Clone(P4Sync):
depotPath = args[0]
depotDir = re.sub("(@[^@]*)$", "", depotPath)
depotDir = re.sub("(#[^#]*)$", "", depotDir)
- depotDir = re.sub(r"\.\.\.$,", "", depotDir)
+ depotDir = re.sub(r"\.\.\.$", "", depotDir)
depotDir = re.sub(r"/$", "", depotDir)
return os.path.split(depotDir)[1]
diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview
index 449ee69bf4..4c99dfb903 100755
--- a/contrib/gitview/gitview
+++ b/contrib/gitview/gitview
@@ -27,20 +27,17 @@ import math
import string
import fcntl
+have_gtksourceview2 = False
+have_gtksourceview = False
try:
import gtksourceview2
have_gtksourceview2 = True
except ImportError:
- have_gtksourceview2 = False
-
-try:
- import gtksourceview
- have_gtksourceview = True
-except ImportError:
- have_gtksourceview = False
-
-if not have_gtksourceview2 and not have_gtksourceview:
- print "Running without gtksourceview2 or gtksourceview module"
+ try:
+ import gtksourceview
+ have_gtksourceview = True
+ except ImportError:
+ print "Running without gtksourceview2 or gtksourceview module"
re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\d+) (?P<tz>[+-]\d{4})')
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
index 7a1c3e497f..d72ffbb777 100755
--- a/contrib/hg-to-git/hg-to-git.py
+++ b/contrib/hg-to-git/hg-to-git.py
@@ -1,6 +1,6 @@
#! /usr/bin/python
-""" hg-to-svn.py - A Mercurial to GIT converter
+""" hg-to-git.py - A Mercurial to GIT converter
Copyright (C)2007 Stelian Pop <stelian@popies.net>
@@ -27,6 +27,8 @@ import re
hgvers = {}
# List of children for each hg revision
hgchildren = {}
+# List of parents for each hg revision
+hgparents = {}
# Current branch for each hg revision
hgbranch = {}
# Number of new changesets converted from hg
@@ -99,17 +101,19 @@ if state:
else:
print 'State does not exist, first run'
-tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip()
+tip = os.popen('hg tip --template "{rev}"').read()
print 'tip is', tip
# Calculate the branches
print 'analysing the branches...'
hgchildren["0"] = ()
+hgparents["0"] = (None, None)
hgbranch["0"] = "master"
for cset in range(1, int(tip) + 1):
hgchildren[str(cset)] = ()
- prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
- if len(prnts) > 0:
+ prnts = os.popen('hg log -r %d --template "{parents}"' % cset).read().strip().split(' ')
+ prnts = map(lambda x: x[:x.find(':')], prnts)
+ if prnts[0] != '':
parent = prnts[0].strip()
else:
parent = str(cset - 1)
@@ -120,6 +124,8 @@ for cset in range(1, int(tip) + 1):
else:
mparent = None
+ hgparents[str(cset)] = (parent, mparent)
+
if mparent:
# For merge changesets, take either one, preferably the 'master' branch
if hgbranch[mparent] == 'master':
@@ -147,34 +153,27 @@ for cset in range(int(tip) + 1):
hgnewcsets += 1
# get info
- prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
- if len(prnts) > 0:
- parent = prnts[0].strip()
- else:
- parent = str(cset - 1)
- if len(prnts) > 1:
- mparent = prnts[1].strip()
- else:
- mparent = None
-
+ log_data = os.popen('hg log -r %d --template "{tags}\n{date|date}\n{author}\n"' % cset).readlines()
+ tag = log_data[0].strip()
+ date = log_data[1].strip()
+ user = log_data[2].strip()
+ parent = hgparents[str(cset)][0]
+ mparent = hgparents[str(cset)][1]
+
+ #get comment
(fdcomment, filecomment) = tempfile.mkstemp()
- csetcomment = os.popen('hg log -r %d -v | grep -v ^changeset: | grep -v ^parent: | grep -v ^user: | grep -v ^date | grep -v ^files: | grep -v ^description: | grep -v ^tag:' % cset).read().strip()
+ csetcomment = os.popen('hg log -r %d --template "{desc}"' % cset).read().strip()
os.write(fdcomment, csetcomment)
os.close(fdcomment)
- date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip()
-
- tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip()
-
- user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip()
-
print '-----------------------------------------'
print 'cset:', cset
print 'branch:', hgbranch[str(cset)]
print 'user:', user
print 'date:', date
print 'comment:', csetcomment
- print 'parent:', parent
+ if parent:
+ print 'parent:', parent
if mparent:
print 'mparent:', mparent
if tag:
@@ -211,7 +210,7 @@ for cset in range(int(tip) + 1):
os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin')
# commit
- os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment)
+ os.system(getgitenv(user, date) + 'git commit --allow-empty -a -F %s' % filecomment)
os.unlink(filecomment)
# tag
@@ -224,8 +223,7 @@ for cset in range(int(tip) + 1):
os.system('git-branch -d %s' % otherbranch)
# retrieve and record the version
- vvv = os.popen('git-show | head -1').read()
- vvv = vvv[vvv.index(' ') + 1 : ].strip()
+ vvv = os.popen('git-show --quiet --pretty=format:%H').read()
print 'record', cset, '->', vvv
hgvers[str(cset)] = vvv
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index b188aa3d67..77c88ebf1f 100644
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -2,24 +2,26 @@
#
# Copyright (c) 2007 Andy Parkins
#
-# An example hook script to mail out commit update information. This hook sends emails
-# listing new revisions to the repository introduced by the change being reported. The
-# rule is that (for branch updates) each commit will appear on one email and one email
-# only.
+# An example hook script to mail out commit update information. This hook
+# sends emails listing new revisions to the repository introduced by the
+# change being reported. The rule is that (for branch updates) each commit
+# will appear on one email and one email only.
#
-# This hook is stored in the contrib/hooks directory. Your distribution will have put
-# this somewhere standard. You should make this script executable then link to it in
-# the repository you would like to use it in. For example, on debian the hook is stored
-# in /usr/share/doc/git-core/contrib/hooks/post-receive-email:
+# This hook is stored in the contrib/hooks directory. Your distribution
+# will have put this somewhere standard. You should make this script
+# executable then link to it in the repository you would like to use it in.
+# For example, on debian the hook is stored in
+# /usr/share/doc/git-core/contrib/hooks/post-receive-email:
#
# chmod a+x post-receive-email
# cd /path/to/your/repository.git
# ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive
#
-# This hook script assumes it is enabled on the central repository of a project, with
-# all users pushing only to it and not between each other. It will still work if you
-# don't operate in that style, but it would become possible for the email to be from
-# someone other than the person doing the push.
+# This hook script assumes it is enabled on the central repository of a
+# project, with all users pushing only to it and not between each other. It
+# will still work if you don't operate in that style, but it would become
+# possible for the email to be from someone other than the person doing the
+# push.
#
# Config
# ------
@@ -28,15 +30,17 @@
# emails for every ref update.
# hooks.announcelist
# This is the list that all pushes of annotated tags will go to. Leave it
-# blank to default to the mailinglist field. The announce emails lists the
-# short log summary of the changes since the last annotated tag.
-# hook.envelopesender
-# If set then the -f option is passed to sendmail to allow the envelope sender
-# address to be set
+# blank to default to the mailinglist field. The announce emails lists
+# the short log summary of the changes since the last annotated tag.
+# hooks.envelopesender
+# If set then the -f option is passed to sendmail to allow the envelope
+# sender address to be set
+# hooks.emailprefix
+# All emails have their subjects prefixed with this prefix, or "[SCM]"
+# if emailprefix is unset, to aid filtering
#
# Notes
# -----
-# All emails have their subjects prefixed with "[SCM]" to aid filtering.
# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
# give information for debugging.
@@ -49,8 +53,8 @@
# this is and 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:
+# 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
@@ -152,10 +156,6 @@ generate_email()
fi
# Email parameters
- # The committer will be obtained from the latest existing rev; so
- # for a deletion it will be the oldrev, for the others, then newrev
- committer=$(git show --pretty=full -s $rev | sed -ne "s/^Commit: //p" |
- sed -ne 's/\(.*\) </"\1" </p')
# The email subject will contain the best description of the ref
# that we can build from the parameters
describe=$(git describe $rev 2>/dev/null)
@@ -186,7 +186,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
@@ -225,8 +225,9 @@ generate_create_branch_email()
echo $LOGBEGIN
# This shows all log entries that are not already covered by
# another ref - i.e. commits that are now accessible from this
- # ref that were previously not accessible (see generate_update_branch_email
- # for the explanation of this command)
+ # ref that were previously not accessible
+ # (see generate_update_branch_email for the explanation of this
+ # command)
git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
git rev-list --pretty --stdin $newrev
echo $LOGEND
@@ -247,35 +248,37 @@ generate_update_branch_email()
# In this case we want to issue an email containing only revisions
# 3, 4, and N. Given (almost) by
#
- # git-rev-list N ^O --not --all
+ # git rev-list N ^O --not --all
#
# The reason for the "almost", is that the "--not --all" will take
# precedence over the "N", and effectively will translate to
#
- # git-rev-list N ^O ^X ^N
+ # git rev-list N ^O ^X ^N
#
- # So, we need to build up the list more carefully. git-rev-parse will
- # generate a list of revs that may be fed into git-rev-list. We can get
- # it to make the "--not --all" part and then filter out the "^N" with:
+ # So, we need to build up the list more carefully. git rev-parse
+ # will generate a list of revs that may be fed into git rev-list.
+ # We can get it to make the "--not --all" part and then filter out
+ # the "^N" with:
#
- # git-rev-parse --not --all | grep -v N
+ # git rev-parse --not --all | grep -v N
#
- # Then, using the --stdin switch to git-rev-list we have effectively
+ # Then, using the --stdin switch to git rev-list we have effectively
# manufactured
#
- # git-rev-list N ^O ^X
+ # git rev-list N ^O ^X
#
# This leaves a problem when someone else updates the repository
- # while this script is running. Their new value of the ref we're working
- # on would be included in the "--not --all" output; and as our $newrev
- # would be an ancestor of that commit, it would exclude all of our
- # commits. What we really want is to exclude the current value of
- # $refname from the --not list, rather than N itself. So:
+ # while this script is running. Their new value of the ref we're
+ # working on would be included in the "--not --all" output; and as
+ # our $newrev would be an ancestor of that commit, it would exclude
+ # all of our commits. What we really want is to exclude the current
+ # value of $refname from the --not list, rather than N itself. So:
#
- # git-rev-parse --not --all | grep -v $(git-rev-parse $refname)
+ # git rev-parse --not --all | grep -v $(git rev-parse $refname)
#
- # Get's us to something pretty safe (apart from the small time between
- # refname being read, and git-rev-parse running - for that, I give up)
+ # Get's us to something pretty safe (apart from the small time
+ # between refname being read, and git rev-parse running - for that,
+ # I give up)
#
#
# Next problem, consider this:
@@ -283,18 +286,18 @@ generate_update_branch_email()
# \
# * --- X --- * --- N ($newrev)
#
- # That is to say, there is no guarantee that oldrev is a strict subset of
- # newrev (it would have required a --force, but that's allowed). So, we
- # can't simply say rev-list $oldrev..$newrev. Instead we find the common
- # base of the two revs and list from there.
+ # That is to say, there is no guarantee that oldrev is a strict
+ # subset of newrev (it would have required a --force, but that's
+ # allowed). So, we can't simply say rev-list $oldrev..$newrev.
+ # Instead we find the common base of the two revs and list from
+ # there.
#
- # As above, we need to take into account the presence of X; if another
- # branch is already in the repository and points at some of the revisions
- # that we are about to output - we don't want them. The solution is as
- # before: git-rev-parse output filtered.
+ # As above, we need to take into account the presence of X; if
+ # another branch is already in the repository and points at some of
+ # the revisions that we are about to output - we don't want them.
+ # The solution is as before: git rev-parse output filtered.
#
- # Finally, tags:
- # 1 --- 2 --- O --- T --- 3 --- 4 --- N
+ # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N
#
# Tags pushed into the repository generate nice shortlog emails that
# summarise the commits between them and the previous tag. However,
@@ -302,13 +305,14 @@ generate_update_branch_email()
# for a branch update. Therefore we still want to output revisions
# that have been output on a tag email.
#
- # Luckily, git-rev-parse includes just the tool. Instead of using "--all"
- # we use "--branches"; this has the added benefit that "remotes/" will
- # be ignored as well.
-
- # List all of the revisions that were removed by this update, in a fast forward
- # update, this list will be empty, because rev-list O ^N is empty. For a non
- # fast forward, O ^N is the list of removed revisions
+ # Luckily, git rev-parse includes just the tool. Instead of using
+ # "--all" we use "--branches"; this has the added benefit that
+ # "remotes/" will be ignored as well.
+
+ # List all of the revisions that were removed by this update, in a
+ # fast forward update, this list will be empty, because rev-list O
+ # ^N is empty. For a non fast forward, O ^N is the list of removed
+ # revisions
fast_forward=""
rev=""
for rev in $(git rev-list $newrev..$oldrev)
@@ -321,32 +325,33 @@ generate_update_branch_email()
fi
# List all the revisions from baserev to newrev in a kind of
- # "table-of-contents"; note this list can include revisions that have
- # already had notification emails and is present to show the full detail
- # of the change from rolling back the old revision to the base revision and
- # then forward to the new revision
+ # "table-of-contents"; note this list can include revisions that
+ # have already had notification emails and is present to show the
+ # full detail of the change from rolling back the old revision to
+ # the base revision and then forward to the new revision
for rev in $(git rev-list $oldrev..$newrev)
do
revtype=$(git cat-file -t "$rev")
echo " via $rev ($revtype)"
done
- if [ -z "$fastforward" ]; then
+ if [ "$fast_forward" ]; then
echo " from $oldrev ($oldrev_type)"
else
- # 1. Existing revisions were removed. In this case newrev is a
- # subset of oldrev - this is the reverse of a fast-forward,
- # a rewind
- # 2. New revisions were added on top of an old revision, this is
- # a rewind and addition.
+ # 1. Existing revisions were removed. In this case newrev
+ # is a subset of oldrev - this is the reverse of a
+ # fast-forward, a rewind
+ # 2. New revisions were added on top of an old revision,
+ # this is a rewind and addition.
- # (1) certainly happened, (2) possibly. When (2) hasn't happened,
- # we set a flag to indicate that no log printout is required.
+ # (1) certainly happened, (2) possibly. When (2) hasn't
+ # happened, we set a flag to indicate that no log printout
+ # is required.
echo ""
- # Find the common ancestor of the old and new revisions and compare
- # it with newrev
+ # Find the common ancestor of the old and new revisions and
+ # compare it with newrev
baserev=$(git merge-base $oldrev $newrev)
rewind_only=""
if [ "$baserev" = "$newrev" ]; then
@@ -387,21 +392,22 @@ generate_update_branch_email()
git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
git rev-list --pretty --stdin $oldrev..$newrev
- # XXX: Need a way of detecting whether git rev-list actually outputted
- # anything, so that we can issue a "no new revisions added by this
- # update" message
+ # XXX: Need a way of detecting whether git rev-list actually
+ # outputted anything, so that we can issue a "no new
+ # revisions added by this update" message
echo $LOGEND
else
echo "No new revisions were added by this update."
fi
- # The diffstat is shown from the old revision to the new revision. This
- # is to show the truth of what happened in this change. There's no point
- # showing the stat from the base to the new revision because the base
- # is effectively a random revision at this point - the user will be
- # interested in what this revision changed - including the undoing of
- # previous revisions in the case of non-fast forward updates.
+ # The diffstat is shown from the old revision to the new revision.
+ # This is to show the truth of what happened in this change.
+ # There's no point showing the stat from the base to the new
+ # revision because the base is effectively a random revision at this
+ # point - the user will be interested in what this revision changed
+ # - including the undoing of previous revisions in the case of
+ # non-fast forward updates.
echo ""
echo "Summary of changes:"
git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev
@@ -448,7 +454,8 @@ generate_update_atag_email()
#
generate_atag_email()
{
- # Use git-for-each-ref to pull out the individual fields from the tag
+ # Use git for-each-ref to pull out the individual fields from the
+ # tag
eval $(git for-each-ref --shell --format='
tagobject=%(*objectname)
tagtype=%(*objecttype)
@@ -459,8 +466,10 @@ generate_atag_email()
echo " tagging $tagobject ($tagtype)"
case "$tagtype" in
commit)
+
# If the tagged object is a commit, then we assume this is a
- # release, and so we calculate which tag this tag is replacing
+ # release, and so we calculate which tag this tag is
+ # replacing
prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
if [ -n "$prevtag" ]; then
@@ -477,25 +486,27 @@ generate_atag_email()
echo ""
echo $LOGBEGIN
- # Show the content of the tag message; this might contain a change log
- # or release notes so is worth displaying.
+ # Show the content of the tag message; this might contain a change
+ # log or release notes so is worth displaying.
git cat-file tag $newrev | sed -e '1,/^$/d'
echo ""
case "$tagtype" in
commit)
- # Only commit tags make sense to have rev-list operations performed
- # on them
+ # Only commit tags make sense to have rev-list operations
+ # performed on them
if [ -n "$prevtag" ]; then
# Show changes since the previous release
git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
else
- # No previous tag, show all the changes since time began
+ # No previous tag, show all the changes since time
+ # began
git rev-list --pretty=short $newrev | git shortlog
fi
;;
*)
- # XXX: Is there anything useful we can do for non-commit objects?
+ # XXX: Is there anything useful we can do for non-commit
+ # objects?
;;
esac
@@ -544,13 +555,14 @@ generate_update_general_email()
#
generate_general_email()
{
- # Unannotated tags are more about marking a point than releasing a version;
- # therefore we don't do the shortlog summary that we do for annotated tags
- # above - we simply show that the point has been marked, and print the log
- # message for the marked point for reference purposes
+ # Unannotated tags are more about marking a point than releasing a
+ # version; therefore we don't do the shortlog summary that we do for
+ # annotated tags above - we simply show that the point has been
+ # marked, and print the log message for the marked point for
+ # reference purposes
#
- # Note this section also catches any other reference type (although there
- # aren't any) and deals with them in the same way.
+ # Note this section also catches any other reference type (although
+ # there aren't any) and deals with them in the same way.
echo ""
if [ "$newrev_type" = "commit" ]; then
@@ -558,10 +570,10 @@ generate_general_email()
git show --no-color --root -s $newrev
echo $LOGEND
else
- # What can we do here? The tag marks an object that is not a commit,
- # so there is no log for us to display. It's probably not wise to
- # output git-cat-file as it could be a binary blob. We'll just say how
- # big it is
+ # What can we do here? The tag marks an object that is not
+ # a commit, so there is no log for us to display. It's
+ # probably not wise to output git cat-file as it could be a
+ # binary blob. We'll just say how big it is
echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
fi
}
@@ -590,7 +602,6 @@ send_mail()
# ---------------------------- main()
# --- Constants
-EMAILPREFIX="[SCM] "
LOGBEGIN="- Log -----------------------------------------------------------------"
LOGEND="-----------------------------------------------------------------------"
@@ -604,23 +615,25 @@ if [ -z "$GIT_DIR" ]; then
fi
projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
-# Check if the description is unchanged from it's default, and shorten it to a
-# more manageable length if it is
+# 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
then
projectdesc="UNNAMED PROJECT"
fi
-recipients=$(git repo-config hooks.mailinglist)
-announcerecipients=$(git repo-config hooks.announcelist)
-envelopesender=$(git-repo-config hooks.envelopesender)
+recipients=$(git config hooks.mailinglist)
+announcerecipients=$(git config hooks.announcelist)
+envelopesender=$(git config hooks.envelopesender)
+emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
# --- Main loop
-# Allow dual mode: run from the command line just like the update hook, or if
-# no arguments are given then run as a hook script
+# Allow dual mode: run from the command line just like the update hook, or
+# if no arguments are given then run as a hook script
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
+ # resend an email; they could redirect the output to sendmail
+ # themselves
PAGER= generate_email $2 $3 $1
else
while read oldrev newrev refname
diff --git a/contrib/remotes2config.sh b/contrib/remotes2config.sh
index 5838b3ab05..1cda19f66a 100644..100755
--- a/contrib/remotes2config.sh
+++ b/contrib/remotes2config.sh
@@ -11,11 +11,11 @@ if [ -d "$GIT_DIR"/remotes ]; then
{
cd "$GIT_DIR"/remotes
ls | while read f; do
- name=$(printf "$f" | tr -c "A-Za-z0-9" ".")
+ name=$(printf "$f" | tr -c "A-Za-z0-9-" ".")
sed -n \
- -e "s/^URL: \(.*\)$/remote.$name.url \1 ./p" \
- -e "s/^Pull: \(.*\)$/remote.$name.fetch \1 ^$ /p" \
- -e "s/^Push: \(.*\)$/remote.$name.push \1 ^$ /p" \
+ -e "s/^URL:[ ]*\(.*\)$/remote.$name.url \1 ./p" \
+ -e "s/^Pull:[ ]*\(.*\)$/remote.$name.fetch \1 ^$ /p" \
+ -e "s/^Push:[ ]*\(.*\)$/remote.$name.push \1 ^$ /p" \
< "$f"
done
echo done
diff --git a/convert.c b/convert.c
index aa95834eb3..d8c94cb3ed 100644
--- a/convert.c
+++ b/convert.c
@@ -17,8 +17,8 @@
#define CRLF_INPUT 2
struct text_stat {
- /* CR, LF and CRLF counts */
- unsigned cr, lf, crlf;
+ /* NUL, CR, LF and CRLF counts */
+ unsigned nul, cr, lf, crlf;
/* These are just approximations! */
unsigned printable, nonprintable;
@@ -51,6 +51,9 @@ static void gather_stats(const char *buf, unsigned long size, struct text_stat *
case '\b': case '\t': case '\033': case '\014':
stats->printable++;
break;
+ case 0:
+ stats->nul++;
+ /* fall through */
default:
stats->nonprintable++;
}
@@ -66,6 +69,8 @@ static void gather_stats(const char *buf, unsigned long size, struct text_stat *
static int is_binary(unsigned long size, struct text_stat *stats)
{
+ if (stats->nul)
+ return 1;
if ((stats->printable >> 7) < stats->nonprintable)
return 1;
/*
@@ -80,8 +85,39 @@ static int is_binary(unsigned long size, struct text_stat *stats)
return 0;
}
+static void check_safe_crlf(const char *path, int action,
+ struct text_stat *stats, enum safe_crlf checksafe)
+{
+ if (!checksafe)
+ return;
+
+ if (action == CRLF_INPUT || auto_crlf <= 0) {
+ /*
+ * CRLFs would not be restored by checkout:
+ * check if we'd remove CRLFs
+ */
+ if (stats->crlf) {
+ if (checksafe == SAFE_CRLF_WARN)
+ warning("CRLF will be replaced by LF in %s.", path);
+ else /* i.e. SAFE_CRLF_FAIL */
+ die("CRLF would be replaced by LF in %s.", path);
+ }
+ } else if (auto_crlf > 0) {
+ /*
+ * CRLFs would be added by checkout:
+ * check if we have "naked" LFs
+ */
+ if (stats->lf != stats->crlf) {
+ if (checksafe == SAFE_CRLF_WARN)
+ warning("LF will be replaced by CRLF in %s", path);
+ else /* i.e. SAFE_CRLF_FAIL */
+ die("LF would be replaced by CRLF in %s", path);
+ }
+ }
+}
+
static int crlf_to_git(const char *path, const char *src, size_t len,
- struct strbuf *buf, int action)
+ struct strbuf *buf, int action, enum safe_crlf checksafe)
{
struct text_stat stats;
char *dst;
@@ -90,9 +126,6 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
return 0;
gather_stats(src, len, &stats);
- /* No CR? Nothing to convert, regardless. */
- if (!stats.cr)
- return 0;
if (action == CRLF_GUESS) {
/*
@@ -110,6 +143,12 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
return 0;
}
+ check_safe_crlf(path, action, &stats, checksafe);
+
+ /* Optimization: No CR? Nothing to convert, regardless. */
+ if (!stats.cr)
+ return 0;
+
/* only grow if not in place */
if (strbuf_avail(buf) + buf->len < len)
strbuf_grow(buf, len - buf->len);
@@ -192,48 +231,39 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
return 1;
}
-static int filter_buffer(const char *path, const char *src,
- unsigned long size, const char *cmd)
+struct filter_params {
+ const char *src;
+ unsigned long size;
+ const char *cmd;
+};
+
+static int filter_buffer(int fd, void *data)
{
/*
* Spawn cmd and feed the buffer contents through its stdin.
*/
struct child_process child_process;
- int pipe_feed[2];
+ struct filter_params *params = (struct filter_params *)data;
int write_err, status;
+ const char *argv[] = { "sh", "-c", params->cmd, NULL };
memset(&child_process, 0, sizeof(child_process));
+ child_process.argv = argv;
+ child_process.in = -1;
+ child_process.out = fd;
- if (pipe(pipe_feed) < 0) {
- error("cannot create pipe to run external filter %s", cmd);
- return 1;
- }
-
- child_process.pid = fork();
- if (child_process.pid < 0) {
- error("cannot fork to run external filter %s", cmd);
- close(pipe_feed[0]);
- close(pipe_feed[1]);
- return 1;
- }
- if (!child_process.pid) {
- dup2(pipe_feed[0], 0);
- close(pipe_feed[0]);
- close(pipe_feed[1]);
- execlp("sh", "sh", "-c", cmd, NULL);
- return 1;
- }
- close(pipe_feed[0]);
+ if (start_command(&child_process))
+ return error("cannot fork to run external filter %s", params->cmd);
- write_err = (write_in_full(pipe_feed[1], src, size) < 0);
- if (close(pipe_feed[1]))
+ write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
+ if (close(child_process.in))
write_err = 1;
if (write_err)
- error("cannot feed the input to external filter %s", cmd);
+ error("cannot feed the input to external filter %s", params->cmd);
status = finish_command(&child_process);
if (status)
- error("external filter %s failed %d", cmd, -status);
+ error("external filter %s failed %d", params->cmd, -status);
return (write_err || status);
}
@@ -246,49 +276,36 @@ static int apply_filter(const char *path, const char *src, size_t len,
*
* (child --> cmd) --> us
*/
- int pipe_feed[2];
- int status, ret = 1;
- struct child_process child_process;
+ int ret = 1;
struct strbuf nbuf;
+ struct async async;
+ struct filter_params params;
if (!cmd)
return 0;
- memset(&child_process, 0, sizeof(child_process));
-
- if (pipe(pipe_feed) < 0) {
- error("cannot create pipe to run external filter %s", cmd);
- return 0;
- }
+ memset(&async, 0, sizeof(async));
+ async.proc = filter_buffer;
+ async.data = &params;
+ params.src = src;
+ params.size = len;
+ params.cmd = cmd;
fflush(NULL);
- child_process.pid = fork();
- if (child_process.pid < 0) {
- error("cannot fork to run external filter %s", cmd);
- close(pipe_feed[0]);
- close(pipe_feed[1]);
- return 0;
- }
- if (!child_process.pid) {
- dup2(pipe_feed[1], 1);
- close(pipe_feed[0]);
- close(pipe_feed[1]);
- exit(filter_buffer(path, src, len, cmd));
- }
- close(pipe_feed[1]);
+ if (start_async(&async))
+ return 0; /* error was already reported */
strbuf_init(&nbuf, 0);
- if (strbuf_read(&nbuf, pipe_feed[0], len) < 0) {
+ if (strbuf_read(&nbuf, async.out, len) < 0) {
error("read from external filter %s failed", cmd);
ret = 0;
}
- if (close(pipe_feed[0])) {
+ if (close(async.out)) {
error("read from external filter %s failed", cmd);
ret = 0;
}
- status = finish_command(&child_process);
- if (status) {
- error("external filter %s failed %d", cmd, -status);
+ if (finish_async(&async)) {
+ error("external filter %s failed", cmd);
ret = 0;
}
@@ -343,14 +360,14 @@ static int read_convert_config(const char *var, const char *value)
if (!strcmp("smudge", ep)) {
if (!value)
- return error("%s: lacks value", var);
+ return config_error_nonbool(var);
drv->smudge = strdup(value);
return 0;
}
if (!strcmp("clean", ep)) {
if (!value)
- return error("%s: lacks value", var);
+ return config_error_nonbool(var);
drv->clean = strdup(value);
return 0;
}
@@ -553,7 +570,8 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check)
return !!ATTR_TRUE(value);
}
-int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst)
+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[3];
int crlf = CRLF_GUESS;
@@ -575,7 +593,7 @@ int convert_to_git(const char *path, const char *src, size_t len, struct strbuf
src = dst->buf;
len = dst->len;
}
- ret |= crlf_to_git(path, src, len, dst, crlf);
+ ret |= crlf_to_git(path, src, len, dst, crlf, checksafe);
if (ret) {
src = dst->buf;
len = dst->len;
diff --git a/csum-file.c b/csum-file.c
index 9ab997120d..9728a99541 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -8,6 +8,7 @@
* able to verify hasn't been messed with afterwards.
*/
#include "cache.h"
+#include "progress.h"
#include "csum-file.h"
static void sha1flush(struct sha1file *f, unsigned int count)
@@ -17,6 +18,8 @@ static void sha1flush(struct sha1file *f, unsigned int count)
for (;;) {
int ret = xwrite(f->fd, buf, count);
if (ret > 0) {
+ f->total += ret;
+ display_throughput(f->tp, f->total);
buf = (char *) buf + ret;
count -= ret;
if (count)
@@ -31,22 +34,27 @@ static void sha1flush(struct sha1file *f, unsigned int count)
int sha1close(struct sha1file *f, unsigned char *result, int final)
{
+ int fd;
unsigned offset = f->offset;
if (offset) {
SHA1_Update(&f->ctx, f->buffer, offset);
sha1flush(f, offset);
f->offset = 0;
}
- if (!final)
- return 0; /* only want to flush (no checksum write, no close) */
- SHA1_Final(f->buffer, &f->ctx);
- if (result)
- hashcpy(result, f->buffer);
- sha1flush(f, 20);
- if (close(f->fd))
- die("%s: sha1 file error on close (%s)", f->name, strerror(errno));
+ if (final) {
+ /* write checksum and close fd */
+ SHA1_Final(f->buffer, &f->ctx);
+ if (result)
+ hashcpy(result, f->buffer);
+ sha1flush(f, 20);
+ if (close(f->fd))
+ die("%s: sha1 file error on close (%s)",
+ f->name, strerror(errno));
+ fd = 0;
+ } else
+ fd = f->fd;
free(f);
- return 0;
+ return fd;
}
int sha1write(struct sha1file *f, void *buf, unsigned int count)
@@ -75,20 +83,17 @@ int sha1write(struct sha1file *f, void *buf, unsigned int count)
struct sha1file *sha1fd(int fd, const char *name)
{
- struct sha1file *f;
- unsigned len;
-
- f = xmalloc(sizeof(*f));
-
- len = strlen(name);
- if (len >= PATH_MAX)
- die("you wascally wabbit, you");
- f->namelen = len;
- memcpy(f->name, name, len+1);
+ return sha1fd_throughput(fd, name, NULL);
+}
+struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp)
+{
+ struct sha1file *f = xmalloc(sizeof(*f));
f->fd = fd;
- f->error = 0;
f->offset = 0;
+ f->total = 0;
+ f->tp = tp;
+ f->name = name;
f->do_crc = 0;
SHA1_Init(&f->ctx);
return f;
diff --git a/csum-file.h b/csum-file.h
index c3c792f1b5..1af76562f3 100644
--- a/csum-file.h
+++ b/csum-file.h
@@ -1,18 +1,23 @@
#ifndef CSUM_FILE_H
#define CSUM_FILE_H
+struct progress;
+
/* A SHA1-protected file */
struct sha1file {
- int fd, error;
- unsigned int offset, namelen;
+ int fd;
+ unsigned int offset;
SHA_CTX ctx;
- char name[PATH_MAX];
+ off_t total;
+ struct progress *tp;
+ const char *name;
int do_crc;
uint32_t crc32;
unsigned char buffer[8192];
};
extern struct sha1file *sha1fd(int fd, const char *name);
+extern struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp);
extern int sha1close(struct sha1file *, unsigned char *, int);
extern int sha1write(struct sha1file *, void *, unsigned int);
extern void crc32_begin(struct sha1file *);
diff --git a/daemon.c b/daemon.c
index 9cf22fef41..41a60af624 100644
--- a/daemon.c
+++ b/daemon.c
@@ -9,6 +9,10 @@
#define HOST_NAME_MAX 256
#endif
+#ifndef NI_MAXSERV
+#define NI_MAXSERV 32
+#endif
+
static int log_syslog;
static int verbose;
static int reuseaddr;
@@ -402,7 +406,8 @@ static struct daemon_service daemon_service[] = {
{ "receive-pack", "receivepack", receive_pack, 0, 1 },
};
-static void enable_service(const char *name, int ena) {
+static void enable_service(const char *name, int ena)
+{
int i;
for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
if (!strcmp(daemon_service[i].name, name)) {
@@ -413,7 +418,8 @@ static void enable_service(const char *name, int ena) {
die("No such service %s", name);
}
-static void make_service_overridable(const char *name, int ena) {
+static void make_service_overridable(const char *name, int ena)
+{
int i;
for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
if (!strcmp(daemon_service[i].name, name)) {
@@ -536,7 +542,7 @@ static int execute(struct sockaddr *addr)
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 = sin_addr->sin_port;
+ port = ntohs(sin_addr->sin_port);
#ifndef NO_IPV6
} else if (addr && addr->sa_family == AF_INET6) {
struct sockaddr_in6 *sin6_addr = (void *) addr;
@@ -546,7 +552,7 @@ static int execute(struct sockaddr *addr)
inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1);
strcat(buf, "]");
- port = sin6_addr->sin6_port;
+ port = ntohs(sin6_addr->sin6_port);
#endif
}
loginfo("Connection from %s:%d", addrbuf, port);
diff --git a/diff-delta.c b/diff-delta.c
index 9e440a9299..a4e28df714 100644
--- a/diff-delta.c
+++ b/diff-delta.c
@@ -212,11 +212,24 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
if (hash_count[i] <= HASH_LIMIT)
continue;
- entries -= hash_count[i] - HASH_LIMIT;
/* We leave exactly HASH_LIMIT entries in the bucket */
+ entries -= hash_count[i] - HASH_LIMIT;
entry = hash[i];
acc = 0;
+
+ /*
+ * Assume that this loop is gone through exactly
+ * HASH_LIMIT times and is entered and left with
+ * acc==0. So the first statement in the loop
+ * contributes (hash_count[i]-HASH_LIMIT)*HASH_LIMIT
+ * to the accumulator, and the inner loop consequently
+ * is run (hash_count[i]-HASH_LIMIT) times, removing
+ * one element from the list each time. Since acc
+ * balances out to 0 at the final run, the inner loop
+ * body can't be left with entry==NULL. So we indeed
+ * encounter entry==NULL in the outer loop only.
+ */
do {
acc += hash_count[i] - HASH_LIMIT;
if (acc > 0) {
@@ -229,30 +242,17 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
}
entry = entry->next;
} while (entry);
-
- /* Assume that this loop is gone through exactly
- * HASH_LIMIT times and is entered and left with
- * acc==0. So the first statement in the loop
- * contributes (hash_count[i]-HASH_LIMIT)*HASH_LIMIT
- * to the accumulator, and the inner loop consequently
- * is run (hash_count[i]-HASH_LIMIT) times, removing
- * one element from the list each time. Since acc
- * balances out to 0 at the final run, the inner loop
- * body can't be left with entry==NULL. So we indeed
- * encounter entry==NULL in the outer loop only.
- */
}
free(hash_count);
- /* Now create the packed index in array form rather than
- * linked lists */
-
+ /*
+ * Now create the packed index in array form
+ * rather than linked lists.
+ */
memsize = sizeof(*index)
+ sizeof(*packed_hash) * (hsize+1)
+ sizeof(*packed_entry) * entries;
-
mem = malloc(memsize);
-
if (!mem) {
free(hash);
return NULL;
@@ -264,24 +264,24 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
index->src_size = bufsize;
index->hash_mask = hmask;
- mem = index + 1;
+ mem = index->hash;
packed_hash = mem;
mem = packed_hash + (hsize+1);
packed_entry = mem;
- /* Coalesce all entries belonging to one linked list into
- * consecutive array entries */
-
for (i = 0; i < hsize; i++) {
+ /*
+ * Coalesce all entries belonging to one linked list
+ * into consecutive array entries.
+ */
packed_hash[i] = packed_entry;
for (entry = hash[i]; entry; entry = entry->next)
*packed_entry++ = entry->entry;
}
- /* Sentinel value to indicate the length of the last hash
- * bucket */
-
+ /* Sentinel value to indicate the length of the last hash bucket */
packed_hash[hsize] = packed_entry;
+
assert(packed_entry - (struct index_entry *)mem == entries);
free(hash);
diff --git a/diff-lib.c b/diff-lib.c
index da5571302d..03eaa7cef3 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -9,6 +9,7 @@
#include "revision.h"
#include "cache-tree.h"
#include "path-list.h"
+#include "unpack-trees.h"
/*
* diff-files
@@ -37,7 +38,7 @@ static int get_mode(const char *path, int *mode)
if (!path || !strcmp(path, "/dev/null"))
*mode = 0;
else if (!strcmp(path, "-"))
- *mode = ntohl(create_ce_mode(0666));
+ *mode = create_ce_mode(0666);
else if (stat(path, &st))
return error("Could not access '%s'", path);
else
@@ -121,7 +122,7 @@ static int queue_diff(struct diff_options *o,
} else {
struct diff_filespec *d1, *d2;
- if (o->reverse_diff) {
+ if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
unsigned tmp;
const char *tmp_c;
tmp = mode1; mode1 = mode2; mode2 = tmp;
@@ -173,9 +174,10 @@ static int is_in_index(const char *path)
}
static int handle_diff_files_args(struct rev_info *revs,
- int argc, const char **argv, int *silent)
+ int argc, const char **argv,
+ unsigned int *options)
{
- *silent = 0;
+ *options = 0;
/* revs->max_count == -2 means --no-index */
while (1 < argc && argv[1][0] == '-') {
@@ -188,11 +190,11 @@ static int handle_diff_files_args(struct rev_info *revs,
else if (!strcmp(argv[1], "-n") ||
!strcmp(argv[1], "--no-index")) {
revs->max_count = -2;
- revs->diffopt.exit_with_status = 1;
- revs->diffopt.no_index = 1;
+ DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
+ DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
}
else if (!strcmp(argv[1], "-q"))
- *silent = 1;
+ *options |= DIFF_SILENT_ON_REMOVED;
else
return error("invalid option: %s", argv[1]);
argv++; argc--;
@@ -207,7 +209,7 @@ static int handle_diff_files_args(struct rev_info *revs,
if (!is_in_index(revs->diffopt.paths[0]) ||
!is_in_index(revs->diffopt.paths[1])) {
revs->max_count = -2;
- revs->diffopt.no_index = 1;
+ DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
}
}
@@ -230,7 +232,7 @@ static int handle_diff_files_args(struct rev_info *revs,
static int is_outside_repo(const char *path, int nongit, const char *prefix)
{
int i;
- if (nongit || !strcmp(path, "-") || path[0] == '/')
+ if (nongit || !strcmp(path, "-") || is_absolute_path(path))
return 1;
if (prefixcmp(path, "../"))
return 0;
@@ -258,7 +260,7 @@ int setup_diff_no_index(struct rev_info *revs,
break;
} else if (i < argc - 3 && !strcmp(argv[i], "--no-index")) {
i = argc - 3;
- revs->diffopt.exit_with_status = 1;
+ DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
break;
}
if (argc != i + 2 || (!is_outside_repo(argv[i + 1], nongit, prefix) &&
@@ -296,7 +298,7 @@ int setup_diff_no_index(struct rev_info *revs,
else
revs->diffopt.paths = argv + argc - 2;
revs->diffopt.nr_paths = 2;
- revs->diffopt.no_index = 1;
+ DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
revs->max_count = -2;
if (diff_setup_done(&revs->diffopt) < 0)
die("diff_setup_done failed");
@@ -305,12 +307,12 @@ int setup_diff_no_index(struct rev_info *revs,
int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
{
- int silent_on_removed;
+ unsigned int options;
- if (handle_diff_files_args(revs, argc, argv, &silent_on_removed))
+ if (handle_diff_files_args(revs, argc, argv, &options))
return -1;
- if (revs->diffopt.no_index) {
+ if (DIFF_OPT_TST(&revs->diffopt, NO_INDEX)) {
if (revs->diffopt.nr_paths != 2)
return error("need two files/directories with --no-index");
if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
@@ -329,13 +331,16 @@ int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
perror("read_cache");
return -1;
}
- return run_diff_files(revs, silent_on_removed);
+ return run_diff_files(revs, options);
}
-int run_diff_files(struct rev_info *revs, int silent_on_removed)
+int run_diff_files(struct rev_info *revs, unsigned int option)
{
int entries, i;
int diff_unmerged_stage = revs->max_count;
+ int silent_on_removed = option & DIFF_SILENT_ON_REMOVED;
+ unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
+ ? CE_MATCH_RACY_IS_DIRTY : 0);
if (diff_unmerged_stage < 0)
diff_unmerged_stage = 2;
@@ -346,7 +351,8 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
struct cache_entry *ce = active_cache[i];
int changed;
- if (revs->diffopt.quiet && revs->diffopt.has_changes)
+ if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
+ DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
break;
if (!ce_path_match(ce, revs->prune_data))
@@ -379,7 +385,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
continue;
}
else
- dpath->mode = ntohl(ce_mode_from_stat(ce, st.st_mode));
+ dpath->mode = ce_mode_from_stat(ce, st.st_mode);
while (i < entries) {
struct cache_entry *nce = active_cache[i];
@@ -393,10 +399,10 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
*/
stage = ce_stage(nce);
if (2 <= stage) {
- int mode = ntohl(nce->ce_mode);
+ int mode = nce->ce_mode;
num_compare_stages++;
hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
- dpath->parent[stage-2].mode = ntohl(ce_mode_from_stat(nce, mode));
+ dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);
dpath->parent[stage-2].status =
DIFF_STATUS_MODIFIED;
}
@@ -430,6 +436,8 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
continue;
}
+ if (ce_uptodate(ce))
+ continue;
if (lstat(ce->name, &st) < 0) {
if (errno != ENOENT && errno != ENOTDIR) {
perror(ce->name);
@@ -437,15 +445,15 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
}
if (silent_on_removed)
continue;
- diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
+ diff_addremove(&revs->diffopt, '-', ce->ce_mode,
ce->sha1, ce->name, NULL);
continue;
}
- changed = ce_match_stat(ce, &st, 0);
- if (!changed && !revs->diffopt.find_copies_harder)
+ changed = ce_match_stat(ce, &st, ce_option);
+ if (!changed && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
continue;
- oldmode = ntohl(ce->ce_mode);
- newmode = ntohl(ce_mode_from_stat(ce, st.st_mode));
+ oldmode = ce->ce_mode;
+ newmode = ce_mode_from_stat(ce, st.st_mode);
diff_change(&revs->diffopt, oldmode, newmode,
ce->sha1, (changed ? null_sha1 : ce->sha1),
ce->name, NULL);
@@ -466,7 +474,7 @@ static void diff_index_show_file(struct rev_info *revs,
struct cache_entry *ce,
unsigned char *sha1, unsigned int mode)
{
- diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
+ diff_addremove(&revs->diffopt, prefix[0], mode,
sha1, ce->name, NULL);
}
@@ -545,14 +553,14 @@ static int show_modified(struct rev_info *revs,
p->len = pathlen;
memcpy(p->path, new->name, pathlen);
p->path[pathlen] = 0;
- p->mode = ntohl(mode);
+ p->mode = mode;
hashclr(p->sha1);
memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent));
p->parent[0].status = DIFF_STATUS_MODIFIED;
- p->parent[0].mode = ntohl(new->ce_mode);
+ p->parent[0].mode = new->ce_mode;
hashcpy(p->parent[0].sha1, new->sha1);
p->parent[1].status = DIFF_STATUS_MODIFIED;
- p->parent[1].mode = ntohl(old->ce_mode);
+ p->parent[1].mode = old->ce_mode;
hashcpy(p->parent[1].sha1, old->sha1);
show_combined_diff(p, 2, revs->dense_combined_merges, revs);
free(p);
@@ -561,121 +569,157 @@ static int show_modified(struct rev_info *revs,
oldmode = old->ce_mode;
if (mode == oldmode && !hashcmp(sha1, old->sha1) &&
- !revs->diffopt.find_copies_harder)
+ !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
return 0;
- mode = ntohl(mode);
- oldmode = ntohl(oldmode);
-
diff_change(&revs->diffopt, oldmode, mode,
old->sha1, sha1, old->name, NULL);
return 0;
}
-static int diff_cache(struct rev_info *revs,
- struct cache_entry **ac, int entries,
- const char **pathspec,
- int cached, int match_missing)
+/*
+ * This turns all merge entries into "stage 3". That guarantees that
+ * when we read in the new tree (into "stage 1"), we won't lose sight
+ * of the fact that we had unmerged entries.
+ */
+static void mark_merge_entries(void)
{
- while (entries) {
- struct cache_entry *ce = *ac;
- int same = (entries > 1) && ce_same_name(ce, ac[1]);
+ int i;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (!ce_stage(ce))
+ continue;
+ ce->ce_flags |= CE_STAGEMASK;
+ }
+}
- if (revs->diffopt.quiet && revs->diffopt.has_changes)
- break;
+/*
+ * This gets a mix of an existing index and a tree, one pathname entry
+ * at a time. The index entry may be a single stage-0 one, but it could
+ * also be multiple unmerged entries (in which case idx_pos/idx_nr will
+ * give you the position and number of entries in the index).
+ */
+static void do_oneway_diff(struct unpack_trees_options *o,
+ struct cache_entry *idx,
+ struct cache_entry *tree,
+ int idx_pos, int idx_nr)
+{
+ struct rev_info *revs = o->unpack_data;
+ int match_missing, cached;
+
+ /*
+ * Backward compatibility wart - "diff-index -m" does
+ * not mean "do not ignore merges", but "match_missing".
+ *
+ * But with the revision flag parsing, that's found in
+ * "!revs->ignore_merges".
+ */
+ cached = o->index_only;
+ match_missing = !revs->ignore_merges;
+
+ if (cached && idx && ce_stage(idx)) {
+ if (tree)
+ diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1);
+ return;
+ }
+
+ /*
+ * Something added to the tree?
+ */
+ if (!tree) {
+ show_new_file(revs, idx, cached, match_missing);
+ return;
+ }
+
+ /*
+ * Something removed from the tree?
+ */
+ if (!idx) {
+ diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode);
+ return;
+ }
+
+ /* Show difference between old and new */
+ show_modified(revs, tree, idx, 1, cached, match_missing);
+}
+
+/*
+ * Count how many index entries go with the first one
+ */
+static inline int count_skip(const struct cache_entry *src, int pos)
+{
+ int skip = 1;
- if (!ce_path_match(ce, pathspec))
- goto skip_entry;
+ /* We can only have multiple entries if the first one is not stage-0 */
+ if (ce_stage(src)) {
+ struct cache_entry **p = active_cache + pos;
+ int namelen = ce_namelen(src);
- switch (ce_stage(ce)) {
- case 0:
- /* No stage 1 entry? That means it's a new file */
- if (!same) {
- show_new_file(revs, ce, cached, match_missing);
+ for (;;) {
+ const struct cache_entry *ce;
+ pos++;
+ if (pos >= active_nr)
break;
- }
- /* Show difference between old and new */
- show_modified(revs, ac[1], ce, 1,
- cached, match_missing);
- break;
- case 1:
- /* No stage 3 (merge) entry?
- * That means it's been deleted.
- */
- if (!same) {
- diff_index_show_file(revs, "-", ce,
- ce->sha1, ce->ce_mode);
+ ce = *++p;
+ if (ce_namelen(ce) != namelen)
break;
- }
- /* We come here with ce pointing at stage 1
- * (original tree) and ac[1] pointing at stage
- * 3 (unmerged). show-modified with
- * report-missing set to false does not say the
- * file is deleted but reports true if work
- * tree does not have it, in which case we
- * fall through to report the unmerged state.
- * Otherwise, we show the differences between
- * the original tree and the work tree.
- */
- if (!cached &&
- !show_modified(revs, ce, ac[1], 0,
- cached, match_missing))
+ if (memcmp(ce->name, src->name, namelen))
break;
- diff_unmerge(&revs->diffopt, ce->name,
- ntohl(ce->ce_mode), ce->sha1);
- break;
- case 3:
- diff_unmerge(&revs->diffopt, ce->name,
- 0, null_sha1);
- break;
-
- default:
- die("impossible cache entry stage");
+ skip++;
}
-
-skip_entry:
- /*
- * Ignore all the different stages for this file,
- * we've handled the relevant cases now.
- */
- do {
- ac++;
- entries--;
- } while (entries && ce_same_name(ce, ac[0]));
}
- return 0;
+ return skip;
}
/*
- * This turns all merge entries into "stage 3". That guarantees that
- * when we read in the new tree (into "stage 1"), we won't lose sight
- * of the fact that we had unmerged entries.
+ * The unpack_trees() interface is designed for merging, so
+ * the different source entries are designed primarily for
+ * the source trees, with the old index being really mainly
+ * used for being replaced by the result.
+ *
+ * For diffing, the index is more important, and we only have a
+ * single tree.
+ *
+ * We're supposed to return how many index entries we want to skip.
+ *
+ * This wrapper makes it all more readable, and takes care of all
+ * the fairly complex unpack_trees() semantic requirements, including
+ * the skipping, the path matching, the type conflict cases etc.
*/
-static void mark_merge_entries(void)
+static int oneway_diff(struct cache_entry **src,
+ struct unpack_trees_options *o,
+ int index_pos)
{
- int i;
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (!ce_stage(ce))
- continue;
- ce->ce_flags |= htons(CE_STAGEMASK);
- }
+ int skip = 0;
+ struct cache_entry *idx = src[0];
+ struct cache_entry *tree = src[1];
+ struct rev_info *revs = o->unpack_data;
+
+ if (index_pos >= 0)
+ skip = count_skip(idx, index_pos);
+
+ /*
+ * Unpack-trees generates a DF/conflict entry if
+ * there was a directory in the index and a tree
+ * in the tree. From a diff standpoint, that's a
+ * delete of the tree and a create of the file.
+ */
+ if (tree == o->df_conflict_entry)
+ tree = NULL;
+
+ if (ce_path_match(idx ? idx : tree, revs->prune_data))
+ do_oneway_diff(o, idx, tree, index_pos, skip);
+
+ return skip;
}
int run_diff_index(struct rev_info *revs, int cached)
{
- int ret;
struct object *ent;
struct tree *tree;
const char *tree_name;
- int match_missing = 0;
-
- /*
- * Backward compatibility wart - "diff-index -m" does
- * not mean "do not ignore merges", but totally different.
- */
- if (!revs->ignore_merges)
- match_missing = 1;
+ struct unpack_trees_options opts;
+ struct tree_desc t;
mark_merge_entries();
@@ -684,13 +728,20 @@ int run_diff_index(struct rev_info *revs, int cached)
tree = parse_tree_indirect(ent->sha1);
if (!tree)
return error("bad tree object %s", tree_name);
- if (read_tree(tree, 1, revs->prune_data))
- return error("unable to read tree object %s", tree_name);
- ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
- cached, match_missing);
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.index_only = cached;
+ opts.merge = 1;
+ opts.fn = oneway_diff;
+ opts.unpack_data = revs;
+
+ init_tree_desc(&t, tree->buffer, tree->size);
+ unpack_trees(1, &t, &opts);
+
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
- return ret;
+ return 0;
}
int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
@@ -700,6 +751,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
int i;
struct cache_entry **dst;
struct cache_entry *last = NULL;
+ struct unpack_trees_options opts;
+ struct tree_desc t;
/*
* This is used by git-blame to run diff-cache internally;
@@ -716,8 +769,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
cache_tree_invalidate_path(active_cache_tree,
ce->name);
last = ce;
- ce->ce_mode = 0;
- ce->ce_flags &= ~htons(CE_STAGEMASK);
+ ce->ce_flags |= CE_REMOVE;
}
*dst++ = ce;
}
@@ -728,8 +780,15 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
tree = parse_tree_indirect(tree_sha1);
if (!tree)
die("bad tree object %s", sha1_to_hex(tree_sha1));
- if (read_tree(tree, 1, opt->paths))
- return error("unable to read tree %s", sha1_to_hex(tree_sha1));
- return diff_cache(&revs, active_cache, active_nr, revs.prune_data,
- 1, 0);
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.index_only = 1;
+ opts.merge = 1;
+ opts.fn = oneway_diff;
+ opts.unpack_data = &revs;
+
+ init_tree_desc(&t, tree->buffer, tree->size);
+ unpack_trees(1, &t, &opts);
+ return 0;
}
diff --git a/diff.c b/diff.c
index 6648e01521..699b21f4e3 100644
--- a/diff.c
+++ b/diff.c
@@ -9,6 +9,8 @@
#include "xdiff-interface.h"
#include "color.h"
#include "attr.h"
+#include "run-command.h"
+#include "utf8.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
@@ -18,7 +20,8 @@
static int diff_detect_rename_default;
static int diff_rename_limit_default = 100;
-static int diff_use_color_default;
+int diff_use_color_default = -1;
+static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
static char diff_colors[][COLOR_MAXLEN] = {
@@ -54,17 +57,9 @@ static int parse_diff_color_slot(const char *var, int ofs)
static struct ll_diff_driver {
const char *name;
struct ll_diff_driver *next;
- char *cmd;
+ const char *cmd;
} *user_diff, **user_diff_tail;
-static void read_config_if_needed(void)
-{
- if (!user_diff_tail) {
- user_diff_tail = &user_diff;
- git_config(git_diff_ui_config);
- }
-}
-
/*
* Currently there is only "diff.<drivername>.command" variable;
* because there are "diff.color.<slot>" variables, we are parsing
@@ -91,10 +86,7 @@ static int parse_lldiff_command(const char *var, const char *ep, const char *val
user_diff_tail = &(drv->next);
}
- if (!value)
- return error("%s: lacks value", var);
- drv->cmd = strdup(value);
- return 0;
+ return git_config_string(&(drv->cmd), var, value);
}
/*
@@ -145,7 +137,7 @@ int git_diff_ui_config(const char *var, const char *value)
return 0;
}
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
- diff_use_color_default = git_config_colorbool(var, value);
+ diff_use_color_default = git_config_colorbool(var, value, -1);
return 0;
}
if (!strcmp(var, "diff.renames")) {
@@ -162,23 +154,44 @@ int git_diff_ui_config(const char *var, const char *value)
diff_auto_refresh_index = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "diff.external")) {
+ if (!value)
+ return config_error_nonbool(var);
+ external_diff_cmd_cfg = xstrdup(value);
+ return 0;
+ }
if (!prefixcmp(var, "diff.")) {
const char *ep = strrchr(var, '.');
- if (ep != var + 4) {
- if (!strcmp(ep, ".command"))
- return parse_lldiff_command(var, ep, value);
- if (!strcmp(ep, ".funcname"))
- return parse_funcname_pattern(var, ep, value);
- }
+ if (ep != var + 4 && !strcmp(ep, ".command"))
+ return parse_lldiff_command(var, ep, value);
}
+
+ return git_diff_basic_config(var, value);
+}
+
+int git_diff_basic_config(const char *var, const char *value)
+{
if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
int slot = parse_diff_color_slot(var, 11);
+ if (!value)
+ return config_error_nonbool(var);
color_parse(value, var, diff_colors[slot]);
return 0;
}
- return git_default_config(var, value);
+ if (!prefixcmp(var, "diff.")) {
+ const char *ep = strrchr(var, '.');
+ if (ep != var + 4) {
+ if (!strcmp(ep, ".funcname")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return parse_funcname_pattern(var, ep, value);
+ }
+ }
+ }
+
+ return git_color_default_config(var, value);
}
static char *quote_two(const char *one, const char *two)
@@ -208,6 +221,8 @@ static const char *external_diff(void)
if (done_preparing)
return external_diff_cmd;
external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
+ if (!external_diff_cmd)
+ external_diff_cmd = external_diff_cmd_cfg;
done_preparing = 1;
return external_diff_cmd;
}
@@ -282,28 +297,35 @@ static void emit_rewrite_diff(const char *name_a,
const char *name_b,
struct diff_filespec *one,
struct diff_filespec *two,
- int color_diff)
+ struct diff_options *o)
{
int lc_a, lc_b;
+ int color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
const char *name_a_tab, *name_b_tab;
const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
const char *reset = diff_get_color(color_diff, DIFF_RESET);
+ static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
name_a += (*name_a == '/');
name_b += (*name_b == '/');
name_a_tab = strchr(name_a, ' ') ? "\t" : "";
name_b_tab = strchr(name_b, ' ') ? "\t" : "";
+ strbuf_reset(&a_name);
+ strbuf_reset(&b_name);
+ quote_two_c_style(&a_name, o->a_prefix, name_a, 0);
+ quote_two_c_style(&b_name, o->b_prefix, name_b, 0);
+
diff_populate_filespec(one, 0);
diff_populate_filespec(two, 0);
lc_a = count_lines(one->data, one->size);
lc_b = count_lines(two->data, two->size);
- printf("%s--- a/%s%s%s\n%s+++ b/%s%s%s\n%s@@ -",
- metainfo, name_a, name_a_tab, reset,
- metainfo, name_b, name_b_tab, reset, fraginfo);
+ printf("%s--- %s%s%s\n%s+++ %s%s%s\n%s@@ -",
+ metainfo, a_name.buf, name_a_tab, reset,
+ metainfo, b_name.buf, name_b_tab, reset, fraginfo);
print_line_count(lc_a);
printf(" +");
print_line_count(lc_b);
@@ -438,7 +460,7 @@ static void diff_words_show(struct diff_words_data *diff_words)
ecb.outf = xdiff_outf;
ecb.priv = diff_words;
diff_words->xm.consume = fn_out_diff_words_aux;
- xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+ xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
free(minus.ptr);
free(plus.ptr);
@@ -450,9 +472,13 @@ static void diff_words_show(struct diff_words_data *diff_words)
}
}
+typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
+
struct emit_callback {
struct xdiff_emit_state xm;
int nparents, color_diff;
+ unsigned ws_rule;
+ sane_truncate_fn truncate;
const char **label_path;
struct diff_words_data *diff_words;
int *found_changesp;
@@ -484,75 +510,9 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix)
static void emit_line(const char *set, const char *reset, const char *line, int len)
{
- if (len > 0 && line[len-1] == '\n')
- len--;
fputs(set, stdout);
fwrite(line, len, 1, stdout);
- puts(reset);
-}
-
-static void emit_line_with_ws(int nparents,
- const char *set, const char *reset, const char *ws,
- const char *line, int len)
-{
- int col0 = nparents;
- int last_tab_in_indent = -1;
- int last_space_in_indent = -1;
- int i;
- int tail = len;
- int need_highlight_leading_space = 0;
- /* The line is a newly added line. Does it have funny leading
- * whitespaces? In indent, SP should never precede a TAB.
- */
- for (i = col0; i < len; i++) {
- if (line[i] == '\t') {
- last_tab_in_indent = i;
- if (0 <= last_space_in_indent)
- need_highlight_leading_space = 1;
- }
- else if (line[i] == ' ')
- last_space_in_indent = i;
- else
- break;
- }
- fputs(set, stdout);
- fwrite(line, col0, 1, stdout);
fputs(reset, stdout);
- if (((i == len) || line[i] == '\n') && i != col0) {
- /* The whole line was indent */
- emit_line(ws, reset, line + col0, len - col0);
- return;
- }
- i = col0;
- if (need_highlight_leading_space) {
- while (i < last_tab_in_indent) {
- if (line[i] == ' ') {
- fputs(ws, stdout);
- putchar(' ');
- fputs(reset, stdout);
- }
- else
- putchar(line[i]);
- i++;
- }
- }
- tail = len - 1;
- if (line[tail] == '\n' && i < tail)
- tail--;
- while (i < tail) {
- if (!isspace(line[tail]))
- break;
- tail--;
- }
- if ((i < tail && line[tail + 1] != '\n')) {
- /* This has whitespace between tail+1..len */
- fputs(set, stdout);
- fwrite(line + i, tail - i + 1, 1, stdout);
- fputs(reset, stdout);
- emit_line(ws, reset, line + tail + 1, len - tail - 1);
- }
- else
- emit_line(set, reset, line + i, len - i);
}
static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
@@ -562,9 +522,31 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons
if (!*ws)
emit_line(set, reset, line, len);
- else
- emit_line_with_ws(ecbdata->nparents, set, reset, ws,
- line, len);
+ else {
+ /* Emit just the prefix, then the rest. */
+ emit_line(set, reset, line, ecbdata->nparents);
+ (void)check_and_emit_line(line + ecbdata->nparents,
+ len - ecbdata->nparents, ecbdata->ws_rule,
+ stdout, set, reset, ws);
+ }
+}
+
+static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
+{
+ const char *cp;
+ unsigned long allot;
+ size_t l = len;
+
+ if (ecb->truncate)
+ return ecb->truncate(line, len);
+ cp = line;
+ allot = l;
+ while (0 < l) {
+ (void) utf8_width(&cp, &l);
+ if (!cp)
+ break; /* truncated in the middle? */
+ }
+ return allot - l;
}
static void fn_out_consume(void *priv, char *line, unsigned long len)
@@ -572,7 +554,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
int i;
int color;
struct emit_callback *ecbdata = priv;
- const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
+ const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
+ const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
*(ecbdata->found_changesp) = 1;
@@ -584,9 +567,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
printf("%s--- %s%s%s\n",
- set, ecbdata->label_path[0], reset, name_a_tab);
+ meta, ecbdata->label_path[0], reset, name_a_tab);
printf("%s+++ %s%s%s\n",
- set, ecbdata->label_path[1], reset, name_b_tab);
+ meta, ecbdata->label_path[1], reset, name_b_tab);
ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
}
@@ -597,13 +580,15 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
;
if (2 <= i && i < len && line[i] == ' ') {
ecbdata->nparents = i - 1;
+ len = sane_truncate_line(ecbdata, line, len);
emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
reset, line, len);
+ if (line[len-1] != '\n')
+ putchar('\n');
return;
}
if (len < ecbdata->nparents) {
- set = reset;
emit_line(reset, reset, line, len);
return;
}
@@ -627,7 +612,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
diff_words_show(ecbdata->diff_words);
line++;
len--;
- emit_line(set, reset, line, len);
+ emit_line(plain, reset, line, len);
return;
}
for (i = 0; i < ecbdata->nparents && len; i++) {
@@ -719,7 +704,9 @@ struct diffstat_t {
int nr;
int alloc;
struct diffstat_file {
+ char *from_name;
char *name;
+ char *print_name;
unsigned is_unmerged:1;
unsigned is_binary:1;
unsigned is_renamed:1;
@@ -740,11 +727,14 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
}
diffstat->files[diffstat->nr++] = x;
if (name_b) {
- x->name = pprint_rename(name_a, name_b);
+ x->from_name = xstrdup(name_a);
+ x->name = xstrdup(name_b);
x->is_renamed = 1;
}
- else
+ else {
+ x->from_name = NULL;
x->name = xstrdup(name_a);
+ }
return x;
}
@@ -788,6 +778,28 @@ static void show_graph(char ch, int cnt, const char *set, const char *reset)
printf("%s", reset);
}
+static void fill_print_name(struct diffstat_file *file)
+{
+ char *pname;
+
+ if (file->print_name)
+ return;
+
+ if (!file->is_renamed) {
+ struct strbuf buf;
+ strbuf_init(&buf, 0);
+ if (quote_c_style(file->name, &buf, NULL, 0)) {
+ pname = strbuf_detach(&buf, NULL);
+ } else {
+ pname = file->name;
+ strbuf_release(&buf);
+ }
+ } else {
+ pname = pprint_rename(file->from_name, file->name);
+ }
+ file->print_name = pname;
+}
+
static void show_stats(struct diffstat_t* data, struct diff_options *options)
{
int i, len, add, del, total, adds = 0, dels = 0;
@@ -813,27 +825,16 @@ 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(options->color_diff, DIFF_RESET);
- set = diff_get_color(options->color_diff, DIFF_PLAIN);
- add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
- del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
+ 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++) {
struct diffstat_file *file = data->files[i];
int change = file->added + file->deleted;
-
- if (!file->is_renamed) { /* renames are already quoted by pprint_rename */
- struct strbuf buf;
- strbuf_init(&buf, 0);
- if (quote_c_style(file->name, &buf, NULL, 0)) {
- free(file->name);
- file->name = strbuf_detach(&buf, NULL);
- } else {
- strbuf_release(&buf);
- }
- }
-
- len = strlen(file->name);
+ fill_print_name(file);
+ len = strlen(file->print_name);
if (max_len < len)
max_len = len;
@@ -858,7 +859,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
for (i = 0; i < data->nr; i++) {
const char *prefix = "";
- char *name = data->files[i]->name;
+ char *name = data->files[i]->print_name;
int added = data->files[i]->added;
int deleted = data->files[i]->deleted;
int name_len;
@@ -886,17 +887,17 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
printf("%s%d%s", add_c, added, reset);
printf(" bytes");
printf("\n");
- goto free_diffstat_file;
+ continue;
}
else if (data->files[i]->is_unmerged) {
show_name(prefix, name, len, reset, set);
printf(" Unmerged\n");
- goto free_diffstat_file;
+ continue;
}
else if (!data->files[i]->is_renamed &&
(added + deleted == 0)) {
total_files--;
- goto free_diffstat_file;
+ continue;
}
/*
@@ -918,11 +919,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
show_graph('+', add, add_c, reset);
show_graph('-', del, del_c, reset);
putchar('\n');
- free_diffstat_file:
- free(data->files[i]->name);
- free(data->files[i]);
}
- free(data->files);
printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
set, total_files, adds, dels, reset);
}
@@ -947,11 +944,7 @@ static void show_shortstats(struct diffstat_t* data)
dels += deleted;
}
}
- free(data->files[i]->name);
- free(data->files[i]);
}
- free(data->files);
-
printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
total_files, adds, dels);
}
@@ -960,6 +953,9 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
{
int i;
+ if (data->nr == 0)
+ return;
+
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
@@ -967,19 +963,45 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
printf("-\t-\t");
else
printf("%d\t%d\t", file->added, file->deleted);
- if (!file->is_renamed) {
- write_name_quoted(file->name, stdout, options->line_termination);
+ if (options->line_termination) {
+ fill_print_name(file);
+ if (!file->is_renamed)
+ write_name_quoted(file->name, stdout,
+ options->line_termination);
+ else {
+ fputs(file->print_name, stdout);
+ putchar(options->line_termination);
+ }
} else {
- fputs(file->name, stdout);
- putchar(options->line_termination);
+ if (file->is_renamed) {
+ putchar('\0');
+ write_name_quoted(file->from_name, stdout, '\0');
+ }
+ write_name_quoted(file->name, stdout, '\0');
}
}
}
+static void free_diffstat_info(struct diffstat_t *diffstat)
+{
+ int i;
+ for (i = 0; i < diffstat->nr; i++) {
+ struct diffstat_file *f = diffstat->files[i];
+ if (f->name != f->print_name)
+ free(f->print_name);
+ free(f->name);
+ free(f->from_name);
+ free(f);
+ }
+ free(diffstat->files);
+}
+
struct checkdiff_t {
struct xdiff_emit_state xm;
const char *filename;
int lineno, color_diff;
+ unsigned ws_rule;
+ unsigned status;
};
static void checkdiff_consume(void *priv, char *line, unsigned long len)
@@ -988,43 +1010,26 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
const char *ws = diff_get_color(data->color_diff, DIFF_WHITESPACE);
const char *reset = diff_get_color(data->color_diff, DIFF_RESET);
const char *set = diff_get_color(data->color_diff, DIFF_FILE_NEW);
+ char *err;
if (line[0] == '+') {
- int i, spaces = 0, space_before_tab = 0, white_space_at_end = 0;
-
- /* check space before tab */
- for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++)
- if (line[i] == ' ')
- spaces++;
- if (line[i - 1] == '\t' && spaces)
- space_before_tab = 1;
-
- /* check white space at line end */
- if (line[len - 1] == '\n')
- len--;
- if (isspace(line[len - 1]))
- white_space_at_end = 1;
-
- if (space_before_tab || white_space_at_end) {
- printf("%s:%d: %s", data->filename, data->lineno, ws);
- if (space_before_tab) {
- printf("space before tab");
- if (white_space_at_end)
- putchar(',');
- }
- if (white_space_at_end)
- printf("white space at end");
- printf(":%s ", reset);
- emit_line_with_ws(1, set, reset, ws, line, len);
- }
-
data->lineno++;
+ data->status = check_and_emit_line(line + 1, len - 1,
+ data->ws_rule, NULL, NULL, NULL, NULL);
+ if (!data->status)
+ return;
+ err = whitespace_error_string(data->status);
+ printf("%s:%d: %s.\n", data->filename, data->lineno, err);
+ free(err);
+ emit_line(set, reset, line, 1);
+ (void)check_and_emit_line(line + 1, len - 1, data->ws_rule,
+ stdout, set, reset, ws);
} else if (line[0] == ' ')
data->lineno++;
else if (line[0] == '@') {
char *plus = strchr(line, '+');
if (plus)
- data->lineno = strtol(plus, NULL, 10);
+ data->lineno = strtol(plus, NULL, 10) - 1;
else
die("invalid diff");
}
@@ -1180,7 +1185,6 @@ static const char *funcname_pattern(const char *ident)
{
struct funcname_pattern *pp;
- read_config_if_needed();
for (pp = funcname_pattern_list; pp; pp = pp->next)
if (!strcmp(ident, pp->name))
return pp->pattern;
@@ -1195,7 +1199,7 @@ static struct builtin_funcname_pattern {
"new\\|return\\|switch\\|throw\\|while\\)\n"
"^[ ]*\\(\\([ ]*"
"[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}"
- "[ ]*([^;]*$\\)" },
+ "[ ]*([^;]*\\)$" },
{ "tex", "^\\(\\\\\\(sub\\)*section{.*\\)$" },
};
@@ -1222,7 +1226,7 @@ static const char *diff_funcname_pattern(struct diff_filespec *one)
/*
* And define built-in fallback patterns here. Note that
- * these can be overriden by the user's config settings.
+ * these can be overridden by the user's config settings.
*/
for (i = 0; i < ARRAY_SIZE(builtin_funcname_pattern); i++)
if (!strcmp(ident, builtin_funcname_pattern[i].name))
@@ -1242,11 +1246,11 @@ static void builtin_diff(const char *name_a,
mmfile_t mf1, mf2;
const char *lbl[2];
char *a_one, *b_two;
- const char *set = diff_get_color(o->color_diff, DIFF_METAINFO);
- const char *reset = diff_get_color(o->color_diff, DIFF_RESET);
+ const char *set = diff_get_color_opt(o, DIFF_METAINFO);
+ const char *reset = diff_get_color_opt(o, DIFF_RESET);
- a_one = quote_two("a/", name_a + (*name_a == '/'));
- b_two = quote_two("b/", name_b + (*name_b == '/'));
+ a_one = quote_two(o->a_prefix, name_a + (*name_a == '/'));
+ b_two = quote_two(o->b_prefix, name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
printf("%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
@@ -1275,8 +1279,7 @@ static void builtin_diff(const char *name_a,
if ((one->mode ^ two->mode) & S_IFMT)
goto free_ab_and_return;
if (complete_rewrite) {
- emit_rewrite_diff(name_a, name_b, one, two,
- o->color_diff);
+ emit_rewrite_diff(name_a, name_b, one, two, o);
o->found_changes = 1;
goto free_ab_and_return;
}
@@ -1285,13 +1288,13 @@ static void builtin_diff(const char *name_a,
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
- if (!o->text &&
+ if (!DIFF_OPT_TST(o, TEXT) &&
(diff_filespec_is_binary(one) || diff_filespec_is_binary(two))) {
/* Quite common confusing case */
if (mf1.size == mf2.size &&
!memcmp(mf1.ptr, mf2.ptr, mf1.size))
goto free_ab_and_return;
- if (o->binary)
+ if (DIFF_OPT_TST(o, BINARY))
emit_binary_diff(&mf1, &mf2);
else
printf("Binary files %s and %s differ\n",
@@ -1314,8 +1317,9 @@ static void builtin_diff(const char *name_a,
memset(&xecfg, 0, sizeof(xecfg));
memset(&ecbdata, 0, sizeof(ecbdata));
ecbdata.label_path = lbl;
- ecbdata.color_diff = o->color_diff;
+ ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
ecbdata.found_changesp = &o->found_changes;
+ ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
xecfg.ctxlen = o->context;
xecfg.flags = XDL_EMIT_FUNCNAMES;
@@ -1330,11 +1334,11 @@ static void builtin_diff(const char *name_a,
ecb.outf = xdiff_outf;
ecb.priv = &ecbdata;
ecbdata.xm.consume = fn_out_consume;
- if (o->color_diff_words)
+ if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
ecbdata.diff_words =
xcalloc(1, sizeof(struct diff_words_data));
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
- if (o->color_diff_words)
+ xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
free_diff_words_data(&ecbdata);
}
@@ -1386,7 +1390,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
ecb.outf = xdiff_outf;
ecb.priv = diffstat;
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
free_and_return:
@@ -1408,7 +1412,8 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
data.xm.consume = checkdiff_consume;
data.filename = name_b ? name_b : name_a;
data.lineno = 0;
- data.color_diff = o->color_diff;
+ data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
+ data.ws_rule = whitespace_rule(data.filename);
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
@@ -1425,11 +1430,13 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
xpp.flags = XDF_NEED_MINIMAL;
ecb.outf = xdiff_outf;
ecb.priv = &data;
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
free_and_return:
diff_free_filespec_data(one);
diff_free_filespec_data(two);
+ if (data.status)
+ DIFF_OPT_SET(o, CHECK_FAILED);
}
struct diff_filespec *alloc_filespec(const char *path)
@@ -1440,9 +1447,18 @@ struct diff_filespec *alloc_filespec(const char *path)
memset(spec, 0, sizeof(*spec));
spec->path = (char *)(spec + 1);
memcpy(spec->path, path, namelen+1);
+ spec->count = 1;
return spec;
}
+void free_filespec(struct diff_filespec *spec)
+{
+ if (!--spec->count) {
+ diff_free_filespec_data(spec);
+ free(spec);
+ }
+}
+
void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
unsigned short mode)
{
@@ -1496,22 +1512,28 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
if (pos < 0)
return 0;
ce = active_cache[pos];
- if ((lstat(name, &st) < 0) ||
- !S_ISREG(st.st_mode) || /* careful! */
- ce_match_stat(ce, &st, 0) ||
- hashcmp(sha1, ce->sha1))
+
+ /*
+ * This is not the sha1 we are looking for, or
+ * unreusable because it is not a regular file.
+ */
+ if (hashcmp(sha1, ce->sha1) || !S_ISREG(ce->ce_mode))
return 0;
- /* we return 1 only when we can stat, it is a regular file,
- * stat information matches, and sha1 recorded in the cache
- * matches. I.e. we know the file in the work tree really is
- * the same as the <name, sha1> pair.
+
+ /*
+ * If ce matches the file in the work tree, we can reuse it.
*/
- return 1;
+ if (ce_uptodate(ce) ||
+ (!lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
+ return 1;
+
+ return 0;
}
static int populate_from_stdin(struct diff_filespec *s)
{
struct strbuf buf;
+ size_t size = 0;
strbuf_init(&buf, 0);
if (strbuf_read(&buf, 0, 0) < 0)
@@ -1519,7 +1541,8 @@ static int populate_from_stdin(struct diff_filespec *s)
strerror(errno));
s->should_munmap = 0;
- s->data = strbuf_detach(&buf, &s->size);
+ s->data = strbuf_detach(&buf, &size);
+ s->size = size;
s->should_free = 1;
return 0;
}
@@ -1608,10 +1631,12 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
* Convert from working tree format to canonical git format
*/
strbuf_init(&buf, 0);
- if (convert_to_git(s->path, s->data, s->size, &buf)) {
+ if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) {
+ size_t size = 0;
munmap(s->data, s->size);
s->should_munmap = 0;
- s->data = strbuf_detach(&buf, &s->size);
+ s->data = strbuf_detach(&buf, &size);
+ s->size = size;
s->should_free = 1;
}
}
@@ -1748,40 +1773,6 @@ static void remove_tempfile_on_signal(int signo)
raise(signo);
}
-static int spawn_prog(const char *pgm, const char **arg)
-{
- pid_t pid;
- int status;
-
- fflush(NULL);
- pid = fork();
- if (pid < 0)
- die("unable to fork");
- if (!pid) {
- execvp(pgm, (char *const*) arg);
- exit(255);
- }
-
- while (waitpid(pid, &status, 0) < 0) {
- if (errno == EINTR)
- continue;
- return -1;
- }
-
- /* Earlier we did not check the exit status because
- * diff exits non-zero if files are different, and
- * we are not interested in knowing that. It was a
- * mistake which made it harder to quit a diff-*
- * session that uses the git-apply-patch-script as
- * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
- * should also exit non-zero only when it wants to
- * abort the entire diff-* session.
- */
- if (WIFEXITED(status) && !WEXITSTATUS(status))
- return 0;
- return -1;
-}
-
/* An external diff command takes:
*
* diff-cmd name infile1 infile1-sha1 infile1-mode \
@@ -1834,7 +1825,8 @@ static void run_external_diff(const char *pgm,
*arg++ = name;
}
*arg = NULL;
- retval = spawn_prog(pgm, spawn_arg);
+ fflush(NULL);
+ retval = run_command_v_opt(spawn_arg, 0);
remove_tempfile();
if (retval) {
fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@ -1854,7 +1846,6 @@ static const char *external_diff_attr(const char *name)
!ATTR_UNSET(value)) {
struct ll_diff_driver *drv;
- read_config_if_needed();
for (drv = user_diff; drv; drv = drv->next)
if (!strcmp(drv->name, value))
return drv->cmd;
@@ -1872,7 +1863,7 @@ static void run_diff_cmd(const char *pgm,
struct diff_options *o,
int complete_rewrite)
{
- if (!o->allow_external)
+ if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
pgm = NULL;
else {
const char *cmd = external_diff_attr(name);
@@ -1970,9 +1961,9 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
}
if (hashcmp(one->sha1, two->sha1)) {
- int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
+ int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
- if (o->binary) {
+ if (DIFF_OPT_TST(o, BINARY)) {
mmfile_t mf;
if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
(!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
@@ -2064,8 +2055,14 @@ void diff_setup(struct diff_options *options)
options->change = diff_change;
options->add_remove = diff_addremove;
- options->color_diff = diff_use_color_default;
+ if (diff_use_color_default > 0)
+ DIFF_OPT_SET(options, COLOR_DIFF);
+ else
+ DIFF_OPT_CLR(options, COLOR_DIFF);
options->detect_rename = diff_detect_rename_default;
+
+ options->a_prefix = "a/";
+ options->b_prefix = "b/";
}
int diff_setup_done(struct diff_options *options)
@@ -2083,7 +2080,7 @@ int diff_setup_done(struct diff_options *options)
if (count > 1)
die("--name-only, --name-status, --check and -s are mutually exclusive");
- if (options->find_copies_harder)
+ if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
options->detect_rename = DIFF_DETECT_COPY;
if (options->output_format & (DIFF_FORMAT_NAME |
@@ -2107,12 +2104,12 @@ int diff_setup_done(struct diff_options *options)
DIFF_FORMAT_SHORTSTAT |
DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_CHECKDIFF))
- options->recursive = 1;
+ DIFF_OPT_SET(options, RECURSIVE);
/*
* Also pickaxe would not work very well if you do not say recursive
*/
if (options->pickaxe)
- options->recursive = 1;
+ DIFF_OPT_SET(options, RECURSIVE);
if (options->detect_rename && options->rename_limit < 0)
options->rename_limit = diff_rename_limit_default;
@@ -2134,9 +2131,9 @@ int diff_setup_done(struct diff_options *options)
* to have found. It does not make sense not to return with
* exit code in such a case either.
*/
- if (options->quiet) {
+ if (DIFF_OPT_TST(options, QUIET)) {
options->output_format = DIFF_FORMAT_NO_OUTPUT;
- options->exit_with_status = 1;
+ DIFF_OPT_SET(options, EXIT_WITH_STATUS);
}
/*
@@ -2144,7 +2141,7 @@ int diff_setup_done(struct diff_options *options)
* upon the first hit. We need to run diff as usual.
*/
if (options->pickaxe || options->filter)
- options->quiet = 0;
+ DIFF_OPT_CLR(options, QUIET);
return 0;
}
@@ -2201,21 +2198,32 @@ static int diff_scoreopt_parse(const char *opt);
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
{
const char *arg = av[0];
+
+ /* Output format options */
if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
options->output_format |= DIFF_FORMAT_PATCH;
else if (opt_arg(arg, 'U', "unified", &options->context))
options->output_format |= DIFF_FORMAT_PATCH;
else if (!strcmp(arg, "--raw"))
options->output_format |= DIFF_FORMAT_RAW;
- else if (!strcmp(arg, "--patch-with-raw")) {
+ else if (!strcmp(arg, "--patch-with-raw"))
options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
- }
- else if (!strcmp(arg, "--numstat")) {
+ else if (!strcmp(arg, "--numstat"))
options->output_format |= DIFF_FORMAT_NUMSTAT;
- }
- else if (!strcmp(arg, "--shortstat")) {
+ else if (!strcmp(arg, "--shortstat"))
options->output_format |= DIFF_FORMAT_SHORTSTAT;
- }
+ else if (!strcmp(arg, "--check"))
+ options->output_format |= DIFF_FORMAT_CHECKDIFF;
+ else if (!strcmp(arg, "--summary"))
+ options->output_format |= DIFF_FORMAT_SUMMARY;
+ else if (!strcmp(arg, "--patch-with-stat"))
+ options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT;
+ else if (!strcmp(arg, "--name-only"))
+ options->output_format |= DIFF_FORMAT_NAME;
+ else if (!strcmp(arg, "--name-status"))
+ 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;
@@ -2243,99 +2251,95 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
options->stat_name_width = name_width;
options->stat_width = width;
}
- else if (!strcmp(arg, "--check"))
- options->output_format |= DIFF_FORMAT_CHECKDIFF;
- else if (!strcmp(arg, "--summary"))
- options->output_format |= DIFF_FORMAT_SUMMARY;
- else if (!strcmp(arg, "--patch-with-stat")) {
- options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT;
- }
- else if (!strcmp(arg, "-z"))
- options->line_termination = 0;
- else if (!prefixcmp(arg, "-l"))
- options->rename_limit = strtoul(arg+2, NULL, 10);
- else if (!strcmp(arg, "--full-index"))
- options->full_index = 1;
- else if (!strcmp(arg, "--binary")) {
- options->output_format |= DIFF_FORMAT_PATCH;
- options->binary = 1;
- }
- else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) {
- options->text = 1;
- }
- else if (!strcmp(arg, "--name-only"))
- options->output_format |= DIFF_FORMAT_NAME;
- else if (!strcmp(arg, "--name-status"))
- options->output_format |= DIFF_FORMAT_NAME_STATUS;
- else if (!strcmp(arg, "-R"))
- options->reverse_diff = 1;
- else if (!prefixcmp(arg, "-S"))
- options->pickaxe = arg + 2;
- else if (!strcmp(arg, "-s")) {
- options->output_format |= DIFF_FORMAT_NO_OUTPUT;
- }
- else if (!prefixcmp(arg, "-O"))
- options->orderfile = arg + 2;
- else if (!prefixcmp(arg, "--diff-filter="))
- options->filter = arg + 14;
- else if (!strcmp(arg, "--pickaxe-all"))
- options->pickaxe_opts = DIFF_PICKAXE_ALL;
- else if (!strcmp(arg, "--pickaxe-regex"))
- options->pickaxe_opts = DIFF_PICKAXE_REGEX;
+
+ /* renames options */
else if (!prefixcmp(arg, "-B")) {
- if ((options->break_opt =
- diff_scoreopt_parse(arg)) == -1)
+ if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
return -1;
}
else if (!prefixcmp(arg, "-M")) {
- if ((options->rename_score =
- diff_scoreopt_parse(arg)) == -1)
+ if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
return -1;
options->detect_rename = DIFF_DETECT_RENAME;
}
else if (!prefixcmp(arg, "-C")) {
if (options->detect_rename == DIFF_DETECT_COPY)
- options->find_copies_harder = 1;
- if ((options->rename_score =
- diff_scoreopt_parse(arg)) == -1)
+ DIFF_OPT_SET(options, FIND_COPIES_HARDER);
+ if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
return -1;
options->detect_rename = DIFF_DETECT_COPY;
}
- else if (!strcmp(arg, "--find-copies-harder"))
- options->find_copies_harder = 1;
- else if (!strcmp(arg, "--follow"))
- options->follow_renames = 1;
- else if (!strcmp(arg, "--abbrev"))
- options->abbrev = DEFAULT_ABBREV;
- else if (!prefixcmp(arg, "--abbrev=")) {
- options->abbrev = strtoul(arg + 9, NULL, 10);
- if (options->abbrev < MINIMUM_ABBREV)
- options->abbrev = MINIMUM_ABBREV;
- else if (40 < options->abbrev)
- options->abbrev = 40;
- }
- else if (!strcmp(arg, "--color"))
- options->color_diff = 1;
- else if (!strcmp(arg, "--no-color"))
- options->color_diff = 0;
+ else if (!strcmp(arg, "--no-renames"))
+ options->detect_rename = 0;
+
+ /* xdiff options */
else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
options->xdl_opts |= XDF_IGNORE_WHITESPACE;
else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(arg, "--ignore-space-at-eol"))
options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+
+ /* flags options */
+ else if (!strcmp(arg, "--binary")) {
+ options->output_format |= DIFF_FORMAT_PATCH;
+ DIFF_OPT_SET(options, BINARY);
+ }
+ else if (!strcmp(arg, "--full-index"))
+ DIFF_OPT_SET(options, FULL_INDEX);
+ else if (!strcmp(arg, "-a") || !strcmp(arg, "--text"))
+ DIFF_OPT_SET(options, TEXT);
+ else if (!strcmp(arg, "-R"))
+ DIFF_OPT_SET(options, REVERSE_DIFF);
+ else if (!strcmp(arg, "--find-copies-harder"))
+ DIFF_OPT_SET(options, FIND_COPIES_HARDER);
+ else if (!strcmp(arg, "--follow"))
+ DIFF_OPT_SET(options, FOLLOW_RENAMES);
+ else if (!strcmp(arg, "--color"))
+ DIFF_OPT_SET(options, COLOR_DIFF);
+ else if (!strcmp(arg, "--no-color"))
+ DIFF_OPT_CLR(options, COLOR_DIFF);
else if (!strcmp(arg, "--color-words"))
- options->color_diff = options->color_diff_words = 1;
- else if (!strcmp(arg, "--no-renames"))
- options->detect_rename = 0;
+ options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
else if (!strcmp(arg, "--exit-code"))
- options->exit_with_status = 1;
+ DIFF_OPT_SET(options, EXIT_WITH_STATUS);
else if (!strcmp(arg, "--quiet"))
- options->quiet = 1;
+ DIFF_OPT_SET(options, QUIET);
else if (!strcmp(arg, "--ext-diff"))
- options->allow_external = 1;
+ DIFF_OPT_SET(options, ALLOW_EXTERNAL);
else if (!strcmp(arg, "--no-ext-diff"))
- options->allow_external = 0;
+ DIFF_OPT_CLR(options, ALLOW_EXTERNAL);
+
+ /* 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 (!strcmp(arg, "--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;
+ else if (!strcmp(arg, "--abbrev"))
+ options->abbrev = DEFAULT_ABBREV;
+ else if (!prefixcmp(arg, "--abbrev=")) {
+ options->abbrev = strtoul(arg + 9, NULL, 10);
+ if (options->abbrev < MINIMUM_ABBREV)
+ options->abbrev = MINIMUM_ABBREV;
+ 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 (!strcmp(arg, "--no-prefix"))
+ options->a_prefix = options->b_prefix = "";
else
return 0;
return 1;
@@ -2431,10 +2435,8 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
void diff_free_filepair(struct diff_filepair *p)
{
- diff_free_filespec_data(p->one);
- diff_free_filespec_data(p->two);
- free(p->one);
- free(p->two);
+ free_filespec(p->one);
+ free_filespec(p->two);
free(p);
}
@@ -2586,9 +2588,9 @@ void diff_debug_filepair(const struct diff_filepair *p, int i)
{
diff_debug_filespec(p->one, i, "one");
diff_debug_filespec(p->two, i, "two");
- fprintf(stderr, "score %d, status %c stays %d broken %d\n",
+ fprintf(stderr, "score %d, status %c rename_used %d broken %d\n",
p->score, p->status ? p->status : '?',
- p->source_stays, p->broken_pair);
+ p->one->rename_used, p->broken_pair);
}
void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
@@ -2606,8 +2608,8 @@ void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
static void diff_resolve_rename_copy(void)
{
- int i, j;
- struct diff_filepair *p, *pp;
+ int i;
+ struct diff_filepair *p;
struct diff_queue_struct *q = &diff_queued_diff;
diff_debug_queue("resolve-rename-copy", q);
@@ -2629,27 +2631,21 @@ static void diff_resolve_rename_copy(void)
* either in-place edit or rename/copy edit.
*/
else if (DIFF_PAIR_RENAME(p)) {
- if (p->source_stays) {
- p->status = DIFF_STATUS_COPIED;
- continue;
- }
- /* See if there is some other filepair that
- * copies from the same source as us. If so
- * we are a copy. Otherwise we are either a
- * copy if the path stays, or a rename if it
- * does not, but we already handled "stays" case.
+ /*
+ * A rename might have re-connected a broken
+ * pair up, causing the pathnames to be the
+ * same again. If so, that's not a rename at
+ * all, just a modification..
+ *
+ * Otherwise, see if this source was used for
+ * multiple renames, in which case we decrement
+ * the count, and call it a copy.
*/
- for (j = i + 1; j < q->nr; j++) {
- pp = q->queue[j];
- if (strcmp(pp->one->path, p->one->path))
- continue; /* not us */
- if (!DIFF_PAIR_RENAME(pp))
- continue; /* not a rename/copy */
- /* pp is a rename/copy from the same source */
+ if (!strcmp(p->one->path, p->two->path))
+ p->status = DIFF_STATUS_MODIFIED;
+ else if (--p->one->rename_used > 0)
p->status = DIFF_STATUS_COPIED;
- break;
- }
- if (!p->status)
+ else
p->status = DIFF_STATUS_RENAMED;
}
else if (hashcmp(p->one->sha1, p->two->sha1) ||
@@ -2739,7 +2735,7 @@ static void diff_summary(struct diff_filepair *p)
break;
default:
if (p->score) {
- puts(" rewrite ");
+ fputs(" rewrite ", stdout);
write_name_quoted(p->two->path, stdout, ' ');
printf("(%d%%)\n", similarity_index(p));
}
@@ -2861,7 +2857,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
xecfg.flags = XDL_EMIT_FUNCNAMES;
ecb.outf = xdiff_outf;
ecb.priv = &data;
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
SHA1_Final(sha1, &ctx);
@@ -2948,8 +2944,9 @@ void diff_flush(struct diff_options *options)
show_numstat(&diffstat, options);
if (output_format & DIFF_FORMAT_DIFFSTAT)
show_stats(&diffstat, options);
- else if (output_format & DIFF_FORMAT_SHORTSTAT)
+ if (output_format & DIFF_FORMAT_SHORTSTAT)
show_shortstats(&diffstat);
+ free_diffstat_info(&diffstat);
separator++;
}
@@ -3098,7 +3095,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
* to determine how many paths were dirty only
* due to stat info mismatch.
*/
- if (!diffopt->no_index)
+ if (!DIFF_OPT_TST(diffopt, NO_INDEX))
diffopt->skip_stat_unmatch++;
diff_free_filepair(p);
}
@@ -3109,10 +3106,10 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
void diffcore_std(struct diff_options *options)
{
- if (options->quiet)
+ if (DIFF_OPT_TST(options, QUIET))
return;
- if (options->skip_stat_unmatch && !options->find_copies_harder)
+ if (options->skip_stat_unmatch && !DIFF_OPT_TST(options, FIND_COPIES_HARDER))
diffcore_skip_stat_unmatch(options);
if (options->break_opt != -1)
diffcore_break(options->break_opt);
@@ -3127,9 +3124,26 @@ void diffcore_std(struct diff_options *options)
diff_resolve_rename_copy();
diffcore_apply_filter(options->filter);
- options->has_changes = !!diff_queued_diff.nr;
+ if (diff_queued_diff.nr)
+ DIFF_OPT_SET(options, HAS_CHANGES);
+ else
+ DIFF_OPT_CLR(options, HAS_CHANGES);
}
+int diff_result_code(struct diff_options *opt, int status)
+{
+ int result = 0;
+ if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+ !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
+ return status;
+ if (DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+ DIFF_OPT_TST(opt, HAS_CHANGES))
+ result |= 01;
+ if ((opt->output_format & DIFF_FORMAT_CHECKDIFF) &&
+ DIFF_OPT_TST(opt, CHECK_FAILED))
+ result |= 02;
+ return result;
+}
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
@@ -3151,7 +3165,7 @@ void diff_addremove(struct diff_options *options,
* Before the final output happens, they are pruned after
* merged into rename/copy pairs as appropriate.
*/
- if (options->reverse_diff)
+ if (DIFF_OPT_TST(options, REVERSE_DIFF))
addremove = (addremove == '+' ? '-' :
addremove == '-' ? '+' : addremove);
@@ -3166,7 +3180,7 @@ void diff_addremove(struct diff_options *options,
fill_filespec(two, sha1, mode);
diff_queue(&diff_queued_diff, one, two);
- options->has_changes = 1;
+ DIFF_OPT_SET(options, HAS_CHANGES);
}
void diff_change(struct diff_options *options,
@@ -3178,7 +3192,7 @@ void diff_change(struct diff_options *options,
char concatpath[PATH_MAX];
struct diff_filespec *one, *two;
- if (options->reverse_diff) {
+ if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
unsigned tmp;
const unsigned char *tmp_c;
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
@@ -3192,7 +3206,7 @@ void diff_change(struct diff_options *options,
fill_filespec(two, new_sha1, new_mode);
diff_queue(&diff_queued_diff, one, two);
- options->has_changes = 1;
+ DIFF_OPT_SET(options, HAS_CHANGES);
}
void diff_unmerge(struct diff_options *options,
diff --git a/diff.h b/diff.h
index 4546aad219..8e73f07d7e 100644
--- a/diff.h
+++ b/diff.h
@@ -43,26 +43,34 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
#define DIFF_FORMAT_CALLBACK 0x1000
+#define DIFF_OPT_RECURSIVE (1 << 0)
+#define DIFF_OPT_TREE_IN_RECURSIVE (1 << 1)
+#define DIFF_OPT_BINARY (1 << 2)
+#define DIFF_OPT_TEXT (1 << 3)
+#define DIFF_OPT_FULL_INDEX (1 << 4)
+#define DIFF_OPT_SILENT_ON_REMOVE (1 << 5)
+#define DIFF_OPT_FIND_COPIES_HARDER (1 << 6)
+#define DIFF_OPT_FOLLOW_RENAMES (1 << 7)
+#define DIFF_OPT_COLOR_DIFF (1 << 8)
+#define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9)
+#define DIFF_OPT_HAS_CHANGES (1 << 10)
+#define DIFF_OPT_QUIET (1 << 11)
+#define DIFF_OPT_NO_INDEX (1 << 12)
+#define DIFF_OPT_ALLOW_EXTERNAL (1 << 13)
+#define DIFF_OPT_EXIT_WITH_STATUS (1 << 14)
+#define DIFF_OPT_REVERSE_DIFF (1 << 15)
+#define DIFF_OPT_CHECK_FAILED (1 << 16)
+#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
+#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)
+#define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag)
+
struct diff_options {
const char *filter;
const char *orderfile;
const char *pickaxe;
const char *single_follow;
- unsigned recursive:1,
- tree_in_recursive:1,
- binary:1,
- text:1,
- full_index:1,
- silent_on_remove:1,
- find_copies_harder:1,
- follow_renames:1,
- color_diff:1,
- color_diff_words:1,
- has_changes:1,
- quiet:1,
- no_index:1,
- allow_external:1,
- exit_with_status:1;
+ const char *a_prefix, *b_prefix;
+ unsigned flags;
int context;
int break_opt;
int detect_rename;
@@ -71,7 +79,6 @@ struct diff_options {
int output_format;
int pickaxe_opts;
int rename_score;
- int reverse_diff;
int rename_limit;
int setup;
int abbrev;
@@ -105,6 +112,9 @@ enum color_diff {
DIFF_WHITESPACE = 7,
};
const char *diff_get_color(int diff_use_color, enum color_diff ix);
+#define diff_get_color_opt(o, ix) \
+ diff_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
+
extern const char mime_boundary_leader[];
@@ -162,7 +172,9 @@ extern void diff_unmerge(struct diff_options *,
#define DIFF_SETUP_USE_CACHE 2
#define DIFF_SETUP_USE_SIZE_CACHE 4
+extern int git_diff_basic_config(const char *var, const char *value);
extern int git_diff_ui_config(const char *var, const char *value);
+extern int diff_use_color_default;
extern void diff_setup(struct diff_options *);
extern int diff_opt_parse(struct diff_options *, const char **, int);
extern int diff_setup_done(struct diff_options *);
@@ -224,7 +236,11 @@ extern void diff_flush(struct diff_options*);
extern const char *diff_unique_abbrev(const unsigned char *, int);
-extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
+/* do not report anything on removed paths */
+#define DIFF_SILENT_ON_REMOVED 01
+/* report racily-clean paths as modified */
+#define DIFF_RACY_IS_MODIFIED 02
+extern int run_diff_files(struct rev_info *revs, unsigned int option);
extern int setup_diff_no_index(struct rev_info *revs,
int argc, const char ** argv, int nongit, const char *prefix);
extern int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv);
@@ -234,4 +250,6 @@ extern int run_diff_index(struct rev_info *revs, int cached);
extern int do_diff_cache(const unsigned char *, struct diff_options *);
extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
+extern int diff_result_code(struct diff_options *, int);
+
#endif /* DIFF_H */
diff --git a/diffcore-break.c b/diffcore-break.c
index ae8a7d03e2..31cdcfe8bc 100644
--- a/diffcore-break.c
+++ b/diffcore-break.c
@@ -45,15 +45,17 @@ static int should_break(struct diff_filespec *src,
* The value we return is 1 if we want the pair to be broken,
* or 0 if we do not.
*/
- unsigned long delta_size, base_size, src_copied, literal_added,
- src_removed;
+ unsigned long delta_size, base_size, max_size;
+ unsigned long src_copied, literal_added, src_removed;
*merge_score_p = 0; /* assume no deletion --- "do not break"
* is the default.
*/
- if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
- return 0; /* leave symlink rename alone */
+ if (S_ISREG(src->mode) != S_ISREG(dst->mode)) {
+ *merge_score_p = (int)MAX_SCORE;
+ return 1; /* even their types are different */
+ }
if (src->sha1_valid && dst->sha1_valid &&
!hashcmp(src->sha1, dst->sha1))
@@ -63,7 +65,8 @@ static int should_break(struct diff_filespec *src,
return 0; /* error but caught downstream */
base_size = ((src->size < dst->size) ? src->size : dst->size);
- if (base_size < MINIMUM_BREAK_SIZE)
+ max_size = ((src->size > dst->size) ? src->size : dst->size);
+ if (max_size < MINIMUM_BREAK_SIZE)
return 0; /* we do not break too small filepair */
if (diffcore_count_changes(src, dst,
@@ -89,12 +92,14 @@ static int should_break(struct diff_filespec *src,
* less than the minimum, after rename/copy runs.
*/
*merge_score_p = (int)(src_removed * MAX_SCORE / src->size);
+ if (*merge_score_p > break_score)
+ return 1;
/* Extent of damage, which counts both inserts and
* deletes.
*/
delta_size = src_removed + literal_added;
- if (delta_size * MAX_SCORE / base_size < break_score)
+ if (delta_size * MAX_SCORE / max_size < break_score)
return 0;
/* If you removed a lot without adding new material, that is
@@ -165,11 +170,13 @@ void diffcore_break(int break_score)
struct diff_filepair *p = q->queue[i];
int score;
- /* We deal only with in-place edit of non directory.
+ /*
+ * We deal only with in-place edit of blobs.
* We do not break anything else.
*/
if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two) &&
- !S_ISDIR(p->one->mode) && !S_ISDIR(p->two->mode) &&
+ object_type(p->one->mode) == OBJ_BLOB &&
+ object_type(p->two->mode) == OBJ_BLOB &&
!strcmp(p->one->path, p->two->path)) {
if (should_break(p->one, p->two,
break_score, &score)) {
diff --git a/diffcore-delta.c b/diffcore-delta.c
index d9729e5ec2..e670f85125 100644
--- a/diffcore-delta.c
+++ b/diffcore-delta.c
@@ -46,22 +46,6 @@ struct spanhash_top {
struct spanhash data[FLEX_ARRAY];
};
-static struct spanhash *spanhash_find(struct spanhash_top *top,
- unsigned int hashval)
-{
- int sz = 1 << top->alloc_log2;
- int bucket = hashval & (sz - 1);
- while (1) {
- struct spanhash *h = &(top->data[bucket++]);
- if (!h->cnt)
- return NULL;
- if (h->hashval == hashval)
- return h;
- if (sz <= bucket)
- bucket = 0;
- }
-}
-
static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig)
{
struct spanhash_top *new;
@@ -122,6 +106,20 @@ static struct spanhash_top *add_spanhash(struct spanhash_top *top,
}
}
+static int spanhash_cmp(const void *a_, const void *b_)
+{
+ const struct spanhash *a = a_;
+ const struct spanhash *b = b_;
+
+ /* A count of zero compares at the end.. */
+ if (!a->cnt)
+ return !b->cnt ? 0 : 1;
+ if (!b->cnt)
+ return -1;
+ return a->hashval < b->hashval ? -1 :
+ a->hashval > b->hashval ? 1 : 0;
+}
+
static struct spanhash_top *hash_chars(struct diff_filespec *one)
{
int i, n;
@@ -158,6 +156,10 @@ static struct spanhash_top *hash_chars(struct diff_filespec *one)
n = 0;
accum1 = accum2 = 0;
}
+ qsort(hash->data,
+ 1ul << hash->alloc_log2,
+ sizeof(hash->data[0]),
+ spanhash_cmp);
return hash;
}
@@ -169,7 +171,7 @@ int diffcore_count_changes(struct diff_filespec *src,
unsigned long *src_copied,
unsigned long *literal_added)
{
- int i, ssz;
+ struct spanhash *s, *d;
struct spanhash_top *src_count, *dst_count;
unsigned long sc, la;
@@ -190,22 +192,26 @@ int diffcore_count_changes(struct diff_filespec *src,
}
sc = la = 0;
- ssz = 1 << src_count->alloc_log2;
- for (i = 0; i < ssz; i++) {
- struct spanhash *s = &(src_count->data[i]);
- struct spanhash *d;
+ s = src_count->data;
+ d = dst_count->data;
+ for (;;) {
unsigned dst_cnt, src_cnt;
if (!s->cnt)
- continue;
+ break; /* we checked all in src */
+ while (d->cnt) {
+ if (d->hashval >= s->hashval)
+ break;
+ d++;
+ }
src_cnt = s->cnt;
- d = spanhash_find(dst_count, s->hashval);
- dst_cnt = d ? d->cnt : 0;
+ dst_cnt = d->hashval == s->hashval ? d->cnt : 0;
if (src_cnt < dst_cnt) {
la += dst_cnt - src_cnt;
sc += src_cnt;
}
else
sc += dst_cnt;
+ s++;
}
if (!src_count_p)
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 142e5376dd..3d377251be 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -4,6 +4,7 @@
#include "cache.h"
#include "diff.h"
#include "diffcore.h"
+#include "hash.h"
/* Table of rename/copy destinations */
@@ -55,12 +56,10 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
static struct diff_rename_src {
struct diff_filespec *one;
unsigned short score; /* to remember the break score */
- unsigned src_path_left : 1;
} *rename_src;
static int rename_src_nr, rename_src_alloc;
static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
- int src_path_left,
unsigned short score)
{
int first, last;
@@ -92,33 +91,9 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
(rename_src_nr - first - 1) * sizeof(*rename_src));
rename_src[first].one = one;
rename_src[first].score = score;
- rename_src[first].src_path_left = src_path_left;
return &(rename_src[first]);
}
-static int is_exact_match(struct diff_filespec *src,
- struct diff_filespec *dst,
- int contents_too)
-{
- if (src->sha1_valid && dst->sha1_valid &&
- !hashcmp(src->sha1, dst->sha1))
- return 1;
- if (!contents_too)
- return 0;
- if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1))
- return 0;
- if (src->size != dst->size)
- return 0;
- if (src->sha1_valid && dst->sha1_valid)
- return !hashcmp(src->sha1, dst->sha1);
- if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
- return 0;
- if (src->size == dst->size &&
- !memcmp(src->data, dst->data, src->size))
- return 1;
- return 0;
-}
-
static int basename_same(struct diff_filespec *src, struct diff_filespec *dst)
{
int src_len = strlen(src->path), dst_len = strlen(dst->path);
@@ -169,6 +144,20 @@ static int estimate_similarity(struct diff_filespec *src,
if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
return 0;
+ /*
+ * Need to check that source and destination sizes are
+ * filled in before comparing them.
+ *
+ * If we already have "cnt_data" filled in, we know it's
+ * all good (avoid checking the size for zero, as that
+ * is a possible size - we really should have a flag to
+ * say whether the size is valid or not!)
+ */
+ if (!src->cnt_data && diff_populate_filespec(src, 0))
+ return 0;
+ if (!dst->cnt_data && diff_populate_filespec(dst, 0))
+ return 0;
+
max_size = ((src->size > dst->size) ? src->size : dst->size);
base_size = ((src->size < dst->size) ? src->size : dst->size);
delta_size = max_size - base_size;
@@ -184,11 +173,6 @@ static int estimate_similarity(struct diff_filespec *src,
if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
return 0;
- if ((!src->cnt_data && diff_populate_filespec(src, 0))
- || (!dst->cnt_data && diff_populate_filespec(dst, 0)))
- return 0; /* error but caught downstream */
-
-
delta_limit = (unsigned long)
(base_size * (MAX_SCORE-minimum_score) / MAX_SCORE);
if (diffcore_count_changes(src, dst,
@@ -209,27 +193,25 @@ static int estimate_similarity(struct diff_filespec *src,
static void record_rename_pair(int dst_index, int src_index, int score)
{
- struct diff_filespec *one, *two, *src, *dst;
+ struct diff_filespec *src, *dst;
struct diff_filepair *dp;
if (rename_dst[dst_index].pair)
die("internal error: dst already matched.");
src = rename_src[src_index].one;
- one = alloc_filespec(src->path);
- fill_filespec(one, src->sha1, src->mode);
+ src->rename_used++;
+ src->count++;
dst = rename_dst[dst_index].two;
- two = alloc_filespec(dst->path);
- fill_filespec(two, dst->sha1, dst->mode);
+ dst->count++;
- dp = diff_queue(NULL, one, two);
+ dp = diff_queue(NULL, src, dst);
dp->renamed_pair = 1;
if (!strcmp(src->path, dst->path))
dp->score = rename_src[src_index].score;
else
dp->score = score;
- dp->source_stays = rename_src[src_index].src_path_left;
rename_dst[dst_index].pair = dp;
}
@@ -247,19 +229,162 @@ static int score_compare(const void *a_, const void *b_)
return b->score - a->score;
}
-static int compute_stays(struct diff_queue_struct *q,
- struct diff_filespec *one)
+struct file_similarity {
+ int src_dst, index;
+ struct diff_filespec *filespec;
+ struct file_similarity *next;
+};
+
+static int find_identical_files(struct file_similarity *src,
+ struct file_similarity *dst)
{
- int i;
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- if (strcmp(one->path, p->two->path))
- continue;
- if (DIFF_PAIR_RENAME(p)) {
- return 0; /* something else is renamed into this */
+ int renames = 0;
+
+ /*
+ * Walk over all the destinations ...
+ */
+ do {
+ struct diff_filespec *target = dst->filespec;
+ struct file_similarity *p, *best;
+ int i = 100, best_score = -1;
+
+ /*
+ * .. to find the best source match
+ */
+ best = NULL;
+ for (p = src; p; p = p->next) {
+ int score;
+ struct diff_filespec *source = p->filespec;
+
+ /* False hash collission? */
+ if (hashcmp(source->sha1, target->sha1))
+ continue;
+ /* Non-regular files? If so, the modes must match! */
+ if (!S_ISREG(source->mode) || !S_ISREG(target->mode)) {
+ if (source->mode != target->mode)
+ continue;
+ }
+ /* Give higher scores to sources that haven't been used already */
+ score = !source->rename_used;
+ score += basename_same(source, target);
+ if (score > best_score) {
+ best = p;
+ best_score = score;
+ if (score == 2)
+ break;
+ }
+
+ /* Too many identical alternatives? Pick one */
+ if (!--i)
+ break;
}
+ if (best) {
+ record_rename_pair(dst->index, best->index, MAX_SCORE);
+ renames++;
+ }
+ } while ((dst = dst->next) != NULL);
+ return renames;
+}
+
+static void free_similarity_list(struct file_similarity *p)
+{
+ while (p) {
+ struct file_similarity *entry = p;
+ p = p->next;
+ free(entry);
+ }
+}
+
+static int find_same_files(void *ptr)
+{
+ int ret;
+ struct file_similarity *p = ptr;
+ struct file_similarity *src = NULL, *dst = NULL;
+
+ /* Split the hash list up into sources and destinations */
+ do {
+ struct file_similarity *entry = p;
+ p = p->next;
+ if (entry->src_dst < 0) {
+ entry->next = src;
+ src = entry;
+ } else {
+ entry->next = dst;
+ dst = entry;
+ }
+ } while (p);
+
+ /*
+ * If we have both sources *and* destinations, see if
+ * we can match them up
+ */
+ ret = (src && dst) ? find_identical_files(src, dst) : 0;
+
+ /* Free the hashes and return the number of renames found */
+ free_similarity_list(src);
+ free_similarity_list(dst);
+ return ret;
+}
+
+static unsigned int hash_filespec(struct diff_filespec *filespec)
+{
+ unsigned int hash;
+ if (!filespec->sha1_valid) {
+ if (diff_populate_filespec(filespec, 0))
+ return 0;
+ hash_sha1_file(filespec->data, filespec->size, "blob", filespec->sha1);
+ }
+ memcpy(&hash, filespec->sha1, sizeof(hash));
+ return hash;
+}
+
+static void insert_file_table(struct hash_table *table, int src_dst, int index, struct diff_filespec *filespec)
+{
+ void **pos;
+ unsigned int hash;
+ struct file_similarity *entry = xmalloc(sizeof(*entry));
+
+ entry->src_dst = src_dst;
+ entry->index = index;
+ entry->filespec = filespec;
+ entry->next = NULL;
+
+ hash = hash_filespec(filespec);
+ pos = insert_hash(hash, entry, table);
+
+ /* We already had an entry there? */
+ if (pos) {
+ entry->next = *pos;
+ *pos = entry;
}
- return 1;
+}
+
+/*
+ * Find exact renames first.
+ *
+ * The first round matches up the up-to-date entries,
+ * and then during the second round we try to match
+ * cache-dirty entries as well.
+ */
+static int find_exact_renames(void)
+{
+ 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);
+
+ 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);
+
+ /* .. and free the hash data structure */
+ free_hash(&file_table);
+
+ return i;
}
void diffcore_rename(struct diff_options *options)
@@ -270,12 +395,11 @@ void diffcore_rename(struct diff_options *options)
struct diff_queue_struct *q = &diff_queued_diff;
struct diff_queue_struct outq;
struct diff_score *mx;
- int i, j, rename_count, contents_too;
+ int i, j, rename_count;
int num_create, num_src, dst_cnt;
if (!minimum_score)
minimum_score = DEFAULT_RENAME_SCORE;
- rename_count = 0;
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
@@ -289,81 +413,66 @@ void diffcore_rename(struct diff_options *options)
locate_rename_dst(p->two, 1);
}
else if (!DIFF_FILE_VALID(p->two)) {
- /* If the source is a broken "delete", and
+ /*
+ * If the source is a broken "delete", and
* they did not really want to get broken,
* that means the source actually stays.
+ * So we increment the "rename_used" score
+ * by one, to indicate ourselves as a user
+ */
+ if (p->broken_pair && !p->score)
+ p->one->rename_used++;
+ register_rename_src(p->one, p->score);
+ }
+ else if (detect_rename == DIFF_DETECT_COPY) {
+ /*
+ * Increment the "rename_used" score by
+ * one, to indicate ourselves as a user.
*/
- int stays = (p->broken_pair && !p->score);
- register_rename_src(p->one, stays, p->score);
+ p->one->rename_used++;
+ register_rename_src(p->one, p->score);
}
- else if (detect_rename == DIFF_DETECT_COPY)
- register_rename_src(p->one, 1, p->score);
}
if (rename_dst_nr == 0 || rename_src_nr == 0)
goto cleanup; /* nothing to do */
/*
+ * We really want to cull the candidates list early
+ * with cheap tests in order to avoid doing deltas.
+ */
+ rename_count = find_exact_renames();
+
+ /* Did we only want exact renames? */
+ if (minimum_score == MAX_SCORE)
+ goto cleanup;
+
+ /*
+ * Calculate how many renames are left (but all the source
+ * 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:
*
- * rename_dst_nr * rename_src_nr > rename_limit * rename_limit
+ * 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 (rename_dst_nr > rename_limit && rename_src_nr > rename_limit)
+ if (num_create > rename_limit && num_src > rename_limit)
goto cleanup;
- if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit)
+ if (num_create * num_src > rename_limit * rename_limit)
goto cleanup;
- /* We really want to cull the candidates list early
- * with cheap tests in order to avoid doing deltas.
- * The first round matches up the up-to-date entries,
- * and then during the second round we try to match
- * cache-dirty entries as well.
- */
- for (contents_too = 0; contents_too < 2; contents_too++) {
- for (i = 0; i < rename_dst_nr; i++) {
- struct diff_filespec *two = rename_dst[i].two;
- if (rename_dst[i].pair)
- continue; /* dealt with an earlier round */
- for (j = 0; j < rename_src_nr; j++) {
- int k;
- struct diff_filespec *one = rename_src[j].one;
- if (!is_exact_match(one, two, contents_too))
- continue;
-
- /* see if there is a basename match, too */
- for (k = j; k < rename_src_nr; k++) {
- one = rename_src[k].one;
- if (basename_same(one, two) &&
- is_exact_match(one, two,
- contents_too)) {
- j = k;
- break;
- }
- }
-
- record_rename_pair(i, j, (int)MAX_SCORE);
- rename_count++;
- break; /* we are done with this entry */
- }
- }
- }
-
- /* Have we run out the created file pool? If so we can avoid
- * doing the delta matrix altogether.
- */
- if (rename_count == rename_dst_nr)
- goto cleanup;
-
- if (minimum_score == MAX_SCORE)
- goto cleanup;
-
- num_create = (rename_dst_nr - rename_count);
- num_src = rename_src_nr;
mx = xmalloc(sizeof(*mx) * num_create * num_src);
for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
int base = dst_cnt * num_src;
@@ -388,6 +497,19 @@ void diffcore_rename(struct diff_options *options)
qsort(mx, num_create * num_src, sizeof(*mx), score_compare);
for (i = 0; i < num_create * num_src; i++) {
struct diff_rename_dst *dst = &rename_dst[mx[i].dst];
+ struct diff_filespec *src;
+ if (dst->pair)
+ continue; /* already done, either exact or fuzzy. */
+ if (mx[i].score < minimum_score)
+ break; /* there is no more usable pair. */
+ src = rename_src[mx[i].src].one;
+ if (src->rename_used)
+ continue;
+ record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
+ rename_count++;
+ }
+ for (i = 0; i < num_create * num_src; i++) {
+ struct diff_rename_dst *dst = &rename_dst[mx[i].dst];
if (dst->pair)
continue; /* already done, either exact or fuzzy. */
if (mx[i].score < minimum_score)
@@ -452,16 +574,7 @@ void diffcore_rename(struct diff_options *options)
pair_to_free = p;
}
else {
- for (j = 0; j < rename_dst_nr; j++) {
- if (!rename_dst[j].pair)
- continue;
- if (strcmp(rename_dst[j].pair->
- one->path,
- p->one->path))
- continue;
- break;
- }
- if (j < rename_dst_nr)
+ if (p->one->rename_used)
/* this path remains */
pair_to_free = p;
}
@@ -487,27 +600,8 @@ void diffcore_rename(struct diff_options *options)
*q = outq;
diff_debug_queue("done collapsing", q);
- /* We need to see which rename source really stays here;
- * earlier we only checked if the path is left in the result,
- * but even if a path remains in the result, if that is coming
- * from copying something else on top of it, then the original
- * source is lost and does not stay.
- */
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- if (DIFF_PAIR_RENAME(p) && p->source_stays) {
- /* If one appears as the target of a rename-copy,
- * then mark p->source_stays = 0; otherwise
- * leave it as is.
- */
- p->source_stays = compute_stays(q, p->one);
- }
- }
-
- for (i = 0; i < rename_dst_nr; i++) {
- diff_free_filespec_data(rename_dst[i].two);
- free(rename_dst[i].two);
- }
+ for (i = 0; i < rename_dst_nr; i++)
+ free_filespec(rename_dst[i].two);
free(rename_dst);
rename_dst = NULL;
diff --git a/diffcore.h b/diffcore.h
index eb618b1ec0..cc96c20734 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -29,7 +29,9 @@ struct diff_filespec {
void *cnt_data;
const char *funcname_pattern_ident;
unsigned long size;
+ int count; /* Reference count */
int xfrm_flags; /* for use by the xfrm */
+ int rename_used; /* Count of rename users */
unsigned short mode; /* file mode */
unsigned sha1_valid : 1; /* if true, use sha1 and trust mode;
* if false, use the name and read from
@@ -43,6 +45,7 @@ struct diff_filespec {
};
extern struct diff_filespec *alloc_filespec(const char *);
+extern void free_filespec(struct diff_filespec *);
extern void fill_filespec(struct diff_filespec *, const unsigned char *,
unsigned short);
@@ -56,7 +59,6 @@ struct diff_filepair {
struct diff_filespec *two;
unsigned short int score;
char status; /* M C R N D U (see Documentation/diff-format.txt) */
- unsigned source_stays : 1; /* all of R/C are copies */
unsigned broken_pair : 1;
unsigned renamed_pair : 1;
unsigned is_unmerged : 1;
diff --git a/dir.c b/dir.c
index eb6c3abd30..1f507daff2 100644
--- a/dir.c
+++ b/dir.c
@@ -17,6 +17,7 @@ struct path_simplify {
static int read_directory_recursive(struct dir_struct *dir,
const char *path, const char *base, int baselen,
int check_only, const struct path_simplify *simplify);
+static int get_dtype(struct dirent *de, const char *path);
int common_prefix(const char **pathspec)
{
@@ -118,25 +119,56 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre
return retval;
}
+static int no_wildcard(const char *string)
+{
+ return string[strcspn(string, "*?[{")] == '\0';
+}
+
void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which)
{
- struct exclude *x = xmalloc(sizeof (*x));
-
- x->pattern = string;
+ struct exclude *x;
+ size_t len;
+ int to_exclude = 1;
+ int flags = 0;
+
+ if (*string == '!') {
+ to_exclude = 0;
+ string++;
+ }
+ len = strlen(string);
+ if (len && string[len - 1] == '/') {
+ char *s;
+ x = xmalloc(sizeof(*x) + len);
+ s = (char*)(x+1);
+ memcpy(s, string, len - 1);
+ s[len - 1] = '\0';
+ string = s;
+ x->pattern = s;
+ flags = EXC_FLAG_MUSTBEDIR;
+ } else {
+ x = xmalloc(sizeof(*x));
+ x->pattern = string;
+ }
+ x->to_exclude = to_exclude;
+ x->patternlen = strlen(string);
x->base = base;
x->baselen = baselen;
- if (which->nr == which->alloc) {
- which->alloc = alloc_nr(which->alloc);
- which->excludes = xrealloc(which->excludes,
- which->alloc * sizeof(x));
- }
+ x->flags = flags;
+ if (!strchr(string, '/'))
+ x->flags |= EXC_FLAG_NODIR;
+ if (no_wildcard(string))
+ x->flags |= EXC_FLAG_NOWILDCARD;
+ if (*string == '*' && no_wildcard(string+1))
+ x->flags |= EXC_FLAG_ENDSWITH;
+ ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
which->excludes[which->nr++] = x;
}
static int add_excludes_from_file_1(const char *fname,
const char *base,
int baselen,
+ char **buf_p,
struct exclude_list *which)
{
struct stat st;
@@ -154,9 +186,14 @@ static int add_excludes_from_file_1(const char *fname,
}
buf = xmalloc(size+1);
if (read_in_full(fd, buf, size) != size)
+ {
+ free(buf);
goto err;
+ }
close(fd);
+ if (buf_p)
+ *buf_p = buf;
buf[size++] = '\n';
entry = buf;
for (i = 0; i < size; i++) {
@@ -178,38 +215,70 @@ static int add_excludes_from_file_1(const char *fname,
void add_excludes_from_file(struct dir_struct *dir, const char *fname)
{
- if (add_excludes_from_file_1(fname, "", 0,
+ if (add_excludes_from_file_1(fname, "", 0, NULL,
&dir->exclude_list[EXC_FILE]) < 0)
die("cannot use %s as an exclude file", fname);
}
-int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
{
- char exclude_file[PATH_MAX];
- struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
- int current_nr = el->nr;
-
- if (dir->exclude_per_dir) {
- memcpy(exclude_file, base, baselen);
- strcpy(exclude_file + baselen, dir->exclude_per_dir);
- add_excludes_from_file_1(exclude_file, base, baselen, el);
+ struct exclude_list *el;
+ struct exclude_stack *stk = NULL;
+ int current;
+
+ if ((!dir->exclude_per_dir) ||
+ (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
+ return; /* too long a path -- ignore */
+
+ /* Pop the ones that are not the prefix of the path being checked. */
+ el = &dir->exclude_list[EXC_DIRS];
+ while ((stk = dir->exclude_stack) != NULL) {
+ if (stk->baselen <= baselen &&
+ !strncmp(dir->basebuf, base, stk->baselen))
+ break;
+ dir->exclude_stack = stk->prev;
+ while (stk->exclude_ix < el->nr)
+ free(el->excludes[--el->nr]);
+ free(stk->filebuf);
+ free(stk);
}
- return current_nr;
-}
-void pop_exclude_per_directory(struct dir_struct *dir, int stk)
-{
- struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+ /* Read from the parent directories and push them down. */
+ current = stk ? stk->baselen : -1;
+ while (current < baselen) {
+ struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
+ const char *cp;
- while (stk < el->nr)
- free(el->excludes[--el->nr]);
+ if (current < 0) {
+ cp = base;
+ current = 0;
+ }
+ else {
+ cp = strchr(base + current + 1, '/');
+ if (!cp)
+ die("oops in prep_exclude");
+ cp++;
+ }
+ stk->prev = dir->exclude_stack;
+ stk->baselen = cp - base;
+ stk->exclude_ix = el->nr;
+ memcpy(dir->basebuf + current, base + current,
+ stk->baselen - current);
+ strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
+ add_excludes_from_file_1(dir->basebuf,
+ dir->basebuf, stk->baselen,
+ &stk->filebuf, el);
+ dir->exclude_stack = stk;
+ current = stk->baselen;
+ }
+ dir->basebuf[baselen] = '\0';
}
/* Scan the list and let the last match determines the fate.
* Return 1 for exclude, 0 for include and -1 for undecided.
*/
static int excluded_1(const char *pathname,
- int pathlen,
+ int pathlen, const char *basename, int *dtype,
struct exclude_list *el)
{
int i;
@@ -218,19 +287,28 @@ static int excluded_1(const char *pathname,
for (i = el->nr - 1; 0 <= i; i--) {
struct exclude *x = el->excludes[i];
const char *exclude = x->pattern;
- int to_exclude = 1;
+ int to_exclude = x->to_exclude;
- if (*exclude == '!') {
- to_exclude = 0;
- exclude++;
+ if (x->flags & EXC_FLAG_MUSTBEDIR) {
+ if (*dtype == DT_UNKNOWN)
+ *dtype = get_dtype(NULL, pathname);
+ if (*dtype != DT_DIR)
+ continue;
}
- if (!strchr(exclude, '/')) {
+ if (x->flags & EXC_FLAG_NODIR) {
/* match basename */
- const char *basename = strrchr(pathname, '/');
- basename = (basename) ? basename+1 : pathname;
- if (fnmatch(exclude, basename, 0) == 0)
- return to_exclude;
+ if (x->flags & EXC_FLAG_NOWILDCARD) {
+ if (!strcmp(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))
+ return to_exclude;
+ } else {
+ if (fnmatch(exclude, basename, 0) == 0)
+ return to_exclude;
+ }
}
else {
/* match with FNM_PATHNAME:
@@ -246,22 +324,31 @@ static int excluded_1(const char *pathname,
strncmp(pathname, x->base, baselen))
continue;
- if (fnmatch(exclude, pathname+baselen,
- FNM_PATHNAME) == 0)
- return to_exclude;
+ if (x->flags & EXC_FLAG_NOWILDCARD) {
+ if (!strcmp(exclude, pathname + baselen))
+ return to_exclude;
+ } else {
+ if (fnmatch(exclude, pathname+baselen,
+ FNM_PATHNAME) == 0)
+ return to_exclude;
+ }
}
}
}
return -1; /* undecided */
}
-int excluded(struct dir_struct *dir, const char *pathname)
+int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
{
int pathlen = strlen(pathname);
int st;
+ const char *basename = strrchr(pathname, '/');
+ basename = (basename) ? basename+1 : pathname;
+ prep_exclude(dir, pathname, basename-pathname);
for (st = EXC_CMDL; st <= EXC_FILE; st++) {
- switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
+ switch (excluded_1(pathname, pathlen, basename,
+ dtype_p, &dir->exclude_list[st])) {
case 0:
return 0;
case 1:
@@ -271,7 +358,8 @@ int excluded(struct dir_struct *dir, const char *pathname)
return 0;
}
-static struct dir_entry *dir_entry_new(const char *pathname, int len) {
+static struct dir_entry *dir_entry_new(const char *pathname, int len)
+{
struct dir_entry *ent;
ent = xmalloc(sizeof(*ent) + len + 1);
@@ -283,7 +371,7 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len) {
struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
{
- if (cache_name_pos(pathname, len) >= 0)
+ if (cache_name_exists(pathname, len))
return NULL;
ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
@@ -328,7 +416,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
break;
if (endchar == '/')
return index_directory;
- if (!endchar && S_ISGITLINK(ntohl(ce->ce_mode)))
+ if (!endchar && S_ISGITLINK(ce->ce_mode))
return index_gitdir;
}
return index_nonexistent;
@@ -443,6 +531,24 @@ static int in_pathspec(const char *path, int len, const struct path_simplify *si
return 0;
}
+static int get_dtype(struct dirent *de, const char *path)
+{
+ int dtype = de ? DTYPE(de) : DT_UNKNOWN;
+ struct stat st;
+
+ if (dtype != DT_UNKNOWN)
+ return dtype;
+ if (lstat(path, &st))
+ return dtype;
+ if (S_ISREG(st.st_mode))
+ return DT_REG;
+ if (S_ISDIR(st.st_mode))
+ return DT_DIR;
+ if (S_ISLNK(st.st_mode))
+ return DT_LNK;
+ return dtype;
+}
+
/*
* Read a directory tree. We currently ignore anything but
* directories, regular files and symlinks. That's because git
@@ -458,15 +564,12 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
int contents = 0;
if (fdir) {
- int exclude_stk;
struct dirent *de;
char fullname[PATH_MAX + 1];
memcpy(fullname, base, baselen);
- exclude_stk = push_exclude_per_directory(dir, base, baselen);
-
while ((de = readdir(fdir)) != NULL) {
- int len;
+ int len, dtype;
int exclude;
if ((de->d_name[0] == '.') &&
@@ -482,28 +585,36 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
if (simplify_away(fullname, baselen + len, simplify))
continue;
- exclude = excluded(dir, fullname);
+ dtype = DTYPE(de);
+ exclude = excluded(dir, fullname, &dtype);
if (exclude && dir->collect_ignored
&& in_pathspec(fullname, baselen + len, simplify))
dir_add_ignored(dir, fullname, baselen + len);
- if (exclude != dir->show_ignored) {
- if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
+
+ /*
+ * Excluded? If we don't explicitly want to show
+ * ignored files, ignore it
+ */
+ if (exclude && !dir->show_ignored)
+ continue;
+
+ if (dtype == DT_UNKNOWN)
+ dtype = get_dtype(de, fullname);
+
+ /*
+ * Do we want to see just the ignored files?
+ * We still need to recurse into directories,
+ * even if we don't ignore them, since the
+ * directory may contain files that we do..
+ */
+ if (!exclude && dir->show_ignored) {
+ if (dtype != DT_DIR)
continue;
- }
}
- switch (DTYPE(de)) {
- struct stat st;
+ switch (dtype) {
default:
continue;
- case DT_UNKNOWN:
- if (lstat(fullname, &st))
- continue;
- if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
- break;
- if (!S_ISDIR(st.st_mode))
- continue;
- /* fallthrough */
case DT_DIR:
memcpy(fullname + baselen + len, "/", 2);
len++;
@@ -532,8 +643,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
}
exit_early:
closedir(fdir);
-
- pop_exclude_per_directory(dir, exclude_stk);
}
return contents;
@@ -603,32 +712,6 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
{
struct path_simplify *simplify = create_simplify(pathspec);
- /*
- * Make sure to do the per-directory exclude for all the
- * directories leading up to our base.
- */
- if (baselen) {
- if (dir->exclude_per_dir) {
- char *p, *pp = xmalloc(baselen+1);
- memcpy(pp, base, baselen+1);
- p = pp;
- while (1) {
- char save = *p;
- *p = 0;
- push_exclude_per_directory(dir, pp, p-pp);
- *p++ = save;
- if (!save)
- break;
- p = strchr(p, '/');
- if (p)
- p++;
- else
- p = pp + baselen;
- }
- free(pp);
- }
- }
-
read_directory_recursive(dir, path, base, baselen, 0, simplify);
free_simplify(simplify);
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
@@ -636,11 +719,10 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
return dir->nr;
}
-int
-file_exists(const char *f)
+int file_exists(const char *f)
{
- struct stat sb;
- return stat(f, &sb) == 0;
+ struct stat sb;
+ return lstat(f, &sb) == 0;
}
/*
@@ -685,3 +767,56 @@ int is_inside_dir(const char *dir)
char buffer[PATH_MAX];
return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
}
+
+int remove_dir_recursively(struct strbuf *path, int only_empty)
+{
+ DIR *dir = opendir(path->buf);
+ struct dirent *e;
+ int ret = 0, original_len = path->len, len;
+
+ if (!dir)
+ return -1;
+ if (path->buf[original_len - 1] != '/')
+ strbuf_addch(path, '/');
+
+ len = path->len;
+ while ((e = readdir(dir)) != NULL) {
+ struct stat st;
+ if ((e->d_name[0] == '.') &&
+ ((e->d_name[1] == 0) ||
+ ((e->d_name[1] == '.') && e->d_name[2] == 0)))
+ continue; /* "." and ".." */
+
+ strbuf_setlen(path, len);
+ strbuf_addstr(path, e->d_name);
+ if (lstat(path->buf, &st))
+ ; /* fall thru */
+ else if (S_ISDIR(st.st_mode)) {
+ if (!remove_dir_recursively(path, only_empty))
+ continue; /* happy */
+ } else if (!only_empty && !unlink(path->buf))
+ continue; /* happy, too */
+
+ /* path too long, stat fails, or non-directory still exists */
+ ret = -1;
+ break;
+ }
+ closedir(dir);
+
+ strbuf_setlen(path, original_len);
+ if (!ret)
+ ret = rmdir(path->buf);
+ return ret;
+}
+
+void setup_standard_excludes(struct dir_struct *dir)
+{
+ const char *path;
+
+ dir->exclude_per_dir = ".gitignore";
+ path = git_path("info/exclude");
+ if (!access(path, R_OK))
+ add_excludes_from_file(dir, path);
+ if (excludes_file && !access(excludes_file, R_OK))
+ add_excludes_from_file(dir, excludes_file);
+}
diff --git a/dir.h b/dir.h
index f55a87b2cd..2df15defb6 100644
--- a/dir.h
+++ b/dir.h
@@ -1,32 +1,36 @@
#ifndef DIR_H
#define DIR_H
-/*
- * We maintain three exclude pattern lists:
- * EXC_CMDL lists patterns explicitly given on the command line.
- * EXC_DIRS lists patterns obtained from per-directory ignore files.
- * EXC_FILE lists patterns from fallback ignore files.
- */
-#define EXC_CMDL 0
-#define EXC_DIRS 1
-#define EXC_FILE 2
-
-
struct dir_entry {
unsigned int len;
char name[FLEX_ARRAY]; /* more */
};
+#define EXC_FLAG_NODIR 1
+#define EXC_FLAG_NOWILDCARD 2
+#define EXC_FLAG_ENDSWITH 4
+#define EXC_FLAG_MUSTBEDIR 8
+
struct exclude_list {
int nr;
int alloc;
struct exclude {
const char *pattern;
+ int patternlen;
const char *base;
int baselen;
+ int to_exclude;
+ int flags;
} **excludes;
};
+struct exclude_stack {
+ struct exclude_stack *prev;
+ char *filebuf;
+ int baselen;
+ int exclude_ix;
+};
+
struct dir_struct {
int nr, alloc;
int ignored_nr, ignored_alloc;
@@ -41,6 +45,18 @@ struct dir_struct {
/* Exclude info */
const char *exclude_per_dir;
struct exclude_list exclude_list[3];
+ /*
+ * We maintain three exclude pattern lists:
+ * EXC_CMDL lists patterns explicitly given on the command line.
+ * EXC_DIRS lists patterns obtained from per-directory ignore files.
+ * EXC_FILE lists patterns from fallback ignore files.
+ */
+#define EXC_CMDL 0
+#define EXC_DIRS 1
+#define EXC_FILE 2
+
+ struct exclude_stack *exclude_stack;
+ char basebuf[PATH_MAX];
};
extern int common_prefix(const char **pathspec);
@@ -51,10 +67,8 @@ extern int common_prefix(const char **pathspec);
extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);
-extern int push_exclude_per_directory(struct dir_struct *, const char *, int);
-extern void pop_exclude_per_directory(struct dir_struct *, int);
-extern int excluded(struct dir_struct *, const char *);
+extern int excluded(struct dir_struct *, const char *, int *);
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);
@@ -64,4 +78,7 @@ extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathna
extern char *get_relative_cwd(char *buffer, int size, const char *dir);
extern int is_inside_dir(const char *dir);
+extern void setup_standard_excludes(struct dir_struct *dir);
+extern int remove_dir_recursively(struct strbuf *path, int only_empty);
+
#endif
diff --git a/entry.c b/entry.c
index 98f5f6d4ec..44f4b897d4 100644
--- a/entry.c
+++ b/entry.c
@@ -103,7 +103,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
int fd;
long wrote;
- switch (ntohl(ce->ce_mode) & S_IFMT) {
+ switch (ce->ce_mode & S_IFMT) {
char *new;
struct strbuf buf;
unsigned long size;
@@ -119,15 +119,17 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
*/
strbuf_init(&buf, 0);
if (convert_to_working_tree(ce->name, new, size, &buf)) {
+ size_t newsize = 0;
free(new);
- new = strbuf_detach(&buf, &size);
+ new = strbuf_detach(&buf, &newsize);
+ size = newsize;
}
if (to_tempfile) {
strcpy(path, ".merge_file_XXXXXX");
fd = mkstemp(path);
} else
- fd = create_file(path, ntohl(ce->ce_mode));
+ fd = create_file(path, ce->ce_mode);
if (fd < 0) {
free(new);
return error("git-checkout-index: unable to create file %s (%s)",
@@ -201,7 +203,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
strcpy(path + len, ce->name);
if (!lstat(path, &st)) {
- unsigned changed = ce_match_stat(ce, &st, 1);
+ unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
if (!changed)
return 0;
if (!state->force) {
@@ -219,7 +221,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
unlink(path);
if (S_ISDIR(st.st_mode)) {
/* If it is a gitlink, leave it alone! */
- if (S_ISGITLINK(ntohl(ce->ce_mode)))
+ if (S_ISGITLINK(ce->ce_mode))
return 0;
if (!state->force)
return error("%s is a directory", path);
diff --git a/environment.c b/environment.c
index b5a6c69f7c..3527f1663f 100644
--- a/environment.c
+++ b/environment.c
@@ -30,11 +30,13 @@ int core_compression_seen;
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;
-char *pager_program;
-int pager_in_use;
+const char *pager_program;
int pager_use_color = 1;
-char *editor_program;
+const char *editor_program;
+const char *excludes_file;
int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */
+enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
+unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
/* This is set by setup_git_dir_gently() and/or git_default_config() */
char *git_work_tree_cfg;
diff --git a/exec_cmd.c b/exec_cmd.c
index 9b74ed2f42..e189caca62 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -5,11 +5,11 @@
extern char **environ;
static const char *builtin_exec_path = GIT_EXEC_PATH;
-static const char *current_exec_path;
+static const char *argv_exec_path;
-void git_set_exec_path(const char *exec_path)
+void git_set_argv_exec_path(const char *exec_path)
{
- current_exec_path = exec_path;
+ argv_exec_path = exec_path;
}
@@ -18,8 +18,8 @@ const char *git_exec_path(void)
{
const char *env;
- if (current_exec_path)
- return current_exec_path;
+ if (argv_exec_path)
+ return argv_exec_path;
env = getenv(EXEC_PATH_ENVIRONMENT);
if (env && *env) {
@@ -29,85 +29,69 @@ const char *git_exec_path(void)
return builtin_exec_path;
}
+static void add_path(struct strbuf *out, const char *path)
+{
+ if (path && *path) {
+ if (is_absolute_path(path))
+ strbuf_addstr(out, path);
+ else
+ strbuf_addstr(out, make_absolute_path(path));
+
+ strbuf_addch(out, ':');
+ }
+}
+
+void setup_path(const char *cmd_path)
+{
+ const char *old_path = getenv("PATH");
+ struct strbuf new_path;
+
+ strbuf_init(&new_path, 0);
+
+ add_path(&new_path, argv_exec_path);
+ add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT));
+ add_path(&new_path, builtin_exec_path);
+ add_path(&new_path, cmd_path);
+
+ if (old_path)
+ strbuf_addstr(&new_path, old_path);
+ else
+ strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin");
+
+ setenv("PATH", new_path.buf, 1);
+
+ strbuf_release(&new_path);
+}
int execv_git_cmd(const char **argv)
{
- char git_command[PATH_MAX + 1];
- int i;
- const char *paths[] = { current_exec_path,
- getenv(EXEC_PATH_ENVIRONMENT),
- builtin_exec_path };
-
- for (i = 0; i < ARRAY_SIZE(paths); ++i) {
- size_t len;
- int rc;
- const char *exec_dir = paths[i];
- const char *tmp;
-
- if (!exec_dir || !*exec_dir) continue;
-
- if (*exec_dir != '/') {
- if (!getcwd(git_command, sizeof(git_command))) {
- fprintf(stderr, "git: cannot determine "
- "current directory: %s\n",
- strerror(errno));
- break;
- }
- len = strlen(git_command);
-
- /* Trivial cleanup */
- while (!prefixcmp(exec_dir, "./")) {
- exec_dir += 2;
- while (*exec_dir == '/')
- exec_dir++;
- }
-
- rc = snprintf(git_command + len,
- sizeof(git_command) - len, "/%s",
- exec_dir);
- if (rc < 0 || rc >= sizeof(git_command) - len) {
- fprintf(stderr, "git: command name given "
- "is too long.\n");
- break;
- }
- } else {
- if (strlen(exec_dir) + 1 > sizeof(git_command)) {
- fprintf(stderr, "git: command name given "
- "is too long.\n");
- break;
- }
- strcpy(git_command, exec_dir);
- }
-
- len = strlen(git_command);
- rc = snprintf(git_command + len, sizeof(git_command) - len,
- "/git-%s", argv[0]);
- if (rc < 0 || rc >= sizeof(git_command) - len) {
- fprintf(stderr,
- "git: command name given is too long.\n");
- break;
- }
+ struct strbuf cmd;
+ const char *tmp;
- /* argv[0] must be the git command, but the argv array
- * belongs to the caller, and my be reused in
- * subsequent loop iterations. Save argv[0] and
- * restore it on error.
- */
+ strbuf_init(&cmd, 0);
+ strbuf_addf(&cmd, "git-%s", argv[0]);
- tmp = argv[0];
- argv[0] = git_command;
+ /*
+ * argv[0] must be the git command, but the argv array
+ * belongs to the caller, and may be reused in
+ * subsequent loop iterations. Save argv[0] and
+ * restore it on error.
+ */
+ tmp = argv[0];
+ argv[0] = cmd.buf;
- trace_argv_printf(argv, -1, "trace: exec:");
+ trace_argv_printf(argv, "trace: exec:");
- /* execve() can only ever return if it fails */
- execve(git_command, (char **)argv, environ);
+ /* execvp() can only ever return if it fails */
+ execvp(cmd.buf, (char **)argv);
- trace_printf("trace: exec failed: %s\n", strerror(errno));
+ trace_printf("trace: exec failed: %s\n", strerror(errno));
- argv[0] = tmp;
- }
- return -1;
+ argv[0] = tmp;
+ strbuf_release(&cmd);
+
+ return -1;
}
diff --git a/exec_cmd.h b/exec_cmd.h
index 849a8395a0..a892355c82 100644
--- a/exec_cmd.h
+++ b/exec_cmd.h
@@ -1,8 +1,9 @@
#ifndef GIT_EXEC_CMD_H
#define GIT_EXEC_CMD_H
-extern void git_set_exec_path(const char *exec_path);
+extern void git_set_argv_exec_path(const char *exec_path);
extern const char* git_exec_path(void);
+extern void setup_path(const char *);
extern int execv_git_cmd(const char **argv); /* NULL terminated */
extern int execl_git_cmd(const char *cmd, ...);
diff --git a/fast-import.c b/fast-import.c
index e9c80be4cd..0d3449f2ce 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -153,13 +153,16 @@ Format of STDIN stream:
#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 *next;
uint32_t offset;
- unsigned type : TYPE_BITS;
- unsigned pack_id : PACK_ID_BITS;
+ uint32_t type : TYPE_BITS,
+ pack_id : PACK_ID_BITS,
+ depth : DEPTH_BITS;
unsigned char sha1[20];
};
@@ -193,7 +196,7 @@ struct mem_pool
struct mem_pool *next_pool;
char *next_free;
char *end;
- char space[FLEX_ARRAY]; /* more */
+ uintmax_t space[FLEX_ARRAY]; /* more */
};
struct atom_str
@@ -272,6 +275,8 @@ struct recent_command
static unsigned long max_depth = 10;
static off_t max_packsize = (1LL << 32) - 1;
static int force_update;
+static int pack_compression_level = Z_DEFAULT_COMPRESSION;
+static int pack_compression_seen;
/* Stats and misc. counters */
static uintmax_t alloc_count;
@@ -367,6 +372,8 @@ static void write_branch_report(FILE *rpt, struct branch *b)
fputc('\n', rpt);
}
+static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
+
static void write_crash_report(const char *err)
{
char *loc = git_path("fast_import_crash_%d", getpid());
@@ -425,12 +432,37 @@ static void write_crash_report(const char *err)
write_branch_report(rpt, b);
}
+ if (first_tag) {
+ struct tag *tg;
+ fputc('\n', rpt);
+ fputs("Annotated Tags\n", rpt);
+ fputs("--------------\n", rpt);
+ for (tg = first_tag; tg; tg = tg->next_tag) {
+ fputs(sha1_to_hex(tg->sha1), rpt);
+ fputc(' ', rpt);
+ fputs(tg->name, rpt);
+ fputc('\n', rpt);
+ }
+ }
+
+ fputc('\n', rpt);
+ fputs("Marks\n", rpt);
+ fputs("-----\n", rpt);
+ if (mark_file)
+ fprintf(rpt, " exported to %s\n", mark_file);
+ else
+ dump_marks_helper(rpt, 0, marks);
+
fputc('\n', rpt);
fputs("-------------------\n", rpt);
fputs("END OF CRASH REPORT\n", rpt);
fclose(rpt);
}
+static void end_packfile(void);
+static void unkeep_all_packs(void);
+static void dump_marks(void);
+
static NORETURN void die_nicely(const char *err, va_list params)
{
static int zombie;
@@ -444,6 +476,9 @@ static NORETURN void die_nicely(const char *err, va_list params)
if (!zombie) {
zombie = 1;
write_crash_report(message);
+ end_packfile();
+ unkeep_all_packs();
+ dump_marks();
}
exit(128);
}
@@ -531,15 +566,15 @@ static void *pool_alloc(size_t len)
total_allocd += sizeof(struct mem_pool) + mem_pool_alloc;
p = xmalloc(sizeof(struct mem_pool) + mem_pool_alloc);
p->next_pool = mem_pool;
- p->next_free = p->space;
+ p->next_free = (char *) p->space;
p->end = p->next_free + mem_pool_alloc;
mem_pool = p;
}
r = p->next_free;
- /* round out to a pointer alignment */
- if (len & (sizeof(void*) - 1))
- len += sizeof(void*) - (len & (sizeof(void*) - 1));
+ /* round out to a 'uintmax_t' alignment */
+ if (len & (sizeof(uintmax_t) - 1))
+ len += sizeof(uintmax_t) - (len & (sizeof(uintmax_t) - 1));
p->next_free += len;
return r;
}
@@ -639,8 +674,9 @@ static struct branch *new_branch(const char *name)
if (b)
die("Invalid attempt to create duplicate branch: %s", name);
switch (check_ref_format(name)) {
- case 0: break; /* its valid */
- case -2: break; /* valid, but too few '/', allow anyway */
+ case 0: break; /* its valid */
+ case CHECK_REF_FORMAT_ONELEVEL:
+ break; /* valid, but too few '/', allow anyway */
default:
die("Branch name doesn't conform to GIT standards: %s", name);
}
@@ -874,8 +910,9 @@ static char *keep_pack(char *curr_index_name)
keep_fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
if (keep_fd < 0)
die("cannot create keep file");
- write(keep_fd, keep_msg, strlen(keep_msg));
- close(keep_fd);
+ write_or_die(keep_fd, keep_msg, strlen(keep_msg));
+ if (close(keep_fd))
+ die("failed to write keep file");
snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
get_object_directory(), sha1_to_hex(pack_data->sha1));
@@ -912,6 +949,7 @@ static void end_packfile(void)
struct branch *b;
struct tag *t;
+ close_pack_windows(pack_data);
fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1,
pack_data->pack_name, object_count);
close(pack_data->pack_fd);
@@ -921,7 +959,6 @@ static void end_packfile(void)
new_p = add_packed_git(idx_name, strlen(idx_name), 1);
if (!new_p)
die("core git rejected index %s", idx_name);
- new_p->windows = old_p->windows;
all_packs[pack_id] = new_p;
install_packed_git(new_p);
@@ -1033,7 +1070,7 @@ static int store_object(
delta = NULL;
memset(&s, 0, sizeof(s));
- deflateInit(&s, zlib_compression_level);
+ deflateInit(&s, pack_compression_level);
if (delta) {
s.next_in = delta;
s.avail_in = deltalen;
@@ -1061,7 +1098,7 @@ static int store_object(
delta = NULL;
memset(&s, 0, sizeof(s));
- deflateInit(&s, zlib_compression_level);
+ deflateInit(&s, pack_compression_level);
s.next_in = (void *)dat->buf;
s.avail_in = dat->len;
s.avail_out = deflateBound(&s, s.avail_in);
@@ -1083,7 +1120,7 @@ static int store_object(
unsigned pos = sizeof(hdr) - 1;
delta_count_by_type[type]++;
- last->depth++;
+ e->depth = last->depth + 1;
hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr);
write_or_die(pack_data->pack_fd, hdr, hdrlen);
@@ -1095,8 +1132,7 @@ static int store_object(
write_or_die(pack_data->pack_fd, hdr + pos, sizeof(hdr) - pos);
pack_size += sizeof(hdr) - pos;
} else {
- if (last)
- last->depth = 0;
+ e->depth = 0;
hdrlen = encode_header(type, dat->len, hdr);
write_or_die(pack_data->pack_fd, hdr, hdrlen);
pack_size += hdrlen;
@@ -1114,18 +1150,54 @@ static int store_object(
strbuf_swap(&last->data, dat);
}
last->offset = e->offset;
+ last->depth = e->depth;
}
return 0;
}
+/* All calls must be guarded by find_object() or find_mark() to
+ * ensure the 'struct object_entry' passed was written by this
+ * process instance. We unpack the entry by the offset, avoiding
+ * the need for the corresponding .idx file. This unpacking rule
+ * works because we only use OBJ_REF_DELTA within the packfiles
+ * created by fast-import.
+ *
+ * oe must not be NULL. Such an oe usually comes from giving
+ * an unknown SHA-1 to find_object() or an undefined mark to
+ * find_mark(). Callers must test for this condition and use
+ * the standard read_sha1_file() when it happens.
+ *
+ * oe->pack_id must not be MAX_PACK_ID. Such an oe is usually from
+ * find_mark(), where the mark was reloaded from an existing marks
+ * file and is referencing an object that this fast-import process
+ * instance did not write out to a packfile. Callers must test for
+ * this condition and use read_sha1_file() instead.
+ */
static void *gfi_unpack_entry(
struct object_entry *oe,
unsigned long *sizep)
{
enum object_type type;
struct packed_git *p = all_packs[oe->pack_id];
- if (p == pack_data)
+ if (p == pack_data && p->pack_size < (pack_size + 20)) {
+ /* The object is stored in the packfile we are writing to
+ * and we have modified it since the last time we scanned
+ * back to read a previously written object. If an old
+ * window covered [p->pack_size, p->pack_size + 20) its
+ * data is stale and is not valid. Closing all windows
+ * and updating the packfile length ensures we can read
+ * the newly written data.
+ */
+ close_pack_windows(p);
+
+ /* We have to offer 20 bytes additional on the end of
+ * the packfile as the core unpacker code assumes the
+ * footer is present at the file end and must promise
+ * at least 20 bytes within any window it maps. But
+ * we don't actually create the footer here.
+ */
p->pack_size = pack_size + 20;
+ }
return unpack_entry(p, oe->offset, &type, sizep);
}
@@ -1160,8 +1232,10 @@ static void load_tree(struct tree_entry *root)
if (myoe && myoe->pack_id != MAX_PACK_ID) {
if (myoe->type != OBJ_TREE)
die("Not a tree: %s", sha1_to_hex(sha1));
- t->delta_depth = 0;
+ t->delta_depth = myoe->depth;
buf = gfi_unpack_entry(myoe, &size);
+ if (!buf)
+ die("Can't load tree %s", sha1_to_hex(sha1));
} else {
enum object_type type;
buf = read_sha1_file(sha1, &type, &size);
@@ -1534,17 +1608,36 @@ static void dump_marks(void)
f = fdopen(mark_fd, "w");
if (!f) {
+ int saved_errno = errno;
rollback_lock_file(&mark_lock);
failure |= error("Unable to write marks file %s: %s",
- mark_file, strerror(errno));
+ mark_file, strerror(saved_errno));
return;
}
+ /*
+ * Since the lock file was fdopen()'ed, it should not be close()'ed.
+ * Assign -1 to the lock file descriptor so that commit_lock_file()
+ * won't try to close() it.
+ */
+ mark_lock.fd = -1;
+
dump_marks_helper(f, 0, marks);
- fclose(f);
- if (commit_lock_file(&mark_lock))
+ if (ferror(f) || fclose(f)) {
+ int saved_errno = errno;
+ rollback_lock_file(&mark_lock);
failure |= error("Unable to write marks file %s: %s",
- mark_file, strerror(errno));
+ mark_file, strerror(saved_errno));
+ return;
+ }
+
+ if (commit_lock_file(&mark_lock)) {
+ int saved_errno = errno;
+ rollback_lock_file(&mark_lock);
+ failure |= error("Unable to commit marks file %s: %s",
+ mark_file, strerror(saved_errno));
+ return;
+ }
}
static int read_next_command(void)
@@ -1616,6 +1709,7 @@ static void cmd_data(struct strbuf *sb)
char *term = xstrdup(command_buf.buf + 5 + 2);
size_t term_len = command_buf.len - 5 - 2;
+ strbuf_detach(&command_buf, NULL);
for (;;) {
if (strbuf_getline(&command_buf, stdin, '\n') == EOF)
die("EOF in data (terminator '%s' not found)", term);
@@ -1817,7 +1911,7 @@ static void file_change_m(struct branch *b)
} else if (oe) {
if (oe->type != OBJ_BLOB)
die("Not a blob (actually a %s): %s",
- command_buf.buf, typename(oe->type));
+ typename(oe->type), command_buf.buf);
} else {
enum object_type type = sha1_object_info(sha1, NULL);
if (type < 0)
@@ -2255,6 +2349,27 @@ static void import_marks(const char *input_file)
fclose(f);
}
+static int git_pack_config(const char *k, const char *v)
+{
+ if (!strcmp(k, "pack.depth")) {
+ max_depth = git_config_int(k, v);
+ if (max_depth > MAX_DEPTH)
+ max_depth = MAX_DEPTH;
+ return 0;
+ }
+ if (!strcmp(k, "pack.compression")) {
+ int level = git_config_int(k, v);
+ if (level == -1)
+ level = Z_DEFAULT_COMPRESSION;
+ else if (level < 0 || level > Z_BEST_COMPRESSION)
+ die("bad pack compression level %d", level);
+ pack_compression_level = level;
+ pack_compression_seen = 1;
+ return 0;
+ }
+ return git_default_config(k, v);
+}
+
static const char fast_import_usage[] =
"git-fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
@@ -2262,7 +2377,10 @@ int main(int argc, const char **argv)
{
unsigned int i, show_stats = 1;
- git_config(git_default_config);
+ git_config(git_pack_config);
+ if (!pack_compression_seen && core_compression_seen)
+ pack_compression_level = core_compression_level;
+
alloc_objects(object_entry_alloc);
strbuf_init(&command_buf, 0);
atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*));
@@ -2288,8 +2406,11 @@ int main(int argc, const char **argv)
}
else if (!prefixcmp(a, "--max-pack-size="))
max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024;
- else if (!prefixcmp(a, "--depth="))
+ else if (!prefixcmp(a, "--depth=")) {
max_depth = strtoul(a + 8, NULL, 0);
+ if (max_depth > MAX_DEPTH)
+ die("--depth cannot exceed %u", MAX_DEPTH);
+ }
else if (!prefixcmp(a, "--active-branches="))
max_active_branches = strtoul(a + 18, NULL, 0);
else if (!prefixcmp(a, "--import-marks="))
diff --git a/fetch-pack.h b/fetch-pack.h
new file mode 100644
index 0000000000..8d35ef60bf
--- /dev/null
+++ b/fetch-pack.h
@@ -0,0 +1,26 @@
+#ifndef FETCH_PACK_H
+#define FETCH_PACK_H
+
+struct fetch_pack_args
+{
+ const char *uploadpack;
+ int unpacklimit;
+ int depth;
+ unsigned quiet:1,
+ keep_pack:1,
+ lock_pack:1,
+ use_thin_pack:1,
+ fetch_all:1,
+ verbose:1,
+ no_progress:1;
+};
+
+struct ref *fetch_pack(struct fetch_pack_args *args,
+ int fd[], struct child_process *conn,
+ const struct ref *ref,
+ const char *dest,
+ int nr_heads,
+ char **heads,
+ char **pack_lockfile);
+
+#endif
diff --git a/fetch.h b/fetch.h
deleted file mode 100644
index be48c6f190..0000000000
--- a/fetch.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef PULL_H
-#define PULL_H
-
-/*
- * Fetch object given SHA1 from the remote, and store it locally under
- * GIT_OBJECT_DIRECTORY. Return 0 on success, -1 on failure. To be
- * provided by the particular implementation.
- */
-extern int fetch(unsigned char *sha1);
-
-/*
- * Fetch the specified object and store it locally; fetch() will be
- * called later to determine success. To be provided by the particular
- * implementation.
- */
-extern void prefetch(unsigned char *sha1);
-
-/*
- * Fetch ref (relative to $GIT_DIR/refs) from the remote, and store
- * the 20-byte SHA1 in sha1. Return 0 on success, -1 on failure. To
- * be provided by the particular implementation.
- */
-extern int fetch_ref(char *ref, unsigned char *sha1);
-
-/* Set to fetch the target tree. */
-extern int get_tree;
-
-/* Set to fetch the commit history. */
-extern int get_history;
-
-/* Set to fetch the trees in the commit history. */
-extern int get_all;
-
-/* Set to be verbose */
-extern int get_verbosely;
-
-/* Set to check on all reachable objects. */
-extern int get_recover;
-
-/* Report what we got under get_verbosely */
-extern void pull_say(const char *, const char *);
-
-/* Load pull targets from stdin */
-extern int pull_targets_stdin(char ***target, const char ***write_ref);
-
-/* Free up loaded targets */
-extern void pull_targets_free(int targets, char **target, const char **write_ref);
-
-/* If write_ref is set, the ref filename to write the target value to. */
-/* If write_ref_log_details is set, additional text will appear in the ref log. */
-extern int pull(int targets, char **target, const char **write_ref,
- const char *write_ref_log_details);
-
-#endif /* PULL_H */
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index 17df47b950..a2913c2a2c 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -9,35 +9,8 @@ struct cmdname_help
static struct cmdname_help common_cmds[] = {"
-sort <<\EOF |
-add
-apply
-archive
-bisect
-branch
-checkout
-cherry-pick
-clone
-commit
-diff
-fetch
-grep
-init
-log
-merge
-mv
-prune
-pull
-push
-rebase
-reset
-revert
-rm
-show
-show-branch
-status
-tag
-EOF
+sed -n -e 's/^git-\([^ ]*\)[ ].* common.*/\1/p' command-list.txt |
+sort |
while read cmd
do
sed -n '
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index ac598f88e6..a0a81f134a 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -1,6 +1,45 @@
#!/usr/bin/perl -w
use strict;
+use Git;
+
+my $repo = Git->repository();
+
+my $menu_use_color = $repo->get_colorbool('color.interactive');
+my ($prompt_color, $header_color, $help_color) =
+ $menu_use_color ? (
+ $repo->get_color('color.interactive.prompt', 'bold blue'),
+ $repo->get_color('color.interactive.header', 'bold'),
+ $repo->get_color('color.interactive.help', 'red bold'),
+ ) : ();
+
+my $diff_use_color = $repo->get_colorbool('color.diff');
+my ($fraginfo_color) =
+ $diff_use_color ? (
+ $repo->get_color('color.diff.frag', 'cyan'),
+ ) : ();
+
+my $normal_color = $repo->get_color("", "reset");
+
+sub colored {
+ my $color = shift;
+ my $string = join("", @_);
+
+ if (defined $color) {
+ # Put a color code at the beginning of each line, a reset at the end
+ # color after newlines that are not at the end of the string
+ $string =~ s/(\n+)(.)/$1$color$2/g;
+ # reset before newlines
+ $string =~ s/(\n+)/$normal_color$1/g;
+ # codes at beginning and end (if necessary):
+ $string =~ s/^/$color/;
+ $string =~ s/$/$normal_color/ unless $string =~ /\n$/;
+ }
+ return $string;
+}
+
+# command line options
+my $patch_mode;
sub run_cmd_pipe {
if ($^O eq 'MSWin32') {
@@ -37,17 +76,26 @@ sub list_untracked {
chomp $_;
$_;
}
- run_cmd_pipe(qw(git ls-files --others
- --exclude-per-directory=.gitignore),
- "--exclude-from=$GIT_DIR/info/exclude",
- '--', @_);
+ run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
}
my $status_fmt = '%12s %12s %s';
my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
+{
+ my $initial;
+ sub is_initial_commit {
+ $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
+ unless defined $initial;
+ return $initial;
+ }
+}
+
+sub get_empty_tree {
+ return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
+}
+
# Returns list of hashes, contents of each of which are:
-# PRINT: print message
# VALUE: pathname
# BINARY: is a binary path
# INDEX: is index different from HEAD?
@@ -59,9 +107,19 @@ sub list_modified {
my ($only) = @_;
my (%data, @return);
my ($add, $del, $adddel, $file);
+ my @tracked = ();
+
+ if (@ARGV) {
+ @tracked = map {
+ chomp $_; $_;
+ } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV);
+ return if (!@tracked);
+ }
+ my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
for (run_cmd_pipe(qw(git diff-index --cached
- --numstat --summary HEAD))) {
+ --numstat --summary), $reference,
+ '--', @tracked)) {
if (($add, $del, $file) =
/^([-\d]+) ([-\d]+) (.*)/) {
my ($change, $bin);
@@ -84,7 +142,7 @@ sub list_modified {
}
}
- for (run_cmd_pipe(qw(git diff-files --numstat --summary))) {
+ for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) {
if (($add, $del, $file) =
/^([-\d]+) ([-\d]+) (.*)/) {
if (!exists $data{$file}) {
@@ -125,8 +183,6 @@ sub list_modified {
}
push @return, +{
VALUE => $_,
- PRINT => (sprintf $status_fmt,
- $it->{INDEX}, $it->{FILE}, $_),
%$it,
};
}
@@ -162,10 +218,106 @@ sub find_unique {
return $found;
}
+# inserts string into trie and updates count for each character
+sub update_trie {
+ my ($trie, $string) = @_;
+ foreach (split //, $string) {
+ $trie = $trie->{$_} ||= {COUNT => 0};
+ $trie->{COUNT}++;
+ }
+}
+
+# returns an array of tuples (prefix, remainder)
+sub find_unique_prefixes {
+ my @stuff = @_;
+ my @return = ();
+
+ # any single prefix exceeding the soft limit is omitted
+ # if any prefix exceeds the hard limit all are omitted
+ # 0 indicates no limit
+ my $soft_limit = 0;
+ my $hard_limit = 3;
+
+ # build a trie modelling all possible options
+ my %trie;
+ foreach my $print (@stuff) {
+ if ((ref $print) eq 'ARRAY') {
+ $print = $print->[0];
+ }
+ elsif ((ref $print) eq 'HASH') {
+ $print = $print->{VALUE};
+ }
+ update_trie(\%trie, $print);
+ push @return, $print;
+ }
+
+ # use the trie to find the unique prefixes
+ for (my $i = 0; $i < @return; $i++) {
+ my $ret = $return[$i];
+ my @letters = split //, $ret;
+ my %search = %trie;
+ my ($prefix, $remainder);
+ my $j;
+ for ($j = 0; $j < @letters; $j++) {
+ my $letter = $letters[$j];
+ if ($search{$letter}{COUNT} == 1) {
+ $prefix = substr $ret, 0, $j + 1;
+ $remainder = substr $ret, $j + 1;
+ last;
+ }
+ else {
+ my $prefix = substr $ret, 0, $j;
+ return ()
+ if ($hard_limit && $j + 1 > $hard_limit);
+ }
+ %search = %{$search{$letter}};
+ }
+ if ($soft_limit && $j + 1 > $soft_limit) {
+ $prefix = undef;
+ $remainder = $ret;
+ }
+ $return[$i] = [$prefix, $remainder];
+ }
+ return @return;
+}
+
+# filters out prefixes which have special meaning to list_and_choose()
+sub is_valid_prefix {
+ my $prefix = shift;
+ return (defined $prefix) &&
+ !($prefix =~ /[\s,]/) && # separators
+ !($prefix =~ /^-/) && # deselection
+ !($prefix =~ /^\d+/) && # selection
+ ($prefix ne '*') && # "all" wildcard
+ ($prefix ne '?'); # prompt help
+}
+
+# given a prefix/remainder tuple return a string with the prefix highlighted
+# for now use square brackets; later might use ANSI colors (underline, bold)
+sub highlight_prefix {
+ my $prefix = shift;
+ my $remainder = shift;
+
+ if (!defined $prefix) {
+ return $remainder;
+ }
+
+ if (!is_valid_prefix($prefix)) {
+ return "$prefix$remainder";
+ }
+
+ if (!$menu_use_color) {
+ return "[$prefix]$remainder";
+ }
+
+ return "$prompt_color$prefix$normal_color$remainder";
+}
+
sub list_and_choose {
my ($opts, @stuff) = @_;
my (@chosen, @return);
my $i;
+ my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
TOPLOOP:
while (1) {
@@ -175,18 +327,26 @@ sub list_and_choose {
if (!$opts->{LIST_FLAT}) {
print " ";
}
- print "$opts->{HEADER}\n";
+ print colored $header_color, "$opts->{HEADER}\n";
}
for ($i = 0; $i < @stuff; $i++) {
my $chosen = $chosen[$i] ? '*' : ' ';
my $print = $stuff[$i];
- if (ref $print) {
- if ((ref $print) eq 'ARRAY') {
- $print = $print->[0];
- }
- else {
- $print = $print->{PRINT};
- }
+ my $ref = ref $print;
+ my $highlighted = highlight_prefix(@{$prefixes[$i]})
+ if @prefixes;
+ if ($ref eq 'ARRAY') {
+ $print = $highlighted || $print->[0];
+ }
+ elsif ($ref eq 'HASH') {
+ my $value = $highlighted || $print->{VALUE};
+ $print = sprintf($status_fmt,
+ $print->{INDEX},
+ $print->{FILE},
+ $value);
+ }
+ else {
+ $print = $highlighted || $print;
}
printf("%s%2d: %s", $chosen, $i+1, $print);
if (($opts->{LIST_FLAT}) &&
@@ -205,7 +365,7 @@ sub list_and_choose {
return if ($opts->{LIST_ONLY});
- print $opts->{PROMPT};
+ print colored $prompt_color, $opts->{PROMPT};
if ($opts->{SINGLETON}) {
print "> ";
}
@@ -220,6 +380,12 @@ sub list_and_choose {
}
chomp $line;
last if $line eq '';
+ if ($line eq '?') {
+ $opts->{SINGLETON} ?
+ singleton_prompt_help_cmd() :
+ prompt_help_cmd();
+ next TOPLOOP;
+ }
for my $choice (split(/[\s,]+/, $line)) {
my $choose = 1;
my ($bottom, $top);
@@ -255,7 +421,7 @@ sub list_and_choose {
$chosen[$i] = $choose;
}
}
- last if ($opts->{IMMEDIATE});
+ last if ($opts->{IMMEDIATE} || $line eq '*');
}
for ($i = 0; $i < @stuff; $i++) {
if ($chosen[$i]) {
@@ -265,6 +431,28 @@ sub list_and_choose {
return @return;
}
+sub singleton_prompt_help_cmd {
+ print colored $help_color, <<\EOF ;
+Prompt help:
+1 - select a numbered item
+foo - select item based on unique prefix
+ - (empty) select nothing
+EOF
+}
+
+sub prompt_help_cmd {
+ print colored $help_color, <<\EOF ;
+Prompt help:
+1 - select a single item
+3-5 - select a range of items
+2-3,6-9 - select multiple ranges
+foo - select item based on unique prefix
+-... - unselect specified items
+* - choose all items
+ - (empty) finish selecting
+EOF
+}
+
sub status_cmd {
list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
list_modified());
@@ -303,21 +491,27 @@ sub revert_cmd {
HEADER => $status_head, },
list_modified());
if (@update) {
- my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
- map { $_->{VALUE} } @update);
- my $fh;
- open $fh, '| git update-index --index-info'
- or die;
- for (@lines) {
- print $fh $_;
+ if (is_initial_commit()) {
+ system(qw(git rm --cached),
+ map { $_->{VALUE} } @update);
}
- close($fh);
- for (@update) {
- if ($_->{INDEX_ADDDEL} &&
- $_->{INDEX_ADDDEL} eq 'create') {
- system(qw(git update-index --force-remove --),
- $_->{VALUE});
- print "note: $_->{VALUE} is untracked now.\n";
+ else {
+ my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
+ map { $_->{VALUE} } @update);
+ my $fh;
+ open $fh, '| git update-index --index-info'
+ or die;
+ for (@lines) {
+ print $fh $_;
+ }
+ close($fh);
+ for (@update) {
+ if ($_->{INDEX_ADDDEL} &&
+ $_->{INDEX_ADDDEL} eq 'create') {
+ system(qw(git update-index --force-remove --),
+ $_->{VALUE});
+ print "note: $_->{VALUE} is untracked now.\n";
+ }
}
}
refresh();
@@ -339,13 +533,19 @@ sub add_untracked_cmd {
sub parse_diff {
my ($path) = @_;
my @diff = run_cmd_pipe(qw(git diff-files -p --), $path);
- my (@hunk) = { TEXT => [] };
+ my @colored = ();
+ if ($diff_use_color) {
+ @colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);
+ }
+ my (@hunk) = { TEXT => [], DISPLAY => [] };
- for (@diff) {
- if (/^@@ /) {
- push @hunk, { TEXT => [] };
+ for (my $i = 0; $i < @diff; $i++) {
+ if ($diff[$i] =~ /^@@ /) {
+ push @hunk, { TEXT => [], DISPLAY => [] };
}
- push @{$hunk[-1]{TEXT}}, $_;
+ push @{$hunk[-1]{TEXT}}, $diff[$i];
+ push @{$hunk[-1]{DISPLAY}},
+ ($diff_use_color ? $colored[$i] : $diff[$i]);
}
return @hunk;
}
@@ -367,9 +567,11 @@ sub parse_hunk_header {
}
sub split_hunk {
- my ($text) = @_;
+ my ($text, $display) = @_;
my @split = ();
-
+ if (!defined $display) {
+ $display = $text;
+ }
# If there are context lines in the middle of a hunk,
# it can be split, but we would need to take care of
# overlaps later.
@@ -383,16 +585,19 @@ sub split_hunk {
my $i = $hunk_start - 1;
my $this = +{
TEXT => [],
+ DISPLAY => [],
OLD => $o_ofs,
NEW => $n_ofs,
OCNT => 0,
NCNT => 0,
ADDDEL => 0,
POSTCTX => 0,
+ USE => undef,
};
while (++$i < @$text) {
my $line = $text->[$i];
+ my $display = $display->[$i];
if ($line =~ /^ /) {
if ($this->{ADDDEL} &&
!defined $next_hunk_start) {
@@ -404,6 +609,7 @@ sub split_hunk {
$next_hunk_start = $i;
}
push @{$this->{TEXT}}, $line;
+ push @{$this->{DISPLAY}}, $display;
$this->{OCNT}++;
$this->{NCNT}++;
if (defined $next_hunk_start) {
@@ -426,6 +632,7 @@ sub split_hunk {
redo OUTER;
}
push @{$this->{TEXT}}, $line;
+ push @{$this->{DISPLAY}}, $display;
$this->{ADDDEL}++;
if ($line =~ /^-/) {
$this->{OCNT}++;
@@ -450,9 +657,14 @@ sub split_hunk {
" +$n_ofs" .
(($n_cnt != 1) ? ",$n_cnt" : '') .
" @@\n");
+ my $display_head = $head;
unshift @{$hunk->{TEXT}}, $head;
+ if ($diff_use_color) {
+ $display_head = colored($fraginfo_color, $head);
+ }
+ unshift @{$hunk->{DISPLAY}}, $display_head;
}
- return map { $_->{TEXT} } @split;
+ return @split;
}
sub find_last_o_ctx {
@@ -544,35 +756,46 @@ sub coalesce_overlapping_hunks {
}
sub help_patch_cmd {
- print <<\EOF ;
+ print colored $help_color, <<\EOF ;
y - stage this hunk
n - do not stage this hunk
-a - stage this and all the remaining hunks
-d - do not stage this hunk nor any of the remaining hunks
+a - stage this and all the remaining hunks in the file
+d - do not stage this hunk nor any of the remaining hunks in the file
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
+? - print help
EOF
}
sub patch_update_cmd {
- my @mods = list_modified('file-only');
- @mods = grep { !($_->{BINARY}) } @mods;
- return if (!@mods);
+ my @mods = grep { !($_->{BINARY}) } list_modified('file-only');
+ my @them;
- my ($it) = list_and_choose({ PROMPT => 'Patch update',
- SINGLETON => 1,
- IMMEDIATE => 1,
- HEADER => $status_head, },
- @mods);
- return if (!$it);
+ if (!@mods) {
+ print STDERR "No changes.\n";
+ return 0;
+ }
+ if ($patch_mode) {
+ @them = @mods;
+ }
+ else {
+ @them = list_and_choose({ PROMPT => 'Patch update',
+ HEADER => $status_head, },
+ @mods);
+ }
+ for (@them) {
+ patch_update_file($_->{VALUE});
+ }
+}
+sub patch_update_file {
my ($ix, $num);
- my $path = $it->{VALUE};
+ my $path = shift;
my ($head, @hunk) = parse_diff($path);
- for (@{$head->{TEXT}}) {
+ for (@{$head->{DISPLAY}}) {
print;
}
$num = scalar @hunk;
@@ -616,10 +839,10 @@ sub patch_update_cmd {
if (hunk_splittable($hunk[$ix]{TEXT})) {
$other .= '/s';
}
- for (@{$hunk[$ix]{TEXT}}) {
+ for (@{$hunk[$ix]{DISPLAY}}) {
print;
}
- print "Stage this hunk [y/n/a/d$other/?]? ";
+ print colored $prompt_color, "Stage this hunk [y/n/a/d$other/?]? ";
my $line = <STDIN>;
if ($line) {
if ($line =~ /^y/i) {
@@ -671,14 +894,12 @@ sub patch_update_cmd {
next;
}
elsif ($other =~ /s/ && $line =~ /^s/) {
- my @split = split_hunk($hunk[$ix]{TEXT});
+ my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
if (1 < @split) {
- print "Split into ",
+ print colored $header_color, "Split into ",
scalar(@split), " hunks.\n";
}
- splice(@hunk, $ix, 1,
- map { +{ TEXT => $_, USE => undef } }
- @split);
+ splice (@hunk, $ix, 1, @split);
$num = scalar @hunk;
next;
}
@@ -756,8 +977,9 @@ sub diff_cmd {
HEADER => $status_head, },
@mods);
return if (!@them);
- system(qw(git diff-index -p --cached HEAD --),
- map { $_->{VALUE} } @them);
+ my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
+ system(qw(git diff -p --cached), $reference, '--',
+ map { $_->{VALUE} } @them);
}
sub quit_cmd {
@@ -766,7 +988,7 @@ sub quit_cmd {
}
sub help_cmd {
- print <<\EOF ;
+ print colored $help_color, <<\EOF ;
status - show paths with changes
update - add working tree state to the staged set of changes
revert - revert staged set of changes back to the HEAD version
@@ -776,6 +998,20 @@ add untracked - add contents of untracked files to the staged set of changes
EOF
}
+sub process_args {
+ return unless @ARGV;
+ my $arg = shift @ARGV;
+ if ($arg eq "--patch") {
+ $patch_mode = 1;
+ $arg = shift @ARGV or die "missing --";
+ die "invalid argument $arg, expecting --"
+ unless $arg eq "--";
+ }
+ elsif ($arg ne "--") {
+ die "invalid argument $arg, expecting --";
+ }
+}
+
sub main_loop {
my @cmd = ([ 'status', \&status_cmd, ],
[ 'update', \&update_cmd, ],
@@ -804,6 +1040,12 @@ sub main_loop {
}
}
+process_args();
refresh();
-status_cmd();
-main_loop();
+if ($patch_mode) {
+ patch_update_cmd();
+}
+else {
+ status_cmd();
+ main_loop();
+}
diff --git a/git-am.sh b/git-am.sh
index b02ae6a76f..2ecebc45a9 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -2,11 +2,26 @@
#
# Copyright (c) 2005, 2006 Junio C Hamano
-USAGE='[--signoff] [--dotest=<dir>] [--keep] [--utf8 | --no-utf8]
- [--3way] [--interactive] [--binary]
- [--whitespace=<option>] [-C<n>] [-p<n>]
- <mbox>|<Maildir>...
- or, when resuming [--skip | --resolved]'
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-am [options] <mbox>|<Maildir>...
+git-am [options] --resolved
+git-am [options] --skip
+--
+d,dotest= use <dir> and not .dotest
+i,interactive run interactively
+b,binary pass --allo-binary-replacement to git-apply
+3,3way allow fall back on 3way merging if needed
+s,signoff add a Signed-off-by line to the commit message
+u,utf8 recode into utf8 (default)
+k,keep pass -k flag to git-mailinfo
+whitespace= pass it through git-apply
+C= pass it through git-apply
+p= pass it through git-apply
+resolvemsg= override error message when patch failure occurs
+r,resolved to be used after a patch failure
+skip skip the current patch"
+
. git-sh-setup
set_reflog_action am
require_work_tree
@@ -102,6 +117,10 @@ It does not apply to blobs recorded in its index."
unset GITHEAD_$his_tree
}
+reread_subject () {
+ git stripspace <"$1" | sed -e 1q
+}
+
prec=4
dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary=
resolvemsg= resume=
@@ -110,49 +129,38 @@ git_apply_opt=
while test $# != 0
do
case "$1" in
- -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
- dotest=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;;
- -d|--d|--do|--dot|--dote|--dotes|--dotest)
- case "$#" in 1) usage ;; esac; shift
- dotest="$1"; shift;;
-
- -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
- --interacti|--interactiv|--interactive)
- interactive=t; shift ;;
-
- -b|--b|--bi|--bin|--bina|--binar|--binary)
- binary=t; shift ;;
-
- -3|--3|--3w|--3wa|--3way)
- threeway=t; shift ;;
- -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
- sign=t; shift ;;
- -u|--u|--ut|--utf|--utf8)
- utf8=t; shift ;; # this is now default
- --no-u|--no-ut|--no-utf|--no-utf8)
- utf8=; shift ;;
- -k|--k|--ke|--kee|--keep)
- keep=t; shift ;;
-
- -r|--r|--re|--res|--reso|--resol|--resolv|--resolve|--resolved)
- resolved=t; shift ;;
-
- --sk|--ski|--skip)
- skip=t; shift ;;
-
- --whitespace=*|-C*|-p*)
- git_apply_opt="$git_apply_opt $1"; shift ;;
-
- --resolvemsg=*)
- resolvemsg=${1#--resolvemsg=}; shift ;;
-
+ -i|--interactive)
+ interactive=t ;;
+ -b|--binary)
+ binary=t ;;
+ -3|--3way)
+ threeway=t ;;
+ -s|--signoff)
+ sign=t ;;
+ -u|--utf8)
+ utf8=t ;; # this is now default
+ --no-utf8)
+ utf8= ;;
+ -k|--keep)
+ keep=t ;;
+ -r|--resolved)
+ resolved=t ;;
+ --skip)
+ skip=t ;;
+ -d|--dotest)
+ shift; dotest=$1;;
+ --resolvemsg)
+ shift; resolvemsg=$1 ;;
+ --whitespace)
+ git_apply_opt="$git_apply_opt $1=$2"; shift ;;
+ -C|-p)
+ git_apply_opt="$git_apply_opt $1$2"; shift ;;
--)
- shift; break ;;
- -*)
- usage ;;
+ shift; break ;;
*)
- break ;;
+ usage ;;
esac
+ shift
done
# If the dotest directory exists, but we have finished applying all the
@@ -214,7 +222,7 @@ fi
case "$resolved" in
'')
- files=$(git diff-index --cached --name-only HEAD) || exit
+ files=$(git diff-index --cached --name-only HEAD --) || exit
if [ "$files" ]; then
echo "Dirty index: cannot apply patches (dirty: $files)" >&2
exit 1
@@ -348,7 +356,7 @@ do
case "$resolved$interactive" in
tt)
# This is used only for interactive view option.
- git diff-index -p --cached HEAD >"$dotest/patch"
+ git diff-index -p --cached HEAD -- >"$dotest/patch"
;;
esac
esac
@@ -372,6 +380,7 @@ do
[aA]*) action=yes interactive= ;;
[nN]*) action=skip ;;
[eE]*) git_editor "$dotest/final-commit"
+ SUBJECT=$(reread_subject "$dotest/final-commit")
action=again ;;
[vV]*) action=again
LESS=-S ${PAGER:-less} "$dotest/patch" ;;
@@ -394,9 +403,7 @@ do
stop_here $this
fi
- echo
printf 'Applying %s\n' "$SUBJECT"
- echo
case "$resolved" in
'')
@@ -409,7 +416,7 @@ do
# trust what the user has in the index file and the
# working tree.
resolved=
- git diff-index --quiet --cached HEAD && {
+ git diff-index --quiet --cached HEAD -- && {
echo "No changes - did you forget to use 'git add'?"
stop_here_user_resolve $this
}
@@ -431,7 +438,7 @@ do
then
# 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 && {
+ git diff-index --quiet --cached HEAD -- && {
echo No changes -- Patch already applied.
go_next
continue
@@ -452,10 +459,8 @@ do
fi
tree=$(git write-tree) &&
- echo Wrote tree $tree &&
parent=$(git rev-parse --verify HEAD) &&
commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
- echo Committed: $commit &&
git update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent ||
stop_here $this
@@ -464,9 +469,9 @@ do
"$GIT_DIR"/hooks/post-applypatch
fi
- git gc --auto
-
go_next
done
+git gc --auto
+
rm -fr "$dotest"
diff --git a/git-bisect.sh b/git-bisect.sh
index 388887a556..74715edf0b 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -1,12 +1,14 @@
#!/bin/sh
-USAGE='[start|bad|good|next|reset|visualize|replay|log|run]'
+USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]'
LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
reset bisect state and start bisection.
git bisect bad [<rev>]
mark <rev> a known-bad revision.
git bisect good [<rev>...]
mark <rev>... known-good revisions.
+git bisect skip [<rev>...]
+ mark <rev>... untestable revisions.
git bisect next
find next bisection to test and check it out.
git bisect reset [<branch>]
@@ -20,9 +22,13 @@ git bisect log
git bisect run <cmd>...
use <cmd>... to automatically bisect.'
+OPTIONS_SPEC=
. git-sh-setup
require_work_tree
+_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"
+
sq() {
@@PERL@@ -e '
for (@ARGV) {
@@ -34,7 +40,7 @@ sq() {
}
bisect_autostart() {
- test -d "$GIT_DIR/refs/bisect" || {
+ test -f "$GIT_DIR/BISECT_NAMES" || {
echo >&2 'You need to start by "git bisect start"'
if test -t 0
then
@@ -57,19 +63,20 @@ bisect_start() {
# top-of-line master first!
#
head=$(GIT_DIR="$GIT_DIR" git symbolic-ref HEAD) ||
- die "Bad HEAD - I need a symbolic ref"
+ head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
+ die "Bad HEAD - I need a HEAD"
case "$head" in
refs/heads/bisect)
if [ -s "$GIT_DIR/head-name" ]; then
branch=`cat "$GIT_DIR/head-name"`
else
branch=master
- fi
+ fi
git checkout $branch || exit
;;
- refs/heads/*)
+ refs/heads/*|$_x40)
[ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
- echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name"
+ echo "${head#refs/heads/}" >"$GIT_DIR/head-name"
;;
*)
die "Bad HEAD - strange symbolic ref"
@@ -80,7 +87,6 @@ bisect_start() {
# Get rid of any old bisect state
#
bisect_clean_state
- mkdir "$GIT_DIR/refs/bisect"
#
# Check for one bad and then some good revisions.
@@ -95,75 +101,74 @@ bisect_start() {
arg="$1"
case "$arg" in
--)
- shift
+ shift
break
;;
*)
- rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
+ rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
test $has_double_dash -eq 1 &&
die "'$arg' does not appear to be a valid revision"
break
}
- if [ $bad_seen -eq 0 ]; then
- bad_seen=1
- bisect_write_bad "$rev"
- else
- bisect_write_good "$rev"
- fi
- shift
+ case $bad_seen in
+ 0) state='bad' ; bad_seen=1 ;;
+ *) state='good' ;;
+ esac
+ bisect_write "$state" "$rev" 'nolog'
+ shift
;;
esac
- done
+ done
sq "$@" >"$GIT_DIR/BISECT_NAMES"
echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG"
bisect_auto_next
}
-bisect_bad() {
- bisect_autostart
- case "$#" in
- 0)
- rev=$(git rev-parse --verify HEAD) ;;
- 1)
- rev=$(git rev-parse --verify "$1^{commit}") ;;
- *)
- usage ;;
- esac || exit
- bisect_write_bad "$rev"
- echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
- bisect_auto_next
-}
-
-bisect_write_bad() {
- rev="$1"
- echo "$rev" >"$GIT_DIR/refs/bisect/bad"
- echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+bisect_write() {
+ state="$1"
+ rev="$2"
+ nolog="$3"
+ case "$state" in
+ bad) tag="$state" ;;
+ good|skip) tag="$state"-"$rev" ;;
+ *) die "Bad bisect_write argument: $state" ;;
+ esac
+ git update-ref "refs/bisect/$tag" "$rev"
+ echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
+ test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
}
-bisect_good() {
+bisect_state() {
bisect_autostart
- case "$#" in
- 0) revs=$(git rev-parse --verify HEAD) || exit ;;
- *) revs=$(git rev-parse --revs-only --no-flags "$@") &&
- test '' != "$revs" || die "Bad rev input: $@" ;;
+ state=$1
+ case "$#,$state" in
+ 0,*)
+ die "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"
+ bisect_write "$state" "$rev" ;;
+ 2,bad)
+ rev=$(git rev-parse --verify "$2^{commit}") ||
+ die "Bad rev input: $2"
+ bisect_write "$state" "$rev" ;;
+ *,good|*,skip)
+ shift
+ revs=$(git rev-parse --revs-only --no-flags "$@") &&
+ test '' != "$revs" || die "Bad rev input: $@"
+ for rev in $revs
+ do
+ rev=$(git rev-parse --verify "$rev^{commit}") ||
+ die "Bad rev commit: $rev^{commit}"
+ bisect_write "$state" "$rev"
+ done ;;
+ *)
+ usage ;;
esac
- for rev in $revs
- do
- rev=$(git rev-parse --verify "$rev^{commit}") || exit
- bisect_write_good "$rev"
- echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
-
- done
bisect_auto_next
}
-bisect_write_good() {
- rev="$1"
- echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
- echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
-}
-
bisect_next_check() {
missing_good= missing_bad=
git show-ref -q --verify refs/bisect/bad || missing_bad=t
@@ -190,7 +195,7 @@ bisect_next_check() {
;;
*)
THEN=''
- test -d "$GIT_DIR/refs/bisect" || {
+ test -f "$GIT_DIR/BISECT_NAMES" || {
echo >&2 'You need to start by "git bisect start".'
THEN='then '
}
@@ -206,17 +211,97 @@ bisect_auto_next() {
bisect_next_check && bisect_next || :
}
+filter_skipped() {
+ _eval="$1"
+ _skip="$2"
+
+ if [ -z "$_skip" ]; then
+ eval $_eval
+ return
+ fi
+
+ # Let's parse the output of:
+ # "git rev-list --bisect-vars --bisect-all ..."
+ eval $_eval | while read hash line
+ do
+ case "$VARS,$FOUND,$TRIED,$hash" in
+ # We display some vars.
+ 1,*,*,*) echo "$hash $line" ;;
+
+ # Split line.
+ ,*,*,---*) ;;
+
+ # We had nothing to search.
+ ,,,bisect_rev*)
+ echo "bisect_rev="
+ VARS=1
+ ;;
+
+ # We did not find a good bisect rev.
+ # This should happen only if the "bad"
+ # commit is also a "skip" commit.
+ ,,*,bisect_rev*)
+ echo "bisect_rev=$TRIED"
+ VARS=1
+ ;;
+
+ # We are searching.
+ ,,*,*)
+ TRIED="${TRIED:+$TRIED|}$hash"
+ case "$_skip" in
+ *$hash*) ;;
+ *)
+ echo "bisect_rev=$hash"
+ echo "bisect_tried=\"$TRIED\""
+ FOUND=1
+ ;;
+ esac
+ ;;
+
+ # We have already found a rev to be tested.
+ ,1,*,bisect_rev*) VARS=1 ;;
+ ,1,*,*) ;;
+
+ # ???
+ *) die "filter_skipped error " \
+ "VARS: '$VARS' " \
+ "FOUND: '$FOUND' " \
+ "TRIED: '$TRIED' " \
+ "hash: '$hash' " \
+ "line: '$line'"
+ ;;
+ esac
+ done
+}
+
+exit_if_skipped_commits () {
+ _tried=$1
+ if expr "$_tried" : ".*[|].*" > /dev/null ; then
+ echo "There are only 'skip'ped commit left to test."
+ echo "The first bad commit could be any of:"
+ echo "$_tried" | tr '[|]' '[\012]'
+ echo "We cannot bisect more!"
+ exit 2
+ fi
+}
+
bisect_next() {
- case "$#" in 0) ;; *) usage ;; esac
+ case "$#" in 0) ;; *) usage ;; esac
bisect_autostart
bisect_next_check good
+ skip=$(git for-each-ref --format='%(objectname)' \
+ "refs/bisect/skip-*" | tr '[\012]' ' ') || exit
+
+ BISECT_OPT=''
+ test -n "$skip" && BISECT_OPT='--bisect-all'
+
bad=$(git rev-parse --verify refs/bisect/bad) &&
good=$(git for-each-ref --format='^%(objectname)' \
"refs/bisect/good-*" | tr '[\012]' ' ') &&
- eval="git rev-list --bisect-vars $good $bad --" &&
+ eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
- eval=$(eval "$eval") &&
+ eval=$(filter_skipped "$eval" "$skip") &&
eval "$eval" || exit
if [ -z "$bisect_rev" ]; then
@@ -224,38 +309,59 @@ bisect_next() {
exit 1
fi
if [ "$bisect_rev" = "$bad" ]; then
+ exit_if_skipped_commits "$bisect_tried"
echo "$bisect_rev is first bad commit"
git diff-tree --pretty $bisect_rev
exit 0
fi
+ # We should exit here only if the "bad"
+ # commit is also a "skip" commit (see above).
+ exit_if_skipped_commits "$bisect_rev"
+
echo "Bisecting: $bisect_nr revisions left to test after this"
- echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect"
+ git branch -f new-bisect "$bisect_rev"
git checkout -q new-bisect || exit
- mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
- GIT_DIR="$GIT_DIR" git symbolic-ref HEAD refs/heads/bisect
+ git branch -M new-bisect bisect
git show-branch "$bisect_rev"
}
bisect_visualize() {
bisect_next_check fail
- not=`cd "$GIT_DIR/refs" && echo bisect/good-*`
- eval gitk bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
+
+ if test $# = 0
+ then
+ case "${DISPLAY+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
+ '') set git log ;;
+ set*) set gitk ;;
+ esac
+ else
+ case "$1" in
+ git*|tig) ;;
+ -*) set git log "$@" ;;
+ *) set git "$@" ;;
+ esac
+ fi
+
+ not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
+ eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
}
bisect_reset() {
+ test -f "$GIT_DIR/BISECT_NAMES" || {
+ echo "We are not bisecting."
+ return
+ }
case "$#" in
0) if [ -s "$GIT_DIR/head-name" ]; then
branch=`cat "$GIT_DIR/head-name"`
else
branch=master
fi ;;
- 1) git show-ref --verify --quiet -- "refs/heads/$1" || {
- echo >&2 "$1 does not seem to be a valid branch"
- exit 1
- }
+ 1) git show-ref --verify --quiet -- "refs/heads/$1" ||
+ die "$1 does not seem to be a valid branch"
branch="$1" ;;
- *)
+ *)
usage ;;
esac
if git checkout "$branch"; then
@@ -265,18 +371,19 @@ bisect_reset() {
}
bisect_clean_state() {
- rm -fr "$GIT_DIR/refs/bisect"
- rm -f "$GIT_DIR/refs/heads/bisect"
+ # There may be some refs packed during bisection.
+ git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* refs/heads/bisect |
+ while read ref hash
+ do
+ git update-ref -d $ref $hash
+ done
rm -f "$GIT_DIR/BISECT_LOG"
rm -f "$GIT_DIR/BISECT_NAMES"
rm -f "$GIT_DIR/BISECT_RUN"
}
bisect_replay () {
- test -r "$1" || {
- echo >&2 "cannot read $1 for replaying"
- exit 1
- }
+ test -r "$1" || die "cannot read $1 for replaying"
bisect_reset
while read bisect command rev
do
@@ -284,21 +391,11 @@ bisect_replay () {
case "$command" in
start)
cmd="bisect_start $rev"
- eval "$cmd"
- ;;
- good)
- echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
- echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
- echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
- ;;
- bad)
- echo "$rev" >"$GIT_DIR/refs/bisect/bad"
- echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
- echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
- ;;
+ eval "$cmd" ;;
+ good|bad|skip)
+ bisect_write "$command" "$rev" ;;
*)
- echo >&2 "?? what are you talking about?"
- exit 1 ;;
+ die "?? what are you talking about?" ;;
esac
done <"$1"
bisect_auto_next
@@ -320,24 +417,31 @@ bisect_run () {
exit $res
fi
- # Use "bisect_good" or "bisect_bad"
- # depending on run success or failure.
- if [ $res -gt 0 ]; then
- next_bisect='bisect_bad'
+ # Find current state depending on run success or failure.
+ # A special exit code of 125 means cannot test.
+ if [ $res -eq 125 ]; then
+ state='skip'
+ elif [ $res -gt 0 ]; then
+ state='bad'
else
- next_bisect='bisect_good'
+ state='good'
fi
- # We have to use a subshell because bisect_good or
- # bisect_bad functions can exit.
- ( $next_bisect > "$GIT_DIR/BISECT_RUN" )
+ # We have to use a subshell because "bisect_state" can exit.
+ ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
res=$?
cat "$GIT_DIR/BISECT_RUN"
+ if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
+ > /dev/null; then
+ echo >&2 "bisect run cannot continue any more"
+ exit $res
+ fi
+
if [ $res -ne 0 ]; then
echo >&2 "bisect run failed:"
- echo >&2 "$next_bisect exited with error code $res"
+ echo >&2 "'bisect_state $state' exited with error code $res"
exit $res
fi
@@ -359,14 +463,12 @@ case "$#" in
case "$cmd" in
start)
bisect_start "$@" ;;
- bad)
- bisect_bad "$@" ;;
- good)
- bisect_good "$@" ;;
+ bad|good|skip)
+ bisect_state "$cmd" "$@" ;;
next)
# Not sure we want "next" at the UI level anymore.
bisect_next "$@" ;;
- visualize)
+ visualize|view)
bisect_visualize "$@" ;;
reset)
bisect_reset "$@" ;;
diff --git a/git-checkout.sh b/git-checkout.sh
index 8993920673..bd74d701a1 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -1,6 +1,16 @@
#!/bin/sh
-USAGE='[-q] [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
+OPTIONS_KEEPDASHDASH=t
+OPTIONS_SPEC="\
+git-checkout [options] [<branch>] [<paths>...]
+--
+b= create a new branch started at <branch>
+l create the new branch's reflog
+track arrange that the new branch tracks the remote branch
+f proceed even if the index or working tree is not HEAD
+m merge local modifications into the new branch
+q,quiet be quiet
+"
SUBDIRECTORY_OK=Sometimes
. git-sh-setup
require_work_tree
@@ -20,13 +30,12 @@ quiet=
v=-v
LF='
'
-while [ "$#" != "0" ]; do
- arg="$1"
- shift
- case "$arg" in
- "-b")
- newbranch="$1"
+
+while test $# != 0; do
+ case "$1" in
+ -b)
shift
+ newbranch="$1"
[ -z "$newbranch" ] &&
die "git checkout: -b needs a branch name"
git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
@@ -34,64 +43,55 @@ while [ "$#" != "0" ]; do
git check-ref-format "heads/$newbranch" ||
die "git checkout: we do not like '$newbranch' as a branch name."
;;
- "-l")
+ -l)
newbranch_log=-l
;;
- "--track"|"--no-track")
- track="$arg"
+ --track|--no-track)
+ track="$1"
;;
- "-f")
+ -f)
force=1
;;
-m)
merge=1
;;
- "-q")
+ -q|--quiet)
quiet=1
v=
;;
--)
+ shift
break
;;
- -*)
- usage
- ;;
*)
- if rev=$(git rev-parse --verify "$arg^0" 2>/dev/null)
- then
- if [ -z "$rev" ]; then
- echo "unknown flag $arg"
- exit 1
- fi
- new_name="$arg"
- if git show-ref --verify --quiet -- "refs/heads/$arg"
- then
- rev=$(git rev-parse --verify "refs/heads/$arg^0")
- branch="$arg"
- fi
- new="$rev"
- elif rev=$(git rev-parse --verify "$arg^{tree}" 2>/dev/null)
- then
- # checking out selected paths from a tree-ish.
- new="$rev"
- new_name="$arg^{tree}"
- branch=
- else
- new=
- new_name=
- branch=
- set x "$arg" "$@"
- shift
- fi
- case "$1" in
- --)
- shift ;;
- esac
- break
+ usage
;;
- esac
+ esac
+ shift
done
+arg="$1"
+rev=$(git rev-parse --verify "$arg" 2>/dev/null)
+if rev=$(git rev-parse --verify "$rev^0" 2>/dev/null)
+then
+ [ -z "$rev" ] && die "unknown flag $arg"
+ new_name="$arg"
+ if git show-ref --verify --quiet -- "refs/heads/$arg"
+ then
+ rev=$(git rev-parse --verify "refs/heads/$arg^0")
+ branch="$arg"
+ fi
+ new="$rev"
+ shift
+elif rev=$(git rev-parse --verify "$rev^{tree}" 2>/dev/null)
+then
+ # checking out selected paths from a tree-ish.
+ new="$rev"
+ new_name="$rev^{tree}"
+ shift
+fi
+[ "$1" = "--" ] && shift
+
case "$newbranch,$track" in
,--*)
die "git checkout: --track and --no-track require -b"
@@ -134,12 +134,12 @@ Did you intend to checkout '$@' which can not be resolved as commit?"
fi
# Make sure the request is about existing paths.
- git ls-files --error-unmatch -- "$@" >/dev/null || exit
- git ls-files -- "$@" |
- git checkout-index -f -u --stdin
+ git ls-files --full-name --error-unmatch -- "$@" >/dev/null || exit
+ git ls-files --full-name -- "$@" |
+ (cd_to_toplevel && git checkout-index -f -u --stdin)
- # Run a post-checkout hook -- the HEAD does not change so the
- # current HEAD is passed in for both args
+ # Run a post-checkout hook -- the HEAD does not change so the
+ # current HEAD is passed in for both args
if test -x "$GIT_DIR"/hooks/post-checkout; then
"$GIT_DIR"/hooks/post-checkout $old $old 0
fi
@@ -176,7 +176,7 @@ detach_warn=
describe_detached_head () {
test -n "$quiet" || {
printf >&2 "$1 "
- GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2"
+ GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" --
}
}
@@ -267,7 +267,7 @@ if [ "$?" -eq 0 ]; then
if test -n "$branch"
then
old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
- GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from $old_branch_name to $branch" HEAD "refs/heads/$branch"
+ GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch"
if test -n "$quiet"
then
true # nothing
@@ -279,7 +279,8 @@ if [ "$?" -eq 0 ]; then
fi
elif test -n "$detached"
then
- git update-ref --no-deref -m "checkout: moving to $arg" HEAD "$detached" ||
+ old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
+ git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" ||
die "Cannot detach HEAD"
if test -n "$detach_warn"
then
@@ -294,5 +295,5 @@ fi
# Run a post-checkout hook
if test -x "$GIT_DIR"/hooks/post-checkout; then
- "$GIT_DIR"/hooks/post-checkout $old $new 1
+ "$GIT_DIR"/hooks/post-checkout $old $new 1
fi
diff --git a/git-clean.sh b/git-clean.sh
deleted file mode 100755
index 4491738186..0000000000
--- a/git-clean.sh
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005-2006 Pavel Roskin
-#
-
-USAGE="[-d] [-f] [-n] [-q] [-x | -X] [--] <paths>..."
-LONG_USAGE='Clean untracked files from the working directory
- -d remove directories as well
- -f override clean.requireForce and clean anyway
- -n don'\''t remove anything, just show what would be done
- -q be quiet, only report errors
- -x remove ignored files as well
- -X remove only ignored files
-When optional <paths>... arguments are given, the paths
-affected are further limited to those that match them.'
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-
-ignored=
-ignoredonly=
-cleandir=
-disabled="`git config --bool clean.requireForce`"
-rmf="rm -f --"
-rmrf="rm -rf --"
-rm_refuse="echo Not removing"
-echo1="echo"
-
-while test $# != 0
-do
- case "$1" in
- -d)
- cleandir=1
- ;;
- -f)
- disabled=
- ;;
- -n)
- disabled=
- rmf="echo Would remove"
- rmrf="echo Would remove"
- rm_refuse="echo Would not remove"
- echo1=":"
- ;;
- -q)
- echo1=":"
- ;;
- -x)
- ignored=1
- ;;
- -X)
- ignoredonly=1
- ;;
- --)
- shift
- break
- ;;
- -*)
- usage
- ;;
- *)
- break
- esac
- shift
-done
-
-if [ "$disabled" = true ]; then
- echo "clean.requireForce set and -n or -f not given; refusing to clean"
- exit 1
-fi
-
-case "$ignored,$ignoredonly" in
- 1,1) usage;;
-esac
-
-if [ -z "$ignored" ]; then
- excl="--exclude-per-directory=.gitignore"
- if [ -f "$GIT_DIR/info/exclude" ]; then
- excl_info="--exclude-from=$GIT_DIR/info/exclude"
- fi
- if [ "$ignoredonly" ]; then
- excl="$excl --ignored"
- fi
-fi
-
-git ls-files --others --directory $excl ${excl_info:+"$excl_info"} -- "$@" |
-while read -r file; do
- if [ -d "$file" -a ! -L "$file" ]; then
- if [ -z "$cleandir" ]; then
- $rm_refuse "$file"
- continue
- fi
- $echo1 "Removing $file"
- $rmrf "$file"
- else
- $echo1 "Removing $file"
- $rmf "$file"
- fi
-done
diff --git a/git-clone.sh b/git-clone.sh
index 5e582fe247..0d686c3a03 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -8,15 +8,36 @@
# See git-sh-setup why.
unset CDPATH
+OPTIONS_SPEC="\
+git-clone [options] [--] <repo> [<dir>]
+--
+n,no-checkout don't create a checkout
+bare create a bare repository
+naked create a bare repository
+l,local to clone from a local repository
+no-hardlinks don't use local hardlinks, always copy
+s,shared setup as a shared repository
+template= path to the template directory
+q,quiet be quiet
+reference= reference repository
+o,origin= use <name> instead of 'origin' to track upstream
+u,upload-pack= path to git-upload-pack on the remote
+depth= create a shallow clone of that depth
+
+use-separate-remote compatibility, do not use
+no-separate-remote compatibility, do not use"
+
die() {
echo >&2 "$@"
exit 1
}
usage() {
- die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [--depth <n>] [-n] <repo> [<dir>]"
+ exec "$0" -h
}
+eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+
get_repo_base() {
(
cd "`/bin/pwd`" &&
@@ -28,17 +49,19 @@ get_repo_base() {
) 2>/dev/null
}
-if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+if [ -n "$GIT_SSL_NO_VERIFY" -o \
+ "`git config --bool http.sslVerify`" = false ]; then
curl_extra_args="-k"
fi
http_fetch () {
# $1 = Remote, $2 = Local
- curl -nsfL $curl_extra_args "$1" >"$2" ||
- case $? in
- 126|127) exit ;;
- *) return $? ;;
- esac
+ curl -nsfL $curl_extra_args "$1" >"$2"
+ curl_exit_status=$?
+ case $curl_exit_status in
+ 126|127) exit ;;
+ *) return $curl_exit_status ;;
+ esac
}
clone_dumb_http () {
@@ -105,64 +128,57 @@ depth=
no_progress=
local_explicitly_asked_for=
test -t 1 || no_progress=--no-progress
-while
- case "$#,$1" in
- 0,*) break ;;
- *,-n|*,--no|*,--no-|*,--no-c|*,--no-ch|*,--no-che|*,--no-chec|\
- *,--no-check|*,--no-checko|*,--no-checkou|*,--no-checkout)
- no_checkout=yes ;;
- *,--na|*,--nak|*,--nake|*,--naked|\
- *,-b|*,--b|*,--ba|*,--bar|*,--bare) bare=yes ;;
- *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local)
- local_explicitly_asked_for=yes
- use_local_hardlink=yes ;;
- *,--no-h|*,--no-ha|*,--no-har|*,--no-hard|*,--no-hardl|\
- *,--no-hardli|*,--no-hardlin|*,--no-hardlink|*,--no-hardlinks)
- use_local_hardlink=no ;;
- *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared)
- local_shared=yes; ;;
- 1,--template) usage ;;
- *,--template)
+
+while test $# != 0
+do
+ case "$1" in
+ -n|--no-checkout)
+ no_checkout=yes ;;
+ --naked|--bare)
+ bare=yes ;;
+ -l|--local)
+ local_explicitly_asked_for=yes
+ use_local_hardlink=yes
+ ;;
+ --no-hardlinks)
+ use_local_hardlink=no ;;
+ -s|--shared)
+ local_shared=yes ;;
+ --template)
shift; template="--template=$1" ;;
- *,--template=*)
- template="$1" ;;
- *,-q|*,--quiet) quiet=-q ;;
- *,--use-separate-remote) ;;
- *,--no-separate-remote)
+ -q|--quiet)
+ quiet=-q ;;
+ --use-separate-remote|--no-separate-remote)
die "clones are always made with separate-remote layout" ;;
- 1,--reference) usage ;;
- *,--reference)
+ --reference)
shift; reference="$1" ;;
- *,--reference=*)
- reference=`expr "z$1" : 'z--reference=\(.*\)'` ;;
- *,-o|*,--or|*,--ori|*,--orig|*,--origi|*,--origin)
- case "$2" in
+ -o|--origin)
+ shift;
+ case "$1" in
'')
usage ;;
*/*)
- die "'$2' is not suitable for an origin name"
+ die "'$1' is not suitable for an origin name"
esac
- git check-ref-format "heads/$2" ||
- die "'$2' is not suitable for a branch name"
+ git check-ref-format "heads/$1" ||
+ die "'$1' is not suitable for a branch name"
test -z "$origin_override" ||
die "Do not give more than one --origin options."
origin_override=yes
- origin="$2"; shift
+ origin="$1"
;;
- 1,-u|1,--upload-pack) usage ;;
- *,-u|*,--upload-pack)
+ -u|--upload-pack)
shift
upload_pack="--upload-pack=$1" ;;
- *,--upload-pack=*)
- upload_pack=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
- 1,--depth) usage;;
- *,--depth)
+ --depth)
+ shift
+ depth="--depth=$1" ;;
+ --)
shift
- depth="--depth=$1";;
- *,-*) usage ;;
- *) break ;;
+ break ;;
+ *)
+ usage ;;
esac
-do
shift
done
@@ -190,7 +206,10 @@ fi
# it is local
if base=$(get_repo_base "$repo"); then
repo="$base"
- local=yes
+ if test -z "$depth"
+ then
+ local=yes
+ fi
fi
dir="$2"
@@ -214,7 +233,7 @@ cleanup() {
trap cleanup 0
mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
-W=$(cd "$GIT_WORK_TREE" && pwd) && export GIT_WORK_TREE="$W"
+W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE
if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
GIT_DIR="$D"
else
@@ -282,7 +301,8 @@ yes)
find objects -type f -print | sed -e 1q)
# objects directory should not be empty because
# we are cloning!
- test -f "$repo/$sample_file" || exit
+ test -f "$repo/$sample_file" ||
+ die "fatal: cannot clone empty repository"
if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
then
rm -f "$GIT_DIR/objects/sample"
@@ -389,11 +409,12 @@ else
cd "$D" || exit
fi
-if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD"
+if test -z "$bare"
then
# a non-bare repository is always in separate-remote layout
remote_top="refs/remotes/$origin"
- head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+ head_sha1=
+ test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
case "$head_sha1" in
'ref: refs/'*)
# Uh-oh, the remote told us (http transport done against
@@ -450,9 +471,16 @@ then
git config branch."$head_points_at".merge "refs/heads/$head_points_at"
;;
'')
- # Source had detached HEAD pointing nowhere
- git update-ref --no-deref HEAD "$head_sha1" &&
- rm -f "refs/remotes/$origin/HEAD"
+ if test -z "$head_sha1"
+ then
+ # Source had nonexistent ref in HEAD
+ echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
+ no_checkout=t
+ else
+ # Source had detached HEAD pointing nowhere
+ git update-ref --no-deref HEAD "$head_sha1" &&
+ rm -f "refs/remotes/$origin/HEAD"
+ fi
;;
esac
diff --git a/git-compat-util.h b/git-compat-util.h
index f23d934f66..2a40703c85 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -4,10 +4,24 @@
#define _FILE_OFFSET_BITS 64
#ifndef FLEX_ARRAY
-#if defined(__GNUC__) && (__GNUC__ < 3)
-#define FLEX_ARRAY 0
-#else
-#define FLEX_ARRAY /* empty */
+/*
+ * See if our compiler is known to support flexible array members.
+ */
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+# define FLEX_ARRAY /* empty */
+#elif defined(__GNUC__)
+# if (__GNUC__ >= 3)
+# define FLEX_ARRAY /* empty */
+# else
+# define FLEX_ARRAY 0 /* older GNU extension */
+# endif
+#endif
+
+/*
+ * Otherwise, default to safer but a bit wasteful traditional style
+ */
+#ifndef FLEX_ARRAY
+# define FLEX_ARRAY 1
#endif
#endif
@@ -20,6 +34,7 @@
#endif
#define MSB(x, bits) ((x) & TYPEOF(x)(~0ULL << (sizeof(x) * 8 - (bits))))
+#define HAS_MULTI_BITS(i) ((i) & ((i) - 1)) /* checks if an integer has more than 1 bit set */
/* Approximation of the length of the decimal representation of this type. */
#define decimal_length(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
@@ -52,6 +67,10 @@
#include <fnmatch.h>
#include <sys/poll.h>
#include <sys/socket.h>
+#include <sys/ioctl.h>
+#ifndef NO_SYS_SELECT_H
+#include <sys/select.h>
+#endif
#include <assert.h>
#include <regex.h>
#include <netinet/in.h>
@@ -105,6 +124,8 @@ extern void set_die_routine(void (*routine)(const char *err, va_list params) NOR
extern void set_error_routine(void (*routine)(const char *err, va_list params));
extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
+extern int prefixcmp(const char *str, const char *prefix);
+
#ifdef NO_MMAP
#ifndef PROT_READ
@@ -147,6 +168,11 @@ extern ssize_t git_pread(int fd, void *buf, size_t count, off_t offset);
extern int gitsetenv(const char *, const char *, int);
#endif
+#ifdef NO_MKDTEMP
+#define mkdtemp gitmkdtemp
+extern char *gitmkdtemp(char *);
+#endif
+
#ifdef NO_UNSETENV
#define unsetenv gitunsetenv
extern void gitunsetenv(const char *);
@@ -178,6 +204,27 @@ void *gitmemmem(const void *haystack, size_t haystacklen,
const void *needle, size_t needlelen);
#endif
+#ifdef FREAD_READS_DIRECTORIES
+#define fopen(a,b) git_fopen(a,b)
+extern FILE *git_fopen(const char*, const char*);
+#endif
+
+#ifdef __GLIBC_PREREQ
+#if __GLIBC_PREREQ(2, 1)
+#define HAVE_STRCHRNUL
+#endif
+#endif
+
+#ifndef HAVE_STRCHRNUL
+#define strchrnul gitstrchrnul
+static inline char *gitstrchrnul(const char *s, int c)
+{
+ while (*s && *s != c)
+ s++;
+ return (char *)s;
+}
+#endif
+
extern void release_pack_memory(size_t, int);
static inline char* xstrdup(const char *str)
@@ -358,11 +405,6 @@ static inline int sane_case(int x, int high)
return x;
}
-static inline int prefixcmp(const char *str, const char *prefix)
-{
- return strncmp(str, prefix, strlen(prefix));
-}
-
static inline int strtoul_ui(char const *s, int base, unsigned int *result)
{
unsigned long ul;
@@ -376,4 +418,23 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)
return 0;
}
+static inline int strtol_i(char const *s, int base, int *result)
+{
+ long ul;
+ char *p;
+
+ errno = 0;
+ ul = strtol(s, &p, base);
+ if (errno || *p || p == s || (int) ul != ul)
+ return -1;
+ *result = ul;
+ return 0;
+}
+
+#ifdef INTERNAL_QSORT
+void git_qsort(void *base, size_t nmemb, size_t size,
+ int(*compar)(const void *, const void *));
+#define qsort git_qsort
+#endif
+
#endif
diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
index 7a955d4530..b6036bd4d3 100755
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
@@ -1,28 +1,41 @@
#!/usr/bin/perl -w
-# Known limitations:
-# - does not propagate permissions
-# - error handling has not been extensively tested
-#
-
use strict;
use Getopt::Std;
use File::Temp qw(tempdir);
use Data::Dumper;
use File::Basename qw(basename dirname);
+use File::Spec;
-unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
- die "GIT_DIR is not defined or is unreadable";
-}
+our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w);
-our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u);
-
-getopts('uhPpvcfam:d:');
+getopts('uhPpvcfam:d:w:');
$opt_h && usage();
die "Need at least one commit identifier!" unless @ARGV;
+if ($opt_w) {
+ # Remember where GIT_DIR is before changing to CVS checkout
+ unless ($ENV{GIT_DIR}) {
+ # No GIT_DIR set. Figure it out for ourselves
+ my $gd =`git-rev-parse --git-dir`;
+ chomp($gd);
+ $ENV{GIT_DIR} = $gd;
+ }
+ # Make sure GIT_DIR is absolute
+ $ENV{GIT_DIR} = File::Spec->rel2abs($ENV{GIT_DIR});
+
+ if (! -d $opt_w."/CVS" ) {
+ die "$opt_w is not a CVS checkout";
+ }
+ chdir $opt_w or die "Cannot change to CVS checkout at $opt_w";
+}
+unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
+ die "GIT_DIR is not defined or is unreadable";
+}
+
+
my @cvs;
if ($opt_d) {
@cvs = ('cvs', '-d', $opt_d);
@@ -82,6 +95,7 @@ foreach my $line (@commit) {
}
}
+my $noparent = "0000000000000000000000000000000000000000";
if ($parent) {
my $found;
# double check that it's a valid parent
@@ -95,8 +109,10 @@ if ($parent) {
} else { # we don't have a parent from the cmdline...
if (@parents == 1) { # it's safe to get it from the commit
$parent = $parents[0];
- } else { # or perhaps not!
- die "This commit has more than one parent -- please name the parent you want to use explicitly";
+ } elsif (@parents == 0) { # there is no parent
+ $parent = $noparent;
+ } else { # cannot choose automatically from multiple parents
+ die "This commit has more than one parent -- please name the parent you want to use explicitly";
}
}
@@ -116,7 +132,11 @@ if ($opt_a) {
}
close MSG;
-`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+if ($parent eq $noparent) {
+ `git-diff-tree --binary -p --root $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+} else {
+ `git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+}
## apply non-binary changes
@@ -174,18 +194,42 @@ foreach my $f (@files) {
my %cvsstat;
if (@canstatusfiles) {
if ($opt_u) {
- my @updated = safe_pipe_capture(@cvs, 'update', @canstatusfiles);
+ my @updated = xargs_safe_pipe_capture([@cvs, 'update'], @canstatusfiles);
print @updated;
}
- my @cvsoutput;
- @cvsoutput= safe_pipe_capture(@cvs, 'status', @canstatusfiles);
- my $matchcount = 0;
- foreach my $l (@cvsoutput) {
- chomp $l;
- if ( $l =~ /^File:/ and $l =~ /Status: (.*)$/ ) {
- $cvsstat{$canstatusfiles[$matchcount]} = $1;
- $matchcount++;
+ # "cvs status" reorders the parameters, notably when there are multiple
+ # arguments with the same basename. So be precise here.
+
+ my %added = map { $_ => 1 } @afiles;
+ my %todo = map { $_ => 1 } @canstatusfiles;
+
+ while (%todo) {
+ my @canstatusfiles2 = ();
+ my %fullname = ();
+ foreach my $name (keys %todo) {
+ my $basename = basename($name);
+
+ $basename = "no file " . $basename if (exists($added{$basename}));
+ chomp($basename);
+
+ if (!exists($fullname{$basename})) {
+ $fullname{$basename} = $name;
+ push (@canstatusfiles2, $name);
+ delete($todo{$name});
}
+ }
+ my @cvsoutput;
+ @cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles2);
+ foreach my $l (@cvsoutput) {
+ chomp $l;
+ if ($l =~ /^File:\s+(.*\S)\s+Status: (.*)$/) {
+ if (!exists($fullname{$1})) {
+ print STDERR "Huh? Status reported for unexpected file '$1'\n";
+ } else {
+ $cvsstat{$fullname{$1}} = $2;
+ }
+ }
+ }
}
}
@@ -219,6 +263,17 @@ print "Applying\n";
print "Patch applied successfully. Adding new files and directories to CVS\n";
my $dirtypatch = 0;
+
+#
+# We have to add the directories in order otherwise we will have
+# problems when we try and add the sub-directory of a directory we
+# have not added yet.
+#
+# Luckily this is easy to deal with by sorting the directories and
+# dealing with the shortest ones first.
+#
+@dirs = sort { length $a <=> length $b} @dirs;
+
foreach my $d (@dirs) {
if (system(@cvs,'add',$d)) {
$dirtypatch = 1;
@@ -256,13 +311,14 @@ if ($dirtypatch) {
print "You'll need to apply the patch in .cvsexportcommit.diff manually\n";
print "using a patch program. After applying the patch and resolving the\n";
print "problems you may commit using:";
+ print "\n cd \"$opt_w\"" if $opt_w;
print "\n $cmd\n\n";
exit(1);
}
if ($opt_c) {
print "Autocommit\n $cmd\n";
- print safe_pipe_capture(@cvs, 'commit', '-F', '.msg', @files);
+ print xargs_safe_pipe_capture([@cvs, 'commit', '-F', '.msg'], @files);
if ($?) {
die "Exiting: The commit did not succeed";
}
@@ -283,7 +339,7 @@ sleep(1);
sub usage {
print STDERR <<END;
-Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
+Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-u] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
END
exit(1);
}
@@ -302,15 +358,24 @@ sub safe_pipe_capture {
return wantarray ? @output : join('',@output);
}
-sub safe_pipe_capture_blob {
- my $output;
- if (my $pid = open my $child, '-|') {
- local $/;
- undef $/;
- $output = (<$child>);
- close $child or die join(' ',@_).": $! $?";
- } else {
- exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
- }
- return $output;
+sub xargs_safe_pipe_capture {
+ my $MAX_ARG_LENGTH = 65536;
+ my $cmd = shift;
+ my @output;
+ my $output;
+ while(@_) {
+ my @args;
+ my $length = 0;
+ while(@_ && $length < $MAX_ARG_LENGTH) {
+ push @args, shift;
+ $length += length($args[$#args]);
+ }
+ if (wantarray) {
+ push @output, safe_pipe_capture(@$cmd, @args);
+ }
+ else {
+ $output .= safe_pipe_capture(@$cmd, @args);
+ }
+ }
+ return wantarray ? @output : $output;
}
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 2954fb846e..9516242338 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -88,7 +88,7 @@ sub write_author_info($) {
close ($f);
}
-# convert getopts specs for use by git-repo-config
+# convert getopts specs for use by git config
sub read_repo_config {
# Split the string between characters, unless there is a ':'
# So "abc:de" becomes ["a", "b", "c:", "d", "e"]
@@ -96,7 +96,7 @@ sub read_repo_config {
foreach my $o (@opts) {
my $key = $o;
$key =~ s/://g;
- my $arg = 'git-repo-config';
+ my $arg = 'git config';
$arg .= ' --bool' if ($o !~ /:$/);
chomp(my $tmp = `$arg --get cvsimport.$key`);
@@ -108,10 +108,6 @@ sub read_repo_config {
}
}
}
- if (@ARGV == 0) {
- chomp(my $module = `git-repo-config --get cvsimport.module`);
- push(@ARGV, $module);
- }
}
my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:";
@@ -119,6 +115,10 @@ read_repo_config($opts);
getopts($opts) or usage();
usage if $opt_h;
+if (@ARGV == 0) {
+ chomp(my $module = `git config --get cvsimport.module`);
+ push(@ARGV, $module) if $? == 0;
+}
@ARGV <= 1 or usage("You can't specify more than one CVS module");
if ($opt_d) {
@@ -164,7 +164,7 @@ if ($#ARGV == 0) {
our @mergerx = ();
if ($opt_m) {
- @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+ @mergerx = ( qr/\b(?:from|of|merge|merging|merged) (\w+)/i );
}
if ($opt_M) {
push (@mergerx, qr/$opt_M/);
@@ -223,7 +223,8 @@ sub conn {
}
}
- $user="anonymous" unless defined $user;
+ # if username is not explicit in CVSROOT, then use current user, as cvs would
+ $user=(getlogin() || $ENV{'LOGNAME'} || $ENV{'USER'} || "anonymous") unless $user;
my $rr2 = "-";
unless ($port) {
$rr2 = ":pserver:$user\@$serv:$repo";
@@ -526,18 +527,12 @@ sub is_sha1 {
return $s =~ /^[a-f0-9]{40}$/;
}
-sub get_headref ($$) {
- my $name = shift;
- my $git_dir = shift;
-
- my $f = "$git_dir/$remote/$name";
- if (open(my $fh, $f)) {
- chomp(my $r = <$fh>);
- is_sha1($r) or die "Cannot get head id for $name ($r): $!";
- return $r;
- }
- die "unable to open $f: $!" unless $! == POSIX::ENOENT;
- return undef;
+sub get_headref ($) {
+ my $name = shift;
+ my $r = `git rev-parse --verify '$name' 2>/dev/null`;
+ return undef unless $? == 0;
+ chomp $r;
+ return $r;
}
-d $git_tree
@@ -637,6 +632,7 @@ unless ($opt_P) {
print $cvspsfh $_;
}
close CVSPS;
+ $? == 0 or die "git-cvsimport: fatal: cvsps reported error\n";
close $cvspsfh;
} else {
$cvspsfile = $opt_P;
@@ -697,7 +693,8 @@ my (@old,@new,@skipped,%ignorebranch);
$ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
sub commit {
- if ($branch eq $opt_o && !$index{branch} && !get_headref($branch, $git_dir)) {
+ if ($branch eq $opt_o && !$index{branch} &&
+ !get_headref("$remote/$branch")) {
# looks like an initial commit
# use the index primed by git-init
$ENV{GIT_INDEX_FILE} = "$git_dir/index";
@@ -721,7 +718,7 @@ sub commit {
update_index(@old, @new);
@old = @new = ();
my $tree = write_tree();
- my $parent = get_headref($last_branch, $git_dir);
+ my $parent = get_headref("$remote/$last_branch");
print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
my @commit_args;
@@ -732,7 +729,7 @@ sub commit {
foreach my $rx (@mergerx) {
next unless $logmsg =~ $rx && $1;
my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
- if (my $sha1 = get_headref($mparent, $git_dir)) {
+ if (my $sha1 = get_headref("$remote/$mparent")) {
push @commit_args, '-p', $mparent;
print "Merge parent branch: $mparent\n" if $opt_v;
}
@@ -818,6 +815,7 @@ while (<CVS>) {
$state = 4;
} elsif ($state == 4 and s/^Branch:\s+//) {
s/\s+$//;
+ tr/_/\./ if ( $opt_u );
s/[\/]/$opt_s/g;
$branch = $_;
$state = 5;
@@ -851,7 +849,7 @@ while (<CVS>) {
}
if (!$opt_a && $starttime - 300 - (defined $opt_z ? $opt_z : 300) <= $date) {
# skip if the commit is too recent
- # that the cvsps default fuzz is 300s, we give ourselves another
+ # given that the cvsps default fuzz is 300s, we give ourselves another
# 300s just in case -- this also prevents skipping commits
# due to server clock drift
print "skip patchset $patchset: $date too recent\n" if $opt_v;
@@ -868,29 +866,27 @@ while (<CVS>) {
print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
$ancestor = $opt_o;
}
- if (-f "$git_dir/$remote/$branch") {
+ if (defined get_headref("$remote/$branch")) {
print STDERR "Branch $branch already exists!\n";
$state=11;
next;
}
- unless (open(H,"$git_dir/$remote/$ancestor")) {
+ my $id = get_headref("$remote/$ancestor");
+ if (!$id) {
print STDERR "Branch $ancestor does not exist!\n";
$ignorebranch{$branch} = 1;
$state=11;
next;
}
- chomp(my $id = <H>);
- close(H);
- unless (open(H,"> $git_dir/$remote/$branch")) {
- print STDERR "Could not create branch $branch: $!\n";
+
+ system(qw(git update-ref -m cvsimport),
+ "$remote/$branch", $id);
+ if($? != 0) {
+ print STDERR "Could not create branch $branch\n";
$ignorebranch{$branch} = 1;
$state=11;
next;
}
- print H "$id\n"
- or die "Could not write branch $branch: $!";
- close(H)
- or die "Could not write branch $branch: $!";
}
$last_branch = $branch if $branch ne $last_branch;
$state = 9;
@@ -1002,7 +998,7 @@ if ($orig_branch) {
$orig_branch = "master";
print "DONE; creating $orig_branch branch\n" if $opt_v;
system("git-update-ref", "refs/heads/master", "$remote/$opt_o")
- unless -f "$git_dir/refs/heads/master";
+ unless defined get_headref('refs/heads/master');
system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
if ($opt_r && $opt_o ne 'HEAD');
system('git-update-ref', 'HEAD', "$orig_branch");
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 13dbd27a80..afe3d0b7fe 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -145,8 +145,10 @@ if ($state->{method} eq 'pserver') {
}
my $request = $1;
$line = <STDIN>; chomp $line;
- req_Root('root', $line) # reuse Root
- or die "E Invalid root $line \n";
+ unless (req_Root('root', $line)) { # reuse Root
+ print "E Invalid root $line \n";
+ exit 1;
+ }
$line = <STDIN>; chomp $line;
unless ($line eq 'anonymous') {
print "E Only anonymous user allowed via pserver\n";
@@ -1209,13 +1211,13 @@ sub req_ci
chdir $tmpdir;
- # populate the temporary index based
+ # populate the temporary index
system("git-read-tree", $parenthash);
unless ($? == 0)
{
die "Error running git-read-tree $state->{module} $file_index $!";
}
- $log->info("Created index '$file_index' with for head $state->{module} - exit status $?");
+ $log->info("Created index '$file_index' for head $state->{module} - exit status $?");
my @committedfiles = ();
my %oldmeta;
@@ -1235,7 +1237,7 @@ sub req_ci
my ( $filepart, $dirpart ) = filenamesplit($filename);
- # do a checkout of the file if it part of this tree
+ # do a checkout of the file if it is part of this tree
if ($wrev) {
system('git-checkout-index', '-f', '-u', $filename);
unless ($? == 0) {
@@ -1322,11 +1324,11 @@ sub req_ci
exit;
}
- # Check that this is allowed, just as we would with a receive-pack
- my @cmd = ( $ENV{GIT_DIR}.'hooks/update', "refs/heads/$state->{module}",
+ ### Emulate git-receive-pack by running hooks/update
+ my @hook = ( $ENV{GIT_DIR}.'hooks/update', "refs/heads/$state->{module}",
$parenthash, $commithash );
- if( -x $cmd[0] ) {
- unless( system( @cmd ) == 0 )
+ if( -x $hook[0] ) {
+ unless( system( @hook ) == 0 )
{
$log->warn("Commit failed (update hook declined to update ref)");
print "error 1 Commit failed (update hook declined)\n";
@@ -1335,6 +1337,7 @@ sub req_ci
}
}
+ ### Update the ref
if (system(qw(git update-ref -m), "cvsserver ci",
"refs/heads/$state->{module}", $commithash, $parenthash)) {
$log->warn("update-ref for $state->{module} failed.");
@@ -1342,6 +1345,24 @@ sub req_ci
exit;
}
+ ### Emulate git-receive-pack by running hooks/post-receive
+ my $hook = $ENV{GIT_DIR}.'hooks/post-receive';
+ if( -x $hook ) {
+ open(my $pipe, "| $hook") || die "can't fork $!";
+
+ local $SIG{PIPE} = sub { die 'pipe broke' };
+
+ print $pipe "$parenthash $commithash refs/heads/$state->{module}\n";
+
+ close $pipe || die "bad pipe: $! $?";
+ }
+
+ ### Then hooks/post-update
+ $hook = $ENV{GIT_DIR}.'hooks/post-update';
+ if (-x $hook) {
+ system($hook, "refs/heads/$state->{module}");
+ }
+
$updater->update();
# foreach file specified on the command line ...
@@ -2522,8 +2543,15 @@ sub update
if ($parent eq $lastpicked) {
next;
}
- my $base = safe_pipe_capture('git-merge-base',
+ my $base = eval {
+ safe_pipe_capture('git-merge-base',
$lastpicked, $parent);
+ };
+ # The two branches may not be related at all,
+ # in which case merge base simply fails to find
+ # any, but that's Ok.
+ next if ($@);
+
chomp $base;
if ($base) {
my @merged;
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index ffcc408ee5..49e13f0bb1 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -8,6 +8,9 @@
# a new branch. You can specify a number of filters to modify the commits,
# files and trees.
+# The following functions will also be available in the commit filter:
+
+functions=$(cat << \EOF
warn () {
echo "$*" >&2
}
@@ -46,6 +49,10 @@ die()
echo "$*" >&2
exit 1
}
+EOF
+)
+
+eval "$functions"
# When piped a commit, output a script to set the ident of either
# "author" or "committer
@@ -59,17 +66,17 @@ set_ident () {
h
s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
s/'\''/'\''\'\'\''/g
- s/.*/export GIT_'$uid'_NAME='\''&'\''/p
+ s/.*/GIT_'$uid'_NAME='\''&'\''; export GIT_'$uid'_NAME/p
g
s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
s/'\''/'\''\'\'\''/g
- s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
+ s/.*/GIT_'$uid'_EMAIL='\''&'\''; export GIT_'$uid'_EMAIL/p
g
s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
s/'\''/'\''\'\'\''/g
- s/.*/export GIT_'$uid'_DATE='\''&'\''/p
+ s/.*/GIT_'$uid'_DATE='\''&'\''; export GIT_'$uid'_DATE/p
q
}
@@ -77,14 +84,9 @@ set_ident () {
LANG=C LC_ALL=C sed -ne "$pick_id_script"
# Ensure non-empty id name.
- echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
+ echo "case \"\$GIT_${uid}_NAME\" in \"\") GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\" && export GIT_${uid}_NAME;; esac"
}
-# This script can be sourced by the commit filter to get the functions
-test "a$SOURCE_FUNCTIONS" = a1 && return
-this_script="$(cd "$(dirname "$0")"; pwd)"/$(basename "$0")
-export this_script
-
USAGE="[--env-filter <command>] [--tree-filter <command>] \
[--index-filter <command>] [--parent-filter <command>] \
[--msg-filter <command>] [--commit-filter <command>] \
@@ -92,10 +94,11 @@ USAGE="[--env-filter <command>] [--tree-filter <command>] \
[--original <namespace>] [-d <directory>] [-f | --force] \
[<rev-list options>...]"
+OPTIONS_SPEC=
. git-sh-setup
git diff-files --quiet &&
- git diff-index --cached --quiet HEAD ||
+ git diff-index --cached --quiet HEAD -- ||
die "Cannot rewrite branch(es) with a dirty working directory."
tempdir=.git-rewrite
@@ -111,7 +114,6 @@ orig_namespace=refs/original/
force=
while :
do
- test $# = 0 && usage
case "$1" in
--)
shift
@@ -155,7 +157,7 @@ do
filter_msg="$OPTARG"
;;
--commit-filter)
- filter_commit='SOURCE_FUNCTIONS=1 . "$this_script";'" $OPTARG"
+ filter_commit="$functions; $OPTARG"
;;
--tag-name-filter)
filter_tag_name="$OPTARG"
@@ -186,6 +188,9 @@ cd "$tempdir/t" &&
workdir="$(pwd)" ||
die ""
+# Remove tempdir on exit
+trap 'cd ../..; rm -rf "$tempdir"' 0
+
# Make sure refs/original is empty
git for-each-ref > "$tempdir"/backup-refs
while read sha1 type name
@@ -203,32 +208,18 @@ done < "$tempdir"/backup-refs
ORIG_GIT_DIR="$GIT_DIR"
ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
-export GIT_DIR GIT_WORK_TREE=.
-
-# These refs should be updated if their heads were rewritten
-
-git rev-parse --revs-only --symbolic "$@" |
-while read ref
-do
- # normalize ref
- case "$ref" in
- HEAD)
- ref="$(git symbolic-ref "$ref")"
- ;;
- refs/*)
- ;;
- *)
- ref="$(git for-each-ref --format='%(refname)' |
- grep /"$ref")"
- esac
+GIT_WORK_TREE=.
+export GIT_DIR GIT_WORK_TREE
- git check-ref-format "$ref" && echo "$ref"
-done > "$tempdir"/heads
+# The refs should be updated if their heads were rewritten
+git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" |
+sed -e '/^^/d' >"$tempdir"/heads
test -s "$tempdir"/heads ||
die "Which ref do you want to rewrite?"
-export GIT_INDEX_FILE="$(pwd)/../index"
+GIT_INDEX_FILE="$(pwd)/../index"
+export GIT_INDEX_FILE
git read-tree || die "Could not seed the index"
ret=0
@@ -264,7 +255,8 @@ while read commit parents; do
git read-tree -i -m $commit:"$filter_subdir"
esac || die "Could not initialize the index"
- export GIT_COMMIT=$commit
+ GIT_COMMIT=$commit
+ export GIT_COMMIT
git cat-file commit "$commit" >../commit ||
die "Cannot read commit $commit"
@@ -284,10 +276,11 @@ while read commit parents; do
eval "$filter_tree" < /dev/null ||
die "tree filter failed: $filter_tree"
- git diff-index -r $commit | cut -f 2- | tr '\n' '\0' | \
- xargs -0 git update-index --add --replace --remove
- git ls-files -z --others | \
- xargs -0 git update-index --add --replace --remove
+ (
+ git diff-index -r --name-only $commit
+ git ls-files --others
+ ) |
+ git update-index --add --replace --remove --stdin
fi
eval "$filter_index" < /dev/null ||
@@ -336,7 +329,6 @@ done < "$tempdir"/heads
_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"
-count=0
echo
while read ref
do
@@ -374,7 +366,6 @@ do
;;
esac
git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1
- count=$(($count+1))
done < "$tempdir"/heads
# TODO: This should possibly go, with the semantics that all positive given
@@ -398,7 +389,8 @@ if [ "$filter_tag_name" ]; then
[ -f "../map/$sha1" ] || continue
new_sha1="$(cat "../map/$sha1")"
- export GIT_COMMIT="$sha1"
+ GIT_COMMIT="$sha1"
+ export GIT_COMMIT
new_ref="$(echo "$ref" | eval "$filter_tag_name")" ||
die "tag name filter failed: $filter_tag_name"
@@ -416,9 +408,8 @@ fi
cd ../..
rm -rf "$tempdir"
-echo
-test $count -gt 0 && echo "These refs were rewritten:"
-git show-ref | grep ^"$orig_namespace"
+
+trap - 0
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
test -z "$ORIG_GIT_DIR" || GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
diff --git a/git-gui/.gitignore b/git-gui/.gitignore
index 020b86deae..6483b21cbf 100644
--- a/git-gui/.gitignore
+++ b/git-gui/.gitignore
@@ -1,5 +1,8 @@
+.DS_Store
+config.mak
+Git Gui.app*
+git-gui.tcl
GIT-VERSION-FILE
GIT-GUI-VARS
-git-citool
git-gui
lib/tclIndex
diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN
index 9770b0bc27..cfe46a857e 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.8.GITGUI
+DEF_VER=0.9.GITGUI
LF='
'
diff --git a/git-gui/Makefile b/git-gui/Makefile
index 18e6750137..081d7550a7 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -2,18 +2,28 @@ all::
# Define V=1 to have a more verbose compile.
#
+# Define NO_MSGFMT if you do not have msgfmt from the GNU gettext
+# package and want to use our rough pure Tcl po->msg translator.
+# TCL_PATH must be vaild for this to work.
+#
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
@$(SHELL_PATH) ./GIT-VERSION-GEN
-include GIT-VERSION-FILE
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
SCRIPT_SH = git-gui.sh
+GITGUI_MAIN := git-gui
GITGUI_BUILT_INS = git-citool
-ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH))
ALL_LIBFILES = $(wildcard lib/*.tcl)
PRELOAD_FILES = lib/class.tcl
+NONTCL_LIBFILES = \
+ lib/git-gui.ico \
+ $(wildcard lib/win32_*.js) \
+#end NONTCL_LIBFILES
ifndef SHELL_PATH
SHELL_PATH = /bin/sh
@@ -31,15 +41,17 @@ ifndef INSTALL
INSTALL = install
endif
-RM_F ?= rm -f
+RM_RF ?= rm -rf
RMDIR ?= rmdir
-INSTALL_D0 = $(INSTALL) -d -m755 # space is required here
+INSTALL_D0 = $(INSTALL) -d -m 755 # space is required here
INSTALL_D1 =
-INSTALL_R0 = $(INSTALL) -m644 # space is required here
+INSTALL_R0 = $(INSTALL) -m 644 # space is required here
INSTALL_R1 =
-INSTALL_X0 = $(INSTALL) -m755 # space is required here
+INSTALL_X0 = $(INSTALL) -m 755 # space is required here
INSTALL_X1 =
+INSTALL_A0 = find # space is required here
+INSTALL_A1 = | cpio -pud
INSTALL_L0 = rm -f # space is required here
INSTALL_L1 = && ln # space is required here
INSTALL_L2 =
@@ -47,23 +59,26 @@ INSTALL_L3 =
REMOVE_D0 = $(RMDIR) # space is required here
REMOVE_D1 = || true
-REMOVE_F0 = $(RM_F) # space is required here
+REMOVE_F0 = $(RM_RF) # space is required here
REMOVE_F1 =
CLEAN_DST = true
ifndef V
QUIET = @
- QUIET_GEN = $(QUIET)echo ' ' GEN $@ &&
- QUIET_BUILT_IN = $(QUIET)echo ' ' BUILTIN $@ &&
+ QUIET_GEN = $(QUIET)echo ' ' GEN '$@' &&
QUIET_INDEX = $(QUIET)echo ' ' INDEX $(dir $@) &&
+ QUIET_MSGFMT0 = $(QUIET)printf ' MSGFMT %12s ' $@ && v=`
+ QUIET_MSGFMT1 = 2>&1` && echo "$$v" | sed -e 's/fuzzy translations/fuzzy/' | sed -e 's/ messages*//g'
QUIET_2DEVNULL = 2>/dev/null
INSTALL_D0 = dir=
- INSTALL_D1 = && echo ' ' DEST $$dir && $(INSTALL) -d -m755 "$$dir"
+ INSTALL_D1 = && echo ' ' DEST $$dir && $(INSTALL) -d -m 755 "$$dir"
INSTALL_R0 = src=
- INSTALL_R1 = && echo ' ' INSTALL 644 `basename $$src` && $(INSTALL) -m644 $$src
+ INSTALL_R1 = && echo ' ' INSTALL 644 `basename $$src` && $(INSTALL) -m 644 $$src
INSTALL_X0 = src=
- INSTALL_X1 = && echo ' ' INSTALL 755 `basename $$src` && $(INSTALL) -m755 $$src
+ INSTALL_X1 = && echo ' ' INSTALL 755 `basename $$src` && $(INSTALL) -m 755 $$src
+ INSTALL_A0 = src=
+ INSTALL_A1 = && echo ' ' INSTALL ' ' `basename "$$src"` && find "$$src" | cpio -pud
INSTALL_L0 = dst=
INSTALL_L1 = && src=
@@ -74,51 +89,143 @@ ifndef V
REMOVE_D0 = dir=
REMOVE_D1 = && echo ' ' REMOVE $$dir && test -d "$$dir" && $(RMDIR) "$$dir" || true
REMOVE_F0 = dst=
- REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_F) "$$dst"
+ REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_RF) "$$dst"
endif
TCL_PATH ?= tclsh
TCLTK_PATH ?= wish
+ifeq ($(uname_S),Darwin)
+ TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app
+ ifeq ($(shell expr "$(uname_R)" : '9\.'),2)
+ TKFRAMEWORK = /System/Library/Frameworks/Tk.framework/Resources/Wish\ Shell.app
+ endif
+ TKEXECUTABLE = $(shell basename "$(TKFRAMEWORK)" .app)
+endif
+
ifeq ($(findstring $(MAKEFLAGS),s),s)
QUIET_GEN =
-QUIET_BUILT_IN =
endif
+-include config.mak
+
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
TCL_PATH_SQ = $(subst ','\'',$(TCL_PATH))
TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
+TCLTK_PATH_SED = $(subst ','\'',$(subst \,\\,$(TCLTK_PATH)))
gg_libdir ?= $(sharedir)/git-gui/lib
libdir_SQ = $(subst ','\'',$(gg_libdir))
+libdir_SED = $(subst ','\'',$(subst \,\\,$(gg_libdir_sed_in)))
+exedir = $(dir $(gitexecdir))share/git-gui/lib
+
+GITGUI_SCRIPT := $$0
+GITGUI_RELATIVE :=
+GITGUI_MACOSXAPP :=
+
+ifeq ($(uname_O),Cygwin)
+ GITGUI_SCRIPT := `cygpath --windows --absolute "$(GITGUI_SCRIPT)"`
+ gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)")
+else
+ ifeq ($(exedir),$(gg_libdir))
+ GITGUI_RELATIVE := 1
+ endif
+ gg_libdir_sed_in := $(gg_libdir)
+endif
+ifeq ($(uname_S),Darwin)
+ ifeq ($(shell test -d $(TKFRAMEWORK) && echo y),y)
+ GITGUI_MACOSXAPP := YesPlease
+ endif
+endif
+ifneq (,$(findstring MINGW,$(uname_S)))
+ NO_MSGFMT=1
+ GITGUI_WINDOWS_WRAPPER := YesPlease
+endif
+
+ifdef GITGUI_MACOSXAPP
+GITGUI_MAIN := git-gui.tcl
+
+git-gui: GIT-VERSION-FILE GIT-GUI-VARS
+ $(QUIET_GEN)rm -f $@ $@+ && \
+ echo '#!$(SHELL_PATH_SQ)' >$@+ && \
+ echo 'if test "z$$*" = zversion ||' >>$@+ && \
+ echo ' test "z$$*" = z--version' >>$@+ && \
+ echo then >>$@+ && \
+ echo ' 'echo \'git-gui version '$(GITGUI_VERSION)'\' >>$@+ && \
+ echo else >>$@+ && \
+ echo ' 'exec \''$(libdir_SQ)/Git Gui.app/Contents/MacOS/$(subst \,,$(TKEXECUTABLE))'\' \
+ '"$$0" "$$@"' >>$@+ && \
+ echo fi >>$@+ && \
+ chmod +x $@+ && \
+ mv $@+ $@
+
+Git\ Gui.app: GIT-VERSION-FILE GIT-GUI-VARS \
+ macosx/Info.plist \
+ macosx/git-gui.icns \
+ macosx/AppMain.tcl \
+ $(TKFRAMEWORK)/Contents/MacOS/$(TKEXECUTABLE)
+ $(QUIET_GEN)rm -rf '$@' '$@'+ && \
+ mkdir -p '$@'+/Contents/MacOS && \
+ mkdir -p '$@'+/Contents/Resources/Scripts && \
+ cp '$(subst ','\'',$(subst \,,$(TKFRAMEWORK)/Contents/MacOS/$(TKEXECUTABLE)))' \
+ '$@'+/Contents/MacOS && \
+ cp macosx/git-gui.icns '$@'+/Contents/Resources && \
+ sed -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
+ -e 's/@@GITGUI_TKEXECUTABLE@@/$(TKEXECUTABLE)/g' \
+ macosx/Info.plist \
+ >'$@'+/Contents/Info.plist && \
+ sed -e 's|@@gitexecdir@@|$(gitexecdir_SQ)|' \
+ -e 's|@@GITGUI_LIBDIR@@|$(libdir_SED)|' \
+ macosx/AppMain.tcl \
+ >'$@'+/Contents/Resources/Scripts/AppMain.tcl && \
+ mv '$@'+ '$@'
+endif
-exedir = $(dir $(gitexecdir))share/git-gui/lib
-exedir_SQ = $(subst ','\'',$(exedir))
+ifdef GITGUI_WINDOWS_WRAPPER
+GITGUI_MAIN := git-gui.tcl
-$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
+git-gui: windows/git-gui.sh
+ cp $< $@
+endif
+
+$(GITGUI_MAIN): git-gui.sh GIT-VERSION-FILE GIT-GUI-VARS
$(QUIET_GEN)rm -f $@ $@+ && \
- GITGUI_RELATIVE= && \
- if test '$(exedir_SQ)' = '$(libdir_SQ)'; then \
- if test "$(uname_O)" = Cygwin; \
- then GITGUI_RELATIVE= ; \
- else GITGUI_RELATIVE=1; \
- fi; \
- fi && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
- -e 's|^ exec wish "$$0"| exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \
+ -e '1,30s|^ argv0=$$0| argv0=$(GITGUI_SCRIPT)|' \
+ -e '1,30s|^ exec wish | exec '\''$(TCLTK_PATH_SED)'\'' |' \
-e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
- -e 's|@@GITGUI_RELATIVE@@|'$$GITGUI_RELATIVE'|' \
- -e $$GITGUI_RELATIVE's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \
- $@.sh >$@+ && \
+ -e 's|@@GITGUI_RELATIVE@@|$(GITGUI_RELATIVE)|' \
+ -e '$(GITGUI_RELATIVE)s|@@GITGUI_LIBDIR@@|$(libdir_SED)|' \
+ git-gui.sh >$@+ && \
chmod +x $@+ && \
mv $@+ $@
-$(GITGUI_BUILT_INS): git-gui
- $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@
+XGETTEXT ?= xgettext
+ifdef NO_MSGFMT
+ MSGFMT ?= $(TCL_PATH) po/po2msg.sh
+else
+ MSGFMT ?= msgfmt
+ ifeq ($(shell $(MSGFMT) >/dev/null 2>&1 || echo $$?),127)
+ MSGFMT := $(TCL_PATH) po/po2msg.sh
+ endif
+endif
+
+msgsdir = $(gg_libdir)/msgs
+msgsdir_SQ = $(subst ','\'',$(msgsdir))
+PO_TEMPLATE = po/git-gui.pot
+ALL_POFILES = $(wildcard po/*.po)
+ALL_MSGFILES = $(subst .po,.msg,$(ALL_POFILES))
-lib/tclIndex: $(ALL_LIBFILES)
+$(PO_TEMPLATE): $(SCRIPT_SH) $(ALL_LIBFILES)
+ $(XGETTEXT) -kmc -LTcl -o $@ $(SCRIPT_SH) $(ALL_LIBFILES)
+update-po:: $(PO_TEMPLATE)
+ $(foreach p, $(ALL_POFILES), echo Updating $p ; msgmerge -U $p $(PO_TEMPLATE) ; )
+$(ALL_MSGFILES): %.msg : %.po
+ $(QUIET_MSGFMT0)$(MSGFMT) --statistics --tcl -l $(basename $(notdir $<)) -d $(dir $@) $< $(QUIET_MSGFMT1)
+
+lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS
$(QUIET_INDEX)if echo \
$(foreach p,$(PRELOAD_FILES),source $p\;) \
auto_mkindex lib '*.tcl' \
@@ -132,16 +239,13 @@ lib/tclIndex: $(ALL_LIBFILES)
echo >>$@ ; \
fi
-# These can record GITGUI_VERSION
-$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS
-lib/tclIndex: GIT-GUI-VARS
-
TRACK_VARS = \
$(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \
$(subst ','\'',TCL_PATH='$(TCL_PATH_SQ)') \
$(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \
$(subst ','\'',gitexecdir='$(gitexecdir_SQ)') \
$(subst ','\'',gg_libdir='$(libdir_SQ)') \
+ GITGUI_MACOSXAPP=$(GITGUI_MACOSXAPP) \
#end TRACK_VARS
GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
@@ -151,24 +255,49 @@ GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
echo 1>$@ "$$VARS"; \
fi
-all:: $(ALL_PROGRAMS) lib/tclIndex
+ifdef GITGUI_MACOSXAPP
+all:: git-gui Git\ Gui.app
+endif
+ifdef GITGUI_WINDOWS_WRAPPER
+all:: git-gui
+endif
+all:: $(GITGUI_MAIN) lib/tclIndex $(ALL_MSGFILES)
install: all
$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
$(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
+ifdef GITGUI_WINDOWS_WRAPPER
+ $(QUIET)$(INSTALL_R0)git-gui.tcl $(INSTALL_R1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+endif
$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(INSTALL_D1)
$(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)'
- $(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true
+ifdef GITGUI_MACOSXAPP
+ $(QUIET)$(INSTALL_A0)'Git Gui.app' $(INSTALL_A1) '$(DESTDIR_SQ)$(libdir_SQ)'
+ $(QUIET)$(INSTALL_X0)git-gui.tcl $(INSTALL_X1) '$(DESTDIR_SQ)$(libdir_SQ)'
+endif
+ $(QUIET)$(foreach p,$(ALL_LIBFILES) $(NONTCL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true
+ $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(msgsdir_SQ)' $(INSTALL_D1)
+ $(QUIET)$(foreach p,$(ALL_MSGFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(msgsdir_SQ)' &&) true
uninstall:
$(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui $(REMOVE_F1)
$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/$p $(REMOVE_F1) &&) true
+ifdef GITGUI_WINDOWS_WRAPPER
+ $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui.tcl $(REMOVE_F1)
+endif
$(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(libdir_SQ)'
$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/tclIndex $(REMOVE_F1)
- $(QUIET)$(foreach p,$(ALL_LIBFILES), $(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/$(notdir $p) $(REMOVE_F1) &&) true
+ifdef GITGUI_MACOSXAPP
+ $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)/Git Gui.app' $(REMOVE_F1)
+ $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/git-gui.tcl $(REMOVE_F1)
+endif
+ $(QUIET)$(foreach p,$(ALL_LIBFILES) $(NONTCL_LIBFILES), $(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/$(notdir $p) $(REMOVE_F1) &&) true
+ $(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(msgsdir_SQ)'
+ $(QUIET)$(foreach p,$(ALL_MSGFILES), $(REMOVE_F0)'$(DESTDIR_SQ)$(msgsdir_SQ)'/$(notdir $p) $(REMOVE_F1) &&) true
$(QUIET)$(REMOVE_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(REMOVE_D1)
+ $(QUIET)$(REMOVE_D0)'$(DESTDIR_SQ)$(msgsdir_SQ)' $(REMOVE_D1)
$(QUIET)$(REMOVE_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(REMOVE_D1)
$(QUIET)$(REMOVE_D0)`dirname '$(DESTDIR_SQ)$(libdir_SQ)'` $(REMOVE_D1)
@@ -177,8 +306,14 @@ dist-version:
@echo $(GITGUI_VERSION) > $(TARDIR)/version
clean::
- rm -f $(ALL_PROGRAMS) lib/tclIndex
- rm -f GIT-VERSION-FILE GIT-GUI-VARS
+ $(RM_RF) $(GITGUI_MAIN) lib/tclIndex po/*.msg
+ $(RM_RF) GIT-VERSION-FILE GIT-GUI-VARS
+ifdef GITGUI_MACOSXAPP
+ $(RM_RF) 'Git Gui.app'* git-gui
+endif
+ifdef GITGUI_WINDOWS_WRAPPER
+ $(RM_RF) git-gui
+endif
.PHONY: all install uninstall dist-version clean
.PHONY: .FORCE-GIT-VERSION-FILE
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index f789e91b66..5d65272e26 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -6,11 +6,12 @@
echo 'git-gui version @@GITGUI_VERSION@@'; \
exit; \
fi; \
- exec wish "$0" -- "$@"
+ argv0=$0; \
+ exec wish "$argv0" -- "$@"
set appvers {@@GITGUI_VERSION@@}
-set copyright {
-Copyright © 2006, 2007 Shawn Pearce, et. al.
+set copyright [encoding convertfrom utf-8 {
+Copyright © 2006, 2007 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
@@ -24,7 +25,7 @@ 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}
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA}]
######################################################################
##
@@ -37,7 +38,7 @@ if {[catch {package require Tcl 8.4} err]
tk_messageBox \
-icon error \
-type ok \
- -title "git-gui: fatal error" \
+ -title [mc "git-gui: fatal error"] \
-message $err
exit 1
}
@@ -46,6 +47,24 @@ catch {rename send {}} ; # What an evil concept...
######################################################################
##
+## locate our library
+
+set oguilib {@@GITGUI_LIBDIR@@}
+set oguirel {@@GITGUI_RELATIVE@@}
+if {$oguirel eq {1}} {
+ set oguilib [file dirname [file dirname [file normalize $argv0]]]
+ set oguilib [file join $oguilib share git-gui lib]
+ set oguimsg [file join $oguilib msgs]
+} elseif {[string match @@* $oguirel]} {
+ set oguilib [file join [file dirname [file normalize $argv0]] lib]
+ set oguimsg [file join [file dirname [file normalize $argv0]] po]
+} else {
+ set oguimsg [file join $oguilib msgs]
+}
+unset oguirel
+
+######################################################################
+##
## enable verbose loading?
if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
@@ -64,21 +83,39 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
######################################################################
##
-## Fake internationalization to ease backporting of changes.
+## Internationalization (i18n) through msgcat and gettext. See
+## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
-proc mc {fmt args} {
+package require msgcat
+
+proc _mc_trim {fmt} {
set cmk [string first @@ $fmt]
if {$cmk > 0} {
- set fmt [string range $fmt 0 [expr {$cmk - 1}]]
+ return [string range $fmt 0 [expr {$cmk - 1}]]
+ }
+ return $fmt
+}
+
+proc mc {en_fmt args} {
+ set fmt [_mc_trim [::msgcat::mc $en_fmt]]
+ if {[catch {set msg [eval [list format $fmt] $args]} err]} {
+ set msg [eval [list format [_mc_trim $en_fmt]] $args]
}
- return [eval [list format $fmt] $args]
+ return $msg
+}
+
+proc strcat {args} {
+ return [join $args {}]
}
+::msgcat::mcload $oguimsg
+unset oguimsg
+
######################################################################
##
## read only globals
-set _appname [lindex [file split $argv0] end]
+set _appname {Git Gui}
set _gitdir {}
set _gitexec {}
set _reponame {}
@@ -175,6 +212,7 @@ proc disable_option {option} {
proc is_many_config {name} {
switch -glob -- $name {
+ gui.recentrepo -
remote.*.fetch -
remote.*.push
{return 1}
@@ -203,51 +241,6 @@ proc get_config {name} {
}
}
-proc load_config {include_global} {
- global repo_config global_config default_config
-
- array unset global_config
- if {$include_global} {
- catch {
- set fd_rc [git_read config --global --list]
- while {[gets $fd_rc line] >= 0} {
- if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
- if {[is_many_config $name]} {
- lappend global_config($name) $value
- } else {
- set global_config($name) $value
- }
- }
- }
- close $fd_rc
- }
- }
-
- array unset repo_config
- catch {
- set fd_rc [git_read config --list]
- while {[gets $fd_rc line] >= 0} {
- if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
- if {[is_many_config $name]} {
- lappend repo_config($name) $value
- } else {
- set repo_config($name) $value
- }
- }
- }
- close $fd_rc
- }
-
- foreach name [array names default_config] {
- if {[catch {set v $global_config($name)}]} {
- set global_config($name) $default_config($name)
- }
- if {[catch {set v $repo_config($name)}]} {
- set repo_config($name) $default_config($name)
- }
- }
-}
-
######################################################################
##
## handy utils
@@ -305,7 +298,7 @@ proc _which {what} {
global env _search_exe _search_path
if {$_search_path eq {}} {
- if {[is_Cygwin]} {
+ if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
set _search_path [split [exec cygpath \
--windows \
--path \
@@ -313,6 +306,9 @@ proc _which {what} {
$env(PATH)] {;}]
set _search_exe .exe
} elseif {[is_Windows]} {
+ set gitguidir [file dirname [info script]]
+ regsub -all ";" $gitguidir "\\;" gitguidir
+ set env(PATH) "$gitguidir;$env(PATH)"
set _search_path [split $env(PATH) {;}]
set _search_exe .exe
} else {
@@ -442,6 +438,34 @@ proc git_write {args} {
return [open [concat $opt $cmdp $args] w]
}
+proc githook_read {hook_name args} {
+ set pchook [gitdir hooks $hook_name]
+ lappend args 2>@1
+
+ # On Cygwin [file executable] might lie so we need to ask
+ # the shell if the hook is executable. Yes that's annoying.
+ #
+ if {[is_Cygwin]} {
+ upvar #0 _sh interp
+ if {![info exists interp]} {
+ set interp [_which sh]
+ }
+ if {$interp eq {}} {
+ error "hook execution requires sh (not in PATH)"
+ }
+
+ set scr {if test -x "$1";then exec "$@";fi}
+ set sh_c [list | $interp -c $scr $interp $pchook]
+ return [_open_stdout_stderr [concat $sh_c $args]]
+ }
+
+ if {[file executable $pchook]} {
+ return [_open_stdout_stderr [concat [list | $pchook] $args]]
+ }
+
+ return {}
+}
+
proc sq {value} {
regsub -all ' $value "'\\''" value
return "'$value'"
@@ -491,6 +515,111 @@ proc rmsel_tag {text} {
return $text
}
+set root_exists 0
+bind . <Visibility> {
+ bind . <Visibility> {}
+ set root_exists 1
+}
+
+if {[is_Windows]} {
+ wm iconbitmap . -default $oguilib/git-gui.ico
+}
+
+######################################################################
+##
+## config defaults
+
+set cursor_ptr arrow
+font create font_diff -family Courier -size 10
+font create font_ui
+catch {
+ label .dummy
+ eval font configure font_ui [font actual [.dummy cget -font]]
+ destroy .dummy
+}
+
+font create font_uiitalic
+font create font_uibold
+font create font_diffbold
+font create font_diffitalic
+
+foreach class {Button Checkbutton Entry Label
+ Labelframe Listbox Menu Message
+ Radiobutton Spinbox Text} {
+ option add *$class.font font_ui
+}
+unset class
+
+if {[is_Windows] || [is_MacOSX]} {
+ option add *Menu.tearOff 0
+}
+
+if {[is_MacOSX]} {
+ set M1B M1
+ set M1T Cmd
+} else {
+ set M1B Control
+ set M1T Ctrl
+}
+
+proc bind_button3 {w cmd} {
+ bind $w <Any-Button-3> $cmd
+ if {[is_MacOSX]} {
+ # Mac OS X sends Button-2 on right click through three-button mouse,
+ # or through trackpad right-clicking (two-finger touch + click).
+ bind $w <Any-Button-2> $cmd
+ bind $w <Control-Button-1> $cmd
+ }
+}
+
+proc apply_config {} {
+ global repo_config font_descs
+
+ foreach option $font_descs {
+ set name [lindex $option 0]
+ set font [lindex $option 1]
+ if {[catch {
+ set need_weight 1
+ foreach {cn cv} $repo_config(gui.$name) {
+ if {$cn eq {-weight}} {
+ set need_weight 0
+ }
+ font configure $font $cn $cv
+ }
+ if {$need_weight} {
+ font configure $font -weight normal
+ }
+ } err]} {
+ error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
+ }
+ foreach {cn cv} [font configure $font] {
+ font configure ${font}bold $cn $cv
+ font configure ${font}italic $cn $cv
+ }
+ font configure ${font}bold -weight bold
+ font configure ${font}italic -slant italic
+ }
+}
+
+set default_config(merge.diffstat) true
+set default_config(merge.summary) false
+set default_config(merge.verbosity) 2
+set default_config(user.name) {}
+set default_config(user.email) {}
+
+set default_config(gui.matchtrackingbranch) false
+set default_config(gui.pruneduringfetch) false
+set default_config(gui.trustmtime) false
+set default_config(gui.diffcontext) 5
+set default_config(gui.newbranchtemplate) {}
+set default_config(gui.spellingdictionary) {}
+set default_config(gui.fontui) [font configure font_ui]
+set default_config(gui.fontdiff) [font configure font_diff]
+set font_descs {
+ {fontui font_ui {mc "Main Font"}}
+ {fontdiff font_diff {mc "Diff/Console Font"}}
+}
+
######################################################################
##
## find git
@@ -498,7 +627,11 @@ proc rmsel_tag {text} {
set _git [_which git]
if {$_git eq {}} {
catch {wm withdraw .}
- error_popup "Cannot find git in PATH."
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [mc "git-gui: fatal error"] \
+ -message [mc "Cannot find git in PATH."]
exit 1
}
@@ -511,7 +644,7 @@ if {[catch {set _git_version [git --version]} err]} {
tk_messageBox \
-icon error \
-type ok \
- -title "git-gui: fatal error" \
+ -title [mc "git-gui: fatal error"] \
-message "Cannot determine Git version:
$err
@@ -524,8 +657,8 @@ if {![regsub {^git version } $_git_version {} _git_version]} {
tk_messageBox \
-icon error \
-type ok \
- -title "git-gui: fatal error" \
- -message "Cannot parse Git version string:\n\n$_git_version"
+ -title [mc "git-gui: fatal error"] \
+ -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
exit 1
}
@@ -534,6 +667,7 @@ regsub -- {-dirty$} $_git_version {} _git_version
regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
regsub {\.rc[0-9]+$} $_git_version {} _git_version
regsub {\.GIT$} $_git_version {} _git_version
+regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version
if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
catch {wm withdraw .}
@@ -542,14 +676,14 @@ if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
-type yesno \
-default no \
-title "[appname]: warning" \
- -message "Git version cannot be determined.
+ -message [mc "Git version cannot be determined.
-$_git claims it is version '$_real_git_version'.
+%s claims it is version '%s'.
-[appname] requires at least Git 1.5.0 or later.
+%s requires at least Git 1.5.0 or later.
-Assume '$_real_git_version' is version 1.5.0?
-"] eq {yes}} {
+Assume '%s' is version 1.5.0?
+" $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
set _git_version 1.5.0
} else {
exit 1
@@ -606,7 +740,7 @@ if {[git-version < 1.5]} {
tk_messageBox \
-icon error \
-type ok \
- -title "git-gui: fatal error" \
+ -title [mc "git-gui: fatal error"] \
-message "[appname] requires Git 1.5.0 or later.
You are using [git-version]:
@@ -619,22 +753,13 @@ You are using [git-version]:
##
## configure our library
-set oguilib {@@GITGUI_LIBDIR@@}
-set oguirel {@@GITGUI_RELATIVE@@}
-if {$oguirel eq {1}} {
- set oguilib [file dirname [file dirname [file normalize $argv0]]]
- set oguilib [file join $oguilib share git-gui lib]
-} elseif {[string match @@* $oguirel]} {
- set oguilib [file join [file dirname [file normalize $argv0]] lib]
-}
-
set idx [file join $oguilib tclIndex]
if {[catch {set fd [open $idx r]} err]} {
catch {wm withdraw .}
tk_messageBox \
-icon error \
-type ok \
- -title "git-gui: fatal error" \
+ -title [mc "git-gui: fatal error"] \
-message $err
exit 1
}
@@ -661,13 +786,78 @@ if {$idx ne {}} {
} else {
set auto_path [concat [list $oguilib] $auto_path]
}
-unset -nocomplain oguirel idx fd
+unset -nocomplain idx fd
+
+######################################################################
+##
+## config file parsing
+
+git-version proc _parse_config {arr_name args} {
+ >= 1.5.3 {
+ upvar $arr_name arr
+ array unset arr
+ set buf {}
+ catch {
+ set fd_rc [eval \
+ [list git_read config] \
+ $args \
+ [list --null --list]]
+ fconfigure $fd_rc -translation binary
+ set buf [read $fd_rc]
+ close $fd_rc
+ }
+ foreach line [split $buf "\0"] {
+ if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
+ if {[is_many_config $name]} {
+ lappend arr($name) $value
+ } else {
+ set arr($name) $value
+ }
+ }
+ }
+ }
+ default {
+ upvar $arr_name arr
+ array unset arr
+ catch {
+ set fd_rc [eval [list git_read config --list] $args]
+ while {[gets $fd_rc line] >= 0} {
+ if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
+ if {[is_many_config $name]} {
+ lappend arr($name) $value
+ } else {
+ set arr($name) $value
+ }
+ }
+ }
+ close $fd_rc
+ }
+ }
+}
+
+proc load_config {include_global} {
+ global repo_config global_config default_config
+
+ if {$include_global} {
+ _parse_config global_config --global
+ }
+ _parse_config repo_config
+
+ foreach name [array names default_config] {
+ if {[catch {set v $global_config($name)}]} {
+ set global_config($name) $default_config($name)
+ }
+ if {[catch {set v $repo_config($name)}]} {
+ set repo_config($name) $default_config($name)
+ }
+ }
+}
######################################################################
##
## feature option selection
-if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
+if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
unset _junk
} else {
set subcommand gui
@@ -715,35 +905,35 @@ if {[catch {
set _gitdir [git rev-parse --git-dir]
set _prefix [git rev-parse --show-prefix]
} err]} {
- catch {wm withdraw .}
- error_popup "Cannot find the git directory:\n\n$err"
- exit 1
+ load_config 1
+ apply_config
+ choose_repository::pick
}
if {![file isdirectory $_gitdir] && [is_Cygwin]} {
- catch {set _gitdir [exec cygpath --unix $_gitdir]}
+ catch {set _gitdir [exec cygpath --windows $_gitdir]}
}
if {![file isdirectory $_gitdir]} {
catch {wm withdraw .}
- error_popup "Git directory not found:\n\n$_gitdir"
+ error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
exit 1
}
if {$_prefix ne {}} {
regsub -all {[^/]+/} $_prefix ../ cdup
if {[catch {cd $cdup} err]} {
catch {wm withdraw .}
- error_popup "Cannot move to top of working directory:\n\n$err"
+ error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
exit 1
}
unset cdup
} elseif {![is_enabled bare]} {
if {[lindex [file split $_gitdir] end] ne {.git}} {
catch {wm withdraw .}
- error_popup "Cannot use funny .git directory:\n\n$_gitdir"
+ error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
exit 1
}
if {[catch {cd [file dirname $_gitdir]} err]} {
catch {wm withdraw .}
- error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
+ error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
exit 1
}
}
@@ -890,7 +1080,7 @@ proc rescan {after {honor_trustmtime 1}} {
rescan_stage2 {} $after
} else {
set rescan_active 1
- ui_status {Refreshing file status...}
+ ui_status [mc "Refreshing file status..."]
set fd_rf [git_read update-index \
-q \
--unmerged \
@@ -903,6 +1093,35 @@ proc rescan {after {honor_trustmtime 1}} {
}
}
+if {[is_Cygwin]} {
+ set is_git_info_link {}
+ set is_git_info_exclude {}
+ proc have_info_exclude {} {
+ global is_git_info_link is_git_info_exclude
+
+ if {$is_git_info_link eq {}} {
+ set is_git_info_link [file isfile [gitdir info.lnk]]
+ }
+
+ if {$is_git_info_link} {
+ if {$is_git_info_exclude eq {}} {
+ if {[catch {exec test -f [gitdir info exclude]}]} {
+ set is_git_info_exclude 0
+ } else {
+ set is_git_info_exclude 1
+ }
+ }
+ return $is_git_info_exclude
+ } else {
+ return [file readable [gitdir info exclude]]
+ }
+ }
+} else {
+ proc have_info_exclude {} {
+ return [file readable [gitdir info exclude]]
+ }
+}
+
proc rescan_stage2 {fd after} {
global rescan_active buf_rdi buf_rdf buf_rlo
@@ -913,9 +1132,8 @@ proc rescan_stage2 {fd after} {
}
set ls_others [list --exclude-per-directory=.gitignore]
- set info_exclude [gitdir info exclude]
- if {[file readable $info_exclude]} {
- lappend ls_others "--exclude-from=$info_exclude"
+ 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]} {
@@ -927,7 +1145,7 @@ proc rescan_stage2 {fd after} {
set buf_rlo {}
set rescan_active 3
- ui_status {Scanning for modified files ...}
+ ui_status [mc "Scanning for modified files ..."]
set fd_di [git_read diff-index --cached -z [PARENT]]
set fd_df [git_read diff-files -z]
set fd_lo [eval git_read ls-files --others -z $ls_others]
@@ -1093,11 +1311,17 @@ proc mapdesc {state path} {
}
proc ui_status {msg} {
- $::main_status show $msg
+ global main_status
+ if {[info exists main_status]} {
+ $main_status show $msg
+ }
}
proc ui_ready {{test {}}} {
- $::main_status show {Ready.} $test
+ global main_status
+ if {[info exists main_status]} {
+ $main_status show [mc "Ready."] $test
+ }
}
proc escape_path {path} {
@@ -1362,31 +1586,32 @@ set all_icons(O$ui_workdir) file_plain
set max_status_desc 0
foreach i {
- {__ "Unmodified"}
-
- {_M "Modified, not staged"}
- {M_ "Staged for commit"}
- {MM "Portions staged for commit"}
- {MD "Staged for commit, missing"}
-
- {_O "Untracked, not staged"}
- {A_ "Staged for commit"}
- {AM "Portions staged for commit"}
- {AD "Staged for commit, missing"}
-
- {_D "Missing"}
- {D_ "Staged for removal"}
- {DO "Staged for removal, still present"}
-
- {U_ "Requires merge resolution"}
- {UU "Requires merge resolution"}
- {UM "Requires merge resolution"}
- {UD "Requires merge resolution"}
+ {__ {mc "Unmodified"}}
+
+ {_M {mc "Modified, not staged"}}
+ {M_ {mc "Staged for commit"}}
+ {MM {mc "Portions staged for commit"}}
+ {MD {mc "Staged for commit, missing"}}
+
+ {_O {mc "Untracked, not staged"}}
+ {A_ {mc "Staged for commit"}}
+ {AM {mc "Portions staged for commit"}}
+ {AD {mc "Staged for commit, missing"}}
+
+ {_D {mc "Missing"}}
+ {D_ {mc "Staged for removal"}}
+ {DO {mc "Staged for removal, still present"}}
+
+ {U_ {mc "Requires merge resolution"}}
+ {UU {mc "Requires merge resolution"}}
+ {UM {mc "Requires merge resolution"}}
+ {UD {mc "Requires merge resolution"}}
} {
- if {$max_status_desc < [string length [lindex $i 1]]} {
- set max_status_desc [string length [lindex $i 1]]
+ set text [eval [lindex $i 1]]
+ if {$max_status_desc < [string length $text]} {
+ set max_status_desc [string length $text]
}
- set all_descs([lindex $i 0]) [lindex $i 1]
+ set all_descs([lindex $i 0]) $text
}
unset i
@@ -1394,16 +1619,6 @@ unset i
##
## util
-proc bind_button3 {w cmd} {
- bind $w <Any-Button-3> $cmd
- if {[is_MacOSX]} {
- # Mac OS X sends Button-2 on right click through three-button mouse,
- # or through trackpad right-clicking (two-finger touch + click).
- bind $w <Any-Button-2> $cmd
- bind $w <Control-Button-1> $cmd
- }
-}
-
proc scrollbar2many {list mode args} {
foreach w $list {eval $w $mode $args}
}
@@ -1425,7 +1640,7 @@ proc incr_font_size {font {amt 1}} {
##
## ui commands
-set starting_gitk_msg {Starting gitk... please wait...}
+set starting_gitk_msg [mc "Starting gitk... please wait..."]
proc do_gitk {revs} {
# -- Always start gitk through whatever we were loaded with. This
@@ -1434,9 +1649,29 @@ proc do_gitk {revs} {
set exe [file join [file dirname $::_git] gitk]
set cmd [list [info nameofexecutable] $exe]
if {! [file exists $exe]} {
- error_popup "Unable to start gitk:\n\n$exe does not exist"
+ error_popup [mc "Unable to start gitk:\n\n%s does not exist" $exe]
} else {
+ global env
+
+ if {[info exists env(GIT_DIR)]} {
+ set old_GIT_DIR $env(GIT_DIR)
+ } else {
+ set old_GIT_DIR {}
+ }
+
+ set pwd [pwd]
+ cd [file dirname [gitdir]]
+ set env(GIT_DIR) [file tail [gitdir]]
+
eval exec $cmd $revs &
+
+ if {$old_GIT_DIR eq {}} {
+ unset env(GIT_DIR)
+ } else {
+ set env(GIT_DIR) $old_GIT_DIR
+ }
+ cd $pwd
+
ui_status $::starting_gitk_msg
after 10000 {
ui_ready $starting_gitk_msg
@@ -1449,6 +1684,7 @@ set is_quitting 0
proc do_quit {} {
global ui_comm is_quitting repo_config commit_type
global GITGUI_BCK_exists GITGUI_BCK_i
+ global ui_comm_spell
if {$is_quitting} return
set is_quitting 1
@@ -1476,6 +1712,12 @@ proc do_quit {} {
}
}
+ # -- Cancel our spellchecker if its running.
+ #
+ if {[info exists ui_comm_spell]} {
+ $ui_comm_spell stop
+ }
+
# -- Remove our editor backup, its not needed.
#
after cancel $GITGUI_BCK_i
@@ -1487,8 +1729,8 @@ proc do_quit {} {
#
set cfg_geometry [list]
lappend cfg_geometry [wm geometry .]
- lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
- lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
+ lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
+ lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
set rc_geometry {}
}
@@ -1605,104 +1847,26 @@ proc add_range_to_selection {w x y} {
######################################################################
##
-## config defaults
-
-set cursor_ptr arrow
-font create font_diff -family Courier -size 10
-font create font_ui
-catch {
- label .dummy
- eval font configure font_ui [font actual [.dummy cget -font]]
- destroy .dummy
-}
-
-font create font_uiitalic
-font create font_uibold
-font create font_diffbold
-font create font_diffitalic
-
-foreach class {Button Checkbutton Entry Label
- Labelframe Listbox Menu Message
- Radiobutton Spinbox Text} {
- option add *$class.font font_ui
-}
-unset class
-
-if {[is_Windows] || [is_MacOSX]} {
- option add *Menu.tearOff 0
-}
-
-if {[is_MacOSX]} {
- set M1B M1
- set M1T Cmd
-} else {
- set M1B Control
- set M1T Ctrl
-}
-
-proc apply_config {} {
- global repo_config font_descs
-
- foreach option $font_descs {
- set name [lindex $option 0]
- set font [lindex $option 1]
- if {[catch {
- foreach {cn cv} $repo_config(gui.$name) {
- font configure $font $cn $cv
- }
- } err]} {
- error_popup "Invalid font specified in gui.$name:\n\n$err"
- }
- foreach {cn cv} [font configure $font] {
- font configure ${font}bold $cn $cv
- font configure ${font}italic $cn $cv
- }
- font configure ${font}bold -weight bold
- font configure ${font}italic -slant italic
- }
-}
-
-set default_config(merge.diffstat) true
-set default_config(merge.summary) false
-set default_config(merge.verbosity) 2
-set default_config(user.name) {}
-set default_config(user.email) {}
+## ui construction
-set default_config(gui.matchtrackingbranch) false
-set default_config(gui.pruneduringfetch) false
-set default_config(gui.trustmtime) false
-set default_config(gui.diffcontext) 5
-set default_config(gui.newbranchtemplate) {}
-set default_config(gui.fontui) [font configure font_ui]
-set default_config(gui.fontdiff) [font configure font_diff]
-set font_descs {
- {fontui font_ui {Main Font}}
- {fontdiff font_diff {Diff/Console Font}}
-}
load_config 0
apply_config
-
-######################################################################
-##
-## ui construction
-
set ui_comm {}
# -- Menu Bar
#
menu .mbar -tearoff 0
-.mbar add cascade -label Repository -menu .mbar.repository
-.mbar add cascade -label Edit -menu .mbar.edit
+.mbar add cascade -label [mc Repository] -menu .mbar.repository
+.mbar add cascade -label [mc Edit] -menu .mbar.edit
if {[is_enabled branch]} {
- .mbar add cascade -label Branch -menu .mbar.branch
+ .mbar add cascade -label [mc Branch] -menu .mbar.branch
}
if {[is_enabled multicommit] || [is_enabled singlecommit]} {
- .mbar add cascade -label Commit -menu .mbar.commit
+ .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
}
if {[is_enabled transport]} {
- .mbar add cascade -label Merge -menu .mbar.merge
- .mbar add cascade -label Fetch -menu .mbar.fetch
- .mbar add cascade -label Push -menu .mbar.push
+ .mbar add cascade -label [mc Merge] -menu .mbar.merge
+ .mbar add cascade -label [mc Remote] -menu .mbar.remote
}
. configure -menu .mbar
@@ -1711,87 +1875,87 @@ if {[is_enabled transport]} {
menu .mbar.repository
.mbar.repository add command \
- -label {Browse Current Branch's Files} \
+ -label [mc "Browse Current Branch's Files"] \
-command {browser::new $current_branch}
set ui_browse_current [.mbar.repository index last]
.mbar.repository add command \
- -label {Browse Branch Files...} \
+ -label [mc "Browse Branch Files..."] \
-command browser_open::dialog
.mbar.repository add separator
.mbar.repository add command \
- -label {Visualize Current Branch's History} \
+ -label [mc "Visualize Current Branch's History"] \
-command {do_gitk $current_branch}
set ui_visualize_current [.mbar.repository index last]
.mbar.repository add command \
- -label {Visualize All Branch History} \
+ -label [mc "Visualize All Branch History"] \
-command {do_gitk --all}
.mbar.repository add separator
proc current_branch_write {args} {
global current_branch
.mbar.repository entryconf $::ui_browse_current \
- -label "Browse $current_branch's Files"
+ -label [mc "Browse %s's Files" $current_branch]
.mbar.repository entryconf $::ui_visualize_current \
- -label "Visualize $current_branch's History"
+ -label [mc "Visualize %s's History" $current_branch]
}
trace add variable current_branch write current_branch_write
if {[is_enabled multicommit]} {
- .mbar.repository add command -label {Database Statistics} \
+ .mbar.repository add command -label [mc "Database Statistics"] \
-command do_stats
- .mbar.repository add command -label {Compress Database} \
+ .mbar.repository add command -label [mc "Compress Database"] \
-command do_gc
- .mbar.repository add command -label {Verify Database} \
+ .mbar.repository add command -label [mc "Verify Database"] \
-command do_fsck_objects
.mbar.repository add separator
if {[is_Cygwin]} {
.mbar.repository add command \
- -label {Create Desktop Icon} \
+ -label [mc "Create Desktop Icon"] \
-command do_cygwin_shortcut
} elseif {[is_Windows]} {
.mbar.repository add command \
- -label {Create Desktop Icon} \
+ -label [mc "Create Desktop Icon"] \
-command do_windows_shortcut
} elseif {[is_MacOSX]} {
.mbar.repository add command \
- -label {Create Desktop Icon} \
+ -label [mc "Create Desktop Icon"] \
-command do_macosx_app
}
}
-.mbar.repository add command -label Quit \
+.mbar.repository add command -label [mc Quit] \
-command do_quit \
-accelerator $M1T-Q
# -- Edit Menu
#
menu .mbar.edit
-.mbar.edit add command -label Undo \
+.mbar.edit add command -label [mc Undo] \
-command {catch {[focus] edit undo}} \
-accelerator $M1T-Z
-.mbar.edit add command -label Redo \
+.mbar.edit add command -label [mc Redo] \
-command {catch {[focus] edit redo}} \
-accelerator $M1T-Y
.mbar.edit add separator
-.mbar.edit add command -label Cut \
+.mbar.edit add command -label [mc Cut] \
-command {catch {tk_textCut [focus]}} \
-accelerator $M1T-X
-.mbar.edit add command -label Copy \
+.mbar.edit add command -label [mc Copy] \
-command {catch {tk_textCopy [focus]}} \
-accelerator $M1T-C
-.mbar.edit add command -label Paste \
+.mbar.edit add command -label [mc Paste] \
-command {catch {tk_textPaste [focus]; [focus] see insert}} \
-accelerator $M1T-V
-.mbar.edit add command -label Delete \
+.mbar.edit add command -label [mc Delete] \
-command {catch {[focus] delete sel.first sel.last}} \
-accelerator Del
.mbar.edit add separator
-.mbar.edit add command -label {Select All} \
+.mbar.edit add command -label [mc "Select All"] \
-command {catch {[focus] tag add sel 0.0 end}} \
-accelerator $M1T-A
@@ -1800,29 +1964,29 @@ menu .mbar.edit
if {[is_enabled branch]} {
menu .mbar.branch
- .mbar.branch add command -label {Create...} \
+ .mbar.branch add command -label [mc "Create..."] \
-command branch_create::dialog \
-accelerator $M1T-N
lappend disable_on_lock [list .mbar.branch entryconf \
[.mbar.branch index last] -state]
- .mbar.branch add command -label {Checkout...} \
+ .mbar.branch add command -label [mc "Checkout..."] \
-command branch_checkout::dialog \
-accelerator $M1T-O
lappend disable_on_lock [list .mbar.branch entryconf \
[.mbar.branch index last] -state]
- .mbar.branch add command -label {Rename...} \
+ .mbar.branch add command -label [mc "Rename..."] \
-command branch_rename::dialog
lappend disable_on_lock [list .mbar.branch entryconf \
[.mbar.branch index last] -state]
- .mbar.branch add command -label {Delete...} \
+ .mbar.branch add command -label [mc "Delete..."] \
-command branch_delete::dialog
lappend disable_on_lock [list .mbar.branch entryconf \
[.mbar.branch index last] -state]
- .mbar.branch add command -label {Reset...} \
+ .mbar.branch add command -label [mc "Reset..."] \
-command merge::reset_hard
lappend disable_on_lock [list .mbar.branch entryconf \
[.mbar.branch index last] -state]
@@ -1834,7 +1998,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
menu .mbar.commit
.mbar.commit add radiobutton \
- -label {New Commit} \
+ -label [mc "New Commit"] \
-command do_select_commit_type \
-variable selected_commit_type \
-value new
@@ -1842,7 +2006,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
[list .mbar.commit entryconf [.mbar.commit index last] -state]
.mbar.commit add radiobutton \
- -label {Amend Last Commit} \
+ -label [mc "Amend Last Commit"] \
-command do_select_commit_type \
-variable selected_commit_type \
-value amend
@@ -1851,40 +2015,41 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
.mbar.commit add separator
- .mbar.commit add command -label Rescan \
+ .mbar.commit add command -label [mc Rescan] \
-command do_rescan \
-accelerator F5
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
- .mbar.commit add command -label {Stage To Commit} \
- -command do_add_selection
+ .mbar.commit add command -label [mc "Stage To Commit"] \
+ -command do_add_selection \
+ -accelerator $M1T-T
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
- .mbar.commit add command -label {Stage Changed Files To Commit} \
+ .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
-command do_add_all \
-accelerator $M1T-I
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
- .mbar.commit add command -label {Unstage From Commit} \
+ .mbar.commit add command -label [mc "Unstage From Commit"] \
-command do_unstage_selection
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
- .mbar.commit add command -label {Revert Changes} \
+ .mbar.commit add command -label [mc "Revert Changes"] \
-command do_revert_selection
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
.mbar.commit add separator
- .mbar.commit add command -label {Sign Off} \
+ .mbar.commit add command -label [mc "Sign Off"] \
-command do_signoff \
-accelerator $M1T-S
- .mbar.commit add command -label Commit \
+ .mbar.commit add command -label [mc Commit@@verb] \
-command do_commit \
-accelerator $M1T-Return
lappend disable_on_lock \
@@ -1895,12 +2060,12 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
#
if {[is_enabled branch]} {
menu .mbar.merge
- .mbar.merge add command -label {Local Merge...} \
+ .mbar.merge add command -label [mc "Local Merge..."] \
-command merge::dialog \
-accelerator $M1T-M
lappend disable_on_lock \
[list .mbar.merge entryconf [.mbar.merge index last] -state]
- .mbar.merge add command -label {Abort Merge...} \
+ .mbar.merge add command -label [mc "Abort Merge..."] \
-command merge::reset_hard
lappend disable_on_lock \
[list .mbar.merge entryconf [.mbar.merge index last] -state]
@@ -1909,41 +2074,46 @@ if {[is_enabled branch]} {
# -- Transport Menu
#
if {[is_enabled transport]} {
- menu .mbar.fetch
+ menu .mbar.remote
- menu .mbar.push
- .mbar.push add command -label {Push...} \
+ .mbar.remote add command \
+ -label [mc "Push..."] \
-command do_push_anywhere \
-accelerator $M1T-P
- .mbar.push add command -label {Delete...} \
+ .mbar.remote add command \
+ -label [mc "Delete..."] \
-command remote_branch_delete::dialog
}
if {[is_MacOSX]} {
# -- Apple Menu (Mac OS X only)
#
- .mbar add cascade -label Apple -menu .mbar.apple
+ .mbar add cascade -label [mc Apple] -menu .mbar.apple
menu .mbar.apple
- .mbar.apple add command -label "About [appname]" \
+ .mbar.apple add command -label [mc "About %s" [appname]] \
-command do_about
- .mbar.apple add command -label "Options..." \
- -command do_options
+ .mbar.apple add separator
+ .mbar.apple add command \
+ -label [mc "Preferences..."] \
+ -command do_options \
+ -accelerator $M1T-,
+ bind . <$M1B-,> do_options
} else {
# -- Edit Menu
#
.mbar.edit add separator
- .mbar.edit add command -label {Options...} \
+ .mbar.edit add command -label [mc "Options..."] \
-command do_options
}
# -- Help Menu
#
-.mbar add cascade -label Help -menu .mbar.help
+.mbar add cascade -label [mc Help] -menu .mbar.help
menu .mbar.help
if {![is_MacOSX]} {
- .mbar.help add command -label "About [appname]" \
+ .mbar.help add command -label [mc "About %s" [appname]] \
-command do_about
}
@@ -1980,17 +2150,11 @@ if {[file isfile $doc_path]} {
}
if {$browser ne {}} {
- .mbar.help add command -label {Online Documentation} \
+ .mbar.help add command -label [mc "Online Documentation"] \
-command [list exec $browser $doc_url &]
}
unset browser doc_path doc_url
-set root_exists 0
-bind . <Visibility> {
- bind . <Visibility> {}
- set root_exists 1
-}
-
# -- Standard bindings
#
wm protocol . WM_DELETE_WINDOW do_quit
@@ -2070,7 +2234,7 @@ blame {
}
blame {
if {$head eq {} && ![file exists $path]} {
- puts stderr "fatal: cannot stat path $path: No such file or directory"
+ puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
exit 1
}
blame::new $head $path
@@ -2082,7 +2246,8 @@ citool -
gui {
if {[llength $argv] != 0} {
puts -nonewline stderr "usage: $argv0"
- if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
+ if {$subcommand ne {gui}
+ && [file tail $argv0] ne "git-$subcommand"} {
puts -nonewline stderr " $subcommand"
}
puts stderr {}
@@ -2102,7 +2267,7 @@ frame .branch \
-borderwidth 1 \
-relief sunken
label .branch.l1 \
- -text {Current Branch:} \
+ -text [mc "Current Branch:"] \
-anchor w \
-justify left
label .branch.cb \
@@ -2115,15 +2280,15 @@ pack .branch -side top -fill x
# -- Main Window Layout
#
-panedwindow .vpane -orient vertical
-panedwindow .vpane.files -orient horizontal
+panedwindow .vpane -orient horizontal
+panedwindow .vpane.files -orient vertical
.vpane add .vpane.files -sticky nsew -height 100 -width 200
pack .vpane -anchor n -side top -fill both -expand 1
# -- Index File List
#
frame .vpane.files.index -height 100 -width 200
-label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
+label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
-background lightgreen
text $ui_index -background white -borderwidth 0 \
-width 20 -height 10 \
@@ -2138,12 +2303,11 @@ pack .vpane.files.index.title -side top -fill x
pack .vpane.files.index.sx -side bottom -fill x
pack .vpane.files.index.sy -side right -fill y
pack $ui_index -side left -fill both -expand 1
-.vpane.files add .vpane.files.index -sticky nsew
# -- Working Directory File List
#
frame .vpane.files.workdir -height 100 -width 200
-label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
+label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
-background lightsalmon
text $ui_workdir -background white -borderwidth 0 \
-width 20 -height 10 \
@@ -2158,7 +2322,9 @@ pack .vpane.files.workdir.title -side top -fill x
pack .vpane.files.workdir.sx -side bottom -fill x
pack .vpane.files.workdir.sy -side right -fill y
pack $ui_workdir -side left -fill both -expand 1
+
.vpane.files add .vpane.files.workdir -sticky nsew
+.vpane.files add .vpane.files.index -sticky nsew
foreach i [list $ui_index $ui_workdir] {
rmsel_tag $i
@@ -2171,8 +2337,8 @@ unset i
frame .vpane.lower -height 300 -width 400
frame .vpane.lower.commarea
frame .vpane.lower.diff -relief sunken -borderwidth 1
-pack .vpane.lower.commarea -side top -fill x
-pack .vpane.lower.diff -side bottom -fill both -expand 1
+pack .vpane.lower.diff -fill both -expand 1
+pack .vpane.lower.commarea -side bottom -fill x
.vpane add .vpane.lower -sticky nsew
# -- Commit Area Buttons
@@ -2184,29 +2350,29 @@ label .vpane.lower.commarea.buttons.l -text {} \
pack .vpane.lower.commarea.buttons.l -side top -fill x
pack .vpane.lower.commarea.buttons -side left -fill y
-button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
+button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
-command do_rescan
pack .vpane.lower.commarea.buttons.rescan -side top -fill x
lappend disable_on_lock \
{.vpane.lower.commarea.buttons.rescan conf -state}
-button .vpane.lower.commarea.buttons.incall -text {Stage Changed} \
+button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
-command do_add_all
pack .vpane.lower.commarea.buttons.incall -side top -fill x
lappend disable_on_lock \
{.vpane.lower.commarea.buttons.incall conf -state}
-button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
+button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
-command do_signoff
pack .vpane.lower.commarea.buttons.signoff -side top -fill x
-button .vpane.lower.commarea.buttons.commit -text {Commit} \
+button .vpane.lower.commarea.buttons.commit -text [mc Commit@@verb] \
-command do_commit
pack .vpane.lower.commarea.buttons.commit -side top -fill x
lappend disable_on_lock \
{.vpane.lower.commarea.buttons.commit conf -state}
-button .vpane.lower.commarea.buttons.push -text {Push} \
+button .vpane.lower.commarea.buttons.push -text [mc Push] \
-command do_push_anywhere
pack .vpane.lower.commarea.buttons.push -side top -fill x
@@ -2217,14 +2383,14 @@ frame .vpane.lower.commarea.buffer.header
set ui_comm .vpane.lower.commarea.buffer.t
set ui_coml .vpane.lower.commarea.buffer.header.l
radiobutton .vpane.lower.commarea.buffer.header.new \
- -text {New Commit} \
+ -text [mc "New Commit"] \
-command do_select_commit_type \
-variable selected_commit_type \
-value new
lappend disable_on_lock \
[list .vpane.lower.commarea.buffer.header.new conf -state]
radiobutton .vpane.lower.commarea.buffer.header.amend \
- -text {Amend Last Commit} \
+ -text [mc "Amend Last Commit"] \
-command do_select_commit_type \
-variable selected_commit_type \
-value amend
@@ -2236,12 +2402,12 @@ label $ui_coml \
proc trace_commit_type {varname args} {
global ui_coml commit_type
switch -glob -- $commit_type {
- initial {set txt {Initial Commit Message:}}
- amend {set txt {Amended Commit Message:}}
- amend-initial {set txt {Amended Initial Commit Message:}}
- amend-merge {set txt {Amended Merge Commit Message:}}
- merge {set txt {Merge Commit Message:}}
- * {set txt {Commit Message:}}
+ initial {set txt [mc "Initial Commit Message:"]}
+ amend {set txt [mc "Amended Commit Message:"]}
+ amend-initial {set txt [mc "Amended Initial Commit Message:"]}
+ amend-merge {set txt [mc "Amended Merge Commit Message:"]}
+ merge {set txt [mc "Merge Commit Message:"]}
+ * {set txt [mc "Commit Message:"]}
}
$ui_coml conf -text $txt
}
@@ -2270,23 +2436,23 @@ pack .vpane.lower.commarea.buffer -side left -fill y
set ctxm .vpane.lower.commarea.buffer.ctxm
menu $ctxm -tearoff 0
$ctxm add command \
- -label {Cut} \
+ -label [mc Cut] \
-command {tk_textCut $ui_comm}
$ctxm add command \
- -label {Copy} \
+ -label [mc Copy] \
-command {tk_textCopy $ui_comm}
$ctxm add command \
- -label {Paste} \
+ -label [mc Paste] \
-command {tk_textPaste $ui_comm}
$ctxm add command \
- -label {Delete} \
+ -label [mc Delete] \
-command {$ui_comm delete sel.first sel.last}
$ctxm add separator
$ctxm add command \
- -label {Select All} \
+ -label [mc "Select All"] \
-command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
$ctxm add command \
- -label {Copy All} \
+ -label [mc "Copy All"] \
-command {
$ui_comm tag add sel 0.0 end
tk_textCopy $ui_comm
@@ -2294,9 +2460,9 @@ $ctxm add command \
}
$ctxm add separator
$ctxm add command \
- -label {Sign Off} \
+ -label [mc "Sign Off"] \
-command do_signoff
-bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
+set ui_comm_ctxm $ctxm
# -- Diff Header
#
@@ -2310,7 +2476,7 @@ proc trace_current_diff_path {varname args} {
} else {
set p $current_diff_path
set s [mapdesc [lindex $file_states($p) 0] $p]
- set f {File:}
+ set f [mc "File:"]
set p [escape_path $p]
set o normal
}
@@ -2344,7 +2510,7 @@ pack .vpane.lower.diff.header.path -fill x
set ctxm .vpane.lower.diff.header.ctxm
menu $ctxm -tearoff 0
$ctxm add command \
- -label {Copy} \
+ -label [mc Copy] \
-command {
clipboard clear
clipboard append \
@@ -2412,19 +2578,40 @@ $ui_diff tag raise sel
set ctxm .vpane.lower.diff.body.ctxm
menu $ctxm -tearoff 0
$ctxm add command \
- -label {Refresh} \
+ -label [mc "Apply/Reverse Hunk"] \
+ -command {apply_hunk $cursorX $cursorY}
+set ui_diff_applyhunk [$ctxm index last]
+lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
+$ctxm add separator
+$ctxm add command \
+ -label [mc "Show Less Context"] \
+ -command {if {$repo_config(gui.diffcontext) >= 1} {
+ incr repo_config(gui.diffcontext) -1
+ reshow_diff
+ }}
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add command \
+ -label [mc "Show More Context"] \
+ -command {if {$repo_config(gui.diffcontext) < 99} {
+ incr repo_config(gui.diffcontext)
+ reshow_diff
+ }}
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add separator
+$ctxm add command \
+ -label [mc Refresh] \
-command reshow_diff
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
$ctxm add command \
- -label {Copy} \
+ -label [mc Copy] \
-command {tk_textCopy $ui_diff}
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
$ctxm add command \
- -label {Select All} \
+ -label [mc "Select All"] \
-command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
$ctxm add command \
- -label {Copy All} \
+ -label [mc "Copy All"] \
-command {
$ui_diff tag add sel 0.0 end
tk_textCopy $ui_diff
@@ -2433,45 +2620,24 @@ $ctxm add command \
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
$ctxm add separator
$ctxm add command \
- -label {Apply/Reverse Hunk} \
- -command {apply_hunk $cursorX $cursorY}
-set ui_diff_applyhunk [$ctxm index last]
-lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
-$ctxm add separator
-$ctxm add command \
- -label {Decrease Font Size} \
+ -label [mc "Decrease Font Size"] \
-command {incr_font_size font_diff -1}
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
$ctxm add command \
- -label {Increase Font Size} \
+ -label [mc "Increase Font Size"] \
-command {incr_font_size font_diff 1}
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
$ctxm add separator
-$ctxm add command \
- -label {Show Less Context} \
- -command {if {$repo_config(gui.diffcontext) >= 1} {
- incr repo_config(gui.diffcontext) -1
- reshow_diff
- }}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add command \
- -label {Show More Context} \
- -command {if {$repo_config(gui.diffcontext) < 99} {
- incr repo_config(gui.diffcontext)
- reshow_diff
- }}
-lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-$ctxm add separator
-$ctxm add command -label {Options...} \
+$ctxm add command -label [mc "Options..."] \
-command do_options
proc popup_diff_menu {ctxm x y X Y} {
global current_diff_path file_states
set ::cursorX $x
set ::cursorY $y
if {$::ui_index eq $::current_diff_side} {
- set l "Unstage Hunk From Commit"
+ set l [mc "Unstage Hunk From Commit"]
} else {
- set l "Stage Hunk For Commit"
+ set l [mc "Stage Hunk For Commit"]
}
if {$::is_3way_diff
|| $current_diff_path eq {}
@@ -2490,7 +2656,7 @@ bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
#
set main_status [::status_bar::new .status]
pack .status -anchor w -side bottom -fill x
-$main_status show {Initializing...}
+$main_status show [mc "Initializing..."]
# -- Load geometry
#
@@ -2498,17 +2664,19 @@ catch {
set gm $repo_config(gui.geometry)
wm geometry . [lindex $gm 0]
.vpane sash place 0 \
- [lindex [.vpane sash coord 0] 0] \
- [lindex $gm 1]
+ [lindex $gm 1] \
+ [lindex [.vpane sash coord 0] 1]
.vpane.files sash place 0 \
- [lindex $gm 2] \
- [lindex [.vpane.files sash coord 0] 1]
+ [lindex [.vpane.files sash coord 0] 0] \
+ [lindex $gm 2]
unset gm
}
# -- Key Bindings
#
bind $ui_comm <$M1B-Key-Return> {do_commit;break}
+bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
+bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
bind $ui_comm <$M1B-Key-i> {do_add_all;break}
bind $ui_comm <$M1B-Key-I> {do_add_all;break}
bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
@@ -2558,6 +2726,8 @@ bind . <$M1B-Key-r> do_rescan
bind . <$M1B-Key-R> do_rescan
bind . <$M1B-Key-s> do_signoff
bind . <$M1B-Key-S> do_signoff
+bind . <$M1B-Key-t> do_add_selection
+bind . <$M1B-Key-T> do_add_selection
bind . <$M1B-Key-i> do_add_all
bind . <$M1B-Key-I> do_add_all
bind . <$M1B-Key-Return> do_commit
@@ -2581,13 +2751,13 @@ focus -force $ui_comm
if {[is_Cygwin]} {
set ignored_env 0
set suggest_user {}
- set msg "Possible environment issues exist.
+ set msg [mc "Possible environment issues exist.
The following environment variables are probably
going to be ignored by any Git subprocess run
-by [appname]:
+by %s:
-"
+" [appname]]
foreach name [array names env] {
switch -regexp -- $name {
{^GIT_INDEX_FILE$} -
@@ -2611,18 +2781,18 @@ by [appname]:
}
}
if {$ignored_env > 0} {
- append msg "
+ append msg [mc "
This is due to a known issue with the
-Tcl binary distributed by Cygwin."
+Tcl binary distributed by Cygwin."]
if {$suggest_user ne {}} {
- append msg "
+ append msg [mc "
-A good replacement for $suggest_user
+A good replacement for %s
is placing values for the user.name and
user.email settings into your personal
~/.gitconfig file.
-"
+" $suggest_user]
}
warn_popup $msg
}
@@ -2634,8 +2804,14 @@ user.email settings into your personal
if {[is_enabled transport]} {
load_all_remotes
- populate_fetch_menu
+ set n [.mbar.remote index end]
populate_push_menu
+ populate_fetch_menu
+ set n [expr {[.mbar.remote index end] - $n}]
+ if {$n > 0} {
+ .mbar.remote insert $n separator
+ }
+ unset n
}
if {[winfo exists $ui_comm]} {
@@ -2689,6 +2865,30 @@ if {[winfo exists $ui_comm]} {
}
backup_commit_buffer
+
+ # -- If the user has aspell available we can drive it
+ # in pipe mode to spellcheck the commit message.
+ #
+ set spell_cmd [list |]
+ set spell_dict [get_config gui.spellingdictionary]
+ lappend spell_cmd aspell
+ if {$spell_dict ne {}} {
+ lappend spell_cmd --master=$spell_dict
+ }
+ lappend spell_cmd --mode=none
+ lappend spell_cmd --encoding=utf-8
+ lappend spell_cmd pipe
+ if {$spell_dict eq {none}
+ || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
+ bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
+ } else {
+ set ui_comm_spell [spellcheck::init \
+ $spell_fd \
+ $ui_comm \
+ $ui_comm_ctxm \
+ ]
+ }
+ unset -nocomplain spell_cmd spell_fd spell_err spell_dict
}
lock_index begin-read
diff --git a/git-gui/lib/about.tcl b/git-gui/lib/about.tcl
new file mode 100644
index 0000000000..47be8eb97a
--- /dev/null
+++ b/git-gui/lib/about.tcl
@@ -0,0 +1,86 @@
+# git-gui about git-gui dialog
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc do_about {} {
+ global appvers copyright oguilib
+ global tcl_patchLevel tk_patchLevel
+ global ui_comm_spell
+
+ set w .about_dialog
+ toplevel $w
+ wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+ pack [git_logo $w.git_logo] -side left -fill y -padx 10 -pady 10
+ label $w.header -text [mc "About %s" [appname]] \
+ -font font_uibold
+ pack $w.header -side top -fill x
+
+ frame $w.buttons
+ button $w.buttons.close -text {Close} \
+ -default active \
+ -command [list destroy $w]
+ pack $w.buttons.close -side right
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ label $w.desc \
+ -text "[mc "git-gui - a graphical user interface for Git."]\n$copyright" \
+ -padx 5 -pady 5 \
+ -justify left \
+ -anchor w \
+ -borderwidth 1 \
+ -relief solid
+ pack $w.desc -side top -fill x -padx 5 -pady 5
+
+ set v {}
+ append v "git-gui version $appvers\n"
+ append v "[git version]\n"
+ append v "\n"
+ if {$tcl_patchLevel eq $tk_patchLevel} {
+ append v "Tcl/Tk version $tcl_patchLevel"
+ } else {
+ append v "Tcl version $tcl_patchLevel"
+ append v ", Tk version $tk_patchLevel"
+ }
+ if {[info exists ui_comm_spell]} {
+ append v "\n"
+ append v [$ui_comm_spell version]
+ }
+
+ set d {}
+ append d "git wrapper: $::_git\n"
+ append d "git exec dir: [gitexec]\n"
+ append d "git-gui lib: $oguilib"
+
+ label $w.vers \
+ -text $v \
+ -padx 5 -pady 5 \
+ -justify left \
+ -anchor w \
+ -borderwidth 1 \
+ -relief solid
+ pack $w.vers -side top -fill x -padx 5 -pady 5
+
+ label $w.dirs \
+ -text $d \
+ -padx 5 -pady 5 \
+ -justify left \
+ -anchor w \
+ -borderwidth 1 \
+ -relief solid
+ pack $w.dirs -side top -fill x -padx 5 -pady 5
+
+ menu $w.ctxm -tearoff 0
+ $w.ctxm add command \
+ -label {Copy} \
+ -command "
+ clipboard clear
+ clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
+ "
+
+ bind $w <Visibility> "grab $w; focus $w.buttons.close"
+ bind $w <Key-Escape> "destroy $w"
+ bind $w <Key-Return> "destroy $w"
+ bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
+ wm title $w "About [appname]"
+ tkwait window $w
+}
diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
index 96072847a2..00ecf21333 100644
--- a/git-gui/lib/blame.tcl
+++ b/git-gui/lib/blame.tcl
@@ -74,11 +74,11 @@ constructor new {i_commit i_path} {
set path $i_path
make_toplevel top w
- wm title $top "[appname] ([reponame]): File Viewer"
+ wm title $top [append "[appname] ([reponame]): " [mc "File Viewer"]]
frame $w.header -background gold
label $w.header.commit_l \
- -text {Commit:} \
+ -text [mc "Commit:"] \
-background gold \
-anchor w \
-justify left
@@ -101,7 +101,7 @@ constructor new {i_commit i_path} {
-anchor w \
-justify left
label $w.header.path_l \
- -text {File:} \
+ -text [mc "File:"] \
-background gold \
-anchor w \
-justify left
@@ -246,7 +246,7 @@ constructor new {i_commit i_path} {
menu $w.ctxm -tearoff 0
$w.ctxm add command \
- -label "Copy Commit" \
+ -label [mc "Copy Commit"] \
-command [cb _copycommit]
foreach i $w_columns {
@@ -366,7 +366,7 @@ method _load {jump} {
set amov_data [list [list]]
set asim_data [list [list]]
- $status show "Reading $commit:[escape_path $path]..."
+ $status show [mc "Reading %s..." "$commit:[escape_path $path]"]
$w_path conf -text [escape_path $path]
if {$commit eq {}} {
set fd [open $path r]
@@ -470,7 +470,7 @@ method _read_file {fd jump} {
_exec_blame $this $w_asim @asim_data \
[list] \
- { copy/move tracking}
+ [mc "Loading copy/move tracking annotations..."]
}
} ifdeleted { catch {close $fd} }
@@ -489,8 +489,8 @@ method _exec_blame {cur_w cur_d options cur_s} {
set blame_lines 0
$status start \
- "Loading$cur_s annotations..." \
- {lines annotated}
+ $cur_s \
+ [mc "lines annotated"]
}
method _read_blame {fd cur_w cur_d} {
@@ -671,10 +671,10 @@ method _read_blame {fd cur_w cur_d} {
if {$cur_w eq $w_asim} {
_exec_blame $this $w_amov @amov_data \
$original_options \
- { original location}
+ [mc "Loading original location annotations..."]
} else {
set current_fd {}
- $status stop {Annotation complete.}
+ $status stop [mc "Annotation complete."]
}
} else {
$status update $blame_lines $total_lines
@@ -728,7 +728,7 @@ method _showcommit {cur_w lno} {
if {$dat eq {}} {
set cmit {}
- $w_cviewer insert end "Loading annotation..." still_loading
+ $w_cviewer insert end [mc "Loading annotation..."] still_loading
} else {
set cmit [lindex $dat 0]
set file [lindex $dat 1]
@@ -743,20 +743,14 @@ method _showcommit {cur_w lno} {
set author_time {}
catch {set author_name $header($cmit,author)}
catch {set author_email $header($cmit,author-mail)}
- catch {set author_time [clock format \
- $header($cmit,author-time) \
- -format {%Y-%m-%d %H:%M:%S}
- ]}
+ catch {set author_time [format_date $header($cmit,author-time)]}
set committer_name {}
set committer_email {}
set committer_time {}
catch {set committer_name $header($cmit,committer)}
catch {set committer_email $header($cmit,committer-mail)}
- catch {set committer_time [clock format \
- $header($cmit,committer-time) \
- -format {%Y-%m-%d %H:%M:%S}
- ]}
+ catch {set committer_time [format_date $header($cmit,committer-time)]}
if {[catch {set msg $header($cmit,message)}]} {
set msg {}
@@ -790,16 +784,16 @@ method _showcommit {cur_w lno} {
}
$w_cviewer insert end "commit $cmit\n" header_key
- $w_cviewer insert end "Author:\t" header_key
+ $w_cviewer insert end [strcat [mc "Author:"] "\t"] header_key
$w_cviewer insert end "$author_name $author_email" header_val
$w_cviewer insert end " $author_time\n" header_val
- $w_cviewer insert end "Committer:\t" header_key
+ $w_cviewer insert end [strcat [mc "Committer:"] "\t"] header_key
$w_cviewer insert end "$committer_name $committer_email" header_val
$w_cviewer insert end " $committer_time\n" header_val
if {$file ne $path} {
- $w_cviewer insert end "Original File:\t" header_key
+ $w_cviewer insert end [strcat [mc "Original File:"] "\t"] header_key
$w_cviewer insert end "[escape_path $file]\n" header_val
}
@@ -892,10 +886,7 @@ method _open_tooltip {cur_w} {
set author_time {}
catch {set author_name $header($cmit,author)}
catch {set summary $header($cmit,summary)}
- catch {set author_time [clock format \
- $header($cmit,author-time) \
- -format {%Y-%m-%d %H:%M:%S}
- ]}
+ catch {set author_time [format_date $header($cmit,author-time)]}
$tooltip_t insert end "commit $cmit\n"
$tooltip_t insert end "$author_name $author_time\n"
@@ -914,23 +905,20 @@ method _open_tooltip {cur_w} {
set author_time {}
catch {set author_name $header($cmit,author)}
catch {set summary $header($cmit,summary)}
- catch {set author_time [clock format \
- $header($cmit,author-time) \
- -format {%Y-%m-%d %H:%M:%S}
- ]}
+ catch {set author_time [format_date $header($cmit,author-time)]}
- $tooltip_t insert end "Originally By:\n" section_header
+ $tooltip_t insert end [strcat [mc "Originally By:"] "\n"] section_header
$tooltip_t insert end "commit $cmit\n"
$tooltip_t insert end "$author_name $author_time\n"
$tooltip_t insert end "$summary\n"
if {$file ne $path} {
- $tooltip_t insert end "In File: " section_header
+ $tooltip_t insert end [strcat [mc "In File:"] " "] section_header
$tooltip_t insert end "$file\n"
}
$tooltip_t insert end "\n"
- $tooltip_t insert end "Copied Or Moved Here By:\n" section_header
+ $tooltip_t insert end [strcat [mc "Copied Or Moved Here By:"] "\n"] section_header
$tooltip_t insert end $save
}
diff --git a/git-gui/lib/branch_checkout.tcl b/git-gui/lib/branch_checkout.tcl
index 72c45b4554..6603703ea1 100644
--- a/git-gui/lib/branch_checkout.tcl
+++ b/git-gui/lib/branch_checkout.tcl
@@ -11,37 +11,37 @@ field opt_detach 0; # force a detached head case?
constructor dialog {} {
make_toplevel top w
- wm title $top "[appname] ([reponame]): Checkout Branch"
+ wm title $top [append "[appname] ([reponame]): " [mc "Checkout Branch"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
}
- label $w.header -text {Checkout Branch} -font font_uibold
+ label $w.header -text [mc "Checkout Branch"] -font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
- button $w.buttons.create -text Checkout \
+ button $w.buttons.create -text [mc Checkout] \
-default active \
-command [cb _checkout]
pack $w.buttons.create -side right
- button $w.buttons.cancel -text {Cancel} \
+ button $w.buttons.cancel -text [mc Cancel] \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- set w_rev [::choose_rev::new $w.rev {Revision}]
+ set w_rev [::choose_rev::new $w.rev [mc Revision]]
$w_rev bind_listbox <Double-Button-1> [cb _checkout]
pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
- labelframe $w.options -text {Options}
+ labelframe $w.options -text [mc Options]
checkbutton $w.options.fetch \
- -text {Fetch Tracking Branch} \
+ -text [mc "Fetch Tracking Branch"] \
-variable @opt_fetch
pack $w.options.fetch -anchor nw
checkbutton $w.options.detach \
- -text {Detach From Local Branch} \
+ -text [mc "Detach From Local Branch"] \
-variable @opt_detach
pack $w.options.detach -anchor nw
diff --git a/git-gui/lib/branch_create.tcl b/git-gui/lib/branch_create.tcl
index def615d19d..53dfb4ce6b 100644
--- a/git-gui/lib/branch_create.tcl
+++ b/git-gui/lib/branch_create.tcl
@@ -19,28 +19,28 @@ constructor dialog {} {
global repo_config
make_toplevel top w
- wm title $top "[appname] ([reponame]): Create Branch"
+ wm title $top [append "[appname] ([reponame]): " [mc "Create Branch"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
}
- label $w.header -text {Create New Branch} -font font_uibold
+ label $w.header -text [mc "Create New Branch"] -font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
- button $w.buttons.create -text Create \
+ button $w.buttons.create -text [mc Create] \
-default active \
-command [cb _create]
pack $w.buttons.create -side right
- button $w.buttons.cancel -text {Cancel} \
+ button $w.buttons.cancel -text [mc Cancel] \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- labelframe $w.desc -text {Branch Name}
+ labelframe $w.desc -text [mc "Branch Name"]
radiobutton $w.desc.name_r \
-anchor w \
- -text {Name:} \
+ -text [mc "Name:"] \
-value user \
-variable @name_type
set w_name $w.desc.name_t
@@ -55,7 +55,7 @@ constructor dialog {} {
radiobutton $w.desc.match_r \
-anchor w \
- -text {Match Tracking Branch Name} \
+ -text [mc "Match Tracking Branch Name"] \
-value match \
-variable @name_type
grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2
@@ -63,38 +63,38 @@ constructor dialog {} {
grid columnconfigure $w.desc 1 -weight 1
pack $w.desc -anchor nw -fill x -pady 5 -padx 5
- set w_rev [::choose_rev::new $w.rev {Starting Revision}]
+ set w_rev [::choose_rev::new $w.rev [mc "Starting Revision"]]
pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
- labelframe $w.options -text {Options}
+ labelframe $w.options -text [mc Options]
frame $w.options.merge
- label $w.options.merge.l -text {Update Existing Branch:}
+ label $w.options.merge.l -text [mc "Update Existing Branch:"]
pack $w.options.merge.l -side left
radiobutton $w.options.merge.no \
- -text No \
+ -text [mc No] \
-value none \
-variable @opt_merge
pack $w.options.merge.no -side left
radiobutton $w.options.merge.ff \
- -text {Fast Forward Only} \
+ -text [mc "Fast Forward Only"] \
-value ff \
-variable @opt_merge
pack $w.options.merge.ff -side left
radiobutton $w.options.merge.reset \
- -text {Reset} \
+ -text [mc Reset] \
-value reset \
-variable @opt_merge
pack $w.options.merge.reset -side left
pack $w.options.merge -anchor nw
checkbutton $w.options.fetch \
- -text {Fetch Tracking Branch} \
+ -text [mc "Fetch Tracking Branch"] \
-variable @opt_fetch
pack $w.options.fetch -anchor nw
checkbutton $w.options.checkout \
- -text {Checkout After Creation} \
+ -text [mc "Checkout After Creation"] \
-variable @opt_checkout
pack $w.options.checkout -anchor nw
pack $w.options -anchor nw -fill x -pady 5 -padx 5
@@ -128,7 +128,7 @@ method _create {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Please select a tracking branch."
+ -message [mc "Please select a tracking branch."]
return
}
if {![regsub ^refs/heads/ [lindex $spec 2] {} newbranch]} {
@@ -137,7 +137,7 @@ method _create {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Tracking branch [$w get] is not a branch in the remote repository."
+ -message [mc "Tracking branch %s is not a branch in the remote repository." [$w get]]
return
}
}
@@ -150,7 +150,7 @@ method _create {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Please supply a branch name."
+ -message [mc "Please supply a branch name."]
focus $w_name
return
}
@@ -161,7 +161,7 @@ method _create {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "'$newbranch' is not an acceptable branch name."
+ -message [mc "'%s' is not an acceptable branch name." $newbranch]
focus $w_name
return
}
diff --git a/git-gui/lib/branch_delete.tcl b/git-gui/lib/branch_delete.tcl
index c7573c6c72..86c4f73370 100644
--- a/git-gui/lib/branch_delete.tcl
+++ b/git-gui/lib/branch_delete.tcl
@@ -12,29 +12,29 @@ constructor dialog {} {
global current_branch
make_toplevel top w
- wm title $top "[appname] ([reponame]): Delete Branch"
+ wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
}
- label $w.header -text {Delete Local Branch} -font font_uibold
+ label $w.header -text [mc "Delete Local Branch"] -font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
set w_delete $w.buttons.delete
button $w_delete \
- -text Delete \
+ -text [mc Delete] \
-default active \
-state disabled \
-command [cb _delete]
pack $w_delete -side right
button $w.buttons.cancel \
- -text {Cancel} \
+ -text [mc Cancel] \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- labelframe $w.list -text {Local Branches}
+ labelframe $w.list -text [mc "Local Branches"]
set w_heads $w.list.l
listbox $w_heads \
-height 10 \
@@ -49,9 +49,9 @@ constructor dialog {} {
set w_check [choose_rev::new \
$w.check \
- {Delete Only If Merged Into} \
+ [mc "Delete Only If Merged Into"] \
]
- $w_check none {Always (Do not perform merge test.)}
+ $w_check none [mc "Always (Do not perform merge test.)"]
pack $w.check -anchor nw -fill x -pady 5 -padx 5
foreach h [load_all_heads] {
@@ -100,7 +100,7 @@ method _delete {} {
lappend to_delete [list $b $o]
}
if {$not_merged ne {}} {
- set msg "The following branches are not completely merged into [$w_check get]:
+ set msg "[mc "The following branches are not completely merged into %s:" [$w_check get]]
- [join $not_merged "\n - "]"
tk_messageBox \
@@ -112,9 +112,7 @@ method _delete {} {
}
if {$to_delete eq {}} return
if {$check_cmt eq {}} {
- set msg {Recovering deleted branches is difficult.
-
-Delete the selected branches?}
+ set msg [mc "Recovering deleted branches is difficult. \n\n Delete the selected branches?"]
if {[tk_messageBox \
-icon warning \
-type yesno \
@@ -140,7 +138,7 @@ Delete the selected branches?}
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Failed to delete branches:\n$failed"
+ -message [mc "Failed to delete branches:\n%s" $failed]
}
destroy $w
diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl
index 1cadc31d20..166538808f 100644
--- a/git-gui/lib/branch_rename.tcl
+++ b/git-gui/lib/branch_rename.tcl
@@ -11,7 +11,7 @@ constructor dialog {} {
global current_branch
make_toplevel top w
- wm title $top "[appname] ([reponame]): Rename Branch"
+ wm title $top [append "[appname] ([reponame]): " [mc "Rename Branch"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
}
@@ -19,24 +19,24 @@ constructor dialog {} {
set oldname $current_branch
set newname [get_config gui.newbranchtemplate]
- label $w.header -text {Rename Branch} -font font_uibold
+ label $w.header -text [mc "Rename Branch"] -font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
- button $w.buttons.rename -text Rename \
+ button $w.buttons.rename -text [mc Rename] \
-default active \
-command [cb _rename]
pack $w.buttons.rename -side right
- button $w.buttons.cancel -text {Cancel} \
+ button $w.buttons.cancel -text [mc Cancel] \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
frame $w.rename
- label $w.rename.oldname_l -text {Branch:}
+ label $w.rename.oldname_l -text [mc "Branch:"]
eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads]
- label $w.rename.newname_l -text {New Name:}
+ label $w.rename.newname_l -text [mc "New Name:"]
entry $w.rename.newname_t \
-borderwidth 1 \
-relief sunken \
@@ -72,7 +72,7 @@ method _rename {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Please select a branch to rename."
+ -message [mc "Please select a branch to rename."]
focus $w.rename.oldname_m
return
}
@@ -83,7 +83,7 @@ method _rename {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Please supply a branch name."
+ -message [mc "Please supply a branch name."]
focus $w.rename.newname_t
return
}
@@ -93,7 +93,7 @@ method _rename {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Branch '$newname' already exists."
+ -message [mc "Branch '%s' already exists." $newname]
focus $w.rename.newname_t
return
}
@@ -103,7 +103,7 @@ method _rename {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "We do not like '$newname' as a branch name."
+ -message [mc "'%s' is not an acceptable branch name." $newname]
focus $w.rename.newname_t
return
}
@@ -114,7 +114,7 @@ method _rename {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Failed to rename '$oldname'.\n\n$err"
+ -message [strcat [mc "Failed to rename '%s'." $oldname] "\n\n$err"]
return
}
diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl
index 31349009ae..53d5a62816 100644
--- a/git-gui/lib/browser.tcl
+++ b/git-gui/lib/browser.tcl
@@ -14,7 +14,7 @@ field w
field browser_commit
field browser_path
field browser_files {}
-field browser_status {Starting...}
+field browser_status [mc "Starting..."]
field browser_stack {}
field browser_busy 1
@@ -23,7 +23,7 @@ field ls_buf {}; # Buffered record output from ls-tree
constructor new {commit {path {}}} {
global cursor_ptr M1B
make_toplevel top w
- wm title $top "[appname] ([reponame]): File Browser"
+ wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]]
set browser_commit $commit
set browser_path $browser_commit:$path
@@ -122,7 +122,7 @@ method _parent {} {
} else {
regsub {/[^/]+$} $browser_path {} browser_path
}
- set browser_status "Loading $browser_path..."
+ set browser_status [mc "Loading %s..." $browser_path]
_ls $this [lindex $parent 0] [lindex $parent 1]
}
}
@@ -139,7 +139,7 @@ method _enter {} {
tree {
set name [lindex $info 2]
set escn [escape_path $name]
- set browser_status "Loading $escn..."
+ set browser_status [mc "Loading %s..." $escn]
append browser_path $escn
_ls $this [lindex $info 1] $name
}
@@ -183,7 +183,7 @@ method _ls {tree_id {name {}}} {
-align center -padx 5 -pady 1 \
-name icon0 \
-image ::browser::img_parent
- $w insert end {[Up To Parent]}
+ $w insert end [mc "\[Up To Parent\]"]
lappend browser_files parent
}
lappend browser_stack [list $tree_id $name]
@@ -242,7 +242,7 @@ method _read {fd} {
if {[eof $fd]} {
close $fd
- set browser_status Ready.
+ set browser_status [mc "Ready."]
set browser_busy 0
set ls_buf {}
if {$n > 0} {
@@ -263,27 +263,27 @@ field w_rev ; # mega-widget to pick the initial revision
constructor dialog {} {
make_toplevel top w
- wm title $top "[appname] ([reponame]): Browse Branch Files"
+ wm title $top [append "[appname] ([reponame]): " [mc "Browse Branch Files"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
}
label $w.header \
- -text {Browse Branch Files} \
+ -text [mc "Browse Branch Files"] \
-font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
- button $w.buttons.browse -text Browse \
+ button $w.buttons.browse -text [mc Browse] \
-default active \
-command [cb _open]
pack $w.buttons.browse -side right
- button $w.buttons.cancel -text {Cancel} \
+ button $w.buttons.cancel -text [mc Cancel] \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- set w_rev [::choose_rev::new $w.rev {Revision}]
+ set w_rev [::choose_rev::new $w.rev [mc Revision]]
$w_rev bind_listbox <Double-Button-1> [cb _open]
pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl
index 76f04f2854..6e1411711b 100644
--- a/git-gui/lib/checkout_op.tcl
+++ b/git-gui/lib/checkout_op.tcl
@@ -76,7 +76,7 @@ method run {} {
_toplevel $this {Refreshing Tracking Branch}
set w_cons [::console::embed \
$w.console \
- "Fetching $r_name from $remote"]
+ [mc "Fetching %s from %s" $r_name $remote]]
pack $w.console -fill both -expand 1
$w_cons exec $cmd [cb _finish_fetch]
@@ -124,7 +124,7 @@ method _finish_fetch {ok} {
}
if {[catch {set new_hash [git rev-parse --verify "$l_trck^0"]} err]} {
set ok 0
- $w_cons insert "fatal: Cannot resolve $l_trck"
+ $w_cons insert [mc "fatal: Cannot resolve %s" $l_trck]
$w_cons insert $err
}
}
@@ -137,7 +137,7 @@ method _finish_fetch {ok} {
destroy $w
set w {}
} else {
- button $w.close -text Close -command [list destroy $w]
+ button $w.close -text [mc Close] -command [list destroy $w]
pack $w.close -side bottom -anchor e -padx 10 -pady 10
}
@@ -166,7 +166,7 @@ method _update_ref {} {
# Assume it does not exist, and that is what the error was.
#
if {!$create} {
- _error $this "Branch '$newbranch' does not exist."
+ _error $this [mc "Branch '%s' does not exist." $newbranch]
return 0
}
@@ -176,7 +176,7 @@ method _update_ref {} {
# We were told to create it, but not do a merge.
# Bad. Name shouldn't have existed.
#
- _error $this "Branch '$newbranch' already exists."
+ _error $this [mc "Branch '%s' already exists." $newbranch]
return 0
} elseif {!$create && $merge_type eq {none}} {
# We aren't creating, it exists and we don't merge.
@@ -203,7 +203,7 @@ method _update_ref {} {
set new $cur
set new_hash $cur
} else {
- _error $this "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to $new_expr.\nA merge is required."
+ _error $this [mc "Branch '%s' already exists.\n\nIt cannot fast-forward to %s.\nA merge is required." $newbranch $new_expr]
return 0
}
}
@@ -217,7 +217,7 @@ method _update_ref {} {
}
}
default {
- _error $this "Merge strategy '$merge_type' not supported."
+ _error $this [mc "Merge strategy '%s' not supported." $merge_type]
return 0
}
}
@@ -236,7 +236,7 @@ method _update_ref {} {
if {[catch {
git update-ref -m $reflog_msg $ref $new $cur
} err]} {
- _error $this "Failed to update '$newbranch'.\n\n$err"
+ _error $this [strcat [mc "Failed to update '%s'." $newbranch] "\n\n$err"]
return 0
}
}
@@ -248,7 +248,7 @@ method _checkout {} {
if {[lock_index checkout_op]} {
after idle [cb _start_checkout]
} else {
- _error $this "Staging area (index) is already locked."
+ _error $this [mc "Staging area (index) is already locked."]
delete_this
}
}
@@ -263,12 +263,12 @@ method _start_checkout {} {
&& $curType eq {normal}
&& $curHEAD eq $HEAD} {
} elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
- info_popup {Last scanned state does not match repository state.
+ info_popup [mc "Last scanned state does not match repository state.
Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed.
The rescan will be automatically started now.
-}
+"]
unlock_index
rescan ui_ready
delete_this
@@ -280,7 +280,7 @@ The rescan will be automatically started now.
} elseif {[is_config_true gui.trustmtime]} {
_readtree $this
} else {
- ui_status {Refreshing file status...}
+ ui_status [mc "Refreshing file status..."]
set fd [git_read update-index \
-q \
--unmerged \
@@ -319,8 +319,8 @@ method _readtree {} {
set readtree_d {}
$::main_status start \
- "Updating working directory to '[_name $this]'..." \
- {files checked out}
+ [mc "Updating working directory to '%s'..." [_name $this]] \
+ [mc "files checked out"]
set fd [git_read --stderr read-tree \
-m \
@@ -350,12 +350,12 @@ method _readtree_wait {fd} {
if {[catch {close $fd}]} {
set err $readtree_d
regsub {^fatal: } $err {} err
- $::main_status stop "Aborted checkout of '[_name $this]' (file level merging is required)."
- warn_popup "File level merge required.
+ $::main_status stop [mc "Aborted checkout of '%s' (file level merging is required)." [_name $this]]
+ warn_popup [strcat [mc "File level merge required."] "
$err
-Staying on branch '$current_branch'."
+" [mc "Staying on branch '%s'." $current_branch]]
unlock_index
delete_this
return
@@ -426,9 +426,9 @@ method _after_readtree {} {
}
if {$is_detached} {
- info_popup "You are no longer on a local branch.
+ info_popup [mc "You are no longer on a local branch.
-If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."
+If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."]
}
# -- Update our repository state. If we were previously in
@@ -443,11 +443,11 @@ If you wanted to be on a branch, create one now starting from 'This Detached Che
$ui_comm delete 0.0 end
$ui_comm edit reset
$ui_comm edit modified false
- rescan [list ui_status "Checked out '$name'."]
+ rescan [list ui_status [mc "Checked out '%s'." $name]]
} else {
repository_state commit_type HEAD MERGE_HEAD
set PARENT $HEAD
- ui_status "Checked out '$name'."
+ ui_status [mc "Checked out '%s'." $name]
}
delete_this
}
@@ -475,7 +475,7 @@ method _confirm_reset {cur} {
pack [label $w.msg1 \
-anchor w \
-justify left \
- -text "Resetting '$name' to $new_expr will lose the following commits:" \
+ -text [mc "Resetting '%s' to '%s' will lose the following commits:" $name $new_expr]\
] -anchor w
set list $w.list.l
@@ -497,21 +497,21 @@ method _confirm_reset {cur} {
pack [label $w.msg2 \
-anchor w \
-justify left \
- -text {Recovering lost commits may not be easy.} \
+ -text [mc "Recovering lost commits may not be easy."] \
]
pack [label $w.msg3 \
-anchor w \
-justify left \
- -text "Reset '$name'?" \
+ -text [mc "Reset '%s'?" $name] \
]
frame $w.buttons
button $w.buttons.visualize \
- -text Visualize \
+ -text [mc Visualize] \
-command $gitk
pack $w.buttons.visualize -side left
button $w.buttons.reset \
- -text Reset \
+ -text [mc Reset] \
-command "
set @reset_ok 1
destroy $w
@@ -519,7 +519,7 @@ method _confirm_reset {cur} {
pack $w.buttons.reset -side right
button $w.buttons.cancel \
-default active \
- -text Cancel \
+ -text [mc Cancel] \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
@@ -575,13 +575,13 @@ method _toplevel {title} {
}
method _fatal {err} {
- error_popup "Failed to set current branch.
+ error_popup [strcat [mc "Failed to set current branch.
This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file.
-This should not have occurred. [appname] will now close and give up.
+This should not have occurred. %s will now close and give up." [appname]] "
-$err"
+$err"]
exit 1
}
diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl
new file mode 100644
index 0000000000..86faf24cc8
--- /dev/null
+++ b/git-gui/lib/choose_repository.tcl
@@ -0,0 +1,1038 @@
+# git-gui Git repository chooser
+# Copyright (C) 2007 Shawn Pearce
+
+class choose_repository {
+
+field top
+field w
+field w_body ; # Widget holding the center content
+field w_next ; # Next button
+field w_quit ; # Quit button
+field o_cons ; # Console object (if active)
+field w_types ; # List of type buttons in clone
+field w_recentlist ; # Listbox containing recent repositories
+
+field done 0 ; # Finished picking the repository?
+field local_path {} ; # Where this repository is locally
+field origin_url {} ; # Where we are cloning from
+field origin_name origin ; # What we shall call 'origin'
+field clone_type hardlink ; # Type of clone to construct
+field readtree_err ; # Error output from read-tree (if any)
+field sorted_recent ; # recent repositories (sorted)
+
+constructor pick {} {
+ global M1T M1B
+
+ make_toplevel top w
+ wm title $top [mc "Git Gui"]
+
+ if {$top eq {.}} {
+ menu $w.mbar -tearoff 0
+ $top configure -menu $w.mbar
+
+ set m_repo $w.mbar.repository
+ $w.mbar add cascade \
+ -label [mc Repository] \
+ -menu $m_repo
+ menu $m_repo
+
+ if {[is_MacOSX]} {
+ $w.mbar add cascade -label [mc Apple] -menu .mbar.apple
+ menu $w.mbar.apple
+ $w.mbar.apple add command \
+ -label [mc "About %s" [appname]] \
+ -command do_about
+ } else {
+ $w.mbar add cascade -label [mc Help] -menu $w.mbar.help
+ menu $w.mbar.help
+ $w.mbar.help add command \
+ -label [mc "About %s" [appname]] \
+ -command do_about
+ }
+
+ wm protocol $top WM_DELETE_WINDOW exit
+ bind $top <$M1B-q> exit
+ bind $top <$M1B-Q> exit
+ bind $top <Key-Escape> exit
+ } else {
+ wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+ bind $top <Key-Escape> [list destroy $top]
+ set m_repo {}
+ }
+
+ pack [git_logo $w.git_logo] -side left -fill y -padx 10 -pady 10
+
+ set w_body $w.body
+ set opts $w_body.options
+ frame $w_body
+ text $opts \
+ -cursor $::cursor_ptr \
+ -relief flat \
+ -background [$w_body cget -background] \
+ -wrap none \
+ -spacing1 5 \
+ -width 50 \
+ -height 3
+ pack $opts -anchor w -fill x
+
+ $opts tag conf link_new -foreground blue -underline 1
+ $opts tag bind link_new <1> [cb _next new]
+ $opts insert end [mc "Create New Repository"] link_new
+ $opts insert end "\n"
+ if {$m_repo ne {}} {
+ $m_repo add command \
+ -command [cb _next new] \
+ -accelerator $M1T-N \
+ -label [mc "New..."]
+ bind $top <$M1B-n> [cb _next new]
+ bind $top <$M1B-N> [cb _next new]
+ }
+
+ $opts tag conf link_clone -foreground blue -underline 1
+ $opts tag bind link_clone <1> [cb _next clone]
+ $opts insert end [mc "Clone Existing Repository"] link_clone
+ $opts insert end "\n"
+ if {$m_repo ne {}} {
+ $m_repo add command \
+ -command [cb _next clone] \
+ -accelerator $M1T-C \
+ -label [mc "Clone..."]
+ bind $top <$M1B-c> [cb _next clone]
+ bind $top <$M1B-C> [cb _next clone]
+ }
+
+ $opts tag conf link_open -foreground blue -underline 1
+ $opts tag bind link_open <1> [cb _next open]
+ $opts insert end [mc "Open Existing Repository"] link_open
+ $opts insert end "\n"
+ if {$m_repo ne {}} {
+ $m_repo add command \
+ -command [cb _next open] \
+ -accelerator $M1T-O \
+ -label [mc "Open..."]
+ bind $top <$M1B-o> [cb _next open]
+ bind $top <$M1B-O> [cb _next open]
+ }
+
+ $opts conf -state disabled
+
+ set sorted_recent [_get_recentrepos]
+ if {[llength $sorted_recent] > 0} {
+ if {$m_repo ne {}} {
+ $m_repo add separator
+ $m_repo add command \
+ -state disabled \
+ -label [mc "Recent Repositories"]
+ }
+
+ label $w_body.space
+ label $w_body.recentlabel \
+ -anchor w \
+ -text [mc "Open Recent Repository:"]
+ set w_recentlist $w_body.recentlist
+ text $w_recentlist \
+ -cursor $::cursor_ptr \
+ -relief flat \
+ -background [$w_body.recentlabel cget -background] \
+ -wrap none \
+ -width 50 \
+ -height 10
+ $w_recentlist tag conf link \
+ -foreground blue \
+ -underline 1
+ set home $::env(HOME)
+ if {[is_Cygwin]} {
+ set home [exec cygpath --windows --absolute $home]
+ }
+ set home "[file normalize $home]/"
+ set hlen [string length $home]
+ foreach p $sorted_recent {
+ set path $p
+ if {[string equal -length $hlen $home $p]} {
+ set p "~/[string range $p $hlen end]"
+ }
+ regsub -all "\n" $p "\\n" p
+ $w_recentlist insert end $p link
+ $w_recentlist insert end "\n"
+
+ if {$m_repo ne {}} {
+ $m_repo add command \
+ -command [cb _open_recent_path $path] \
+ -label " $p"
+ }
+ }
+ $w_recentlist conf -state disabled
+ $w_recentlist tag bind link <1> [cb _open_recent %x,%y]
+ pack $w_body.space -anchor w -fill x
+ pack $w_body.recentlabel -anchor w -fill x
+ pack $w_recentlist -anchor w -fill x
+ }
+ pack $w_body -fill x -padx 10 -pady 10
+
+ frame $w.buttons
+ set w_next $w.buttons.next
+ set w_quit $w.buttons.quit
+ button $w_quit \
+ -text [mc "Quit"] \
+ -command exit
+ pack $w_quit -side right -padx 5
+ pack $w.buttons -side bottom -fill x -padx 10 -pady 10
+
+ if {$m_repo ne {}} {
+ $m_repo add separator
+ $m_repo add command \
+ -label [mc Quit] \
+ -command exit \
+ -accelerator $M1T-Q
+ }
+
+ bind $top <Return> [cb _invoke_next]
+ bind $top <Visibility> "
+ [cb _center]
+ grab $top
+ focus $top
+ bind $top <Visibility> {}
+ "
+ wm deiconify $top
+ tkwait variable @done
+
+ if {$top eq {.}} {
+ eval destroy [winfo children $top]
+ }
+}
+
+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]
+ set rx [expr {([winfo screenwidth $top] - $nx) / 3}]
+ set ry [expr {([winfo screenheight $top] - $ny) / 3}]
+ wm geometry $top [format {+%d+%d} $rx $ry]
+}
+
+method _invoke_next {} {
+ if {[winfo exists $w_next]} {
+ uplevel #0 [$w_next cget -command]
+ }
+}
+
+proc _get_recentrepos {} {
+ set recent [list]
+ foreach p [get_config gui.recentrepo] {
+ if {[_is_git [file join $p .git]]} {
+ lappend recent $p
+ }
+ }
+ return [lsort $recent]
+}
+
+proc _unset_recentrepo {p} {
+ regsub -all -- {([()\[\]{}\.^$+*?\\])} $p {\\\1} p
+ git config --global --unset gui.recentrepo "^$p\$"
+}
+
+proc _append_recentrepos {path} {
+ set path [file normalize $path]
+ set recent [get_config gui.recentrepo]
+
+ if {[lindex $recent end] eq $path} {
+ return
+ }
+
+ set i [lsearch $recent $path]
+ if {$i >= 0} {
+ _unset_recentrepo $path
+ set recent [lreplace $recent $i $i]
+ }
+
+ lappend recent $path
+ git config --global --add gui.recentrepo $path
+
+ while {[llength $recent] > 10} {
+ _unset_recentrepo [lindex $recent 0]
+ set recent [lrange $recent 1 end]
+ }
+}
+
+method _open_recent {xy} {
+ set id [lindex [split [$w_recentlist index @$xy] .] 0]
+ set local_path [lindex $sorted_recent [expr {$id - 1}]]
+ _do_open2 $this
+}
+
+method _open_recent_path {p} {
+ set local_path $p
+ _do_open2 $this
+}
+
+method _next {action} {
+ destroy $w_body
+ if {![winfo exists $w_next]} {
+ button $w_next -default active
+ pack $w_next -side right -padx 5 -before $w_quit
+ }
+ _do_$action $this
+}
+
+method _write_local_path {args} {
+ if {$local_path eq {}} {
+ $w_next conf -state disabled
+ } else {
+ $w_next conf -state normal
+ }
+}
+
+method _git_init {} {
+ if {[catch {file mkdir $local_path} err]} {
+ error_popup [strcat \
+ [mc "Failed to create repository %s:" $local_path] \
+ "\n\n$err"]
+ return 0
+ }
+
+ if {[catch {cd $local_path} err]} {
+ error_popup [strcat \
+ [mc "Failed to create repository %s:" $local_path] \
+ "\n\n$err"]
+ return 0
+ }
+
+ if {[catch {git init} err]} {
+ error_popup [strcat \
+ [mc "Failed to create repository %s:" $local_path] \
+ "\n\n$err"]
+ return 0
+ }
+
+ _append_recentrepos [pwd]
+ set ::_gitdir .git
+ set ::_prefix {}
+ return 1
+}
+
+proc _is_git {path} {
+ if {[file exists [file join $path HEAD]]
+ && [file exists [file join $path objects]]
+ && [file exists [file join $path config]]} {
+ return 1
+ }
+ if {[is_Cygwin]} {
+ if {[file exists [file join $path HEAD]]
+ && [file exists [file join $path objects.lnk]]
+ && [file exists [file join $path config.lnk]]} {
+ return 1
+ }
+ }
+ return 0
+}
+
+proc _objdir {path} {
+ set objdir [file join $path .git objects]
+ if {[file isdirectory $objdir]} {
+ return $objdir
+ }
+
+ set objdir [file join $path objects]
+ if {[file isdirectory $objdir]} {
+ return $objdir
+ }
+
+ if {[is_Cygwin]} {
+ set objdir [file join $path .git objects.lnk]
+ if {[file isfile $objdir]} {
+ return [win32_read_lnk $objdir]
+ }
+
+ set objdir [file join $path objects.lnk]
+ if {[file isfile $objdir]} {
+ return [win32_read_lnk $objdir]
+ }
+ }
+
+ return {}
+}
+
+######################################################################
+##
+## Create New Repository
+
+method _do_new {} {
+ $w_next conf \
+ -state disabled \
+ -command [cb _do_new2] \
+ -text [mc "Create"]
+
+ frame $w_body
+ label $w_body.h \
+ -font font_uibold \
+ -text [mc "Create New Repository"]
+ pack $w_body.h -side top -fill x -pady 10
+ pack $w_body -fill x -padx 10
+
+ frame $w_body.where
+ label $w_body.where.l -text [mc "Directory:"]
+ entry $w_body.where.t \
+ -textvariable @local_path \
+ -font font_diff \
+ -width 50
+ button $w_body.where.b \
+ -text [mc "Browse"] \
+ -command [cb _new_local_path]
+
+ pack $w_body.where.b -side right
+ pack $w_body.where.l -side left
+ pack $w_body.where.t -fill x
+ pack $w_body.where -fill x
+
+ trace add variable @local_path write [cb _write_local_path]
+ bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]]
+ update
+ focus $w_body.where.t
+}
+
+method _new_local_path {} {
+ if {$local_path ne {}} {
+ set p [file dirname $local_path]
+ } else {
+ set p [_home]
+ }
+
+ set p [tk_chooseDirectory \
+ -initialdir $p \
+ -parent $top \
+ -title [mc "Git Repository"] \
+ -mustexist false]
+ if {$p eq {}} return
+
+ set p [file normalize $p]
+ if {![_new_ok $p]} {
+ return
+ }
+ set local_path $p
+}
+
+method _do_new2 {} {
+ if {![_new_ok $local_path]} {
+ return
+ }
+ if {![_git_init $this]} {
+ return
+ }
+ set done 1
+}
+
+proc _new_ok {p} {
+ if {[file isdirectory $p]} {
+ if {[_is_git [file join $p .git]]} {
+ error_popup [mc "Directory %s already exists." $p]
+ return 0
+ }
+ } elseif {[file exists $p]} {
+ error_popup [mc "File %s already exists." $p]
+ return 0
+ }
+ return 1
+}
+
+######################################################################
+##
+## Clone Existing Repository
+
+method _do_clone {} {
+ $w_next conf \
+ -state disabled \
+ -command [cb _do_clone2] \
+ -text [mc "Clone"]
+
+ frame $w_body
+ label $w_body.h \
+ -font font_uibold \
+ -text [mc "Clone Existing Repository"]
+ pack $w_body.h -side top -fill x -pady 10
+ pack $w_body -fill x -padx 10
+
+ set args $w_body.args
+ frame $w_body.args
+ pack $args -fill both
+
+ label $args.origin_l -text [mc "URL:"]
+ entry $args.origin_t \
+ -textvariable @origin_url \
+ -font font_diff \
+ -width 50
+ button $args.origin_b \
+ -text [mc "Browse"] \
+ -command [cb _open_origin]
+ grid $args.origin_l $args.origin_t $args.origin_b -sticky ew
+
+ label $args.where_l -text [mc "Directory:"]
+ entry $args.where_t \
+ -textvariable @local_path \
+ -font font_diff \
+ -width 50
+ button $args.where_b \
+ -text [mc "Browse"] \
+ -command [cb _new_local_path]
+ grid $args.where_l $args.where_t $args.where_b -sticky ew
+
+ label $args.type_l -text [mc "Clone Type:"]
+ frame $args.type_f
+ set w_types [list]
+ lappend w_types [radiobutton $args.type_f.hardlink \
+ -state disabled \
+ -anchor w \
+ -text [mc "Standard (Fast, Semi-Redundant, Hardlinks)"] \
+ -variable @clone_type \
+ -value hardlink]
+ lappend w_types [radiobutton $args.type_f.full \
+ -state disabled \
+ -anchor w \
+ -text [mc "Full Copy (Slower, Redundant Backup)"] \
+ -variable @clone_type \
+ -value full]
+ lappend w_types [radiobutton $args.type_f.shared \
+ -state disabled \
+ -anchor w \
+ -text [mc "Shared (Fastest, Not Recommended, No Backup)"] \
+ -variable @clone_type \
+ -value shared]
+ foreach r $w_types {
+ pack $r -anchor w
+ }
+ grid $args.type_l $args.type_f -sticky new
+
+ grid columnconfigure $args 1 -weight 1
+
+ trace add variable @local_path write [cb _update_clone]
+ trace add variable @origin_url write [cb _update_clone]
+ bind $w_body.h <Destroy> "
+ [list trace remove variable @local_path write [cb _update_clone]]
+ [list trace remove variable @origin_url write [cb _update_clone]]
+ "
+ update
+ focus $args.origin_t
+}
+
+method _open_origin {} {
+ if {$origin_url ne {} && [file isdirectory $origin_url]} {
+ set p $origin_url
+ } else {
+ set p [_home]
+ }
+
+ set p [tk_chooseDirectory \
+ -initialdir $p \
+ -parent $top \
+ -title [mc "Git Repository"] \
+ -mustexist true]
+ if {$p eq {}} return
+
+ set p [file normalize $p]
+ if {![_is_git [file join $p .git]] && ![_is_git $p]} {
+ error_popup [mc "Not a Git repository: %s" [file tail $p]]
+ return
+ }
+ set origin_url $p
+}
+
+method _update_clone {args} {
+ if {$local_path ne {} && $origin_url ne {}} {
+ $w_next conf -state normal
+ } else {
+ $w_next conf -state disabled
+ }
+
+ if {$origin_url ne {} &&
+ ( [_is_git [file join $origin_url .git]]
+ || [_is_git $origin_url])} {
+ set e normal
+ if {[[lindex $w_types 0] cget -state] eq {disabled}} {
+ set clone_type hardlink
+ }
+ } else {
+ set e disabled
+ set clone_type full
+ }
+
+ foreach r $w_types {
+ $r conf -state $e
+ }
+}
+
+method _do_clone2 {} {
+ if {[file isdirectory $origin_url]} {
+ set origin_url [file normalize $origin_url]
+ }
+
+ if {$clone_type eq {hardlink} && ![file isdirectory $origin_url]} {
+ error_popup [mc "Standard only available for local repository."]
+ return
+ }
+ if {$clone_type eq {shared} && ![file isdirectory $origin_url]} {
+ error_popup [mc "Shared only available for local repository."]
+ return
+ }
+
+ if {$clone_type eq {hardlink} || $clone_type eq {shared}} {
+ set objdir [_objdir $origin_url]
+ if {$objdir eq {}} {
+ error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
+ return
+ }
+ }
+
+ set giturl $origin_url
+ if {[is_Cygwin] && [file isdirectory $giturl]} {
+ set giturl [exec cygpath --unix --absolute $giturl]
+ if {$clone_type eq {shared}} {
+ set objdir [exec cygpath --unix --absolute $objdir]
+ }
+ }
+
+ if {[file exists $local_path]} {
+ error_popup [mc "Location %s already exists." $local_path]
+ return
+ }
+
+ if {![_git_init $this]} return
+ set local_path [pwd]
+
+ if {[catch {
+ git config remote.$origin_name.url $giturl
+ git config remote.$origin_name.fetch +refs/heads/*:refs/remotes/$origin_name/*
+ } err]} {
+ error_popup [strcat [mc "Failed to configure origin"] "\n\n$err"]
+ return
+ }
+
+ destroy $w_body $w_next
+
+ switch -exact -- $clone_type {
+ hardlink {
+ set o_cons [status_bar::two_line $w_body]
+ pack $w_body -fill x -padx 10 -pady 10
+
+ $o_cons start \
+ [mc "Counting objects"] \
+ [mc "buckets"]
+ update
+
+ if {[file exists [file join $objdir info alternates]]} {
+ set pwd [pwd]
+ if {[catch {
+ file mkdir [gitdir objects info]
+ set f_in [open [file join $objdir info alternates] r]
+ set f_cp [open [gitdir objects info alternates] w]
+ fconfigure $f_in -translation binary -encoding binary
+ fconfigure $f_cp -translation binary -encoding binary
+ cd $objdir
+ while {[gets $f_in line] >= 0} {
+ if {[is_Cygwin]} {
+ puts $f_cp [exec cygpath --unix --absolute $line]
+ } else {
+ puts $f_cp [file normalize $line]
+ }
+ }
+ close $f_in
+ close $f_cp
+ cd $pwd
+ } err]} {
+ catch {cd $pwd}
+ _clone_failed $this [mc "Unable to copy objects/info/alternates: %s" $err]
+ return
+ }
+ }
+
+ set tolink [list]
+ set buckets [glob \
+ -tails \
+ -nocomplain \
+ -directory [file join $objdir] ??]
+ set bcnt [expr {[llength $buckets] + 2}]
+ set bcur 1
+ $o_cons update $bcur $bcnt
+ update
+
+ file mkdir [file join .git objects pack]
+ foreach i [glob -tails -nocomplain \
+ -directory [file join $objdir pack] *] {
+ lappend tolink [file join pack $i]
+ }
+ $o_cons update [incr bcur] $bcnt
+ update
+
+ foreach i $buckets {
+ file mkdir [file join .git objects $i]
+ foreach j [glob -tails -nocomplain \
+ -directory [file join $objdir $i] *] {
+ lappend tolink [file join $i $j]
+ }
+ $o_cons update [incr bcur] $bcnt
+ update
+ }
+ $o_cons stop
+
+ if {$tolink eq {}} {
+ info_popup [strcat \
+ [mc "Nothing to clone from %s." $origin_url] \
+ "\n" \
+ [mc "The 'master' branch has not been initialized."] \
+ ]
+ destroy $w_body
+ set done 1
+ return
+ }
+
+ set i [lindex $tolink 0]
+ if {[catch {
+ file link -hard \
+ [file join .git objects $i] \
+ [file join $objdir $i]
+ } err]} {
+ info_popup [mc "Hardlinks are unavailable. Falling back to copying."]
+ set i [_copy_files $this $objdir $tolink]
+ } else {
+ set i [_link_files $this $objdir [lrange $tolink 1 end]]
+ }
+ if {!$i} return
+
+ destroy $w_body
+ }
+ full {
+ set o_cons [console::embed \
+ $w_body \
+ [mc "Cloning from %s" $origin_url]]
+ pack $w_body -fill both -expand 1 -padx 10
+ $o_cons exec \
+ [list git fetch --no-tags -k $origin_name] \
+ [cb _do_clone_tags]
+ }
+ shared {
+ set fd [open [gitdir objects info alternates] w]
+ fconfigure $fd -translation binary
+ puts $fd $objdir
+ close $fd
+ }
+ }
+
+ if {$clone_type eq {hardlink} || $clone_type eq {shared}} {
+ if {![_clone_refs $this]} return
+ set pwd [pwd]
+ if {[catch {
+ cd $origin_url
+ set HEAD [git rev-parse --verify HEAD^0]
+ } err]} {
+ _clone_failed $this [mc "Not a Git repository: %s" [file tail $origin_url]]
+ return 0
+ }
+ cd $pwd
+ _do_clone_checkout $this $HEAD
+ }
+}
+
+method _copy_files {objdir tocopy} {
+ $o_cons start \
+ [mc "Copying objects"] \
+ [mc "KiB"]
+ set tot 0
+ set cmp 0
+ foreach p $tocopy {
+ incr tot [file size [file join $objdir $p]]
+ }
+ foreach p $tocopy {
+ if {[catch {
+ set f_in [open [file join $objdir $p] r]
+ set f_cp [open [file join .git objects $p] w]
+ fconfigure $f_in -translation binary -encoding binary
+ fconfigure $f_cp -translation binary -encoding binary
+
+ while {![eof $f_in]} {
+ incr cmp [fcopy $f_in $f_cp -size 16384]
+ $o_cons update \
+ [expr {$cmp / 1024}] \
+ [expr {$tot / 1024}]
+ update
+ }
+
+ close $f_in
+ close $f_cp
+ } err]} {
+ _clone_failed $this [mc "Unable to copy object: %s" $err]
+ return 0
+ }
+ }
+ return 1
+}
+
+method _link_files {objdir tolink} {
+ set total [llength $tolink]
+ $o_cons start \
+ [mc "Linking objects"] \
+ [mc "objects"]
+ for {set i 0} {$i < $total} {} {
+ set p [lindex $tolink $i]
+ if {[catch {
+ file link -hard \
+ [file join .git objects $p] \
+ [file join $objdir $p]
+ } err]} {
+ _clone_failed $this [mc "Unable to hardlink object: %s" $err]
+ return 0
+ }
+
+ incr i
+ if {$i % 5 == 0} {
+ $o_cons update $i $total
+ update
+ }
+ }
+ return 1
+}
+
+method _clone_refs {} {
+ set pwd [pwd]
+ if {[catch {cd $origin_url} err]} {
+ error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
+ return 0
+ }
+ set fd_in [git_read for-each-ref \
+ --tcl \
+ {--format=list %(refname) %(objectname) %(*objectname)}]
+ cd $pwd
+
+ set fd [open [gitdir packed-refs] w]
+ fconfigure $fd -translation binary
+ puts $fd "# pack-refs with: peeled"
+ while {[gets $fd_in line] >= 0} {
+ set line [eval $line]
+ set refn [lindex $line 0]
+ set robj [lindex $line 1]
+ set tobj [lindex $line 2]
+
+ if {[regsub ^refs/heads/ $refn \
+ "refs/remotes/$origin_name/" refn]} {
+ puts $fd "$robj $refn"
+ } elseif {[string match refs/tags/* $refn]} {
+ puts $fd "$robj $refn"
+ if {$tobj ne {}} {
+ puts $fd "^$tobj"
+ }
+ }
+ }
+ close $fd_in
+ close $fd
+ return 1
+}
+
+method _do_clone_tags {ok} {
+ if {$ok} {
+ $o_cons exec \
+ [list git fetch --tags -k $origin_name] \
+ [cb _do_clone_HEAD]
+ } else {
+ $o_cons done $ok
+ _clone_failed $this [mc "Cannot fetch branches and objects. See console output for details."]
+ }
+}
+
+method _do_clone_HEAD {ok} {
+ if {$ok} {
+ $o_cons exec \
+ [list git fetch $origin_name HEAD] \
+ [cb _do_clone_full_end]
+ } else {
+ $o_cons done $ok
+ _clone_failed $this [mc "Cannot fetch tags. See console output for details."]
+ }
+}
+
+method _do_clone_full_end {ok} {
+ $o_cons done $ok
+
+ if {$ok} {
+ destroy $w_body
+
+ set HEAD {}
+ if {[file exists [gitdir FETCH_HEAD]]} {
+ set fd [open [gitdir FETCH_HEAD] r]
+ while {[gets $fd line] >= 0} {
+ if {[regexp "^(.{40})\t\t" $line line HEAD]} {
+ break
+ }
+ }
+ close $fd
+ }
+
+ catch {git pack-refs}
+ _do_clone_checkout $this $HEAD
+ } else {
+ _clone_failed $this [mc "Cannot determine HEAD. See console output for details."]
+ }
+}
+
+method _clone_failed {{why {}}} {
+ if {[catch {file delete -force $local_path} err]} {
+ set why [strcat \
+ $why \
+ "\n\n" \
+ [mc "Unable to cleanup %s" $local_path] \
+ "\n\n" \
+ $err]
+ }
+ if {$why ne {}} {
+ update
+ error_popup [strcat [mc "Clone failed."] "\n" $why]
+ }
+}
+
+method _do_clone_checkout {HEAD} {
+ if {$HEAD eq {}} {
+ info_popup [strcat \
+ [mc "No default branch obtained."] \
+ "\n" \
+ [mc "The 'master' branch has not been initialized."] \
+ ]
+ set done 1
+ return
+ }
+ if {[catch {
+ git update-ref HEAD $HEAD^0
+ } err]} {
+ info_popup [strcat \
+ [mc "Cannot resolve %s as a commit." $HEAD^0] \
+ "\n $err" \
+ "\n" \
+ [mc "The 'master' branch has not been initialized."] \
+ ]
+ set done 1
+ return
+ }
+
+ set o_cons [status_bar::two_line $w_body]
+ pack $w_body -fill x -padx 10 -pady 10
+ $o_cons start \
+ [mc "Creating working directory"] \
+ [mc "files"]
+
+ set readtree_err {}
+ set fd [git_read --stderr read-tree \
+ -m \
+ -u \
+ -v \
+ HEAD \
+ HEAD \
+ ]
+ fconfigure $fd -blocking 0 -translation binary
+ fileevent $fd readable [cb _readtree_wait $fd]
+}
+
+method _readtree_wait {fd} {
+ set buf [read $fd]
+ $o_cons update_meter $buf
+ append readtree_err $buf
+
+ fconfigure $fd -blocking 1
+ if {![eof $fd]} {
+ fconfigure $fd -blocking 0
+ return
+ }
+
+ if {[catch {close $fd}]} {
+ set err $readtree_err
+ regsub {^fatal: } $err {} err
+ error_popup [strcat \
+ [mc "Initial file checkout failed."] \
+ "\n\n$err"]
+ return
+ }
+
+ set done 1
+}
+
+######################################################################
+##
+## Open Existing Repository
+
+method _do_open {} {
+ $w_next conf \
+ -state disabled \
+ -command [cb _do_open2] \
+ -text [mc "Open"]
+
+ frame $w_body
+ label $w_body.h \
+ -font font_uibold \
+ -text [mc "Open Existing Repository"]
+ pack $w_body.h -side top -fill x -pady 10
+ pack $w_body -fill x -padx 10
+
+ frame $w_body.where
+ label $w_body.where.l -text [mc "Repository:"]
+ entry $w_body.where.t \
+ -textvariable @local_path \
+ -font font_diff \
+ -width 50
+ button $w_body.where.b \
+ -text [mc "Browse"] \
+ -command [cb _open_local_path]
+
+ pack $w_body.where.b -side right
+ pack $w_body.where.l -side left
+ pack $w_body.where.t -fill x
+ pack $w_body.where -fill x
+
+ trace add variable @local_path write [cb _write_local_path]
+ bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]]
+ update
+ focus $w_body.where.t
+}
+
+method _open_local_path {} {
+ if {$local_path ne {}} {
+ set p $local_path
+ } else {
+ set p [_home]
+ }
+
+ set p [tk_chooseDirectory \
+ -initialdir $p \
+ -parent $top \
+ -title [mc "Git Repository"] \
+ -mustexist true]
+ if {$p eq {}} return
+
+ set p [file normalize $p]
+ if {![_is_git [file join $p .git]]} {
+ error_popup [mc "Not a Git repository: %s" [file tail $p]]
+ return
+ }
+ set local_path $p
+}
+
+method _do_open2 {} {
+ if {![_is_git [file join $local_path .git]]} {
+ error_popup [mc "Not a Git repository: %s" [file tail $local_path]]
+ return
+ }
+
+ if {[catch {cd $local_path} err]} {
+ error_popup [strcat \
+ [mc "Failed to open repository %s:" $local_path] \
+ "\n\n$err"]
+ return
+ }
+
+ _append_recentrepos [pwd]
+ set ::_gitdir .git
+ set ::_prefix {}
+ set done 1
+}
+
+}
diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl
index ec064b3e13..c8821c1463 100644
--- a/git-gui/lib/choose_rev.tcl
+++ b/git-gui/lib/choose_rev.tcl
@@ -50,14 +50,14 @@ constructor _new {path unmerged_only title} {
if {$is_detached} {
radiobutton $w.detachedhead_r \
-anchor w \
- -text {This Detached Checkout} \
+ -text [mc "This Detached Checkout"] \
-value HEAD \
-variable @revtype
grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2
}
radiobutton $w.expr_r \
- -text {Revision Expression:} \
+ -text [mc "Revision Expression:"] \
-value expr \
-variable @revtype
entry $w.expr_t \
@@ -71,17 +71,17 @@ constructor _new {path unmerged_only title} {
frame $w.types
radiobutton $w.types.head_r \
- -text {Local Branch} \
+ -text [mc "Local Branch"] \
-value head \
-variable @revtype
pack $w.types.head_r -side left
radiobutton $w.types.trck_r \
- -text {Tracking Branch} \
+ -text [mc "Tracking Branch"] \
-value trck \
-variable @revtype
pack $w.types.trck_r -side left
radiobutton $w.types.tag_r \
- -text {Tag} \
+ -text [mc "Tag"] \
-value tag \
-variable @revtype
pack $w.types.tag_r -side left
@@ -133,13 +133,13 @@ constructor _new {path unmerged_only title} {
append fmt { %(objecttype)}
append fmt { %(objectname)}
append fmt { [concat %(taggername) %(authorname)]}
- append fmt { [concat %(taggerdate) %(authordate)]}
+ append fmt { [reformat_date [concat %(taggerdate) %(authordate)]]}
append fmt { %(subject)}
append fmt {] [list}
append fmt { %(*objecttype)}
append fmt { %(*objectname)}
append fmt { %(*authorname)}
- append fmt { %(*authordate)}
+ append fmt { [reformat_date %(*authordate)]}
append fmt { %(*subject)}
append fmt {]}
set all_refn [list]
@@ -314,7 +314,7 @@ method commit_or_die {} {
}
set top [winfo toplevel $w]
- set msg "Invalid revision: [get $this]\n\n$err"
+ set msg [strcat [mc "Invalid revision: %s" [get $this]] "\n\n$err"]
tk_messageBox \
-icon error \
-type ok \
@@ -335,7 +335,7 @@ method _expr {} {
if {$i ne {}} {
return [lindex $cur_specs $i 1]
} else {
- error "No revision selected."
+ error [mc "No revision selected."]
}
}
@@ -343,7 +343,7 @@ method _expr {} {
if {$c_expr ne {}} {
return $c_expr
} else {
- error "Revision expression is empty."
+ error [mc "Revision expression is empty."]
}
}
HEAD { return HEAD }
@@ -451,7 +451,8 @@ method _sb_set {sb orient first last} {
focus $old_focus
}
}
- $sb set $first $last
+
+ catch {$sb set $first $last}
}
method _show_tooltip {pos} {
@@ -527,14 +528,14 @@ method _open_tooltip {} {
set last [_reflog_last $this [lindex $spec 1]]
if {$last ne {}} {
$tooltip_t insert end "\n"
- $tooltip_t insert end "updated"
+ $tooltip_t insert end [mc "Updated"]
$tooltip_t insert end " $last"
}
$tooltip_t insert end "\n"
if {$tag ne {}} {
$tooltip_t insert end "\n"
- $tooltip_t insert end "tag" section_header
+ $tooltip_t insert end [mc "Tag"] section_header
$tooltip_t insert end " [lindex $tag 1]\n"
$tooltip_t insert end [lindex $tag 2]
$tooltip_t insert end " ([lindex $tag 3])\n"
@@ -544,7 +545,7 @@ method _open_tooltip {} {
if {$cmit ne {}} {
$tooltip_t insert end "\n"
- $tooltip_t insert end "commit" section_header
+ $tooltip_t insert end [mc "Commit@@noun"] section_header
$tooltip_t insert end " [lindex $cmit 1]\n"
$tooltip_t insert end [lindex $cmit 2]
$tooltip_t insert end " ([lindex $cmit 3])\n"
@@ -553,11 +554,11 @@ method _open_tooltip {} {
if {[llength $spec] > 2} {
$tooltip_t insert end "\n"
- $tooltip_t insert end "remote" section_header
+ $tooltip_t insert end [mc "Remote"] section_header
$tooltip_t insert end " [lindex $spec 2]\n"
- $tooltip_t insert end "url"
+ $tooltip_t insert end [mc "URL"]
$tooltip_t insert end " $remote_url([lindex $spec 2])\n"
- $tooltip_t insert end "branch"
+ $tooltip_t insert end [mc "Branch"]
$tooltip_t insert end " [lindex $spec 3]"
}
@@ -583,7 +584,7 @@ method _reflog_last {name} {
}
if {$last ne {}} {
- set last [clock format $last -format {%a %b %e %H:%M:%S %Y}]
+ set last [format_date $last]
}
set reflog_last($name) $last
return $last
diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl
index f857a2ff5b..40a7103557 100644
--- a/git-gui/lib/commit.tcl
+++ b/git-gui/lib/commit.tcl
@@ -6,19 +6,19 @@ proc load_last_commit {} {
global repo_config
if {[llength $PARENT] == 0} {
- error_popup {There is nothing to amend.
+ error_popup [mc "There is nothing to amend.
You are about to create the initial commit. There is no commit before this to amend.
-}
+"]
return
}
repository_state curType curHEAD curMERGE_HEAD
if {$curType eq {merge}} {
- error_popup {Cannot amend while merging.
+ error_popup [mc "Cannot amend while merging.
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.
-}
+"]
return
}
@@ -46,7 +46,7 @@ You are currently in the middle of a merge that has not been fully completed. Y
}
set msg [string trim $msg]
} err]} {
- error_popup "Error loading commit data for amend:\n\n$err"
+ error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"]
return
}
@@ -73,12 +73,12 @@ proc committer_ident {} {
if {$GIT_COMMITTER_IDENT eq {}} {
if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
- error_popup "Unable to obtain your identity:\n\n$err"
+ error_popup [strcat [mc "Unable to obtain your identity:"] "\n\n$err"]
return {}
}
if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
$me me GIT_COMMITTER_IDENT]} {
- error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
+ error_popup [strcat [mc "Invalid GIT_COMMITTER_IDENT:"] "\n\n$me"]
return {}
}
}
@@ -130,12 +130,12 @@ proc commit_tree {} {
&& $curType eq {normal}
&& $curHEAD eq $HEAD} {
} elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
- info_popup {Last scanned state does not match repository state.
+ info_popup [mc "Last scanned state does not match repository state.
Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created.
The rescan will be automatically started now.
-}
+"]
unlock_index
rescan ui_ready
return
@@ -151,26 +151,26 @@ The rescan will be automatically started now.
D? -
M? {set files_ready 1}
U? {
- error_popup "Unmerged files cannot be committed.
+ error_popup [mc "Unmerged files cannot be committed.
-File [short_path $path] has merge conflicts. You must resolve them and stage the file before committing.
-"
+File %s has merge conflicts. You must resolve them and stage the file before committing.
+" [short_path $path]]
unlock_index
return
}
default {
- error_popup "Unknown file state [lindex $s 0] detected.
+ error_popup [mc "Unknown file state %s detected.
-File [short_path $path] cannot be committed by this program.
-"
+File %s cannot be committed by this program.
+" [lindex $s 0] [short_path $path]]
}
}
}
if {!$files_ready && ![string match *merge $curType]} {
- info_popup {No changes to commit.
+ info_popup [mc "No changes to commit.
You must stage at least 1 file before you can commit.
-}
+"]
unlock_index
return
}
@@ -180,57 +180,64 @@ You must stage at least 1 file before you can commit.
set msg [string trim [$ui_comm get 1.0 end]]
regsub -all -line {[ \t\r]+$} $msg {} msg
if {$msg eq {}} {
- error_popup {Please supply a commit message.
+ error_popup [mc "Please supply a commit message.
A good commit message has the following format:
-- First line: Describe in one sentance what you did.
+- First line: Describe in one sentence what you did.
- Second line: Blank
- Remaining lines: Describe why this change is good.
-}
+"]
unlock_index
return
}
- # -- Run the pre-commit hook.
+ # -- Build the message file.
#
- set pchook [gitdir hooks pre-commit]
+ set msg_p [gitdir GITGUI_EDITMSG]
+ set msg_wt [open $msg_p w]
+ fconfigure $msg_wt -translation lf
+ if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
+ set enc utf-8
+ }
+ set use_enc [tcl_encoding $enc]
+ if {$use_enc ne {}} {
+ fconfigure $msg_wt -encoding $use_enc
+ } else {
+ puts stderr [mc "warning: Tcl does not support encoding '%s'." $enc]
+ fconfigure $msg_wt -encoding utf-8
+ }
+ puts $msg_wt $msg
+ close $msg_wt
- # On Cygwin [file executable] might lie so we need to ask
- # the shell if the hook is executable. Yes that's annoying.
+ # -- Run the pre-commit hook.
#
- if {[is_Cygwin] && [file isfile $pchook]} {
- set pchook [list sh -c [concat \
- "if test -x \"$pchook\";" \
- "then exec \"$pchook\" 2>&1;" \
- "fi"]]
- } elseif {[file executable $pchook]} {
- set pchook [list $pchook |& cat]
- } else {
- commit_writetree $curHEAD $msg
+ set fd_ph [githook_read pre-commit]
+ if {$fd_ph eq {}} {
+ commit_commitmsg $curHEAD $msg_p
return
}
- ui_status {Calling pre-commit hook...}
+ ui_status [mc "Calling pre-commit hook..."]
set pch_error {}
- set fd_ph [open "| $pchook" r]
fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
fileevent $fd_ph readable \
- [list commit_prehook_wait $fd_ph $curHEAD $msg]
+ [list commit_prehook_wait $fd_ph $curHEAD $msg_p]
}
-proc commit_prehook_wait {fd_ph curHEAD msg} {
+proc commit_prehook_wait {fd_ph curHEAD msg_p} {
global pch_error
append pch_error [read $fd_ph]
fconfigure $fd_ph -blocking 1
if {[eof $fd_ph]} {
if {[catch {close $fd_ph}]} {
- ui_status {Commit declined by pre-commit hook.}
+ catch {file delete $msg_p}
+ ui_status [mc "Commit declined by pre-commit hook."]
hook_failed_popup pre-commit $pch_error
unlock_index
} else {
- commit_writetree $curHEAD $msg
+ commit_commitmsg $curHEAD $msg_p
}
set pch_error {}
return
@@ -238,14 +245,52 @@ proc commit_prehook_wait {fd_ph curHEAD msg} {
fconfigure $fd_ph -blocking 0
}
-proc commit_writetree {curHEAD msg} {
- ui_status {Committing changes...}
+proc commit_commitmsg {curHEAD msg_p} {
+ global pch_error
+
+ # -- Run the commit-msg hook.
+ #
+ set fd_ph [githook_read commit-msg $msg_p]
+ if {$fd_ph eq {}} {
+ commit_writetree $curHEAD $msg_p
+ return
+ }
+
+ ui_status [mc "Calling commit-msg hook..."]
+ set pch_error {}
+ fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
+ fileevent $fd_ph readable \
+ [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p]
+}
+
+proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
+ global pch_error
+
+ append pch_error [read $fd_ph]
+ fconfigure $fd_ph -blocking 1
+ if {[eof $fd_ph]} {
+ if {[catch {close $fd_ph}]} {
+ catch {file delete $msg_p}
+ ui_status [mc "Commit declined by commit-msg hook."]
+ hook_failed_popup commit-msg $pch_error
+ unlock_index
+ } else {
+ commit_writetree $curHEAD $msg_p
+ }
+ set pch_error {}
+ return
+ }
+ fconfigure $fd_ph -blocking 0
+}
+
+proc commit_writetree {curHEAD msg_p} {
+ ui_status [mc "Committing changes..."]
set fd_wt [git_read write-tree]
fileevent $fd_wt readable \
- [list commit_committree $fd_wt $curHEAD $msg]
+ [list commit_committree $fd_wt $curHEAD $msg_p]
}
-proc commit_committree {fd_wt curHEAD msg} {
+proc commit_committree {fd_wt curHEAD msg_p} {
global HEAD PARENT MERGE_HEAD commit_type
global current_branch
global ui_comm selected_commit_type
@@ -253,9 +298,10 @@ proc commit_committree {fd_wt curHEAD msg} {
global repo_config
gets $fd_wt tree_id
- if {$tree_id eq {} || [catch {close $fd_wt} err]} {
- error_popup "write-tree failed:\n\n$err"
- ui_status {Commit failed.}
+ if {[catch {close $fd_wt} err]} {
+ catch {file delete $msg_p}
+ error_popup [strcat [mc "write-tree failed:"] "\n\n$err"]
+ ui_status [mc "Commit failed."]
unlock_index
return
}
@@ -272,40 +318,23 @@ proc commit_committree {fd_wt curHEAD msg} {
&& [string length $old_tree] == 45} {
set old_tree [string range $old_tree 5 end]
} else {
- error "Commit $PARENT appears to be corrupt"
+ error [mc "Commit %s appears to be corrupt" $PARENT]
}
if {$tree_id eq $old_tree} {
- info_popup {No changes to commit.
+ catch {file delete $msg_p}
+ info_popup [mc "No changes to commit.
No files were modified by this commit and it was not a merge commit.
A rescan will be automatically started now.
-}
+"]
unlock_index
- rescan {ui_status {No changes to commit.}}
+ rescan {ui_status [mc "No changes to commit."]}
return
}
}
- # -- Build the message.
- #
- set msg_p [gitdir COMMIT_EDITMSG]
- set msg_wt [open $msg_p w]
- fconfigure $msg_wt -translation lf
- if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
- set enc utf-8
- }
- set use_enc [tcl_encoding $enc]
- if {$use_enc ne {}} {
- fconfigure $msg_wt -encoding $use_enc
- } else {
- puts stderr "warning: Tcl does not support encoding '$enc'."
- fconfigure $msg_wt -encoding utf-8
- }
- puts -nonewline $msg_wt $msg
- close $msg_wt
-
# -- Create the commit.
#
set cmd [list commit-tree $tree_id]
@@ -314,8 +343,9 @@ A rescan will be automatically started now.
}
lappend cmd <$msg_p
if {[catch {set cmt_id [eval git $cmd]} err]} {
- error_popup "commit-tree failed:\n\n$err"
- ui_status {Commit failed.}
+ catch {file delete $msg_p}
+ error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
+ ui_status [mc "Commit failed."]
unlock_index
return
}
@@ -326,18 +356,16 @@ A rescan will be automatically started now.
if {$commit_type ne {normal}} {
append reflogm " ($commit_type)"
}
- set i [string first "\n" $msg]
- if {$i >= 0} {
- set subject [string range $msg 0 [expr {$i - 1}]]
- } else {
- set subject $msg
- }
+ set msg_fd [open $msg_p r]
+ gets $msg_fd subject
+ close $msg_fd
append reflogm {: } $subject
if {[catch {
git update-ref -m $reflogm HEAD $cmt_id $curHEAD
} err]} {
- error_popup "update-ref failed:\n\n$err"
- ui_status {Commit failed.}
+ catch {file delete $msg_p}
+ error_popup [strcat [mc "update-ref failed:"] "\n\n$err"]
+ ui_status [mc "Commit failed."]
unlock_index
return
}
@@ -363,17 +391,13 @@ A rescan will be automatically started now.
# -- Run the post-commit hook.
#
- set pchook [gitdir hooks post-commit]
- if {[is_Cygwin] && [file isfile $pchook]} {
- set pchook [list sh -c [concat \
- "if test -x \"$pchook\";" \
- "then exec \"$pchook\";" \
- "fi"]]
- } elseif {![file executable $pchook]} {
- set pchook {}
- }
- if {$pchook ne {}} {
- catch {exec $pchook &}
+ set fd_ph [githook_read post-commit]
+ if {$fd_ph ne {}} {
+ upvar #0 pch_error$cmt_id pc_err
+ set pc_err {}
+ fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
+ fileevent $fd_ph readable \
+ [list commit_postcommit_wait $fd_ph $cmt_id]
}
$ui_comm delete 0.0 end
@@ -427,5 +451,20 @@ A rescan will be automatically started now.
display_all_files
unlock_index
reshow_diff
- ui_status "Created commit [string range $cmt_id 0 7]: $subject"
+ ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject]
+}
+
+proc commit_postcommit_wait {fd_ph cmt_id} {
+ upvar #0 pch_error$cmt_id pch_error
+
+ append pch_error [read $fd_ph]
+ fconfigure $fd_ph -blocking 1
+ if {[eof $fd_ph]} {
+ if {[catch {close $fd_ph}]} {
+ hook_failed_popup post-commit $pch_error 0
+ }
+ unset pch_error
+ return
+ }
+ fconfigure $fd_ph -blocking 0
}
diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl
index 6f718fbac3..5597188d80 100644
--- a/git-gui/lib/console.tcl
+++ b/git-gui/lib/console.tcl
@@ -6,6 +6,7 @@ class console {
field t_short
field t_long
field w
+field w_t
field console_cr
field is_toplevel 1; # are we our own window?
@@ -36,6 +37,7 @@ method _init {} {
}
set console_cr 1.0
+ set w_t $w.m.t
frame $w.m
label $w.m.l1 \
@@ -43,51 +45,47 @@ method _init {} {
-anchor w \
-justify left \
-font font_uibold
- text $w.m.t \
+ text $w_t \
-background white -borderwidth 1 \
-relief sunken \
-width 80 -height 10 \
-wrap none \
-font font_diff \
-state disabled \
- -xscrollcommand [list $w.m.sbx set] \
- -yscrollcommand [list $w.m.sby set]
- label $w.m.s -text {Working... please wait...} \
+ -xscrollcommand [cb _sb_set $w.m.sbx h] \
+ -yscrollcommand [cb _sb_set $w.m.sby v]
+ label $w.m.s -text [mc "Working... please wait..."] \
-anchor w \
-justify left \
-font font_uibold
- scrollbar $w.m.sbx -command [list $w.m.t xview] -orient h
- scrollbar $w.m.sby -command [list $w.m.t yview]
pack $w.m.l1 -side top -fill x
pack $w.m.s -side bottom -fill x
- pack $w.m.sbx -side bottom -fill x
- pack $w.m.sby -side right -fill y
- pack $w.m.t -side left -fill both -expand 1
+ pack $w_t -side left -fill both -expand 1
pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
menu $w.ctxm -tearoff 0
- $w.ctxm add command -label "Copy" \
- -command "tk_textCopy $w.m.t"
- $w.ctxm add command -label "Select All" \
- -command "focus $w.m.t;$w.m.t tag add sel 0.0 end"
- $w.ctxm add command -label "Copy All" \
+ $w.ctxm add command -label [mc "Copy"] \
+ -command "tk_textCopy $w_t"
+ $w.ctxm add command -label [mc "Select All"] \
+ -command "focus $w_t;$w_t tag add sel 0.0 end"
+ $w.ctxm add command -label [mc "Copy All"] \
-command "
- $w.m.t tag add sel 0.0 end
- tk_textCopy $w.m.t
- $w.m.t tag remove sel 0.0 end
+ $w_t tag add sel 0.0 end
+ tk_textCopy $w_t
+ $w_t tag remove sel 0.0 end
"
if {$is_toplevel} {
- button $w.ok -text {Close} \
+ button $w.ok -text [mc "Close"] \
-state disabled \
-command [list destroy $w]
pack $w.ok -side bottom -anchor e -pady 10 -padx 10
bind $w <Visibility> [list focus $w]
}
- bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
- bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
- bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
+ bind_button3 $w_t "tk_popup $w.ctxm %X %Y"
+ bind $w_t <$M1B-Key-a> "$w_t tag add sel 0.0 end;break"
+ bind $w_t <$M1B-Key-A> "$w_t tag add sel 0.0 end;break"
}
method exec {cmd {after {}}} {
@@ -104,8 +102,8 @@ method exec {cmd {after {}}} {
method _read {fd after} {
set buf [read $fd]
if {$buf ne {}} {
- if {![winfo exists $w.m.t]} {_init $this}
- $w.m.t conf -state normal
+ if {![winfo exists $w_t]} {_init $this}
+ $w_t conf -state normal
set c 0
set n [string length $buf]
while {$c < $n} {
@@ -115,20 +113,20 @@ method _read {fd after} {
if {$lf < 0} {set lf [expr {$n + 1}]}
if {$lf < $cr} {
- $w.m.t insert end [string range $buf $c $lf]
- set console_cr [$w.m.t index {end -1c}]
+ $w_t insert end [string range $buf $c $lf]
+ set console_cr [$w_t index {end -1c}]
set c $lf
incr c
} else {
- $w.m.t delete $console_cr end
- $w.m.t insert end "\n"
- $w.m.t insert end [string range $buf $c $cr]
+ $w_t delete $console_cr end
+ $w_t insert end "\n"
+ $w_t insert end [string range $buf $c [expr {$cr - 1}]]
set c $cr
incr c
}
}
- $w.m.t conf -state disabled
- $w.m.t see end
+ $w_t conf -state disabled
+ $w_t see end
}
fconfigure $fd -blocking 1
@@ -171,33 +169,50 @@ method chain {cmdlist {ok 1}} {
}
method insert {txt} {
- if {![winfo exists $w.m.t]} {_init $this}
- $w.m.t conf -state normal
- $w.m.t insert end "$txt\n"
- set console_cr [$w.m.t index {end -1c}]
- $w.m.t conf -state disabled
+ if {![winfo exists $w_t]} {_init $this}
+ $w_t conf -state normal
+ $w_t insert end "$txt\n"
+ set console_cr [$w_t index {end -1c}]
+ $w_t conf -state disabled
}
method done {ok} {
if {$ok} {
if {[winfo exists $w.m.s]} {
- $w.m.s conf -background green -text {Success}
+ bind $w.m.s <Destroy> [list delete_this $this]
+ $w.m.s conf -background green -text [mc "Success"]
if {$is_toplevel} {
$w.ok conf -state normal
focus $w.ok
}
+ } else {
+ delete_this
}
} else {
if {![winfo exists $w.m.s]} {
_init $this
}
- $w.m.s conf -background red -text {Error: Command Failed}
+ bind $w.m.s <Destroy> [list delete_this $this]
+ $w.m.s conf -background red -text [mc "Error: Command Failed"]
if {$is_toplevel} {
$w.ok conf -state normal
focus $w.ok
}
}
- delete_this
+}
+
+method _sb_set {sb orient first last} {
+ if {![winfo exists $sb]} {
+ if {$first == $last || ($first == 0 && $last == 1)} return
+ if {$orient eq {h}} {
+ scrollbar $sb -orient h -command [list $w_t xview]
+ pack $sb -fill x -side bottom -before $w_t
+ } else {
+ scrollbar $sb -orient v -command [list $w_t yview]
+ pack $sb -fill y -side right -before $w_t
+ }
+ }
+ $sb set $first $last
}
}
diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl
index 0657cc2245..d66aa3fe33 100644
--- a/git-gui/lib/database.tcl
+++ b/git-gui/lib/database.tcl
@@ -24,14 +24,14 @@ proc do_stats {} {
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
- label $w.header -text {Database Statistics}
+ label $w.header -text [mc "Database Statistics"]
pack $w.header -side top -fill x
frame $w.buttons -border 1
- button $w.buttons.close -text Close \
+ button $w.buttons.close -text [mc Close] \
-default active \
-command [list destroy $w]
- button $w.buttons.gc -text {Compress Database} \
+ button $w.buttons.gc -text [mc "Compress Database"] \
-default normal \
-command "destroy $w;do_gc"
pack $w.buttons.close -side right
@@ -40,16 +40,16 @@ proc do_stats {} {
frame $w.stat -borderwidth 1 -relief solid
foreach s {
- {count {Number of loose objects}}
- {size {Disk space used by loose objects} { KiB}}
- {in-pack {Number of packed objects}}
- {packs {Number of packs}}
- {size-pack {Disk space used by packed objects} { KiB}}
- {prune-packable {Packed objects waiting for pruning}}
- {garbage {Garbage files}}
+ {count {mc "Number of loose objects"}}
+ {size {mc "Disk space used by loose objects"} { KiB}}
+ {in-pack {mc "Number of packed objects"}}
+ {packs {mc "Number of packs"}}
+ {size-pack {mc "Disk space used by packed objects"} { KiB}}
+ {prune-packable {mc "Packed objects waiting for pruning"}}
+ {garbage {mc "Garbage files"}}
} {
set name [lindex $s 0]
- set label [lindex $s 1]
+ set label [eval [lindex $s 1]]
if {[catch {set value $stats($name)}]} continue
if {[llength $s] > 2} {
set value "$value[lindex $s 2]"
@@ -64,12 +64,12 @@ proc do_stats {} {
bind $w <Visibility> "grab $w; focus $w.buttons.close"
bind $w <Key-Escape> [list destroy $w]
bind $w <Key-Return> [list destroy $w]
- wm title $w "[appname] ([reponame]): Database Statistics"
+ wm title $w [append "[appname] ([reponame]): " [mc "Database Statistics"]]
tkwait window $w
}
proc do_gc {} {
- set w [console::new {gc} {Compressing the object database}]
+ set w [console::new {gc} [mc "Compressing the object database"]]
console::chain $w {
{exec git pack-refs --prune}
{exec git reflog expire --all}
@@ -80,7 +80,7 @@ proc do_gc {} {
proc do_fsck_objects {} {
set w [console::new {fsck-objects} \
- {Verifying the object database with fsck-objects}]
+ [mc "Verifying the object database with fsck-objects"]]
set cmd [list git fsck-objects]
lappend cmd --full
lappend cmd --cache
@@ -105,11 +105,11 @@ proc hint_gc {} {
set objects_current [expr {$objects_current * 256}]
set object_limit [expr {$object_limit * 256}]
if {[ask_popup \
- "This repository currently has approximately $objects_current loose objects.
+ [mc "This repository currently has approximately %i loose objects.
-To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
+To maintain optimal performance it is strongly recommended that you compress the database when more than %i loose objects exist.
-Compress the database now?"] eq yes} {
+Compress the database now?" $objects_current $object_limit]] eq yes} {
do_gc
}
}
diff --git a/git-gui/lib/date.tcl b/git-gui/lib/date.tcl
new file mode 100644
index 0000000000..abe82992b6
--- /dev/null
+++ b/git-gui/lib/date.tcl
@@ -0,0 +1,53 @@
+# git-gui date processing support
+# Copyright (C) 2007 Shawn Pearce
+
+set git_month(Jan) 1
+set git_month(Feb) 2
+set git_month(Mar) 3
+set git_month(Apr) 4
+set git_month(May) 5
+set git_month(Jun) 6
+set git_month(Jul) 7
+set git_month(Aug) 8
+set git_month(Sep) 9
+set git_month(Oct) 10
+set git_month(Nov) 11
+set git_month(Dec) 12
+
+proc parse_git_date {s} {
+ if {$s eq {}} {
+ return {}
+ }
+
+ if {![regexp \
+ {^... (...) (\d{1,2}) (\d\d):(\d\d):(\d\d) (\d{4}) ([+-]?)(\d\d)(\d\d)$} $s s \
+ month day hr mm ss yr ew tz_h tz_m]} {
+ error [mc "Invalid date from Git: %s" $s]
+ }
+
+ set s [clock scan [format {%4.4i%2.2i%2.2iT%2s%2s%2s} \
+ $yr $::git_month($month) $day \
+ $hr $mm $ss] \
+ -gmt 1]
+
+ regsub ^0 $tz_h {} tz_h
+ regsub ^0 $tz_m {} tz_m
+ switch -- $ew {
+ - {set ew +}
+ + {set ew -}
+ {} {set ew -}
+ }
+
+ return [expr "$s $ew ($tz_h * 3600 + $tz_m * 60)"]
+}
+
+proc format_date {s} {
+ if {$s eq {}} {
+ return {}
+ }
+ return [clock format $s -format {%a %b %e %H:%M:%S %Y}]
+}
+
+proc reformat_date {s} {
+ return [format_date [parse_git_date $s]]
+}
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
index 694834ab7a..d04f6dbde2 100644
--- a/git-gui/lib/diff.tcl
+++ b/git-gui/lib/diff.tcl
@@ -39,13 +39,13 @@ proc handle_empty_diff {} {
set s $file_states($path)
if {[lindex $s 0] ne {_M}} return
- info_popup "No differences detected.
+ info_popup [mc "No differences detected.
-[short_path $path] has no changes.
+%s has no changes.
The modification date of this file was updated by another application, but the content within the file was not changed.
-A rescan will be automatically started to find other files which may have the same state."
+A rescan will be automatically started to find other files which may have the same state." [short_path $path]]
clear_diff
display_file $path __
@@ -78,7 +78,7 @@ proc show_diff {path w {lno {}}} {
set current_diff_path $path
set current_diff_side $w
set current_diff_header {}
- ui_status "Loading diff of [escape_path $path]..."
+ ui_status [mc "Loading diff of %s..." [escape_path $path]]
# - Git won't give us the diff, there's nothing to compare to!
#
@@ -111,13 +111,16 @@ proc show_diff {path w {lno {}}} {
} err ]} {
set diff_active 0
unlock_index
- ui_status "Unable to display [escape_path $path]"
- error_popup "Error loading file:\n\n$err"
+ ui_status [mc "Unable to display %s" [escape_path $path]]
+ error_popup [strcat [mc "Error loading file:"] "\n\n$err"]
return
}
$ui_diff conf -state normal
if {$type eq {submodule}} {
- $ui_diff insert end "* Git Repository (subproject)\n" d_@
+ $ui_diff insert end [append \
+ "* " \
+ [mc "Git Repository (subproject)"] \
+ "\n"] d_@
} elseif {![catch {set type [exec file $path]}]} {
set n [string length $path]
if {[string equal -length $n $path $type]} {
@@ -128,7 +131,7 @@ proc show_diff {path w {lno {}}} {
}
if {[string first "\0" $content] != -1} {
$ui_diff insert end \
- "* Binary file (not showing content)." \
+ [mc "* Binary file (not showing content)."] \
d_@
} else {
if {$sz > $max_sz} {
@@ -178,8 +181,8 @@ proc show_diff {path w {lno {}}} {
if {[catch {set fd [eval git_read --nice $cmd]} err]} {
set diff_active 0
unlock_index
- ui_status "Unable to display [escape_path $path]"
- error_popup "Error loading diff:\n\n$err"
+ ui_status [mc "Unable to display %s" [escape_path $path]]
+ error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
return
}
@@ -217,6 +220,7 @@ proc read_diff {fd} {
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]
@@ -296,14 +300,14 @@ proc apply_hunk {x y} {
set apply_cmd {apply --cached --whitespace=nowarn}
set mi [lindex $file_states($current_diff_path) 0]
if {$current_diff_side eq $ui_index} {
- set mode unstage
+ set failed_msg [mc "Failed to unstage selected hunk."]
lappend apply_cmd --reverse
if {[string index $mi 0] ne {M}} {
unlock_index
return
}
} else {
- set mode stage
+ set failed_msg [mc "Failed to stage selected hunk."]
if {[string index $mi 1] ne {M}} {
unlock_index
return
@@ -328,7 +332,7 @@ proc apply_hunk {x y} {
puts -nonewline $p $current_diff_header
puts -nonewline $p [$ui_diff get $s_lno $e_lno]
close $p} err]} {
- error_popup "Failed to $mode selected hunk.\n\n$err"
+ error_popup [append $failed_msg "\n\n$err"]
unlock_index
return
}
@@ -354,5 +358,7 @@ proc apply_hunk {x y} {
display_file $current_diff_path $mi
if {$o eq {_}} {
clear_diff
+ } else {
+ set current_diff_path $current_diff_path
}
}
diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl
index 16a22187b2..0fdd7531da 100644
--- a/git-gui/lib/error.tcl
+++ b/git-gui/lib/error.tcl
@@ -9,7 +9,7 @@ proc error_popup {msg} {
set cmd [list tk_messageBox \
-icon error \
-type ok \
- -title "$title: error" \
+ -title [append "$title: " [mc "error"]] \
-message $msg]
if {[winfo ismapped .]} {
lappend cmd -parent .
@@ -25,7 +25,7 @@ proc warn_popup {msg} {
set cmd [list tk_messageBox \
-icon warning \
-type ok \
- -title "$title: warning" \
+ -title [append "$title: " [mc "warning"]] \
-message $msg]
if {[winfo ismapped .]} {
lappend cmd -parent .
@@ -62,7 +62,7 @@ proc ask_popup {msg} {
eval $cmd
}
-proc hook_failed_popup {hook msg} {
+proc hook_failed_popup {hook msg {is_fatal 1}} {
set w .hookfail
toplevel $w
@@ -77,14 +77,16 @@ proc hook_failed_popup {hook msg} {
-width 80 -height 10 \
-font font_diff \
-yscrollcommand [list $w.m.sby set]
- label $w.m.l2 \
- -text {You must correct the above errors before committing.} \
- -anchor w \
- -justify left \
- -font font_uibold
scrollbar $w.m.sby -command [list $w.m.t yview]
pack $w.m.l1 -side top -fill x
- pack $w.m.l2 -side bottom -fill x
+ if {$is_fatal} {
+ label $w.m.l2 \
+ -text [mc "You must correct the above errors before committing."] \
+ -anchor w \
+ -justify left \
+ -font font_uibold
+ pack $w.m.l2 -side bottom -fill x
+ }
pack $w.m.sby -side right -fill y
pack $w.m.t -side left -fill both -expand 1
pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
@@ -99,6 +101,6 @@ proc hook_failed_popup {hook msg} {
bind $w <Visibility> "grab $w; focus $w"
bind $w <Key-Return> "destroy $w"
- wm title $w "[appname] ([reponame]): error"
+ wm title $w [strcat "[appname] ([reponame]): " [mc "error"]]
tkwait window $w
}
diff --git a/git-gui/lib/git-gui.ico b/git-gui/lib/git-gui.ico
new file mode 100644
index 0000000000..334cfa5a1a
--- /dev/null
+++ b/git-gui/lib/git-gui.ico
Binary files differ
diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
index 44689ab63b..3c1fce7475 100644
--- a/git-gui/lib/index.tcl
+++ b/git-gui/lib/index.tcl
@@ -1,6 +1,56 @@
# git-gui index (add/remove) support
# Copyright (C) 2006, 2007 Shawn Pearce
+proc _delete_indexlock {} {
+ if {[catch {file delete -- [gitdir index.lock]} err]} {
+ error_popup [strcat [mc "Unable to unlock the index."] "\n\n$err"]
+ }
+}
+
+proc _close_updateindex {fd after} {
+ fconfigure $fd -blocking 1
+ if {[catch {close $fd} err]} {
+ set w .indexfried
+ toplevel $w
+ wm title $w [strcat "[appname] ([reponame]): " [mc "Index Error"]]
+ wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+ pack [label $w.msg \
+ -justify left \
+ -anchor w \
+ -text [strcat \
+ [mc "Updating the Git index failed. A rescan will be automatically started to resynchronize git-gui."] \
+ "\n\n$err"] \
+ ] -anchor w
+
+ frame $w.buttons
+ button $w.buttons.continue \
+ -text [mc "Continue"] \
+ -command [list destroy $w]
+ pack $w.buttons.continue -side right -padx 5
+ button $w.buttons.unlock \
+ -text [mc "Unlock Index"] \
+ -command "destroy $w; _delete_indexlock"
+ pack $w.buttons.unlock -side right
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ wm protocol $w WM_DELETE_WINDOW update
+ bind $w.buttons.continue <Visibility> "
+ grab $w
+ focus $w.buttons.continue
+ "
+ tkwait window $w
+
+ $::main_status stop
+ unlock_index
+ rescan $after 0
+ return
+ }
+
+ $::main_status stop
+ unlock_index
+ uplevel #0 $after
+}
+
proc update_indexinfo {msg pathList after} {
global update_index_cp
@@ -12,12 +62,7 @@ proc update_indexinfo {msg pathList after} {
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
- ui_status [format \
- "%s... %i/%i files (%.2f%%)" \
- $msg \
- $update_index_cp \
- $totalCnt \
- 0.0]
+ $::main_status start $msg [mc "files"]
set fd [git_write update-index -z --index-info]
fconfigure $fd \
-blocking 0 \
@@ -31,19 +76,16 @@ proc update_indexinfo {msg pathList after} {
$pathList \
$totalCnt \
$batch \
- $msg \
$after \
]
}
-proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
+proc write_update_indexinfo {fd pathList totalCnt batch after} {
global update_index_cp
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
- close $fd
- unlock_index
- uplevel #0 $after
+ _close_updateindex $fd $after
return
}
@@ -68,12 +110,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
display_file $path $new
}
- ui_status [format \
- "%s... %i/%i files (%.2f%%)" \
- $msg \
- $update_index_cp \
- $totalCnt \
- [expr {100.0 * $update_index_cp / $totalCnt}]]
+ $::main_status update $update_index_cp $totalCnt
}
proc update_index {msg pathList after} {
@@ -87,12 +124,7 @@ proc update_index {msg pathList after} {
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
- ui_status [format \
- "%s... %i/%i files (%.2f%%)" \
- $msg \
- $update_index_cp \
- $totalCnt \
- 0.0]
+ $::main_status start $msg [mc "files"]
set fd [git_write update-index --add --remove -z --stdin]
fconfigure $fd \
-blocking 0 \
@@ -106,19 +138,16 @@ proc update_index {msg pathList after} {
$pathList \
$totalCnt \
$batch \
- $msg \
$after \
]
}
-proc write_update_index {fd pathList totalCnt batch msg after} {
+proc write_update_index {fd pathList totalCnt batch after} {
global update_index_cp
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
- close $fd
- unlock_index
- uplevel #0 $after
+ _close_updateindex $fd $after
return
}
@@ -147,12 +176,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} {
display_file $path $new
}
- ui_status [format \
- "%s... %i/%i files (%.2f%%)" \
- $msg \
- $update_index_cp \
- $totalCnt \
- [expr {100.0 * $update_index_cp / $totalCnt}]]
+ $::main_status update $update_index_cp $totalCnt
}
proc checkout_index {msg pathList after} {
@@ -166,12 +190,7 @@ proc checkout_index {msg pathList after} {
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
- ui_status [format \
- "%s... %i/%i files (%.2f%%)" \
- $msg \
- $update_index_cp \
- $totalCnt \
- 0.0]
+ $::main_status start $msg [mc "files"]
set fd [git_write checkout-index \
--index \
--quiet \
@@ -191,19 +210,16 @@ proc checkout_index {msg pathList after} {
$pathList \
$totalCnt \
$batch \
- $msg \
$after \
]
}
-proc write_checkout_index {fd pathList totalCnt batch msg after} {
+proc write_checkout_index {fd pathList totalCnt batch after} {
global update_index_cp
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
- close $fd
- unlock_index
- uplevel #0 $after
+ _close_updateindex $fd $after
return
}
@@ -222,12 +238,7 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} {
}
}
- ui_status [format \
- "%s... %i/%i files (%.2f%%)" \
- $msg \
- $update_index_cp \
- $totalCnt \
- [expr {100.0 * $update_index_cp / $totalCnt}]]
+ $::main_status update $update_index_cp $totalCnt
}
proc unstage_helper {txt paths} {
@@ -268,7 +279,7 @@ proc do_unstage_selection {} {
[array names selected_paths]
} elseif {$current_diff_path ne {}} {
unstage_helper \
- "Unstaging [short_path $current_diff_path] from commit" \
+ [mc "Unstaging %s from commit" [short_path $current_diff_path]] \
[list $current_diff_path]
}
}
@@ -299,7 +310,7 @@ proc add_helper {txt paths} {
update_index \
$txt \
$pathList \
- [concat $after {ui_status {Ready to commit.}}]
+ [concat $after {ui_status [mc "Ready to commit."]}]
}
}
@@ -312,7 +323,7 @@ proc do_add_selection {} {
[array names selected_paths]
} elseif {$current_diff_path ne {}} {
add_helper \
- "Adding [short_path $current_diff_path]" \
+ [mc "Adding %s" [short_path $current_diff_path]] \
[list $current_diff_path]
}
}
@@ -351,26 +362,37 @@ proc revert_helper {txt paths} {
}
}
+
+ # Split question between singular and plural cases, because
+ # such distinction is needed in some languages. Previously, the
+ # code used "Revert changes in" for both, but that can't work
+ # in languages where 'in' must be combined with word from
+ # rest of string (in diffrent way for both cases of course).
+ #
+ # FIXME: Unfortunately, even that isn't enough in some languages
+ # as they have quite complex plural-form rules. Unfortunately,
+ # msgcat doesn't seem to support that kind of string translation.
+ #
set n [llength $pathList]
if {$n == 0} {
unlock_index
return
} elseif {$n == 1} {
- set s "[short_path [lindex $pathList]]"
+ set query [mc "Revert changes in file %s?" [short_path [lindex $pathList]]]
} else {
- set s "these $n files"
+ set query [mc "Revert changes in these %i files?" $n]
}
set reply [tk_dialog \
.confirm_revert \
"[appname] ([reponame])" \
- "Revert changes in $s?
+ "$query
-Any unstaged changes will be permanently lost by the revert." \
+[mc "Any unstaged changes will be permanently lost by the revert."]" \
question \
1 \
- {Do Nothing} \
- {Revert Changes} \
+ [mc "Do Nothing"] \
+ [mc "Revert Changes"] \
]
if {$reply == 1} {
checkout_index \
diff --git a/git-gui/lib/logo.tcl b/git-gui/lib/logo.tcl
new file mode 100644
index 0000000000..5ff76692f5
--- /dev/null
+++ b/git-gui/lib/logo.tcl
@@ -0,0 +1,43 @@
+# git-gui Git Gui logo
+# Copyright (C) 2007 Shawn Pearce
+
+# Henrik Nyh's alternative Git logo, from his blog post
+# http://henrik.nyh.se/2007/06/alternative-git-logo-and-favicon
+#
+image create photo ::git_logo_data -data {
+R0lGODdhYQC8AIQbAGZmZtg4LW9vb3l5eYKCgoyMjEC/TOJpYZWVlZ+fn2/PeKmpqbKysry8vMXF
+xZ/fpc/Pz7fnvPXNytnZ2eLi4s/v0vja1+zs7Of36fX19f3z8v///////////////////ywAAAAA
+YQC8AAAF/uAmjmRpnmiqrmzrvq4hz3RtGw+s7zx5/7dcb0hUAY8zYXHJRCKVzGjPeYRKry8q0Irt
+GrVBr3gFDo/PprKNix6ra+y2902Ly7H05L2dl9n3UX04gGeCf4RFhohiiotdjY5XkJGBfYeUOpOY
+iZablXmXURgPpKWmp6ipqYIKqq6vqREjFYK1trUKs7e7vFq5IrS9wsM0vxvBxMm8xsjKzqy6z9J5
+zNPWatXX2k7Z29433d/iMuHj3+Xm2+jp1+vs0+7vz/HyyvT1xPf4wvr7y9H+pBkbBasgLFYGE8ba
+o8nTlE4OOYGKKJFOKIopGmLMAnHjDo0eWYAM+WUiSRgj/k+eSKmyBMuWI17C3CATZs2WN1XmPLmT
+ZM+QPz0G3VihqNGjSJNWwDCzqdOnUKPu0SChqtWrWLNq3cq1q9evYCVYGCEhgNmzaNOqXcu2rdu3
+cOMGOEBWrt27ePPCpSuirN6/gAO35bvBr+DDiPMSNpy4sWO2ix9Lnmw2MuXLiS1j3gxYM+fPdz2D
+Hv1WNOnTak2jXj23LuvXlV3DZq16Nujatjnjzo15N2/Kvn9LDi7cMfHimaUqX868ufPn0KPPpOCA
+AQMWCQBo3869u/fv4MNrd3DlQoMC3QlkSJFdvPv38LVDWJLBAYHwE1LE38+/+/UhGTAggHv5odDf
+gfv9/seDgPAVeAKCELqnIAwU3BefgyZEqOF3E7rAQH8YlrDhiNt1uEIG6IGoH4kjmpjCBRaqaCCL
+G7p4AgUDIhgiCTTW2AKOEe44Qo8a2khCBgNoKKQIREZopAgZxAjhkhs0CeGTG7Sn5IpW9vekAyRS
+2eWBRl6Q44ZijhlfAQlQmeKIaarpHZsMTHABCxDQGKec3JH3QpIs7snndn6yAKaeXA7aZwuABppo
+fAws0GiEhaKQJ40F3DkjfwVC8CaCAlCgAgIkJjDfCgdiOMGn/Q2w3gkZtPgqC6ma0ECECaBwa4QE
+aOpCrSYAqeMJpEKYqw7ABnsmfwQ8aCwPySqLYKUb/kwAYbPQyoiCtQcOUMKHBwrgK7LaogBuuaxC
+OkS0KEwa37EiLBufALPuwO4Jh/InwAixkknEvSe4C9+p3PY3rr3lpnDufguIcCmzRQAc7IHYLhxf
+w/8mnILA74lg8cARa4xCsZxusMCBomZccgsfv0deuh2HvLKh/sLs3hJSvieuCwUzvIHN4tGXc3ih
+vtDzmj8fSNLR8BWQdH9LH+g00OFF3d/UBx4cUcvuOc21eFRiouV+Xvvr0dDvlX21R/2uzTR89TqU
+L3+5UoBgAxtRHd5/CHpLkd13i4D2e3hHRLKMY+9Hr0Nvx/fq3Pw57cng7/m9wQVObnIyhAiQwHF8
+/tQS8nDgI2wOYeh3CAvhuIBHiDEgqvdtwudkaz3GBPKaTcKuGgqAJRMZmK6h1hnk3ncDcUvhgPFS
+o5B476ZKQcECzCN4qgmYN4lAncmzcAEEkhJp+QlfkyhAAdtbN8H67FvHQAF6b4g6v9UryqfkKkBu
+v/0prxD//kR63YnqB8AeqcdoBRxU/1zAuwRaaX4reJ4DSSRAHUhwgrgqwgUx2B94EWGDHISPBzUY
+QgSNcAn6K6F4fscDCtBOhdoRwPW6kIHDwZA7vWoDBF44Qd/tIUAEBCACbIeG4AXxfmFrQ4B4OCYE
+JBEQELChmgbAACJioj4JOCKCCLCABZ6EAg1IHwDlyLYAB1gRJhSYgHUQAD9WnQ9+CWBAA+wknTpC
+JwQAOw==
+}
+
+proc git_logo {w} {
+ label $w \
+ -borderwidth 1 \
+ -relief sunken \
+ -background white \
+ -image ::git_logo_data
+ return $w
+}
diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl
index 0e50919d4c..cc26b07808 100644
--- a/git-gui/lib/merge.tcl
+++ b/git-gui/lib/merge.tcl
@@ -10,10 +10,10 @@ method _can_merge {} {
global HEAD commit_type file_states
if {[string match amend* $commit_type]} {
- info_popup {Cannot merge while amending.
+ info_popup [mc "Cannot merge while amending.
You must finish amending this commit before starting any type of merge.
-}
+"]
return 0
}
@@ -24,12 +24,12 @@ You must finish amending this commit before starting any type of merge.
#
repository_state curType curHEAD curMERGE_HEAD
if {$commit_type ne $curType || $HEAD ne $curHEAD} {
- info_popup {Last scanned state does not match repository state.
+ info_popup [mc "Last scanned state does not match repository state.
Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed.
The rescan will be automatically started now.
-}
+"]
unlock_index
rescan ui_ready
return 0
@@ -41,22 +41,22 @@ The rescan will be automatically started now.
continue; # and pray it works!
}
U? {
- error_popup "You are in the middle of a conflicted merge.
+ error_popup [mc "You are in the middle of a conflicted merge.
-File [short_path $path] has merge conflicts.
+File %s has merge conflicts.
You must resolve them, stage the file, and commit to complete the current merge. Only then can you begin another merge.
-"
+" [short_path $path]]
unlock_index
return 0
}
?? {
- error_popup "You are in the middle of a change.
+ error_popup [mc "You are in the middle of a change.
-File [short_path $path] is modified.
+File %s is modified.
You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise.
-"
+" [short_path $path]]
unlock_index
return 0
}
@@ -103,7 +103,7 @@ method _start {} {
regsub {^[^:@]*@} $remote {} remote
}
set branch [lindex $spec 2]
- set stitle "$branch of $remote"
+ set stitle [mc "%s of %s" $branch $remote]
}
regsub ^refs/heads/ $branch {} branch
puts $fh "$cmit\t\tbranch '$branch' of $remote"
@@ -116,9 +116,8 @@ method _start {} {
lappend cmd HEAD
lappend cmd $name
- set msg "Merging $current_branch and $stitle"
- ui_status "$msg..."
- set cons [console::new "Merge" "merge $stitle"]
+ ui_status [mc "Merging %s and %s..." $current_branch $stitle]
+ set cons [console::new [mc "Merge"] "merge $stitle"]
console::exec $cons $cmd [cb _finish $cons]
wm protocol $w WM_DELETE_WINDOW {}
@@ -128,9 +127,9 @@ method _start {} {
method _finish {cons ok} {
console::done $cons $ok
if {$ok} {
- set msg {Merge completed successfully.}
+ set msg [mc "Merge completed successfully."]
} else {
- set msg {Merge failed. Conflict resolution is required.}
+ set msg [mc "Merge failed. Conflict resolution is required."]
}
unlock_index
rescan [list ui_status $msg]
@@ -147,7 +146,7 @@ constructor dialog {} {
}
make_toplevel top w
- wm title $top "[appname] ([reponame]): Merge"
+ wm title $top [append "[appname] ([reponame]): " [mc "Merge"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
}
@@ -155,26 +154,26 @@ constructor dialog {} {
set _start [cb _start]
label $w.header \
- -text "Merge Into $current_branch" \
+ -text [mc "Merge Into %s" $current_branch] \
-font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
button $w.buttons.visualize \
- -text Visualize \
+ -text [mc Visualize] \
-command [cb _visualize]
pack $w.buttons.visualize -side left
button $w.buttons.merge \
- -text Merge \
+ -text [mc Merge] \
-command $_start
pack $w.buttons.merge -side right
button $w.buttons.cancel \
- -text {Cancel} \
+ -text [mc "Cancel"] \
-command [cb _cancel]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- set w_rev [::choose_rev::new_unmerged $w.rev {Revision To Merge}]
+ set w_rev [::choose_rev::new_unmerged $w.rev [mc "Revision To Merge"]]
pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
bind $w <$M1B-Key-Return> $_start
@@ -209,34 +208,34 @@ proc reset_hard {} {
global HEAD commit_type file_states
if {[string match amend* $commit_type]} {
- info_popup {Cannot abort while amending.
+ info_popup [mc "Cannot abort while amending.
You must finish amending this commit.
-}
+"]
return
}
if {![lock_index abort]} return
if {[string match *merge* $commit_type]} {
- set op_question "Abort merge?
+ set op_question [mc "Abort merge?
Aborting the current merge will cause *ALL* uncommitted changes to be lost.
-Continue with aborting the current merge?"
+Continue with aborting the current merge?"]
} else {
- set op_question "Reset changes?
+ set op_question [mc "Reset changes?
Resetting the changes will cause *ALL* uncommitted changes to be lost.
-Continue with resetting the current changes?"
+Continue with resetting the current changes?"]
}
if {[ask_popup $op_question] eq {yes}} {
set fd [git_read --stderr read-tree --reset -u -v HEAD]
fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [namespace code [list _reset_wait $fd]]
- $::main_status start {Aborting} {files reset}
+ $::main_status start [mc "Aborting"] [mc "files reset"]
} else {
unlock_index
}
@@ -263,9 +262,9 @@ proc _reset_wait {fd} {
catch {file delete [gitdir GITGUI_MSG]}
if {$fail} {
- warn_popup "Abort failed.\n\n$err"
+ warn_popup "[mc "Abort failed."]\n\n$err"
}
- rescan {ui_status {Abort completed. Ready.}}
+ rescan {ui_status [mc "Abort completed. Ready."]}
} else {
fconfigure $fd -blocking 0
}
diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl
index 063f5df6f7..ea80df0092 100644
--- a/git-gui/lib/option.tcl
+++ b/git-gui/lib/option.tcl
@@ -5,6 +5,7 @@ proc save_config {} {
global default_config font_descs
global repo_config global_config
global repo_config_new global_config_new
+ global ui_comm_spell
foreach option $font_descs {
set name [lindex $option 0]
@@ -52,90 +53,23 @@ proc save_config {} {
set repo_config($name) $value
}
}
-}
-
-proc do_about {} {
- global appvers copyright oguilib
- global tcl_patchLevel tk_patchLevel
-
- set w .about_dialog
- toplevel $w
- wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
- label $w.header -text "About [appname]" \
- -font font_uibold
- pack $w.header -side top -fill x
-
- frame $w.buttons
- button $w.buttons.close -text {Close} \
- -default active \
- -command [list destroy $w]
- pack $w.buttons.close -side right
- pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
- label $w.desc \
- -text "git-gui - a graphical user interface for Git.
-$copyright" \
- -padx 5 -pady 5 \
- -justify left \
- -anchor w \
- -borderwidth 1 \
- -relief solid
- pack $w.desc -side top -fill x -padx 5 -pady 5
-
- set v {}
- append v "git-gui version $appvers\n"
- append v "[git version]\n"
- append v "\n"
- if {$tcl_patchLevel eq $tk_patchLevel} {
- append v "Tcl/Tk version $tcl_patchLevel"
- } else {
- append v "Tcl version $tcl_patchLevel"
- append v ", Tk version $tk_patchLevel"
+ if {[info exists repo_config(gui.spellingdictionary)]} {
+ set value $repo_config(gui.spellingdictionary)
+ if {$value eq {none}} {
+ if {[info exists ui_comm_spell]} {
+ $ui_comm_spell stop
+ }
+ } elseif {[info exists ui_comm_spell]} {
+ $ui_comm_spell lang $value
+ }
}
-
- set d {}
- append d "git wrapper: $::_git\n"
- append d "git exec dir: [gitexec]\n"
- append d "git-gui lib: $oguilib"
-
- label $w.vers \
- -text $v \
- -padx 5 -pady 5 \
- -justify left \
- -anchor w \
- -borderwidth 1 \
- -relief solid
- pack $w.vers -side top -fill x -padx 5 -pady 5
-
- label $w.dirs \
- -text $d \
- -padx 5 -pady 5 \
- -justify left \
- -anchor w \
- -borderwidth 1 \
- -relief solid
- pack $w.dirs -side top -fill x -padx 5 -pady 5
-
- menu $w.ctxm -tearoff 0
- $w.ctxm add command \
- -label {Copy} \
- -command "
- clipboard clear
- clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
- "
-
- bind $w <Visibility> "grab $w; focus $w.buttons.close"
- bind $w <Key-Escape> "destroy $w"
- bind $w <Key-Return> "destroy $w"
- bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
- wm title $w "About [appname]"
- tkwait window $w
}
proc do_options {} {
global repo_config global_config font_descs
global repo_config_new global_config_new
+ global ui_comm_spell
array unset repo_config_new
array unset global_config_new
@@ -157,48 +91,44 @@ proc do_options {} {
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
- label $w.header -text "Options" \
- -font font_uibold
- pack $w.header -side top -fill x
-
frame $w.buttons
- button $w.buttons.restore -text {Restore Defaults} \
+ button $w.buttons.restore -text [mc "Restore Defaults"] \
-default normal \
-command do_restore_defaults
pack $w.buttons.restore -side left
- button $w.buttons.save -text Save \
+ button $w.buttons.save -text [mc Save] \
-default active \
-command [list do_save_config $w]
pack $w.buttons.save -side right
- button $w.buttons.cancel -text {Cancel} \
+ button $w.buttons.cancel -text [mc "Cancel"] \
-default normal \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- labelframe $w.repo -text "[reponame] Repository"
- labelframe $w.global -text {Global (All Repositories)}
+ labelframe $w.repo -text [mc "%s Repository" [reponame]]
+ labelframe $w.global -text [mc "Global (All Repositories)"]
pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
set optid 0
foreach option {
- {t user.name {User Name}}
- {t user.email {Email Address}}
-
- {b merge.summary {Summarize Merge Commits}}
- {i-1..5 merge.verbosity {Merge Verbosity}}
- {b merge.diffstat {Show Diffstat After Merge}}
-
- {b gui.trustmtime {Trust File Modification Timestamps}}
- {b gui.pruneduringfetch {Prune Tracking Branches During Fetch}}
- {b gui.matchtrackingbranch {Match Tracking Branches}}
- {i-0..99 gui.diffcontext {Number of Diff Context Lines}}
- {t gui.newbranchtemplate {New Branch Name Template}}
+ {t user.name {mc "User Name"}}
+ {t user.email {mc "Email Address"}}
+
+ {b merge.summary {mc "Summarize Merge Commits"}}
+ {i-1..5 merge.verbosity {mc "Merge Verbosity"}}
+ {b merge.diffstat {mc "Show Diffstat After Merge"}}
+
+ {b gui.trustmtime {mc "Trust File Modification Timestamps"}}
+ {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}}
+ {b gui.matchtrackingbranch {mc "Match Tracking Branches"}}
+ {i-0..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
+ {t gui.newbranchtemplate {mc "New Branch Name Template"}}
} {
set type [lindex $option 0]
set name [lindex $option 1]
- set text [lindex $option 2]
+ set text [eval [lindex $option 2]]
incr optid
foreach f {repo global} {
switch -glob -- $type {
@@ -242,11 +172,37 @@ proc do_options {} {
}
}
+ set all_dicts [linsert \
+ [spellcheck::available_langs] \
+ 0 \
+ none]
+ incr optid
+ foreach f {repo global} {
+ if {![info exists ${f}_config_new(gui.spellingdictionary)]} {
+ if {[info exists ui_comm_spell]} {
+ set value [$ui_comm_spell lang]
+ } else {
+ set value none
+ }
+ set ${f}_config_new(gui.spellingdictionary) $value
+ }
+
+ frame $w.$f.$optid
+ label $w.$f.$optid.l -text [mc "Spelling Dictionary:"]
+ eval tk_optionMenu $w.$f.$optid.v \
+ ${f}_config_new(gui.spellingdictionary) \
+ $all_dicts
+ pack $w.$f.$optid.l -side left -anchor w -fill x
+ pack $w.$f.$optid.v -side right -anchor e -padx 5
+ pack $w.$f.$optid -side top -anchor w -fill x
+ }
+ unset all_dicts
+
set all_fonts [lsort [font families]]
foreach option $font_descs {
set name [lindex $option 0]
set font [lindex $option 1]
- set text [lindex $option 2]
+ set text [eval [lindex $option 2]]
set global_config_new(gui.$font^^family) \
[font configure $font -family]
@@ -278,7 +234,13 @@ proc do_options {} {
bind $w <Visibility> "grab $w; focus $w.buttons.save"
bind $w <Key-Escape> "destroy $w"
bind $w <Key-Return> [list do_save_config $w]
- wm title $w "[appname] ([reponame]): Options"
+
+ if {[is_MacOSX]} {
+ set t [mc "Preferences"]
+ } else {
+ set t [mc "Options"]
+ }
+ wm title $w "[appname] ([reponame]): $t"
tkwait window $w
}
@@ -309,7 +271,7 @@ proc do_restore_defaults {} {
proc do_save_config {w} {
if {[catch {save_config} err]} {
- error_popup "Failed to completely save options:\n\n$err"
+ error_popup [strcat [mc "Failed to completely save options:"] "\n\n$err"]
}
reshow_diff
destroy $w
diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl
index cf9b9d5829..0e86ddac09 100644
--- a/git-gui/lib/remote.tcl
+++ b/git-gui/lib/remote.tcl
@@ -135,8 +135,10 @@ proc load_all_remotes {} {
proc populate_fetch_menu {} {
global all_remotes repo_config
- set m .mbar.fetch
- set prune_list [list]
+ set remote_m .mbar.remote
+ set fetch_m $remote_m.fetch
+ set prune_m $remote_m.prune
+
foreach r $all_remotes {
set enable 0
if {![catch {set a $repo_config(remote.$r.url)}]} {
@@ -157,28 +159,34 @@ proc populate_fetch_menu {} {
}
if {$enable} {
- lappend prune_list $r
- $m add command \
- -label "Fetch from $r..." \
+ if {![winfo exists $fetch_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
+ }
+
+ $fetch_m add command \
+ -label $r \
-command [list fetch_from $r]
+ $prune_m add command \
+ -label $r \
+ -command [list prune_from $r]
}
}
-
- if {$prune_list ne {}} {
- $m add separator
- }
- foreach r $prune_list {
- $m add command \
- -label "Prune from $r..." \
- -command [list prune_from $r]
- }
}
proc populate_push_menu {} {
global all_remotes repo_config
- set m .mbar.push
- set fast_count 0
+ set remote_m .mbar.remote
+ set push_m $remote_m.push
+
foreach r $all_remotes {
set enable 0
if {![catch {set a $repo_config(remote.$r.url)}]} {
@@ -199,13 +207,16 @@ proc populate_push_menu {} {
}
if {$enable} {
- if {!$fast_count} {
- $m add separator
+ if {![winfo exists $push_m]} {
+ menu $push_m
+ $remote_m insert 0 cascade \
+ -label [mc "Push to"] \
+ -menu $push_m
}
- $m add command \
- -label "Push to $r..." \
+
+ $push_m add command \
+ -label $r \
-command [list push_to $r]
- incr fast_count
}
}
}
diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl
index c88a360db5..c7b8148698 100644
--- a/git-gui/lib/remote_branch_delete.tcl
+++ b/git-gui/lib/remote_branch_delete.tcl
@@ -26,28 +26,28 @@ constructor dialog {} {
global all_remotes M1B
make_toplevel top w
- wm title $top "[appname] ([reponame]): Delete Remote Branch"
+ wm title $top [append "[appname] ([reponame]): " [mc "Delete Remote Branch"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
}
- label $w.header -text {Delete Remote Branch} -font font_uibold
+ label $w.header -text [mc "Delete Remote Branch"] -font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
- button $w.buttons.delete -text Delete \
+ button $w.buttons.delete -text [mc Delete] \
-default active \
-command [cb _delete]
pack $w.buttons.delete -side right
- button $w.buttons.cancel -text {Cancel} \
+ button $w.buttons.cancel -text [mc "Cancel"] \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- labelframe $w.dest -text {From Repository}
+ labelframe $w.dest -text [mc "From Repository"]
if {$all_remotes ne {}} {
radiobutton $w.dest.remote_r \
- -text {Remote:} \
+ -text [mc "Remote:"] \
-value remote \
-variable @urltype
eval tk_optionMenu $w.dest.remote_m @remote $all_remotes
@@ -63,7 +63,7 @@ constructor dialog {} {
set urltype url
}
radiobutton $w.dest.url_r \
- -text {Arbitrary URL:} \
+ -text [mc "Arbitrary URL:"] \
-value url \
-variable @urltype
entry $w.dest.url_t \
@@ -81,7 +81,7 @@ constructor dialog {} {
grid columnconfigure $w.dest 1 -weight 1
pack $w.dest -anchor nw -fill x -pady 5 -padx 5
- labelframe $w.heads -text {Branches}
+ labelframe $w.heads -text [mc "Branches"]
listbox $w.heads.l \
-height 10 \
-width 70 \
@@ -96,7 +96,7 @@ constructor dialog {} {
-anchor w \
-justify left
button $w.heads.footer.rescan \
- -text {Rescan} \
+ -text [mc "Rescan"] \
-command [cb _rescan]
pack $w.heads.footer.status -side left -fill x
pack $w.heads.footer.rescan -side right
@@ -106,9 +106,9 @@ constructor dialog {} {
pack $w.heads.l -side left -fill both -expand 1
pack $w.heads -fill both -expand 1 -pady 5 -padx 5
- labelframe $w.validate -text {Delete Only If}
+ labelframe $w.validate -text [mc "Delete Only If"]
radiobutton $w.validate.head_r \
- -text {Merged Into:} \
+ -text [mc "Merged Into:"] \
-value head \
-variable @checktype
set head_m [tk_optionMenu $w.validate.head_m @check_head {}]
@@ -116,7 +116,7 @@ constructor dialog {} {
trace add variable @check_head write [cb _write_check_head]
grid $w.validate.head_r $w.validate.head_m -sticky w
radiobutton $w.validate.always_r \
- -text {Always (Do not perform merge checks)} \
+ -text [mc "Always (Do not perform merge checks)"] \
-value always \
-variable @checktype
grid $w.validate.always_r -columnspan 2 -sticky w
@@ -149,7 +149,7 @@ method _delete {} {
-type ok \
-title [wm title $w] \
-parent $w \
- -message "A branch is required for 'Merged Into'."
+ -message [mc "A branch is required for 'Merged Into'."]
return
}
set crev $full_cache("$cache\nrefs/heads/$check_head")
@@ -181,14 +181,12 @@ method _delete {} {
}
if {$not_merged ne {}} {
- set msg "The following branches are not completely merged into $check_head:
+ set msg [mc "The following branches are not completely merged into %s:
- - [join $not_merged "\n - "]"
+ - %s" $check_head [join $not_merged "\n - "]]
if {$need_fetch} {
- append msg "
-
-One or more of the merge tests failed because you have not fetched the necessary commits. Try fetching from $uri first."
+ append msg "\n\n" [mc "One or more of the merge tests failed because you have not fetched the necessary commits. Try fetching from %s first." $uri]
}
tk_messageBox \
@@ -206,7 +204,7 @@ One or more of the merge tests failed because you have not fetched the necessary
-type ok \
-title [wm title $w] \
-parent $w \
- -message "Please select one or more branches to delete."
+ -message [mc "Please select one or more branches to delete."]
return
}
@@ -215,9 +213,9 @@ One or more of the merge tests failed because you have not fetched the necessary
-type yesno \
-title [wm title $w] \
-parent $w \
- -message {Recovering deleted branches is difficult.
+ -message [mc "Recovering deleted branches is difficult.
-Delete the selected branches?}] ne yes} {
+Delete the selected branches?"]] ne yes} {
return
}
@@ -225,7 +223,7 @@ Delete the selected branches?}] ne yes} {
set cons [console::new \
"push $uri" \
- "Deleting branches from $uri"]
+ [mc "Deleting branches from %s" $uri]]
console::exec $cons $push_cmd
}
@@ -285,12 +283,12 @@ method _load {cache uri} {
$w.heads.l conf -state disabled
set head_list [list]
set full_list [list]
- set status {No repository selected.}
+ set status [mc "No repository selected."]
return
}
if {[catch {set x $cached($cache)}]} {
- set status "Scanning $uri..."
+ set status [mc "Scanning %s..." $uri]
$w.heads.l conf -state disabled
set head_list [list]
set full_list [list]
diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl
index c36be2f3cd..38c3151b05 100644
--- a/git-gui/lib/shortcut.tcl
+++ b/git-gui/lib/shortcut.tcl
@@ -2,28 +2,22 @@
# Copyright (C) 2006, 2007 Shawn Pearce
proc do_windows_shortcut {} {
- global argv0
-
set fn [tk_getSaveFile \
-parent . \
- -title "[appname] ([reponame]): Create Desktop Icon" \
- -initialfile "Git [reponame].bat"]
+ -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \
+ -initialfile "Git [reponame].lnk"]
if {$fn != {}} {
- if {[file extension $fn] ne {.bat}} {
- set fn ${fn}.bat
+ if {[file extension $fn] ne {.lnk}} {
+ set fn ${fn}.lnk
}
if {[catch {
- set ge [file normalize [file dirname $::_git]]
- set fd [open $fn w]
- puts $fd "@ECHO Entering [reponame]"
- puts $fd "@ECHO Starting git-gui... please wait..."
- puts $fd "@SET PATH=$ge;%PATH%"
- puts $fd "@SET GIT_DIR=[file normalize [gitdir]]"
- puts -nonewline $fd "@\"[info nameofexecutable]\""
- puts $fd " \"[file normalize $argv0]\""
- close $fd
+ win32_create_lnk $fn [list \
+ [info nameofexecutable] \
+ [file normalize $::argv0] \
+ ] \
+ [file dirname [file normalize [gitdir]]]
} err]} {
- error_popup "Cannot write script:\n\n$err"
+ error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"]
}
}
}
@@ -42,15 +36,14 @@ proc do_cygwin_shortcut {} {
}
set fn [tk_getSaveFile \
-parent . \
- -title "[appname] ([reponame]): Create Desktop Icon" \
+ -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \
-initialdir $desktop \
- -initialfile "Git [reponame].bat"]
+ -initialfile "Git [reponame].lnk"]
if {$fn != {}} {
- if {[file extension $fn] ne {.bat}} {
- set fn ${fn}.bat
+ if {[file extension $fn] ne {.lnk}} {
+ set fn ${fn}.lnk
}
if {[catch {
- set fd [open $fn w]
set sh [exec cygpath \
--windows \
--absolute \
@@ -59,19 +52,13 @@ proc do_cygwin_shortcut {} {
--unix \
--absolute \
$argv0]
- set gd [exec cygpath \
- --unix \
- --absolute \
- [gitdir]]
- puts $fd "@ECHO Entering [reponame]"
- puts $fd "@ECHO Starting git-gui... please wait..."
- puts -nonewline $fd "@\"$sh\" --login -c \""
- puts -nonewline $fd "GIT_DIR=[sq $gd]"
- puts -nonewline $fd " [sq $me]"
- puts $fd " &\""
- close $fd
+ win32_create_lnk $fn [list \
+ $sh -c \
+ "CHERE_INVOKING=1 source /etc/profile;[sq $me]" \
+ ] \
+ [file dirname [file normalize [gitdir]]]
} err]} {
- error_popup "Cannot write script:\n\n$err"
+ error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"]
}
}
}
@@ -81,7 +68,7 @@ proc do_macosx_app {} {
set fn [tk_getSaveFile \
-parent . \
- -title "[appname] ([reponame]): Create Desktop Icon" \
+ -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \
-initialdir [file join $env(HOME) Desktop] \
-initialfile "Git [reponame].app"]
if {$fn != {}} {
@@ -146,7 +133,7 @@ proc do_macosx_app {} {
file attributes $exe -permissions u+x,g+x,o+x
} err]} {
- error_popup "Cannot write icon:\n\n$err"
+ error_popup [strcat [mc "Cannot write icon:"] "\n\n$err"]
}
}
}
diff --git a/git-gui/lib/spellcheck.tcl b/git-gui/lib/spellcheck.tcl
new file mode 100644
index 0000000000..7f018e4009
--- /dev/null
+++ b/git-gui/lib/spellcheck.tcl
@@ -0,0 +1,363 @@
+# git-gui spellchecking support through aspell
+# Copyright (C) 2008 Shawn Pearce
+
+class spellcheck {
+
+field s_fd {} ; # pipe to aspell
+field s_version ; # aspell version string
+field s_lang ; # current language code
+
+field w_text ; # text widget we are spelling
+field w_menu ; # context menu for the widget
+field s_menuidx 0 ; # last index of insertion into $w_menu
+
+field s_i ; # timer registration for _run callbacks
+field s_clear 0 ; # did we erase mispelled tags yet?
+field s_seen [list] ; # lines last seen from $w_text in _run
+field s_checked [list] ; # lines already checked
+field s_pending [list] ; # [$line $data] sent to aspell
+field s_suggest ; # array, list of suggestions, keyed by misspelling
+
+constructor init {pipe_fd ui_text ui_menu} {
+ set w_text $ui_text
+ set w_menu $ui_menu
+
+ _connect $this $pipe_fd
+ return $this
+}
+
+method _connect {pipe_fd} {
+ fconfigure $pipe_fd \
+ -encoding utf-8 \
+ -eofchar {} \
+ -translation lf
+
+ if {[gets $pipe_fd s_version] <= 0} {
+ close $pipe_fd
+ error [mc "Not connected to aspell"]
+ }
+ if {{@(#) } ne [string range $s_version 0 4]} {
+ close $pipe_fd
+ error [strcat [mc "Unrecognized aspell version"] ": $s_version"]
+ }
+ set s_version [string range $s_version 5 end]
+
+ puts $pipe_fd ! ; # enable terse mode
+ puts $pipe_fd {$$cr master} ; # fetch the language
+ flush $pipe_fd
+
+ gets $pipe_fd s_lang
+ regexp {[/\\]([^/\\]+)\.[^\.]+$} $s_lang _ s_lang
+
+ if {$::default_config(gui.spellingdictionary) eq {}
+ && [get_config gui.spellingdictionary] eq {}} {
+ set ::default_config(gui.spellingdictionary) $s_lang
+ }
+
+ if {$s_fd ne {}} {
+ catch {close $s_fd}
+ }
+ set s_fd $pipe_fd
+
+ fconfigure $s_fd -blocking 0
+ fileevent $s_fd readable [cb _read]
+
+ $w_text tag conf misspelled \
+ -foreground red \
+ -underline 1
+ bind_button3 $w_text [cb _popup_suggest %X %Y @%x,%y]
+
+ array unset s_suggest
+ set s_seen [list]
+ set s_checked [list]
+ set s_pending [list]
+ _run $this
+}
+
+method lang {{n {}}} {
+ if {$n ne {} && $s_lang ne $n} {
+ set spell_cmd [list |]
+ lappend spell_cmd aspell
+ lappend spell_cmd --master=$n
+ lappend spell_cmd --mode=none
+ lappend spell_cmd --encoding=UTF-8
+ lappend spell_cmd pipe
+ _connect $this [open $spell_cmd r+]
+ }
+ return $s_lang
+}
+
+method version {} {
+ return "$s_version, $s_lang"
+}
+
+method stop {} {
+ while {$s_menuidx > 0} {
+ $w_menu delete 0
+ incr s_menuidx -1
+ }
+ $w_text tag delete misspelled
+
+ catch {close $s_fd}
+ catch {after cancel $s_i}
+ set s_fd {}
+ set s_i {}
+ set s_lang {}
+}
+
+method _popup_suggest {X Y pos} {
+ while {$s_menuidx > 0} {
+ $w_menu delete 0
+ incr s_menuidx -1
+ }
+
+ set b_loc [$w_text index "$pos wordstart"]
+ set e_loc [_wordend $this $b_loc]
+ set orig [$w_text get $b_loc $e_loc]
+ set tags [$w_text tag names $b_loc]
+
+ if {[lsearch -exact $tags misspelled] >= 0} {
+ if {[info exists s_suggest($orig)]} {
+ set cnt 0
+ foreach s $s_suggest($orig) {
+ if {$cnt < 5} {
+ $w_menu insert $s_menuidx command \
+ -label $s \
+ -command [cb _replace $b_loc $e_loc $s]
+ incr s_menuidx
+ incr cnt
+ } else {
+ break
+ }
+ }
+ } else {
+ $w_menu insert $s_menuidx command \
+ -label [mc "No Suggestions"] \
+ -state disabled
+ incr s_menuidx
+ }
+ $w_menu insert $s_menuidx separator
+ incr s_menuidx
+ }
+
+ $w_text mark set saved-insert insert
+ tk_popup $w_menu $X $Y
+}
+
+method _replace {b_loc e_loc word} {
+ $w_text configure -autoseparators 0
+ $w_text edit separator
+
+ $w_text delete $b_loc $e_loc
+ $w_text insert $b_loc $word
+
+ $w_text edit separator
+ $w_text configure -autoseparators 1
+ $w_text mark set insert saved-insert
+}
+
+method _restart_timer {} {
+ set s_i [after 300 [cb _run]]
+}
+
+proc _match_length {max_line arr_name} {
+ upvar $arr_name a
+
+ if {[llength $a] > $max_line} {
+ set a [lrange $a 0 $max_line]
+ }
+ while {[llength $a] <= $max_line} {
+ lappend a {}
+ }
+}
+
+method _wordend {pos} {
+ set pos [$w_text index "$pos wordend"]
+ set tags [$w_text tag names $pos]
+ while {[lsearch -exact $tags misspelled] >= 0} {
+ set pos [$w_text index "$pos +1c"]
+ set tags [$w_text tag names $pos]
+ }
+ return $pos
+}
+
+method _run {} {
+ set cur_pos [$w_text index {insert -1c}]
+ set cur_line [lindex [split $cur_pos .] 0]
+ set max_line [lindex [split [$w_text index end] .] 0]
+ _match_length $max_line s_seen
+ _match_length $max_line s_checked
+
+ # Nothing in the message buffer? Nothing to spellcheck.
+ #
+ if {$cur_line == 1
+ && $max_line == 2
+ && [$w_text get 1.0 end] eq "\n"} {
+ array unset s_suggest
+ _restart_timer $this
+ return
+ }
+
+ set active 0
+ for {set n 1} {$n <= $max_line} {incr n} {
+ set s [$w_text get "$n.0" "$n.end"]
+
+ # Don't spellcheck the current line unless we are at
+ # a word boundary. The user might be typing on it.
+ #
+ if {$n == $cur_line
+ && ![regexp {^\W$} [$w_text get $cur_pos insert]]} {
+
+ # If the current word is mispelled remove the tag
+ # but force a spellcheck later.
+ #
+ set tags [$w_text tag names $cur_pos]
+ if {[lsearch -exact $tags misspelled] >= 0} {
+ $w_text tag remove misspelled \
+ "$cur_pos wordstart" \
+ [_wordend $this $cur_pos]
+ lset s_seen $n $s
+ lset s_checked $n {}
+ }
+
+ continue
+ }
+
+ if {[lindex $s_seen $n] eq $s
+ && [lindex $s_checked $n] ne $s} {
+ # Don't send empty lines to Aspell it doesn't check them.
+ #
+ if {$s eq {}} {
+ lset s_checked $n $s
+ continue
+ }
+
+ # Don't send typical s-b-o lines as the emails are
+ # almost always misspelled according to Aspell.
+ #
+ if {[regexp -nocase {^[a-z-]+-by:.*<.*@.*>$} $s]} {
+ $w_text tag remove misspelled "$n.0" "$n.end"
+ lset s_checked $n $s
+ continue
+ }
+
+ puts $s_fd ^$s
+ lappend s_pending [list $n $s]
+ set active 1
+ } else {
+ # Delay until another idle loop to make sure we don't
+ # spellcheck lines the user is actively changing.
+ #
+ lset s_seen $n $s
+ }
+ }
+
+ if {$active} {
+ set s_clear 1
+ flush $s_fd
+ } else {
+ _restart_timer $this
+ }
+}
+
+method _read {} {
+ while {[gets $s_fd line] >= 0} {
+ set lineno [lindex $s_pending 0 0]
+
+ if {$s_clear} {
+ $w_text tag remove misspelled "$lineno.0" "$lineno.end"
+ set s_clear 0
+ }
+
+ if {$line eq {}} {
+ lset s_checked $lineno [lindex $s_pending 0 1]
+ set s_pending [lrange $s_pending 1 end]
+ set s_clear 1
+ continue
+ }
+
+ set sugg [list]
+ switch -- [string range $line 0 1] {
+ {& } {
+ set line [split [string range $line 2 end] :]
+ set info [split [lindex $line 0] { }]
+ set orig [lindex $info 0]
+ set offs [lindex $info 2]
+ foreach s [split [lindex $line 1] ,] {
+ lappend sugg [string range $s 1 end]
+ }
+ }
+ {# } {
+ set info [split [string range $line 2 end] { }]
+ set orig [lindex $info 0]
+ set offs [lindex $info 1]
+ }
+ default {
+ puts stderr "<spell> $line"
+ continue
+ }
+ }
+
+ incr offs -1
+ set b_loc "$lineno.$offs"
+ set e_loc [$w_text index "$lineno.$offs wordend"]
+ set curr [$w_text get $b_loc $e_loc]
+
+ # At least for English curr = "bob", orig = "bob's"
+ # so Tk didn't include the 's but Aspell did. We
+ # try to round out the word.
+ #
+ while {$curr ne $orig
+ && [string equal -length [string length $curr] $curr $orig]} {
+ set n_loc [$w_text index "$e_loc +1c"]
+ set n_curr [$w_text get $b_loc $n_loc]
+ if {$n_curr eq $curr} {
+ break
+ }
+ set curr $n_curr
+ set e_loc $n_loc
+ }
+
+ if {$curr eq $orig} {
+ $w_text tag add misspelled $b_loc $e_loc
+ if {[llength $sugg] > 0} {
+ set s_suggest($orig) $sugg
+ } else {
+ unset -nocomplain s_suggest($orig)
+ }
+ } else {
+ unset -nocomplain s_suggest($orig)
+ }
+ }
+
+ fconfigure $s_fd -block 1
+ if {[eof $s_fd]} {
+ if {![catch {close $s_fd} err]} {
+ set err [mc "unexpected eof from aspell"]
+ }
+ catch {after cancel $s_i}
+ $w_text tag remove misspelled 1.0 end
+ error_popup [strcat "Spell Checker Failed" "\n\n" $err]
+ return
+ }
+ fconfigure $s_fd -block 0
+
+ if {[llength $s_pending] == 0} {
+ _restart_timer $this
+ }
+}
+
+proc available_langs {} {
+ set langs [list]
+ catch {
+ set fd [open [list | aspell dump dicts] r]
+ while {[gets $fd line] >= 0} {
+ if {$line eq {}} continue
+ lappend langs $line
+ }
+ close $fd
+ }
+ return $langs
+}
+
+}
diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl
index 72a8fe1fd3..51d4177551 100644
--- a/git-gui/lib/status_bar.tcl
+++ b/git-gui/lib/status_bar.tcl
@@ -6,6 +6,7 @@ class status_bar {
field w ; # our own window path
field w_l ; # text widget we draw messages into
field w_c ; # canvas we draw a progress bar into
+field c_pack ; # script to pack the canvas with
field status {}; # single line of text we show
field prefix {}; # text we format into status
field units {}; # unit of progress
@@ -24,6 +25,29 @@ constructor new {path} {
-anchor w \
-justify left
pack $w_l -side left
+ set c_pack [cb _oneline_pack]
+
+ bind $w <Destroy> [cb _delete %W]
+ return $this
+}
+
+method _oneline_pack {} {
+ $w_c conf -width 100
+ pack $w_c -side right
+}
+
+constructor two_line {path} {
+ set w $path
+ set w_l $w.l
+ set w_c $w.c
+
+ frame $w
+ label $w_l \
+ -textvariable @status \
+ -anchor w \
+ -justify left
+ pack $w_l -anchor w -fill x
+ set c_pack [list pack $w_c -fill x]
bind $w <Destroy> [cb _delete %W]
return $this
@@ -34,13 +58,12 @@ method start {msg uds} {
$w_c coords bar 0 0 0 20
} else {
canvas $w_c \
- -width 100 \
-height [expr {int([winfo reqheight $w_l] * 0.6)}] \
-borderwidth 1 \
-relief groove \
-highlightt 0
$w_c create rectangle 0 0 0 20 -tags bar -fill navy
- pack $w_c -side right
+ eval $c_pack
}
set status $msg
@@ -53,11 +76,16 @@ method update {have total} {
set pdone 0
if {$total > 0} {
set pdone [expr {100 * $have / $total}]
+ set cdone [expr {[winfo width $w_c] * $have / $total}]
}
- set status [format "%s ... %i of %i %s (%2i%%)" \
- $prefix $have $total $units $pdone]
- $w_c coords bar 0 0 $pdone 20
+ set prec [string length [format %i $total]]
+ set status [mc "%s ... %*i of %*i %s (%3i%%)" \
+ $prefix \
+ $prec $have \
+ $prec $total \
+ $units $pdone]
+ $w_c coords bar 0 0 $cdone 20
}
method update_meter {buf} {
@@ -69,7 +97,10 @@ method update_meter {buf} {
set prior [string range $meter 0 $r]
set meter [string range $meter [expr {$r + 1}] end]
- if {[regexp "\\((\\d+)/(\\d+)\\)\\s+done\r\$" $prior _j a b]} {
+ set p "\\((\\d+)/(\\d+)\\)"
+ if {[regexp ":\\s*\\d+% $p\(?:, done.\\s*\n|\\s*\r)\$" $prior _j a b]} {
+ update $this $a $b
+ } elseif {[regexp "$p\\s+done\r\$" $prior _j a b]} {
update $this $a $b
}
}
diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl
index 3a22bd40d4..8e6a9d0a60 100644
--- a/git-gui/lib/transport.tcl
+++ b/git-gui/lib/transport.tcl
@@ -3,8 +3,8 @@
proc fetch_from {remote} {
set w [console::new \
- "fetch $remote" \
- "Fetching new changes from $remote"]
+ [mc "fetch %s" $remote] \
+ [mc "Fetching new changes from %s" $remote]]
set cmds [list]
lappend cmds [list exec git fetch $remote]
if {[is_config_true gui.pruneduringfetch]} {
@@ -15,15 +15,15 @@ proc fetch_from {remote} {
proc prune_from {remote} {
set w [console::new \
- "remote prune $remote" \
- "Pruning tracking branches deleted from $remote"]
+ [mc "remote prune %s" $remote] \
+ [mc "Pruning tracking branches deleted from %s" $remote]]
console::exec $w [list git remote prune $remote]
}
proc push_to {remote} {
set w [console::new \
- "push $remote" \
- "Pushing changes to $remote"]
+ [mc "push %s" $remote] \
+ [mc "Pushing changes to %s" $remote]]
set cmd [list git push]
lappend cmd -v
lappend cmd $remote
@@ -32,6 +32,7 @@ proc push_to {remote} {
proc start_push_anywhere_action {w} {
global push_urltype push_remote push_url push_thin push_tags
+ global push_force
set r_url {}
switch -- $push_urltype {
@@ -45,6 +46,9 @@ proc start_push_anywhere_action {w} {
if {$push_thin} {
lappend cmd --thin
}
+ if {$push_force} {
+ lappend cmd --force
+ }
if {$push_tags} {
lappend cmd --tags
}
@@ -64,8 +68,8 @@ proc start_push_anywhere_action {w} {
}
set cons [console::new \
- "push $r_url" \
- "Pushing $cnt $unit to $r_url"]
+ [mc "push %s" $r_url] \
+ [mc "Pushing %s %s to %s" $cnt $unit $r_url]]
console::exec $cons $cmd
destroy $w
}
@@ -76,26 +80,27 @@ trace add variable push_remote write \
proc do_push_anywhere {} {
global all_remotes current_branch
global push_urltype push_remote push_url push_thin push_tags
+ global push_force
set w .push_setup
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
- label $w.header -text {Push Branches} -font font_uibold
+ label $w.header -text [mc "Push Branches"] -font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
- button $w.buttons.create -text Push \
+ button $w.buttons.create -text [mc Push] \
-default active \
-command [list start_push_anywhere_action $w]
pack $w.buttons.create -side right
- button $w.buttons.cancel -text {Cancel} \
+ button $w.buttons.cancel -text [mc "Cancel"] \
-default normal \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- labelframe $w.source -text {Source Branches}
+ labelframe $w.source -text [mc "Source Branches"]
listbox $w.source.l \
-height 10 \
-width 70 \
@@ -112,10 +117,10 @@ proc do_push_anywhere {} {
pack $w.source.l -side left -fill both -expand 1
pack $w.source -fill both -expand 1 -pady 5 -padx 5
- labelframe $w.dest -text {Destination Repository}
+ labelframe $w.dest -text [mc "Destination Repository"]
if {$all_remotes ne {}} {
radiobutton $w.dest.remote_r \
- -text {Remote:} \
+ -text [mc "Remote:"] \
-value remote \
-variable push_urltype
eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
@@ -130,7 +135,7 @@ proc do_push_anywhere {} {
set push_urltype url
}
radiobutton $w.dest.url_r \
- -text {Arbitrary URL:} \
+ -text [mc "Arbitrary URL:"] \
-value url \
-variable push_urltype
entry $w.dest.url_t \
@@ -150,25 +155,30 @@ proc do_push_anywhere {} {
grid columnconfigure $w.dest 1 -weight 1
pack $w.dest -anchor nw -fill x -pady 5 -padx 5
- labelframe $w.options -text {Transfer Options}
+ labelframe $w.options -text [mc "Transfer Options"]
+ checkbutton $w.options.force \
+ -text [mc "Force overwrite existing branch (may discard changes)"] \
+ -variable push_force
+ grid $w.options.force -columnspan 2 -sticky w
checkbutton $w.options.thin \
- -text {Use thin pack (for slow network connections)} \
+ -text [mc "Use thin pack (for slow network connections)"] \
-variable push_thin
grid $w.options.thin -columnspan 2 -sticky w
checkbutton $w.options.tags \
- -text {Include tags} \
+ -text [mc "Include tags"] \
-variable push_tags
grid $w.options.tags -columnspan 2 -sticky w
grid columnconfigure $w.options 1 -weight 1
pack $w.options -anchor nw -fill x -pady 5 -padx 5
set push_url {}
+ set push_force 0
set push_thin 0
set push_tags 0
bind $w <Visibility> "grab $w; focus $w.buttons.create"
bind $w <Key-Escape> "destroy $w"
bind $w <Key-Return> [list start_push_anywhere_action $w]
- wm title $w "[appname] ([reponame]): Push"
+ wm title $w [append "[appname] ([reponame]): " [mc "Push"]]
tkwait window $w
}
diff --git a/git-gui/lib/win32.tcl b/git-gui/lib/win32.tcl
new file mode 100644
index 0000000000..d7f93d045d
--- /dev/null
+++ b/git-gui/lib/win32.tcl
@@ -0,0 +1,26 @@
+# git-gui Misc. native Windows 32 support
+# Copyright (C) 2007 Shawn Pearce
+
+proc win32_read_lnk {lnk_path} {
+ return [exec cscript.exe \
+ /E:jscript \
+ /nologo \
+ [file join $::oguilib win32_shortcut.js] \
+ $lnk_path]
+}
+
+proc win32_create_lnk {lnk_path lnk_exec lnk_dir} {
+ global oguilib
+
+ set lnk_args [lrange $lnk_exec 1 end]
+ set lnk_exec [lindex $lnk_exec 0]
+
+ eval [list exec wscript.exe \
+ /E:jscript \
+ /nologo \
+ [file join $oguilib win32_shortcut.js] \
+ $lnk_path \
+ [file join $oguilib git-gui.ico] \
+ $lnk_dir \
+ $lnk_exec] $lnk_args
+}
diff --git a/git-gui/lib/win32_shortcut.js b/git-gui/lib/win32_shortcut.js
new file mode 100644
index 0000000000..117923f886
--- /dev/null
+++ b/git-gui/lib/win32_shortcut.js
@@ -0,0 +1,34 @@
+// git-gui Windows shortcut support
+// Copyright (C) 2007 Shawn Pearce
+
+var WshShell = WScript.CreateObject("WScript.Shell");
+var argv = WScript.Arguments;
+var argi = 0;
+var lnk_path = argv.item(argi++);
+var ico_path = argi < argv.length ? argv.item(argi++) : undefined;
+var dir_path = argi < argv.length ? argv.item(argi++) : undefined;
+var lnk_exec = argi < argv.length ? argv.item(argi++) : undefined;
+var lnk_args = '';
+while (argi < argv.length) {
+ var s = argv.item(argi++);
+ if (lnk_args != '')
+ lnk_args += ' ';
+ if (s.indexOf(' ') >= 0) {
+ lnk_args += '"';
+ lnk_args += s;
+ lnk_args += '"';
+ } else {
+ lnk_args += s;
+ }
+}
+
+var lnk = WshShell.CreateShortcut(lnk_path);
+if (argv.length == 1) {
+ WScript.echo(lnk.TargetPath);
+} else {
+ lnk.TargetPath = lnk_exec;
+ lnk.Arguments = lnk_args;
+ lnk.IconLocation = ico_path + ", 0";
+ lnk.WorkingDirectory = dir_path;
+ lnk.Save();
+}
diff --git a/git-gui/macosx/AppMain.tcl b/git-gui/macosx/AppMain.tcl
new file mode 100644
index 0000000000..41ca08e2b7
--- /dev/null
+++ b/git-gui/macosx/AppMain.tcl
@@ -0,0 +1,22 @@
+set gitexecdir {@@gitexecdir@@}
+set gitguilib {@@GITGUI_LIBDIR@@}
+set env(PATH) "$gitexecdir:$env(PATH)"
+
+if {[string first -psn [lindex $argv 0]] == 0} {
+ lset argv 0 [file join $gitexecdir git-gui]
+}
+
+if {[file tail [lindex $argv 0]] eq {gitk}} {
+ set argv0 [file join $gitexecdir gitk]
+ set AppMain_source $argv0
+} else {
+ set argv0 [file join $gitexecdir [file tail [lindex $argv 0]]]
+ set AppMain_source [file join $gitguilib git-gui.tcl]
+ if {[pwd] eq {/}} {
+ cd $env(HOME)
+ }
+}
+
+unset gitexecdir gitguilib
+set argv [lrange $argv 1 end]
+source $AppMain_source
diff --git a/git-gui/macosx/Info.plist b/git-gui/macosx/Info.plist
new file mode 100644
index 0000000000..b3bf15fa1c
--- /dev/null
+++ b/git-gui/macosx/Info.plist
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>@@GITGUI_TKEXECUTABLE@@</string>
+ <key>CFBundleGetInfoString</key>
+ <string>Git Gui @@GITGUI_VERSION@@ © 2006-2007 Shawn Pearce, et. al.</string>
+ <key>CFBundleIconFile</key>
+ <string>git-gui.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>cz.or.repo.git-gui</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>Git Gui</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>@@GITGUI_VERSION@@</string>
+ <key>CFBundleSignature</key>
+ <string>GITg</string>
+ <key>CFBundleVersion</key>
+ <string>@@GITGUI_VERSION@@</string>
+</dict>
+</plist>
diff --git a/git-gui/macosx/git-gui.icns b/git-gui/macosx/git-gui.icns
new file mode 100644
index 0000000000..77d88a77a7
--- /dev/null
+++ b/git-gui/macosx/git-gui.icns
Binary files differ
diff --git a/git-gui/po/.gitignore b/git-gui/po/.gitignore
new file mode 100644
index 0000000000..a89cf44969
--- /dev/null
+++ b/git-gui/po/.gitignore
@@ -0,0 +1,2 @@
+*.msg
+*~
diff --git a/git-gui/po/README b/git-gui/po/README
new file mode 100644
index 0000000000..9d8b7364fd
--- /dev/null
+++ b/git-gui/po/README
@@ -0,0 +1,209 @@
+Localizing git-gui for your language
+====================================
+
+This short note is to help you, who reads and writes English and your
+own language, help us getting git-gui localized for more languages. It
+does not try to be a comprehensive manual of GNU gettext, which is the
+i18n framework we use, but tries to help you get started by covering the
+basics and how it is used in this project.
+
+1. Getting started.
+
+You would first need to have a working "git". Your distribution may
+have it as "git-core" package (do not get "GNU Interactive Tools" --
+that is a different "git"). You would also need GNU gettext toolchain
+to test the resulting translation out. Although you can work on message
+translation files with a regular text editor, it is a good idea to have
+specialized so-called "po file editors" (e.g. emacs po-mode, KBabel,
+poedit, GTranslator --- any of them would work well). Please install
+them.
+
+You would then need to clone the git-gui internationalization project
+repository, so that you can work on it:
+
+ $ git clone mob@repo.or.cz:/srv/git/git-gui/git-gui-i18n.git/
+ $ cd git-gui-i18n
+ $ git checkout --track -b mob origin/mob
+ $ git config remote.origin.push mob
+
+The "git checkout" command creates a 'mob' branch from upstream's
+corresponding branch and makes it your current branch. You will be
+working on this branch.
+
+The "git config" command records in your repository configuration file
+that you would push "mob" branch to the upstream when you say "git
+push".
+
+
+2. Starting a new language.
+
+In the git-gui-i18n directory is a po/ subdirectory. It has a
+handful files whose names end with ".po". Is there a file that has
+messages in your language?
+
+If you do not know what your language should be named, you need to find
+it. This currently follows ISO 639-1 two letter codes:
+
+ http://www.loc.gov/standards/iso639-2/php/code_list.php
+
+For example, if you are preparing a translation for Afrikaans, the
+language code is "af". If there already is a translation for your
+language, you do not have to perform any step in this section, but keep
+reading, because we are covering the basics.
+
+If you did not find your language, you would need to start one yourself.
+Copy po/git-gui.pot file to po/af.po (replace "af" with the code for
+your language). Edit the first several lines to match existing *.po
+files to make it clear this is a translation table for git-gui project,
+and you are the primary translator. The result of your editing would
+look something like this:
+
+ # Translation of git-gui to Afrikaans
+ # Copyright (C) 2007 Shawn Pearce
+ # This file is distributed under the same license as the git-gui package.
+ # YOUR NAME <YOUR@E-MAIL.ADDRESS>, 2007.
+ #
+ #, fuzzy
+ msgid ""
+ msgstr ""
+ "Project-Id-Version: git-gui\n"
+ "Report-Msgid-Bugs-To: \n"
+ "POT-Creation-Date: 2007-07-24 22:19+0300\n"
+ "PO-Revision-Date: 2007-07-25 18:00+0900\n"
+ "Last-Translator: YOUR NAME <YOUR@E-MAIL.ADDRESS>\n"
+ "Language-Team: Afrikaans\n"
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=UTF-8\n"
+ "Content-Transfer-Encoding: 8bit\n"
+
+You will find many pairs of a "msgid" line followed by a "msgstr" line.
+These pairs define how messages in git-gui application are translated to
+your language. Your primarily job is to fill in the empty double quote
+pairs on msgstr lines with the translation of the strings on their
+matching msgid lines. A few tips:
+
+ - Control characters, such as newlines, are written in backslash
+ sequence similar to string literals in the C programming language.
+ When the string given on a msgid line has such a backslash sequence,
+ you would typically want to have corresponding ones in the string on
+ your msgstr line.
+
+ - Some messages contain an optional context indicator at the end,
+ for example "@@noun" or "@@verb". This indicator allows the
+ software to select the correct translation depending upon the use.
+ The indicator is not actually part of the message and will not
+ be shown to the end-user.
+
+ If your language does not require a different translation you
+ will still need to translate both messages.
+
+ - Often the messages being translated are format strings given to
+ "printf()"-like functions. Make sure "%s", "%d", and "%%" in your
+ translated messages match the original.
+
+ When you have to change the order of words, you can add "<number>\$"
+ between '%' and the conversion ('s', 'd', etc.) to say "<number>-th
+ parameter to the format string is used at this point". For example,
+ if the original message is like this:
+
+ "Length is %d, Weight is %d"
+
+ and if for whatever reason your translation needs to say weight first
+ and then length, you can say something like:
+
+ "WEIGHT IS %2\$d, LENGTH IS %1\$d"
+
+ The reason you need a backslash before dollar sign is because
+ this is a double quoted string in Tcl language, and without
+ it the letter introduces a variable interpolation, which you
+ do not want here.
+
+ - A long message can be split across multiple lines by ending the
+ string with a double quote, and starting another string on the next
+ line with another double quote. They will be concatenated in the
+ result. For example:
+
+ #: 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 ""
+ "HERE YOU WILL WRITE YOUR TRANSLATION OF THE ABOVE LONG "
+ "MESSAGE IN YOUR LANGUAGE."
+
+You can test your translation by running "make install", which would
+create po/af.msg file and installs the result, and then running the
+resulting git-gui under your locale:
+
+ $ make install
+ $ LANG=af git-gui
+
+There is a trick to test your translation without first installing:
+
+ $ make
+ $ LANG=af ./git-gui.sh
+
+When you are satisfied with your translation, commit your changes, and
+push it back to the 'mob' branch:
+
+ $ edit po/af.po
+ ... be sure to update Last-Translator: and
+ ... PO-Revision-Date: lines.
+ $ git add po/af.po
+ $ git commit -m 'Started Afrikaans translation.'
+ $ git push
+
+
+3. Updating your translation.
+
+There may already be a translation for your language, and you may want
+to contribute an update. This may be because you would want to improve
+the translation of existing messages, or because the git-gui software
+itself was updated and there are new messages that need translation.
+
+In any case, make sure you are up-to-date before starting your work:
+
+ $ git pull
+
+In the former case, you will edit po/af.po (again, replace "af" with
+your language code), and after testing and updating the Last-Translator:
+and PO-Revision-Date: lines, "add/commit/push" as in the previous
+section.
+
+By comparing "POT-Creation-Date:" line in po/git-gui.pot file and
+po/af.po file, you can tell if there are new messages that need to be
+translated. You would need the GNU gettext package to perform this
+step.
+
+ $ msgmerge -U po/af.po po/git-gui.pot
+
+[NEEDSWORK: who is responsible for updating po/git-gui.pot file by
+running xgettext? IIRC, Christian recommended against running it
+nilly-willy because it can become a source of unnecessary merge
+conflicts. Perhaps we should mention something like "
+
+The po/git-gui.pot file is updated by the internationalization
+coordinator from time to time. You _could_ update it yourself, but
+translators are discouraged from doing so because we would want all
+language teams to be working off of the same version of git-gui.pot.
+
+" here?]
+
+This updates po/af.po (again, replace "af" with your language
+code) so that it contains msgid lines (i.e. the original) that
+your translation did not have before. There are a few things to
+watch out for:
+
+ - The original text in English of an older message you already
+ translated might have been changed. You will notice a comment line
+ that begins with "#, fuzzy" in front of such a message. msgmerge
+ tool made its best effort to match your old translation with the
+ message from the updated software, but you may find cases that it
+ matched your old translated message to a new msgid and the pairing
+ does not make any sense -- you would need to fix them, and then
+ remove the "#, fuzzy" line from the message (your fixed translation
+ of the message will not be used before you remove the marker).
+
+ - New messages added to the software will have msgstr lines with empty
+ strings. You would need to translate them.
diff --git a/git-gui/po/de.po b/git-gui/po/de.po
new file mode 100644
index 0000000000..d7c38f9c73
--- /dev/null
+++ b/git-gui/po/de.po
@@ -0,0 +1,1952 @@
+# Translation of git-gui to German.
+# Copyright (C) 2007 Shawn Pearce, et al.
+# This file is distributed under the same license as the git package.
+# Christian Stimming <stimming@tuhh.de>, 2007
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-02-02 10:14+0100\n"
+"PO-Revision-Date: 2008-02-02 10:18+0100\n"
+"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
+"Language-Team: German\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:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714
+#: git-gui.sh:733
+msgid "git-gui: fatal error"
+msgstr "git-gui: Programmfehler"
+
+#: git-gui.sh:565
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "Ungültige Zeichensatz-Angabe in %s:"
+
+#: git-gui.sh:590
+msgid "Main Font"
+msgstr "Programmschriftart"
+
+#: git-gui.sh:591
+msgid "Diff/Console Font"
+msgstr "Vergleich-Schriftart"
+
+#: git-gui.sh:605
+msgid "Cannot find git in PATH."
+msgstr "Git kann im PATH nicht gefunden werden."
+
+#: git-gui.sh:632
+msgid "Cannot parse Git version string:"
+msgstr "Git Versionsangabe kann nicht erkannt werden:"
+
+#: git-gui.sh:650
+#, 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 ""
+"Die Version von Git kann nicht bestimmt werden.\n"
+"\n"
+"»%s« behauptet, es sei Version »%s«.\n"
+"\n"
+"%s benötigt mindestens Git 1.5.0 oder höher.\n"
+"\n"
+"Soll angenommen werden, »%s« sei Version 1.5.0?\n"
+
+#: git-gui.sh:888
+msgid "Git directory not found:"
+msgstr "Git-Verzeichnis nicht gefunden:"
+
+#: git-gui.sh:895
+msgid "Cannot move to top of working directory:"
+msgstr ""
+"Es konnte nicht in das oberste Verzeichnis der Arbeitskopie gewechselt "
+"werden:"
+
+#: git-gui.sh:902
+msgid "Cannot use funny .git directory:"
+msgstr "Unerwartete Struktur des .git Verzeichnis:"
+
+#: git-gui.sh:907
+msgid "No working directory"
+msgstr "Kein Arbeitsverzeichnis"
+
+#: git-gui.sh:1054
+msgid "Refreshing file status..."
+msgstr "Dateistatus aktualisieren..."
+
+#: git-gui.sh:1119
+msgid "Scanning for modified files ..."
+msgstr "Nach geänderten Dateien suchen..."
+
+#: git-gui.sh:1294 lib/browser.tcl:245
+msgid "Ready."
+msgstr "Bereit."
+
+#: git-gui.sh:1560
+msgid "Unmodified"
+msgstr "Unverändert"
+
+#: git-gui.sh:1562
+msgid "Modified, not staged"
+msgstr "Verändert, nicht bereitgestellt"
+
+#: git-gui.sh:1563 git-gui.sh:1568
+msgid "Staged for commit"
+msgstr "Bereitgestellt zum Eintragen"
+
+#: git-gui.sh:1564 git-gui.sh:1569
+msgid "Portions staged for commit"
+msgstr "Teilweise bereitgestellt zum Eintragen"
+
+#: git-gui.sh:1565 git-gui.sh:1570
+msgid "Staged for commit, missing"
+msgstr "Bereitgestellt zum Eintragen, fehlend"
+
+#: git-gui.sh:1567
+msgid "Untracked, not staged"
+msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt"
+
+#: git-gui.sh:1572
+msgid "Missing"
+msgstr "Fehlend"
+
+#: git-gui.sh:1573
+msgid "Staged for removal"
+msgstr "Bereitgestellt zum Löschen"
+
+#: git-gui.sh:1574
+msgid "Staged for removal, still present"
+msgstr "Bereitgestellt zum Löschen, trotzdem vorhanden"
+
+#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579
+msgid "Requires merge resolution"
+msgstr "Konfliktauflösung nötig"
+
+#: git-gui.sh:1614
+msgid "Starting gitk... please wait..."
+msgstr "Gitk wird gestartet... bitte warten."
+
+#: git-gui.sh:1623
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+"Gitk kann nicht gestartet werden:\n"
+"\n"
+"%s existiert nicht"
+
+#: git-gui.sh:1823 lib/choose_repository.tcl:35
+msgid "Repository"
+msgstr "Projektarchiv"
+
+#: git-gui.sh:1824
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: git-gui.sh:1826 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr "Zweig"
+
+#: git-gui.sh:1829 lib/choose_rev.tcl:547
+msgid "Commit@@noun"
+msgstr "Version"
+
+#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "Zusammenführen"
+
+#: git-gui.sh:1833 lib/choose_rev.tcl:556
+msgid "Remote"
+msgstr "Andere Archive"
+
+#: git-gui.sh:1842
+msgid "Browse Current Branch's Files"
+msgstr "Aktuellen Zweig durchblättern"
+
+#: git-gui.sh:1846
+msgid "Browse Branch Files..."
+msgstr "Einen Zweig durchblättern..."
+
+#: git-gui.sh:1851
+msgid "Visualize Current Branch's History"
+msgstr "Aktuellen Zweig darstellen"
+
+#: git-gui.sh:1855
+msgid "Visualize All Branch History"
+msgstr "Alle Zweige darstellen"
+
+#: git-gui.sh:1862
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "Zweig »%s« durchblättern"
+
+#: git-gui.sh:1864
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "Historie von »%s« darstellen"
+
+#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "Datenbankstatistik"
+
+#: git-gui.sh:1872 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "Datenbank komprimieren"
+
+#: git-gui.sh:1875
+msgid "Verify Database"
+msgstr "Datenbank überprüfen"
+
+#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr "Desktop-Icon erstellen"
+
+#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
+msgid "Quit"
+msgstr "Beenden"
+
+#: git-gui.sh:1902
+msgid "Undo"
+msgstr "Rückgängig"
+
+#: git-gui.sh:1905
+msgid "Redo"
+msgstr "Wiederholen"
+
+#: git-gui.sh:1909 git-gui.sh:2403
+msgid "Cut"
+msgstr "Ausschneiden"
+
+#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr "Kopieren"
+
+#: git-gui.sh:1915 git-gui.sh:2409
+msgid "Paste"
+msgstr "Einfügen"
+
+#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "Löschen"
+
+#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69
+msgid "Select All"
+msgstr "Alle auswählen"
+
+#: git-gui.sh:1931
+msgid "Create..."
+msgstr "Erstellen..."
+
+#: git-gui.sh:1937
+msgid "Checkout..."
+msgstr "Umstellen..."
+
+#: git-gui.sh:1943
+msgid "Rename..."
+msgstr "Umbenennen..."
+
+#: git-gui.sh:1948 git-gui.sh:2048
+msgid "Delete..."
+msgstr "Löschen..."
+
+#: git-gui.sh:1953
+msgid "Reset..."
+msgstr "Zurücksetzen..."
+
+#: git-gui.sh:1965 git-gui.sh:2350
+msgid "New Commit"
+msgstr "Neue Version"
+
+#: git-gui.sh:1973 git-gui.sh:2357
+msgid "Amend Last Commit"
+msgstr "Letzte nachbessern"
+
+#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "Neu laden"
+
+#: git-gui.sh:1988
+msgid "Stage To Commit"
+msgstr "Zum Eintragen bereitstellen"
+
+#: git-gui.sh:1994
+msgid "Stage Changed Files To Commit"
+msgstr "Geänderte Dateien bereitstellen"
+
+#: git-gui.sh:2000
+msgid "Unstage From Commit"
+msgstr "Aus der Bereitstellung herausnehmen"
+
+#: git-gui.sh:2005 lib/index.tcl:393
+msgid "Revert Changes"
+msgstr "Änderungen verwerfen"
+
+#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427
+msgid "Sign Off"
+msgstr "Abzeichnen"
+
+#: git-gui.sh:2016 git-gui.sh:2333
+msgid "Commit@@verb"
+msgstr "Eintragen"
+
+#: git-gui.sh:2027
+msgid "Local Merge..."
+msgstr "Lokales Zusammenführen..."
+
+#: git-gui.sh:2032
+msgid "Abort Merge..."
+msgstr "Zusammenführen abbrechen..."
+
+#: git-gui.sh:2044
+msgid "Push..."
+msgstr "Versenden..."
+
+#: git-gui.sh:2055 lib/choose_repository.tcl:40
+msgid "Apple"
+msgstr "Apple"
+
+#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
+#, tcl-format
+msgid "About %s"
+msgstr "Ãœber %s"
+
+#: git-gui.sh:2062
+msgid "Preferences..."
+msgstr "Einstellungen..."
+
+#: git-gui.sh:2070 git-gui.sh:2595
+msgid "Options..."
+msgstr "Optionen..."
+
+#: git-gui.sh:2076 lib/choose_repository.tcl:46
+msgid "Help"
+msgstr "Hilfe"
+
+#: git-gui.sh:2117
+msgid "Online Documentation"
+msgstr "Online-Dokumentation"
+
+#: git-gui.sh:2201
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr ""
+"Fehler: Verzeichnis »%s« kann nicht gelesen werden: Datei oder Verzeichnis "
+"nicht gefunden"
+
+#: git-gui.sh:2234
+msgid "Current Branch:"
+msgstr "Aktueller Zweig:"
+
+#: git-gui.sh:2255
+msgid "Staged Changes (Will Commit)"
+msgstr "Bereitstellung (zum Eintragen)"
+
+#: git-gui.sh:2274
+msgid "Unstaged Changes"
+msgstr "Nicht bereitgestellte Änderungen"
+
+#: git-gui.sh:2323
+msgid "Stage Changed"
+msgstr "Alles bereitstellen"
+
+#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "Versenden"
+
+#: git-gui.sh:2369
+msgid "Initial Commit Message:"
+msgstr "Erste Versionsbeschreibung:"
+
+#: git-gui.sh:2370
+msgid "Amended Commit Message:"
+msgstr "Nachgebesserte Beschreibung:"
+
+#: git-gui.sh:2371
+msgid "Amended Initial Commit Message:"
+msgstr "Nachgebesserte erste Beschreibung:"
+
+#: git-gui.sh:2372
+msgid "Amended Merge Commit Message:"
+msgstr "Nachgebesserte Zusammenführungs-Beschreibung:"
+
+#: git-gui.sh:2373
+msgid "Merge Commit Message:"
+msgstr "Zusammenführungs-Beschreibung:"
+
+#: git-gui.sh:2374
+msgid "Commit Message:"
+msgstr "Versionsbeschreibung:"
+
+#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71
+msgid "Copy All"
+msgstr "Alle kopieren"
+
+#: git-gui.sh:2443 lib/blame.tcl:104
+msgid "File:"
+msgstr "Datei:"
+
+#: git-gui.sh:2573
+msgid "Apply/Reverse Hunk"
+msgstr "Kontext anwenden/umkehren"
+
+#: git-gui.sh:2579
+msgid "Show Less Context"
+msgstr "Weniger Zeilen anzeigen"
+
+#: git-gui.sh:2586
+msgid "Show More Context"
+msgstr "Mehr Zeilen anzeigen"
+
+#: git-gui.sh:2594
+msgid "Refresh"
+msgstr "Aktualisieren"
+
+#: git-gui.sh:2615
+msgid "Decrease Font Size"
+msgstr "Schriftgröße verkleinern"
+
+#: git-gui.sh:2619
+msgid "Increase Font Size"
+msgstr "Schriftgröße vergrößern"
+
+#: git-gui.sh:2630
+msgid "Unstage Hunk From Commit"
+msgstr "Kontext aus Bereitstellung herausnehmen"
+
+#: git-gui.sh:2604
+msgid "Stage Hunk For Commit"
+msgstr "Kontext zur Bereitstellung hinzufügen"
+
+#: git-gui.sh:2623
+msgid "Initializing..."
+msgstr "Initialisieren..."
+
+#: git-gui.sh:2718
+#, 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 ""
+"Möglicherweise gibt es Probleme mit manchen Umgebungsvariablen.\n"
+"\n"
+"Die folgenden Umgebungsvariablen können vermutlich nicht \n"
+"von %s an Git weitergegeben werden:\n"
+"\n"
+
+#: git-gui.sh:2748
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"Dies ist ein bekanntes Problem der Tcl-Version, die\n"
+"in Cygwin mitgeliefert wird."
+
+#: git-gui.sh:2753
+#, 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"
+"Um den Namen »%s« zu ändern, sollten Sie die \n"
+"gewünschten Werte für die Einstellung user.name und \n"
+"user.email in Ihre Datei ~/.gitconfig einfügen.\n"
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - eine grafische Oberfläche für Git."
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr "Datei-Browser"
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr "Version:"
+
+#: lib/blame.tcl:249
+msgid "Copy Commit"
+msgstr "Version kopieren"
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr "%s lesen..."
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr "Annotierungen für Kopieren/Verschieben werden geladen..."
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr "Zeilen annotiert"
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr "Annotierungen für ursprünglichen Ort werden geladen..."
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr "Annotierung vollständig."
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr "Annotierung laden..."
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr "Autor:"
+
+#: lib/blame.tcl:791
+msgid "Committer:"
+msgstr "Eintragender:"
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr "Ursprüngliche Datei:"
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr "Ursprünglich von:"
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr "In Datei:"
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr "Kopiert oder verschoben durch:"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "Auf Zweig umstellen"
+
+#: lib/branch_checkout.tcl:23
+msgid "Checkout"
+msgstr "Umstellen"
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr "Version"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+msgid "Options"
+msgstr "Optionen"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Ãœbernahmezweig anfordern"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "Verbindung zu lokalem Zweig lösen"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "Zweig erstellen"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "Neuen Zweig erstellen"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
+msgid "Create"
+msgstr "Erstellen"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "Zweigname"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr "Name:"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "Passend zu Ãœbernahmezweig-Name"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Anfangsversion"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Existierenden Zweig aktualisieren:"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Nein"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Nur Schnellzusammenführung"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr "Zurücksetzen"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Arbeitskopie umstellen nach Erstellen"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "Bitte wählen Sie einen Übernahmezweig."
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr "Übernahmezweig »%s« ist kein Zweig im anderen Projektarchiv."
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr "Bitte geben Sie einen Zweignamen an."
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "»%s« ist kein zulässiger Zweigname."
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr "Zweig löschen"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "Lokalen Zweig löschen"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "Lokale Zweige"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "Nur löschen, wenn darin zusammengeführt"
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr "Immer (ohne Zusammenführungstest)"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+"Gelöschte Zweige können nur mit größerem Aufwand wiederhergestellt werden.\n"
+"\n"
+"Gewählte Zweige jetzt löschen?"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Fehler beim Löschen der Zweige:\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "Zweig umbenennen"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "Umbenennen"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "Zweig:"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "Neuer Name:"
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr "Bitte wählen Sie einen Zweig zum umbenennen."
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "Zweig »%s« existiert bereits."
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Fehler beim Umbenennen von »%s«."
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Starten..."
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "Datei-Browser"
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr "%s laden..."
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr "[Nach oben]"
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+msgid "Browse Branch Files"
+msgstr "Dateien des Zweigs durchblättern"
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
+msgid "Browse"
+msgstr "Blättern"
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Änderungen »%s« von »%s« anfordern"
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden"
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr "Schließen"
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "Zweig »%s« existiert nicht."
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"Zweig »%s« existiert bereits.\n"
+"\n"
+"Zweig kann nicht mit »%s« schnellzusammengeführt werden. Reguläres "
+"Zusammenführen ist notwendig."
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "Zusammenführungsmethode »%s« nicht unterstützt."
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "Aktualisieren von »%s« fehlgeschlagen."
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr "Bereitstellung (»index«) ist zur Bearbeitung gesperrt (»locked«)."
+
+#: lib/checkout_op.tcl:266
+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 ""
+"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"\n"
+"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
+"geändert. Vor dem Wechseln des lokalen Zweigs muss neu geladen werden.\n"
+"\n"
+"Es wird gleich neu geladen.\n"
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "Arbeitskopie umstellen auf »%s«..."
+
+#: lib/checkout_op.tcl:323
+msgid "files checked out"
+msgstr "Dateien aktualisiert"
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr ""
+"Auf Zweig »%s« umstellen abgebrochen (Zusammenführen der Dateien ist "
+"notwendig)."
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr "Zusammenführen der Dateien ist notwendig."
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Es wird auf Zweig »%s« verblieben."
+
+#: lib/checkout_op.tcl:429
+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 ""
+"Die Arbeitskopie ist nicht auf einem lokalen Zweig.\n"
+"\n"
+"Wenn Sie auf einem Zweig arbeiten möchten, erstellen Sie bitte jetzt einen "
+"Zweig mit der Auswahl »Abgetrennte Arbeitskopie-Version«."
+
+#: lib/checkout_op.tcl:446
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "Umgestellt auf »%s«."
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:"
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr ""
+"Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt "
+"werden."
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "»%s« zurücksetzen?"
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr "Darstellen"
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+"Lokaler Zweig kann nicht gesetzt werden.\n"
+"\n"
+"Diese Arbeitskopie ist nur teilweise umgestellt. Die Dateien sind korrekt "
+"aktualisiert, aber einige interne Git-Dateien konnten nicht geändert "
+"werden.\n"
+"\n"
+"Dies ist ein interner Programmfehler von %s. Programm wird jetzt abgebrochen."
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "Auswählen"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "Schriftfamilie"
+
+#: lib/choose_font.tcl:73
+msgid "Font Size"
+msgstr "Schriftgröße"
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr "Schriftbeispiel"
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"Dies ist ein Beispieltext.\n"
+"Wenn Ihnen dieser Text gefällt, sollten Sie diese Schriftart wählen."
+
+#: lib/choose_repository.tcl:27
+msgid "Git Gui"
+msgstr "Git Gui"
+
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
+msgid "Create New Repository"
+msgstr "Neues Projektarchiv"
+
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr "Neu..."
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
+msgid "Clone Existing Repository"
+msgstr "Projektarchiv klonen"
+
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr "Klonen..."
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
+msgid "Open Existing Repository"
+msgstr "Projektarchiv öffnen"
+
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr "Öffnen..."
+
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr "Zuletzt benutzte Projektarchive"
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr "Zuletzt benutztes Projektarchiv öffnen:"
+
+#: lib/choose_repository.tcl:294
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "Projektarchiv »%s« existiert bereits."
+
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Projektarchiv »%s« konnte nicht erstellt werden:"
+
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
+msgid "Directory:"
+msgstr "Verzeichnis:"
+
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
+msgid "Git Repository"
+msgstr "Git Projektarchiv"
+
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "Verzeichnis »%s« existiert bereits."
+
+#: lib/choose_repository.tcl:442
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Datei »%s« existiert bereits."
+
+#: lib/choose_repository.tcl:463
+msgid "Clone"
+msgstr "Klonen"
+
+#: lib/choose_repository.tcl:476
+msgid "URL:"
+msgstr "URL:"
+
+#: lib/choose_repository.tcl:496
+msgid "Clone Type:"
+msgstr "Art des Klonens:"
+
+#: lib/choose_repository.tcl:502
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Standard (schnell, teilweise redundant, Hardlinks)"
+
+#: lib/choose_repository.tcl:508
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Alles kopieren (langsamer, volle Redundanz)"
+
+#: lib/choose_repository.tcl:514
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)"
+
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Kein Git-Projektarchiv in »%s« gefunden."
+
+#: lib/choose_repository.tcl:586
+msgid "Standard only available for local repository."
+msgstr "Standard ist nur für lokale Projektarchive verfügbar."
+
+#: lib/choose_repository.tcl:590
+msgid "Shared only available for local repository."
+msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar."
+
+#: lib/choose_repository.tcl:617
+msgid "Failed to configure origin"
+msgstr "Der Ursprungsort konnte nicht eingerichtet werden"
+
+#: lib/choose_repository.tcl:629
+msgid "Counting objects"
+msgstr "Objekte werden gezählt"
+
+#: lib/choose_repository.tcl:630
+msgid "buckets"
+msgstr "Buckets"
+
+#: lib/choose_repository.tcl:654
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Kopien von Objekten/Info/Alternates konnten nicht erstellt werden: %s"
+
+#: lib/choose_repository.tcl:690
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Von »%s« konnte nichts geklont werden."
+
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
+msgid "The 'master' branch has not been initialized."
+msgstr "Der »master«-Zweig wurde noch nicht initialisiert."
+
+#: lib/choose_repository.tcl:705
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr "Hardlinks nicht verfügbar. Stattdessen wird kopiert."
+
+#: lib/choose_repository.tcl:717
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Kopieren von »%s«"
+
+#: lib/choose_repository.tcl:748
+msgid "Copying objects"
+msgstr "Objektdatenbank kopieren"
+
+#: lib/choose_repository.tcl:749
+msgid "KiB"
+msgstr "KB"
+
+#: lib/choose_repository.tcl:773
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Objekt kann nicht kopiert werden: %s"
+
+#: lib/choose_repository.tcl:783
+msgid "Linking objects"
+msgstr "Objekte verlinken"
+
+#: lib/choose_repository.tcl:784
+msgid "objects"
+msgstr "Objekte"
+
+#: lib/choose_repository.tcl:792
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s"
+
+#: lib/choose_repository.tcl:847
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr ""
+"Zweige und Objekte konnten nicht angefordert werden. Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch tags. See console output for details."
+msgstr ""
+"Markierungen konnten nicht angefordert werden. Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
+
+#: lib/choose_repository.tcl:882
+msgid "Cannot determine HEAD. See console output for details."
+msgstr ""
+"Die Zweigspitze (HEAD) konnte nicht gefunden werden. Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
+
+#: lib/choose_repository.tcl:891
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "Verzeichnis »%s« kann nicht aufgeräumt werden."
+
+#: lib/choose_repository.tcl:897
+msgid "Clone failed."
+msgstr "Klonen fehlgeschlagen."
+
+#: lib/choose_repository.tcl:904
+msgid "No default branch obtained."
+msgstr "Kein voreingestellter Zweig gefunden."
+
+#: lib/choose_repository.tcl:915
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "»%s« wurde nicht als Version gefunden."
+
+#: lib/choose_repository.tcl:927
+msgid "Creating working directory"
+msgstr "Arbeitskopie erstellen"
+
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr "Dateien"
+
+#: lib/choose_repository.tcl:957
+msgid "Initial file checkout failed."
+msgstr "Erstellen der Arbeitskopie fehlgeschlagen."
+
+#: lib/choose_repository.tcl:973
+msgid "Open"
+msgstr "Öffnen"
+
+#: lib/choose_repository.tcl:983
+msgid "Repository:"
+msgstr "Projektarchiv:"
+
+#: lib/choose_repository.tcl:1033
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Projektarchiv »%s« konnte nicht geöffnet werden."
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr "Abgetrennte Arbeitskopie-Version"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Version Regexp-Ausdruck:"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "Lokaler Zweig"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "Ãœbernahmezweig"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr "Markierung"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Ungültige Version: %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "Keine Version ausgewählt."
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "Versions-Ausdruck ist leer."
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr "Aktualisiert"
+
+#: lib/choose_rev.tcl:558
+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 ""
+"Keine Version zur Nachbesserung vorhanden.\n"
+"\n"
+"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende "
+"Version, die Sie nachbessern könnten.\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 ""
+"Nachbesserung währen Zusammenführung nicht möglich.\n"
+"\n"
+"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht "
+"beendet. Sie können keine vorige Übertragung nachbessern, solange eine "
+"unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
+"beenden oder abbrechen.\n"
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:"
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr "Benutzername konnte nicht bestimmt werden:"
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Ungültiger Wert von GIT_COMMITTER_INDENT:"
+
+#: lib/commit.tcl:133
+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 ""
+"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"\n"
+"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
+"geändert. Vor dem Eintragen einer neuen Version muss neu geladen werden.\n"
+"\n"
+"Es wird gleich neu geladen.\n"
+
+#: lib/commit.tcl:154
+#, 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 ""
+"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n"
+"\n"
+"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
+"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Unbekannter Dateizustand »%s«.\n"
+"\n"
+"Datei »%s« kann nicht eingetragen werden.\n"
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Keine Änderungen vorhanden, die eingetragen werden könnten.\n"
+"\n"
+"Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n"
+
+#: lib/commit.tcl:183
+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 ""
+"Bitte geben Sie eine Versionsbeschreibung ein.\n"
+"\n"
+"Eine gute Versionsbeschreibung enthält folgende Abschnitte:\n"
+"\n"
+"- Erste Zeile: Eine Zusammenfassung, was man gemacht hat.\n"
+"\n"
+"- Zweite Zeile: Leerzeile\n"
+"\n"
+"- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n"
+
+#: lib/commit.tcl:207
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht."
+
+#: lib/commit.tcl:221
+msgid "Calling pre-commit hook..."
+msgstr ""
+
+#: lib/commit.tcl:236
+msgid "Commit declined by pre-commit hook."
+msgstr ""
+
+#: lib/commit.tcl:259
+msgid "Calling commit-msg hook..."
+msgstr ""
+
+#: lib/commit.tcl:274
+msgid "Commit declined by commit-msg hook."
+msgstr ""
+
+#: lib/commit.tcl:287
+msgid "Committing changes..."
+msgstr "Änderungen eintragen..."
+
+#: lib/commit.tcl:303
+msgid "write-tree failed:"
+msgstr "write-tree fehlgeschlagen:"
+
+#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368
+msgid "Commit failed."
+msgstr "Eintragen fehlgeschlagen."
+
+#: lib/commit.tcl:321
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "Version »%s« scheint beschädigt zu sein"
+
+#: lib/commit.tcl:279
+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 ""
+"Keine Änderungen einzutragen.\n"
+"\n"
+"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts "
+"zusammengeführt.\n"
+"\n"
+"Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr "Keine Änderungen, die eingetragen werden können."
+
+#: lib/commit.tcl:347
+msgid "commit-tree failed:"
+msgstr "commit-tree fehlgeschlagen:"
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr "update-ref fehlgeschlagen:"
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Version %s übertragen: %s"
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr "Verarbeitung. Bitte warten..."
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr "Erfolgreich"
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr "Fehler: Kommando fehlgeschlagen"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "Anzahl unverknüpfter Objekte"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "Festplattenplatz von unverknüpften Objekten"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "Anzahl komprimierter Objekte"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "Anzahl Komprimierungseinheiten"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "Festplattenplatz von komprimierten Objekten"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "Komprimierte Objekte, die zum Entfernen vorgesehen sind"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "Dateien im Mülleimer"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Objektdatenbank komprimieren"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Die Objektdatenbank durch »fsck-objects« überprüfen lassen"
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n"
+"\n"
+"Für eine optimale Performance wird empfohlen, die Datenbank des "
+"Projektarchivs zu komprimieren, sobald mehr als %i nicht verknüpfte Objekte "
+"vorliegen.\n"
+"\n"
+"Soll die Datenbank jetzt komprimiert werden?"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Ungültiges Datum von Git: %s"
+
+#: lib/diff.tcl:42
+#, 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 ""
+"Keine Änderungen feststellbar.\n"
+"\n"
+"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei "
+"von einem anderen Programm modifiziert, aber der Inhalt der Datei ist "
+"unverändert.\n"
+"\n"
+"Das Arbeitsverzeichnis wird jetzt neu geladen, um diese Änderung bei allen "
+"Dateien zu prüfen."
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Vergleich von »%s« laden..."
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Datei »%s« kann nicht angezeigt werden"
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr "Fehler beim Laden der Datei:"
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr "Git-Projektarchiv (Unterprojekt)"
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr "* Binärdatei (Inhalt wird nicht angezeigt)"
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr "Fehler beim Laden des Vergleichs:"
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr ""
+"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr "Fehler"
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr "Warnung"
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr ""
+"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können."
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Bereitstellung kann nicht wieder freigegeben werden."
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "Fehler in Bereitstellung"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine "
+"Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu "
+"synchronisieren."
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "Fortsetzen"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "Bereitstellung freigeben"
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
+
+#: lib/index.tcl:313
+msgid "Ready to commit."
+msgstr "Bereit zum Eintragen."
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr "»%s« hinzufügen..."
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Änderungen in Datei »%s« verwerfen?"
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
+
+#: lib/index.tcl:389
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+"Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen."
+
+#: lib/index.tcl:392
+msgid "Do Nothing"
+msgstr "Nichts tun"
+
+#: 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 ""
+"Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n"
+"\n"
+"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie "
+"zusammenführen können.\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 ""
+"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"\n"
+"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
+"geändert. Vor einem Zusammenführen muss neu geladen werden.\n"
+"\n"
+"Es wird gleich neu geladen.\n"
+
+#: lib/merge.tcl:44
+#, 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 ""
+"Zusammenführung mit Konflikten.\n"
+"\n"
+"Die Datei »%s« enthält Konflikte beim Zusammenführen. Sie müssen diese "
+"Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder "
+"bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst "
+"danach kann eine neue Zusammenführung begonnen werden.\n"
+
+#: lib/merge.tcl:54
+#, 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 ""
+"Es liegen Änderungen vor.\n"
+"\n"
+"Die Datei »%s« wurde geändert. Sie sollten zuerst die bereitgestellte "
+"Version abschließen, bevor Sie eine Zusammenführung beginnen. Mit dieser "
+"Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich "
+"einfacher beheben oder abbrechen.\n"
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s von %s"
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr "Zusammenführen von %s und %s"
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr "Zusammenführen erfolgreich abgeschlossen."
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Zusammenführen in %s"
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr "Zusammenzuführende Version"
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Abbruch der Nachbesserung ist nicht möglich.\n"
+"\n"
+"Sie müssen die Nachbesserung der Version abschließen.\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 ""
+"Zusammenführen abbrechen?\n"
+"\n"
+"Wenn Sie abbrechen, gehen alle noch nicht eingetragenen Änderungen "
+"verloren.\n"
+"\n"
+"Zusammenführen jetzt abbrechen?"
+
+#: 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 ""
+"Änderungen zurücksetzen?\n"
+"\n"
+"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen "
+"verloren.\n"
+"\n"
+"Änderungen jetzt zurücksetzen?"
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr "Abbruch"
+
+#: lib/merge.tcl:238
+msgid "files reset"
+msgstr "Dateien zurückgesetzt"
+
+#: lib/merge.tcl:265
+msgid "Abort failed."
+msgstr "Abbruch fehlgeschlagen."
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr "Abbruch durchgeführt. Bereit."
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr "Voreinstellungen wiederherstellen"
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr "Speichern"
+
+#: lib/option.tcl:96
+#, tcl-format
+msgid "%s Repository"
+msgstr "Projektarchiv %s"
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr "Global (Alle Projektarchive)"
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr "Benutzername"
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr "E-Mail-Adresse"
+
+#: lib/option.tcl:106
+msgid "Summarize Merge Commits"
+msgstr "Zusammenführungs-Versionen zusammenfassen"
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr "Ausführlichkeit der Zusammenführen-Meldungen"
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr "Vergleichsstatistik nach Zusammenführen anzeigen"
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr "Auf Dateiänderungsdatum verlassen"
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr "Übernahmezweige entfernen während Anforderung"
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr "Passend zu Ãœbernahmezweig"
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr "Anzahl der Kontextzeilen beim Vergleich"
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr "Namensvorschlag für neue Zweige"
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr "Schriftart ändern"
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr "%s wählen"
+
+#: lib/option.tcl:186
+msgid "pt."
+msgstr "pt."
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr "Einstellungen"
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr "Optionen konnten nicht gespeichert werden:"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr "Zweig aus anderem Projektarchiv löschen"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "Von Projektarchiv"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr "Anderes Archiv:"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr "Kommunikation mit URL:"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "Zweige"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "Löschen, falls"
+
+#: lib/remote_branch_delete.tcl:111
+msgid "Merged Into:"
+msgstr "Zusammenführen mit:"
+
+#: lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Immer (Keine Zusammenführungsprüfung)"
+
+#: lib/remote_branch_delete.tcl:152
+msgid "A branch is required for 'Merged Into'."
+msgstr "Für »Zusammenführen mit« muss ein Zweig angegeben werden."
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"Folgende Zweige sind noch nicht mit »%s« zusammengeführt:\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 ""
+"Ein oder mehrere Zusammenführungen sind fehlgeschlagen, da Sie nicht die "
+"notwendigen Versionen vorher angefordert haben. Sie sollten versuchen, "
+"zuerst von »%s« anzufordern."
+
+#: lib/remote_branch_delete.tcl:207
+msgid "Please select one or more branches to delete."
+msgstr "Bitte wählen Sie mindestens einen Zweig, der gelöscht werden soll."
+
+#: lib/remote_branch_delete.tcl:216
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
+"möglich.\n"
+"\n"
+"Sollen die ausgewählten Zweige gelöscht werden?"
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr "Zweige auf »%s« werden gelöscht"
+
+#: lib/remote_branch_delete.tcl:286
+msgid "No repository selected."
+msgstr "Kein Projektarchiv ausgewählt."
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "»%s« laden..."
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "Entfernen von"
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "Anfordern von"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "Versenden nach"
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "Fehler beim Schreiben der Verknüpfung:"
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr "Fehler beim Erstellen des Icons:"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s ... %*i von %*i %s (%3i%%)"
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "»%s« anfordern"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Neue Änderungen von »%s« holen"
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "Entfernen von »%s« aus anderem Archiv"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Übernahmezweige entfernen, die in »%s« gelöscht wurden"
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "»%s« versenden..."
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Änderungen nach »%s« versenden"
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "%s %s nach %s versenden"
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr "Zweige versenden"
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr "Herkunftszweige"
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr "Ziel-Projektarchiv"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr "Netzwerk-Einstellungen"
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr ""
+"Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)"
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)"
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr "Mit Markierungen übertragen"
diff --git a/git-gui/po/fr.po b/git-gui/po/fr.po
new file mode 100644
index 0000000000..d389bdaca1
--- /dev/null
+++ b/git-gui/po/fr.po
@@ -0,0 +1,1875 @@
+# translation of fr.po to French
+# Translation of git-gui to French.
+# Copyright (C) 2008 Shawn Pearce, et al.
+# This file is distributed under the same license as the git package.
+#
+# Christian Couder <chriscool@tuxfamily.org>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: fr\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-11-24 10:36+0100\n"
+"PO-Revision-Date: 2008-01-14 21:08+0100\n"
+"Last-Translator: Christian Couder <chriscool@tuxfamily.org>\n"
+"Language-Team: French\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: git-gui.sh:41 git-gui.sh:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714
+#: git-gui.sh:733
+msgid "git-gui: fatal error"
+msgstr "git-gui: erreur fatale"
+
+#: git-gui.sh:565
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "Invalide fonte spécifiée dans %s :"
+
+#: git-gui.sh:590
+msgid "Main Font"
+msgstr "Fonte principale"
+
+#: git-gui.sh:591
+msgid "Diff/Console Font"
+msgstr "Fonte diff/console"
+
+#: git-gui.sh:605
+msgid "Cannot find git in PATH."
+msgstr "Impossible de trouver git dans PATH."
+
+#: git-gui.sh:632
+msgid "Cannot parse Git version string:"
+msgstr "Impossible de parser la version de Git :"
+
+#: git-gui.sh:650
+#, 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 ""
+"Impossible de déterminer la version de Git.\n"
+"\n"
+"%s affirme qu'il s'agit de la version '%s'.\n"
+"\n"
+"%s nécessite au moins Git 1.5.0.\n"
+"\n"
+"Peut'on considérer que '%s' est en version 1.5.0 ?\n"
+
+#: git-gui.sh:888
+msgid "Git directory not found:"
+msgstr "Impossible de trouver le répertoire de Git :"
+
+#: git-gui.sh:895
+msgid "Cannot move to top of working directory:"
+msgstr "Impossible d'aller à la racine du répertoire de travail :"
+
+#: git-gui.sh:902
+msgid "Cannot use funny .git directory:"
+msgstr "Impossible d'utiliser un drôle de répertoire git :"
+
+#: git-gui.sh:907
+msgid "No working directory"
+msgstr "Pas de répertoire de travail"
+
+#: git-gui.sh:1054
+msgid "Refreshing file status..."
+msgstr "Rafraichissement du status des fichiers..."
+
+#: git-gui.sh:1119
+msgid "Scanning for modified files ..."
+msgstr "Recherche de fichiers modifiés..."
+
+#: git-gui.sh:1294 lib/browser.tcl:245
+msgid "Ready."
+msgstr "Prêt."
+
+#: git-gui.sh:1560
+msgid "Unmodified"
+msgstr "Non modifié"
+
+#: git-gui.sh:1562
+msgid "Modified, not staged"
+msgstr "Modifié, non pré-commité"
+
+#: git-gui.sh:1563 git-gui.sh:1568
+msgid "Staged for commit"
+msgstr "Pré-commité"
+
+#: git-gui.sh:1564 git-gui.sh:1569
+msgid "Portions staged for commit"
+msgstr "En partie pré-commité"
+
+#: git-gui.sh:1565 git-gui.sh:1570
+msgid "Staged for commit, missing"
+msgstr "Pré-commité, manquant"
+
+#: git-gui.sh:1567
+msgid "Untracked, not staged"
+msgstr "Non suivi, non pré-commité"
+
+#: git-gui.sh:1572
+msgid "Missing"
+msgstr "Manquant"
+
+#: git-gui.sh:1573
+msgid "Staged for removal"
+msgstr "Pré-commité pour suppression"
+
+#: git-gui.sh:1574
+msgid "Staged for removal, still present"
+msgstr "Pré-commité pour suppression, toujours présent"
+
+#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579
+msgid "Requires merge resolution"
+msgstr "Nécessite la résolution d'une fusion"
+
+#: git-gui.sh:1614
+msgid "Starting gitk... please wait..."
+msgstr "Lancement de gitk... merci de patienter..."
+
+#: git-gui.sh:1623
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+"Impossible de lancer gitk :\n"
+"\n"
+"%s inexistant"
+
+#: git-gui.sh:1823 lib/choose_repository.tcl:35
+msgid "Repository"
+msgstr "Référentiel"
+
+#: git-gui.sh:1824
+msgid "Edit"
+msgstr "Editer"
+
+#: git-gui.sh:1826 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr "Branche"
+
+#: git-gui.sh:1829 lib/choose_rev.tcl:547
+msgid "Commit@@noun"
+msgstr "Commit"
+
+#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "Fusionner"
+
+#: git-gui.sh:1833 lib/choose_rev.tcl:556
+msgid "Remote"
+msgstr "Référentiel distant"
+
+#: git-gui.sh:1842
+msgid "Browse Current Branch's Files"
+msgstr "Visionner fichiers dans branche courante"
+
+#: git-gui.sh:1846
+msgid "Browse Branch Files..."
+msgstr "Visionner fichiers de branche"
+
+#: git-gui.sh:1851
+msgid "Visualize Current Branch's History"
+msgstr "Visualiser historique branche courante"
+
+#: git-gui.sh:1855
+msgid "Visualize All Branch History"
+msgstr "Visualiser historique toutes branches"
+
+#: git-gui.sh:1862
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "Visionner fichiers de %s"
+
+#: git-gui.sh:1864
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "Visualiser historique de %s"
+
+#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "Statistiques base de donnée"
+
+#: git-gui.sh:1872 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "Comprimer base de donnée"
+
+#: git-gui.sh:1875
+msgid "Verify Database"
+msgstr "Vérifier base de donnée"
+
+#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr "Créer icône sur bureau"
+
+#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
+msgid "Quit"
+msgstr "Quitter"
+
+#: git-gui.sh:1902
+msgid "Undo"
+msgstr "Défaire"
+
+#: git-gui.sh:1905
+msgid "Redo"
+msgstr "Refaire"
+
+#: git-gui.sh:1909 git-gui.sh:2403
+msgid "Cut"
+msgstr "Couper"
+
+#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr "Copier"
+
+#: git-gui.sh:1915 git-gui.sh:2409
+msgid "Paste"
+msgstr "Coller"
+
+#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "Supprimer"
+
+#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69
+msgid "Select All"
+msgstr "Tout sélectionner"
+
+#: git-gui.sh:1931
+msgid "Create..."
+msgstr "Créer..."
+
+#: git-gui.sh:1937
+msgid "Checkout..."
+msgstr "Emprunter... "
+
+#: git-gui.sh:1943
+msgid "Rename..."
+msgstr "Renommer..."
+
+#: git-gui.sh:1948 git-gui.sh:2048
+msgid "Delete..."
+msgstr "Supprimer..."
+
+#: git-gui.sh:1953
+msgid "Reset..."
+msgstr "Réinitialiser..."
+
+#: git-gui.sh:1965 git-gui.sh:2350
+msgid "New Commit"
+msgstr "Nouveau commit"
+
+#: git-gui.sh:1973 git-gui.sh:2357
+msgid "Amend Last Commit"
+msgstr "Corriger dernier commit"
+
+#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "Resynchroniser"
+
+#: git-gui.sh:1988
+msgid "Stage To Commit"
+msgstr "Commiter un pré-commit"
+
+#: git-gui.sh:1994
+msgid "Stage Changed Files To Commit"
+msgstr "Commiter fichiers modifiés dans pré-commit"
+
+#: git-gui.sh:2000
+msgid "Unstage From Commit"
+msgstr "Commit vers pré-commit"
+
+#: git-gui.sh:2005 lib/index.tcl:393
+msgid "Revert Changes"
+msgstr "Inverser modification"
+
+#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427
+msgid "Sign Off"
+msgstr "Se désinscrire"
+
+#: git-gui.sh:2016 git-gui.sh:2333
+msgid "Commit@@verb"
+msgstr "Commiter"
+
+#: git-gui.sh:2027
+msgid "Local Merge..."
+msgstr "Fusion locale..."
+
+#: git-gui.sh:2032
+msgid "Abort Merge..."
+msgstr "Abandonner fusion..."
+
+#: git-gui.sh:2044
+msgid "Push..."
+msgstr "Pousser..."
+
+#: git-gui.sh:2055 lib/choose_repository.tcl:40
+msgid "Apple"
+msgstr "Pomme"
+
+#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
+#, tcl-format
+msgid "About %s"
+msgstr "A propos de %s"
+
+#: git-gui.sh:2062
+msgid "Preferences..."
+msgstr "Préférences..."
+
+#: git-gui.sh:2070 git-gui.sh:2595
+msgid "Options..."
+msgstr "Options..."
+
+#: git-gui.sh:2076 lib/choose_repository.tcl:46
+msgid "Help"
+msgstr "Aide"
+
+#: git-gui.sh:2117
+msgid "Online Documentation"
+msgstr "Documentation en ligne"
+
+#: git-gui.sh:2201
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr "fatale : pas d'infos sur le chemin %s : Fichier ou répertoire inexistant"
+
+#: git-gui.sh:2234
+msgid "Current Branch:"
+msgstr "Branche courante :"
+
+#: git-gui.sh:2255
+msgid "Staged Changes (Will Commit)"
+msgstr "Modifications pré-commitées"
+
+#: git-gui.sh:2274
+msgid "Unstaged Changes"
+msgstr "Modifications non pré-commitées"
+
+#: git-gui.sh:2323
+msgid "Stage Changed"
+msgstr "Pré-commit modifié"
+
+#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "Pousser"
+
+#: git-gui.sh:2369
+msgid "Initial Commit Message:"
+msgstr "Message de commit initial :"
+
+#: git-gui.sh:2370
+msgid "Amended Commit Message:"
+msgstr "Message de commit corrigé :"
+
+#: git-gui.sh:2371
+msgid "Amended Initial Commit Message:"
+msgstr "Message de commit initial corrigé :"
+
+#: git-gui.sh:2372
+msgid "Amended Merge Commit Message:"
+msgstr "Message de commit de fusion corrigé :"
+
+#: git-gui.sh:2373
+msgid "Merge Commit Message:"
+msgstr "Message de commit de fusion :"
+
+#: git-gui.sh:2374
+msgid "Commit Message:"
+msgstr "Message de commit :"
+
+#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71
+msgid "Copy All"
+msgstr "Copier tout"
+
+#: git-gui.sh:2443 lib/blame.tcl:104
+msgid "File:"
+msgstr "Fichier :"
+
+#: git-gui.sh:2545
+msgid "Refresh"
+msgstr "Rafraichir"
+
+#: git-gui.sh:2566
+msgid "Apply/Reverse Hunk"
+msgstr "Appliquer/Inverser section"
+
+#: git-gui.sh:2572
+msgid "Decrease Font Size"
+msgstr "Réduire fonte"
+
+#: git-gui.sh:2576
+msgid "Increase Font Size"
+msgstr "Agrandir fonte"
+
+#: git-gui.sh:2581
+msgid "Show Less Context"
+msgstr "Montrer moins de contexte"
+
+#: git-gui.sh:2588
+msgid "Show More Context"
+msgstr "Montrer plus de contexte"
+
+#: git-gui.sh:2602
+msgid "Unstage Hunk From Commit"
+msgstr "Enlever section pré-commitée"
+
+#: git-gui.sh:2604
+msgid "Stage Hunk For Commit"
+msgstr "Pré-commiter section"
+
+#: git-gui.sh:2623
+msgid "Initializing..."
+msgstr "Initialisation..."
+
+#: git-gui.sh:2718
+#, 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 ""
+"Des problèmes d'environnement sont possibles.\n"
+"\n"
+"Les variables d'environnement suivantes seront\n"
+"probablement ignorées par tous les\n"
+"sous-processus de Git lancés par %s\n"
+"\n"
+
+#: git-gui.sh:2748
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"Ceci est du à un problème connu avec\n"
+"le binaire Tcl distribué par Cygwin."
+
+#: git-gui.sh:2753
+#, 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"
+"Un bon remplacement pour %s\n"
+"est de mettre les valeurs pour 'user.name' (nom\n"
+"de l'utilisateur) et 'user.email' (addresse email\n"
+"de l'utilisateur) dans votre fichier '~/.gitconfig'.\n"
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - une interface graphique utilisateur pour Git"
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr "Visionneur de fichier"
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr "Commit :"
+
+#: lib/blame.tcl:249
+msgid "Copy Commit"
+msgstr "Copier commit"
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr "Lecture de %s..."
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr "Chargement des annotations de suivi des copies/déplacements..."
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr "lignes annotées"
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr "Chargement des annotations d'emplacement original"
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr "Annotation terminée."
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr "Chargement des annotations..."
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr "Auteur :"
+
+#: lib/blame.tcl:791
+msgid "Committer:"
+msgstr "Commiteur :"
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr "Fichier original :"
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr "A l'origine par :"
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr "Dans le fichier :"
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr "Copié ou déplacé ici par :"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "Emprunter branche"
+
+#: lib/branch_checkout.tcl:23
+msgid "Checkout"
+msgstr "Emprunter"
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr "Annuler"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr "Révision"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+msgid "Options"
+msgstr "Options"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Branche suivant récupération"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "Détacher de branche locale"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "Créer branche"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "Créer nouvelle branche"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
+msgid "Create"
+msgstr "Créer"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "Nom de branche"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr "Nom :"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "Trouver nom de branche de suivi"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Début de révision"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Mettre à jour branche existante :"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Non"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Avance rapide seulement"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr "Réinitialiser"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Emprunt après création"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "Merci de choisir une branche de suivi"
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr "La branche de suivi %s n'est pas une branche dans le référentiel distant."
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr "Merci de fournir un nom de branche."
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "'%s' n'est pas un nom de branche acceptable."
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr "Supprimer branche"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "Supprimer branche locale"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "Branches locales"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "Supprimer ssi fusion dedans"
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr "Toujours (Ne pas faire de test de fusion.)"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "Les branches suivantes ne sont pas complètement fusionnées dans %s :"
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+"Récupérer des branches supprimées est difficile.\n"
+"\n"
+"Supprimer les branches sélectionnées ?"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"La suppression des branches suivantes a échouée :\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "Renommer branche"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "Renommer"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "Branche :"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "Nouveau nom :"
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr "Merci de sélectionner une branche à renommer."
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "La branche '%s' existe déjà."
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Le renommage de '%s' a échoué."
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Lancement..."
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "Visionneur de fichier"
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr "Chargement de %s..."
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr "[Jusqu'au parent]"
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+msgid "Browse Branch Files"
+msgstr "Visionner fichiers de branches"
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
+msgid "Browse"
+msgstr "Visionner"
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Récupération de %s à partir de %s"
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "Erreur fatale : Impossible de résoudre %s"
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr "Fermer"
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "La branche '%s' n'existe pas."
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"La branche '%s' existe déjà.\n"
+"\n"
+"Impossible d'avancer rapidement à %s.\n"
+"Une fusion est nécessaire."
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "La stratégie de fusion '%s' n'est pas supportée."
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "La mise à jour de '%s' a échouée."
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr "L'espace de pré-commit ('index' ou 'staging') est déjà vérouillé."
+
+#: lib/checkout_op.tcl:266
+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 ""
+"L'état lors de la dernière synchronisation ne correspond plus à l'état du référentiel.\n"
+"\n"
+"Un autre programme Git a modifié ce référentiel depuis la dernière synchronisation. Une resynchronisation doit être effectuée avant de pouvoir modifier la branche courante.\n"
+"\n"
+"Cela va être fait tout de suite automatiquement.\n"
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "Mise à jour du répertoire courant avec '%s'..."
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr "Emprunt de '%s' abandonné. (Il est nécessaire de fusionner des fichiers.)"
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr "Il est nécessaire de fusionner des fichiers."
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Le répertoire de travail reste sur la branche '%s'."
+
+#: lib/checkout_op.tcl:429
+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 ""
+"Vous n'êtes plus ur une branche locale.\n"
+"\n"
+"Si vous vouliez être sur une branche, créez en une maintenant en partant de 'Cet emprunt détaché'."
+
+#: lib/checkout_op.tcl:446
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "'%s' emprunté."
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr "Réinitialiser '%s' à '%s' va faire perdre les commits suivants :"
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr "Récupérer les commits perdus ne sera peut être pas facile."
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "Réinitialiser '%s' ?"
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr "Visualiser"
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+"Le changement de la branche courante a échoué.\n"
+"\n"
+"Le répertoire courant n'est que partiellement modifié. Les fichiers ont été mis à jour avec succès, mais la mise à jour d'un fichier interne à Git a échouée.\n"
+"\n"
+"Cela n'aurait pas du se produire. %s va abandonner et se terminer."
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "Sélectionner"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "Famille de fonte"
+
+#: lib/choose_font.tcl:73
+msgid "Font Size"
+msgstr "Taille de fonte"
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr "Exemple de fonte"
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"C'est un texte d'exemple.\n"
+"Si vous aimez ce texte, vous pouvez choisir cette fonte."
+
+#: lib/choose_repository.tcl:27
+msgid "Git Gui"
+msgstr "Git Gui"
+
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
+msgid "Create New Repository"
+msgstr "Créer nouveau référentiel"
+
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr "Nouveau..."
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
+msgid "Clone Existing Repository"
+msgstr "Cloner référentiel existant"
+
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr "Cloner..."
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
+msgid "Open Existing Repository"
+msgstr "Ouvrir référentiel existant"
+
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr "Ouvrir..."
+
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr "Référentiels récents"
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr "Ouvrir référentiel récent :"
+
+#: lib/choose_repository.tcl:294
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "L'emplacement %s existe déjà."
+
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "La création du référentiel %s a échouée :"
+
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
+msgid "Directory:"
+msgstr "Répertoire :"
+
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
+msgid "Git Repository"
+msgstr "Référentiel Git"
+
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "Le répertoire %s existe déjà."
+
+#: lib/choose_repository.tcl:442
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Le fichier %s existe déjà."
+
+#: lib/choose_repository.tcl:463
+msgid "Clone"
+msgstr "Cloner"
+
+#: lib/choose_repository.tcl:476
+msgid "URL:"
+msgstr "URL :"
+
+#: lib/choose_repository.tcl:496
+msgid "Clone Type:"
+msgstr "Type de clonage :"
+
+#: lib/choose_repository.tcl:502
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Standard (rapide, semi-redondant, liens durs)"
+
+#: lib/choose_repository.tcl:508
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Copy complète (plus lent, sauvegarde redondante)"
+
+#: lib/choose_repository.tcl:514
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Partagé (le plus rapide, non recommandé, pas de sauvegarde)"
+
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "'%s' n'est pas un référentiel Git."
+
+#: lib/choose_repository.tcl:586
+msgid "Standard only available for local repository."
+msgstr "Standard n'est disponible que pour un référentiel local."
+
+#: lib/choose_repository.tcl:590
+msgid "Shared only available for local repository."
+msgstr "Partagé n'est disponible que pour un référentiel local."
+
+#: lib/choose_repository.tcl:617
+msgid "Failed to configure origin"
+msgstr "La configuration de l'origine a échouée."
+
+#: lib/choose_repository.tcl:629
+msgid "Counting objects"
+msgstr "Comptage des objets"
+
+#: lib/choose_repository.tcl:630
+msgid "buckets"
+msgstr "paniers"
+
+#: lib/choose_repository.tcl:654
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Impossible de copier 'objects/info/alternates' : %s"
+
+#: lib/choose_repository.tcl:690
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Il n'y a rien à cloner depuis %s."
+
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
+msgid "The 'master' branch has not been initialized."
+msgstr "Cette branche 'master' n'a pas été initialisée."
+
+#: lib/choose_repository.tcl:705
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr "Les liens durs ne sont pas disponibles. On se résoud à copier."
+
+#: lib/choose_repository.tcl:717
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Clonage depuis %s"
+
+#: lib/choose_repository.tcl:748
+msgid "Copying objects"
+msgstr "Copie des objets"
+
+#: lib/choose_repository.tcl:749
+msgid "KiB"
+msgstr "KiB"
+
+#: lib/choose_repository.tcl:773
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Impossible de copier l'objet : %s"
+
+#: lib/choose_repository.tcl:783
+msgid "Linking objects"
+msgstr "Liaison des objets"
+
+#: lib/choose_repository.tcl:784
+msgid "objects"
+msgstr "objets"
+
+#: lib/choose_repository.tcl:792
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Impossible créer un lien dur pour l'objet : %s"
+
+#: lib/choose_repository.tcl:847
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr "Impossible de récupérer les branches et objets. Voir la sortie console pour plus de détails."
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch tags. See console output for details."
+msgstr "Impossible de récupérer les marques. Voir la sortie console pour plus de détails."
+
+#: lib/choose_repository.tcl:882
+msgid "Cannot determine HEAD. See console output for details."
+msgstr "Impossible de déterminer HEAD. Voir la sortie console pour plus de détails."
+
+#: lib/choose_repository.tcl:891
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "Impossible de nettoyer %s"
+
+#: lib/choose_repository.tcl:897
+msgid "Clone failed."
+msgstr "Le clonage a échoué."
+
+#: lib/choose_repository.tcl:904
+msgid "No default branch obtained."
+msgstr "Aucune branche par défaut n'a été obtenue."
+
+#: lib/choose_repository.tcl:915
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "Impossible de résoudre %s comme commit."
+
+#: lib/choose_repository.tcl:927
+msgid "Creating working directory"
+msgstr "Création du répertoire de travail"
+
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr "fichiers"
+
+#: lib/choose_repository.tcl:957
+msgid "Initial file checkout failed."
+msgstr "L'emprunt initial de fichier a échoué."
+
+#: lib/choose_repository.tcl:973
+msgid "Open"
+msgstr "Ouvrir"
+
+#: lib/choose_repository.tcl:983
+msgid "Repository:"
+msgstr "Référentiel :"
+
+#: lib/choose_repository.tcl:1033
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Impossible d'ouvrir le référentiel %s :"
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr "Cet emprunt détaché"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Expression de révision :"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "Branche locale"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "Suivi de branche"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr "Marque"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Révision invalide : %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "Pas de révision selectionnée."
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "L'expression de révision est vide."
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr "Misa à jour"
+
+#: lib/choose_rev.tcl:558
+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 ""
+"Il n'y a rien à corriger.\n"
+"\n"
+"Vous allez créer le commit initial. Il n'y a pas de commit avant celui-ci à corriger.\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 ""
+"Impossible de corriger pendant une fusion.\n"
+"\n"
+"Vous êtes actuellement au milieu d'une fusion qui n'a pas été completement terminée. Vous ne pouvez pas corriger le commit précédant sauf si vous abandonnez la fusion courante.\n"
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr "Erreur lors du chargement des données de commit pour correction :"
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr "Impossible d'obtenir votre identité :"
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "GIT_COMMITTER_IDENT invalide :"
+
+#: lib/commit.tcl:133
+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 ""
+"L'état lors de la dernière synchronisation ne correspond plus à l'état du référentiel.\n"
+"\n"
+"Un autre programme Git a modifié ce référentiel depuis la dernière synchronisation. Une resynshronisation doit être effectuée avant de pouvoir créer un nouveau commit.\n"
+"\n"
+"Cela va être fait tout de suite automatiquement.\n"
+
+#: lib/commit.tcl:154
+#, 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 ""
+"Des fichiers non fusionnés ne peuvent être commités.\n"
+"\n"
+"Le fichier %s a des conflicts de fusion. Vous devez les résoudre et pré-commiter le fichier avant de pouvoir commiter.\n"
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Un état de fichier inconnu %s a été détecté.\n"
+"\n"
+"Le fichier %s ne peut pas être commité par ce programme.\n"
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Pas de modification à commiter.\n"
+"\n"
+"Vous devez pré-commiter au moins 1 fichier avant de pouvoir commiter.\n"
+
+#: lib/commit.tcl:183
+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 ""
+"Merci de fournir un message de commit.\n"
+"\n"
+"Un bon message de commit a le format suivant :\n"
+"\n"
+"- Première ligne : décrire en une phrase ce que vous avez fait.\n"
+"- Deuxième ligne : rien.\n"
+"- Lignes suivantes : Décrire pourquoi ces modifications sont bonnes.\n"
+
+#: lib/commit.tcl:257
+msgid "write-tree failed:"
+msgstr "write-tree a échoué :"
+
+#: lib/commit.tcl:275
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "Le commit %s semble être corrompu"
+
+#: lib/commit.tcl:279
+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 ""
+"Pas de modification à commiter.\n"
+"\n"
+"Aucun fichier n'a été modifié par ce commit et il ne s'agit pas d'un commit de fusion.\n"
+"\n"
+"Une resynchronisation va être lancée tout de suite automatiquement.\n"
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr "Pas de modifications à commiter."
+
+#: lib/commit.tcl:303
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "attention : Tcl ne supporte pas l'encodage '%s'."
+
+#: lib/commit.tcl:317
+msgid "commit-tree failed:"
+msgstr "commit-tree a échoué :"
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr "update-ref a échoué"
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Commit créé %s : %s"
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr "Travail en cours... merci de patienter..."
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr "Succès"
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr "Erreur : échec de la commande"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "Nombre d'objets en fichier particulier"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "Espace disque utilisé par les fichiers particuliers"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "Nombre d'objets empaquetés"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "Nombre de paquets d'objets"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "Espace disque utilisé par les objets empaquetés"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "Objets empaquetés attendant d'être supprimés"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "Fichiers poubelle"
+
+#: lib/database.tcl:72ets
+msgid "Compressing the object database"
+msgstr "Compression de la base des objets"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Vérification de la base des objets avec fsck-objects"
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Ce référentiel comprend actuellement environ %i objets ayant leur fichier particulier.\n"
+"\n"
+"Pour conserver une performance optimale, il est fortement recommandé de comprimer la base quand plus de %i objets ayant leur fichier particulier existent.\n"
+"\n"
+"Comprimer la base maintenant ?"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Date invalide de Git : %s"
+
+#: lib/diff.tcl:42
+#, 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 ""
+"Aucune différence détectée.\n"
+"\n"
+"%s ne comporte aucune modification.\n"
+"\n"
+"La date de modification de ce fichier a été mise à jour par une autre application, mais le contenu du fichier n'a pas changé.\n"
+"\n"
+"Une resynchronisation va être lancée automatiquement pour trouver d'autres fichiers qui pourraient se trouver dans le même état."
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Chargement des différences de %s..."
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Impossible d'afficher %s"
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr "Erreur lors du chargement du fichier :"
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr "Référentiel Git (sous projet)"
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr "* Fichier binaire (pas d'apperçu du contenu)."
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr "Erreur lors du chargement des différences :"
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr "La suppression dans le pré-commit de la section sélectionnée a échouée."
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr "Le pré-commit de la section sélectionnée a échoué."
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr "erreur"
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr "attention"
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr "Vous devez corriger les erreurs suivantes avant de pouvoir commiter."
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Impossible de dévérouiller le pré-commit."
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "Erreur de pré-commit"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr "Le pré-commit a échoué. Une resynchronisation va être lancée automatiquement."
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "Continuer"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "Dévérouiller le pré-commit"
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "Supprimer %s du commit"
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr "Ajouter %s"
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Inverser les modifications dans le fichier %s ? "
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "Inverser les modifications dans ces %i fichiers ?"
+
+#: lib/index.tcl:389
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr "Toutes les modifications non pré-commitées seront définitivement perdues lors de l'inversion."
+
+#: lib/index.tcl:392
+msgid "Do Nothing"
+msgstr "Ne rien faire"
+
+#: 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 ""
+"Impossible de fucionner pendant une correction.\n"
+"\n"
+"Vous devez finir de corriger ce commit avant de lancer une quelconque fusion.\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 ""
+"L'état lors de la dernière synchronisation ne correspond plus à l'état du référentiel.\n"
+"\n"
+"Un autre programme Git a modifié ce référentiel depuis la dernière synchronisation. Une resynchronisation doit être effectuée avant de pouvoir fusionner de nouveau.\n"
+"\n"
+"Cela va être fait tout de suite automatiquement\n"
+
+#: lib/merge.tcl:44
+#, 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 ""
+"Vous êtes au milieu d'une fusion conflictuelle.\n"
+"\n"
+"Le fichier %s a des conflicts de fusion.\n"
+"\n"
+"Vous devez les résoudre, puis pré-commiter le fichier, et enfin commiter pour terminer la fusion courante. Seulementà ce moment là, il sera possible d'effectuer une nouvelle fusion.\n"
+
+#: lib/merge.tcl:54
+#, 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 ""
+"Vous êtes au milieu d'une modification.\n"
+"\n"
+"Le fichier %s est modifié.\n"
+"\n"
+"Vous devriez terminer le commit courant avant de lancer une fusion. En faisait comme cela, vous éviterez de devoir éventuellement abandonner une fusion ayant échouée.\n"
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s de %s"
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr "Fusion de %s et %s"
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr "La fusion s'est faite avec succès."
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr "La fusion a echouée. Il est nécessaire de résoudre les conflicts."
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Fusion dans %s"
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr "Révision à fusionner"
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Impossible d'abandonner en cours de correction.\n"
+"\n"
+"Vous devez finir de corriger ce commit.\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 ""
+"Abandonner la fusion ?\n"
+"\n"
+"Abandonner la fusion courante entrainera la perte de TOUTES les modifications non commitées.\n"
+"\n"
+"Abandonner quand même la fusion courante ?"
+
+#: 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 ""
+"Réinitialiser les modifications ?\n"
+"\n"
+"Réinitialiser les modifications va faire perdre TOUTES les modifications non commitées.\n"
+"\n"
+"Réinitialiser quand même les modifications courantes ?"
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr "Abandon"
+
+#: lib/merge.tcl:266
+msgid "Abort failed."
+msgstr "L'abandon a échoué."
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr "Abandon teminé. Prêt."
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr "Remettre les valeurs par défaut"
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr "Sauvegarder"
+
+#: lib/option.tcl:96
+#, tcl-format
+msgid "%s Repository"
+msgstr "Référentiel de %s"
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr "Globales (tous les référentiels)"
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr "Nom d'utilisateur"
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr "Adresse email"
+
+#: lib/option.tcl:106
+msgid "Summarize Merge Commits"
+msgstr "Résumer les commits de fusion"
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr "Fusion bavarde"
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr "Montrer statistiques de diff après fusion"
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr "Faire confiance aux dates de modification de fichiers "
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr "Nettoyer les branches de suivi pendant la récupération"
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr "Faire correspondre les branches de suivi"
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr "Nombre de lignes de contexte dans les diffs"
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr "Nouveau modèle de nom de branche"
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr "Modifier les fontes"
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr "Choisir %s"
+
+#: lib/option.tcl:186
+msgid "pt."
+msgstr "pt."
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr "Préférences"
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr "La sauvegarde complète des options a échouée :"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr "Supprimer branche distante"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "Référentiel"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr "Branche distante :"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr "URL arbitraire :"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "Branches"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "Supprimer seulement si"
+
+#: lib/remote_branch_delete.tcl:111
+msgid "Merged Into:"
+msgstr "Fusionné dans :"
+
+#: lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Toujours (ne pas vérifier les fusions)"
+
+#: lib/remote_branch_delete.tcl:152
+msgid "A branch is required for 'Merged Into'."
+msgstr "Une branche est nécessaire pour 'Fusionné dans'."
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"Les branches suivantes ne sont pas complètement fusionnées dans %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 "Une ou plusieurs des tests de fusion ont échoués parce que vous n'avez pas récupéré les commits nécessaires. Essayez de récupéré à partir de %s d'abord."
+
+#: lib/remote_branch_delete.tcl:207
+msgid "Please select one or more branches to delete."
+msgstr "Merci de sélectionner une ou plusieurs branches à supprimer."
+
+#: lib/remote_branch_delete.tcl:216
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Récupérer des branches supprimées est difficile.\n"
+"\n"
+"Souhaitez vous supprimer les branches sélectionnées ?"
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr "Supprimer les branches de %s"
+
+#: lib/remote_branch_delete.tcl:286
+msgid "No repository selected."
+msgstr "Aucun référentiel n'est sélectionné."
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "Synchronisation de %s..."
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "Nettoyer de"
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "Récupérer de"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "Pousser vers"
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "Impossible d'écrire le raccourcis :"
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr "Impossible d'écrire l'icône :"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s ... %*i de %*i %s (%3i%%)"
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "récupérer %s"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Récupération des dernières modifications de %s"
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "nettoyer à distance %s"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Nettoyer les branches de suivi supprimées de %s"
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "pousser %s"
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Les modifications sont poussées vers %s"
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "Pousse %s %s vers %s"
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr "Pousser branches"
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr "Branches source"
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr "Référentiel de destination"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr "Transférer options"
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr "Forcer l'écrasement d'une branche existante (peut supprimer des modifications)"
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr "Utiliser des petits paquets (pour les connexions lentes)"
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr "Inclure les marques"
+
diff --git a/git-gui/po/git-gui.pot b/git-gui/po/git-gui.pot
new file mode 100644
index 0000000000..3f139da6c7
--- /dev/null
+++ b/git-gui/po/git-gui.pot
@@ -0,0 +1,1778 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-02-02 10:14+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: git-gui.sh:41 git-gui.sh:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714
+#: git-gui.sh:733
+msgid "git-gui: fatal error"
+msgstr ""
+
+#: git-gui.sh:565
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr ""
+
+#: git-gui.sh:590
+msgid "Main Font"
+msgstr ""
+
+#: git-gui.sh:591
+msgid "Diff/Console Font"
+msgstr ""
+
+#: git-gui.sh:605
+msgid "Cannot find git in PATH."
+msgstr ""
+
+#: git-gui.sh:632
+msgid "Cannot parse Git version string:"
+msgstr ""
+
+#: git-gui.sh:650
+#, 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 ""
+
+#: git-gui.sh:888
+msgid "Git directory not found:"
+msgstr ""
+
+#: git-gui.sh:895
+msgid "Cannot move to top of working directory:"
+msgstr ""
+
+#: git-gui.sh:902
+msgid "Cannot use funny .git directory:"
+msgstr ""
+
+#: git-gui.sh:907
+msgid "No working directory"
+msgstr ""
+
+#: git-gui.sh:1054
+msgid "Refreshing file status..."
+msgstr ""
+
+#: git-gui.sh:1119
+msgid "Scanning for modified files ..."
+msgstr ""
+
+#: git-gui.sh:1294 lib/browser.tcl:245
+msgid "Ready."
+msgstr ""
+
+#: git-gui.sh:1560
+msgid "Unmodified"
+msgstr ""
+
+#: git-gui.sh:1562
+msgid "Modified, not staged"
+msgstr ""
+
+#: git-gui.sh:1563 git-gui.sh:1568
+msgid "Staged for commit"
+msgstr ""
+
+#: git-gui.sh:1564 git-gui.sh:1569
+msgid "Portions staged for commit"
+msgstr ""
+
+#: git-gui.sh:1565 git-gui.sh:1570
+msgid "Staged for commit, missing"
+msgstr ""
+
+#: git-gui.sh:1567
+msgid "Untracked, not staged"
+msgstr ""
+
+#: git-gui.sh:1572
+msgid "Missing"
+msgstr ""
+
+#: git-gui.sh:1573
+msgid "Staged for removal"
+msgstr ""
+
+#: git-gui.sh:1574
+msgid "Staged for removal, still present"
+msgstr ""
+
+#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579
+msgid "Requires merge resolution"
+msgstr ""
+
+#: git-gui.sh:1614
+msgid "Starting gitk... please wait..."
+msgstr ""
+
+#: git-gui.sh:1623
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+
+#: git-gui.sh:1823 lib/choose_repository.tcl:35
+msgid "Repository"
+msgstr ""
+
+#: git-gui.sh:1824
+msgid "Edit"
+msgstr ""
+
+#: git-gui.sh:1826 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr ""
+
+#: git-gui.sh:1829 lib/choose_rev.tcl:547
+msgid "Commit@@noun"
+msgstr ""
+
+#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr ""
+
+#: git-gui.sh:1833 lib/choose_rev.tcl:556
+msgid "Remote"
+msgstr ""
+
+#: git-gui.sh:1842
+msgid "Browse Current Branch's Files"
+msgstr ""
+
+#: git-gui.sh:1846
+msgid "Browse Branch Files..."
+msgstr ""
+
+#: git-gui.sh:1851
+msgid "Visualize Current Branch's History"
+msgstr ""
+
+#: git-gui.sh:1855
+msgid "Visualize All Branch History"
+msgstr ""
+
+#: git-gui.sh:1862
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr ""
+
+#: git-gui.sh:1864
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr ""
+
+#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr ""
+
+#: git-gui.sh:1872 lib/database.tcl:34
+msgid "Compress Database"
+msgstr ""
+
+#: git-gui.sh:1875
+msgid "Verify Database"
+msgstr ""
+
+#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr ""
+
+#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
+msgid "Quit"
+msgstr ""
+
+#: git-gui.sh:1902
+msgid "Undo"
+msgstr ""
+
+#: git-gui.sh:1905
+msgid "Redo"
+msgstr ""
+
+#: git-gui.sh:1909 git-gui.sh:2403
+msgid "Cut"
+msgstr ""
+
+#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr ""
+
+#: git-gui.sh:1915 git-gui.sh:2409
+msgid "Paste"
+msgstr ""
+
+#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr ""
+
+#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69
+msgid "Select All"
+msgstr ""
+
+#: git-gui.sh:1931
+msgid "Create..."
+msgstr ""
+
+#: git-gui.sh:1937
+msgid "Checkout..."
+msgstr ""
+
+#: git-gui.sh:1943
+msgid "Rename..."
+msgstr ""
+
+#: git-gui.sh:1948 git-gui.sh:2048
+msgid "Delete..."
+msgstr ""
+
+#: git-gui.sh:1953
+msgid "Reset..."
+msgstr ""
+
+#: git-gui.sh:1965 git-gui.sh:2350
+msgid "New Commit"
+msgstr ""
+
+#: git-gui.sh:1973 git-gui.sh:2357
+msgid "Amend Last Commit"
+msgstr ""
+
+#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr ""
+
+#: git-gui.sh:1988
+msgid "Stage To Commit"
+msgstr ""
+
+#: git-gui.sh:1994
+msgid "Stage Changed Files To Commit"
+msgstr ""
+
+#: git-gui.sh:2000
+msgid "Unstage From Commit"
+msgstr ""
+
+#: git-gui.sh:2005 lib/index.tcl:393
+msgid "Revert Changes"
+msgstr ""
+
+#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427
+msgid "Sign Off"
+msgstr ""
+
+#: git-gui.sh:2016 git-gui.sh:2333
+msgid "Commit@@verb"
+msgstr ""
+
+#: git-gui.sh:2027
+msgid "Local Merge..."
+msgstr ""
+
+#: git-gui.sh:2032
+msgid "Abort Merge..."
+msgstr ""
+
+#: git-gui.sh:2044
+msgid "Push..."
+msgstr ""
+
+#: git-gui.sh:2055 lib/choose_repository.tcl:40
+msgid "Apple"
+msgstr ""
+
+#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
+#, tcl-format
+msgid "About %s"
+msgstr ""
+
+#: git-gui.sh:2062
+msgid "Preferences..."
+msgstr ""
+
+#: git-gui.sh:2070 git-gui.sh:2595
+msgid "Options..."
+msgstr ""
+
+#: git-gui.sh:2076 lib/choose_repository.tcl:46
+msgid "Help"
+msgstr ""
+
+#: git-gui.sh:2117
+msgid "Online Documentation"
+msgstr ""
+
+#: git-gui.sh:2201
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr ""
+
+#: git-gui.sh:2234
+msgid "Current Branch:"
+msgstr ""
+
+#: git-gui.sh:2255
+msgid "Staged Changes (Will Commit)"
+msgstr ""
+
+#: git-gui.sh:2274
+msgid "Unstaged Changes"
+msgstr ""
+
+#: git-gui.sh:2323
+msgid "Stage Changed"
+msgstr ""
+
+#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr ""
+
+#: git-gui.sh:2369
+msgid "Initial Commit Message:"
+msgstr ""
+
+#: git-gui.sh:2370
+msgid "Amended Commit Message:"
+msgstr ""
+
+#: git-gui.sh:2371
+msgid "Amended Initial Commit Message:"
+msgstr ""
+
+#: git-gui.sh:2372
+msgid "Amended Merge Commit Message:"
+msgstr ""
+
+#: git-gui.sh:2373
+msgid "Merge Commit Message:"
+msgstr ""
+
+#: git-gui.sh:2374
+msgid "Commit Message:"
+msgstr ""
+
+#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71
+msgid "Copy All"
+msgstr ""
+
+#: git-gui.sh:2443 lib/blame.tcl:104
+msgid "File:"
+msgstr ""
+
+#: git-gui.sh:2573
+msgid "Apply/Reverse Hunk"
+msgstr ""
+
+#: git-gui.sh:2579
+msgid "Show Less Context"
+msgstr ""
+
+#: git-gui.sh:2586
+msgid "Show More Context"
+msgstr ""
+
+#: git-gui.sh:2594
+msgid "Refresh"
+msgstr ""
+
+#: git-gui.sh:2615
+msgid "Decrease Font Size"
+msgstr ""
+
+#: git-gui.sh:2619
+msgid "Increase Font Size"
+msgstr ""
+
+#: git-gui.sh:2630
+msgid "Unstage Hunk From Commit"
+msgstr ""
+
+#: git-gui.sh:2604
+msgid "Stage Hunk For Commit"
+msgstr ""
+
+#: git-gui.sh:2623
+msgid "Initializing..."
+msgstr ""
+
+#: git-gui.sh:2718
+#, 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 ""
+
+#: git-gui.sh:2748
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+
+#: git-gui.sh:2753
+#, 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 ""
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr ""
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr ""
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr ""
+
+#: lib/blame.tcl:249
+msgid "Copy Commit"
+msgstr ""
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr ""
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr ""
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr ""
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr ""
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr ""
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr ""
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr ""
+
+#: lib/blame.tcl:791
+msgid "Committer:"
+msgstr ""
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr ""
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr ""
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr ""
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr ""
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr ""
+
+#: lib/branch_checkout.tcl:23
+msgid "Checkout"
+msgstr ""
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr ""
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr ""
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+msgid "Options"
+msgstr ""
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr ""
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr ""
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr ""
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr ""
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
+msgid "Create"
+msgstr ""
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr ""
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr ""
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr ""
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr ""
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr ""
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr ""
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr ""
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr ""
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr ""
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr ""
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr ""
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr ""
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr ""
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr ""
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr ""
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr ""
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr ""
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr ""
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr ""
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr ""
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr ""
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr ""
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr ""
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr ""
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr ""
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr ""
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr ""
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr ""
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr ""
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr ""
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+msgid "Browse Branch Files"
+msgstr ""
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
+msgid "Browse"
+msgstr ""
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr ""
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr ""
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr ""
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr ""
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr ""
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr ""
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr ""
+
+#: lib/checkout_op.tcl:266
+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 ""
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr ""
+
+#: lib/checkout_op.tcl:323
+msgid "files checked out"
+msgstr ""
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr ""
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr ""
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr ""
+
+#: lib/checkout_op.tcl:429
+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 ""
+
+#: lib/checkout_op.tcl:446
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr ""
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr ""
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr ""
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr ""
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr ""
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr ""
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr ""
+
+#: lib/choose_font.tcl:73
+msgid "Font Size"
+msgstr ""
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr ""
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+
+#: lib/choose_repository.tcl:27
+msgid "Git Gui"
+msgstr ""
+
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
+msgid "Create New Repository"
+msgstr ""
+
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr ""
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
+msgid "Clone Existing Repository"
+msgstr ""
+
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr ""
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
+msgid "Open Existing Repository"
+msgstr ""
+
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr ""
+
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr ""
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr ""
+
+#: lib/choose_repository.tcl:294
+#, tcl-format
+msgid "Location %s already exists."
+msgstr ""
+
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr ""
+
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
+msgid "Directory:"
+msgstr ""
+
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
+msgid "Git Repository"
+msgstr ""
+
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr ""
+
+#: lib/choose_repository.tcl:442
+#, tcl-format
+msgid "File %s already exists."
+msgstr ""
+
+#: lib/choose_repository.tcl:463
+msgid "Clone"
+msgstr ""
+
+#: lib/choose_repository.tcl:476
+msgid "URL:"
+msgstr ""
+
+#: lib/choose_repository.tcl:496
+msgid "Clone Type:"
+msgstr ""
+
+#: lib/choose_repository.tcl:502
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr ""
+
+#: lib/choose_repository.tcl:508
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr ""
+
+#: lib/choose_repository.tcl:514
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr ""
+
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:586
+msgid "Standard only available for local repository."
+msgstr ""
+
+#: lib/choose_repository.tcl:590
+msgid "Shared only available for local repository."
+msgstr ""
+
+#: lib/choose_repository.tcl:617
+msgid "Failed to configure origin"
+msgstr ""
+
+#: lib/choose_repository.tcl:629
+msgid "Counting objects"
+msgstr ""
+
+#: lib/choose_repository.tcl:630
+msgid "buckets"
+msgstr ""
+
+#: lib/choose_repository.tcl:654
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:690
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr ""
+
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
+msgid "The 'master' branch has not been initialized."
+msgstr ""
+
+#: lib/choose_repository.tcl:705
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr ""
+
+#: lib/choose_repository.tcl:717
+#, tcl-format
+msgid "Cloning from %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:748
+msgid "Copying objects"
+msgstr ""
+
+#: lib/choose_repository.tcl:749
+msgid "KiB"
+msgstr ""
+
+#: lib/choose_repository.tcl:773
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:783
+msgid "Linking objects"
+msgstr ""
+
+#: lib/choose_repository.tcl:784
+msgid "objects"
+msgstr ""
+
+#: lib/choose_repository.tcl:792
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:847
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr ""
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch tags. See console output for details."
+msgstr ""
+
+#: lib/choose_repository.tcl:882
+msgid "Cannot determine HEAD. See console output for details."
+msgstr ""
+
+#: lib/choose_repository.tcl:891
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:897
+msgid "Clone failed."
+msgstr ""
+
+#: lib/choose_repository.tcl:904
+msgid "No default branch obtained."
+msgstr ""
+
+#: lib/choose_repository.tcl:915
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr ""
+
+#: lib/choose_repository.tcl:927
+msgid "Creating working directory"
+msgstr ""
+
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr ""
+
+#: lib/choose_repository.tcl:957
+msgid "Initial file checkout failed."
+msgstr ""
+
+#: lib/choose_repository.tcl:973
+msgid "Open"
+msgstr ""
+
+#: lib/choose_repository.tcl:983
+msgid "Repository:"
+msgstr ""
+
+#: lib/choose_repository.tcl:1033
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr ""
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr ""
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr ""
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr ""
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr ""
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr ""
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr ""
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr ""
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr ""
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr ""
+
+#: lib/choose_rev.tcl:558
+msgid "URL"
+msgstr ""
+
+#: 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 ""
+
+#: 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 ""
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr ""
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr ""
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr ""
+
+#: lib/commit.tcl:133
+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 ""
+
+#: lib/commit.tcl:154
+#, 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 ""
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+
+#: lib/commit.tcl:183
+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 ""
+
+#: lib/commit.tcl:207
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr ""
+
+#: lib/commit.tcl:221
+msgid "Calling pre-commit hook..."
+msgstr ""
+
+#: lib/commit.tcl:236
+msgid "Commit declined by pre-commit hook."
+msgstr ""
+
+#: lib/commit.tcl:259
+msgid "Calling commit-msg hook..."
+msgstr ""
+
+#: lib/commit.tcl:274
+msgid "Commit declined by commit-msg hook."
+msgstr ""
+
+#: lib/commit.tcl:287
+msgid "Committing changes..."
+msgstr ""
+
+#: lib/commit.tcl:303
+msgid "write-tree failed:"
+msgstr ""
+
+#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368
+msgid "Commit failed."
+msgstr ""
+
+#: lib/commit.tcl:321
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr ""
+
+#: lib/commit.tcl:279
+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 ""
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr ""
+
+#: lib/commit.tcl:347
+msgid "commit-tree failed:"
+msgstr ""
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr ""
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr ""
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr ""
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr ""
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr ""
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr ""
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr ""
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr ""
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr ""
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr ""
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr ""
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr ""
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr ""
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr ""
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr ""
+
+#: lib/diff.tcl:42
+#, 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 ""
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr ""
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr ""
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr ""
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr ""
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr ""
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr ""
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr ""
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr ""
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr ""
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr ""
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr ""
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr ""
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr ""
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr ""
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr ""
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr ""
+
+#: lib/index.tcl:313
+msgid "Ready to commit."
+msgstr ""
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr ""
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr ""
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr ""
+
+#: lib/index.tcl:389
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+
+#: lib/index.tcl:392
+msgid "Do Nothing"
+msgstr ""
+
+#: 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 ""
+
+#: 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 ""
+
+#: lib/merge.tcl:44
+#, 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 ""
+
+#: lib/merge.tcl:54
+#, 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 ""
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr ""
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr ""
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr ""
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr ""
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr ""
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr ""
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+
+#: 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 ""
+
+#: 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 ""
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr ""
+
+#: lib/merge.tcl:238
+msgid "files reset"
+msgstr ""
+
+#: lib/merge.tcl:265
+msgid "Abort failed."
+msgstr ""
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr ""
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr ""
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr ""
+
+#: lib/option.tcl:96
+#, tcl-format
+msgid "%s Repository"
+msgstr ""
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr ""
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr ""
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr ""
+
+#: lib/option.tcl:106
+msgid "Summarize Merge Commits"
+msgstr ""
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr ""
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr ""
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr ""
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr ""
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr ""
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr ""
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr ""
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr ""
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr ""
+
+#: lib/option.tcl:186
+msgid "pt."
+msgstr ""
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr ""
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:111
+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 ""
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+
+#: 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 ""
+
+#: lib/remote_branch_delete.tcl:207
+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 ""
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:286
+msgid "No repository selected."
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr ""
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr ""
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr ""
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr ""
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr ""
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr ""
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr ""
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr ""
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr ""
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr ""
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr ""
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr ""
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr ""
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr ""
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr ""
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr ""
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr ""
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr ""
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr ""
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr ""
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr ""
diff --git a/git-gui/po/glossary/Makefile b/git-gui/po/glossary/Makefile
new file mode 100644
index 0000000000..749aa2e7ec
--- /dev/null
+++ b/git-gui/po/glossary/Makefile
@@ -0,0 +1,9 @@
+PO_TEMPLATE = git-gui-glossary.pot
+
+ALL_POFILES = $(wildcard *.po)
+
+$(PO_TEMPLATE): $(subst .pot,.txt,$(PO_TEMPLATE))
+ ./txt-to-pot.sh $< > $@
+
+update-po:: git-gui-glossary.pot
+ $(foreach p, $(ALL_POFILES), echo Updating $p ; msgmerge -U $p $(PO_TEMPLATE) ; )
diff --git a/git-gui/po/glossary/de.po b/git-gui/po/glossary/de.po
new file mode 100644
index 0000000000..0b33c572bf
--- /dev/null
+++ b/git-gui/po/glossary/de.po
@@ -0,0 +1,189 @@
+# Translation of git-gui glossary to German
+# Copyright (C) 2007 Shawn Pearce, et al.
+# This file is distributed under the same license as the git package.
+# Christian Stimming <stimming@tuhh.de>, 2007
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui glossary\n"
+"POT-Creation-Date: 2008-01-07 21:20+0100\n"
+"PO-Revision-Date: 2008-01-15 20:32+0100\n"
+"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
+"Language-Team: German \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 ""
+"Deutsche Ãœbersetzung.\n"
+"Andere deutsche SCM:\n"
+" http://tortoisesvn.net/docs/release/TortoiseSVN_de/index.html und http://"
+"tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_de.po "
+"(username=guest, password empty, gut),\n"
+" http://msdn.microsoft.com/de-de/library/ms181038(vs.80).aspx (MS Visual "
+"Source Safe, kommerziell),\n"
+" http://cvsbook.red-bean.com/translations/german/Kap_06.html "
+"(mittelmäßig),\n"
+" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/de_DE.po?"
+"view=markup (mittelmäßig),\n"
+" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/de/rapidsvn.po "
+"(username=guest, password empty, schlecht)"
+
+#. ""
+msgid "amend"
+msgstr "nachbessern (ergänzen)"
+
+#. ""
+msgid "annotate"
+msgstr "annotieren"
+
+#. "A 'branch' is an active line of development."
+msgid "branch [noun]"
+msgstr "Zweig"
+
+#. ""
+msgid "branch [verb]"
+msgstr "verzweigen"
+
+#. ""
+msgid "checkout [noun]"
+msgstr ""
+"Arbeitskopie (Erstellung einer Arbeitskopie; Auscheck? Ausspielung? Abruf? "
+"Source Safe: Auscheckvorgang)"
+
+#. "The action of updating the working tree to a revision which was stored in the object database."
+msgid "checkout [verb]"
+msgstr ""
+"Arbeitskopie erstellen; Zweig umstellen [checkout a branch] (auschecken? "
+"ausspielen? abrufen? Source Safe: auschecken)"
+
+#. ""
+msgid "clone [verb]"
+msgstr "klonen"
+
+#. "A single point in the git history."
+msgid "commit [noun]"
+msgstr ""
+"Version; Eintragung; Änderung (Buchung?, Eintragung?, Übertragung?, "
+"Sendung?, Ãœbergabe?, Einspielung?, Ablagevorgang?)"
+
+#. "The action of storing a new snapshot of the project's state in the git history."
+msgid "commit [verb]"
+msgstr ""
+"eintragen (TortoiseSVN: übertragen; Source Safe: einchecken; senden?, "
+"übergeben?, einspielen?, einpflegen?, ablegen?)"
+
+#. ""
+msgid "diff [noun]"
+msgstr "Vergleich (Source Safe: Unterschiede)"
+
+#. ""
+msgid "diff [verb]"
+msgstr "vergleichen"
+
+#. "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 "Schnellzusammenführung"
+
+#. "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 "anfordern (holen?)"
+
+#. "One context of consecutive lines in a whole patch, which consists of many such hunks"
+msgid "hunk"
+msgstr "Kontext"
+
+#. "A collection of files. The index is a stored version of your working tree."
+msgid "index (in git-gui: staging area)"
+msgstr "Bereitstellung"
+
+#. "A successful merge results in the creation of a new commit representing the result of the merge."
+msgid "merge [noun]"
+msgstr "Zusammenführung"
+
+#. "To bring the contents of another branch into the current branch."
+msgid "merge [verb]"
+msgstr "zusammenführen"
+
+#. ""
+msgid "message"
+msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
+
+#. "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 "entfernen"
+
+#. "Pulling a branch means to fetch it and merge it."
+msgid "pull"
+msgstr "übernehmen (ziehen?)"
+
+#. "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 "versenden (ausliefern? hochladen? verschicken? schieben?)"
+
+#. ""
+msgid "redo"
+msgstr "wiederholen"
+
+#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+msgid "remote"
+msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)"
+
+#. "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 "Projektarchiv"
+
+#. ""
+msgid "reset"
+msgstr "zurücksetzen (zurückkehren?)"
+
+#. ""
+msgid "revert"
+msgstr "verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
+
+#. "A particular state of files and directories which was stored in the object database."
+msgid "revision"
+msgstr "Version (TortoiseSVN: Revision; Source Safe: Version)"
+
+#. ""
+msgid "sign off"
+msgstr "abzeichnen (gegenzeichnen?, freizeichnen?, absegnen?)"
+
+#. ""
+msgid "staging area"
+msgstr "Bereitstellung"
+
+#. ""
+msgid "status"
+msgstr "Status"
+
+#. "A ref pointing to a tag or commit object"
+msgid "tag [noun]"
+msgstr "Markierung"
+
+#. ""
+msgid "tag [verb]"
+msgstr "markieren"
+
+#. "A regular git branch that is used to follow changes from another repository."
+msgid "tracking branch"
+msgstr "Ãœbernahmezweig"
+
+#. ""
+msgid "undo"
+msgstr "rückgängig"
+
+#. ""
+msgid "update"
+msgstr "aktualisieren"
+
+#. ""
+msgid "verify"
+msgstr "überprüfen"
+
+#. "The tree of actual checked out files."
+msgid "working copy, working tree"
+msgstr "Arbeitskopie"
diff --git a/git-gui/po/glossary/fr.po b/git-gui/po/glossary/fr.po
new file mode 100644
index 0000000000..27c006abb2
--- /dev/null
+++ b/git-gui/po/glossary/fr.po
@@ -0,0 +1,166 @@
+# translation of fr.po to French
+# Translation of git-gui glossary to French
+# Copyright (C) 2008 Shawn Pearce, et al.
+#
+# Christian Couder <chriscool@tuxfamily.org>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: fr\n"
+"POT-Creation-Date: 2008-01-15 21:04+0100\n"
+"PO-Revision-Date: 2008-01-15 21:17+0100\n"
+"Last-Translator: Christian Couder <chriscool@tuxfamily.org>\n"
+"Language-Team: French\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\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 "corriger"
+
+#. ""
+msgid "annotate"
+msgstr "annoter"
+
+#. "A 'branch' is an active line of development."
+msgid "branch [noun]"
+msgstr "branche"
+
+#. ""
+msgid "branch [verb]"
+msgstr "créer une branche"
+
+#. ""
+msgid "checkout [noun]"
+msgstr "emprunt"
+
+#. "The action of updating the working tree to a revision which was stored in the object database."
+msgid "checkout [verb]"
+msgstr "emprunter"
+
+#. ""
+msgid "clone [verb]"
+msgstr "cloner"
+
+#. "A single point in the git history."
+msgid "commit [noun]"
+msgstr "commit"
+
+#. "The action of storing a new snapshot of the project's state in the git history."
+msgid "commit [verb]"
+msgstr "commiter"
+
+#. ""
+msgid "diff [noun]"
+msgstr "différence"
+
+#. ""
+msgid "diff [verb]"
+msgstr "comparer"
+
+#. "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 "fusion par avance rapide"
+
+#. "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 "récupérer"
+
+#. "A collection of files. The index is a stored version of your working tree."
+msgid "index (in git-gui: staging area)"
+msgstr "pré-commit"
+
+#. "A successful merge results in the creation of a new commit representing the result of the merge."
+msgid "merge [noun]"
+msgstr "fusion"
+
+#. "To bring the contents of another branch into the current branch."
+msgid "merge [verb]"
+msgstr "fusionner"
+
+#. ""
+msgid "message"
+msgstr "message"
+
+#. "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 "nettoyer"
+
+#. "Pulling a branch means to fetch it and merge it."
+msgid "pull"
+msgstr "tirer"
+
+#. "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 "pousser"
+
+#. ""
+msgid "redo"
+msgstr "refaire"
+
+#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+msgid "remote"
+msgstr "référentiel distant"
+
+#. "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 "référentiel"
+
+#. ""
+msgid "reset"
+msgstr "réinitialiser"
+
+#. ""
+msgid "revert"
+msgstr "inverser"
+
+#. "A particular state of files and directories which was stored in the object database."
+msgid "revision"
+msgstr "révision"
+
+#. ""
+msgid "sign off"
+msgstr "signer"
+
+#. ""
+msgid "staging area"
+msgstr "pré-commit"
+
+#. ""
+msgid "status"
+msgstr "état"
+
+#. "A ref pointing to a tag or commit object"
+msgid "tag [noun]"
+msgstr "marque"
+
+#. ""
+msgid "tag [verb]"
+msgstr "marquer"
+
+#. "A regular git branch that is used to follow changes from another repository."
+msgid "tracking branch"
+msgstr "branche de suivi"
+
+#. ""
+msgid "undo"
+msgstr "défaire"
+
+#. ""
+msgid "update"
+msgstr "mise à jour"
+
+#. ""
+msgid "verify"
+msgstr "vérifier"
+
+#. "The tree of actual checked out files."
+msgid "working copy, working tree"
+msgstr "copie de travail, arborescence de travail"
+
diff --git a/git-gui/po/glossary/git-gui-glossary.pot b/git-gui/po/glossary/git-gui-glossary.pot
new file mode 100644
index 0000000000..40eb3e9c07
--- /dev/null
+++ b/git-gui/po/glossary/git-gui-glossary.pot
@@ -0,0 +1,168 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2008-01-07 21:20+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: ENCODING\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 ""
+
+#. ""
+msgid "annotate"
+msgstr ""
+
+#. "A 'branch' is an active line of development."
+msgid "branch [noun]"
+msgstr ""
+
+#. ""
+msgid "branch [verb]"
+msgstr ""
+
+#. ""
+msgid "checkout [noun]"
+msgstr ""
+
+#. "The action of updating the working tree to a revision which was stored in the object database."
+msgid "checkout [verb]"
+msgstr ""
+
+#. ""
+msgid "clone [verb]"
+msgstr ""
+
+#. "A single point in the git history."
+msgid "commit [noun]"
+msgstr ""
+
+#. "The action of storing a new snapshot of the project's state in the git history."
+msgid "commit [verb]"
+msgstr ""
+
+#. ""
+msgid "diff [noun]"
+msgstr ""
+
+#. ""
+msgid "diff [verb]"
+msgstr ""
+
+#. "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 ""
+
+#. "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 ""
+
+#. "One context of consecutive lines in a whole patch, which consists of many such hunks"
+msgid "hunk"
+msgstr ""
+
+#. "A collection of files. The index is a stored version of your working tree."
+msgid "index (in git-gui: staging area)"
+msgstr ""
+
+#. "A successful merge results in the creation of a new commit representing the result of the merge."
+msgid "merge [noun]"
+msgstr ""
+
+#. "To bring the contents of another branch into the current branch."
+msgid "merge [verb]"
+msgstr ""
+
+#. ""
+msgid "message"
+msgstr ""
+
+#. "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 ""
+
+#. "Pulling a branch means to fetch it and merge it."
+msgid "pull"
+msgstr ""
+
+#. "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 ""
+
+#. ""
+msgid "redo"
+msgstr ""
+
+#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+msgid "remote"
+msgstr ""
+
+#. "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 ""
+
+#. ""
+msgid "reset"
+msgstr ""
+
+#. ""
+msgid "revert"
+msgstr ""
+
+#. "A particular state of files and directories which was stored in the object database."
+msgid "revision"
+msgstr ""
+
+#. ""
+msgid "sign off"
+msgstr ""
+
+#. ""
+msgid "staging area"
+msgstr ""
+
+#. ""
+msgid "status"
+msgstr ""
+
+#. "A ref pointing to a tag or commit object"
+msgid "tag [noun]"
+msgstr ""
+
+#. ""
+msgid "tag [verb]"
+msgstr ""
+
+#. "A regular git branch that is used to follow changes from another repository."
+msgid "tracking branch"
+msgstr ""
+
+#. ""
+msgid "undo"
+msgstr ""
+
+#. ""
+msgid "update"
+msgstr ""
+
+#. ""
+msgid "verify"
+msgstr ""
+
+#. "The tree of actual checked out files."
+msgid "working copy, working tree"
+msgstr ""
+
diff --git a/git-gui/po/glossary/git-gui-glossary.txt b/git-gui/po/glossary/git-gui-glossary.txt
new file mode 100644
index 0000000000..9b31f69152
--- /dev/null
+++ b/git-gui/po/glossary/git-gui-glossary.txt
@@ -0,0 +1,38 @@
+"English Term (Dear translator: This file will never be visible to the user!)" "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.)"
+"amend" ""
+"annotate" ""
+"branch [noun]" "A 'branch' is an active line of development."
+"branch [verb]" ""
+"checkout [noun]" ""
+"checkout [verb]" "The action of updating the working tree to a revision which was stored in the object database."
+"clone [verb]" ""
+"commit [noun]" "A single point in the git history."
+"commit [verb]" "The action of storing a new snapshot of the project's state in the git history."
+"diff [noun]" ""
+"diff [verb]" ""
+"fast forward merge" "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."
+"fetch" "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."
+"hunk" "One context of consecutive lines in a whole patch, which consists of many such hunks"
+"index (in git-gui: staging area)" "A collection of files. The index is a stored version of your working tree."
+"merge [noun]" "A successful merge results in the creation of a new commit representing the result of the merge."
+"merge [verb]" "To bring the contents of another branch into the current branch."
+"message" ""
+"prune" "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>'."
+"pull" "Pulling a branch means to fetch it and merge it."
+"push" "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?)"
+"redo" ""
+"remote" "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+"repository" "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?)"
+"reset" ""
+"revert" ""
+"revision" "A particular state of files and directories which was stored in the object database."
+"sign off" ""
+"staging area" ""
+"status" ""
+"tag [noun]" "A ref pointing to a tag or commit object"
+"tag [verb]" ""
+"tracking branch" "A regular git branch that is used to follow changes from another repository."
+"undo" ""
+"update" ""
+"verify" ""
+"working copy, working tree" "The tree of actual checked out files."
diff --git a/git-gui/po/glossary/it.po b/git-gui/po/glossary/it.po
new file mode 100644
index 0000000000..bb46b48d6b
--- /dev/null
+++ b/git-gui/po/glossary/it.po
@@ -0,0 +1,184 @@
+# Translation of git-gui glossary to Italian
+# Copyright (C) 2007 Shawn Pearce, et al.
+# This file is distributed under the same license as the git package.
+# Christian Stimming <stimming@tuhh.de>, 2007
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui glossary\n"
+"POT-Creation-Date: 2007-10-19 21:43+0200\n"
+"PO-Revision-Date: 2007-10-10 15:24+0200\n"
+"Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n"
+"Language-Team: Italian \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 ""
+"Traduzione italiana.\n"
+"Altri SCM in italiano:\n"
+" http://tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_it."
+"po (username=guest, password empty),\n"
+" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/it_IT.po?"
+"view=markup ,\n"
+" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/it_IT/rapidsvn.po "
+"(username=guest, password empty)"
+
+#. ""
+msgid "amend"
+msgstr "correggere, correzione"
+
+#. ""
+msgid "annotate"
+msgstr "annotare, annotazione"
+
+#. "A 'branch' is an active line of development."
+msgid "branch [noun]"
+msgstr "ramo, diramazione, ramificazione"
+
+#. ""
+msgid "branch [verb]"
+msgstr "creare ramo, ramificare, diramare"
+
+#. ""
+msgid "checkout [noun]"
+msgstr "attivazione, checkout, revisione attiva, prelievo (TortoiseCVS)?"
+
+#. "The action of updating the working tree to a revision which was stored in the object database."
+msgid "checkout [verb]"
+msgstr ""
+"attivare, effettuare un checkout, attivare revisione, prelevare "
+"(TortoiseCVS), ritirare (TSVN)?"
+
+#. ""
+msgid "clone [verb]"
+msgstr "clonare"
+
+#. "A single point in the git history."
+msgid "commit [noun]"
+msgstr "revisione, commit, deposito (TortoiseCVS), invio (TSVN)?"
+
+#. "The action of storing a new snapshot of the project's state in the git history."
+msgid "commit [verb]"
+msgstr ""
+"creare una nuova revisione, archiviare, effettuare un commit, depositare "
+"(nel server), fare un deposito (TortoiseCVS), inviare (TSVN)?"
+
+#. ""
+msgid "diff [noun]"
+msgstr "differenza, confronto, comparazione, raffronto"
+
+#. ""
+msgid "diff [verb]"
+msgstr "confronta, mostra le differenze"
+
+#. "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 "fusione in 'fast-forward', fusione in avanti veloce"
+
+#. "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 "recuperare, prelevare, prendere da, recuperare (TSVN)"
+
+#. "A collection of files. The index is a stored version of your working tree."
+msgid "index (in git-gui: staging area)"
+msgstr "indice"
+
+#. "A successful merge results in the creation of a new commit representing the result of the merge."
+msgid "merge [noun]"
+msgstr "fusione, unione"
+
+#. "To bring the contents of another branch into the current branch."
+msgid "merge [verb]"
+msgstr "effettuare la fusione, unire, fondere, eseguire la fusione"
+
+#. ""
+msgid "message"
+msgstr "messaggio, commento"
+
+#. "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 "potatura"
+
+#. "Pulling a branch means to fetch it and merge it."
+msgid "pull"
+msgstr ""
+"prendi (recupera) e fondi (unisci)? (in pratica una traduzione di fetch + "
+"merge)"
+
+#. "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 "propaga"
+
+#. ""
+msgid "redo"
+msgstr "ripeti, rifai"
+
+#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+msgid "remote"
+msgstr "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 "archivio, repository, database? deposito (rapidsvn)?"
+
+#. ""
+msgid "reset"
+msgstr "ripristinare, annullare, azzerare, ripristinare"
+
+#. ""
+msgid "revert"
+msgstr ""
+"annullare, inverti (rapidsvn), ritorna allo stato precedente, annulla le "
+"modifiche della revisione"
+
+#. "A particular state of files and directories which was stored in the object database."
+msgid "revision"
+msgstr "revisione (TortoiseSVN)"
+
+#. ""
+msgid "sign off"
+msgstr "sign off, firma"
+
+#. ""
+msgid "staging area"
+msgstr ""
+"area di preparazione, zona di preparazione, modifiche in preparazione? "
+"modifiche in allestimento?"
+
+#. ""
+msgid "status"
+msgstr "stato"
+
+#. "A ref pointing to a tag or commit object"
+msgid "tag [noun]"
+msgstr "etichetta, etichettatura (TortoiseCVS)"
+
+#. ""
+msgid "tag [verb]"
+msgstr "etichettare"
+
+#. "A regular git branch that is used to follow changes from another repository."
+msgid "tracking branch"
+msgstr ""
+"duplicato locale di ramo remoto, ramo in 'tracking', ramo inseguitore? ramo "
+"di {inseguimento,allineamento,rilevamento,puntamento}?"
+
+#. ""
+msgid "undo"
+msgstr "annulla"
+
+#. ""
+msgid "update"
+msgstr "aggiornamento, aggiornare"
+
+#. ""
+msgid "verify"
+msgstr "verifica, verificare"
+
+#. "The tree of actual checked out files."
+msgid "working copy, working tree"
+msgstr "directory di lavoro, copia di lavoro"
diff --git a/git-gui/po/glossary/txt-to-pot.sh b/git-gui/po/glossary/txt-to-pot.sh
new file mode 100755
index 0000000000..49bf7c5365
--- /dev/null
+++ b/git-gui/po/glossary/txt-to-pot.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# This is a very, _very_, simple script to convert a tab-separated
+# .txt file into a .pot/.po.
+# Its not clever but it took me 2 minutes to write :)
+# Michael Twomey <michael.twomey@ireland.sun.com>
+# 23 March 2001
+# with slight GnuCash modifications by Christian Stimming <stimming@tuhh.de>
+# 19 Aug 2001, 23 Jul 2007
+
+#check args
+if [ $# -eq 0 ]
+then
+ cat <<!
+Usage: `basename $0` git-gui-glossary.txt > git-gui-glossary.pot
+!
+ exit 1;
+fi
+
+GLOSSARY_CSV="$1";
+
+if [ ! -f "$GLOSSARY_CSV" ]
+then
+ echo "Can't find $GLOSSARY_CSV.";
+ exit 1;
+fi
+
+cat <<!
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: `date +'%Y-%m-%d %H:%M%z'`\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: ENCODING\n"
+
+!
+
+#Yes this is the most simple awk script you've ever seen :)
+awk -F'\t' '{if ($2 != "") print "#. "$2; print "msgid "$1; print "msgstr \"\"\n"}' \
+$GLOSSARY_CSV
diff --git a/git-gui/po/glossary/zh_cn.po b/git-gui/po/glossary/zh_cn.po
new file mode 100644
index 0000000000..158835b5c1
--- /dev/null
+++ b/git-gui/po/glossary/zh_cn.po
@@ -0,0 +1,170 @@
+# Translation of git-gui glossary to Simplified Chinese
+# Copyright (C) 2007 Shawn Pearce, et al.
+# This file is distributed under the same license as the git package.
+# Xudong Guan <xudong.guan@gmail.com> and the zh-kernel.org mailing list, 2007
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui glossary\n"
+"PO-Revision-Date: 2007-07-23 22:07+0200\n"
+"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n"
+"Language-Team: Simplified Chinese \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 "注:这个文件是为了帮助翻译人员统一åè¯æœ¯è¯­ã€‚最终用户ä¸ä¼šå…³å¿ƒè¿™ä¸ªæ–‡ä»¶ã€‚"
+
+#. ""
+#. amend指用户修改最近一次commitçš„æ“作,修订?修改?修正?
+#. [WANG Cong]: æ ¹æ®æˆ‘的了解,这个è¯ä¼¼ä¹Žç¿»è¯‘æˆâ€œä¿®è®¢â€å¤šä¸€äº›ã€‚“修正â€ä¹Ÿå¯ä»¥ï¼Œâ€œä¿®æ”¹â€å†æ¬¡ä¹‹ã€‚
+#. [ZHANG Le]: 修订,感觉一般指对一些大型出版物的大规模å‡çº§ï¼Œæ¯”如修订新åŽå­—å…¸
+# 修正,其实æ¯æ¬¡amend的结果也ä¸ä¸€å®šå°±æ˜¯æœ€åŽç»“果,说ä¸å®šè¿˜éœ€è¦ä¿®æ”¹ã€‚所以ä¸
+# 如就å«ä¿®æ”¹
+msgid "amend"
+msgstr "修订"
+
+#. ""
+#. git annotate 文件å:用æ¥æ ‡æ³¨æ–‡ä»¶çš„æ¯ä¸€è¡Œåœ¨ä»€ä¹ˆæ—¶å€™è¢«è°æœ€åŽä¿®æ”¹ã€‚
+#. [WANG Cong]: "标记"一般是mark。;)
+#. [ZHANG Le]: 标注,或者干脆用原æ„:注解,或注释
+msgid "annotate"
+msgstr "标注"
+
+#. "A 'branch' is an active line of development."
+msgid "branch [noun]"
+msgstr "分支"
+
+#. ""
+msgid "branch [verb]"
+msgstr "建立分支"
+
+#. ""
+#. [WANG Cong]: 网上有人翻译æˆâ€œæ£€å‡ºâ€ï¼Œæˆ‘感觉更好一些,毕竟把checkçš„æ„æ€ç¿»è¯‘出æ¥äº†ã€‚
+#. [ZHNAG Le]: æå–å§ï¼Œæå–分支ï¼ç‰ˆæœ¬
+#. [rae l]: 签出。subversion软件中的大多è¯æ±‡å·²æœ‰ç¿»è¯‘,既然git与subversionåŒæ˜¯SCM管ç†ï¼Œå¯ä»¥å‚考åŒç±»è½¯ä»¶çš„翻译也ä¸é”™ã€‚
+msgid "checkout [noun]"
+msgstr "签出"
+
+#. "The action of updating the working tree to a revision which was stored in the object database."
+msgid "checkout [verb]"
+msgstr "签出"
+
+#. "A single point in the git history."
+msgid "commit [noun]"
+msgstr "æ交"
+
+#. "The action of storing a new snapshot of the project's state in the git history."
+msgid "commit [verb]"
+msgstr "æ交"
+
+#. ""
+#. 差异?差别?
+#. [ZHANG Le]: 个人感觉差别更加中性一些
+msgid "diff [noun]"
+msgstr "差别"
+
+#. ""
+msgid "diff [verb]"
+msgstr "比较"
+
+#. "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 "å¿«è¿›å¼åˆå¹¶"
+
+#. "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."
+#. 获å–?å–得?下载?更新?注æ„å’Œupdate的区分
+msgid "fetch"
+msgstr "获å–"
+
+#. "A collection of files. The index is a stored version of your working tree."
+#. index是working tree和repository之间的缓存
+msgid "index (in git-gui: staging area)"
+msgstr "工作缓存?"
+
+#. "A successful merge results in the creation of a new commit representing the result of the merge."
+msgid "merge [noun]"
+msgstr "åˆå¹¶"
+
+#. "To bring the contents of another branch into the current branch."
+msgid "merge [verb]"
+msgstr "åˆå¹¶"
+
+#. ""
+#. message是指commit中的文字信æ¯
+msgid "message"
+msgstr "æè¿°"
+
+#. "Pulling a branch means to fetch it and merge it."
+msgid "pull"
+msgstr "获å–+åˆå¹¶"
+
+#. "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 "推入"
+
+#. ""
+msgid "redo"
+msgstr "é‡åš"
+
+#. "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 "仓库"
+
+#. ""
+msgid "reset"
+msgstr "é‡ç½®"
+
+#. ""
+msgid "revert"
+msgstr "æ¢å¤"
+
+#. "A particular state of files and directories which was stored in the object database."
+msgid "revision"
+msgstr "版本"
+
+#. ""
+msgid "sign off"
+msgstr "ç­¾å"
+
+#. ""
+#. 似乎是git-gui里é¢æ˜¾ç¤ºçš„本次æ交的文件清å•åŒºåŸŸ
+msgid "staging area"
+msgstr "æ交暂存区"
+
+#. ""
+msgid "status"
+msgstr "状æ€"
+
+#. "A ref pointing to a tag or commit object"
+msgid "tag [noun]"
+msgstr "标签"
+
+#. ""
+msgid "tag [verb]"
+msgstr "添加标签"
+
+#. "A regular git branch that is used to follow changes from another repository."
+msgid "tracking branch"
+msgstr "跟踪分支"
+
+#. ""
+msgid "undo"
+msgstr "撤销"
+
+#. ""
+msgid "update"
+msgstr "更新。注æ„å’Œfetch的区分"
+
+#. ""
+msgid "verify"
+msgstr "验è¯"
+
+#. "The tree of actual checked out files."
+#. "工作副本?工作区域?工作目录"
+#. [LI Yang]: 当å‰å‰¯æœ¬ï¼Œ 当å‰æºç æ ‘?
+msgid "working copy, working tree"
+msgstr "工作副本,工作æºç æ ‘"
diff --git a/git-gui/po/hu.po b/git-gui/po/hu.po
new file mode 100644
index 0000000000..627c05eb99
--- /dev/null
+++ b/git-gui/po/hu.po
@@ -0,0 +1,1937 @@
+# Hungarian translations for git-gui-i package.
+# Copyright (C) 2007 THE git-gui-i'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the git-gui-i package.
+# Miklos Vajna <vmiklos@frugalware.org>, 2007.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui-i 18n\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-11-24 10:36+0100\n"
+"PO-Revision-Date: 2007-12-04 01:15+0100\n"
+"Last-Translator: Miklos Vajna <vmiklos@frugalware.org>\n"
+"Language-Team: Hungarian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: git-gui.sh:41 git-gui.sh:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714
+#: git-gui.sh:733
+msgid "git-gui: fatal error"
+msgstr "git-gui: végzetes hiba"
+
+#: git-gui.sh:565
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "Érvénytelen font lett megadva itt: %s:"
+
+#: git-gui.sh:590
+msgid "Main Font"
+msgstr "Fő betűtípus"
+
+#: git-gui.sh:591
+msgid "Diff/Console Font"
+msgstr "Diff/konzol betűtípus"
+
+#: git-gui.sh:605
+msgid "Cannot find git in PATH."
+msgstr "A git nem található a PATH-ban."
+
+#: git-gui.sh:632
+msgid "Cannot parse Git version string:"
+msgstr "Nem értelmezhető a Git verzió sztring:"
+
+#: git-gui.sh:650
+#, 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 ""
+"Nem állípítható meg a Git verziója.\n"
+"\n"
+"A(z) %s szerint a verzió '%s'.\n"
+"\n"
+"A(z) %s a Git 1.5.0 vagy későbbi verzióját igényli.\n"
+"\n"
+"Feltételezhetjük, hogy a(z) '%s' verziója legalább 1.5.0?\n"
+
+#: git-gui.sh:888
+msgid "Git directory not found:"
+msgstr "A Git könyvtár nem található:"
+
+#: git-gui.sh:895
+msgid "Cannot move to top of working directory:"
+msgstr "Nem lehet a munkakönyvtár tetejére lépni:"
+
+#: git-gui.sh:902
+msgid "Cannot use funny .git directory:"
+msgstr "Nem használható vicces .git könyvtár:"
+
+#: git-gui.sh:907
+msgid "No working directory"
+msgstr "Nincs munkakönyvtár"
+
+#: git-gui.sh:1054
+msgid "Refreshing file status..."
+msgstr "A fájlok státuszának frissítése..."
+
+#: git-gui.sh:1119
+msgid "Scanning for modified files ..."
+msgstr "Módosított fájlok keresése ..."
+
+#: git-gui.sh:1294 lib/browser.tcl:245
+msgid "Ready."
+msgstr "Kész."
+
+#: git-gui.sh:1560
+msgid "Unmodified"
+msgstr "Nem módosított"
+
+#: git-gui.sh:1562
+msgid "Modified, not staged"
+msgstr "Módosított, de nem kiválasztott"
+
+#: git-gui.sh:1563 git-gui.sh:1568
+msgid "Staged for commit"
+msgstr "Kiválasztva commitolásra"
+
+#: git-gui.sh:1564 git-gui.sh:1569
+msgid "Portions staged for commit"
+msgstr "Részek kiválasztva commitolásra"
+
+#: git-gui.sh:1565 git-gui.sh:1570
+msgid "Staged for commit, missing"
+msgstr "Kiválasztva commitolásra, hiányzó"
+
+#: git-gui.sh:1567
+msgid "Untracked, not staged"
+msgstr "Nem követett, nem kiválasztott"
+
+#: git-gui.sh:1572
+msgid "Missing"
+msgstr "Hiányzó"
+
+#: git-gui.sh:1573
+msgid "Staged for removal"
+msgstr "Kiválasztva eltávolításra"
+
+#: git-gui.sh:1574
+msgid "Staged for removal, still present"
+msgstr "Kiválasztva eltávolításra, jelenleg is elérhető"
+
+#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579
+msgid "Requires merge resolution"
+msgstr "Merge feloldás szükséges"
+
+#: git-gui.sh:1614
+msgid "Starting gitk... please wait..."
+msgstr "A gitk indítása... várjunk..."
+
+#: git-gui.sh:1623
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+"A gitk indítása sikertelen:\n"
+"\n"
+"A(z) %s nem létezik"
+
+#: git-gui.sh:1823 lib/choose_repository.tcl:35
+msgid "Repository"
+msgstr "Repó"
+
+#: git-gui.sh:1824
+msgid "Edit"
+msgstr "Szerkesztés"
+
+#: git-gui.sh:1826 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr "Branch"
+
+#: git-gui.sh:1829 lib/choose_rev.tcl:547
+msgid "Commit@@noun"
+msgstr "Commit@@főnév"
+
+#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "Merge"
+
+#: git-gui.sh:1833 lib/choose_rev.tcl:556
+msgid "Remote"
+msgstr "Távoli"
+
+#: git-gui.sh:1842
+msgid "Browse Current Branch's Files"
+msgstr "A jelenlegi branch fájljainak böngészése"
+
+#: git-gui.sh:1846
+msgid "Browse Branch Files..."
+msgstr "A branch fájljainak böngészése..."
+
+#: git-gui.sh:1851
+msgid "Visualize Current Branch's History"
+msgstr "A jelenlegi branch történetének vizualizálása"
+
+#: git-gui.sh:1855
+msgid "Visualize All Branch History"
+msgstr "Az összes branch történetének vizualizálása"
+
+#: git-gui.sh:1862
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "A(z) %s branch fájljainak böngészése"
+
+#: git-gui.sh:1864
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "A(z) %s branch történetének vizualizálása"
+
+#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "Adatbázis statisztikák"
+
+#: git-gui.sh:1872 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "Adatbázis tömörítése"
+
+#: git-gui.sh:1875
+msgid "Verify Database"
+msgstr "Adatbázis ellenőrzése"
+
+#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr "Asztal ikon létrehozása"
+
+#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
+msgid "Quit"
+msgstr "Kilépés"
+
+#: git-gui.sh:1902
+msgid "Undo"
+msgstr "Visszavonás"
+
+#: git-gui.sh:1905
+msgid "Redo"
+msgstr "Mégis"
+
+#: git-gui.sh:1909 git-gui.sh:2403
+msgid "Cut"
+msgstr "Kivágás"
+
+#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr "Másolás"
+
+#: git-gui.sh:1915 git-gui.sh:2409
+msgid "Paste"
+msgstr "Beillesztés"
+
+#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "Törlés"
+
+#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69
+msgid "Select All"
+msgstr "Mindent kiválaszt"
+
+#: git-gui.sh:1931
+msgid "Create..."
+msgstr "Létrehozás..."
+
+#: git-gui.sh:1937
+msgid "Checkout..."
+msgstr "Checkout..."
+
+#: git-gui.sh:1943
+msgid "Rename..."
+msgstr "Ãtnevezés..."
+
+#: git-gui.sh:1948 git-gui.sh:2048
+msgid "Delete..."
+msgstr "Törlés..."
+
+#: git-gui.sh:1953
+msgid "Reset..."
+msgstr "Visszaállítás..."
+
+#: git-gui.sh:1965 git-gui.sh:2350
+msgid "New Commit"
+msgstr "Új commit"
+
+#: git-gui.sh:1973 git-gui.sh:2357
+msgid "Amend Last Commit"
+msgstr "Utolsó commit javítása"
+
+#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "Keresés újra"
+
+#: git-gui.sh:1988
+msgid "Stage To Commit"
+msgstr "Kiválasztás commitolásra"
+
+#: git-gui.sh:1994
+msgid "Stage Changed Files To Commit"
+msgstr "Módosított fájlok kiválasztása commitolásra"
+
+#: git-gui.sh:2000
+msgid "Unstage From Commit"
+msgstr "Commitba való kiválasztás visszavonása"
+
+#: git-gui.sh:2005 lib/index.tcl:393
+msgid "Revert Changes"
+msgstr "Változtatások visszaállítása"
+
+#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427
+msgid "Sign Off"
+msgstr "Aláír"
+
+#: git-gui.sh:2016 git-gui.sh:2333
+msgid "Commit@@verb"
+msgstr "Commit@@ige"
+
+#: git-gui.sh:2027
+msgid "Local Merge..."
+msgstr "Helyi merge..."
+
+#: git-gui.sh:2032
+msgid "Abort Merge..."
+msgstr "Merge megszakítása..."
+
+#: git-gui.sh:2044
+msgid "Push..."
+msgstr "Push..."
+
+#: git-gui.sh:2055 lib/choose_repository.tcl:40
+msgid "Apple"
+msgstr "Apple"
+
+#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
+#, tcl-format
+msgid "About %s"
+msgstr "Névjegy: %s"
+
+#: git-gui.sh:2062
+msgid "Preferences..."
+msgstr "Beállítások..."
+
+#: git-gui.sh:2070 git-gui.sh:2595
+msgid "Options..."
+msgstr "Opciók..."
+
+#: git-gui.sh:2076 lib/choose_repository.tcl:46
+msgid "Help"
+msgstr "Segítség"
+
+#: git-gui.sh:2117
+msgid "Online Documentation"
+msgstr "Online dokumentáció"
+
+#: git-gui.sh:2201
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr "végzetes hiba: nem érhető el a(z) %s útvonal: Nincs ilyen fájl vagy könyvtár"
+
+#: git-gui.sh:2234
+msgid "Current Branch:"
+msgstr "Jelenlegi branch:"
+
+#: git-gui.sh:2255
+msgid "Staged Changes (Will Commit)"
+msgstr "Kiválasztott változtatások (commitolva lesz)"
+
+#: git-gui.sh:2274
+msgid "Unstaged Changes"
+msgstr "Kiválasztatlan változtatások"
+
+#: git-gui.sh:2323
+msgid "Stage Changed"
+msgstr "Változtatások kiválasztása"
+
+#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "Push"
+
+#: git-gui.sh:2369
+msgid "Initial Commit Message:"
+msgstr "Kezdeti commit üzenet:"
+
+#: git-gui.sh:2370
+msgid "Amended Commit Message:"
+msgstr "Javító commit üzenet:"
+
+#: git-gui.sh:2371
+msgid "Amended Initial Commit Message:"
+msgstr "Kezdeti javító commit üzenet:"
+
+#: git-gui.sh:2372
+msgid "Amended Merge Commit Message:"
+msgstr "Javító merge commit üzenet:"
+
+#: git-gui.sh:2373
+msgid "Merge Commit Message:"
+msgstr "Merge commit üzenet:"
+
+#: git-gui.sh:2374
+msgid "Commit Message:"
+msgstr "Commit üzenet:"
+
+#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71
+msgid "Copy All"
+msgstr "Összes másolása"
+
+#: git-gui.sh:2443 lib/blame.tcl:104
+msgid "File:"
+msgstr "Fájl:"
+
+#: git-gui.sh:2545
+msgid "Refresh"
+msgstr "Frissítés"
+
+#: git-gui.sh:2566
+msgid "Apply/Reverse Hunk"
+msgstr "Hunk alkalmazása/visszaállítása"
+
+#: git-gui.sh:2572
+msgid "Decrease Font Size"
+msgstr "Font méret csökkentése"
+
+#: git-gui.sh:2576
+msgid "Increase Font Size"
+msgstr "Fönt méret növelése"
+
+#: git-gui.sh:2581
+msgid "Show Less Context"
+msgstr "Kevesebb környezet mutatása"
+
+#: git-gui.sh:2588
+msgid "Show More Context"
+msgstr "Több környezet mutatása"
+
+#: git-gui.sh:2602
+msgid "Unstage Hunk From Commit"
+msgstr "Hunk törlése commitból"
+
+#: git-gui.sh:2604
+msgid "Stage Hunk For Commit"
+msgstr "Hunk kiválasztása commitba"
+
+#: git-gui.sh:2623
+msgid "Initializing..."
+msgstr "Inicializálás..."
+
+#: git-gui.sh:2718
+#, 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 ""
+"Lehetséges, hogy környezeti problémák vannak.\n"
+"\n"
+"A következő környezeti változók valószínűleg\n"
+"figyelmen kívül lesznek hagyva a(z) %s által\n"
+"indított folyamatok által:\n"
+"\n"
+
+#: git-gui.sh:2748
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"Ez a Cygwin által terjesztett Tcl binárisban\n"
+"lévő ismert hiba miatt van."
+
+#: git-gui.sh:2753
+#, 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"
+"Egy jó helyettesítés a(z) %s számára\n"
+"a user.name és user.email beállítások\n"
+"elhelyezése a személyes\n"
+"~/.gitconfig fájlba.\n"
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - egy grafikus felület a Githez."
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr "Fájl néző"
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr "Commit:"
+
+#: lib/blame.tcl:249
+msgid "Copy Commit"
+msgstr "Commit másolása"
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr "A(z) %s olvasása..."
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr "A másolást/átnevezést követő annotációk betöltése..."
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr "sor annotálva"
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr "Az eredeti hely annotációk betöltése..."
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr "Az annotáció kész."
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr "Az annotáció betöltése..."
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr "Szerző:"
+
+#: lib/blame.tcl:791
+msgid "Committer:"
+msgstr "Commiter:"
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr "Eredeti fájl:"
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr "Eredeti szerző:"
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr "Ebben a fájlban:"
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr "Ide másolta vagy helyezte:"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "Branch checkoutolása"
+
+#: 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:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr "Mégsem"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr "Revízió"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+msgid "Options"
+msgstr "Opciók"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Követő branch letöltése"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "Helyi branch leválasztása"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "Branch létrehozása"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "Új branch létrehozása"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
+msgid "Create"
+msgstr "Létrehozás"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "Branch neve"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr "Név:"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "Egyeztetendő követési branch név"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "A következő revíziótól"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Létező branch frissítése"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Nem"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Csak fast forward"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr "Visszaállítás"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Checkout létrehozás után"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "Válasszunk ki egy követő branchet."
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr "A(z) %s követő branch nem branch a távoli repóban."
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr "Adjunk megy egy branch nevet."
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "A(z) '%s' nem egy elfogadható branch név."
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr "Branch törlése"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "Helyi branch törlése"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "Helyi branchek"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "Csak már merge-ölt törlése"
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr "Mindig (Ne legyen merge teszt.)"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "A következő branchek nem teljesen lettek merge-ölve ebbe: %s:"
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+"A törölt branchek visszaállítása bonyolult. \n"
+"\n"
+" Biztosan törli a kiválasztott brancheket?"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Nem sikerült törölni a következő brancheket:\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "Branch átnevezése"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "Ãtnevezés"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "Branch:"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "Új név:"
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr "Válasszunk ki egy átnevezendő branchet."
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "A(z) '%s' branch már létezik."
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Nem sikerült átnevezni: '%s'."
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Indítás..."
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "Fájl böngésző"
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr "A(z) %s betöltése..."
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr "[Fel a szülőhöz]"
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+msgid "Browse Branch Files"
+msgstr "A branch fájljainak böngészése"
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
+msgid "Browse"
+msgstr "Böngészés"
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "A(z) %s letöltése innen: %s"
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "végzetes: Nem lehet feloldani a következőt: %s"
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr "Bezárás"
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "A(z) '%s' branch nem létezik."
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"A(z) '%s' branch már létezik.\n"
+"\n"
+"Nem lehet fast-forwardolni a következőhöz: %s.\n"
+"Egy merge szükséges."
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "A(z) '%s' merge strategy nem támogatott."
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "Nem sikerült frissíteni a következőt: '%s'."
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr "A kiválasztási terület (index) már zárolva van."
+
+#: lib/checkout_op.tcl:266
+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 ""
+"Az utolsó keresési állapot nem egyezik meg a repó állpotával.\n"
+"\n"
+"Egy másik Git program módosította ezt a repót az utolsó keresés óta. Egy "
+"újrakeresés mindenképpen szükséges mielőtt a jelenlegi branchet módosítani "
+"lehetne.\n"
+"\n"
+"Az újrakeresés most automatikusan el fog indulni.\n"
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "A munkkönyvtár frissiítése a következőre: '%s'..."
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr "A(z) '%s' checkoutja megszakítva (fájlszintű merge-ölés szükséges)."
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr "Fájlszintű merge-ölés szükséges."
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Jelenleg a(z) '%s' branchen."
+
+#: lib/checkout_op.tcl:429
+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 ""
+"Már nem egy helyi branchen vagyunk.\n"
+"\n"
+"Ha egy branchen szeretnénk lenni, hozzunk létre egyet az 'Ez a leválasztott "
+"checkout'-ból."
+
+#: lib/checkout_op.tcl:446
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "'%s' kifejtve."
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr ""
+"A(z) '%s' -> '%s' visszaállítás a következő commitok elvesztését jelenti:"
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr "Az elveszett commitok helyreállítása nem biztos, hogy egyszerű."
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "Visszaállítjuk a következőt: '%s'?"
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr "Vizualizálás"
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+"Nem sikerült beállítani a jelenlegi branchet.\n"
+"\n"
+"A munkakönyvtár csak részben váltott át. A fájlok sikeresen frissítve "
+"lettek, de nem sikerült frissíteni egy belső Git fájlt.\n"
+"\n"
+"Ennek nem szabad megtörténnie. A(z) %s most kilép és feladja."
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "Kiválaszt"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "Font család"
+
+#: lib/choose_font.tcl:73
+msgid "Font Size"
+msgstr "Font méret"
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr "Font példa"
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"Ez egy példa szöveg.\n"
+"Ha ez megfelel, ez lehet a betűtípus."
+
+#: lib/choose_repository.tcl:27
+msgid "Git Gui"
+msgstr "Git Gui"
+
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
+msgid "Create New Repository"
+msgstr "Új repó létrehozása"
+
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr "Új..."
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
+msgid "Clone Existing Repository"
+msgstr "Létező repó másolása"
+
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr "Másolás..."
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
+msgid "Open Existing Repository"
+msgstr "Létező könyvtár megnyitása"
+
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr "Meggyitás..."
+
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr "Legutóbbi repók"
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr "Legutóbbi repók megnyitása:"
+
+#: lib/choose_repository.tcl:294
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "A(z) '%s' hely már létezik."
+
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Nem sikerült letrehozni a(z) %s repót:"
+
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
+msgid "Directory:"
+msgstr "Könyvtár:"
+
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
+msgid "Git Repository"
+msgstr "Git repó"
+
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "A(z) '%s' könyvtár már létezik."
+
+#: lib/choose_repository.tcl:442
+#, tcl-format
+msgid "File %s already exists."
+msgstr "A(z) '%s' fájl már létezik."
+
+#: lib/choose_repository.tcl:463
+msgid "Clone"
+msgstr "Bezárás"
+
+#: lib/choose_repository.tcl:476
+msgid "URL:"
+msgstr "URL:"
+
+#: lib/choose_repository.tcl:496
+msgid "Clone Type:"
+msgstr "Másolás típusa:"
+
+#: lib/choose_repository.tcl:502
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Ãltalános (Gyors, félig-redundáns, hardlinkek)"
+
+#: lib/choose_repository.tcl:508
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Teljes másolás (Lassabb, redundáns biztonsági mentés)"
+
+#: lib/choose_repository.tcl:514
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Megosztott (Leggyorsabb, nem ajánlott, nincs mentés)"
+
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Nem Git repó: %s"
+
+#: lib/choose_repository.tcl:586
+msgid "Standard only available for local repository."
+msgstr "A standard csak helyi repókra érhető el."
+
+#: lib/choose_repository.tcl:590
+msgid "Shared only available for local repository."
+msgstr "A megosztott csak helyi repókra érhető el."
+
+#: lib/choose_repository.tcl:617
+msgid "Failed to configure origin"
+msgstr "Nem sikerült beállítani az origint"
+
+#: lib/choose_repository.tcl:629
+msgid "Counting objects"
+msgstr "Objektumok számolása"
+
+#: lib/choose_repository.tcl:630
+msgid "buckets"
+msgstr "vödrök"
+
+#: lib/choose_repository.tcl:654
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Nem sikerült másolni az objects/info/alternates-t: %s"
+
+#: lib/choose_repository.tcl:690
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Semmi másolni való nincs innen: %s"
+
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
+msgid "The 'master' branch has not been initialized."
+msgstr "A 'master' branch nincs inicializálva."
+
+#: lib/choose_repository.tcl:705
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr "Nem érhetőek el hardlinkek. Másolás használata."
+
+#: lib/choose_repository.tcl:717
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Másolás innen: %s"
+
+#: lib/choose_repository.tcl:748
+msgid "Copying objects"
+msgstr "Objektumok másolása"
+
+#: lib/choose_repository.tcl:749
+msgid "KiB"
+msgstr "KiB"
+
+#: lib/choose_repository.tcl:773
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Nem sikerült másolni az objektumot: %s"
+
+#: lib/choose_repository.tcl:783
+msgid "Linking objects"
+msgstr "Objektumok összefűzése"
+
+#: lib/choose_repository.tcl:784
+msgid "objects"
+msgstr "objektum"
+
+#: lib/choose_repository.tcl:792
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Nem sikerült hardlinkelni az objektumot: %s"
+
+#: lib/choose_repository.tcl:847
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr "Nem sikerült letölteni a branch-eket és az objektumokat. Bővebben a konzolos kimenetben."
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch tags. See console output for details."
+msgstr "Nem sikerült letölteni a tageket. Bővebben a konzolos kimenetben."
+
+#: lib/choose_repository.tcl:882
+msgid "Cannot determine HEAD. See console output for details."
+msgstr "Nem sikerült megállapítani a HEAD-et. Bővebben a konzolos kimenetben."
+
+#: lib/choose_repository.tcl:891
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "Nem sikerült tiszítani: %s."
+
+#: lib/choose_repository.tcl:897
+msgid "Clone failed."
+msgstr "A másolás nem sikerült."
+
+#: lib/choose_repository.tcl:904
+msgid "No default branch obtained."
+msgstr "Nincs alapértelmezett branch."
+
+#: lib/choose_repository.tcl:915
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "Nem sikerült felöldani a(z) %s objektumot commitként."
+
+#: lib/choose_repository.tcl:927
+msgid "Creating working directory"
+msgstr "Munkakönyvtár létrehozása"
+
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr "fájl"
+
+#: lib/choose_repository.tcl:957
+msgid "Initial file checkout failed."
+msgstr "A kezdeti fájl-kibontás sikertelen."
+
+#: lib/choose_repository.tcl:973
+msgid "Open"
+msgstr "Megnyitás"
+
+#: lib/choose_repository.tcl:983
+msgid "Repository:"
+msgstr "Repó:"
+
+#: lib/choose_repository.tcl:1033
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Nem sikerült megnyitni a(z) %s repót:"
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr "Ez a leválasztott checkout"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Revízió kifejezés:"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "Helyi branch"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "Követő branch"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr "Tag"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Érvénytelen revízió: %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "Nincs kiválasztva revízió."
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "A revízió kifejezés üres."
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr "Frissítve"
+
+#: lib/choose_rev.tcl:558
+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 ""
+"Nincs semmi javítanivaló.\n"
+"\n"
+"Az első commit létrehozása előtt nincs semmilyen commit amit javitani "
+"lehetne.\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 ""
+"Nem lehet javítani merge alatt.\n"
+"\n"
+"A jelenlegi merge még nem teljesen fejeződött be. Csak akkor javíthat egy "
+"előbbi commitot, hogyha megszakítja a jelenlegi merge folyamatot.\n"
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr "Hiba a javítandó commit adat betöltése közben:"
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr "Nem sikerült megállapítani az azonosítót:"
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Érvénytelen GIT_COMMITTER_IDENT:"
+
+#: lib/commit.tcl:133
+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 ""
+"Az utolsó keresési állapot nem egyezik meg a repó állapotával.\n"
+"\n"
+"Egy másik Git program módosította ezt a repót az utolsó keresés óta. Egy "
+"újrakeresés mindenképpen szükséges mielőtt a jelenlegi branchet módosítani "
+"lehetne.\n"
+"\n"
+"Az újrakeresés most automatikusan el fog indulni.\n"
+
+#: lib/commit.tcl:154
+#, 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 ""
+"Nem commitolhatunk fájlokat merge előtt.\n"
+"\n"
+"A(z) %s fájlban ütközések vannak. Egyszer azokat ki kell javítani, majd "
+"hozzá ki kell választani a fájlt mielőtt commitolni lehetne.\n"
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Ismeretlen fájl típus %s érzékelve.\n"
+"\n"
+"A(z) %s fájlt nem tudja ez a program commitolni.\n"
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Nincs commitolandó változtatás.\n"
+"\n"
+"Legalább egy fájl ki kell választani, hogy commitolni lehessen.\n"
+
+#: lib/commit.tcl:183
+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 ""
+"Adjunk megy egy commit üzenetet.\n"
+"\n"
+"Egy jó commit üzenetnek a következő a formátuma:\n"
+"\n"
+"- Első sor: Egy mondatban leírja, hogy mit csináltunk.\n"
+"- Második sor: Üres\n"
+"- A többi sor: Leírja, hogy miért jó ez a változtatás.\n"
+
+#: lib/commit.tcl:257
+msgid "write-tree failed:"
+msgstr "a write-tree sikertelen:"
+
+#: lib/commit.tcl:275
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "A(z) %s commit sérültnek tűnik"
+
+#: lib/commit.tcl:279
+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 ""
+"Nincs commitolandó változtatás.\n"
+"\n"
+"Egyetlen fájlt se módosított ez a commit és merge commit se volt.\n"
+"\n"
+"Az újrakeresés most automatikusan el fog indulni.\n"
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr "Nincs commitolandó változtatás."
+
+#: lib/commit.tcl:303
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "figyelmeztetés: a Tcl nem támogatja a(z) '%s' kódolást."
+
+#: lib/commit.tcl:317
+msgid "commit-tree failed:"
+msgstr "a commit-tree sikertelen:"
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr "az update-ref sikertelen:"
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Létrejött a %s commit: %s"
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr "Munka folyamatban.. Várjunk..."
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr "Siker"
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr "Hiba: a parancs sikertelen"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "Elvesztett objektumok száma"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "Elveszett objektumok által elfoglalt lemezterület"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "Csomagolt objektumok számra"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "Csomagok száma"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "A csomagolt objektumok által használt lemezterület"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "Eltávolításra váró csomagolt objektumok számra"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "Hulladék fájlok"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Az objektum adatbázis tömörítése"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Az objektum adatbázis ellenőrzése az fsck-objects használatával"
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Ennek a repónak jelenleg %i különálló objektuma van.\n"
+"\n"
+"Az optimális teljesítményhez erősen ajánlott az adatbázis tömörítése, ha "
+"több mint %i objektum létezik.\n"
+"\n"
+"Lehet most tömöríteni az adatbázist?"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Érvénytelen dátum a Git-től: %s"
+
+#: lib/diff.tcl:42
+#, 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 ""
+"Nincsenek változások.\n"
+"\n"
+"A(z) %s módosítatlan.\n"
+"\n"
+"A fájl módosítási dátumát frissítette egy másik alkalmazás, de a fájl "
+"tartalma változatlan.\n"
+"\n"
+"Egy újrakeresés fog indulni a hasonló állapotú fájlok megtalálása érdekében."
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "A(z) %s diff-jének betöltése..."
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Nem lehet megjeleníteni a következőt: %s"
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr "Hiba a fájl betöltése közben:"
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr "Git repó (alprojekt)"
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr "* Bináris fájl (tartalom elrejtése)."
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr "Hiba a diff betöltése közben:"
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr "Nem visszavonni a hunk kiválasztását."
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr "Nem sikerült kiválasztani a hunkot."
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr "hiba"
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr "figyelmeztetés"
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr "Ki kell javítanunk a fenti hibákat commit előtt."
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Nem sikerült az index zárolásának feloldása."
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "Index hiba"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"A Git index frissítése sikertelen volt. Egy újraolvasás automatikusan elindult, hogy "
+"a git-gui újra szinkonban legyen."
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "Folytatás"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "Index zárolásának feloldása"
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "A(z) %s commitba való kiválasztásának visszavonása"
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr "A(z) %s hozzáadása..."
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Visszaállítja a változtatásokat a(z) %s fájlban?"
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "Visszaállítja a változtatásokat ebben e %i fájlban?"
+
+#: lib/index.tcl:389
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+"Minden nem kiválasztott változtatás el fog veszni ezáltal a visszaállítás "
+"által."
+
+#: lib/index.tcl:392
+msgid "Do Nothing"
+msgstr "Ne csináljunk semmit"
+
+#: 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 ""
+"Javítás közben nem lehetséges a merge.\n"
+"\n"
+"Egyszer be kell fejezni ennek a commitnak a javítását, majd kezdődhet egy "
+"merge.\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 ""
+"Az utolsó keresési állapot nem egyezik meg a repó állapotával.\n"
+"\n"
+"Egy másik Git program módosította ezt a repót az utolsó keresés óta. Egy "
+"újrakeresés mindenképpen szükséges mielőtt a jelenlegi branchet módosítani "
+"lehetne.\n"
+"\n"
+"Az újrakeresés most automatikusan el fog indulni.\n"
+
+#: lib/merge.tcl:44
+#, 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 ""
+"Jelenleg egy ütközés feloldása közben vagyunk.\n"
+"\n"
+"A(z) %s fájlban ütközések vannak.\n"
+"\n"
+"Fel kell oldanunk őket, kiválasztani a fájlt, és commitolni hogy befejezzük "
+"a jelenlegi merge-t. Csak ezután kezdhetünk el egy újabbat.\n"
+
+#: lib/merge.tcl:54
+#, 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 ""
+"Jelenleg egy változtatás közben vagyunk.\n"
+"\n"
+"A(z) %s fájl megváltozott.\n"
+"\n"
+"Először be kell fejeznünk a jelenlegi commitot, hogy elkezdhessünk egy merge-"
+"t. Ez segíteni fog, hogy félbeszakíthassunk egy merge-t.\n"
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s / %s"
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr "A(z) %s és a(z) %s merge-ölése"
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr "A merge sikeresen befejeződött."
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr "A merge sikertelen. Fel kell oldanunk az ütközéseket."
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Merge-ölés a következőbe: %s"
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr "Merge-ölni szándékozott revízió"
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"A commit javítás közben megszakítva.\n"
+"\n"
+"Be kell fejeznünk ennek a commitnak a javítását.\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 ""
+"Megszakítjuk a merge-t?\n"
+"\n"
+"A jelenlegi merge megszakítása *MINDEN* nem commitolt változtatás "
+"elvesztését jelenti.\n"
+"\n"
+"Folytatjuk a jelenlegi merge megszakítását?"
+
+#: 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 ""
+"Visszavonjuk a módosításokat?\n"
+"\n"
+"A módosítások visszavonása *MINDEN* nem commitolt változtatás elvesztését "
+"jelenti.\n"
+"\n"
+"Folytatjuk a jelenlegi módosítások visszavonását?"
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr "Félbeszakítás"
+
+#: lib/merge.tcl:266
+msgid "Abort failed."
+msgstr "A félbeszakítás nem sikerült."
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr "A megkeszakítás befejeződött. Kész."
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr "Alapértelmezés visszaállítása"
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr "Mentés"
+
+#: lib/option.tcl:96
+#, tcl-format
+msgid "%s Repository"
+msgstr "%s Repó"
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr "Globális (minden repó)"
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr "Felhasználónév"
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr "Email cím"
+
+#: lib/option.tcl:106
+msgid "Summarize Merge Commits"
+msgstr "A merge commitok összegzése"
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr "Merge beszédesség"
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr "Diffstat mutatása merge után"
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr "A fájl módosítási dátumok megbízhatóak"
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr "A követő branchek eltávolítása letöltés alatt"
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr "A követő branchek egyeztetése"
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr "A diff környezeti sorok száma"
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr "Új branch név sablon"
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr "Betűtípus megváltoztatása"
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr "%s választása"
+
+#: lib/option.tcl:186
+msgid "pt."
+msgstr "pt."
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr "Beállítások"
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr "Nem sikerült teljesen elmenteni a beállításokat:"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr "Távoli branch törlése"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "Forrás repó"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr "Távoli:"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr "Tetszőleges URL:"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "Branchek"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "Törlés csak akkor ha"
+
+#: lib/remote_branch_delete.tcl:111
+msgid "Merged Into:"
+msgstr "Merge-ölt a következőbe:"
+
+#: lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Mindig (Ne végezzen merge vizsgálatokat)"
+
+#: lib/remote_branch_delete.tcl:152
+msgid "A branch is required for 'Merged Into'."
+msgstr "Egy branch szükséges a 'Merge-ölt a következőbe'-hez."
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"A következő branchek nem teljesen lettek merge-ölve ebbe: %s:"
+"\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 ""
+"Egy vagy több merge teszt hibát jelzett, mivel nem töltöttük le a megfelelő "
+"commitokat. Próbáljunk meg letölteni a következőből: %s először."
+
+#: lib/remote_branch_delete.tcl:207
+msgid "Please select one or more branches to delete."
+msgstr "Válasszunk ki egy vagy több branchet törlésre."
+
+#: lib/remote_branch_delete.tcl:216
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"A törölt branchek visszaállítása nehéz.\n"
+"\n"
+"Töröljük a kiválasztott brancheket?"
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr "Brancek törlése innen: %s"
+
+#: lib/remote_branch_delete.tcl:286
+msgid "No repository selected."
+msgstr "Nincs kiválasztott repó."
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "Keresés itt: %s..."
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "Törlés innen"
+
+# tcl-format
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "Letöltés innen"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "Push ide"
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "Nem sikerült írni a gyorsbillentyűt:"
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr "Nem sikerült írni az ikont:"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s ... %*i / %*i %s (%3i%%)"
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "a(z) %s letöltése"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Új változások letöltése innen: %s"
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "a(z) %s távoli törlése"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "A %s repóból törölt követő branchek törlése"
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "%s push-olása"
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Változások pusholása ide: %s"
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "Pusholás: %s %s, ide: %s"
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr "Branchek pusholása"
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr "Forrás branchek"
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr "Cél repó"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr "Ãtviteli opciók"
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr "Létező branch felülírásának erőltetése (lehet, hogy el fog dobni változtatásokat)"
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr "Vékony csomagok használata (lassú hálózati kapcsolatok számára)"
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr "Tageket is"
+
+#~ msgid "Cannot find the git directory:"
+#~ msgstr "Nem található a git könyvtár:"
+
+#~ msgid "Unstaged Changes (Will Not Be Committed)"
+#~ msgstr "Nem kiválasztott változtatások (nem lesz commitolva)"
+
+#~ msgid "Push to %s..."
+#~ msgstr "Pusholás ide: %s..."
+
+#~ msgid "Add To Commit"
+#~ msgstr "Hozzáadás a commithoz"
+
+#~ msgid "Add Existing To Commit"
+#~ msgstr "Hozzáadás létező commithoz"
+
+#~ msgid "Running miga..."
+#~ msgstr "A miga futtatása..."
+
+#~ msgid "Add Existing"
+#~ msgstr "Létező hozzáadása"
+
+#~ msgid ""
+#~ "Abort commit?\n"
+#~ "\n"
+#~ "Aborting the current commit will cause *ALL* uncommitted changes to be "
+#~ "lost.\n"
+#~ "\n"
+#~ "Continue with aborting the current commit?"
+#~ msgstr ""
+#~ "Megszakítjuk a commitot?\n"
+#~ "\n"
+#~ "A jelenlegi commit megszakítása *MINDEN* nem commitolt változtatás "
+#~ "elvesztését jelenti.\n"
+#~ "\n"
+#~ "Folytatjuk a jelenlegi commit megszakítását?"
+
+#~ msgid "Aborting... please wait..."
+#~ msgstr "Megszakítás... várjunk..."
diff --git a/git-gui/po/it.po b/git-gui/po/it.po
new file mode 100644
index 0000000000..33a8399175
--- /dev/null
+++ b/git-gui/po/it.po
@@ -0,0 +1,1913 @@
+# Translation of git-gui to Italian
+# Copyright (C) 2007 Shawn Pearce
+# This file is distributed under the same license as the git-gui package.
+# Paolo Ciarrocchi <paolo.ciarrocchi@gmail.com>, 2007
+# Michele Ballabio <barra_cuda@katamail.com>, 2007.
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-11-09 11:18+0100\n"
+"PO-Revision-Date: 2007-11-01 21:05+0100\n"
+"Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n"
+"Language-Team: Italian <tp@lists.linux.it>\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:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714
+#: git-gui.sh:733
+msgid "git-gui: fatal error"
+msgstr "git-gui: errore grave"
+
+#: git-gui.sh:565
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "Caratteri non validi specificati in %s:"
+
+#: git-gui.sh:590
+msgid "Main Font"
+msgstr "Caratteri principali"
+
+#: git-gui.sh:591
+msgid "Diff/Console Font"
+msgstr "Caratteri per confronti e terminale"
+
+#: git-gui.sh:605
+msgid "Cannot find git in PATH."
+msgstr "Impossibile trovare git nel PATH"
+
+#: git-gui.sh:632
+msgid "Cannot parse Git version string:"
+msgstr "Impossibile determinare la versione di Git:"
+
+#: git-gui.sh:650
+#, 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 ""
+"La versione di Git non può essere determinata.\n"
+"\n"
+"%s riporta che la versione è '%s'.\n"
+"\n"
+"%s richiede almeno Git 1.5.0 o superiore.\n"
+"\n"
+"Assumere che '%s' sia alla versione 1.5.0?\n"
+
+#: git-gui.sh:888
+msgid "Git directory not found:"
+msgstr "Non trovo la directory di git: "
+
+#: git-gui.sh:895
+msgid "Cannot move to top of working directory:"
+msgstr "Impossibile spostarsi sulla directory principale del progetto:"
+
+#: git-gui.sh:902
+msgid "Cannot use funny .git directory:"
+msgstr "Impossibile usare una .git directory strana:"
+
+#: git-gui.sh:907
+msgid "No working directory"
+msgstr "Nessuna directory di lavoro"
+
+#: git-gui.sh:1054
+msgid "Refreshing file status..."
+msgstr "Controllo dello stato dei file in corso..."
+
+#: git-gui.sh:1119
+msgid "Scanning for modified files ..."
+msgstr "Ricerca di file modificati in corso..."
+
+#: git-gui.sh:1294 lib/browser.tcl:245
+msgid "Ready."
+msgstr "Pronto."
+
+#: git-gui.sh:1560
+msgid "Unmodified"
+msgstr "Non modificato"
+
+#: git-gui.sh:1562
+msgid "Modified, not staged"
+msgstr "Modificato, non preparato per una nuova revisione"
+
+#: git-gui.sh:1563 git-gui.sh:1568
+msgid "Staged for commit"
+msgstr "Preparato per una nuova revisione"
+
+#: git-gui.sh:1564 git-gui.sh:1569
+msgid "Portions staged for commit"
+msgstr "Parti preparate per una nuova revisione"
+
+#: git-gui.sh:1565 git-gui.sh:1570
+msgid "Staged for commit, missing"
+msgstr "Preparato per una nuova revisione, mancante"
+
+#: git-gui.sh:1567
+msgid "Untracked, not staged"
+msgstr "Non tracciato, non preparato per una nuova revisione"
+
+#: git-gui.sh:1572
+msgid "Missing"
+msgstr "Mancante"
+
+#: git-gui.sh:1573
+msgid "Staged for removal"
+msgstr "Preparato per la rimozione"
+
+#: git-gui.sh:1574
+msgid "Staged for removal, still present"
+msgstr "Preparato alla rimozione, ancora presente"
+
+#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579
+msgid "Requires merge resolution"
+msgstr "Richiede risoluzione dei conflitti"
+
+#: git-gui.sh:1614
+msgid "Starting gitk... please wait..."
+msgstr "Avvio di gitk... attendere..."
+
+#: git-gui.sh:1623
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+"Impossibile avviare gitk:\n"
+"\n"
+"%s non esiste"
+
+#: git-gui.sh:1823 lib/choose_repository.tcl:35
+msgid "Repository"
+msgstr "Archivio"
+
+#: git-gui.sh:1824
+msgid "Edit"
+msgstr "Modifica"
+
+#: git-gui.sh:1826 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr "Ramo"
+
+#: git-gui.sh:1829 lib/choose_rev.tcl:547
+msgid "Commit@@noun"
+msgstr "Revisione"
+
+#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "Fusione (Merge)"
+
+#: git-gui.sh:1833 lib/choose_rev.tcl:556
+msgid "Remote"
+msgstr "Remoto"
+
+#: git-gui.sh:1842
+msgid "Browse Current Branch's Files"
+msgstr "Esplora i file del ramo attuale"
+
+#: git-gui.sh:1846
+msgid "Browse Branch Files..."
+msgstr "Esplora i file del ramo..."
+
+#: git-gui.sh:1851
+msgid "Visualize Current Branch's History"
+msgstr "Visualizza la cronologia del ramo attuale"
+
+#: git-gui.sh:1855
+msgid "Visualize All Branch History"
+msgstr "Visualizza la cronologia di tutti i rami"
+
+#: git-gui.sh:1862
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "Esplora i file di %s"
+
+#: git-gui.sh:1864
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "Visualizza la cronologia di %s"
+
+#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "Statistiche dell'archivio"
+
+#: git-gui.sh:1872 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "Comprimi l'archivio"
+
+#: git-gui.sh:1875
+msgid "Verify Database"
+msgstr "Verifica l'archivio"
+
+#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr "Crea icona desktop"
+
+#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
+msgid "Quit"
+msgstr "Esci"
+
+#: git-gui.sh:1902
+msgid "Undo"
+msgstr "Annulla"
+
+#: git-gui.sh:1905
+msgid "Redo"
+msgstr "Ripeti"
+
+#: git-gui.sh:1909 git-gui.sh:2403
+msgid "Cut"
+msgstr "Taglia"
+
+#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr "Copia"
+
+#: git-gui.sh:1915 git-gui.sh:2409
+msgid "Paste"
+msgstr "Incolla"
+
+#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "Elimina"
+
+#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69
+msgid "Select All"
+msgstr "Seleziona tutto"
+
+#: git-gui.sh:1931
+msgid "Create..."
+msgstr "Crea..."
+
+#: git-gui.sh:1937
+msgid "Checkout..."
+msgstr "Attiva..."
+
+#: git-gui.sh:1943
+msgid "Rename..."
+msgstr "Rinomina"
+
+#: git-gui.sh:1948 git-gui.sh:2048
+msgid "Delete..."
+msgstr "Elimina..."
+
+#: git-gui.sh:1953
+msgid "Reset..."
+msgstr "Ripristina..."
+
+#: git-gui.sh:1965 git-gui.sh:2350
+msgid "New Commit"
+msgstr "Nuova revisione"
+
+#: git-gui.sh:1973 git-gui.sh:2357
+msgid "Amend Last Commit"
+msgstr "Correggi l'ultima revisione"
+
+#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "Analizza nuovamente"
+
+#: git-gui.sh:1988
+msgid "Stage To Commit"
+msgstr "Prepara per una nuova revisione"
+
+#: git-gui.sh:1994
+msgid "Stage Changed Files To Commit"
+msgstr "Prepara i file modificati per una nuova revisione"
+
+#: git-gui.sh:2000
+msgid "Unstage From Commit"
+msgstr "Annulla preparazione"
+
+#: git-gui.sh:2005 lib/index.tcl:393
+msgid "Revert Changes"
+msgstr "Annulla modifiche"
+
+#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427
+msgid "Sign Off"
+msgstr "Sign Off"
+
+#: git-gui.sh:2016 git-gui.sh:2333
+msgid "Commit@@verb"
+msgstr "Nuova revisione"
+
+#: git-gui.sh:2027
+msgid "Local Merge..."
+msgstr "Fusione locale..."
+
+#: git-gui.sh:2032
+msgid "Abort Merge..."
+msgstr "Interrompi fusione..."
+
+#: git-gui.sh:2044
+msgid "Push..."
+msgstr "Propaga..."
+
+#: git-gui.sh:2055 lib/choose_repository.tcl:40
+msgid "Apple"
+msgstr "Apple"
+
+#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
+#, tcl-format
+msgid "About %s"
+msgstr "Informazioni su %s"
+
+#: git-gui.sh:2062
+msgid "Preferences..."
+msgstr "Preferenze..."
+
+#: git-gui.sh:2070 git-gui.sh:2595
+msgid "Options..."
+msgstr "Opzioni..."
+
+#: git-gui.sh:2076 lib/choose_repository.tcl:46
+msgid "Help"
+msgstr "Aiuto"
+
+#: git-gui.sh:2117
+msgid "Online Documentation"
+msgstr "Documentazione sul web"
+
+#: git-gui.sh:2201
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr ""
+"errore grave: impossibile effettuare lo stat del path %s: file o directory "
+"non trovata"
+
+#: git-gui.sh:2234
+msgid "Current Branch:"
+msgstr "Ramo attuale:"
+
+#: git-gui.sh:2255
+msgid "Staged Changes (Will Commit)"
+msgstr "Modifiche preparate (saranno nella nuova revisione)"
+
+#: git-gui.sh:2274
+msgid "Unstaged Changes"
+msgstr "Modifiche non preparate"
+
+#: git-gui.sh:2323
+msgid "Stage Changed"
+msgstr "Prepara modificati"
+
+#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "Propaga (Push)"
+
+#: git-gui.sh:2369
+msgid "Initial Commit Message:"
+msgstr "Messaggio di revisione iniziale:"
+
+#: git-gui.sh:2370
+msgid "Amended Commit Message:"
+msgstr "Messaggio di revisione corretto:"
+
+#: git-gui.sh:2371
+msgid "Amended Initial Commit Message:"
+msgstr "Messaggio iniziale di revisione corretto:"
+
+#: git-gui.sh:2372
+msgid "Amended Merge Commit Message:"
+msgstr "Messaggio di fusione corretto:"
+
+#: git-gui.sh:2373
+msgid "Merge Commit Message:"
+msgstr "Messaggio di fusione:"
+
+#: git-gui.sh:2374
+msgid "Commit Message:"
+msgstr "Messaggio di revisione:"
+
+#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71
+msgid "Copy All"
+msgstr "Copia tutto"
+
+#: git-gui.sh:2443 lib/blame.tcl:104
+msgid "File:"
+msgstr "File:"
+
+#: git-gui.sh:2545
+msgid "Refresh"
+msgstr "Rinfresca"
+
+#: git-gui.sh:2566
+msgid "Apply/Reverse Hunk"
+msgstr "Applica/Inverti sezione"
+
+#: git-gui.sh:2572
+msgid "Decrease Font Size"
+msgstr "Diminuisci dimensione caratteri"
+
+#: git-gui.sh:2576
+msgid "Increase Font Size"
+msgstr "Aumenta dimensione caratteri"
+
+#: git-gui.sh:2581
+msgid "Show Less Context"
+msgstr "Mostra meno contesto"
+
+#: git-gui.sh:2588
+msgid "Show More Context"
+msgstr "Mostra più contesto"
+
+#: git-gui.sh:2602
+msgid "Unstage Hunk From Commit"
+msgstr "Sezione non preparata per una nuova revisione"
+
+#: git-gui.sh:2604
+msgid "Stage Hunk For Commit"
+msgstr "Prepara sezione per una nuova revisione"
+
+#: git-gui.sh:2623
+msgid "Initializing..."
+msgstr "Inizializzazione..."
+
+#: git-gui.sh:2718
+#, 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 ""
+"Possibili problemi con le variabili d'ambiente.\n"
+"\n"
+"Le seguenti variabili d'ambiente saranno probabilmente\n"
+"ignorate da tutti i sottoprocessi di Git avviati\n"
+"da %s:\n"
+"\n"
+
+#: git-gui.sh:2748
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"Ciò è dovuto a un problema conosciuto\n"
+"causato dall'eseguibile Tcl distribuito da Cygwin."
+
+#: git-gui.sh:2753
+#, 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"
+"Una buona alternativa a %s\n"
+"consiste nell'assegnare valori alle variabili di configurazione\n"
+"user.name e user.email nel tuo file ~/.gitconfig personale.\n"
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - un'interfaccia grafica per Git."
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr "Mostra file"
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr "Revisione:"
+
+#: lib/blame.tcl:249
+msgid "Copy Commit"
+msgstr "Copia revisione"
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr "Lettura di %s..."
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr "Caricamento annotazioni per copie/spostamenti..."
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr "linee annotate"
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr "Caricamento annotazioni per posizione originaria..."
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr "Annotazione completata."
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr "Caricamento annotazioni..."
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr "Autore:"
+
+#: lib/blame.tcl:791
+msgid "Committer:"
+msgstr "Revisione creata da:"
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr "File originario:"
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr "In origine da:"
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr "Nel file:"
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr "Copiato o spostato qui da:"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "Attiva ramo"
+
+#: lib/branch_checkout.tcl:23
+msgid "Checkout"
+msgstr "Attiva"
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr "Annulla"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr "Revisione"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+msgid "Options"
+msgstr "Opzioni"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Recupera duplicato locale di ramo remoto"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "Stacca da ramo locale"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "Crea ramo"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "Crea nuovo ramo"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
+msgid "Create"
+msgstr "Crea"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "Nome del ramo"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr "Nome:"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "Appaia nome del duplicato locale di ramo remoto"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Revisione iniziale"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Aggiorna ramo esistente:"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "No"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Solo fast forward"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr "Ripristina"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Attiva dopo la creazione"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "Scegliere un duplicato locale di ramo remoto"
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr ""
+"Il duplicato locale del ramo remoto %s non è un ramo nell'archivio remoto."
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr "Inserire un nome per il ramo."
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "'%s' non è utilizzabile come nome di ramo."
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr "Elimina ramo"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "Elimina ramo locale"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "Rami locali"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "Cancella solo se fuso con un altro ramo"
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr "Sempre (Non effettuare verifiche di fusione)."
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "I rami seguenti non sono stati fusi completamente in %s:"
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+"Ricomporre rami cancellati può essere complicato. \n"
+"\n"
+" Eliminare i rami selezionati?"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Impossibile cancellare i rami:\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "Rinomina ramo"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "Rinomina"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "Ramo:"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "Nuovo Nome:"
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr "Scegliere un ramo da rinominare."
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "Il ramo '%s' esiste già."
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Impossibile rinominare '%s'."
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Avvio in corso..."
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "File browser"
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr "Caricamento %s..."
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr "[Directory superiore]"
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+msgid "Browse Branch Files"
+msgstr "Esplora i file del ramo"
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
+msgid "Browse"
+msgstr "Sfoglia"
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Recupero %s da %s"
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "errore grave: impossibile risolvere %s"
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr "Chiudi"
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "Il ramo '%s' non esiste."
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"Il ramo '%s' esiste già.\n"
+"\n"
+"Non può effettuare un 'fast-forward' a %s.\n"
+"E' necessaria una fusione."
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "La strategia di fusione '%s' non è supportata."
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "Impossibile aggiornare '%s'."
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr ""
+"L'area di preparazione per una nuova revisione (indice) è già bloccata."
+
+#: lib/checkout_op.tcl:266
+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 ""
+"L'ultimo stato analizzato non corrisponde allo stato dell'archivio.\n"
+"\n"
+"Un altro programma Git ha modificato questo archivio dall'ultima analisi. "
+"Bisogna effettuare una nuova analisi prima di poter cambiare il ramo "
+"attuale.\n"
+"\n"
+"La nuova analisi comincerà ora.\n"
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "Aggiornamento della directory di lavoro a '%s' in corso..."
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr "Attivazione di '%s' fallita (richiesta una fusione a livello file)."
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr "E' richiesta una fusione a livello file."
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Si rimarrà sul ramo '%s'."
+
+#: lib/checkout_op.tcl:429
+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 ""
+"Non si è più su un ramo locale\n"
+"\n"
+"Se si vuole rimanere su un ramo, crearne uno ora a partire da 'Questa "
+"revisione attiva staccata'."
+
+#: lib/checkout_op.tcl:446
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "Attivazione di '%s' completata."
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr ""
+"Ripristinare '%s' a '%s' comporterà la perdita delle seguenti revisioni:"
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr "Ricomporre le revisioni perdute potrebbe non essere semplice."
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "Ripristinare '%s'?"
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr "Visualizza"
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+"Impossibile preparare il ramo attuale.\n"
+"\n"
+"Questa directory di lavoro è stata convertita solo parzialmente. I file sono "
+"stati aggiornati correttamente, ma l'aggiornamento di un file di Git ha "
+"prodotto degli errori.\n"
+"\n"
+"Questo non sarebbe dovuto succedere. %s ora terminerà senza altre azioni."
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "Seleziona"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "Famiglia di caratteri"
+
+#: lib/choose_font.tcl:73
+msgid "Font Size"
+msgstr "Dimensione caratteri"
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr "Esempio caratteri"
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"Questo è un testo d'esempio.\n"
+"Se ti piace questo testo, può essere il carattere giusto."
+
+#: lib/choose_repository.tcl:27
+msgid "Git Gui"
+msgstr "Git Gui"
+
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
+msgid "Create New Repository"
+msgstr "Crea nuovo archivio"
+
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr "Nuovo..."
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
+msgid "Clone Existing Repository"
+msgstr "Clona archivio esistente"
+
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr "Clona..."
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
+msgid "Open Existing Repository"
+msgstr "Apri archivio esistente"
+
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr "Apri..."
+
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr "Archivi recenti"
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr "Apri archivio recente:"
+
+#: lib/choose_repository.tcl:294
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "La posizione %s esiste già."
+
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Impossibile creare l'archivio %s:"
+
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
+msgid "Directory:"
+msgstr "Directory:"
+
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
+msgid "Git Repository"
+msgstr "Archivio Git"
+
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "La directory %s esiste già."
+
+#: lib/choose_repository.tcl:442
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Il file %s esiste già."
+
+#: lib/choose_repository.tcl:463
+msgid "Clone"
+msgstr "Clona"
+
+#: lib/choose_repository.tcl:476
+msgid "URL:"
+msgstr "URL:"
+
+#: lib/choose_repository.tcl:496
+msgid "Clone Type:"
+msgstr "Tipo di clone:"
+
+#: lib/choose_repository.tcl:502
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Standard (veloce, semi-ridondante, con hardlink)"
+
+#: lib/choose_repository.tcl:508
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Copia completa (più lento, backup ridondante)"
+
+#: lib/choose_repository.tcl:514
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Shared (il più veloce, non raccomandato, nessun backup)"
+
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "%s non è un archivio Git."
+
+#: lib/choose_repository.tcl:586
+msgid "Standard only available for local repository."
+msgstr "Standard è disponibile solo per archivi locali."
+
+#: lib/choose_repository.tcl:590
+msgid "Shared only available for local repository."
+msgstr "Shared è disponibile solo per archivi locali."
+
+#: lib/choose_repository.tcl:617
+msgid "Failed to configure origin"
+msgstr "Impossibile configurare origin"
+
+#: lib/choose_repository.tcl:629
+msgid "Counting objects"
+msgstr "Calcolo oggetti"
+
+#: lib/choose_repository.tcl:630
+msgid "buckets"
+msgstr ""
+
+#: lib/choose_repository.tcl:654
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Impossibile copiare oggetti/info/alternate: %s"
+
+#: lib/choose_repository.tcl:690
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Niente da clonare da %s."
+
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
+msgid "The 'master' branch has not been initialized."
+msgstr "Il ramo 'master' non è stato inizializzato."
+
+#: lib/choose_repository.tcl:705
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr "Impossibile utilizzare gli hardlink. Si ricorrerà alla copia."
+
+#: lib/choose_repository.tcl:717
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Clonazione da %s"
+
+#: lib/choose_repository.tcl:748
+msgid "Copying objects"
+msgstr "Copia degli oggetti"
+
+#: lib/choose_repository.tcl:749
+msgid "KiB"
+msgstr "KiB"
+
+#: lib/choose_repository.tcl:773
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Impossibile copiare oggetto: %s"
+
+#: lib/choose_repository.tcl:783
+msgid "Linking objects"
+msgstr "Collegamento oggetti"
+
+#: lib/choose_repository.tcl:784
+msgid "objects"
+msgstr "oggetti"
+
+#: lib/choose_repository.tcl:792
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Hardlink impossibile sull'oggetto: %s"
+
+#: lib/choose_repository.tcl:847
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr ""
+"Impossibile recuperare rami e oggetti. Controllare i dettagli forniti dalla "
+"console."
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch tags. See console output for details."
+msgstr ""
+"Impossibile recuperare le etichette. Controllare i dettagli forniti dalla "
+"console."
+
+#: lib/choose_repository.tcl:882
+msgid "Cannot determine HEAD. See console output for details."
+msgstr ""
+"Impossibile determinare HEAD. Controllare i dettagli forniti dalla console."
+
+#: lib/choose_repository.tcl:891
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "Impossibile ripulire %s"
+
+#: lib/choose_repository.tcl:897
+msgid "Clone failed."
+msgstr "Clonazione non riuscita."
+
+#: lib/choose_repository.tcl:904
+msgid "No default branch obtained."
+msgstr "Non è stato trovato un ramo predefinito."
+
+#: lib/choose_repository.tcl:915
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "Impossibile risolvere %s come una revisione."
+
+#: lib/choose_repository.tcl:927
+msgid "Creating working directory"
+msgstr "Creazione directory di lavoro"
+
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr "file"
+
+#: lib/choose_repository.tcl:957
+msgid "Initial file checkout failed."
+msgstr "Attivazione iniziale non riuscita."
+
+#: lib/choose_repository.tcl:973
+msgid "Open"
+msgstr "Apri"
+
+#: lib/choose_repository.tcl:983
+msgid "Repository:"
+msgstr "Archivio:"
+
+#: lib/choose_repository.tcl:1033
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Impossibile accedere all'archivio %s:"
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr "Questa revisione attiva staccata"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Espressione di revisione:"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "Ramo locale"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "Duplicato locale di ramo remoto"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr "Etichetta"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Revisione non valida: %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "Nessuna revisione selezionata."
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "L'espressione di revisione è vuota."
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr "Aggiornato"
+
+#: lib/choose_rev.tcl:558
+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 ""
+"Non c'è niente da correggere.\n"
+"\n"
+"Stai per creare la revisione iniziale. Non esiste una revisione precedente "
+"da correggere.\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 ""
+"Non è possibile effettuare una correzione durante una fusione.\n"
+"\n"
+"In questo momento si sta effettuando una fusione che non è stata del tutto "
+"completata. Non puoi correggere la revisione precedente a meno che prima tu "
+"non interrompa l'operazione di fusione in corso.\n"
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr "Errore durante il caricamento dei dati della revisione da correggere:"
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr "Impossibile ottenere la tua identità:"
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "GIT_COMMITTER_IDENT non valida:"
+
+#: lib/commit.tcl:133
+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 ""
+"L'ultimo stato analizzato non corrisponde allo stato dell'archivio.\n"
+"\n"
+"Un altro programma Git ha modificato questo archivio dall'ultima analisi. "
+"Bisogna effettuare una nuova analisi prima di poter creare una nuova "
+"revisione.\n"
+"\n"
+"La nuova analisi comincerà ora.\n"
+
+#: lib/commit.tcl:154
+#, 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 ""
+"Non è possibile creare una revisione con file non sottoposti a fusione.\n"
+"\n"
+"Il file %s presenta dei conflitti. Devi risolverli e preparare il file per "
+"creare una nuova revisione prima di effettuare questa azione.\n"
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Stato di file %s sconosciuto.\n"
+"\n"
+"Questo programma non può creare una revisione contenente il file %s.\n"
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Nessuna modifica per la nuova revisione.\n"
+"\n"
+"Devi preparare per una nuova revisione almeno 1 file prima di effettuare "
+"questa operazione.\n"
+
+#: lib/commit.tcl:183
+msgid ""
+"Please supply a commit message.\n"
+"\n"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentance what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
+msgstr ""
+"Bisogna fornire un messaggio di revisione.\n"
+"\n"
+"Un buon messaggio di revisione ha il seguente formato:\n"
+"\n"
+"- Prima linea: descrivi in una frase ciò che hai fatto.\n"
+"- Seconda linea: vuota.\n"
+"- Terza linea: spiega a cosa serve la tua modifica.\n"
+
+#: lib/commit.tcl:257
+msgid "write-tree failed:"
+msgstr "write-tree non riuscito:"
+
+#: lib/commit.tcl:275
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "La revisione %s sembra essere danneggiata"
+
+#: lib/commit.tcl:279
+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 ""
+"Nessuna modifica per la nuova revisione.\n"
+"\n"
+"Questa revisione non modifica alcun file e non effettua alcuna fusione.\n"
+"\n"
+"Si procederà subito ad una nuova analisi.\n"
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr "Nessuna modifica per la nuova revisione."
+
+#: lib/commit.tcl:303
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "attenzione: Tcl non supporta la codifica '%s'."
+
+#: lib/commit.tcl:317
+msgid "commit-tree failed:"
+msgstr "commit-tree non riuscito:"
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr "update-ref non riuscito:"
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Creata revisione %s: %s"
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr "Elaborazione in corso... attendere..."
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr "Successo"
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr "Errore: comando non riuscito"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "Numero di oggetti slegati"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "Spazio su disco utilizzato da oggetti slegati"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "Numero di oggetti impacchettati"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "Numero di pacchetti"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "Spazio su disco utilizzato da oggetti impacchettati"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "Oggetti impacchettati che attendono la potatura"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "File inutili"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Compressione dell'archivio in corso"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Verifica dell'archivio con fsck-objects in corso"
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Questo archivio attualmente ha circa %i oggetti slegati.\n"
+"\n"
+"Per mantenere buone prestazioni si raccomanda di comprimere l'archivio "
+"quando sono presenti più di %i oggetti slegati.\n"
+"\n"
+"Comprimere l'archivio ora?"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Git ha restituito una data non valida: %s"
+
+#: lib/diff.tcl:42
+#, 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 ""
+"Non sono state trovate differenze.\n"
+"\n"
+"%s non ha modifiche.\n"
+"\n"
+"La data di modifica di questo file è stata cambiata da un'altra "
+"applicazione, ma il contenuto del file è rimasto invariato.\n"
+"\n"
+"Si procederà automaticamente ad una nuova analisi per trovare altri file che "
+"potrebbero avere lo stesso stato."
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Caricamento delle differenze di %s..."
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Impossibile visualizzare %s"
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr "Errore nel caricamento del file:"
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr "Archivio Git (sottoprogetto)"
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr "* File binario (il contenuto non sarà mostrato)."
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr "Errore nel caricamento delle differenze:"
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr "Impossibile rimuovere la sezione scelta dalla nuova revisione."
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr "Impossibile preparare la sezione scelta per una nuova revisione."
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr "errore"
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr "attenzione"
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr ""
+"Bisogna correggere gli errori suddetti prima di creare una nuova revisione."
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Impossibile sbloccare l'accesso all'indice"
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "Errore nell'indice"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"Impossibile aggiornare l'indice. Ora sarà avviata una nuova analisi che "
+"aggiornerà git-gui."
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "Continua"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "Sblocca l'accesso all'indice"
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "%s non farà parte della prossima revisione"
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr "Aggiunta di %s in corso"
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Annullare le modifiche nel file %s?"
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "Annullare le modifiche in questi %i file?"
+
+#: lib/index.tcl:389
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+"Tutte le modifiche non preparate per una nuova revisione saranno perse per "
+"sempre."
+
+#: lib/index.tcl:392
+msgid "Do Nothing"
+msgstr "Non fare niente"
+
+#: 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 ""
+"Non posso effettuare fusioni durante una correzione.\n"
+"\n"
+"Bisogna finire di correggere questa revisione prima di iniziare una "
+"qualunque fusione.\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 ""
+"L'ultimo stato analizzato non corrisponde allo stato dell'archivio.\n"
+"\n"
+"Un altro programma Git ha modificato questo archivio dall'ultima analisi."
+"Bisogna effettuare una nuova analisi prima di poter effettuare una fusione.\n"
+"\n"
+"La nuova analisi comincerà ora.\n"
+
+#: lib/merge.tcl:44
+#, 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 ""
+"Sei nel mezzo di una fusione con conflitti.\n"
+"\n"
+"Il file %s ha dei conflitti.\n"
+"\n"
+"Bisogna risolvere i conflitti, preparare il file per una nuova revisione ed "
+"infine crearla per completare la fusione attuale. Solo a questo punto potrai "
+"iniziare un'altra fusione.\n"
+
+#: lib/merge.tcl:54
+#, 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 ""
+"Sei nel mezzo di una modifica.\n"
+"\n"
+"Il file %s è stato modificato.\n"
+"\n"
+"Bisogna completare la creazione della revisione attuale prima di iniziare "
+"una fusione. In questo modo sarà più facile interrompere una fusione non "
+"riuscita, nel caso ce ne fosse bisogno.\n"
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s di %s"
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr "Fusione di %s e %s in corso"
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr "Fusione completata con successo."
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr "Fusione non riuscita. Bisogna risolvere i conflitti."
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Fusione in %s"
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr "Revisione da fondere"
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Interruzione impossibile durante una correzione.\n"
+"\n"
+"Bisogna finire di correggere questa revisione.\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 ""
+"Interrompere fusione?\n"
+"\n"
+"L'interruzione della fusione attuale causerà la perdita di *TUTTE* le "
+"modifiche non ancora presenti nell'archivio.\n"
+"\n"
+"Continuare con l'interruzione della fusione attuale?"
+
+#: 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 ""
+"Ripristinare la revisione corrente e annullare le modifiche?\n"
+"\n"
+"L'annullamento delle modifiche causerà la perdita di *TUTTE* le modifiche "
+"non ancora presenti nell'archivio.\n"
+"\n"
+"Continuare con l'annullamento delle modifiche attuali?"
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr "Interruzione in corso"
+
+#: lib/merge.tcl:266
+msgid "Abort failed."
+msgstr "Interruzione non riuscita."
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr "Interruzione completata. Pronto."
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr "Ripristina valori predefiniti"
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr "Salva"
+
+#: lib/option.tcl:96
+#, tcl-format
+msgid "%s Repository"
+msgstr "Archivio di %s"
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr "Tutti gli archivi"
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr "Nome utente"
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr "Indirizzo Email"
+
+#: lib/option.tcl:106
+msgid "Summarize Merge Commits"
+msgstr "Riepilogo nelle revisioni di fusione"
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr "Prolissità della fusione"
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr "Mostra statistiche delle differenze dopo la fusione"
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr "Fidati delle date di modifica dei file"
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr ""
+"Effettua potatura dei duplicati locali di rami remoti durante il recupero"
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr "Appaia duplicati locali di rami remoti"
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr "Numero di linee di contesto nelle differenze"
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr "Modello per il nome di un nuovo ramo"
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr "Cambia caratteri"
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr "Scegli %s"
+
+#: lib/option.tcl:186
+msgid "pt."
+msgstr "pt."
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr "Preferenze"
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr "Impossibile salvare completamente le opzioni:"
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "Effettua potatura da"
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "Recupera da"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "Propaga verso"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr "Cancella ramo remoto"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "Da archivio"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr "Remoto:"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr "URL specifico:"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "Rami"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "Elimina solo se"
+
+#: lib/remote_branch_delete.tcl:111
+msgid "Merged Into:"
+msgstr "Fuso in:"
+
+#: lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Sempre (non verificare le fusioni)"
+
+#: lib/remote_branch_delete.tcl:152
+msgid "A branch is required for 'Merged Into'."
+msgstr "Si richiede un ramo per 'Fuso in'."
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"I rami seguenti non sono stati fusi completamente in %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 ""
+"Impossibile verificare una o più fusioni: mancano le revisioni necessarie. "
+"Prova prima a recuperarle da %s."
+
+#: lib/remote_branch_delete.tcl:207
+msgid "Please select one or more branches to delete."
+msgstr "Scegliere uno o più rami da cancellare."
+
+#: lib/remote_branch_delete.tcl:216
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Ricomporre rami cancellati è difficile.\n"
+"\n"
+"Cancellare i rami selezionati?"
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr "Cancellazione rami da %s"
+
+#: lib/remote_branch_delete.tcl:286
+msgid "No repository selected."
+msgstr "Nessun archivio selezionato."
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "Analisi in corso %s..."
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "Impossibile scrivere shortcut:"
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr "Impossibile scrivere icona:"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%1$s ... %6$s: %2$*i di %4$*i (%7$3i%%)"
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "recupera da %s"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Recupero nuove modifiche da %s"
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "potatura remota di %s"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Effettua potatura dei duplicati locali di rami remoti cancellati da %s"
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "propaga verso %s"
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Propagazione modifiche a %s"
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "Propagazione %s %s a %s"
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr "Propaga rami"
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr "Rami di origine"
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr "Archivio di destinazione"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr "Opzioni di trasferimento"
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr "Sovrascrivi ramo esistente (alcune modifiche potrebbero essere perse)"
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr "Utilizza 'thin pack' (per connessioni lente)"
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr "Includi etichette"
diff --git a/git-gui/po/ja.po b/git-gui/po/ja.po
new file mode 100644
index 0000000000..e2cf5bdc06
--- /dev/null
+++ b/git-gui/po/ja.po
@@ -0,0 +1,1883 @@
+# Translation of git-gui to Japanese
+# Copyright (C) 2007 Shawn Pearce
+# This file is distributed under the same license as the git-gui package.
+# ã—らã„ã— ãªãªã“ <nanako3@bluebottle.com>, 2007.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-11-24 10:36+0100\n"
+"PO-Revision-Date: 2007-12-05 06:12+0900\n"
+"Last-Translator: ã—らã„ã— ãªãªã“ <nanako3@bluebottle.com>\n"
+"Language-Team: Japanese\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:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714
+#: git-gui.sh:733
+msgid "git-gui: fatal error"
+msgstr "git-gui: 致命的ãªã‚¨ãƒ©ãƒ¼"
+
+#: git-gui.sh:565
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "%s ã«ç„¡åŠ¹ãªãƒ•ã‚©ãƒ³ãƒˆãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã™:"
+
+#: git-gui.sh:590
+msgid "Main Font"
+msgstr "主フォント"
+
+#: git-gui.sh:591
+msgid "Diff/Console Font"
+msgstr "diff/コンソール・フォント"
+
+#: git-gui.sh:605
+msgid "Cannot find git in PATH."
+msgstr "PATH 中㫠git ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: git-gui.sh:632
+msgid "Cannot parse Git version string:"
+msgstr "Git ãƒãƒ¼ã‚¸ãƒ§ãƒ³åãŒç†è§£ã§ãã¾ã›ã‚“:"
+
+#: git-gui.sh:650
+#, 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 ""
+"Git ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒç¢ºèªã§ãã¾ã›ã‚“。\n"
+"\n"
+"%s ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³ '%s' ã¨ã®ã“ã¨ã§ã™ã€‚\n"
+"\n"
+"%s ã¯æœ€ä½Žã§ã‚‚ 1.5.0 ã‹ãれ以é™ã® Git ãŒå¿…è¦ã§ã™\n"
+"\n"
+"'%s' ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³ 1.5.0 ã¨æ€ã£ã¦è‰¯ã„ã§ã™ã‹ï¼Ÿ\n"
+
+#: git-gui.sh:888
+msgid "Git directory not found:"
+msgstr "Git ディレクトリãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“:"
+
+#: git-gui.sh:895
+msgid "Cannot move to top of working directory:"
+msgstr "作業ディレクトリã®æœ€ä¸Šä½ã«ç§»å‹•ã§ãã¾ã›ã‚“"
+
+#: git-gui.sh:902
+msgid "Cannot use funny .git directory:"
+msgstr "変㪠.git ディレクトリã¯ä½¿ãˆã¾ã›ã‚“"
+
+#: git-gui.sh:907
+msgid "No working directory"
+msgstr "作業ディレクトリãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: git-gui.sh:1054
+msgid "Refreshing file status..."
+msgstr "ファイル状態を更新ã—ã¦ã„ã¾ã™â€¦"
+
+#: git-gui.sh:1119
+msgid "Scanning for modified files ..."
+msgstr "変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’スキャンã—ã¦ã„ã¾ã™â€¦"
+
+#: git-gui.sh:1294 lib/browser.tcl:245
+msgid "Ready."
+msgstr "準備完了"
+
+#: git-gui.sh:1560
+msgid "Unmodified"
+msgstr "変更無ã—"
+
+#: git-gui.sh:1562
+msgid "Modified, not staged"
+msgstr "変更ã‚ã‚Šã€ã‚³ãƒŸãƒƒãƒˆæœªäºˆå®š"
+
+#: git-gui.sh:1563 git-gui.sh:1568
+msgid "Staged for commit"
+msgstr "コミット予定済"
+
+#: git-gui.sh:1564 git-gui.sh:1569
+msgid "Portions staged for commit"
+msgstr "部分的ã«ã‚³ãƒŸãƒƒãƒˆäºˆå®šæ¸ˆ"
+
+#: git-gui.sh:1565 git-gui.sh:1570
+msgid "Staged for commit, missing"
+msgstr "コミット予定済ã€ãƒ•ã‚¡ã‚¤ãƒ«ç„¡ã—"
+
+#: git-gui.sh:1567
+msgid "Untracked, not staged"
+msgstr "管ç†å¤–ã€ã‚³ãƒŸãƒƒãƒˆæœªäºˆå®š"
+
+#: git-gui.sh:1572
+msgid "Missing"
+msgstr "ファイル無ã—"
+
+#: git-gui.sh:1573
+msgid "Staged for removal"
+msgstr "削除予定済"
+
+#: git-gui.sh:1574
+msgid "Staged for removal, still present"
+msgstr "削除予定済ã€ãƒ•ã‚¡ã‚¤ãƒ«æœªå‰Šé™¤"
+
+#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579
+msgid "Requires merge resolution"
+msgstr "è¦ãƒžãƒ¼ã‚¸è§£æ±º"
+
+#: git-gui.sh:1614
+msgid "Starting gitk... please wait..."
+msgstr "gitk を起動中…ãŠå¾…ã¡ä¸‹ã•ã„…"
+
+#: git-gui.sh:1623
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+"gitk ã‚’èµ·å‹•ã§ãã¾ã›ã‚“:\n"
+"\n"
+"%s ãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: git-gui.sh:1823 lib/choose_repository.tcl:35
+msgid "Repository"
+msgstr "リãƒã‚¸ãƒˆãƒª"
+
+#: git-gui.sh:1824
+msgid "Edit"
+msgstr "編集"
+
+#: git-gui.sh:1826 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr "ブランãƒ"
+
+#: git-gui.sh:1829 lib/choose_rev.tcl:547
+msgid "Commit@@noun"
+msgstr "コミット"
+
+#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "マージ"
+
+#: git-gui.sh:1833 lib/choose_rev.tcl:556
+msgid "Remote"
+msgstr "リモート"
+
+#: git-gui.sh:1842
+msgid "Browse Current Branch's Files"
+msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る"
+
+#: git-gui.sh:1846
+msgid "Browse Branch Files..."
+msgstr "ブランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る…"
+
+#: git-gui.sh:1851
+msgid "Visualize Current Branch's History"
+msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®å±¥æ­´ã‚’見る"
+
+#: git-gui.sh:1855
+msgid "Visualize All Branch History"
+msgstr "å…¨ã¦ã®ãƒ–ランãƒã®å±¥æ­´ã‚’見る"
+
+#: git-gui.sh:1862
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "ブランム%s ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る"
+
+#: git-gui.sh:1864
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "ブランム%s ã®å±¥æ­´ã‚’見る"
+
+#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "データベース統計"
+
+#: git-gui.sh:1872 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "データベース圧縮"
+
+#: git-gui.sh:1875
+msgid "Verify Database"
+msgstr "データベース検証"
+
+#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr "デスクトップ・アイコンを作る"
+
+#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
+msgid "Quit"
+msgstr "終了"
+
+#: git-gui.sh:1902
+msgid "Undo"
+msgstr "å…ƒã«æˆ»ã™"
+
+#: git-gui.sh:1905
+msgid "Redo"
+msgstr "ã‚„ã‚Šç›´ã—"
+
+#: git-gui.sh:1909 git-gui.sh:2403
+msgid "Cut"
+msgstr "切りå–ã‚Š"
+
+#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr "コピー"
+
+#: git-gui.sh:1915 git-gui.sh:2409
+msgid "Paste"
+msgstr "貼り付ã‘"
+
+#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "削除"
+
+#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69
+msgid "Select All"
+msgstr "å…¨ã¦é¸æŠž"
+
+#: git-gui.sh:1931
+msgid "Create..."
+msgstr "作æˆâ€¦"
+
+#: git-gui.sh:1937
+msgid "Checkout..."
+msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ"
+
+#: git-gui.sh:1943
+msgid "Rename..."
+msgstr "åå‰å¤‰æ›´â€¦"
+
+#: git-gui.sh:1948 git-gui.sh:2048
+msgid "Delete..."
+msgstr "削除…"
+
+#: git-gui.sh:1953
+msgid "Reset..."
+msgstr "リセット…"
+
+#: git-gui.sh:1965 git-gui.sh:2350
+msgid "New Commit"
+msgstr "æ–°è¦ã‚³ãƒŸãƒƒãƒˆ"
+
+#: git-gui.sh:1973 git-gui.sh:2357
+msgid "Amend Last Commit"
+msgstr "最新コミットを訂正"
+
+#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "å†ã‚¹ã‚­ãƒ£ãƒ³"
+
+#: git-gui.sh:1988
+msgid "Stage To Commit"
+msgstr "コミット予定ã™ã‚‹"
+
+#: git-gui.sh:1994
+msgid "Stage Changed Files To Commit"
+msgstr "変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’コミット予定"
+
+#: git-gui.sh:2000
+msgid "Unstage From Commit"
+msgstr "コミットã‹ã‚‰é™ã‚ã™"
+
+#: git-gui.sh:2005 lib/index.tcl:393
+msgid "Revert Changes"
+msgstr "変更を元ã«æˆ»ã™"
+
+#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427
+msgid "Sign Off"
+msgstr "ç½²å"
+
+#: git-gui.sh:2016 git-gui.sh:2333
+msgid "Commit@@verb"
+msgstr "コミット"
+
+#: git-gui.sh:2027
+msgid "Local Merge..."
+msgstr "ローカル・マージ…"
+
+#: git-gui.sh:2032
+msgid "Abort Merge..."
+msgstr "マージ中止…"
+
+#: git-gui.sh:2044
+msgid "Push..."
+msgstr "プッシュ…"
+
+#: git-gui.sh:2055 lib/choose_repository.tcl:40
+msgid "Apple"
+msgstr "ã‚Šã‚“ã”"
+
+#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
+#, tcl-format
+msgid "About %s"
+msgstr "%s ã«ã¤ã„ã¦"
+
+#: git-gui.sh:2062
+msgid "Preferences..."
+msgstr "設定…"
+
+#: git-gui.sh:2070 git-gui.sh:2595
+msgid "Options..."
+msgstr "オプション…"
+
+#: git-gui.sh:2076 lib/choose_repository.tcl:46
+msgid "Help"
+msgstr "ヘルプ"
+
+#: git-gui.sh:2117
+msgid "Online Documentation"
+msgstr "オンライン・ドキュメント"
+
+#: git-gui.sh:2201
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr ""
+"致命的: パス %s ㌠stat ã§ãã¾ã›ã‚“。ãã®ã‚ˆã†ãªãƒ•ã‚¡ã‚¤ãƒ«ã‚„ディレクトリã¯ã‚ã‚Šã¾"
+"ã›ã‚“"
+
+#: git-gui.sh:2234
+msgid "Current Branch:"
+msgstr "ç¾åœ¨ã®ãƒ–ランãƒ"
+
+#: git-gui.sh:2255
+msgid "Staged Changes (Will Commit)"
+msgstr "ステージングã•ã‚ŒãŸï¼ˆã‚³ãƒŸãƒƒãƒˆäºˆå®šæ¸ˆã®ï¼‰å¤‰æ›´"
+
+#: git-gui.sh:2274
+msgid "Unstaged Changes"
+msgstr "コミット予定ã«å…¥ã£ã¦ã„ãªã„変更"
+
+#: git-gui.sh:2323
+msgid "Stage Changed"
+msgstr "変更をコミット予定ã«å…¥ã‚Œã‚‹"
+
+#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "プッシュ"
+
+#: git-gui.sh:2369
+msgid "Initial Commit Message:"
+msgstr "最åˆã®ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:"
+
+#: git-gui.sh:2370
+msgid "Amended Commit Message:"
+msgstr "訂正ã—ãŸã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:"
+
+#: git-gui.sh:2371
+msgid "Amended Initial Commit Message:"
+msgstr "訂正ã—ãŸæœ€åˆã®ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:"
+
+#: git-gui.sh:2372
+msgid "Amended Merge Commit Message:"
+msgstr "訂正ã—ãŸãƒžãƒ¼ã‚¸ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:"
+
+#: git-gui.sh:2373
+msgid "Merge Commit Message:"
+msgstr "マージコミットメッセージ:"
+
+#: git-gui.sh:2374
+msgid "Commit Message:"
+msgstr "コミットメッセージ:"
+
+#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71
+msgid "Copy All"
+msgstr "å…¨ã¦ã‚³ãƒ”ー"
+
+#: git-gui.sh:2443 lib/blame.tcl:104
+msgid "File:"
+msgstr "ファイル:"
+
+#: git-gui.sh:2545
+msgid "Refresh"
+msgstr "å†èª­ã¿è¾¼ã¿"
+
+#: git-gui.sh:2566
+msgid "Apply/Reverse Hunk"
+msgstr "パッãƒã‚’é©ç”¨/å–り消ã™"
+
+#: git-gui.sh:2572
+msgid "Decrease Font Size"
+msgstr "フォントをå°ã•ã"
+
+#: git-gui.sh:2576
+msgid "Increase Font Size"
+msgstr "フォントを大ãã"
+
+#: git-gui.sh:2581
+msgid "Show Less Context"
+msgstr "文脈を少ãªã"
+
+#: git-gui.sh:2588
+msgid "Show More Context"
+msgstr "文脈を多ã"
+
+#: git-gui.sh:2602
+msgid "Unstage Hunk From Commit"
+msgstr "パッãƒã‚’コミット予定ã‹ã‚‰å¤–ã™"
+
+#: git-gui.sh:2604
+msgid "Stage Hunk For Commit"
+msgstr "パッãƒã‚’コミット予定ã«åŠ ãˆã‚‹"
+
+#: git-gui.sh:2623
+msgid "Initializing..."
+msgstr "åˆæœŸåŒ–ã—ã¦ã„ã¾ã™â€¦"
+
+#: git-gui.sh:2718
+#, 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 ""
+"環境ã«å•é¡ŒãŒã‚ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™\n"
+"\n"
+"以下ã®ç’°å¢ƒå¤‰æ•°ã¯ %s ãŒèµ·å‹•ã™ã‚‹ Git サブプロセスã«ã‚ˆã£ã¦ç„¡è¦–ã•ã‚Œã‚‹ã§ã—ょã†:\n"
+"\n"
+
+#: git-gui.sh:2748
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"ã“れ㯠Cygwin ã§é…布ã•ã‚Œã¦ã„ã‚‹ Tcl ãƒã‚¤ãƒŠãƒªã«\n"
+"é–¢ã—ã¦ã®æ—¢çŸ¥ã®å•é¡Œã«ã‚ˆã‚Šã¾ã™"
+
+#: git-gui.sh:2753
+#, 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"
+"個人的㪠~/.gitconfig ファイル内㧠user.name 㨠user.email ã®å€¤ã‚’設定\n"
+"ã™ã‚‹ã®ãŒã€%s ã®è‰¯ã„代用ã¨ãªã‚Šã¾ã™\n"
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr "Git ã®ã‚°ãƒ©ãƒ•ã‚£ã‚«ãƒ«UI git-gui"
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr "ファイルピューワ"
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr "コミット:"
+
+#: lib/blame.tcl:249
+msgid "Copy Commit"
+msgstr "コミットをコピー"
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr "%s を読んã§ã„ã¾ã™â€¦"
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr "コピー・移動追跡データを読んã§ã„ã¾ã™â€¦"
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr "行を注釈ã—ã¾ã—ãŸ"
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr "å…ƒä½ç½®è¡Œã®æ³¨é‡ˆãƒ‡ãƒ¼ã‚¿ã‚’読んã§ã„ã¾ã™â€¦"
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr "注釈完了ã—ã¾ã—ãŸ"
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr "注釈を読ã¿è¾¼ã‚“ã§ã„ã¾ã™â€¦"
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr "作者:"
+
+#: lib/blame.tcl:791
+msgid "Committer:"
+msgstr "コミット者:"
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr "元ファイル"
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr "原作者:"
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr "ファイル:"
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr "複写・移動者:"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "ブランãƒã‚’ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ"
+
+#: lib/branch_checkout.tcl:23
+msgid "Checkout"
+msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ"
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr "中止"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr "リビジョン"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+msgid "Options"
+msgstr "オプション"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "トラッキング・ブランãƒã‚’フェッãƒ"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "ローカル・ブランãƒã‹ã‚‰å‰Šé™¤"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "ブランãƒã‚’作æˆ"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "ブランãƒã‚’æ–°è¦ä½œæˆ"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
+msgid "Create"
+msgstr "作æˆ"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "ブランãƒå"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr "åå‰:"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "トラッキング・ブランãƒåã‚’åˆã‚ã›ã‚‹"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "åˆæœŸãƒªãƒ“ジョン"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "既存ã®ãƒ–ランãƒã‚’æ›´æ–°:"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "ã„ã„ãˆ"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "æ—©é€ã‚Šã®ã¿"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr "リセット"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "作æˆã—ã¦ã™ããƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "トラッキング・ブランãƒã‚’é¸æŠžã—ã¦ä¸‹ã•ã„。"
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr "トラッキング・ブランム%s ã¯é éš”リãƒã‚¸ãƒˆãƒªã®ãƒ–ランãƒã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr "ブランãƒåを指定ã—ã¦ä¸‹ã•ã„。"
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "'%s' ã¯ãƒ–ランãƒåã«ä½¿ãˆã¾ã›ã‚“。"
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr "ブランãƒå‰Šé™¤"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "ローカル・ブランãƒã‚’削除"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "ローカル・ブランãƒ"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "マージ済ã¿ã®æ™‚ã®ã¿å‰Šé™¤"
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr "ç„¡æ¡ä»¶(マージテストã—ãªã„)"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "以下ã®ãƒ–ランãƒã¯ %s ã«å®Œå…¨ã«ãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã„ã¾ã›ã‚“:"
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+"ブランãƒã‚’削除ã™ã‚‹ã¨å…ƒã«æˆ»ã™ã®ã¯å›°é›£ã§ã™ã€‚ \n"
+"\n"
+" é¸æŠžã—ãŸãƒ–ランãƒã‚’削除ã—ã¾ã™ã‹ï¼Ÿ"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"以下ã®ãƒ–ランãƒã‚’削除ã§ãã¾ã›ã‚“:\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "ブランãƒã®åå‰å¤‰æ›´"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "åå‰å¤‰æ›´"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "ブランãƒ:"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "æ–°ã—ã„åå‰:"
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr "åå‰ã‚’変更ã™ã‚‹ãƒ–ランãƒã‚’é¸ã‚“ã§ä¸‹ã•ã„。"
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "'%s'ã¨ã„ã†ãƒ–ランãƒã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚"
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "'%s'ã®åå‰å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "起動中…"
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "ファイル・ブラウザ"
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr "%s をロード中…"
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr "[上ä½ãƒ•ã‚©ãƒ«ãƒ€ã¸]"
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+msgid "Browse Branch Files"
+msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る"
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
+msgid "Browse"
+msgstr "ブラウズ"
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "%s ã‹ã‚‰ %s をフェッãƒã—ã¦ã„ã¾ã™"
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "致命的エラー: %s を解決ã§ãã¾ã›ã‚“"
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr "é–‰ã˜ã‚‹"
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "ブランãƒ'%s'ã¯å­˜åœ¨ã—ã¾ã›ã‚“。"
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"ブランム'%s' ã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚\n"
+"\n"
+"%s ã«æ—©é€ã‚Šã§ãã¾ã›ã‚“。\n"
+"マージãŒå¿…è¦ã§ã™ã€‚"
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "'%s' マージ戦略ã¯ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“。"
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "'%s' ã®æ›´æ–°ã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr "インデックスã¯æ—¢ã«ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™ã€‚"
+
+#: lib/checkout_op.tcl:266
+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 ""
+"最後ã«ã‚¹ã‚­ãƒ£ãƒ³ã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n"
+"\n"
+"最後ã«ã‚¹ã‚­ãƒ£ãƒ³ã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プログラムãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚ç¾åœ¨"
+"ã®ãƒ–ランãƒã‚’変更ã™ã‚‹å‰ã«ã€å†ã‚¹ã‚­ãƒ£ãƒ³ãŒå¿…è¦ã§ã™ã€‚\n"
+"\n"
+"自動的ã«å†ã‚¹ã‚­ãƒ£ãƒ³ã‚’開始ã—ã¾ã™ã€‚\n"
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "作業ディレクトリを '%s' ã«æ›´æ–°ã—ã¦ã„ã¾ã™â€¦"
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr "'%s' ã®ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã‚’中止ã—ã¾ã—ãŸï¼ˆãƒ•ã‚¡ã‚¤ãƒ«æ¯Žã®ãƒžãƒ¼ã‚¸ãŒå¿…è¦ã§ã™ï¼‰ã€‚"
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr "ファイル毎ã®ãƒžãƒ¼ã‚¸ãŒå¿…è¦ã§ã™ã€‚"
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "ブランム'%s' ã«æ»žã¾ã‚Šã¾ã™ã€‚"
+
+#: lib/checkout_op.tcl:429
+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 ""
+"ローカル・ブランãƒã‹ã‚‰é›¢ã‚Œã¾ã™ã€‚\n"
+"\n"
+"ブランãƒä¸Šã«æ»žã¾ã‚ŠãŸã„ã¨ãã¯ã€ã“ã®ã€Œåˆ†é›¢ã•ã‚ŒãŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã€ã‹ã‚‰æ–°è¦ãƒ–ラン"
+"ãƒã‚’開始ã—ã¦ãã ã•ã„。"
+
+#: lib/checkout_op.tcl:446
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "'%s' ã‚’ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã—ã¾ã—ãŸ"
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr "'%s' ã‚’ '%s' ã«ãƒªã‚»ãƒƒãƒˆã™ã‚‹ã¨ã€ä»¥ä¸‹ã®ã‚³ãƒŸãƒƒãƒˆãŒå¤±ãªã‚ã‚Œã¾ã™:"
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr "失ãªã‚ã‚ŒãŸã‚³ãƒŸãƒƒãƒˆã‚’回復ã™ã‚‹ã®ã¯ç°¡å˜ã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "'%s' をリセットã—ã¾ã™ã‹ï¼Ÿ"
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr "å¯è¦–化"
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+"ç¾åœ¨ã®ãƒ–ランãƒã‚’設定ã§ãã¾ã›ã‚“。\n"
+"\n"
+"作業ディレクトリã¯éƒ¨åˆ†çš„ã«ã—ã‹åˆ‡ã‚Šæ›¿ã‚ã£ã¦ã„ã¾ã›ã‚“。ファイルã®æ›´æ–°ã«ã¯æˆåŠŸã—"
+"ã¾ã—ãŸãŒã€ Git ã®å†…部データを更新ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚\n"
+"èµ·ã“ã‚‹ã¯ãšã®ãªã„エラーã§ã™ã€‚ã‚ãらã‚㦠%s を終了ã—ã¾ã™ã€‚"
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "é¸æŠž"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "フォント・ファミリー"
+
+#: lib/choose_font.tcl:73
+msgid "Font Size"
+msgstr "フォントã®å¤§ãã•"
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr "フォント・サンプル"
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"ã“ã‚Œã¯ã‚µãƒ³ãƒ—ル文ã§ã™ã€‚\n"
+"ã“ã®ãƒ•ã‚©ãƒ³ãƒˆãŒæ°—ã«å…¥ã‚Œã°ãŠä½¿ã„ã«ãªã‚Œã¾ã™ã€‚"
+
+#: lib/choose_repository.tcl:27
+msgid "Git Gui"
+msgstr "Git GUI"
+
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
+msgid "Create New Repository"
+msgstr "æ–°ã—ã„リãƒã‚¸ãƒˆãƒªã‚’作る"
+
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr "æ–°è¦â€¦"
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
+msgid "Clone Existing Repository"
+msgstr "既存リãƒã‚¸ãƒˆãƒªã‚’複製ã™ã‚‹"
+
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr "複製…"
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
+msgid "Open Existing Repository"
+msgstr "既存リãƒã‚¸ãƒˆãƒªã‚’é–‹ã"
+
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr "é–‹ã…"
+
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr "最近使ã£ãŸãƒªãƒã‚¸ãƒˆãƒª"
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr "最近使ã£ãŸãƒªãƒã‚¸ãƒˆãƒªã‚’é–‹ã"
+
+#: lib/choose_repository.tcl:294
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "'%s' ã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚"
+
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "リãƒã‚¸ãƒˆãƒª %s を作製ã§ãã¾ã›ã‚“:"
+
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
+msgid "Directory:"
+msgstr "ディレクトリ:"
+
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
+msgid "Git Repository"
+msgstr "GIT リãƒã‚¸ãƒˆãƒª"
+
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "ディレクトリ '%s' ã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚"
+
+#: lib/choose_repository.tcl:442
+#, tcl-format
+msgid "File %s already exists."
+msgstr "ファイル '%s' ã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚"
+
+#: lib/choose_repository.tcl:463
+msgid "Clone"
+msgstr "複製"
+
+#: lib/choose_repository.tcl:476
+msgid "URL:"
+msgstr "URL:"
+
+#: lib/choose_repository.tcl:496
+msgid "Clone Type:"
+msgstr "複製方å¼:"
+
+#: lib/choose_repository.tcl:502
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "標準(高速・中冗長度・ãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯)"
+
+#: lib/choose_repository.tcl:508
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "全複写(低速・冗長ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—)"
+
+#: lib/choose_repository.tcl:514
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "共有(最高速・éžæŽ¨å¥¨ãƒ»ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ç„¡ã—)"
+
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Git リãƒã‚¸ãƒˆãƒªã§ã¯ã‚ã‚Šã¾ã›ã‚“: %s"
+
+#: lib/choose_repository.tcl:586
+msgid "Standard only available for local repository."
+msgstr "標準方å¼ã¯åŒä¸€è¨ˆç®—機上ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã®ã¿ä½¿ãˆã¾ã™ã€‚"
+
+#: lib/choose_repository.tcl:590
+msgid "Shared only available for local repository."
+msgstr "共有方å¼ã¯åŒä¸€è¨ˆç®—機上ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã®ã¿ä½¿ãˆã¾ã™ã€‚"
+
+#: lib/choose_repository.tcl:617
+msgid "Failed to configure origin"
+msgstr "origin を設定ã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/choose_repository.tcl:629
+msgid "Counting objects"
+msgstr "オブジェクトを数ãˆã¦ã„ã¾ã™"
+
+#: lib/choose_repository.tcl:630
+msgid "buckets"
+msgstr "ãƒã‚±ãƒ„"
+
+#: lib/choose_repository.tcl:654
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "objects/info/alternates を複写ã§ãã¾ã›ã‚“: %s"
+
+#: lib/choose_repository.tcl:690
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "%s ã‹ã‚‰è¤‡è£½ã™ã‚‹å†…容ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
+msgid "The 'master' branch has not been initialized."
+msgstr "'master' ブランãƒãŒåˆæœŸåŒ–ã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: lib/choose_repository.tcl:705
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr "ãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯ãŒä½œã‚Œãªã„ã®ã§ã€ã‚³ãƒ”ーã—ã¾ã™"
+
+#: lib/choose_repository.tcl:717
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "%s ã‹ã‚‰è¤‡è£½ã—ã¦ã„ã¾ã™"
+
+#: lib/choose_repository.tcl:748
+msgid "Copying objects"
+msgstr "オブジェクトを複写ã—ã¦ã„ã¾ã™"
+
+#: lib/choose_repository.tcl:749
+msgid "KiB"
+msgstr "KiB"
+
+#: lib/choose_repository.tcl:773
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "オブジェクトを複写ã§ãã¾ã›ã‚“: %s"
+
+#: lib/choose_repository.tcl:783
+msgid "Linking objects"
+msgstr "オブジェクトを連çµã—ã¦ã„ã¾ã™"
+
+#: lib/choose_repository.tcl:784
+msgid "objects"
+msgstr "オブジェクト"
+
+#: lib/choose_repository.tcl:792
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "オブジェクトをãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯ã§ãã¾ã›ã‚“: %s"
+
+#: lib/choose_repository.tcl:847
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr "ブランãƒã‚„オブジェクトをå–å¾—ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„"
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch tags. See console output for details."
+msgstr "ã‚¿ã‚°ã‚’å–å¾—ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„"
+
+#: lib/choose_repository.tcl:882
+msgid "Cannot determine HEAD. See console output for details."
+msgstr "HEAD を確定ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„"
+
+#: lib/choose_repository.tcl:891
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "%s を掃除ã§ãã¾ã›ã‚“"
+
+#: lib/choose_repository.tcl:897
+msgid "Clone failed."
+msgstr "複写ã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+
+#: lib/choose_repository.tcl:904
+msgid "No default branch obtained."
+msgstr "デフォールト・ブランãƒãŒå–å¾—ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/choose_repository.tcl:915
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "%s をコミットã¨ã—ã¦è§£é‡ˆã§ãã¾ã›ã‚“"
+
+#: lib/choose_repository.tcl:927
+msgid "Creating working directory"
+msgstr "作業ディレクトリを作æˆã—ã¦ã„ã¾ã™"
+
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr "ファイル"
+
+#: lib/choose_repository.tcl:957
+msgid "Initial file checkout failed."
+msgstr "åˆæœŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã«å¤±æ•—ã—ã¾ã—ãŸ"
+
+#: lib/choose_repository.tcl:973
+msgid "Open"
+msgstr "é–‹ã"
+
+#: lib/choose_repository.tcl:983
+msgid "Repository:"
+msgstr "リãƒã‚¸ãƒˆãƒª:"
+
+#: lib/choose_repository.tcl:1033
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "リãƒã‚¸ãƒˆãƒª %s ã‚’é–‹ã‘ã¾ã›ã‚“:"
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr "分離ã•ã‚ŒãŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "リビジョンå¼:"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "ローカル・ブランãƒ"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "トラッキング・ブランãƒ"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr "ã‚¿ã‚°"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "無効ãªãƒªãƒ“ジョン: %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "リビジョンãŒæœªé¸æŠžã§ã™ã€‚"
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "リビジョンå¼ãŒç©ºã§ã™ã€‚"
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr "æ›´æ–°ã—ã¾ã—ãŸ"
+
+#: lib/choose_rev.tcl:558
+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"
+"\n"
+"ã“ã‚Œã‹ã‚‰ä½œã‚‹ã®ã¯æœ€åˆã®ã‚³ãƒŸãƒƒãƒˆã§ã™ã€‚ãã®å‰ã«ã¯ã¾ã è¨‚æ­£ã™ã‚‹ã‚ˆã†ãªã‚³ãƒŸãƒƒãƒˆã¯ã‚"
+"ã‚Šã¾ã›ã‚“。\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"
+"\n"
+"ç¾åœ¨ã¯ã¾ã ãƒžãƒ¼ã‚¸ã®é€”中ã§ã™ã€‚å…ˆã«ã“ã®ãƒžãƒ¼ã‚¸ã‚’中止ã—ãªã„ã¨ã€å‰ã®ã‚³ãƒŸãƒƒãƒˆã®è¨‚æ­£"
+"ã¯ã§ãã¾ã›ã‚“\n"
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr "訂正ã™ã‚‹ã‚³ãƒŸãƒƒãƒˆã®ãƒ‡ãƒ¼ã‚¿ã‚’読ã‚ã¾ã›ã‚“:"
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr "ユーザã®æ­£ä½“を確èªã§ãã¾ã›ã‚“:"
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "GIT_COMMITTER_IDENT ãŒç„¡åŠ¹ã§ã™:"
+
+#: lib/commit.tcl:133
+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 ""
+"最後ã«ã‚¹ã‚­ãƒ£ãƒ³ã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n"
+"\n"
+"最後ã«ã‚¹ã‚­ãƒ£ãƒ³ã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プログラムãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚æ–°ã—"
+"ãコミットã™ã‚‹å‰ã«ã€å†ã‚¹ã‚­ãƒ£ãƒ³ãŒå¿…è¦ã§ã™ã€‚\n"
+"\n"
+"自動的ã«å†ã‚¹ã‚­ãƒ£ãƒ³ã‚’開始ã—ã¾ã™ã€‚\n"
+
+#: lib/commit.tcl:154
+#, 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"
+"\n"
+"ファイル %s ã«ã¯ãƒžãƒ¼ã‚¸è¡çªãŒæ®‹ã£ã¦ã„ã¾ã™ã€‚ã¾ãšè§£æ±ºã—ã¦ã‚³ãƒŸãƒƒãƒˆäºˆå®šã«åŠ ãˆã‚‹å¿…"
+"è¦ãŒã‚ã‚Šã¾ã™ã€‚\n"
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"ä¸æ˜Žãªãƒ•ã‚¡ã‚¤ãƒ«çŠ¶æ…‹ %s ã§ã™ã€‚\n"
+"\n"
+"ファイル %s ã¯æœ¬ãƒ—ログラムã§ã¯ã‚³ãƒŸãƒƒãƒˆã§ãã¾ã›ã‚“。\n"
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n"
+"\n"
+"最低一ã¤ã®å¤‰æ›´ã‚’コミット予定ã«åŠ ãˆã¦ã‹ã‚‰ã‚³ãƒŸãƒƒãƒˆã—ã¦ä¸‹ã•ã„。\n"
+
+#: lib/commit.tcl:183
+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 ""
+"コミット・メッセージを入力ã—ã¦ä¸‹ã•ã„。\n"
+"\n"
+"æ­£ã—ã„コミット・メッセージã¯:\n"
+"\n"
+"- 第1行: 何をã—ãŸã‹ã€ã‚’1行ã§è¦ç´„。\n"
+"- 第2行: 空白\n"
+"- 残りã®è¡Œ: ãªãœã€ã“ã®å¤‰æ›´ãŒè‰¯ã„変更ã‹ã€ã®èª¬æ˜Žã€‚\n"
+
+#: lib/commit.tcl:257
+msgid "write-tree failed:"
+msgstr "write-tree ãŒå¤±æ•—ã—ã¾ã—ãŸ:"
+
+#: lib/commit.tcl:275
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "コミット %s ã¯å£Šã‚Œã¦ã„ã¾ã™"
+
+#: lib/commit.tcl:279
+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"
+"\n"
+"マージã§ãªãã€ã¾ãŸã€ä¸€ã¤ã‚‚変更点ãŒã‚ã‚Šã¾ã›ã‚“。\n"
+"\n"
+"自動的ã«å†ã‚¹ã‚­ãƒ£ãƒ³ã‚’開始ã—ã¾ã™ã€‚\n"
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr "コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: lib/commit.tcl:303
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "警告: Tcl ã¯ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚° '%s' をサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“"
+
+#: lib/commit.tcl:317
+msgid "commit-tree failed:"
+msgstr "commit-tree ãŒå¤±æ•—ã—ã¾ã—ãŸ:"
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr "update-ref ãŒå¤±æ•—ã—ã¾ã—ãŸ:"
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "コミット %s を作æˆã—ã¾ã—ãŸ: %s"
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr "実行中…ãŠå¾…ã¡ä¸‹ã•ã„…"
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr "æˆåŠŸ"
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr "エラー: コマンドãŒå¤±æ•—ã—ã¾ã—ãŸ"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "ã°ã‚‰ã°ã‚‰ãªã‚ªãƒ–ジェクトã®æ•°"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "ã°ã‚‰ã°ã‚‰ãªã‚ªãƒ–ジェクトã®ä½¿ç”¨ã™ã‚‹ãƒ‡ã‚£ã‚¹ã‚¯é‡"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "パックã•ã‚ŒãŸã‚ªãƒ–ジェクトã®æ•°"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "パックã®æ•°"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "パックã•ã‚ŒãŸã‚ªãƒ–ジェクトã®ä½¿ç”¨ã™ã‚‹ãƒ‡ã‚£ã‚¹ã‚¯é‡"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "パックã«å­˜åœ¨ã™ã‚‹ã®ã§æ¨ã¦ã¦è‰¯ã„オブジェクトã®æ•°"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "ゴミファイル"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "データベース圧縮"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "fsck-objects ã§ã‚ªãƒ–ジェクト・データベースを検証ã—ã¦ã„ã¾ã™"
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+"ã“ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã¯ãŠãŠã‚ˆã %i 個ã®å€‹åˆ¥ã‚ªãƒ–ジェクトãŒã‚ã‚Šã¾ã™\n"
+"\n"
+"最é©ãªæ€§èƒ½ã‚’ä¿ã¤ãŸã‚ã«ã€%i 個以上ã®å€‹åˆ¥ã‚ªãƒ–ジェクトを作る毎ã«ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‚’圧"
+"縮ã™ã‚‹ã“ã¨ã‚’推奨ã—ã¾ã™\n"
+"\n"
+"データベースを圧縮ã—ã¾ã™ã‹ï¼Ÿ"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Git ã‹ã‚‰å‡ºãŸç„¡åŠ¹ãªæ—¥ä»˜: %s"
+
+#: lib/diff.tcl:42
+#, 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 ""
+"変更ãŒã‚ã‚Šã¾ã›ã‚“。\n"
+"\n"
+"%s ã«ã¯å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n"
+"\n"
+"ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®å¤‰æ›´æ™‚刻ã¯ä»–ã®ã‚¢ãƒ—リケーションã«ã‚ˆã£ã¦æ›´æ–°ã•ã‚Œã¦ã„ã¾ã™ãŒãƒ•ã‚¡ã‚¤"
+"ル内容ã«ã¯å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n"
+"\n"
+"åŒæ§˜ãªçŠ¶æ…‹ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’探ã™ãŸã‚ã«ã€è‡ªå‹•çš„ã«å†ã‚¹ã‚­ãƒ£ãƒ³ã‚’開始ã—ã¾ã™ã€‚"
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "%s ã®å¤‰æ›´ç‚¹ã‚’ロード中…"
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "%s を表示ã§ãã¾ã›ã‚“"
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr "ファイルを読む際ã®ã‚¨ãƒ©ãƒ¼ã§ã™:"
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr "Git リãƒã‚¸ãƒˆãƒª(サブプロジェクト)"
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr "* ãƒã‚¤ãƒŠãƒªãƒ•ã‚¡ã‚¤ãƒ«(内容ã¯è¡¨ç¤ºã—ã¾ã›ã‚“)"
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr "diff を読む際ã®ã‚¨ãƒ©ãƒ¼ã§ã™:"
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr "é¸æŠžã•ã‚ŒãŸãƒ‘ッãƒã‚’コミット予定ã‹ã‚‰å¤–ã›ã¾ã›ã‚“。"
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr "é¸æŠžã•ã‚ŒãŸãƒ‘ッãƒã‚’コミット予定ã«åŠ ãˆã‚‰ã‚Œã¾ã›ã‚“。"
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr "エラー"
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr "警告"
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr "コミットã™ã‚‹å‰ã«ã€ä»¥ä¸Šã®ã‚¨ãƒ©ãƒ¼ã‚’修正ã—ã¦ä¸‹ã•ã„"
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "インデックスをロックã§ãã¾ã›ã‚“"
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "索引エラー"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr "GIT インデックスã®æ›´æ–°ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚git-gui ã¨åŒæœŸã‚’ã¨ã‚‹ãŸã‚ã«å†ã‚¹ã‚­ãƒ£ãƒ³ã—ã¾ã™ã€‚"
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "続行"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "インデックスã®ãƒ­ãƒƒã‚¯è§£é™¤"
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "コミットã‹ã‚‰ '%s' ã‚’é™ã‚ã™"
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr "コミット㫠%s を加ãˆã¦ã„ã¾ã™"
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "ファイル %s ã«ã—ãŸå¤‰æ›´ã‚’å…ƒã«æˆ»ã—ã¾ã™ã‹ï¼Ÿ"
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "ã“れら %i 個ã®ãƒ•ã‚¡ã‚¤ãƒ«ã«ã—ãŸå¤‰æ›´ã‚’å…ƒã«æˆ»ã—ã¾ã™ã‹ï¼Ÿ"
+
+#: lib/index.tcl:389
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr "変更を元ã«æˆ»ã™ã¨ã‚³ãƒŸãƒƒãƒˆäºˆå®šã—ã¦ã„ãªã„変更ã¯å…¨ã¦å¤±ã‚ã‚Œã¾ã™ã€‚"
+
+#: lib/index.tcl:392
+msgid "Do Nothing"
+msgstr "何もã—ãªã„"
+
+#: 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"
+"\n"
+"訂正処ç†ã‚’完了ã™ã‚‹ã¾ã§ã¯æ–°ãŸã«ãƒžãƒ¼ã‚¸ã‚’開始ã§ãã¾ã›ã‚“。\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 ""
+"最後ã«ã‚¹ã‚­ãƒ£ãƒ³ã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n"
+"\n"
+"最後ã«ã‚¹ã‚­ãƒ£ãƒ³ã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プログラムãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚マー"
+"ジを開始ã™ã‚‹å‰ã«ã€å†ã‚¹ã‚­ãƒ£ãƒ³ãŒå¿…è¦ã§ã™ã€‚\n"
+"\n"
+"自動的ã«å†ã‚¹ã‚­ãƒ£ãƒ³ã‚’開始ã—ã¾ã™ã€‚\n"
+
+#: lib/merge.tcl:44
+#, 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 ""
+"è¡çªã®ã‚ã£ãŸãƒžãƒ¼ã‚¸ã®é€”中ã§ã™ã€‚\n"
+"\n"
+"ファイル %s ã«ã¯ãƒžãƒ¼ã‚¸ä¸­ã®è¡çªãŒæ®‹ã£ã¦ã„ã¾ã™ã€‚\n"
+"\n"
+"ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®è¡çªã‚’解決ã—ã€ã‚³ãƒŸãƒƒãƒˆäºˆå®šã«åŠ ãˆã¦ã€ã‚³ãƒŸãƒƒãƒˆã™ã‚‹ã“ã¨ã§ãƒžãƒ¼ã‚¸ã‚’"
+"完了ã—ã¾ã™ã€‚ãã†ã‚„ã£ã¦å§‹ã‚ã¦ã€æ–°ãŸãªãƒžãƒ¼ã‚¸ã‚’開始ã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚\n"
+
+#: lib/merge.tcl:54
+#, 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 ""
+"変更ã®é€”中ã§ã™ã€‚\n"
+"\n"
+"ファイル %s ã¯å¤‰æ›´ä¸­ã§ã™ã€‚\n"
+"\n"
+"ç¾åœ¨ã®ã‚³ãƒŸãƒƒãƒˆã‚’完了ã—ã¦ã‹ã‚‰ãƒžãƒ¼ã‚¸ã‚’開始ã—ã¦ä¸‹ã•ã„。ãã†ã™ã‚‹æ–¹ãŒãƒžãƒ¼ã‚¸ã«å¤±æ•—"
+"ã—ãŸã¨ãã®å›žå¾©ãŒæ¥½ã§ã™ã€‚\n"
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s ã® %s ブランãƒ"
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr "%s 㨠%s をマージã—ã¾ã™"
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr "マージãŒå®Œäº†ã—ã¾ã—ãŸ"
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr "マージãŒå¤±æ•—ã—ã¾ã—ãŸã€‚è¡çªã®è§£æ±ºãŒå¿…è¦ã§ã™ã€‚"
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "%s ã«ãƒžãƒ¼ã‚¸"
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr "マージã™ã‚‹ãƒªãƒ“ジョン"
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"訂正中ã«ã¯ä¸­æ­¢ã§ãã¾ã›ã‚“。\n"
+"\n"
+"ã¾ãšä»Šã®ã‚³ãƒŸãƒƒãƒˆè¨‚正を完了ã•ã›ã¦ä¸‹ã•ã„。\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 ""
+"マージを中断ã—ã¾ã™ã‹ï¼Ÿ\n"
+"\n"
+"ç¾åœ¨ã®ãƒžãƒ¼ã‚¸ã‚’中断ã™ã‚‹ã¨ã€ã‚³ãƒŸãƒƒãƒˆã—ã¦ã„ãªã„å…¨ã¦ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚\n"
+"\n"
+"マージを中断ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+#: 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 ""
+"変更点をリセットã—ã¾ã™ã‹ï¼Ÿ\n"
+"\n"
+"変更点をリセットã™ã‚‹ã¨ã€ã‚³ãƒŸãƒƒãƒˆã—ã¦ã„ãªã„å…¨ã¦ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚\n"
+"\n"
+"リセットã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr "中断ã—ã¦ã„ã¾ã™"
+
+#: lib/merge.tcl:266
+msgid "Abort failed."
+msgstr "中断ã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr "中断完了。"
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr "既定値ã«æˆ»ã™"
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr "ä¿å­˜"
+
+#: lib/option.tcl:96
+#, tcl-format
+msgid "%s Repository"
+msgstr "%s リãƒã‚¸ãƒˆãƒª"
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr "大域(全ã¦ã®ãƒªãƒã‚¸ãƒˆãƒªï¼‰"
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr "ユーザå"
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr "é›»å­ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹"
+
+#: lib/option.tcl:106
+msgid "Summarize Merge Commits"
+msgstr "マージコミットã®è¦ç´„"
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr "マージã®å†—長度"
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr "マージ後㫠diffstat を表示"
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr "ファイル変更時刻を信頼ã™ã‚‹"
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr "フェッãƒä¸­ã«ãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚°ãƒ–ランãƒã‚’刈る"
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr "トラッキングブランãƒã‚’åˆã‚ã›ã‚‹"
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr "diff ã®æ–‡è„ˆè¡Œæ•°"
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr "æ–°ã—ã„ブランãƒåã®ãƒ†ãƒ³ãƒ—レート"
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr "フォントを変更"
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr "%s ã‚’é¸æŠž"
+
+#: lib/option.tcl:186
+msgid "pt."
+msgstr "ãƒã‚¤ãƒ³ãƒˆ"
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr "設定"
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr "完全ã«ã‚ªãƒ—ションをä¿å­˜ã§ãã¾ã›ã‚“:"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr "リモート・ブランãƒã‚’削除"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "å…ƒã®ãƒªãƒã‚¸ãƒˆãƒª"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr "リモート:"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr "ä»»æ„ã® URL:"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "ブランãƒ"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "æ¡ä»¶ä»˜ã§å‰Šé™¤"
+
+#: lib/remote_branch_delete.tcl:111
+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 "'マージ先' ã«ã¯ãƒ–ランãƒãŒå¿…è¦ã§ã™ã€‚"
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"以下ã®ãƒ–ランãƒã¯ %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 ""
+"å¿…è¦ãªã‚³ãƒŸãƒƒãƒˆãŒä¸è¶³ã—ã¦ã„ã‚‹ãŸã‚ã«ã€ãƒžãƒ¼ã‚¸æ¤œæŸ»ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚ã¾ãš %s ã‹ã‚‰"
+"フェッãƒã—ã¦ä¸‹ã•ã„。"
+
+#: lib/remote_branch_delete.tcl:207
+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
+msgid "No repository selected."
+msgstr "リãƒã‚¸ãƒˆãƒªãŒé¸æŠžã•ã‚Œã¦ã„ã¾ã›ã‚“。"
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "%s をスキャンã—ã¦ã„ã¾ã™â€¦"
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "ã‹ã‚‰åˆˆè¾¼ã‚€â€¦"
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "å–å¾—å…ƒ"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "プッシュ先"
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "ショートカットãŒæ›¸ã‘ã¾ã›ã‚“:"
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr "アイコンãŒæ›¸ã‘ã¾ã›ã‚“:"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%1$s ... %4$*i %6$s 中㮠%2$*i (%7$3i%%)"
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "%s ã‚’å–å¾—"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "%s ã‹ã‚‰æ–°ã—ã„変更をフェッãƒã—ã¦ã„ã¾ã™"
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "é éš”刈込 %s"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "%s ã‹ã‚‰å‰Šé™¤ã•ã‚ŒãŸãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚°ãƒ»ãƒ–ランãƒã‚’刈ã£ã¦ã„ã¾ã™"
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "%s をプッシュ"
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "%s ã¸å¤‰æ›´ã‚’プッシュã—ã¦ã„ã¾ã™"
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "%3$s 㸠%1$s %2$s をプッシュã—ã¦ã„ã¾ã™"
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr "ブランãƒã‚’プッシュ"
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr "å…ƒã®ãƒ–ランãƒ"
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr "é€ã‚Šå…ˆãƒªãƒã‚¸ãƒˆãƒª"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr "通信オプション"
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr "既存ブランãƒã‚’上書ã(変更を破棄ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™)"
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr "Thin Pack を使ã†ï¼ˆé…ã„ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯æŽ¥ç¶šï¼‰"
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr "ã‚¿ã‚°ã‚’å«ã‚ã‚‹"
diff --git a/git-gui/po/po2msg.sh b/git-gui/po/po2msg.sh
new file mode 100644
index 0000000000..b7c4bf3fdf
--- /dev/null
+++ b/git-gui/po/po2msg.sh
@@ -0,0 +1,152 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec tclsh "$0" -- "$@"
+
+# This is a really stupid program, which serves as an alternative to
+# msgfmt. It _only_ translates to Tcl mode, does _not_ validate the
+# input, and does _not_ output any statistics.
+
+proc u2a {s} {
+ set res ""
+ foreach i [split $s ""] {
+ scan $i %c c
+ if {$c<128} {
+ # escape '[', '\' and ']'
+ if {$c == 0x5b || $c == 0x5d} {
+ append res "\\"
+ }
+ append res $i
+ } else {
+ append res \\u[format %04.4x $c]
+ }
+ }
+ return $res
+}
+
+set output_directory "."
+set lang "dummy"
+set files [list]
+set show_statistics 0
+
+# parse options
+for {set i 0} {$i < $argc} {incr i} {
+ set arg [lindex $argv $i]
+ if {$arg == "--statistics"} {
+ incr show_statistics
+ continue
+ }
+ if {$arg == "--tcl"} {
+ # we know
+ continue
+ }
+ if {$arg == "-l"} {
+ incr i
+ set lang [lindex $argv $i]
+ continue
+ }
+ if {$arg == "-d"} {
+ incr i
+ set tmp [lindex $argv $i]
+ regsub "\[^/\]$" $tmp "&/" output_directory
+ continue
+ }
+ lappend files $arg
+}
+
+proc flush_msg {} {
+ global msgid msgstr mode lang out fuzzy
+ global translated_count fuzzy_count not_translated_count
+
+ if {![info exists msgid] || $mode == ""} {
+ return
+ }
+ set mode ""
+ if {$fuzzy == 1} {
+ incr fuzzy_count
+ set fuzzy 0
+ return
+ }
+
+ if {$msgid == ""} {
+ set prefix "set ::msgcat::header"
+ } else {
+ if {$msgstr == ""} {
+ incr not_translated_count
+ return
+ }
+ set prefix "::msgcat::mcset $lang \"[u2a $msgid]\""
+ incr translated_count
+ }
+
+ puts $out "$prefix \"[u2a $msgstr]\""
+}
+
+set fuzzy 0
+set translated_count 0
+set fuzzy_count 0
+set not_translated_count 0
+foreach file $files {
+ regsub "^.*/\(\[^/\]*\)\.po$" $file "$output_directory\\1.msg" outfile
+ set in [open $file "r"]
+ fconfigure $in -encoding utf-8
+ set out [open $outfile "w"]
+
+ set mode ""
+ while {[gets $in line] >= 0} {
+ if {[regexp "^#" $line]} {
+ if {[regexp ", fuzzy" $line]} {
+ set fuzzy 1
+ } else {
+ flush_msg
+ }
+ continue
+ } elseif {[regexp "^msgid \"(.*)\"$" $line dummy match]} {
+ flush_msg
+ set msgid $match
+ set mode "msgid"
+ } elseif {[regexp "^msgstr \"(.*)\"$" $line dummy match]} {
+ set msgstr $match
+ set mode "msgstr"
+ } elseif {$line == ""} {
+ flush_msg
+ } elseif {[regexp "^\"(.*)\"$" $line dummy match]} {
+ if {$mode == "msgid"} {
+ append msgid $match
+ } elseif {$mode == "msgstr"} {
+ append msgstr $match
+ } else {
+ puts stderr "I do not know what to do: $match"
+ }
+ } else {
+ puts stderr "Cannot handle $line"
+ }
+ }
+ flush_msg
+ close $in
+ close $out
+}
+
+if {$show_statistics} {
+ set str ""
+
+ append str "$translated_count translated message"
+ if {$translated_count != 1} {
+ append str s
+ }
+
+ if {$fuzzy_count > 1} {
+ append str ", $fuzzy_count fuzzy translation"
+ if {$fuzzy_count != 1} {
+ append str s
+ }
+ }
+ if {$not_translated_count > 0} {
+ append str ", $not_translated_count untranslated message"
+ if {$not_translated_count != 1} {
+ append str s
+ }
+ }
+
+ append str .
+ puts $str
+}
diff --git a/git-gui/po/ru.po b/git-gui/po/ru.po
new file mode 100644
index 0000000000..6727a832ea
--- /dev/null
+++ b/git-gui/po/ru.po
@@ -0,0 +1,1893 @@
+# Translation of git-gui to russian
+# Copyright (C) 2007 Shawn Pearce
+# This file is distributed under the same license as the git-gui package.
+# Irina Riesen <irina.riesen@gmail.com>, 2007.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-10-31 21:23+0100\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"
+"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:597 git-gui.sh:611 git-gui.sh:624 git-gui.sh:707
+#: git-gui.sh:726
+msgid "git-gui: fatal error"
+msgstr "git-gui: критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°"
+
+#: git-gui.sh:558
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "Ð’ %s уÑтановлен неверный шрифт:"
+
+#: git-gui.sh:583
+msgid "Main Font"
+msgstr "Шрифт интерфейÑа"
+
+#: git-gui.sh:584
+msgid "Diff/Console Font"
+msgstr "Шрифт конÑоли и изменений (diff)"
+
+#: git-gui.sh:598
+msgid "Cannot find git in PATH."
+msgstr "git не найден в PATH."
+
+#: git-gui.sh:625
+msgid "Cannot parse Git version string:"
+msgstr "Ðевозможно раÑпознать Ñтроку верÑии Git: "
+
+#: git-gui.sh:643
+#, 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 ""
+"Ðевозможно определить верÑию Git\n"
+"%s указывает на верÑию '%s'.\n"
+"\n"
+"Ð´Ð»Ñ %s требуетÑÑ Ð²ÐµÑ€ÑÐ¸Ñ Git, Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1.5.0\n"
+"\n"
+"ПринÑÑ‚ÑŒ '%s' как верÑию 1.5.0?\n"
+
+#: git-gui.sh:881
+msgid "Git directory not found:"
+msgstr "Каталог Git не найден:"
+
+#: git-gui.sh:888
+msgid "Cannot move to top of working directory:"
+msgstr "Ðевозможно перейти к корню рабочего каталога репозиториÑ: "
+
+#: git-gui.sh:895
+msgid "Cannot use funny .git directory:"
+msgstr "Каталог.git иÑпорчен: "
+
+#: git-gui.sh:900
+msgid "No working directory"
+msgstr "ОтÑутÑтвует рабочий каталог"
+
+#: git-gui.sh:1047
+msgid "Refreshing file status..."
+msgstr "Обновление информации о ÑоÑтоÑнии файлов..."
+
+#: git-gui.sh:1112
+msgid "Scanning for modified files ..."
+msgstr "ПоиÑк измененных файлов..."
+
+#: git-gui.sh:1287 lib/browser.tcl:245
+msgid "Ready."
+msgstr "Готово."
+
+#: git-gui.sh:1553
+msgid "Unmodified"
+msgstr "Ðе изменено"
+
+#: git-gui.sh:1555
+msgid "Modified, not staged"
+msgstr "Изменено, не подготовлено"
+
+#: git-gui.sh:1556 git-gui.sh:1561
+msgid "Staged for commit"
+msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ"
+
+#: git-gui.sh:1557 git-gui.sh:1562
+msgid "Portions staged for commit"
+msgstr "ЧаÑти, подготовленные Ð´Ð»Ñ ÑохранениÑ"
+
+#: git-gui.sh:1558 git-gui.sh:1563
+msgid "Staged for commit, missing"
+msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ, отÑутÑтвует"
+
+#: git-gui.sh:1560
+msgid "Untracked, not staged"
+msgstr "Ðе отÑлеживаетÑÑ, не подготовлено"
+
+#: git-gui.sh:1565
+msgid "Missing"
+msgstr "ОтÑутÑтвует"
+
+#: git-gui.sh:1566
+msgid "Staged for removal"
+msgstr "Подготовлено Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ"
+
+#: git-gui.sh:1567
+msgid "Staged for removal, still present"
+msgstr "Подготовлено Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ, еще не удалено"
+
+#: git-gui.sh:1569 git-gui.sh:1570 git-gui.sh:1571 git-gui.sh:1572
+msgid "Requires merge resolution"
+msgstr "ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта при объединении"
+
+#: git-gui.sh:1607
+msgid "Starting gitk... please wait..."
+msgstr "ЗапуÑкаетÑÑ gitk... пожалуйÑта, ждите..."
+
+#: git-gui.sh:1616
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+"Ðе удалоÑÑŒ запуÑтить gitk:\n"
+"\n"
+"%s не ÑущеÑтвует"
+
+#: git-gui.sh:1816 lib/choose_repository.tcl:35
+msgid "Repository"
+msgstr "Репозиторий"
+
+#: git-gui.sh:1817
+msgid "Edit"
+msgstr "Редактировать"
+
+#: git-gui.sh:1819 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr "Ветвь"
+
+#: git-gui.sh:1822 lib/choose_rev.tcl:547
+msgid "Commit@@noun"
+msgstr "СоÑтоÑние"
+
+#: git-gui.sh:1825 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "Объединить"
+
+#: git-gui.sh:1826 lib/choose_rev.tcl:556
+msgid "Remote"
+msgstr "Внешние репозитории"
+
+#: git-gui.sh:1835
+msgid "Browse Current Branch's Files"
+msgstr "ПроÑмотреть файлы текущей ветви"
+
+#: git-gui.sh:1839
+msgid "Browse Branch Files..."
+msgstr "Показать файлы ветви..."
+
+#: git-gui.sh:1844
+msgid "Visualize Current Branch's History"
+msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ ветви наглÑдно"
+
+#: git-gui.sh:1848
+msgid "Visualize All Branch History"
+msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²Ñех ветвей наглÑдно"
+
+#: git-gui.sh:1855
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "Показать файлы ветви %s"
+
+#: git-gui.sh:1857
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²ÐµÑ‚Ð²Ð¸ %s наглÑдно"
+
+#: git-gui.sh:1862 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "СтатиÑтика базы данных"
+
+#: git-gui.sh:1865 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "Сжать базу данных"
+
+#: git-gui.sh:1868
+msgid "Verify Database"
+msgstr "Проверить базу данных"
+
+#: git-gui.sh:1875 git-gui.sh:1879 git-gui.sh:1883 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr "Создать Ñрлык на рабочем Ñтоле"
+
+#: git-gui.sh:1888 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
+msgid "Quit"
+msgstr "Выход"
+
+#: git-gui.sh:1895
+msgid "Undo"
+msgstr "Отменить"
+
+#: git-gui.sh:1898
+msgid "Redo"
+msgstr "Повторить"
+
+#: git-gui.sh:1902 git-gui.sh:2395
+msgid "Cut"
+msgstr "Вырезать"
+
+#: git-gui.sh:1905 git-gui.sh:2398 git-gui.sh:2469 git-gui.sh:2541
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr "Копировать"
+
+#: git-gui.sh:1908 git-gui.sh:2401
+msgid "Paste"
+msgstr "Ð’Ñтавить"
+
+#: git-gui.sh:1911 git-gui.sh:2404 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "Удалить"
+
+#: git-gui.sh:1915 git-gui.sh:2408 git-gui.sh:2545 lib/console.tcl:69
+msgid "Select All"
+msgstr "Выделить вÑе"
+
+#: git-gui.sh:1924
+msgid "Create..."
+msgstr "Создать..."
+
+#: git-gui.sh:1930
+msgid "Checkout..."
+msgstr "Перейти..."
+
+#: git-gui.sh:1936
+msgid "Rename..."
+msgstr "Переименовать..."
+
+#: git-gui.sh:1941 git-gui.sh:2040
+msgid "Delete..."
+msgstr "Удалить..."
+
+#: git-gui.sh:1946
+msgid "Reset..."
+msgstr "СброÑить..."
+
+#: git-gui.sh:1958 git-gui.sh:2342
+msgid "New Commit"
+msgstr "Ðовое ÑоÑтоÑние"
+
+#: git-gui.sh:1966 git-gui.sh:2349
+msgid "Amend Last Commit"
+msgstr "ИÑправить поÑледнее ÑоÑтоÑние"
+
+#: git-gui.sh:1975 git-gui.sh:2309 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "Перечитать"
+
+#: git-gui.sh:1981
+msgid "Stage To Commit"
+msgstr "Подготовить Ð´Ð»Ñ ÑохранениÑ"
+
+#: git-gui.sh:1986
+msgid "Stage Changed Files To Commit"
+msgstr "Подготовить измененные файлы Ð´Ð»Ñ ÑохранениÑ"
+
+#: git-gui.sh:1992
+msgid "Unstage From Commit"
+msgstr "Убрать из подготовленного"
+
+#: git-gui.sh:1997 lib/index.tcl:393
+msgid "Revert Changes"
+msgstr "Отменить изменениÑ"
+
+#: git-gui.sh:2004 git-gui.sh:2321 git-gui.sh:2419
+msgid "Sign Off"
+msgstr "ПодпиÑать"
+
+#: git-gui.sh:2008 git-gui.sh:2325
+msgid "Commit@@verb"
+msgstr "Сохранить"
+
+#: git-gui.sh:2019
+msgid "Local Merge..."
+msgstr "Локальное объединение..."
+
+#: git-gui.sh:2024
+msgid "Abort Merge..."
+msgstr "Прервать объединение..."
+
+#: git-gui.sh:2036
+msgid "Push..."
+msgstr "Отправить..."
+
+#: git-gui.sh:2047 lib/choose_repository.tcl:40
+msgid "Apple"
+msgstr ""
+
+#: git-gui.sh:2050 git-gui.sh:2072 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
+#, tcl-format
+msgid "About %s"
+msgstr "О %s"
+
+#: git-gui.sh:2054
+msgid "Preferences..."
+msgstr "ÐаÑтройки..."
+
+#: git-gui.sh:2062 git-gui.sh:2587
+msgid "Options..."
+msgstr "ÐаÑтройки..."
+
+#: git-gui.sh:2068 lib/choose_repository.tcl:46
+msgid "Help"
+msgstr "Помощь"
+
+#: git-gui.sh:2109
+msgid "Online Documentation"
+msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð² интернете"
+
+#: git-gui.sh:2193
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr "критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: %s: нет такого файла или каталога"
+
+#: git-gui.sh:2226
+msgid "Current Branch:"
+msgstr "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ‚Ð²ÑŒ:"
+
+#: git-gui.sh:2247
+msgid "Staged Changes (Will Commit)"
+msgstr "Подготовлено (будет Ñохранено)"
+
+#: git-gui.sh:2266
+msgid "Unstaged Changes"
+msgstr "Изменено (не будет Ñохранено)"
+
+#: git-gui.sh:2315
+msgid "Stage Changed"
+msgstr "Подготовить вÑе"
+
+#: git-gui.sh:2331 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "Отправить"
+
+#: git-gui.sh:2361
+msgid "Initial Commit Message:"
+msgstr "Комментарий к первому ÑоÑтоÑнию:"
+
+#: git-gui.sh:2362
+msgid "Amended Commit Message:"
+msgstr "Комментарий к иÑправленному ÑоÑтоÑнию:"
+
+#: git-gui.sh:2363
+msgid "Amended Initial Commit Message:"
+msgstr "Комментарий к иÑправленному первоначальному ÑоÑтоÑнию:"
+
+#: git-gui.sh:2364
+msgid "Amended Merge Commit Message:"
+msgstr "Комментарий к иÑправленному объединению:"
+
+#: git-gui.sh:2365
+msgid "Merge Commit Message:"
+msgstr "Комментарий к объединению:"
+
+#: git-gui.sh:2366
+msgid "Commit Message:"
+msgstr "Комментарий к ÑоÑтоÑнию:"
+
+#: git-gui.sh:2411 git-gui.sh:2549 lib/console.tcl:71
+msgid "Copy All"
+msgstr "Копировать вÑе"
+
+#: git-gui.sh:2435 lib/blame.tcl:104
+msgid "File:"
+msgstr "Файл:"
+
+#: git-gui.sh:2537
+msgid "Refresh"
+msgstr "Обновить"
+
+#: git-gui.sh:2558
+msgid "Apply/Reverse Hunk"
+msgstr "Применить/Убрать изменение"
+
+#: git-gui.sh:2564
+msgid "Decrease Font Size"
+msgstr "Уменьшить размер шрифта"
+
+#: git-gui.sh:2568
+msgid "Increase Font Size"
+msgstr "Увеличить размер шрифта"
+
+#: git-gui.sh:2573
+msgid "Show Less Context"
+msgstr "Меньше контекÑта"
+
+#: git-gui.sh:2580
+msgid "Show More Context"
+msgstr "Больше контекÑта"
+
+#: git-gui.sh:2594
+msgid "Unstage Hunk From Commit"
+msgstr "Ðе ÑохранÑÑ‚ÑŒ чаÑÑ‚ÑŒ"
+
+#: git-gui.sh:2596
+msgid "Stage Hunk For Commit"
+msgstr "Подготовить чаÑÑ‚ÑŒ Ð´Ð»Ñ ÑохранениÑ"
+
+#: git-gui.sh:2615
+msgid "Initializing..."
+msgstr "ИнициализациÑ..."
+
+#: git-gui.sh:2706
+#, 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 ""
+"Возможны ошибки в переменных окружениÑ.\n"
+"\n"
+"Переменные окружениÑ, которые возможно\n"
+"будут проигнорированы командами Git,\n"
+"запущенными из %s\n"
+"\n"
+
+#: git-gui.sh:2736
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"Это извеÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° Ñ Tcl,\n"
+"раÑпроÑтранÑемым Cygwin."
+
+#: git-gui.sh:2741
+#, 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"
+"ВмеÑто иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ %s можно\n"
+"Ñохранить Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ user.name и\n"
+"user.email в Вашем перÑональном\n"
+"файле ~/.gitconfig.\n"
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - графичеÑкий пользовательÑкий Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ðº Git."
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr "ПроÑмотр файла"
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr "Сохраненное ÑоÑтоÑние:"
+
+#: lib/blame.tcl:249
+msgid "Copy Commit"
+msgstr "Скопировать SHA-1"
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr "Чтение %s..."
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr "Загрузка аннотации копирований/переименований..."
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr "Ñтрок прокомментировано"
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr "Загрузка аннотаций первоначального Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°..."
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr "ÐÐ½Ð½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð°."
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr "Загрузка аннотации..."
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr "Ðвтор:"
+
+#: lib/blame.tcl:791
+msgid "Committer:"
+msgstr "Сохранил:"
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr "ИÑходный файл:"
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr "ИÑточник:"
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr "Файл:"
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr "Скопировано/перемещено в:"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "Перейти на ветвь"
+
+#: lib/branch_checkout.tcl:23
+msgid "Checkout"
+msgstr "Перейти"
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr "Отменить"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr "ВерÑиÑ"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+msgid "Options"
+msgstr "ÐаÑтройки"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Получить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð· внешней ветви"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "ОтÑоединить от локальной ветви"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "Создание ветви"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "Создать новую ветвь"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
+msgid "Create"
+msgstr "Создать"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "Ðазвание ветви"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr "Ðазвание:"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "ВзÑÑ‚ÑŒ из имен ветвей ÑлежениÑ"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "ÐÐ°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Обновить имеющуюÑÑ Ð²ÐµÑ‚Ð²ÑŒ:"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Ðет"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Только Fast Forward"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr "СброÑ"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "ПоÑле ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñделать текущей"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "Укажите ветвь ÑлежениÑ."
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr "Ветвь ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ %s не ÑвлÑетÑÑ Ð²ÐµÑ‚Ð²ÑŒÑŽ во внешнем репозитории."
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr "Укажите название ветви."
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "ÐедопуÑтимое название ветви '%s'."
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr "Удаление ветви"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "Удалить локальную ветвь"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "Локальные ветви"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "Удалить только в Ñлучае, еÑли было объединение Ñ"
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr "Ð’Ñегда (не выполнÑÑ‚ÑŒ проверку на объединение)"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "Следующие ветви объединены Ñ %s не полноÑтью:"
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+"ВоÑÑтанавливать удаленные ветви Ñложно. \n"
+"\n"
+" Удалить выбранные ветви?"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Ðе удалоÑÑŒ удалить ветви:\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "Переименование ветви"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "Переименовать"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "Ветвь:"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "Ðовое название:"
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr "Укажите ветвь Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ."
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "Ветвь '%s' уже ÑущеÑтвует."
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Ðе удалоÑÑŒ переименовать '%s'. "
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "ЗапуÑк..."
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "ПроÑмотр ÑпиÑка файлов"
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr "Загрузка %s..."
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr "[Ðа уровень выше]"
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+msgid "Browse Branch Files"
+msgstr "Показать файлы ветви"
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
+msgid "Browse"
+msgstr "Показать"
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Получение %s из %s "
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: невозможно разрешить %s"
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr "Закрыть"
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "Ветвь '%s' не ÑущеÑтвует "
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"Ветвь '%s' уже ÑущеÑтвует.\n"
+"\n"
+"Она не может быть прокручена(fast-forward) к %s.\n"
+"ТребуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ."
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ '%s' не поддерживаетÑÑ."
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "Ðе удалоÑÑŒ обновить '%s'."
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr "Ð Ð°Ð±Ð¾Ñ‡Ð°Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑŒ заблокирована другим процеÑÑом."
+
+#: lib/checkout_op.tcl:266
+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 ""
+"ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n"
+"\n"
+"С момента поÑледней проверки репозиторий был изменен другой программой Git. "
+"Ðеобходимо перечитать репозиторий, прежде чем изменÑÑ‚ÑŒ текущую ветвь.\n"
+"\n"
+"Это будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n"
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "Обновление рабочего каталога из '%s'..."
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr "Прерван переход на '%s' (требуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ на уровне файлов)"
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr "ТребуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ на уровне файлов."
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Ветвь '%s' оÑтаетÑÑ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹."
+
+#: lib/checkout_op.tcl:429
+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 ""
+"Ð’Ñ‹ находитеÑÑŒ не в локальной ветви.\n"
+"\n"
+"ЕÑли вы хотите Ñнова вернутьÑÑ Ðº какой-нибудь ветви, Ñоздайте ее ÑейчаÑ, "
+"Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 'Текущего отÑоединенного ÑоÑтоÑниÑ'."
+
+#: lib/checkout_op.tcl:446
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "Ветвь '%s' Ñделана текущей."
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr "Ð¡Ð±Ñ€Ð¾Ñ '%s' в '%s' приведет к потере Ñледующих Ñохраненных ÑоÑтоÑний: "
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr "ВоÑÑтановить потерÑнные Ñохраненные ÑоÑтоÑÐ½Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ Ñложно."
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "СброÑить '%s'?"
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr "ÐаглÑдно"
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+"Ðе удалоÑÑŒ уÑтановить текущую ветвь.\n"
+"\n"
+"Ваш рабочий каталог обновлен только чаÑтично. Были обновлены вÑе файлы кроме "
+"Ñлужебных файлов Git. \n"
+"\n"
+"Этого не должно было произойти. %s завершаетÑÑ."
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "Выбрать"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "Шрифт"
+
+#: lib/choose_font.tcl:73
+msgid "Font Size"
+msgstr "Размер шрифта"
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr "Пример текÑта"
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"Это пример текÑта.\n"
+"ЕÑли Вам нравитÑÑ Ñтот текÑÑ‚, Ñто может быть Ваш шрифт."
+
+#: lib/choose_repository.tcl:27
+msgid "Git Gui"
+msgstr ""
+
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
+msgid "Create New Repository"
+msgstr "Создать новый репозиторий"
+
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr "Ðовый..."
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
+msgid "Clone Existing Repository"
+msgstr "Склонировать ÑущеÑтвующий репозиторий"
+
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr "Склонировать..."
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
+msgid "Open Existing Repository"
+msgstr "Выбрать ÑущеÑтвующий репозиторий"
+
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr "Открыть..."
+
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr "Ðедавние репозитории"
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr "Открыть поÑледний репозиторий"
+
+#: lib/choose_repository.tcl:294
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "Путь '%s' уже ÑущеÑтвует."
+
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Ðе удалоÑÑŒ Ñоздать репозиторий %s:"
+
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
+msgid "Directory:"
+msgstr "Каталог:"
+
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
+msgid "Git Repository"
+msgstr "Репозиторий"
+
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "Каталог '%s' уже ÑущеÑтвует."
+
+#: lib/choose_repository.tcl:442
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Файл '%s' уже ÑущеÑтвует."
+
+#: lib/choose_repository.tcl:463
+msgid "Clone"
+msgstr "Склонировать"
+
+#: lib/choose_repository.tcl:476
+msgid "URL:"
+msgstr "СÑылка:"
+
+#: lib/choose_repository.tcl:496
+msgid "Clone Type:"
+msgstr "Тип клона:"
+
+#: lib/choose_repository.tcl:502
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Стандартный (БыÑтрый, полуизбыточный, \"жеÑткие\" ÑÑылки)"
+
+#: lib/choose_repository.tcl:508
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "ÐŸÐ¾Ð»Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ (Медленный, Ñоздает резервную копию)"
+
+#: lib/choose_repository.tcl:514
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Общий (Самый быÑтрый, не рекомендуетÑÑ, без резервной копии)"
+
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Каталог не ÑвлÑетÑÑ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸ÐµÐ¼: %s"
+
+#: lib/choose_repository.tcl:586
+msgid "Standard only available for local repository."
+msgstr "Стандартный клон возможен только Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ репозиториÑ."
+
+#: lib/choose_repository.tcl:590
+msgid "Shared only available for local repository."
+msgstr "Общий клон возможен только Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ репозиториÑ."
+
+#: lib/choose_repository.tcl:617
+msgid "Failed to configure origin"
+msgstr "Ðе могу Ñконфигурировать иÑходный репозиторий."
+
+#: lib/choose_repository.tcl:629
+msgid "Counting objects"
+msgstr "Считаю объекты"
+
+#: lib/choose_repository.tcl:630
+msgid "buckets"
+msgstr ""
+
+#: lib/choose_repository.tcl:654
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Ðе могу Ñкопировать objects/info/alternates: %s"
+
+#: lib/choose_repository.tcl:690
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Ðечего клонировать Ñ %s."
+
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
+msgid "The 'master' branch has not been initialized."
+msgstr "Ðе инициализирована ветвь 'master'."
+
+#: lib/choose_repository.tcl:705
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr "\"ЖеÑткие ÑÑылки\" не доÑтупны. Буду иÑпользовать копирование."
+
+#: lib/choose_repository.tcl:717
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Клонирование %s"
+
+#: lib/choose_repository.tcl:748
+msgid "Copying objects"
+msgstr "Копирование objects"
+
+#: lib/choose_repository.tcl:749
+msgid "KiB"
+msgstr "КБ"
+
+#: lib/choose_repository.tcl:773
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Ðе могу Ñкопировать объект: %s"
+
+#: lib/choose_repository.tcl:783
+msgid "Linking objects"
+msgstr "Создание ÑÑылок на objects"
+
+#: lib/choose_repository.tcl:784
+msgid "objects"
+msgstr "объекты"
+
+#: lib/choose_repository.tcl:792
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Ðе могу \"жеÑтко ÑвÑзать\" объект: %s"
+
+#: lib/choose_repository.tcl:847
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr ""
+"Ðе могу получить ветви и объекты. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли."
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch tags. See console output for details."
+msgstr "Ðе могу получить метки. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли."
+
+#: lib/choose_repository.tcl:882
+msgid "Cannot determine HEAD. See console output for details."
+msgstr "Ðе могу определить HEAD. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли."
+
+#: lib/choose_repository.tcl:891
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "Ðе могу очиÑтить %s"
+
+#: lib/choose_repository.tcl:897
+msgid "Clone failed."
+msgstr "Клонирование не удалоÑÑŒ."
+
+#: lib/choose_repository.tcl:904
+msgid "No default branch obtained."
+msgstr "Ðе было получено ветви по умолчанию."
+
+#: lib/choose_repository.tcl:915
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "Ðе могу раÑпознать %s как ÑоÑтоÑние."
+
+#: lib/choose_repository.tcl:927
+msgid "Creating working directory"
+msgstr "Создаю рабочий каталог"
+
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr "файлов"
+
+#: lib/choose_repository.tcl:957
+msgid "Initial file checkout failed."
+msgstr "Ðе удалоÑÑŒ получить начальное ÑоÑтоÑние файлов репозиториÑ."
+
+#: lib/choose_repository.tcl:973
+msgid "Open"
+msgstr "Открыть"
+
+#: lib/choose_repository.tcl:983
+msgid "Repository:"
+msgstr "Репозиторий:"
+
+#: lib/choose_repository.tcl:1033
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Ðе удалоÑÑŒ открыть репозиторий %s:"
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr "Текущее отÑоединенное ÑоÑтоÑние"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Выражение Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð²ÐµÑ€Ñии:"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "Ð›Ð¾ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ‚Ð²ÑŒ:"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "Ветвь ÑлежениÑ"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr "Таг"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð²ÐµÑ€ÑиÑ: %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "ВерÑÐ¸Ñ Ð½Ðµ указана."
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "ПуÑтое выражение Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð²ÐµÑ€Ñии."
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr "Обновлено"
+
+#: lib/choose_rev.tcl:558
+msgid "URL"
+msgstr "СÑылка"
+
+#: 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"
+"\n"
+"Ð’Ñ‹ Ñоздаете первое ÑоÑтоÑние в репозитории, здеÑÑŒ еще нечего иÑправлÑÑ‚ÑŒ.\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"
+"\n"
+"Текущее объединение не завершено. Ðевозможно иÑправить предыдущее "
+"Ñохраненное ÑоÑтоÑние не Ð¿Ñ€ÐµÑ€Ñ‹Ð²Ð°Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐµ объединение.\n"
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr "Ошибка при загрузке данных Ð´Ð»Ñ Ð¸ÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñохраненного ÑоÑтоÑниÑ:"
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr "Ðевозможно получить информацию об авторÑтве:"
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Ðеверный GIT_COMMITTER_IDENT:"
+
+#: lib/commit.tcl:133
+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 ""
+"ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n"
+"\n"
+"С момента поÑледней проверки репозиторий был изменен другой программой Git. "
+"Ðеобходимо перечитать репозиторий, прежде чем изменÑÑ‚ÑŒ текущую ветвь. \n"
+"\n"
+"Это будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n"
+
+#: lib/commit.tcl:154
+#, 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"
+"\n"
+"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт объединениÑ. Разрешите конфликт и добавьте к "
+"подготовленным файлам перед Ñохранением.\n"
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Обнаружено неизвеÑтное ÑоÑтоÑние файла %s.\n"
+"\n"
+"Файл %s не может быть Ñохранен данной программой.\n"
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"ОтÑутÑтвуют Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ.\n"
+"\n"
+"Подготовьте Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один файл до ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñохраненного ÑоÑтоÑниÑ.\n"
+
+#: lib/commit.tcl:183
+msgid ""
+"Please supply a commit message.\n"
+"\n"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentance what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
+msgstr ""
+"Ðапишите комментарий к Ñохраненному ÑоÑтоÑнию.\n"
+"\n"
+"РекомендуетÑÑ Ñледующий формат комментариÑ:\n"
+"\n"
+"- Ð¿ÐµÑ€Ð²Ð°Ñ Ñтрока: краткое опиÑание Ñделанных изменений.\n"
+"- Ð²Ñ‚Ð¾Ñ€Ð°Ñ Ñтрока пуÑтаÑ\n"
+"- оÑтавшиеÑÑ Ñтроки: опишите, что дают ваши изменениÑ.\n"
+
+#: lib/commit.tcl:257
+msgid "write-tree failed:"
+msgstr "Программа write-tree завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:"
+
+#: lib/commit.tcl:275
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "СоÑтоÑние %s выглÑдит поврежденным"
+
+#: lib/commit.tcl:279
+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"
+"\n"
+"Ðи один файл не был изменен и не было объединениÑ.\n"
+"\n"
+"Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки запуÑтитÑÑ Ð¿ÐµÑ€ÐµÑ‡Ð¸Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ðµ репозиториÑ.\n"
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr "ОтуÑтвуют Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ."
+
+#: lib/commit.tcl:303
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "предупреждение: Tcl не поддерживает кодировку '%s'."
+
+#: lib/commit.tcl:317
+msgid "commit-tree failed:"
+msgstr "Программа commit-tree завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:"
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr "Программа update-ref завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:"
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Создано ÑоÑтоÑние %s: %s "
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr "Ð’ процеÑÑе... пожалуйÑта, ждите..."
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr "ПроцеÑÑ ÑƒÑпешно завершен"
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr "Ошибка: не удалоÑÑŒ выполнить команду"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "КоличеÑтво неÑвÑзанных объектов"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "Объем диÑкового проÑтранÑтва, занÑтый неÑвÑзанными объектами"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "КоличеÑтво упакованных объектов"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "КоличеÑтво pack-файлов"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "Объем диÑкового проÑтранÑтва, занÑтый упакованными объектами"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "ÐеÑвÑзанные объекты, которые можно удалить"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "МуÑор"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Сжатие базы объектов"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Проверка базы объектов при помощи fsck"
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Этот репозиторий ÑÐµÐ¹Ñ‡Ð°Ñ Ñодержит примерно %i Ñвободных объектов\n"
+"\n"
+"Ð”Ð»Ñ Ð»ÑƒÑ‡ÑˆÐµÐ¹ производительноÑти рекомендуетÑÑ Ñжать базу данных, когда еÑÑ‚ÑŒ "
+"более %i неÑвÑзанных объектов.\n"
+"\n"
+"Сжать базу данных ÑейчаÑ?"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "ÐÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ð´Ð°Ñ‚Ð° в репозитории: %s"
+
+#: lib/diff.tcl:42
+#, 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 ""
+"Изменений не обнаружено.\n"
+"\n"
+"в %s отутÑтвуют изменениÑ.\n"
+"\n"
+"Дата Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° была обновлена другой программой, но Ñодержимое файла "
+"оÑталоÑÑŒ прежним.\n"
+"\n"
+"Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð±ÑƒÐ´ÐµÑ‚ запущено перечитывание репозиториÑ, чтобы найти подобные файлы."
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Загрузка изменений в %s..."
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Ðе могу показать %s"
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr "Ошибка загрузки файла:"
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr "Репозиторий Git (подпроект)"
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr "* Двоичный файл (Ñодержимое не показано)"
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr "Ошибка загрузки diff:"
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr "Ðе удалоÑÑŒ иÑключить выбранную чаÑÑ‚ÑŒ."
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr "Ðе удалоÑÑŒ подготовить к Ñохранению выбранную чаÑÑ‚ÑŒ."
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr "ошибка"
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr "предупреждение"
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr "Прежде чем Ñохранить, иÑправьте вышеуказанные ошибки."
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Ðе удалоÑÑŒ разблокировать индекÑ"
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "Ошибка индекÑа"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"Ðе удалоÑÑŒ обновить Ð¸Ð½Ð´ÐµÐºÑ Git. СоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚"
+"перечитано автоматичеÑки."
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "Продолжить"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "Разблокировать индекÑ"
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "Удаление %s из подготовленного"
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr "Добавление %s..."
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² файле %s?"
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² %i файле(-ах)?"
+
+#: lib/index.tcl:389
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+"Любые изменениÑ, не подготовленные к Ñохранению, будут потерÑны при данной "
+"операции."
+
+#: lib/index.tcl:392
+msgid "Do Nothing"
+msgstr "Ðичего не делать"
+
+#: 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"
+"\n"
+"Завершите иÑправление данного ÑоÑтоÑÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ´ выполнением операции "
+"объединениÑ.\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 ""
+"ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n"
+"\n"
+"С момента поÑледней проверки репозиторий был изменен другой программой Git. "
+"Ðеобходимо перечитать репозиторий, прежде чем изменÑÑ‚ÑŒ текущую ветвь.\n"
+"\n"
+"Это будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n"
+
+#: lib/merge.tcl:44
+#, 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 ""
+"Предыдущее объединение не завершено из-за конфликта.\n"
+"\n"
+"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт объединениÑ.\n"
+"\n"
+"Разрешите конфликт, подготовьте файл и Ñохраните. Только поÑле Ñтого можно "
+"начать Ñледующее объединение.\n"
+
+#: lib/merge.tcl:54
+#, 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 ""
+"Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ðµ Ñохранены.\n"
+"\n"
+"Файл %s изменен.\n"
+"\n"
+"Подготовьте и Ñохраните Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ´ началом объединениÑ. Ð’ Ñлучае "
+"необходимоÑти Ñто позволит прервать операцию объединениÑ.\n"
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s из %s"
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr "Объединение %s и %s"
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr "Объединение уÑпешно завершено."
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr "Ðе удалоÑÑŒ завершить объединение. ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта."
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Объединить Ñ %s"
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr "ВерÑÐ¸Ñ Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ"
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Ðевозможно прервать иÑправление.\n"
+"\n"
+"Завершите текущее иÑправление Ñохраненного ÑоÑтоÑниÑ.\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 ""
+"Прервать объединение?\n"
+"\n"
+"Прерывание Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´ÐµÑ‚ к потере *ВСЕХ* неÑохраненных изменений.\n"
+"\n"
+"Продолжить?"
+
+#: 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 ""
+"Прервать объединение?\n"
+"\n"
+"Прерывание Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´ÐµÑ‚ к потере *ВСЕХ* неÑохраненных изменений.\n"
+"\n"
+"Продолжить?"
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr "Прерываю"
+
+#: lib/merge.tcl:266
+msgid "Abort failed."
+msgstr "Прервать не удалоÑÑŒ."
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr "Прервано."
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr "ВоÑÑтановить наÑтройки по умолчанию"
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr "Сохранить"
+
+#: lib/option.tcl:96
+#, tcl-format
+msgid "%s Repository"
+msgstr "Ð´Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ %s"
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr "Общие (Ð´Ð»Ñ Ð²Ñех репозиториев)"
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ"
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr "ÐÐ´ÐµÑ Ñлектронной почты"
+
+#: lib/option.tcl:106
+msgid "Summarize Merge Commits"
+msgstr "Суммарный комментарий при объединении"
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr "Уровень детальноÑти Ñообщений при объединении"
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr "Показать отчет об изменениÑÑ… поÑле объединениÑ"
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr "ДоверÑÑ‚ÑŒ времени модификации файла"
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr "ЧиÑтка ветвей ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ получении изменений"
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr "Ð˜Ð¼Ñ Ð½Ð¾Ð²Ð¾Ð¹ ветви взÑÑ‚ÑŒ из имен ветвей ÑлежениÑ"
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr "ЧиÑло Ñтрок в контекÑте diff"
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr "Шаблон Ð´Ð»Ñ Ð¸Ð¼ÐµÐ½Ð¸ новой ветви"
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr "Изменить шрифт"
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr "Выберите %s"
+
+# carbon copy
+#: lib/option.tcl:186
+msgid "pt."
+msgstr ""
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr "ÐаÑтройки"
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr "Ðе удалоÑÑŒ полноÑтью Ñохранить наÑтройки:"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr "Удалить внешнюю ветвь"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "Из репозиториÑ"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr "внешний:"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr "по указанному URL:"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "Ветви"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "Удалить только в Ñлучае, еÑли"
+
+#: lib/remote_branch_delete.tcl:111
+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 "Ð”Ð»Ñ Ð¾Ð¿Ñ†Ð¸Ð¸ 'Объединено Ñ' требуетÑÑ ÑƒÐºÐ°Ð·Ð°Ñ‚ÑŒ ветвь."
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"Следующие ветви объединены Ñ %s не полноÑтью:\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 ""
+"Один или неÑколько теÑтов на объединение не прошли, потому что Ð’Ñ‹ не "
+"получили необходимые ÑоÑтоÑниÑ. ПопытайтеÑÑŒ получить их из %s."
+
+#: lib/remote_branch_delete.tcl:207
+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
+msgid "No repository selected."
+msgstr "Ðе указан репозиторий."
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "Перечитывание %s... "
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "ЧиÑтка"
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "Получение из"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "Отправить"
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "Ðевозможно запиÑать ÑÑылку:"
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr "Ðевозможно запиÑать значок:"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s ... %*i из %*i %s (%3i%%)"
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "получение %s"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Получение изменений из %s "
+
+# carbon copy
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "чиÑтка внешнего %s"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "ЧиÑтка ветвей ÑлежениÑ, удаленных из %s"
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "отправить %s"
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Отправка изменений в %s "
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "Отправка %s %s в %s"
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr "Отправить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² ветвÑÑ…"
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr "ИÑходные ветви"
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr "Репозиторий назначениÑ"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr "ÐаÑтройки отправки"
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr "Ðамеренно перепиÑать ÑущеÑтвующую ветвь (возможна Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹)"
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr "ИÑпользовать thin pack (Ð´Ð»Ñ Ð¼ÐµÐ´Ð»ÐµÐ½Ð½Ñ‹Ñ… Ñетевых подключений)"
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr "Передать таги"
+
+#~ msgid "Next >"
+#~ msgstr "Дальше >"
diff --git a/git-gui/po/sv.po b/git-gui/po/sv.po
new file mode 100644
index 0000000000..cd3f40b4a8
--- /dev/null
+++ b/git-gui/po/sv.po
@@ -0,0 +1,1895 @@
+# Swedish translation of git-gui.
+# Copyright (C) 2007 Shawn Pearce, et al.
+# This file is distributed under the same license as the git-gui package.
+#
+# Peter Karlsson <peter@softwolves.pp.se>, 2007-2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: sv\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-11-24 10:36+0100\n"
+"PO-Revision-Date: 2008-01-12 09:27+0100\n"
+"Last-Translator: Peter Karlsson <peter@softwolves.pp.se>\n"
+"Language-Team: Swedish <sv@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+
+#: git-gui.sh:41 git-gui.sh:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714
+#: git-gui.sh:733
+msgid "git-gui: fatal error"
+msgstr "git-gui: ödesdigert fel"
+
+#: git-gui.sh:565
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "Ogiltigt teckensnitt angivet i %s:"
+
+#: git-gui.sh:590
+msgid "Main Font"
+msgstr "Huvudteckensnitt"
+
+#: git-gui.sh:591
+msgid "Diff/Console Font"
+msgstr "Diff/konsolteckensnitt"
+
+#: git-gui.sh:605
+msgid "Cannot find git in PATH."
+msgstr "Hittar inte git i PATH."
+
+#: git-gui.sh:632
+msgid "Cannot parse Git version string:"
+msgstr "Kan inte tolka versionssträng från Git:"
+
+#: git-gui.sh:650
+#, 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 ""
+"Kan inte avgöra Gits version.\n"
+"\n"
+"%s säger att dess version är \"%s\".\n"
+"\n"
+"%s kräver minst Git 1.5.0 eller senare.\n"
+"\n"
+"Anta att \"%s\" är version 1.5.0?\n"
+
+#: git-gui.sh:888
+msgid "Git directory not found:"
+msgstr "Git-katalogen hittades inte:"
+
+#: git-gui.sh:895
+msgid "Cannot move to top of working directory:"
+msgstr "Kan inte gå till början på arbetskatalogen:"
+
+#: git-gui.sh:902
+msgid "Cannot use funny .git directory:"
+msgstr "Kan inte använda underlig .git-katalog:"
+
+#: git-gui.sh:907
+msgid "No working directory"
+msgstr "Ingen arbetskatalog"
+
+#: git-gui.sh:1054
+msgid "Refreshing file status..."
+msgstr "Uppdaterar filstatus..."
+
+#: git-gui.sh:1119
+msgid "Scanning for modified files ..."
+msgstr "Söker efter ändrade filer..."
+
+#: git-gui.sh:1294 lib/browser.tcl:245
+msgid "Ready."
+msgstr "Klar."
+
+#: git-gui.sh:1560
+msgid "Unmodified"
+msgstr "Oförändrade"
+
+#: git-gui.sh:1562
+msgid "Modified, not staged"
+msgstr "Förändrade, ej köade"
+
+#: git-gui.sh:1563 git-gui.sh:1568
+msgid "Staged for commit"
+msgstr "Köade för incheckning"
+
+#: git-gui.sh:1564 git-gui.sh:1569
+msgid "Portions staged for commit"
+msgstr "Delar köade för incheckning"
+
+#: git-gui.sh:1565 git-gui.sh:1570
+msgid "Staged for commit, missing"
+msgstr "Köade för incheckning, saknade"
+
+#: git-gui.sh:1567
+msgid "Untracked, not staged"
+msgstr "Ej spårade, ej köade"
+
+#: git-gui.sh:1572
+msgid "Missing"
+msgstr "Saknade"
+
+#: git-gui.sh:1573
+msgid "Staged for removal"
+msgstr "Köade för borttagning"
+
+#: git-gui.sh:1574
+msgid "Staged for removal, still present"
+msgstr "Köade för borttagning, fortfarande närvarande"
+
+#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579
+msgid "Requires merge resolution"
+msgstr "Kräver konflikthantering efter sammanslagning"
+
+#: git-gui.sh:1614
+msgid "Starting gitk... please wait..."
+msgstr "Startar gitk... vänta..."
+
+#: git-gui.sh:1623
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+"Kan inte starta gitk:\n"
+"\n"
+"%s finns inte"
+
+#: git-gui.sh:1823 lib/choose_repository.tcl:35
+msgid "Repository"
+msgstr "Arkiv"
+
+#: git-gui.sh:1824
+msgid "Edit"
+msgstr "Redigera"
+
+#: git-gui.sh:1826 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr "Gren"
+
+#: git-gui.sh:1829 lib/choose_rev.tcl:547
+msgid "Commit@@noun"
+msgstr "Incheckning"
+
+#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "Slå ihop"
+
+#: git-gui.sh:1833 lib/choose_rev.tcl:556
+msgid "Remote"
+msgstr "Fjärr"
+
+#: git-gui.sh:1842
+msgid "Browse Current Branch's Files"
+msgstr "Bläddra i grenens filer"
+
+#: git-gui.sh:1846
+msgid "Browse Branch Files..."
+msgstr "Bläddra filer på gren..."
+
+#: git-gui.sh:1851
+msgid "Visualize Current Branch's History"
+msgstr "Visualisera grenens historik"
+
+#: git-gui.sh:1855
+msgid "Visualize All Branch History"
+msgstr "Visualisera alla grenars historik"
+
+#: git-gui.sh:1862
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "Bläddra i filer för %s"
+
+#: git-gui.sh:1864
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "Visualisera historik för %s"
+
+#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "Databasstatistik"
+
+#: git-gui.sh:1872 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "Komprimera databas"
+
+#: git-gui.sh:1875
+msgid "Verify Database"
+msgstr "Verifiera databas"
+
+#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr "Skapa skrivbordsikon"
+
+#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
+msgid "Quit"
+msgstr "Avsluta"
+
+#: git-gui.sh:1902
+msgid "Undo"
+msgstr "Ã…ngra"
+
+#: git-gui.sh:1905
+msgid "Redo"
+msgstr "Gör om"
+
+#: git-gui.sh:1909 git-gui.sh:2403
+msgid "Cut"
+msgstr "Klipp ut"
+
+#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr "Kopiera"
+
+#: git-gui.sh:1915 git-gui.sh:2409
+msgid "Paste"
+msgstr "Klistra in"
+
+#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "Ta bort"
+
+#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69
+msgid "Select All"
+msgstr "Markera alla"
+
+#: git-gui.sh:1931
+msgid "Create..."
+msgstr "Skapa..."
+
+#: git-gui.sh:1937
+msgid "Checkout..."
+msgstr "Checka ut..."
+
+#: git-gui.sh:1943
+msgid "Rename..."
+msgstr "Byt namn..."
+
+#: git-gui.sh:1948 git-gui.sh:2048
+msgid "Delete..."
+msgstr "Ta bort..."
+
+#: git-gui.sh:1953
+msgid "Reset..."
+msgstr "Återställ..."
+
+#: git-gui.sh:1965 git-gui.sh:2350
+msgid "New Commit"
+msgstr "Ny incheckning"
+
+#: git-gui.sh:1973 git-gui.sh:2357
+msgid "Amend Last Commit"
+msgstr "Lägg till föregående incheckning"
+
+#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "Sök på nytt"
+
+#: git-gui.sh:1988
+msgid "Stage To Commit"
+msgstr "Köa för incheckning"
+
+#: git-gui.sh:1994
+msgid "Stage Changed Files To Commit"
+msgstr "Köa ändrade filer för incheckning"
+
+#: git-gui.sh:2000
+msgid "Unstage From Commit"
+msgstr "Ta bort från incheckningskö"
+
+#: git-gui.sh:2005 lib/index.tcl:393
+msgid "Revert Changes"
+msgstr "Återställ ändringar"
+
+#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427
+msgid "Sign Off"
+msgstr "Skriv under"
+
+#: git-gui.sh:2016 git-gui.sh:2333
+msgid "Commit@@verb"
+msgstr "Checka in"
+
+#: git-gui.sh:2027
+msgid "Local Merge..."
+msgstr "Lokal sammanslagning..."
+
+#: git-gui.sh:2032
+msgid "Abort Merge..."
+msgstr "Avbryt sammanslagning..."
+
+#: git-gui.sh:2044
+msgid "Push..."
+msgstr "Sänd..."
+
+#: git-gui.sh:2055 lib/choose_repository.tcl:40
+msgid "Apple"
+msgstr "Äpple"
+
+#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
+#, tcl-format
+msgid "About %s"
+msgstr "Om %s"
+
+#: git-gui.sh:2062
+msgid "Preferences..."
+msgstr "Inställningar..."
+
+#: git-gui.sh:2070 git-gui.sh:2595
+msgid "Options..."
+msgstr "Alternativ..."
+
+#: git-gui.sh:2076 lib/choose_repository.tcl:46
+msgid "Help"
+msgstr "Hjälp"
+
+#: git-gui.sh:2117
+msgid "Online Documentation"
+msgstr "Webbdokumentation"
+
+#: git-gui.sh:2201
+#, 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:2234
+msgid "Current Branch:"
+msgstr "Aktuell gren:"
+
+#: git-gui.sh:2255
+msgid "Staged Changes (Will Commit)"
+msgstr "Köade ändringar (kommer att checkas in)"
+
+#: git-gui.sh:2274
+msgid "Unstaged Changes"
+msgstr "Oköade ändringar"
+
+#: git-gui.sh:2323
+msgid "Stage Changed"
+msgstr "Köa ändrade"
+
+#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "Sänd"
+
+#: git-gui.sh:2369
+msgid "Initial Commit Message:"
+msgstr "Inledande incheckningsmeddelande:"
+
+#: git-gui.sh:2370
+msgid "Amended Commit Message:"
+msgstr "Utökat incheckningsmeddelande:"
+
+#: git-gui.sh:2371
+msgid "Amended Initial Commit Message:"
+msgstr "Utökat inledande incheckningsmeddelande:"
+
+#: git-gui.sh:2372
+msgid "Amended Merge Commit Message:"
+msgstr "Utökat incheckningsmeddelande för sammanslagning:"
+
+#: git-gui.sh:2373
+msgid "Merge Commit Message:"
+msgstr "Incheckningsmeddelande för sammanslagning:"
+
+#: git-gui.sh:2374
+msgid "Commit Message:"
+msgstr "Incheckningsmeddelande:"
+
+#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71
+msgid "Copy All"
+msgstr "Kopiera alla"
+
+#: git-gui.sh:2443 lib/blame.tcl:104
+msgid "File:"
+msgstr "Fil:"
+
+#: git-gui.sh:2545
+msgid "Refresh"
+msgstr "Uppdatera"
+
+#: git-gui.sh:2566
+msgid "Apply/Reverse Hunk"
+msgstr "Använd/återställ del"
+
+#: git-gui.sh:2572
+msgid "Decrease Font Size"
+msgstr "Minska teckensnittsstorlek"
+
+#: git-gui.sh:2576
+msgid "Increase Font Size"
+msgstr "Öka teckensnittsstorlek"
+
+#: git-gui.sh:2581
+msgid "Show Less Context"
+msgstr "Visa mindre sammanhang"
+
+#: git-gui.sh:2588
+msgid "Show More Context"
+msgstr "Visa mer sammanhang"
+
+#: git-gui.sh:2602
+msgid "Unstage Hunk From Commit"
+msgstr "Ta bort del ur incheckningskö"
+
+#: git-gui.sh:2604
+msgid "Stage Hunk For Commit"
+msgstr "Ställ del i incheckningskö"
+
+#: git-gui.sh:2623
+msgid "Initializing..."
+msgstr "Initierar..."
+
+#: git-gui.sh:2718
+#, 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 ""
+"Det finns möjliga problem med miljövariabler.\n"
+"\n"
+"Följande miljövariabler kommer troligen att\n"
+"ignoreras av alla Git-underprocesser som körs\n"
+"av %s:\n"
+"\n"
+
+#: git-gui.sh:2748
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"Detta beror på ett känt problem med\n"
+"Tcl-binären som följer med Cygwin."
+
+#: git-gui.sh:2753
+#, 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"
+"Du kan ersätta %s\n"
+"med att lägga in värden för inställningarna\n"
+"user.name och user.email i din personliga\n"
+"~/.gitconfig-fil.\n"
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - ett grafiskt användargränssnitt för Git."
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr "Filvisare"
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr "Incheckning:"
+
+#: lib/blame.tcl:249
+msgid "Copy Commit"
+msgstr "Kopiera incheckning"
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr "Läser %s..."
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr "Läser annoteringar för kopiering/flyttning..."
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr "rader annoterade"
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr "Läser in annotering av originalplacering..."
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr "Annotering fullbordad."
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr "Läser in annotering..."
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr "Författare:"
+
+#: lib/blame.tcl:791
+msgid "Committer:"
+msgstr "Incheckare:"
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr "Ursprunglig fil:"
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr "Ursprungligen av:"
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr "I filen:"
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr "Kopierad eller flyttad hit av:"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "Checka ut gren"
+
+#: lib/branch_checkout.tcl:23
+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:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr "Revision"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+msgid "Options"
+msgstr "Alternativ"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Hämta spårande gren"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "Koppla bort från lokal gren"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "Skapa gren"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "Skapa ny gren"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
+msgid "Create"
+msgstr "Skapa"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "Namn på gren"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr "Namn:"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "Använd namn på spårad gren"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Inledande revision"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Uppdatera befintlig gren:"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Nej"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Endast snabbspolning"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr "Återställ"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Checka ut när skapad"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "Välj en gren att spåra."
+
+#: lib/branch_create.tcl:140
+#, 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
+msgid "Please supply a branch name."
+msgstr "Ange ett namn för grenen."
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, 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
+msgid "Delete Branch"
+msgstr "Ta bort gren"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "Ta bort lokal gren"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "Lokala grenar"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "Ta bara bort om sammanslagen med"
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr "Alltid (utför inte sammanslagningstest)."
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+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
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+"Det är svårt att återställa borttagna grenar.\n"
+"\n"
+" Ta bort valda grenar?"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Kunde inte ta bort grenar:\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "Byt namn på gren"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "Byt namn"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "Gren:"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "Nytt namn:"
+
+#: lib/branch_rename.tcl:75
+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:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "Grenen \"%s\" finns redan."
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Kunde inte byta namn på \"%s\"."
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Startar..."
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "Filbläddrare"
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr "Läser %s..."
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr "[Upp till förälder]"
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+msgid "Browse Branch Files"
+msgstr "Bläddra filer på grenen"
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
+msgid "Browse"
+msgstr "Bläddra"
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Hämtar %s från %s"
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "ödesdigert: Kunde inte slå upp %s"
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr "Stäng"
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "Grenen \"%s\" finns inte."
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"Grenen \"%s\" finns redan.\n"
+"\n"
+"Den kan inte snabbspolas till %s.\n"
+"En sammanslagning krävs."
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "Sammanslagningsstrategin \"%s\" stöds inte."
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "Misslyckades med att uppdatera \"%s\"."
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr "Köområdet (index) är redan låst."
+
+#: lib/checkout_op.tcl:266
+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 ""
+"Det senaste inlästa tillståndet motsvarar inte tillståndet i arkivet.\n"
+"\n"
+"Ett annat Git-program har ändrat arkivet sedan senaste avsökningen. Du "
+"måste utföra en ny sökning innan den aktuella grenen kan ändras.\n"
+"\n"
+"Sökningen kommer att startas automatiskt nu.\n"
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "Uppdaterar arbetskatalogen till \"%s\"..."
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr "Avbryter utcheckning av \"%s\" (sammanslagning på filnivå krävs)."
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr "Sammanslagning på filnivå krävs."
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Stannar på grenen \"%s\"."
+
+#: lib/checkout_op.tcl:429
+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 ""
+"Du är inte längre på en lokal gren.\n"
+"\n"
+"Om du ville vara på en gren skapar du en nu, baserad på \"Denna "
+"frånkopplade utcheckning\"."
+
+#: lib/checkout_op.tcl:446
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "Checkade ut \"%s\"."
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr "Om du återställer \"%s\" till \"%s\" får följande incheckningar förlorade:"
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr "Det kanske inte är så enkelt att återskapa förlorade incheckningar."
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "Återställa \"%s\"?"
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr "Visualisera"
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+"Kunde inte ställa in aktuell gren.\n"
+"\n"
+"Arbetskatalogen har bara växlats delvis. Vi uppdaterade filerna "
+"utan problem, men kunde inte uppdatera en intern fil i Git.\n"
+"\n"
+"Detta skulle inte ha hänt. %s kommer nu stängas och ge upp."
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "Välj"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "Teckensnittsfamilj"
+
+#: lib/choose_font.tcl:73
+msgid "Font Size"
+msgstr "Storlek"
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr "Exempel"
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"Detta är en exempeltext.\n"
+"Om du tycker om den här texten kan den vara ditt teckensnitt."
+
+#: lib/choose_repository.tcl:27
+msgid "Git Gui"
+msgstr "Git Gui"
+
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
+msgid "Create New Repository"
+msgstr "Skapa nytt arkiv"
+
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr "Nytt..."
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
+msgid "Clone Existing Repository"
+msgstr "Klona befintligt arkiv"
+
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr "Klona..."
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
+msgid "Open Existing Repository"
+msgstr "Öppna befintligt arkiv"
+
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr "Öppna..."
+
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr "Senaste arkiven"
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr "Öppna tidigare arkiv:"
+
+#: lib/choose_repository.tcl:294
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "Platsen %s finns redan."
+
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Kunde inte skapa arkivet %s:"
+
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
+msgid "Directory:"
+msgstr "Katalog:"
+
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
+msgid "Git Repository"
+msgstr "Gitarkiv"
+
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "Katalogen %s finns redan."
+
+#: lib/choose_repository.tcl:442
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Filen %s finns redan."
+
+#: lib/choose_repository.tcl:463
+msgid "Clone"
+msgstr "Klona"
+
+#: lib/choose_repository.tcl:476
+msgid "URL:"
+msgstr "Webbadress:"
+
+#: lib/choose_repository.tcl:496
+msgid "Clone Type:"
+msgstr "Typ av klon:"
+
+#: lib/choose_repository.tcl:502
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Standard (snabb, semiredundant, hårda länkar)"
+
+#: lib/choose_repository.tcl:508
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Full kopia (långsammare, redundant säkerhetskopia)"
+
+#: lib/choose_repository.tcl:514
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Delad (snabbast, rekommenderas ej, ingen säkerhetskopia)"
+
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Inte ett Gitarkiv: %s"
+
+#: lib/choose_repository.tcl:586
+msgid "Standard only available for local repository."
+msgstr "Standard är endast tillgängligt för lokala arkiv."
+
+#: lib/choose_repository.tcl:590
+msgid "Shared only available for local repository."
+msgstr "Delat är endast tillgängligt för lokala arkiv."
+
+#: lib/choose_repository.tcl:617
+msgid "Failed to configure origin"
+msgstr "Kunde inte konfigurera ursprung"
+
+#: lib/choose_repository.tcl:629
+msgid "Counting objects"
+msgstr "Räknar objekt"
+
+#: lib/choose_repository.tcl:630
+msgid "buckets"
+msgstr "hinkar"
+
+#: lib/choose_repository.tcl:654
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Kunde inte kopiera objekt/info/alternativ: %s"
+
+#: lib/choose_repository.tcl:690
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Ingenting att klona från %s."
+
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
+msgid "The 'master' branch has not been initialized."
+msgstr "Grenen \"master\" har inte initierats."
+
+#: lib/choose_repository.tcl:705
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr "Hårda länkar är inte tillgängliga. Faller tillbaka på kopiering."
+
+#: lib/choose_repository.tcl:717
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Klonar från %s"
+
+#: lib/choose_repository.tcl:748
+msgid "Copying objects"
+msgstr "Kopierar objekt"
+
+#: lib/choose_repository.tcl:749
+msgid "KiB"
+msgstr "KiB"
+
+#: lib/choose_repository.tcl:773
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Kunde inte kopiera objekt: %s"
+
+#: lib/choose_repository.tcl:783
+msgid "Linking objects"
+msgstr "Länkar objekt"
+
+#: lib/choose_repository.tcl:784
+msgid "objects"
+msgstr "objekt"
+
+#: lib/choose_repository.tcl:792
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Kunde inte hårdlänka objekt: %s"
+
+#: lib/choose_repository.tcl:847
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr "Kunde inte hämta grenar och objekt. Se konsolutdata för detaljer."
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch tags. See console output for details."
+msgstr "Kunde inte hämta taggar. Se konsolutdata för detaljer."
+
+#: lib/choose_repository.tcl:882
+msgid "Cannot determine HEAD. See console output for details."
+msgstr "Kunde inte avgöra HEAD. Se konsolutdata för detaljer."
+
+#: lib/choose_repository.tcl:891
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "Kunde inte städa upp %s"
+
+#: lib/choose_repository.tcl:897
+msgid "Clone failed."
+msgstr "Kloning misslyckades."
+
+#: lib/choose_repository.tcl:904
+msgid "No default branch obtained."
+msgstr "Hämtade ingen standardgren."
+
+#: lib/choose_repository.tcl:915
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "Kunde inte slå upp %s till någon incheckning."
+
+#: lib/choose_repository.tcl:927
+msgid "Creating working directory"
+msgstr "Skapar arbetskatalog"
+
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr "filer"
+
+#: lib/choose_repository.tcl:957
+msgid "Initial file checkout failed."
+msgstr "Inledande filutcheckning misslyckades."
+
+#: lib/choose_repository.tcl:973
+msgid "Open"
+msgstr "Öppna"
+
+#: lib/choose_repository.tcl:983
+msgid "Repository:"
+msgstr "Arkiv:"
+
+#: lib/choose_repository.tcl:1033
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Kunde inte öppna arkivet %s:"
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr "Denna frånkopplade utcheckning"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Revisionsuttryck:"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "Lokal gren"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "Spårande gren"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr "Tagg"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Ogiltig revision: %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "Ingen revision vald."
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "Revisionsuttrycket är tomt."
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr "Uppdaterad"
+
+#: lib/choose_rev.tcl:558
+msgid "URL"
+msgstr "Webbadress"
+
+#: 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 ""
+"Det finns ingenting att utöka.\n"
+"\n"
+"Du håller på att skapa den inledande incheckningen. Det finns ingen tidigare "
+"incheckning att utöka.\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 ""
+"Kan inte utöka vid sammanslagning.\n"
+"\n"
+"Du är i mitten av en sammanslagning som inte är fullbordad. Du kan "
+"inte utöka tidigare incheckningar om du inte först avbryter den "
+"pågående sammanslagningen.\n"
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr "Fel vid inläsning av incheckningsdata för utökning:"
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr "Kunde inte hämta din identitet:"
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Felaktig GIT_COMMITTER_IDENT:"
+
+#: lib/commit.tcl:133
+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 ""
+"Det senaste inlästa tillståndet motsvarar inte tillståndet i arkivet.\n"
+"\n"
+"Ett annat Git-program har ändrat arkivet sedan senaste avsökningen. Du "
+"måste utföra en ny sökning innan du kan göra en ny incheckning.\n"
+"\n"
+"Sökningen kommer att startas automatiskt nu.\n"
+
+#: lib/commit.tcl:154
+#, 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 ""
+"Osammanslagna filer kan inte checkas in.\n"
+"\n"
+"Filen %s har sammanslagningskonflikter. Du måste lösa dem och köa filen "
+"innan du checkar in den.\n"
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Okänd filstatus %s upptäckt.\n"
+"\n"
+"Filen %s kan inte checkas in av programmet.\n"
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Inga ändringar att checka in.\n"
+"\n"
+"Du måste köa åtminstone en fil innan du kan checka in.\n"
+
+#: lib/commit.tcl:183
+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 ""
+"Ange ett incheckningsmeddelande.\n"
+"\n"
+"Ett bra incheckningsmeddelande har följande format:\n"
+"\n"
+"- Första raden: Beskriv i en mening vad du gjorde.\n"
+"- Andra raden: Tom\n"
+"- Följande rader: Beskriv varför det här är en bra ändring.\n"
+
+#: lib/commit.tcl:257
+msgid "write-tree failed:"
+msgstr "write-tree misslyckades:"
+
+#: lib/commit.tcl:275
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "Incheckningen %s verkar vara trasig"
+
+#: lib/commit.tcl:279
+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 ""
+"Inga ändringar att checka in.\n"
+"\n"
+"Inga filer ändrades av incheckningen och det var inte en sammanslagning.\n"
+"\n"
+"En sökning kommer att startas automatiskt nu.\n"
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr "Inga ändringar att checka in."
+
+#: lib/commit.tcl:303
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "varning: Tcl stöder inte teckenkodningen \"%s\"."
+
+#: lib/commit.tcl:317
+msgid "commit-tree failed:"
+msgstr "commit-tree misslyckades:"
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr "update-ref misslyckades:"
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Skapade incheckningen %s: %s"
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr "Arbetar... vänta..."
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr "Lyckades"
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr "Fel: Kommando misslyckades"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "Antal lösa objekt"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "Diskutrymme använt av lösa objekt"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "Antal packade objekt"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "Antal paket"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "Diskutrymme använt av packade objekt"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "Packade objekt som väntar på städning"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "Skräpfiler"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Komprimerar objektdatabasen"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Verifierar objektdatabasen med fsck-objects"
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Arkivet har för närvarande omkring %i lösa objekt.\n"
+"\n"
+"För att bibehålla optimal prestanda rekommenderas det å det bestämdaste att "
+"du komprimerar databasen när den innehåller mer än %i lösa objekt.\n"
+"\n"
+"Komprimera databasen nu?"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Ogiltigt datum från Git: %s"
+
+#: lib/diff.tcl:42
+#, 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 ""
+"Hittade inga skillnader.\n"
+"\n"
+"%s innehåller inga ändringar.\n"
+"\n"
+"Modifieringsdatum för filen uppdaterades av ett annat program, men innehållet "
+"i filen har inte ändrats.\n"
+"\n"
+"En sökning kommer automatiskt att startas för att hitta andra filer som kan "
+"vara i samma tillstånd."
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Läser differens för %s..."
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Kan inte visa %s"
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr "Fel vid läsning av fil:"
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr "Gitarkiv (underprojekt)"
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr "* Binärfil (visar inte innehållet)."
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr "Fel vid inläsning av differens:"
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr "Kunde inte ta bort den valda delen från kön."
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr "Kunde inte lägga till den valda delen till kön."
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr "fel"
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr "varning"
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr "Du måste rätta till felen ovan innan du checkar in."
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Kunde inte låsa upp indexet."
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "Indexfel"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"Misslyckades med att uppdatera Gitindexet. En omsökning kommer att startas "
+"automatiskt för att synkronisera om git-gui."
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "Forstätt"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "LÃ¥s upp index"
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "Tar bort %s för incheckningskön"
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr "Lägger till %s"
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Återställ ändringarna i filen %s?"
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "Återställ ändringarna i dessa %i filer?"
+
+#: lib/index.tcl:389
+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:392
+msgid "Do Nothing"
+msgstr "Gör ingenting"
+
+#: 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 ""
+"Kan inte slå ihop vid utökning.\n"
+"\n"
+"Du måste föra färdig utökningen av incheckningen innan du påbörjar någon "
+"slags sammanslagning.\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 ""
+"Det senaste inlästa tillståndet motsvarar inte tillståndet i arkivet.\n"
+"\n"
+"Ett annat Git-program har ändrat arkivet sedan senaste avsökningen. Du "
+"måste utföra en ny sökning innan du kan utföra en sammanslagning.\n"
+"\n"
+"Sökningen kommer att startas automatiskt nu.\n"
+
+#: lib/merge.tcl:44
+#, 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 ""
+"Du är mitt i en sammanslagning med konflikter.\n"
+"\n"
+"Filen %s har sammanslagningskonflikter.\n"
+"\n"
+"Du måste lösa dem, köa filen och checka in för att fullborda den aktuella "
+"sammanslagningen. När du gjort det kan du påbörja en ny sammanslagning.\n"
+
+#: lib/merge.tcl:54
+#, 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 ""
+"Du är mitt i en ändring.\n"
+"\n"
+"Filen %s har ändringar.\n"
+"\n"
+"Du bör fullborda den aktuella incheckningen innan du påbörjar en "
+"sammanslagning. Om du gör det blir det enklare att avbryta en misslyckad "
+"sammanslagning, om det skulle vara nödvändigt.\n"
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s av %s"
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr "Slår ihop %s och %s"
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr "Sammanslagningen avslutades framgångsrikt."
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr "Sammanslagningen misslyckades. Du måste lösa konflikterna."
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Slå ihop i %s"
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr "Revisioner att slå ihop"
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Kan inte avbryta vid utökning.\n"
+"\n"
+"Du måste göra dig färdig med att utöka incheckningen.\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 ""
+"Avbryt sammanslagning?\n"
+"\n"
+"Om du avbryter sammanslagningen kommer *ALLA* ej incheckade ändringar att "
+"gå förlorade.\n"
+"\n"
+"GÃ¥ vidare med att avbryta den aktuella sammanslagningen?"
+
+#: 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 ""
+"Återställ ändringar?\n"
+"\n"
+"Om du återställer ändringarna kommer *ALLA* ej incheckade ändringar att "
+"gå förlorade.\n"
+"\n"
+"Gå vidare med att återställa de aktuella ändringarna?"
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr "Avbryter"
+
+#: lib/merge.tcl:266
+msgid "Abort failed."
+msgstr "Misslyckades avbryta."
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr "Avbrytning fullbordad. Redo."
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr "Återställ standardvärden"
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr "Spara"
+
+#: lib/option.tcl:96
+#, tcl-format
+msgid "%s Repository"
+msgstr "Arkivet %s"
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr "Globalt (alla arkiv)"
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr "Användarnamn"
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr "E-postadress"
+
+#: lib/option.tcl:106
+msgid "Summarize Merge Commits"
+msgstr "Summera sammanslagningsincheckningar"
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr "Pratsamhet för sammanslagningar"
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr "Visa diffstatistik efter sammanslagning"
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr "Lita på filändringstidsstämplar"
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr "Städa spårade grenar vid hämtning"
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr "Matcha spårade grenar"
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr "Antal rader sammanhang i differenser"
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr "Mall för namn på nya grenar"
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr "Byt teckensnitt"
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr "Välj %s"
+
+#: lib/option.tcl:186
+msgid "pt."
+msgstr "p."
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr "Inställningar"
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr "Misslyckades med att helt spara alternativ:"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr "Ta bort fjärrgren"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "Från arkiv"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr "Fjärr:"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr "Godtycklig webbadress:"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "Grenar"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "Ta endast bort om"
+
+#: lib/remote_branch_delete.tcl:111
+msgid "Merged Into:"
+msgstr "Sammanslagen i:"
+
+#: lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Alltid (utför inte sammanslagningstest)"
+
+#: lib/remote_branch_delete.tcl:152
+msgid "A branch is required for 'Merged Into'."
+msgstr "En gren krävs för \"Sammanslagen i\"."
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"Följande grenar har inte helt slagits samman i %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 ""
+"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
+msgid "Please select one or more branches to delete."
+msgstr "Välj en eller flera grenar att ta bort."
+
+#: lib/remote_branch_delete.tcl:216
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Det kan vara svårt att återställa grenar.\n"
+"\n"
+"Ta bort de valda grenarna?"
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr "Tar bort grenar från %s"
+
+#: lib/remote_branch_delete.tcl:286
+msgid "No repository selected."
+msgstr "Inget arkiv markerat."
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "Söker %s..."
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "Ta bort från"
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "Hämta från"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "Sänd till"
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "Kan inte skriva genväg:"
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr "Kan inte skriva ikon:"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s... %*i av %*i %s (%3i%%)"
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "hämta %s"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Hämtar nya ändringar från %s"
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "fjärrborttagning %s"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Tar bort spårande grenar som tagits bort från %s"
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "sänd %s"
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Sänder ändringar till %s"
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "Sänder %s %s till %s"
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr "Sänder grenar"
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr "Källgrenar"
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr "Destinationsarkiv"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr "Överföringsalternativ"
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr "Tvinga överskrivning av befintlig gren (kan kasta bort ändringar)"
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr "Använd tunt paket (för långsamma nätverksanslutningar)"
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr "Ta med taggar"
+
diff --git a/git-gui/po/zh_cn.po b/git-gui/po/zh_cn.po
new file mode 100644
index 0000000000..621c9479b2
--- /dev/null
+++ b/git-gui/po/zh_cn.po
@@ -0,0 +1,1769 @@
+# Translation of git-gui to Chinese
+# Copyright (C) 2007 Shawn Pearce
+# This file is distributed under the same license as the git-gui package.
+# Xudong Guan <xudong.guan@gmail.com>, 2007.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-10-10 04:04-0400\n"
+"PO-Revision-Date: 2007-07-21 01:23-0700\n"
+"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n"
+"Language-Team: Chinese\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:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744
+#: git-gui.sh:763
+msgid "git-gui: fatal error"
+msgstr ""
+
+#: git-gui.sh:595
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr ""
+
+#: git-gui.sh:620
+msgid "Main Font"
+msgstr ""
+
+#: git-gui.sh:621
+msgid "Diff/Console Font"
+msgstr ""
+
+#: git-gui.sh:635
+msgid "Cannot find git in PATH."
+msgstr ""
+
+#: git-gui.sh:662
+msgid "Cannot parse Git version string:"
+msgstr ""
+
+#: git-gui.sh:680
+#, 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 ""
+
+#: git-gui.sh:853
+msgid "Git directory not found:"
+msgstr ""
+
+#: git-gui.sh:860
+msgid "Cannot move to top of working directory:"
+msgstr ""
+
+#: git-gui.sh:867
+msgid "Cannot use funny .git directory:"
+msgstr ""
+
+#: git-gui.sh:872
+msgid "No working directory"
+msgstr ""
+
+#: git-gui.sh:1019
+msgid "Refreshing file status..."
+msgstr ""
+
+#: git-gui.sh:1084
+msgid "Scanning for modified files ..."
+msgstr ""
+
+#: git-gui.sh:1259 lib/browser.tcl:245
+#, fuzzy
+msgid "Ready."
+msgstr "é‡åš"
+
+#: git-gui.sh:1525
+msgid "Unmodified"
+msgstr ""
+
+#: git-gui.sh:1527
+msgid "Modified, not staged"
+msgstr ""
+
+#: git-gui.sh:1528 git-gui.sh:1533
+#, fuzzy
+msgid "Staged for commit"
+msgstr "从本次æ交移除"
+
+#: git-gui.sh:1529 git-gui.sh:1534
+#, fuzzy
+msgid "Portions staged for commit"
+msgstr "从本次æ交移除"
+
+#: git-gui.sh:1530 git-gui.sh:1535
+msgid "Staged for commit, missing"
+msgstr ""
+
+#: git-gui.sh:1532
+msgid "Untracked, not staged"
+msgstr ""
+
+#: git-gui.sh:1537
+msgid "Missing"
+msgstr ""
+
+#: git-gui.sh:1538
+msgid "Staged for removal"
+msgstr ""
+
+#: git-gui.sh:1539
+msgid "Staged for removal, still present"
+msgstr ""
+
+#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544
+msgid "Requires merge resolution"
+msgstr ""
+
+#: git-gui.sh:1579
+msgid "Starting gitk... please wait..."
+msgstr ""
+
+#: git-gui.sh:1588
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+
+#: git-gui.sh:1788 lib/choose_repository.tcl:32
+msgid "Repository"
+msgstr "版本树"
+
+#: git-gui.sh:1789
+msgid "Edit"
+msgstr "编辑"
+
+#: git-gui.sh:1791 lib/choose_rev.tcl:560
+msgid "Branch"
+msgstr "分支"
+
+#: git-gui.sh:1794 lib/choose_rev.tcl:547
+#, fuzzy
+msgid "Commit@@noun"
+msgstr "æ交"
+
+#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "åˆå¹¶"
+
+#: git-gui.sh:1798 lib/choose_rev.tcl:556
+#, fuzzy
+msgid "Remote"
+msgstr "改å..."
+
+#: git-gui.sh:1807
+msgid "Browse Current Branch's Files"
+msgstr "æµè§ˆå½“å‰åˆ†æ”¯æ–‡ä»¶"
+
+#: git-gui.sh:1811
+#, fuzzy
+msgid "Browse Branch Files..."
+msgstr "æµè§ˆå½“å‰åˆ†æ”¯æ–‡ä»¶"
+
+#: git-gui.sh:1816
+msgid "Visualize Current Branch's History"
+msgstr "调用gitk显示当å‰åˆ†æ”¯"
+
+#: git-gui.sh:1820
+msgid "Visualize All Branch History"
+msgstr "调用gitk显示所有分支"
+
+#: git-gui.sh:1827
+#, fuzzy, tcl-format
+msgid "Browse %s's Files"
+msgstr "æµè§ˆå½“å‰åˆ†æ”¯æ–‡ä»¶"
+
+#: git-gui.sh:1829
+#, fuzzy, tcl-format
+msgid "Visualize %s's History"
+msgstr "调用gitk显示所有分支"
+
+#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "æ•°æ®åº“统计数æ®"
+
+#: git-gui.sh:1837 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "压缩数æ®åº“"
+
+#: git-gui.sh:1840
+msgid "Verify Database"
+msgstr "验è¯æ•°æ®åº“"
+
+#: git-gui.sh:1847 git-gui.sh:1851 git-gui.sh:1855 lib/shortcut.tcl:9
+#: lib/shortcut.tcl:45 lib/shortcut.tcl:84
+msgid "Create Desktop Icon"
+msgstr "创建桌é¢å›¾æ ‡"
+
+#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95
+msgid "Quit"
+msgstr "退出"
+
+#: git-gui.sh:1867
+msgid "Undo"
+msgstr "撤销"
+
+#: git-gui.sh:1870
+msgid "Redo"
+msgstr "é‡åš"
+
+#: git-gui.sh:1874 git-gui.sh:2366
+msgid "Cut"
+msgstr "剪切"
+
+#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512
+#: lib/console.tcl:67
+msgid "Copy"
+msgstr "å¤åˆ¶"
+
+#: git-gui.sh:1880 git-gui.sh:2372
+msgid "Paste"
+msgstr "粘贴"
+
+#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "删除"
+
+#: git-gui.sh:1887 git-gui.sh:2379 git-gui.sh:2516 lib/console.tcl:69
+msgid "Select All"
+msgstr "全选"
+
+#: git-gui.sh:1896
+msgid "Create..."
+msgstr "新建..."
+
+#: git-gui.sh:1902
+msgid "Checkout..."
+msgstr "切æ¢..."
+
+#: git-gui.sh:1908
+msgid "Rename..."
+msgstr "改å..."
+
+#: git-gui.sh:1913 git-gui.sh:2012
+msgid "Delete..."
+msgstr "删除..."
+
+#: git-gui.sh:1918
+msgid "Reset..."
+msgstr "é‡ç½®æ‰€æœ‰ä¿®åŠ¨..."
+
+#: git-gui.sh:1930 git-gui.sh:2313
+msgid "New Commit"
+msgstr "æ–°æ交"
+
+#: git-gui.sh:1938 git-gui.sh:2320
+msgid "Amend Last Commit"
+msgstr "修订上次æ交"
+
+#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "é‡æ–°æ‰«æ"
+
+#: git-gui.sh:1953
+#, fuzzy
+msgid "Stage To Commit"
+msgstr "从本次æ交移除"
+
+#: git-gui.sh:1958
+#, fuzzy
+msgid "Stage Changed Files To Commit"
+msgstr "将被æ交的修改"
+
+#: git-gui.sh:1964
+msgid "Unstage From Commit"
+msgstr "从本次æ交移除"
+
+#: git-gui.sh:1969 lib/index.tcl:352
+msgid "Revert Changes"
+msgstr "æ¢å¤ä¿®æ”¹"
+
+#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390
+msgid "Sign Off"
+msgstr "ç­¾å"
+
+#: git-gui.sh:1980 git-gui.sh:2296
+#, fuzzy
+msgid "Commit@@verb"
+msgstr "æ交"
+
+#: git-gui.sh:1991
+msgid "Local Merge..."
+msgstr "本地åˆå¹¶..."
+
+#: git-gui.sh:1996
+msgid "Abort Merge..."
+msgstr "å–消åˆå¹¶..."
+
+#: git-gui.sh:2008
+msgid "Push..."
+msgstr "上传..."
+
+#: git-gui.sh:2019 lib/choose_repository.tcl:41
+msgid "Apple"
+msgstr "苹果"
+
+#: git-gui.sh:2022 git-gui.sh:2044 lib/about.tcl:13
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50
+#, tcl-format
+msgid "About %s"
+msgstr "关于%s"
+
+#: git-gui.sh:2026
+msgid "Preferences..."
+msgstr ""
+
+#: git-gui.sh:2034 git-gui.sh:2558
+msgid "Options..."
+msgstr "选项..."
+
+#: git-gui.sh:2040 lib/choose_repository.tcl:47
+msgid "Help"
+msgstr "帮助"
+
+#: git-gui.sh:2081
+msgid "Online Documentation"
+msgstr "在线文档"
+
+#: git-gui.sh:2165
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr ""
+
+#: git-gui.sh:2198
+msgid "Current Branch:"
+msgstr "当å‰åˆ†æ”¯:"
+
+#: git-gui.sh:2219
+#, fuzzy
+msgid "Staged Changes (Will Commit)"
+msgstr "将被æ交的修改"
+
+#: git-gui.sh:2239
+msgid "Unstaged Changes"
+msgstr ""
+
+#: git-gui.sh:2286
+msgid "Stage Changed"
+msgstr ""
+
+#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "上传"
+
+#: git-gui.sh:2332
+msgid "Initial Commit Message:"
+msgstr "åˆå§‹æ交æè¿°:"
+
+#: git-gui.sh:2333
+msgid "Amended Commit Message:"
+msgstr "修订æ交æè¿°:"
+
+#: git-gui.sh:2334
+msgid "Amended Initial Commit Message:"
+msgstr "修订åˆå§‹æ交æè¿°:"
+
+#: git-gui.sh:2335
+msgid "Amended Merge Commit Message:"
+msgstr "修订åˆå¹¶æ交æè¿°:"
+
+#: git-gui.sh:2336
+msgid "Merge Commit Message:"
+msgstr "åˆå¹¶æ交æè¿°:"
+
+#: git-gui.sh:2337
+msgid "Commit Message:"
+msgstr "æ交æè¿°:"
+
+#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71
+msgid "Copy All"
+msgstr "全部å¤åˆ¶"
+
+#: git-gui.sh:2406 lib/blame.tcl:104
+msgid "File:"
+msgstr ""
+
+#: git-gui.sh:2508
+msgid "Refresh"
+msgstr "刷新"
+
+#: git-gui.sh:2529
+msgid "Apply/Reverse Hunk"
+msgstr "应用/撤消此修改å—"
+
+#: git-gui.sh:2535
+msgid "Decrease Font Size"
+msgstr "缩å°å­—体"
+
+#: git-gui.sh:2539
+msgid "Increase Font Size"
+msgstr "放大字体"
+
+#: git-gui.sh:2544
+msgid "Show Less Context"
+msgstr "显示更多diff上下文"
+
+#: git-gui.sh:2551
+msgid "Show More Context"
+msgstr "显示更少diff上下文"
+
+#: git-gui.sh:2565
+#, fuzzy
+msgid "Unstage Hunk From Commit"
+msgstr "从本次æ交移除"
+
+#: git-gui.sh:2567
+#, fuzzy
+msgid "Stage Hunk For Commit"
+msgstr "从本次æ交移除"
+
+#: git-gui.sh:2586
+msgid "Initializing..."
+msgstr ""
+
+#: git-gui.sh:2677
+#, 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 ""
+
+#: git-gui.sh:2707
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+
+#: git-gui.sh:2712
+#, 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 ""
+
+#: lib/about.tcl:25
+msgid "git-gui - a graphical user interface for Git."
+msgstr ""
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr ""
+
+#: lib/blame.tcl:81
+#, fuzzy
+msgid "Commit:"
+msgstr "æ交"
+
+#: lib/blame.tcl:249
+#, fuzzy
+msgid "Copy Commit"
+msgstr "æ交"
+
+#: lib/blame.tcl:369
+#, tcl-format
+msgid "Reading %s..."
+msgstr ""
+
+#: lib/blame.tcl:473
+msgid "Loading copy/move tracking annotations..."
+msgstr ""
+
+#: lib/blame.tcl:493
+msgid "lines annotated"
+msgstr ""
+
+#: lib/blame.tcl:674
+msgid "Loading original location annotations..."
+msgstr ""
+
+#: lib/blame.tcl:677
+msgid "Annotation complete."
+msgstr ""
+
+#: lib/blame.tcl:731
+msgid "Loading annotation..."
+msgstr ""
+
+#: lib/blame.tcl:787
+msgid "Author:"
+msgstr ""
+
+#: lib/blame.tcl:791
+#, fuzzy
+msgid "Committer:"
+msgstr "æ交"
+
+#: lib/blame.tcl:796
+msgid "Original File:"
+msgstr ""
+
+#: lib/blame.tcl:910
+msgid "Originally By:"
+msgstr ""
+
+#: lib/blame.tcl:916
+msgid "In File:"
+msgstr ""
+
+#: lib/blame.tcl:921
+msgid "Copied Or Moved Here By:"
+msgstr ""
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+#, fuzzy
+msgid "Checkout Branch"
+msgstr "当å‰åˆ†æ”¯:"
+
+#: lib/branch_checkout.tcl:23
+#, fuzzy
+msgid "Checkout"
+msgstr "切æ¢..."
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr ""
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
+msgid "Revision"
+msgstr ""
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
+#, fuzzy
+msgid "Options"
+msgstr "选项..."
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr ""
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr ""
+
+#: lib/branch_create.tcl:22
+#, fuzzy
+msgid "Create Branch"
+msgstr "当å‰åˆ†æ”¯:"
+
+#: lib/branch_create.tcl:27
+#, fuzzy
+msgid "Create New Branch"
+msgstr "当å‰åˆ†æ”¯:"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199
+#, fuzzy
+msgid "Create"
+msgstr "新建..."
+
+#: lib/branch_create.tcl:40
+#, fuzzy
+msgid "Branch Name"
+msgstr "分支"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr ""
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr ""
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr ""
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr ""
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr ""
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr ""
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+#, fuzzy
+msgid "Reset"
+msgstr "é‡ç½®æ‰€æœ‰ä¿®åŠ¨..."
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr ""
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr ""
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr ""
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr ""
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr ""
+
+#: lib/branch_delete.tcl:15
+#, fuzzy
+msgid "Delete Branch"
+msgstr "当å‰åˆ†æ”¯:"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr ""
+
+#: lib/branch_delete.tcl:37
+#, fuzzy
+msgid "Local Branches"
+msgstr "分支"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr ""
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr ""
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr ""
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+#, fuzzy
+msgid "Rename Branch"
+msgstr "当å‰åˆ†æ”¯:"
+
+#: lib/branch_rename.tcl:26
+#, fuzzy
+msgid "Rename"
+msgstr "改å..."
+
+#: lib/branch_rename.tcl:36
+#, fuzzy
+msgid "Branch:"
+msgstr "分支"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr ""
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr ""
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr ""
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr ""
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr ""
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr ""
+
+#: lib/browser.tcl:125 lib/browser.tcl:142
+#, tcl-format
+msgid "Loading %s..."
+msgstr ""
+
+#: lib/browser.tcl:186
+msgid "[Up To Parent]"
+msgstr ""
+
+#: lib/browser.tcl:266 lib/browser.tcl:272
+#, fuzzy
+msgid "Browse Branch Files"
+msgstr "æµè§ˆå½“å‰åˆ†æ”¯æ–‡ä»¶"
+
+#: lib/browser.tcl:277 lib/choose_repository.tcl:215
+#: lib/choose_repository.tcl:305 lib/choose_repository.tcl:315
+#: lib/choose_repository.tcl:811
+msgid "Browse"
+msgstr ""
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr ""
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr ""
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
+msgid "Close"
+msgstr ""
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr ""
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr ""
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr ""
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr ""
+
+#: lib/checkout_op.tcl:266
+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 ""
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr ""
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr ""
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr ""
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr ""
+
+#: lib/checkout_op.tcl:429
+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 ""
+
+#: lib/checkout_op.tcl:446
+#, fuzzy, tcl-format
+msgid "Checked out '%s'."
+msgstr "切æ¢..."
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr ""
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr ""
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr ""
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:164
+msgid "Visualize"
+msgstr ""
+
+#: lib/checkout_op.tcl:578
+#, 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 ""
+
+#: lib/choose_font.tcl:39
+#, fuzzy
+msgid "Select"
+msgstr "全选"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr ""
+
+#: lib/choose_font.tcl:73
+#, fuzzy
+msgid "Font Size"
+msgstr "缩å°å­—体"
+
+#: lib/choose_font.tcl:90
+msgid "Font Example"
+msgstr ""
+
+#: lib/choose_font.tcl:101
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+
+#: lib/choose_repository.tcl:25
+msgid "Git Gui"
+msgstr ""
+
+#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204
+#, fuzzy
+msgid "Create New Repository"
+msgstr "版本树"
+
+#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291
+#, fuzzy
+msgid "Clone Existing Repository"
+msgstr "版本树"
+
+#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800
+#, fuzzy
+msgid "Open Existing Repository"
+msgstr "版本树"
+
+#: lib/choose_repository.tcl:91
+msgid "Next >"
+msgstr ""
+
+#: lib/choose_repository.tcl:152
+#, tcl-format
+msgid "Location %s already exists."
+msgstr ""
+
+#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165
+#: lib/choose_repository.tcl:172
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr ""
+
+#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309
+msgid "Directory:"
+msgstr ""
+
+#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363
+#: lib/choose_repository.tcl:834
+#, fuzzy
+msgid "Git Repository"
+msgstr "版本树"
+
+#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr ""
+
+#: lib/choose_repository.tcl:265
+#, tcl-format
+msgid "File %s already exists."
+msgstr ""
+
+#: lib/choose_repository.tcl:286
+msgid "Clone"
+msgstr ""
+
+#: lib/choose_repository.tcl:299
+msgid "URL:"
+msgstr ""
+
+#: lib/choose_repository.tcl:319
+msgid "Clone Type:"
+msgstr ""
+
+#: lib/choose_repository.tcl:325
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr ""
+
+#: lib/choose_repository.tcl:331
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr ""
+
+#: lib/choose_repository.tcl:337
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr ""
+
+#: lib/choose_repository.tcl:369 lib/choose_repository.tcl:418
+#: lib/choose_repository.tcl:560 lib/choose_repository.tcl:630
+#: lib/choose_repository.tcl:840 lib/choose_repository.tcl:848
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:405
+msgid "Standard only available for local repository."
+msgstr ""
+
+#: lib/choose_repository.tcl:409
+msgid "Shared only available for local repository."
+msgstr ""
+
+#: lib/choose_repository.tcl:439
+msgid "Failed to configure origin"
+msgstr ""
+
+#: lib/choose_repository.tcl:451
+msgid "Counting objects"
+msgstr ""
+
+#: lib/choose_repository.tcl:452
+msgid "buckets"
+msgstr ""
+
+#: lib/choose_repository.tcl:476
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:512
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr ""
+
+#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728
+#: lib/choose_repository.tcl:740
+msgid "The 'master' branch has not been initialized."
+msgstr ""
+
+#: lib/choose_repository.tcl:527
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr ""
+
+#: lib/choose_repository.tcl:539
+#, tcl-format
+msgid "Cloning from %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:570
+#, fuzzy
+msgid "Copying objects"
+msgstr "压缩数æ®åº“"
+
+#: lib/choose_repository.tcl:571
+msgid "KiB"
+msgstr ""
+
+#: lib/choose_repository.tcl:595
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:605
+msgid "Linking objects"
+msgstr ""
+
+#: lib/choose_repository.tcl:606
+msgid "objects"
+msgstr ""
+
+#: lib/choose_repository.tcl:614
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:669
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr ""
+
+#: lib/choose_repository.tcl:680
+msgid "Cannot fetch tags. See console output for details."
+msgstr ""
+
+#: lib/choose_repository.tcl:704
+msgid "Cannot determine HEAD. See console output for details."
+msgstr ""
+
+#: lib/choose_repository.tcl:713
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr ""
+
+#: lib/choose_repository.tcl:719
+msgid "Clone failed."
+msgstr ""
+
+#: lib/choose_repository.tcl:726
+msgid "No default branch obtained."
+msgstr ""
+
+#: lib/choose_repository.tcl:737
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr ""
+
+#: lib/choose_repository.tcl:749
+msgid "Creating working directory"
+msgstr ""
+
+#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80
+#: lib/index.tcl:149
+msgid "files"
+msgstr ""
+
+#: lib/choose_repository.tcl:779
+msgid "Initial file checkout failed."
+msgstr ""
+
+#: lib/choose_repository.tcl:795
+msgid "Open"
+msgstr ""
+
+#: lib/choose_repository.tcl:805
+#, fuzzy
+msgid "Repository:"
+msgstr "版本树"
+
+#: lib/choose_repository.tcl:854
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr ""
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr ""
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr ""
+
+#: lib/choose_rev.tcl:74
+#, fuzzy
+msgid "Local Branch"
+msgstr "分支"
+
+#: lib/choose_rev.tcl:79
+#, fuzzy
+msgid "Tracking Branch"
+msgstr "当å‰åˆ†æ”¯:"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
+msgid "Tag"
+msgstr ""
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr ""
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr ""
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr ""
+
+#: lib/choose_rev.tcl:530
+msgid "Updated"
+msgstr ""
+
+#: lib/choose_rev.tcl:558
+msgid "URL"
+msgstr ""
+
+#: 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 ""
+
+#: 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 ""
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr ""
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr ""
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr ""
+
+#: lib/commit.tcl:133
+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 ""
+
+#: lib/commit.tcl:154
+#, 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 ""
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+
+#: lib/commit.tcl:183
+msgid ""
+"Please supply a commit message.\n"
+"\n"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentance what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
+msgstr ""
+
+#: lib/commit.tcl:257
+msgid "write-tree failed:"
+msgstr ""
+
+#: lib/commit.tcl:275
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr ""
+
+#: lib/commit.tcl:279
+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 ""
+
+#: lib/commit.tcl:286
+msgid "No changes to commit."
+msgstr ""
+
+#: lib/commit.tcl:303
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr ""
+
+#: lib/commit.tcl:317
+msgid "commit-tree failed:"
+msgstr ""
+
+#: lib/commit.tcl:339
+msgid "update-ref failed:"
+msgstr ""
+
+#: lib/commit.tcl:430
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr ""
+
+#: lib/console.tcl:57
+msgid "Working... please wait..."
+msgstr ""
+
+#: lib/console.tcl:183
+msgid "Success"
+msgstr ""
+
+#: lib/console.tcl:196
+msgid "Error: Command Failed"
+msgstr ""
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr ""
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr ""
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr ""
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr ""
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr ""
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr ""
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr ""
+
+#: lib/database.tcl:72
+#, fuzzy
+msgid "Compressing the object database"
+msgstr "压缩数æ®åº“"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr ""
+
+#: lib/database.tcl:108
+#, 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"
+"\n"
+"Compress the database now?"
+msgstr ""
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr ""
+
+#: lib/diff.tcl:42
+#, 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 ""
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr ""
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr ""
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr ""
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr ""
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr ""
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr ""
+
+#: lib/diff.tcl:302
+msgid "Failed to unstage selected hunk."
+msgstr ""
+
+#: lib/diff.tcl:309
+msgid "Failed to stage selected hunk."
+msgstr ""
+
+#: lib/error.tcl:12 lib/error.tcl:102
+msgid "error"
+msgstr ""
+
+#: lib/error.tcl:28
+msgid "warning"
+msgstr ""
+
+#: lib/error.tcl:81
+msgid "You must correct the above errors before committing."
+msgstr ""
+
+#: lib/index.tcl:241
+#, fuzzy, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "从本次æ交移除"
+
+#: lib/index.tcl:285
+#, tcl-format
+msgid "Adding %s"
+msgstr ""
+
+#: lib/index.tcl:340
+#, fuzzy, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "æ¢å¤ä¿®æ”¹"
+
+#: lib/index.tcl:342
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr ""
+
+#: lib/index.tcl:348
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+
+#: lib/index.tcl:351
+msgid "Do Nothing"
+msgstr ""
+
+#: 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 ""
+
+#: 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 ""
+
+#: lib/merge.tcl:44
+#, 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 ""
+
+#: lib/merge.tcl:54
+#, 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 ""
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr ""
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s"
+msgstr ""
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr ""
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr ""
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr ""
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr ""
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+
+#: 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 ""
+
+#: 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 ""
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr ""
+
+#: lib/merge.tcl:266
+msgid "Abort failed."
+msgstr ""
+
+#: lib/merge.tcl:268
+msgid "Abort completed. Ready."
+msgstr ""
+
+#: lib/option.tcl:82
+msgid "Restore Defaults"
+msgstr ""
+
+#: lib/option.tcl:86
+msgid "Save"
+msgstr ""
+
+#: lib/option.tcl:96
+#, fuzzy, tcl-format
+msgid "%s Repository"
+msgstr "版本树"
+
+#: lib/option.tcl:97
+msgid "Global (All Repositories)"
+msgstr ""
+
+#: lib/option.tcl:103
+msgid "User Name"
+msgstr ""
+
+#: lib/option.tcl:104
+msgid "Email Address"
+msgstr ""
+
+#: lib/option.tcl:106
+#, fuzzy
+msgid "Summarize Merge Commits"
+msgstr "修订åˆå¹¶æ交æè¿°:"
+
+#: lib/option.tcl:107
+msgid "Merge Verbosity"
+msgstr ""
+
+#: lib/option.tcl:108
+msgid "Show Diffstat After Merge"
+msgstr ""
+
+#: lib/option.tcl:110
+msgid "Trust File Modification Timestamps"
+msgstr ""
+
+#: lib/option.tcl:111
+msgid "Prune Tracking Branches During Fetch"
+msgstr ""
+
+#: lib/option.tcl:112
+msgid "Match Tracking Branches"
+msgstr ""
+
+#: lib/option.tcl:113
+msgid "Number of Diff Context Lines"
+msgstr ""
+
+#: lib/option.tcl:114
+msgid "New Branch Name Template"
+msgstr ""
+
+#: lib/option.tcl:176
+msgid "Change Font"
+msgstr ""
+
+#: lib/option.tcl:180
+#, tcl-format
+msgid "Choose %s"
+msgstr ""
+
+#: lib/option.tcl:186
+msgid "pt."
+msgstr ""
+
+#: lib/option.tcl:200
+msgid "Preferences"
+msgstr ""
+
+#: lib/option.tcl:235
+msgid "Failed to completely save options:"
+msgstr ""
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr ""
+
+#: lib/remote.tcl:170
+#, fuzzy
+msgid "Fetch from"
+msgstr "导入"
+
+#: lib/remote.tcl:213
+#, fuzzy
+msgid "Push to"
+msgstr "上传"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:47
+#, fuzzy
+msgid "From Repository"
+msgstr "版本树"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+msgid "Arbitrary URL:"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:84
+#, fuzzy
+msgid "Branches"
+msgstr "分支"
+
+#: lib/remote_branch_delete.tcl:109
+#, fuzzy
+msgid "Delete Only If"
+msgstr "删除"
+
+#: lib/remote_branch_delete.tcl:111
+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 ""
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+
+#: 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 ""
+
+#: lib/remote_branch_delete.tcl:207
+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 ""
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:286
+msgid "No repository selected."
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr ""
+
+#: lib/shortcut.tcl:26 lib/shortcut.tcl:74
+msgid "Cannot write script:"
+msgstr ""
+
+#: lib/shortcut.tcl:149
+msgid "Cannot write icon:"
+msgstr ""
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr ""
+
+#: lib/transport.tcl:6
+#, fuzzy, tcl-format
+msgid "fetch %s"
+msgstr "导入"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr ""
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr ""
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr ""
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr ""
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr ""
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr ""
+
+#: lib/transport.tcl:89
+#, fuzzy
+msgid "Push Branches"
+msgstr "分支"
+
+#: lib/transport.tcl:103
+#, fuzzy
+msgid "Source Branches"
+msgstr "当å‰åˆ†æ”¯:"
+
+#: lib/transport.tcl:120
+#, fuzzy
+msgid "Destination Repository"
+msgstr "版本树"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr ""
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr ""
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr ""
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr ""
+
+#~ msgid "Add To Commit"
+#~ msgstr "添加到本次æ交"
+
+#~ msgid "Add Existing To Commit"
+#~ msgstr "添加默认修改文件"
+
+#~ msgid "Unstaged Changes (Will Not Be Committed)"
+#~ msgstr "ä¸è¢«æ交的修改"
+
+#~ msgid "Add Existing"
+#~ msgstr "添加默认修改文件"
+
+#, fuzzy
+#~ msgid "Push to %s..."
+#~ msgstr "上传..."
diff --git a/git-gui/windows/git-gui.sh b/git-gui/windows/git-gui.sh
new file mode 100644
index 0000000000..98f32c0a07
--- /dev/null
+++ b/git-gui/windows/git-gui.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec wish "$0" -- "$@"
+
+if { $argc >=2 && [lindex $argv 0] == "--working-dir" } {
+ cd [lindex $argv 1]
+ set argv [lrange $argv 2 end]
+ incr argc -2
+}
+
+set gitguidir [file dirname [info script]]
+regsub -all ";" $gitguidir "\\;" gitguidir
+set env(PATH) "$gitguidir;$env(PATH)"
+unset gitguidir
+
+source [file join [file dirname [info script]] git-gui.tcl]
diff --git a/git-instaweb.sh b/git-instaweb.sh
index 95c3e5aa1f..6f91c8f845 100755
--- a/git-instaweb.sh
+++ b/git-instaweb.sh
@@ -2,29 +2,38 @@
#
# Copyright (c) 2006 Eric Wong
#
-USAGE='[--start] [--stop] [--restart]
- [--local] [--httpd=<httpd>] [--port=<port>] [--browser=<browser>]
- [--module-path=<path> (for Apache2 only)]'
+
+PERL='@@PERL@@'
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-instaweb [options] (--start | --stop | --restart)
+--
+l,local only bind on 127.0.0.1
+p,port= the port to bind to
+d,httpd= the command to launch
+b,browser= the browser to launch
+m,module-path= the module path (only needed for apache2)
+ Action
+stop stop the web server
+start start the web server
+restart restart the web server
+"
. git-sh-setup
fqgitdir="$GIT_DIR"
local="`git config --bool --get instaweb.local`"
httpd="`git config --get instaweb.httpd`"
-browser="`git config --get instaweb.browser`"
port=`git config --get instaweb.port`
module_path="`git config --get instaweb.modulepath`"
-conf=$GIT_DIR/gitweb/httpd.conf
+conf="$GIT_DIR/gitweb/httpd.conf"
# Defaults:
# if installed, it doesn't need further configuration (module_path)
test -z "$httpd" && httpd='lighttpd -f'
-# probably the most popular browser among gitweb users
-test -z "$browser" && browser='firefox'
-
# any untaken local port will do...
test -z "$port" && port=1234
@@ -32,7 +41,7 @@ start_httpd () {
httpd_only="`echo $httpd | cut -f1 -d' '`"
if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null;; esac
then
- $httpd $fqgitdir/gitweb/httpd.conf
+ $httpd "$fqgitdir/gitweb/httpd.conf"
else
# many httpds are installed in /usr/sbin or /usr/local/sbin
# these days and those are not in most users $PATHs
@@ -78,52 +87,26 @@ do
start_httpd
exit 0
;;
- --local|-l)
+ -l|--local)
local=true
;;
- -d|--httpd|--httpd=*)
- case "$#,$1" in
- *,*=*)
- httpd=`expr "$1" : '-[^=]*=\(.*\)'` ;;
- 1,*)
- usage ;;
- *)
- httpd="$2"
- shift ;;
- esac
+ -d|--httpd)
+ shift
+ httpd="$1"
;;
- -b|--browser|--browser=*)
- case "$#,$1" in
- *,*=*)
- browser=`expr "$1" : '-[^=]*=\(.*\)'` ;;
- 1,*)
- usage ;;
- *)
- browser="$2"
- shift ;;
- esac
+ -b|--browser)
+ shift
+ browser="$1"
;;
- -p|--port|--port=*)
- case "$#,$1" in
- *,*=*)
- port=`expr "$1" : '-[^=]*=\(.*\)'` ;;
- 1,*)
- usage ;;
- *)
- port="$2"
- shift ;;
- esac
+ -p|--port)
+ shift
+ port="$1"
;;
- -m|--module-path=*|--module-path)
- case "$#,$1" in
- *,*=*)
- module_path=`expr "$1" : '-[^=]*=\(.*\)'` ;;
- 1,*)
- usage ;;
- *)
- module_path="$2"
- shift ;;
- esac
+ -m|--module-path)
+ shift
+ module_path="$1"
+ ;;
+ --)
;;
*)
usage
@@ -185,14 +168,14 @@ server.pid-file = "$fqgitdir/pid"
cgi.assign = ( ".cgi" => "" )
mimetype.assign = ( ".css" => "text/css" )
EOF
- test "$local" = true && echo 'server.bind = "127.0.0.1"' >> "$conf"
+ test x"$local" = xtrue && echo 'server.bind = "127.0.0.1"' >> "$conf"
}
apache2_conf () {
test -z "$module_path" && module_path=/usr/lib/apache2/modules
mkdir -p "$GIT_DIR/gitweb/logs"
bind=
- test "$local" = true && bind='127.0.0.1:'
+ test x"$local" = xtrue && bind='127.0.0.1:'
echo 'text/css css' > $fqgitdir/mime.types
cat > "$conf" <<EOF
ServerName "git-instaweb"
@@ -245,16 +228,18 @@ EOF
}
script='
-s#^\(my\|our\) $projectroot =.*#\1 $projectroot = "'`dirname $fqgitdir`'";#
-s#\(my\|our\) $gitbin =.*#\1 $gitbin = "'$GIT_EXEC_PATH'";#
-s#\(my\|our\) $projects_list =.*#\1 $projects_list = $projectroot;#
-s#\(my\|our\) $git_temp =.*#\1 $git_temp = "'$fqgitdir/gitweb/tmp'";#'
+s#^(my|our) \$projectroot =.*#$1 \$projectroot = "'$(dirname "$fqgitdir")'";#;
+s#(my|our) \$gitbin =.*#$1 \$gitbin = "'$GIT_EXEC_PATH'";#;
+s#(my|our) \$projects_list =.*#$1 \$projects_list = \$projectroot;#;
+s#(my|our) \$git_temp =.*#$1 \$git_temp = "'$fqgitdir/gitweb/tmp'";#;'
gitweb_cgi () {
cat > "$1.tmp" <<\EOFGITWEB
@@GITWEB_CGI@@
EOFGITWEB
- sed "$script" "$1.tmp" > "$1"
+ # Use the configured full path to perl to match the generated
+ # scripts' 'hashpling' line
+ "$PERL" -p -e "$script" "$1.tmp" > "$1"
chmod +x "$1"
rm -f "$1.tmp"
}
@@ -265,8 +250,8 @@ gitweb_css () {
EOFGITWEB
}
-gitweb_cgi $GIT_DIR/gitweb/gitweb.cgi
-gitweb_css $GIT_DIR/gitweb/gitweb.css
+gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi"
+gitweb_css "$GIT_DIR/gitweb/gitweb.css"
case "$httpd" in
*lighttpd*)
@@ -285,6 +270,10 @@ webrick)
esac
start_httpd
-test -z "$browser" && browser=echo
url=http://127.0.0.1:$port
-$browser $url || echo $url
+
+if test -n "$browser"; then
+ git web--browse -b "$browser" $url || echo $url
+else
+ git web--browse -c "instaweb.browser" $url || echo $url
+fi
diff --git a/git-lost-found.sh b/git-lost-found.sh
index c0b00e0fd1..9cedaf80ce 100755
--- a/git-lost-found.sh
+++ b/git-lost-found.sh
@@ -2,8 +2,11 @@
USAGE=''
SUBDIRECTORY_OK='Yes'
+OPTIONS_SPEC=
. git-sh-setup
+echo "WARNING: '$0' is deprecated in favor of 'git fsck --lost-found'" >&2
+
if [ "$#" != "0" ]
then
usage
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
index 1e7727d276..9ee3f80452 100755
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
@@ -80,6 +80,10 @@ case "${1:-.}${2:-.}${3:-.}" in
echo "ERROR: $4: Not merging symbolic link changes."
exit 1
;;
+ *,160000,*)
+ echo "ERROR: $4: Not merging conflicting submodule changes."
+ exit 1
+ ;;
esac
src2=`git-unpack-file $3`
diff --git a/git-merge.sh b/git-merge.sh
index c2092a2040..1c123a37e6 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -3,7 +3,19 @@
# Copyright (c) 2005 Junio C Hamano
#
-USAGE='[-n] [--summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s <strategy>] [-m=<merge-message>] <commit>+'
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-merge [options] <remote>...
+git-merge [options] <msg> HEAD <remote>
+--
+summary show a diffstat at the end of the merge
+n,no-summary don't show a diffstat at the end of the merge
+squash create a single commit instead of doing a merge
+commit perform a commit if the merge sucesses (default)
+ff allow fast forward (default)
+s,strategy= merge strategy to use
+m,message= message to be used for the merge commit (if any)
+"
SUBDIRECTORY_OK=Yes
. git-sh-setup
@@ -28,20 +40,19 @@ allow_trivial_merge=t
dropsave() {
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
- "$GIT_DIR/MERGE_SAVE" || exit 1
+ "$GIT_DIR/MERGE_STASH" || exit 1
}
savestate() {
# Stash away any local modifications.
- git diff-index -z --name-only $head |
- cpio -0 -o >"$GIT_DIR/MERGE_SAVE"
+ git stash create >"$GIT_DIR/MERGE_STASH"
}
restorestate() {
- if test -f "$GIT_DIR/MERGE_SAVE"
+ if test -f "$GIT_DIR/MERGE_STASH"
then
git reset --hard $head >/dev/null
- cpio -iuv <"$GIT_DIR/MERGE_SAVE"
+ git stash apply $(cat "$GIT_DIR/MERGE_STASH")
git update-index --refresh >/dev/null
fi
}
@@ -133,72 +144,47 @@ merge_name () {
fi
}
-parse_option () {
- case "$1" in
- -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
- --no-summa|--no-summar|--no-summary)
- show_diffstat=false ;;
- --summary)
- show_diffstat=t ;;
- --sq|--squ|--squa|--squas|--squash)
- allow_fast_forward=t squash=t no_commit=t ;;
- --no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
- allow_fast_forward=t squash= no_commit= ;;
- --c|--co|--com|--comm|--commi|--commit)
- allow_fast_forward=t squash= no_commit= ;;
- --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
- allow_fast_forward=t squash= no_commit=t ;;
- --ff)
- allow_fast_forward=t squash= no_commit= ;;
- --no-ff)
- allow_fast_forward=false squash= no_commit= ;;
- -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
- case " $all_strategies " in
- *" $strategy "*)
- use_strategies="$use_strategies$strategy " ;;
- *)
- die "available strategies are: $all_strategies" ;;
- esac
- ;;
- -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
- merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- have_message=t
- ;;
- -m|--m|--me|--mes|--mess|--messa|--messag|--message)
- shift
- case "$#" in
- 1) usage ;;
- esac
- merge_msg="$1"
- have_message=t
- ;;
- -*) usage ;;
- *) return 1 ;;
- esac
- shift
- args_left=$#
-}
-
parse_config () {
- while test $# -gt 0
- do
- parse_option "$@" || usage
- while test $args_left -lt $#
- do
+ while test $# != 0; do
+ case "$1" in
+ -n|--no-summary)
+ show_diffstat=false ;;
+ --summary)
+ show_diffstat=t ;;
+ --squash)
+ allow_fast_forward=t squash=t no_commit=t ;;
+ --no-squash)
+ allow_fast_forward=t squash= no_commit= ;;
+ --commit)
+ allow_fast_forward=t squash= no_commit= ;;
+ --no-commit)
+ allow_fast_forward=t squash= no_commit=t ;;
+ --ff)
+ allow_fast_forward=t squash= no_commit= ;;
+ --no-ff)
+ allow_fast_forward=false squash= no_commit= ;;
+ -s|--strategy)
+ shift
+ case " $all_strategies " in
+ *" $1 "*)
+ use_strategies="$use_strategies$1 " ;;
+ *)
+ die "available strategies are: $all_strategies" ;;
+ esac
+ ;;
+ -m|--message)
shift
- done
+ merge_msg="$1"
+ have_message=t
+ ;;
+ --)
+ shift
+ break ;;
+ *) usage ;;
+ esac
+ shift
done
+ args_left=$#
}
test $# != 0 || usage
@@ -210,17 +196,12 @@ then
mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions")
if test -n "$mergeopts"
then
- parse_config $mergeopts
+ parse_config $mergeopts --
fi
fi
-while parse_option "$@"
-do
- while test $args_left -lt $#
- do
- shift
- done
-done
+parse_config "$@"
+while test $args_left -lt $#; do shift; done
if test -z "$show_diffstat"; then
test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
@@ -437,7 +418,7 @@ case "$use_strategies" in
single_strategy=no
;;
*)
- rm -f "$GIT_DIR/MERGE_SAVE"
+ rm -f "$GIT_DIR/MERGE_STASH"
single_strategy=yes
;;
esac
diff --git a/git-mergetool.sh b/git-mergetool.sh
index 9f4f3134b6..cbbb707959 100755
--- a/git-mergetool.sh
+++ b/git-mergetool.sh
@@ -10,6 +10,7 @@
USAGE='[--tool=tool] [file to merge] ...'
SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
. git-sh-setup
require_work_tree
prefix=$(git rev-parse --show-prefix)
@@ -151,10 +152,11 @@ merge_file () {
exit 1
fi
- BACKUP="$path.BACKUP.$$"
- LOCAL="$path.LOCAL.$$"
- REMOTE="$path.REMOTE.$$"
- BASE="$path.BASE.$$"
+ ext="$$$(expr "$path" : '.*\(\.[^/]*\)$')"
+ BACKUP="$path.BACKUP.$ext"
+ LOCAL="$path.LOCAL.$ext"
+ REMOTE="$path.REMOTE.$ext"
+ BASE="$path.BASE.$ext"
mv -- "$path" "$BACKUP"
cp -- "$BACKUP" "$path"
@@ -192,10 +194,10 @@ merge_file () {
case "$merge_tool" in
kdiff3)
if base_present ; then
- (kdiff3 --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \
+ ("$merge_tool_path" --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \
-o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
else
- (kdiff3 --auto --L1 "$path (Local)" --L2 "$path (Remote)" \
+ ("$merge_tool_path" --auto --L1 "$path (Local)" --L2 "$path (Remote)" \
-o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
fi
status=$?
@@ -203,35 +205,35 @@ merge_file () {
;;
tkdiff)
if base_present ; then
- tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
+ "$merge_tool_path" -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
else
- tkdiff -o "$path" -- "$LOCAL" "$REMOTE"
+ "$merge_tool_path" -o "$path" -- "$LOCAL" "$REMOTE"
fi
status=$?
save_backup
;;
meld|vimdiff)
touch "$BACKUP"
- $merge_tool -- "$LOCAL" "$path" "$REMOTE"
+ "$merge_tool_path" -- "$LOCAL" "$path" "$REMOTE"
check_unchanged
save_backup
;;
gvimdiff)
touch "$BACKUP"
- gvimdiff -f -- "$LOCAL" "$path" "$REMOTE"
+ "$merge_tool_path" -f -- "$LOCAL" "$path" "$REMOTE"
check_unchanged
save_backup
;;
xxdiff)
touch "$BACKUP"
if base_present ; then
- xxdiff -X --show-merged-pane \
+ "$merge_tool_path" -X --show-merged-pane \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE"
else
- xxdiff -X --show-merged-pane \
+ "$merge_tool_path" -X --show-merged-pane \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
@@ -243,18 +245,28 @@ merge_file () {
opendiff)
touch "$BACKUP"
if base_present; then
- opendiff "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat
+ "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat
else
- opendiff "$LOCAL" "$REMOTE" -merge "$path" | cat
+ "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$path" | cat
+ fi
+ check_unchanged
+ save_backup
+ ;;
+ ecmerge)
+ touch "$BACKUP"
+ if base_present; then
+ "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$path"
+ else
+ "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$path"
fi
check_unchanged
save_backup
;;
emerge)
if base_present ; then
- emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")"
+ "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")"
else
- emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")"
+ "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")"
fi
status=$?
save_backup
@@ -297,17 +309,38 @@ do
shift
done
+valid_tool() {
+ case "$1" in
+ kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
+ ;; # happy
+ *)
+ return 1
+ ;;
+ esac
+}
+
+init_merge_tool_path() {
+ merge_tool_path=`git config mergetool.$1.path`
+ if test -z "$merge_tool_path" ; then
+ case "$1" in
+ emerge)
+ merge_tool_path=emacs
+ ;;
+ *)
+ merge_tool_path=$1
+ ;;
+ esac
+ fi
+}
+
+
if test -z "$merge_tool"; then
merge_tool=`git config merge.tool`
- case "$merge_tool" in
- kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | "")
- ;; # happy
- *)
+ if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
unset merge_tool
- ;;
- esac
+ fi
fi
if test -z "$merge_tool" ; then
@@ -329,40 +362,30 @@ if test -z "$merge_tool" ; then
merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
echo "merge tool candidates: $merge_tool_candidates"
for i in $merge_tool_candidates; do
- if test $i = emerge ; then
- cmd=emacs
- else
- cmd=$i
- fi
- if type $cmd > /dev/null 2>&1; then
+ init_merge_tool_path $i
+ if type "$merge_tool_path" > /dev/null 2>&1; then
merge_tool=$i
break
fi
done
if test -z "$merge_tool" ; then
- echo "No available merge resolution programs available."
+ echo "No known merge resolution program available."
exit 1
fi
+else
+ if ! valid_tool "$merge_tool"; then
+ echo >&2 "Unknown merge_tool $merge_tool"
+ exit 1
+ fi
+
+ init_merge_tool_path "$merge_tool"
+
+ if ! type "$merge_tool_path" > /dev/null 2>&1; then
+ echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
+ exit 1
+ fi
fi
-case "$merge_tool" in
- kdiff3|tkdiff|meld|xxdiff|vimdiff|gvimdiff|opendiff)
- if ! type "$merge_tool" > /dev/null 2>&1; then
- echo "The merge tool $merge_tool is not available"
- exit 1
- fi
- ;;
- emerge)
- if ! type "emacs" > /dev/null 2>&1; then
- echo "Emacs is not available"
- exit 1
- fi
- ;;
- *)
- echo "Unknown merge tool: $merge_tool"
- exit 1
- ;;
-esac
if test $# -eq 0 ; then
files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u`
@@ -370,8 +393,11 @@ if test $# -eq 0 ; then
echo "No files need merging"
exit 0
fi
- echo Merging the files: $files
- git ls-files -u | sed -e 's/^[^ ]* //' | sort -u | while read i
+ echo Merging the files: "$files"
+ git ls-files -u |
+ sed -e 's/^[^ ]* //' |
+ sort -u |
+ while IFS= read i
do
printf "\n"
merge_file "$i" < /dev/tty > /dev/tty
diff --git a/git-pull.sh b/git-pull.sh
index 74bfc16744..46da0f4ca2 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -4,9 +4,10 @@
#
# Fetch one or more remote refs and merge it/them into the current HEAD.
-USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [<fetch-options>] <repo> <head>...'
+USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'
LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.'
SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
. git-sh-setup
set_reflog_action "pull $*"
require_work_tree
@@ -15,7 +16,10 @@ cd_to_toplevel
test -z "$(git ls-files -u)" ||
die "You are in the middle of a conflicted merge."
-strategy_args= no_summary= no_commit= squash=
+strategy_args= no_summary= no_commit= squash= no_ff=
+curr_branch=$(git symbolic-ref -q HEAD)
+curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
+rebase=$(git config --bool branch.$curr_branch_short.rebase)
while :
do
case "$1" in
@@ -27,8 +31,16 @@ do
;;
--no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
no_commit=--no-commit ;;
+ --c|--co|--com|--comm|--commi|--commit)
+ no_commit=--commit ;;
--sq|--squ|--squa|--squas|--squash)
squash=--squash ;;
+ --no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
+ squash=--no-squash ;;
+ --ff)
+ no_ff=--ff ;;
+ --no-ff)
+ no_ff=--no-ff ;;
-s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
--strateg=*|--strategy=*|\
-s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
@@ -43,6 +55,12 @@ do
esac
strategy_args="${strategy_args}-s $strategy "
;;
+ -r|--r|--re|--reb|--reba|--rebas|--rebase)
+ rebase=true
+ ;;
+ --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
+ rebase=false
+ ;;
-h|--h|--he|--hel|--help)
usage
;;
@@ -54,6 +72,49 @@ do
shift
done
+error_on_no_merge_candidates () {
+ exec >&2
+ for opt
+ do
+ case "$opt" in
+ -t|--t|--ta|--tag|--tags)
+ echo "Fetching tags only, you probably meant:"
+ echo " git fetch --tags"
+ exit 1
+ esac
+ done
+
+ curr_branch=${curr_branch#refs/heads/}
+
+ echo "You asked me to pull without telling me which branch you"
+ echo "want to merge with, and 'branch.${curr_branch}.merge' in"
+ echo "your configuration file does not tell me either. Please"
+ echo "name which branch you want to merge on the command line and"
+ echo "try again (e.g. 'git pull <repository> <refspec>')."
+ echo "See git-pull(1) for details on the refspec."
+ echo
+ echo "If you often merge with the same branch, you may want to"
+ echo "configure the following variables in your configuration"
+ echo "file:"
+ echo
+ echo " branch.${curr_branch}.remote = <nickname>"
+ echo " branch.${curr_branch}.merge = <remote-ref>"
+ echo " remote.<nickname>.url = <url>"
+ echo " remote.<nickname>.fetch = <refspec>"
+ echo
+ echo "See git-config(1) for details."
+ exit 1
+}
+
+test true = "$rebase" && {
+ . git-parse-remote &&
+ origin="$1"
+ test -z "$origin" && origin=$(get_default_remote)
+ reflist="$(get_remote_refs_for_fetch "$@" 2>/dev/null |
+ sed "s|refs/heads/\(.*\):|\1|")" &&
+ oldremoteref="$(git rev-parse --verify \
+ "refs/remotes/$origin/$reflist" 2>/dev/null)"
+}
orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
git-fetch --update-head-ok "$@" || exit 1
@@ -86,35 +147,14 @@ merge_head=$(sed -e '/ not-for-merge /d' \
case "$merge_head" in
'')
- curr_branch=$(git symbolic-ref -q HEAD)
case $? in
- 0) ;;
- 1) echo >&2 "You are not currently on a branch; you must explicitly"
- echo >&2 "specify which branch you wish to merge:"
- echo >&2 " git pull <remote> <branch>"
- exit 1;;
- *) exit $?;;
+ 0) error_on_no_merge_candidates "$@";;
+ 1) echo >&2 "You are not currently on a branch; you must explicitly"
+ echo >&2 "specify which branch you wish to merge:"
+ echo >&2 " git pull <remote> <branch>"
+ exit 1;;
+ *) exit $?;;
esac
- curr_branch=${curr_branch#refs/heads/}
-
- echo >&2 "You asked me to pull without telling me which branch you"
- echo >&2 "want to merge with, and 'branch.${curr_branch}.merge' in"
- echo >&2 "your configuration file does not tell me either. Please"
- echo >&2 "name which branch you want to merge on the command line and"
- echo >&2 "try again (e.g. 'git pull <repository> <refspec>')."
- echo >&2 "See git-pull(1) for details on the refspec."
- echo >&2
- echo >&2 "If you often merge with the same branch, you may want to"
- echo >&2 "configure the following variables in your configuration"
- echo >&2 "file:"
- echo >&2
- echo >&2 " branch.${curr_branch}.remote = <nickname>"
- echo >&2 " branch.${curr_branch}.merge = <remote-ref>"
- echo >&2 " remote.<nickname>.url = <url>"
- echo >&2 " remote.<nickname>.fetch = <refspec>"
- echo >&2
- echo >&2 "See git-config(1) for details."
- exit 1
;;
?*' '?*)
if test -z "$orig_head"
@@ -133,5 +173,7 @@ then
fi
merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
-exec git-merge $no_summary $no_commit $squash $strategy_args \
+test true = "$rebase" &&
+ exec git-rebase --onto $merge_head ${oldremoteref:-$merge_head}
+exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \
"$merge_name" HEAD $merge_head
diff --git a/git-quiltimport.sh b/git-quiltimport.sh
index 880c81d121..233e5eae1d 100755
--- a/git-quiltimport.sh
+++ b/git-quiltimport.sh
@@ -1,5 +1,12 @@
#!/bin/sh
-USAGE='--dry-run --author <author> --patches </path/to/quilt/patch/directory>'
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-quiltimport [options]
+--
+n,dry-run dry run
+author= author name and email address for patches without any
+patches= path to the quilt series and patches
+"
SUBDIRECTORY_ON=Yes
. git-sh-setup
@@ -8,39 +15,25 @@ quilt_author=""
while test $# != 0
do
case "$1" in
- --au=*|--aut=*|--auth=*|--autho=*|--author=*)
- quilt_author=$(expr "z$1" : 'z-[^=]*\(.*\)')
- shift
- ;;
-
- --au|--aut|--auth|--autho|--author)
- case "$#" in 1) usage ;; esac
+ --author)
shift
quilt_author="$1"
- shift
;;
-
- --dry-run)
- shift
+ -n|--dry-run)
dry_run=1
;;
-
- --pa=*|--pat=*|--patc=*|--patch=*|--patche=*|--patches=*)
- QUILT_PATCHES=$(expr "z$1" : 'z-[^=]*\(.*\)')
- shift
- ;;
-
- --pa|--pat|--patc|--patch|--patche|--patches)
- case "$#" in 1) usage ;; esac
+ --patches)
shift
QUILT_PATCHES="$1"
- shift
;;
-
+ --)
+ shift
+ break;;
*)
- break
+ usage
;;
esac
+ shift
done
# Quilt Author
@@ -84,8 +77,9 @@ for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do
}
# Parse the author information
- export GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info")
- export GIT_AUTHOR_EMAIL=$(sed -ne 's/Email: //p' "$tmp_info")
+ GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info")
+ GIT_AUTHOR_EMAIL=$(sed -ne 's/Email: //p' "$tmp_info")
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
while test -z "$GIT_AUTHOR_EMAIL" && test -z "$GIT_AUTHOR_NAME" ; do
if [ -n "$quilt_author" ] ; then
GIT_AUTHOR_NAME="$quilt_author_name";
@@ -111,8 +105,9 @@ for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do
GIT_AUTHOR_EMAIL="$patch_author_email"
fi
done
- export GIT_AUTHOR_DATE=$(sed -ne 's/Date: //p' "$tmp_info")
- export SUBJECT=$(sed -ne 's/Subject: //p' "$tmp_info")
+ GIT_AUTHOR_DATE=$(sed -ne 's/Date: //p' "$tmp_info")
+ SUBJECT=$(sed -ne 's/Subject: //p' "$tmp_info")
+ export GIT_AUTHOR_DATE SUBJECT
if [ -z "$SUBJECT" ] ; then
SUBJECT=$(echo $patch_name | sed -e 's/.patch$//')
fi
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 0dd77b4005..fb12b03b20 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -13,6 +13,7 @@
USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]
[--onto <branch>] <upstream> [<branch>])'
+OPTIONS_SPEC=
. git-sh-setup
require_work_tree
@@ -29,6 +30,11 @@ test -d "$REWRITTEN" && PRESERVE_MERGES=t
test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
test -f "$DOTEST"/verbose && VERBOSE=t
+GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
+mark the corrected paths with 'git add <paths>', and
+run 'git rebase --continue'"
+export GIT_CHERRY_PICK_HELP
+
warn () {
echo "$*" >&2
}
@@ -52,7 +58,7 @@ require_clean_work_tree () {
git rev-parse --verify HEAD > /dev/null &&
git update-index --refresh &&
git diff-files --quiet &&
- git diff-index --cached --quiet HEAD ||
+ git diff-index --cached --quiet HEAD -- ||
die "Working tree is dirty"
}
@@ -67,14 +73,19 @@ comment_for_reflog () {
esac
}
+last_count=
mark_action_done () {
sed -e 1q < "$TODO" >> "$DONE"
sed -e 1d < "$TODO" >> "$TODO".new
mv -f "$TODO".new "$TODO"
count=$(($(grep -ve '^$' -e '^#' < "$DONE" | wc -l)))
total=$(($count+$(grep -ve '^$' -e '^#' < "$TODO" | wc -l)))
- printf "Rebasing (%d/%d)\r" $count $total
- test -z "$VERBOSE" || echo
+ if test "$last_count" != "$count"
+ then
+ last_count=$count
+ printf "Rebasing (%d/%d)\r" $count $total
+ test -z "$VERBOSE" || echo
+ fi
}
make_patch () {
@@ -89,6 +100,7 @@ make_patch () {
die_with_patch () {
make_patch "$1"
+ git rerere
die "$2"
}
@@ -116,7 +128,7 @@ pick_one () {
sha1=$(git rev-parse --short $sha1)
output warn Fast forward to $sha1
else
- output git cherry-pick $STRATEGY "$@"
+ output git cherry-pick "$@"
fi
}
@@ -172,19 +184,21 @@ pick_one_preserving_merges () {
author_script=$(get_author_ident_from_commit $sha1)
eval "$author_script"
msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
- # NEEDSWORK: give rerere a chance
+ # No point in merging the first parent, that's HEAD
+ new_parents=${new_parents# $first_parent}
if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
output git merge $STRATEGY -m "$msg" \
$new_parents
then
+ git rerere
printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
die Error redoing merge $sha1
fi
;;
*)
- output git cherry-pick $STRATEGY "$@" ||
+ output git cherry-pick "$@" ||
die_with_patch $sha1 "Could not pick $sha1"
;;
esac
@@ -206,15 +220,17 @@ make_squash_message () {
COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \
< "$SQUASH_MSG" | tail -n 1)+1))
echo "# This is a combination of $COUNT commits."
- sed -n "2,\$p" < "$SQUASH_MSG"
+ sed -e 1d -e '2,/^./{
+ /^$/d
+ }' <"$SQUASH_MSG"
else
COUNT=2
echo "# This is a combination of two commits."
echo "# The first commit's message is:"
echo
git cat-file commit HEAD | sed -e '1,/^$/d'
- echo
fi
+ echo
echo "# This is the $(nth_string $COUNT) commit message:"
echo
git cat-file commit $1 | sed -e '1,/^$/d'
@@ -280,22 +296,22 @@ do_next () {
output git reset --soft HEAD^
pick_one -n $sha1 || failed=t
echo "$author_script" > "$DOTEST"/author-script
- case $failed in
- f)
+ if test $failed = f
+ then
# This is like --amend, but with a different message
eval "$author_script"
GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
- $USE_OUTPUT git commit -F "$MSG" $EDIT_COMMIT
- ;;
- t)
+ $USE_OUTPUT git commit --no-verify -F "$MSG" $EDIT_COMMIT || failed=t
+ fi
+ if test $failed = t
+ then
cp "$MSG" "$GIT_DIR"/MERGE_MSG
warn
warn "Could not apply $sha1... $rest"
die_with_patch $sha1 ""
- ;;
- esac
+ fi
;;
*)
warn "Unknown command: $command $sha1 $rest"
@@ -313,7 +329,12 @@ do_next () {
test -f "$DOTEST"/current-commit &&
current_commit=$(cat "$DOTEST"/current-commit) &&
git rev-parse HEAD > "$REWRITTEN"/$current_commit
- NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
+ if test -f "$REWRITTEN"/$OLDHEAD
+ then
+ NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
+ else
+ NEWHEAD=$OLDHEAD
+ fi
else
NEWHEAD=$(git rev-parse HEAD)
fi &&
@@ -349,16 +370,28 @@ do
test -d "$DOTEST" || die "No interactive rebase running"
- # commit if necessary
- git rev-parse --verify HEAD > /dev/null &&
- git update-index --refresh &&
- git diff-files --quiet &&
- ! git diff-index --cached --quiet HEAD &&
- . "$DOTEST"/author-script && {
- test ! -f "$DOTEST"/amend || git reset --soft HEAD^
- } &&
- export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE &&
- git commit -F "$DOTEST"/message -e
+ # Sanity check
+ git rev-parse --verify HEAD >/dev/null ||
+ die "Cannot read HEAD"
+ git update-index --refresh && git diff-files --quiet ||
+ die "Working tree is dirty"
+
+ # do we have anything to commit?
+ if git diff-index --cached --quiet HEAD --
+ then
+ : Nothing to commit -- skip this
+ else
+ . "$DOTEST"/author-script ||
+ die "Cannot find the author identity"
+ if test -f "$DOTEST"/amend
+ then
+ git reset --soft HEAD^ ||
+ die "Cannot rewind the HEAD"
+ fi
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
+ git commit --no-verify -F "$DOTEST"/message -e ||
+ die "Could not commit staged changes."
+ fi
require_clean_work_tree
do_rest
@@ -366,6 +399,7 @@ do
--abort)
comment_for_reflog abort
+ git rerere clear
test -d "$DOTEST" || die "No interactive rebase running"
HEADNAME=$(cat "$DOTEST"/head-name)
@@ -382,15 +416,15 @@ do
--skip)
comment_for_reflog skip
+ git rerere clear
test -d "$DOTEST" || die "No interactive rebase running"
output git reset --hard && do_rest
;;
-s|--strategy)
- shift
case "$#,$1" in
*,*=*)
- STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
+ STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
1,*)
usage ;;
*)
@@ -398,7 +432,7 @@ do
shift ;;
esac
;;
- --merge)
+ -m|--merge)
# we use merge anyway
;;
-C*)
@@ -483,8 +517,13 @@ do
SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
SHORTHEAD=$(git rev-parse --short $HEAD)
SHORTONTO=$(git rev-parse --short $ONTO)
- cat > "$TODO" << EOF
-# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
+ git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
+ --abbrev=7 --reverse --left-right --cherry-pick \
+ $UPSTREAM...$HEAD | \
+ sed -n "s/^>/pick /p" > "$TODO"
+ cat >> "$TODO" << EOF
+
+# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
#
# Commands:
# pick = use commit
@@ -492,12 +531,9 @@ do
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
+# However, if you remove everything, the rebase will be aborted.
#
EOF
- git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
- --abbrev=7 --reverse --left-right --cherry-pick \
- $UPSTREAM...$HEAD | \
- sed -n "s/^>/pick /p" >> "$TODO"
has_action "$TODO" ||
die_abort "Nothing to do"
diff --git a/git-rebase.sh b/git-rebase.sh
index 1583402a06..bdcea0ed70 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -29,6 +29,7 @@ Example: git-rebase master~1 topic
'
SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
. git-sh-setup
set_reflog_action rebase
require_work_tree
@@ -59,8 +60,8 @@ continue_merge () {
die "$RESOLVEMSG"
fi
- cmt=`cat $dotest/current`
- if ! git diff-index --quiet HEAD
+ cmt=`cat "$dotest/current"`
+ if ! git diff-index --quiet HEAD --
then
if ! git-commit -C "$cmt"
then
@@ -84,14 +85,14 @@ continue_merge () {
}
call_merge () {
- cmt="$(cat $dotest/cmt.$1)"
+ cmt="$(cat "$dotest/cmt.$1")"
echo "$cmt" > "$dotest/current"
hd=$(git rev-parse --verify HEAD)
- cmt_name=$(git symbolic-ref HEAD)
- msgnum=$(cat $dotest/msgnum)
- end=$(cat $dotest/end)
+ 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)"'
+ eval GITHEAD_$hd='$(cat "$dotest/onto_name")'
export GITHEAD_$cmt GITHEAD_$hd
git-merge-$strategy "$cmt^" -- "$hd" "$cmt"
rv=$?
@@ -115,7 +116,24 @@ call_merge () {
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 ||
+ die "Could not move back to $head_name"
+ ;;
+ esac
+}
+
finish_rb_merge () {
+ move_to_original_branch
rm -r "$dotest"
echo "All done."
}
@@ -140,10 +158,10 @@ do
}
if test -d "$dotest"
then
- prev_head="`cat $dotest/prev_head`"
- end="`cat $dotest/end`"
- msgnum="`cat $dotest/msgnum`"
- onto="`cat $dotest/onto`"
+ prev_head=$(cat "$dotest/prev_head")
+ end=$(cat "$dotest/end")
+ msgnum=$(cat "$dotest/msgnum")
+ onto=$(cat "$dotest/onto")
continue_merge
while test "$msgnum" -le "$end"
do
@@ -153,18 +171,23 @@ do
finish_rb_merge
exit
fi
- git am --resolved --3way --resolvemsg="$RESOLVEMSG"
+ head_name=$(cat .dotest/head-name) &&
+ onto=$(cat .dotest/onto) &&
+ orig_head=$(cat .dotest/orig-head) &&
+ git am --resolved --3way --resolvemsg="$RESOLVEMSG" &&
+ move_to_original_branch
exit
;;
--skip)
+ 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`"
+ prev_head=$(cat "$dotest/prev_head")
+ end=$(cat "$dotest/end")
+ msgnum=$(cat "$dotest/msgnum")
msgnum=$(($msgnum + 1))
- onto="`cat $dotest/onto`"
+ onto=$(cat "$dotest/onto")
while test "$msgnum" -le "$end"
do
call_merge "$msgnum"
@@ -173,16 +196,23 @@ do
finish_rb_merge
exit
fi
- git am -3 --skip --resolvemsg="$RESOLVEMSG"
+ head_name=$(cat .dotest/head-name) &&
+ onto=$(cat .dotest/onto) &&
+ orig_head=$(cat .dotest/orig-head) &&
+ git am -3 --skip --resolvemsg="$RESOLVEMSG" &&
+ move_to_original_branch
exit
;;
--abort)
git rerere clear
if test -d "$dotest"
then
+ move_to_original_branch
rm -r "$dotest"
elif test -d .dotest
then
+ dotest=.dotest
+ move_to_original_branch
rm -r .dotest
else
die "No rebase in progress?"
@@ -255,7 +285,7 @@ fi
# The tree must be really really clean.
git update-index --refresh || exit
-diff=$(git diff-index --cached --name-status -r HEAD)
+diff=$(git diff-index --cached --name-status -r HEAD --)
case "$diff" in
?*) echo "cannot rebase: your index is not up-to-date"
echo "$diff"
@@ -318,6 +348,19 @@ then
GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
fi
+# move to a detached HEAD
+orig_head=$(git rev-parse HEAD^0)
+head_name=$(git symbolic-ref HEAD 2> /dev/null)
+case "$head_name" in
+'')
+ head_name="detached HEAD"
+ ;;
+*)
+ git checkout "$orig_head" > /dev/null 2>&1 ||
+ die "could not detach HEAD"
+ ;;
+esac
+
# Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
echo "First, rewinding head to replay your work on top of it..."
git-reset --hard "$onto"
@@ -327,14 +370,21 @@ git-reset --hard "$onto"
if test "$mb" = "$branch"
then
echo >&2 "Fast-forwarded $branch_name to $onto_name."
+ move_to_original_branch
exit 0
fi
if test -z "$do_merge"
then
git format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
- git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG"
- exit $?
+ git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG" &&
+ move_to_original_branch
+ ret=$?
+ test 0 != $ret -a -d .dotest &&
+ echo $head_name > .dotest/head-name &&
+ echo $onto > .dotest/onto &&
+ echo $orig_head > .dotest/orig-head
+ exit $ret
fi
# start doing a rebase with git-merge
@@ -343,8 +393,10 @@ fi
mkdir -p "$dotest"
echo "$onto" > "$dotest/onto"
echo "$onto_name" > "$dotest/onto_name"
-prev_head=`git rev-parse HEAD^0`
+prev_head=$orig_head
echo "$prev_head" > "$dotest/prev_head"
+echo "$orig_head" > "$dotest/orig-head"
+echo "$head_name" > "$dotest/head-name"
msgnum=0
for cmt in `git rev-list --reverse --no-merges "$upstream"..ORIG_HEAD`
diff --git a/git-relink.perl b/git-relink.perl
index f6b4f6a2f8..15fb932021 100755
--- a/git-relink.perl
+++ b/git-relink.perl
@@ -40,7 +40,7 @@ my $master_dir = pop @dirs;
opendir(D,$master_dir . "objects/")
or die "Failed to open $master_dir/objects/ : $!";
-my @hashdirs = grep !/^\.{1,2}$/, readdir(D);
+my @hashdirs = grep { ($_ eq 'pack') || /^[0-9a-f]{2}$/ } readdir(D);
foreach my $repo (@dirs) {
$linked = 0;
diff --git a/git-remote.perl b/git-remote.perl
index 9ca3e7ef37..5cd69513cf 100755
--- a/git-remote.perl
+++ b/git-remote.perl
@@ -1,5 +1,6 @@
#!/usr/bin/perl -w
+use strict;
use Git;
my $git = Git->repository();
@@ -244,7 +245,8 @@ sub show_remote {
print "* remote $name\n";
print " URL: $info->{'URL'}\n";
for my $branchname (sort keys %$branch) {
- next if ($branch->{$branchname}{'REMOTE'} ne $name);
+ next unless (defined $branch->{$branchname}{'REMOTE'} &&
+ $branch->{$branchname}{'REMOTE'} eq $name);
my @merged = map {
s|^refs/heads/||;
$_;
@@ -295,12 +297,13 @@ sub add_remote {
sub update_remote {
my ($name) = @_;
+ my @remotes;
my $conf = $git->config("remotes." . $name);
if (defined($conf)) {
@remotes = split(' ', $conf);
} elsif ($name eq 'default') {
- undef @remotes;
+ @remotes = ();
for (sort keys %$remote) {
my $do_fetch = $git->config_bool("remote." . $_ .
".skipDefaultUpdate");
@@ -340,7 +343,7 @@ sub rm_remote {
my @refs = $git->command('for-each-ref',
'--format=%(refname) %(objectname)', "refs/remotes/$name");
for (@refs) {
- ($ref, $object) = split;
+ my ($ref, $object) = split;
$git->command(qw(update-ref -d), $ref, $object);
}
return 0;
@@ -351,7 +354,7 @@ sub add_usage {
exit(1);
}
-local $VERBOSE = 0;
+my $VERBOSE = 0;
@ARGV = grep {
if ($_ eq '-v' or $_ eq '--verbose') {
$VERBOSE=1;
@@ -394,7 +397,7 @@ elsif ($ARGV[0] eq 'update') {
update_remote("default");
exit(1);
}
- for ($i = 1; $i < @ARGV; $i++) {
+ for (my $i = 1; $i < @ARGV; $i++) {
update_remote($ARGV[$i]);
}
}
diff --git a/git-repack.sh b/git-repack.sh
index e72adc4d91..e18eb3f5dc 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -3,7 +3,22 @@
# Copyright (c) 2005 Linus Torvalds
#
-USAGE='[-a|-A] [-d] [-f] [-l] [-n] [-q] [--max-pack-size=N] [--window=N] [--window-memory=N] [--depth=N]'
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-repack [options]
+--
+a pack everything in a single pack
+A same as -a, and keep unreachable objects too
+d remove redundant packs, and run git-prune-packed
+f pass --no-reuse-delta to git-pack-objects
+q,quiet be quiet
+l pass --local to git-pack-objects
+ Packing constraints
+window= size of the window used for delta compression
+window-memory= same as the above, but limit memory size instead of entries count
+depth= limits the maximum delta depth
+max-pack-size= maximum size of each packfile
+"
SUBDIRECTORY_OK='Yes'
. git-sh-setup
@@ -20,10 +35,9 @@ do
-q) quiet=-q ;;
-f) no_reuse=--no-reuse-object ;;
-l) local=--local ;;
- --max-pack-size=*) extra="$extra $1" ;;
- --window=*) extra="$extra $1" ;;
- --window-memory=*) extra="$extra $1" ;;
- --depth=*) extra="$extra $1" ;;
+ --max-pack-size|--window|--window-memory|--depth)
+ extra="$extra $1=$2"; shift ;;
+ --) shift; break;;
*) usage ;;
esac
shift
@@ -83,9 +97,6 @@ for name in $names ; do
fullbases="$fullbases pack-$name"
chmod a-w "$PACKTMP-$name.pack"
chmod a-w "$PACKTMP-$name.idx"
- if test "$quiet" != '-q'; then
- echo "Pack pack-$name created."
- fi
mkdir -p "$PACKDIR" || exit
for sfx in pack idx
diff --git a/git-request-pull.sh b/git-request-pull.sh
index a992430679..068f5e0fc7 100755
--- a/git-request-pull.sh
+++ b/git-request-pull.sh
@@ -8,6 +8,7 @@ USAGE='<commit> <url> [<head>]'
LONG_USAGE='Summarizes the changes since <commit> to the standard output,
and includes <url> in the message generated.'
SUBDIRECTORY_OK='Yes'
+OPTIONS_SPEC=
. git-sh-setup
. git-parse-remote
@@ -24,13 +25,13 @@ headrev=`git rev-parse --verify "$head"^0` || exit
merge_base=`git merge-base $baserev $headrev` ||
die "fatal: No commits in common between $base and $head"
-url="`get_remote_url "$url"`"
-branch=`git peek-remote "$url" \
+url=$(get_remote_url "$url")
+branch=$(git peek-remote "$url" \
| sed -n -e "/^$headrev refs.heads./{
s/^.* refs.heads.//
p
q
- }"`
+ }")
if [ -z "$branch" ]; then
echo "warn: No branch of $url is at:" >&2
git log --max-count=1 --pretty='format:warn: %h: %s' $headrev >&2
diff --git a/git-send-email.perl b/git-send-email.perl
index 62e1429733..29b1105c4c 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -24,8 +24,6 @@ use Data::Dumper;
use Term::ANSIColor;
use Git;
-$SIG{INT} = sub { print color("reset"), "\n"; exit };
-
package FakeTerm;
sub new {
my ($class, $reason) = @_;
@@ -88,8 +86,13 @@ Options:
--smtp-ssl If set, connects to the SMTP server using SSL.
- --suppress-from Suppress sending emails to yourself if your address
- appears in a From: line. Defaults to off.
+ --suppress-cc Suppress the specified category of auto-CC. The category
+ can be one of 'author' for the patch author, 'self' to
+ avoid copying yourself, 'sob' for Signed-off-by lines,
+ 'cccmd' for the output of the cccmd, or 'all' to suppress
+ all of these.
+
+ --suppress-from Suppress sending emails to yourself. Defaults to off.
--thread Specify that the "In-Reply-To:" header should be set on all
emails. Defaults to on.
@@ -101,6 +104,8 @@ Options:
--envelope-sender Specify the envelope sender used to send the emails.
+ --no-validate Don't perform any sanity checks on patches.
+
EOT
exit(1);
}
@@ -146,6 +151,7 @@ sub format_2822_time {
my $have_email_valid = eval { require Email::Valid; 1 };
my $smtp;
+my $auth;
sub unique_email_list(@);
sub cleanup_compose_files();
@@ -155,7 +161,7 @@ my $compose_filename = ".msg.$$";
# Variables we fill in automatically, or via prompting:
my (@to,@cc,@initial_cc,@bcclist,@xh,
- $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time);
+ $initial_reply_to,$initial_subject,@files,$author,$sender,$smtp_authpass,$compose,$time);
my $envelope_sender;
@@ -164,7 +170,9 @@ my $envelope_sender;
my $repo = Git->repository();
my $term = eval {
- new Term::ReadLine 'git-send-email';
+ $ENV{"GIT_SEND_EMAIL_NOTTY"}
+ ? new Term::ReadLine 'git-send-email', \*STDIN, \*STDOUT
+ : new Term::ReadLine 'git-send-email';
};
if ($@) {
$term = new FakeTerm "$@: going non-interactive";
@@ -175,14 +183,16 @@ my ($quiet, $dry_run) = (0, 0);
# Variables with corresponding config settings
my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd);
-my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_authpass, $smtp_ssl);
+my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_ssl);
my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
+my ($no_validate);
+my (@suppress_cc);
my %config_bool_settings = (
"thread" => [\$thread, 1],
"chainreplyto" => [\$chain_reply_to, 1],
- "suppressfrom" => [\$suppress_from, 0],
- "signedoffcc" => [\$signed_off_cc, 1],
+ "suppressfrom" => [\$suppress_from, undef],
+ "signedoffcc" => [\$signed_off_cc, undef],
"smtpssl" => [\$smtp_ssl, 0],
);
@@ -191,12 +201,37 @@ my %config_settings = (
"smtpserverport" => \$smtp_server_port,
"smtpuser" => \$smtp_authuser,
"smtppass" => \$smtp_authpass,
+ "to" => \@to,
"cccmd" => \$cc_cmd,
"aliasfiletype" => \$aliasfiletype,
"bcc" => \@bcclist,
"aliasesfile" => \@alias_files,
+ "suppresscc" => \@suppress_cc,
);
+# Handle Uncouth Termination
+sub signal_handler {
+
+ # Make text normal
+ print color("reset"), "\n";
+
+ # SMTP password masked
+ system "stty echo";
+
+ # tmp files from --compose
+ if (-e $compose_filename) {
+ print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
+ }
+ if (-e ($compose_filename . ".final")) {
+ print "'$compose_filename.final' contains the composed email.\n"
+ }
+
+ exit;
+};
+
+$SIG{TERM} = \&signal_handler;
+$SIG{INT} = \&signal_handler;
+
# Begin by accumulating all the variables (defined above), that we will end up
# needing, first, from the command line:
@@ -210,17 +245,19 @@ my $rc = GetOptions("sender|from=s" => \$sender,
"smtp-server=s" => \$smtp_server,
"smtp-server-port=s" => \$smtp_server_port,
"smtp-user=s" => \$smtp_authuser,
- "smtp-pass=s" => \$smtp_authpass,
+ "smtp-pass:s" => \$smtp_authpass,
"smtp-ssl!" => \$smtp_ssl,
"identity=s" => \$identity,
"compose" => \$compose,
"quiet" => \$quiet,
"cc-cmd=s" => \$cc_cmd,
"suppress-from!" => \$suppress_from,
+ "suppress-cc=s" => \@suppress_cc,
"signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
"dry-run" => \$dry_run,
"envelope-sender=s" => \$envelope_sender,
"thread!" => \$thread,
+ "no-validate" => \$no_validate,
);
unless ($rc) {
@@ -261,6 +298,35 @@ foreach my $setting (values %config_bool_settings) {
${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]}));
}
+# Set CC suppressions
+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)$/;
+ $suppress_cc{$entry} = 1;
+ }
+}
+
+if ($suppress_cc{'all'}) {
+ foreach my $entry (qw (ccmd cc author self sob)) {
+ $suppress_cc{$entry} = 1;
+ }
+ delete $suppress_cc{'all'};
+}
+
+# If explicit old-style ones are specified, they trump --suppress-cc.
+$suppress_cc{'self'} = $suppress_from if defined $suppress_from;
+$suppress_cc{'sob'} = $signed_off_cc if defined $signed_off_cc;
+
+# Debugging, print out the suppressions.
+if (0) {
+ print "suppressions:\n";
+ foreach my $entry (keys %suppress_cc) {
+ printf " %-5s -> $suppress_cc{$entry}\n", $entry;
+ }
+}
+
my ($repoauthor) = $repo->ident_person('author');
my ($repocommitter) = $repo->ident_person('committer');
@@ -313,12 +379,49 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
($sender) = expand_aliases($sender) if defined $sender;
+# Now that all the defaults are set, process the rest of the command line
+# arguments and collect up the files that need to be processed.
+for my $f (@ARGV) {
+ if (-d $f) {
+ opendir(DH,$f)
+ or die "Failed to opendir $f: $!";
+
+ push @files, grep { -f $_ } map { +$f . "/" . $_ }
+ sort readdir(DH);
+
+ } elsif (-f $f) {
+ push @files, $f;
+
+ } else {
+ print STDERR "Skipping $f - not found.\n";
+ }
+}
+
+if (!$no_validate) {
+ foreach my $f (@files) {
+ my $error = validate_patch($f);
+ $error and die "fatal: $f: $error\nwarning: no patches were sent\n";
+ }
+}
+
+if (@files) {
+ unless ($quiet) {
+ print $_,"\n" for (@files);
+ }
+} else {
+ print STDERR "\nNo patch files specified!\n\n";
+ usage();
+}
+
my $prompting = 0;
if (!defined $sender) {
$sender = $repoauthor || $repocommitter;
- do {
+
+ while (1) {
$_ = $term->readline("Who should the emails appear to be from? [$sender] ");
- } while (!defined $_);
+ last if defined $_;
+ print "\n";
+ }
$sender = $_ if ($_);
print "Emails will be sent from: ", $sender, "\n";
@@ -326,10 +429,14 @@ if (!defined $sender) {
}
if (!@to) {
- do {
- $_ = $term->readline("Who should the emails be sent to? ",
- "");
- } while (!defined $_);
+
+
+ while (1) {
+ $_ = $term->readline("Who should the emails be sent to? ", "");
+ last if defined $_;
+ print "\n";
+ }
+
my $to = $_;
push @to, split /,/, $to;
$prompting++;
@@ -351,23 +458,29 @@ sub expand_aliases {
@bcclist = expand_aliases(@bcclist);
if (!defined $initial_subject && $compose) {
- do {
- $_ = $term->readline("What subject should the emails start with? ",
- $initial_subject);
- } while (!defined $_);
+ while (1) {
+ $_ = $term->readline("What subject should the initial email start with? ", $initial_subject);
+ last if defined $_;
+ print "\n";
+ }
+
$initial_subject = $_;
$prompting++;
}
if ($thread && !defined $initial_reply_to && $prompting) {
- do {
- $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
- $initial_reply_to);
- } while (!defined $_);
+ while (1) {
+ $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ", $initial_reply_to);
+ last if defined $_;
+ print "\n";
+ }
$initial_reply_to = $_;
- $initial_reply_to =~ s/^\s+<?/</;
- $initial_reply_to =~ s/>?\s+$/>/;
+}
+if (defined $initial_reply_to) {
+ $initial_reply_to =~ s/^\s*<?//;
+ $initial_reply_to =~ s/>?\s*$//;
+ $initial_reply_to = "<$initial_reply_to>" if $initial_reply_to ne '';
}
if (!defined $smtp_server) {
@@ -397,7 +510,7 @@ EOT
close(C);
my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
- system($editor, $compose_filename);
+ system('sh', '-c', '$0 $@', $editor, $compose_filename);
open(C2,">",$compose_filename . ".final")
or die "Failed to open $compose_filename.final : " . $!;
@@ -412,44 +525,18 @@ EOT
close(C);
close(C2);
- do {
+ while (1) {
$_ = $term->readline("Send this email? (y|n) ");
- } while (!defined $_);
+ last if defined $_;
+ print "\n";
+ }
if (uc substr($_,0,1) ne 'Y') {
cleanup_compose_files();
exit(0);
}
- @files = ($compose_filename . ".final");
-}
-
-
-# Now that all the defaults are set, process the rest of the command line
-# arguments and collect up the files that need to be processed.
-for my $f (@ARGV) {
- if (-d $f) {
- opendir(DH,$f)
- or die "Failed to opendir $f: $!";
-
- push @files, grep { -f $_ } map { +$f . "/" . $_ }
- sort readdir(DH);
-
- } elsif (-f $f) {
- push @files, $f;
-
- } else {
- print STDERR "Skipping $f - not found.\n";
- }
-}
-
-if (@files) {
- unless ($quiet) {
- print $_,"\n" for (@files);
- }
-} else {
- print STDERR "\nNo patch files specified!\n\n";
- usage();
+ @files = ($compose_filename . ".final", @files);
}
# Variables we set as part of the loop over files
@@ -514,11 +601,13 @@ $time = time - scalar $#files;
sub unquote_rfc2047 {
local ($_) = @_;
- if (s/=\?utf-8\?q\?(.*)\?=/$1/g) {
+ my $encoding;
+ if (s/=\?([^?]+)\?q\?(.*)\?=/$2/g) {
+ $encoding = $1;
s/_/ /g;
s/=([0-9A-F]{2})/chr(hex($1))/eg;
}
- return "$_";
+ return wantarray ? ($_, $encoding) : $_;
}
# use the simplest quoting being able to handle the recipient
@@ -555,7 +644,11 @@ sub sanitize_address
sub send_message
{
my @recipients = unique_email_list(@to);
- @cc = (map { sanitize_address($_) } @cc);
+ @cc = (grep { my $cc = extract_valid_address($_);
+ not grep { $cc eq $_ } @recipients
+ }
+ map { sanitize_address($_) }
+ @cc);
my $to = join (",\n\t", @recipients);
@recipients = unique_email_list(@recipients,@cc,@bcclist);
@recipients = (map { extract_valid_address($_) } @recipients);
@@ -571,7 +664,7 @@ sub send_message
$ccline = "\nCc: $cc";
}
my $sanitized_sender = sanitize_address($sender);
- make_message_id();
+ make_message_id() unless defined($message_id);
my $header = "From: $sanitized_sender
To: $to${ccline}
@@ -628,9 +721,26 @@ X-Mailer: git-send-email $gitversion
die "Unable to initialize SMTP properly. Is there something wrong with your config?";
}
- if ((defined $smtp_authuser) && (defined $smtp_authpass)) {
- $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message;
+ if (defined $smtp_authuser) {
+
+ if (!defined $smtp_authpass) {
+
+ system "stty -echo";
+
+ do {
+ print "Password: ";
+ $_ = <STDIN>;
+ print "\n";
+ } while (!defined $_);
+
+ chomp($smtp_authpass = $_);
+
+ system "stty echo";
+ }
+
+ $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message;
}
+
$smtp->mail( $raw_from ) or die $smtp->message;
$smtp->to( @recipients ) or die $smtp->message;
$smtp->data or die $smtp->message;
@@ -641,7 +751,7 @@ X-Mailer: git-send-email $gitversion
if ($quiet) {
printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject);
} else {
- print (($dry_run ? "Dry-" : "")."OK. Log says:\nDate: $date\n");
+ print (($dry_run ? "Dry-" : "")."OK. Log says:\n");
if ($smtp_server !~ m#^/#) {
print "Server: $smtp_server\n";
print "MAIL FROM:<$raw_from>\n";
@@ -649,7 +759,7 @@ X-Mailer: git-send-email $gitversion
} else {
print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n";
}
- print "From: $sanitized_sender\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
+ print $header, "\n";
if ($smtp) {
print "Result: ", $smtp->code, ' ',
($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
@@ -667,6 +777,9 @@ foreach my $t (@files) {
open(F,"<",$t) or die "can't open file $t";
my $author = undef;
+ my $author_encoding;
+ my $has_content_type;
+ my $body_encoding;
@cc = @initial_cc;
@xh = ();
my $input_format = undef;
@@ -689,15 +802,29 @@ foreach my $t (@files) {
} elsif (/^(Cc|From):\s+(.*)$/) {
if (unquote_rfc2047($2) eq $sender) {
- next if ($suppress_from);
+ next if ($suppress_cc{'self'});
}
elsif ($1 eq 'From') {
- $author = unquote_rfc2047($2);
+ ($author, $author_encoding)
+ = unquote_rfc2047($2);
+ next if ($suppress_cc{'author'});
+ } else {
+ next if ($suppress_cc{'cc'});
}
printf("(mbox) Adding cc: %s from line '%s'\n",
$2, $_) unless $quiet;
push @cc, $2;
}
+ elsif (/^Content-type:/i) {
+ $has_content_type = 1;
+ if (/charset="?[^ "]+/) {
+ $body_encoding = $1;
+ }
+ push @xh, $_;
+ }
+ elsif (/^Message-Id: (.*)/i) {
+ $message_id = $1;
+ }
elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
push @xh, $_;
}
@@ -709,7 +836,7 @@ foreach my $t (@files) {
# line 2 = subject
# So let's support that, too.
$input_format = 'lots';
- if (@cc == 0) {
+ if (@cc == 0 && !$suppress_cc{'cc'}) {
printf("(non-mbox) Adding cc: %s from line '%s'\n",
$_, $_) unless $quiet;
@@ -726,9 +853,11 @@ foreach my $t (@files) {
}
} else {
$message .= $_;
- if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) {
+ if (/^(Signed-off-by|Cc): (.*)$/i) {
+ next if ($suppress_cc{'sob'});
my $c = $2;
chomp $c;
+ next if ($c eq $sender and $suppress_cc{'self'});
push @cc, $c;
printf("(sob) Adding cc: %s from line '%s'\n",
$c, $_) unless $quiet;
@@ -737,13 +866,14 @@ foreach my $t (@files) {
}
close F;
- if (defined $cc_cmd) {
+ if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
open(F, "$cc_cmd $t |")
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;
@@ -754,6 +884,22 @@ foreach my $t (@files) {
if (defined $author) {
$message = "From: $author\n\n$message";
+ if (defined $author_encoding) {
+ if ($has_content_type) {
+ if ($body_encoding eq $author_encoding) {
+ # ok, we already have the right encoding
+ }
+ else {
+ # uh oh, we should re-encode
+ }
+ }
+ else {
+ push @xh,
+ 'MIME-Version: 1.0',
+ "Content-Type: text/plain; charset=$author_encoding",
+ 'Content-Transfer-Encoding: 8bit';
+ }
+ }
}
send_message();
@@ -767,6 +913,7 @@ foreach my $t (@files) {
$references = "$message_id";
}
}
+ $message_id = undef;
}
if ($compose) {
@@ -796,3 +943,15 @@ sub unique_email_list(@) {
}
return @emails;
}
+
+sub validate_patch {
+ my $fn = shift;
+ open(my $fh, '<', $fn)
+ or die "unable to open $fn: $!\n";
+ while (my $line = <$fh>) {
+ if (length($line) > 998) {
+ return "$.: patch contains a line longer than 998 characters";
+ }
+ }
+ return undef;
+}
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 3c325fd133..a44b1c74a3 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -16,9 +16,41 @@ die() {
exit 1
}
-usage() {
- die "Usage: $0 $USAGE"
-}
+if test -n "$OPTIONS_SPEC"; then
+ usage() {
+ "$0" -h
+ exit 1
+ }
+
+ parseopt_extra=
+ [ -n "$OPTIONS_KEEPDASHDASH" ] &&
+ parseopt_extra="--keep-dashdash"
+
+ eval "$(
+ echo "$OPTIONS_SPEC" |
+ git rev-parse --parseopt $parseopt_extra -- "$@" ||
+ echo exit $?
+ )"
+else
+ usage() {
+ die "Usage: $0 $USAGE"
+ }
+
+ if [ -z "$LONG_USAGE" ]
+ then
+ LONG_USAGE="Usage: $0 $USAGE"
+ else
+ LONG_USAGE="Usage: $0 $USAGE
+
+$LONG_USAGE"
+ fi
+
+ case "$1" in
+ -h|--h|--he|--hel|--help)
+ echo "$LONG_USAGE"
+ exit
+ esac
+fi
set_reflog_action() {
if [ -z "${GIT_REFLOG_ACTION:+set}" ]
@@ -87,45 +119,26 @@ get_author_ident_from_commit () {
}
'
encoding=$(git config i18n.commitencoding || echo UTF-8)
- git show -s --pretty=raw --encoding="$encoding" "$1" |
+ git show -s --pretty=raw --encoding="$encoding" "$1" -- |
LANG=C LC_ALL=C sed -ne "$pick_author_script"
}
-if [ -z "$LONG_USAGE" ]
+# Make sure we are in a valid repository of a vintage we understand,
+# if we require to be in a git repository.
+if test -z "$NONGIT_OK"
then
- LONG_USAGE="Usage: $0 $USAGE"
-else
- LONG_USAGE="Usage: $0 $USAGE
-
-$LONG_USAGE"
-fi
-
-case "$1" in
- -h|--h|--he|--hel|--help)
- echo "$LONG_USAGE"
- exit
-esac
-
-# Make sure we are in a valid repository of a vintage we understand.
-if [ -z "$SUBDIRECTORY_OK" ]
-then
- : ${GIT_DIR=.git}
- GIT_DIR=$(GIT_DIR="$GIT_DIR" git rev-parse --git-dir) || {
- exit=$?
- echo >&2 "You need to run this command from the toplevel of the working tree."
- exit $exit
- }
-else
- GIT_DIR=$(git rev-parse --git-dir) || {
- exit=$?
- echo >&2 "Failed to find a valid git directory."
- exit $exit
+ GIT_DIR=$(git rev-parse --git-dir) || exit
+ if [ -z "$SUBDIRECTORY_OK" ]
+ then
+ test -z "$(git rev-parse --show-cdup)" || {
+ exit=$?
+ echo >&2 "You need to run this command from the toplevel of the working tree."
+ exit $exit
+ }
+ fi
+ test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || {
+ echo >&2 "Unable to determine absolute path of git directory"
+ exit 1
}
+ : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
fi
-
-test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || {
- echo >&2 "Unable to determine absolute path of git directory"
- exit 1
-}
-
-: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
diff --git a/git-stash.sh b/git-stash.sh
index 7ba61625ba..b00f888169 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -1,9 +1,10 @@
#!/bin/sh
# Copyright (c) 2007, Nanako Shiraishi
-USAGE='[ | list | show | apply | clear]'
+USAGE='[ | save | list | show | apply | clear | create ]'
SUBDIRECTORY_OK=Yes
+OPTIONS_SPEC=
. git-sh-setup
require_work_tree
cd_to_toplevel
@@ -14,35 +15,33 @@ trap 'rm -f "$TMP-*"' 0
ref_stash=refs/stash
no_changes () {
- git diff-index --quiet --cached HEAD &&
+ git diff-index --quiet --cached HEAD -- &&
git diff-files --quiet
}
clear_stash () {
+ if test $# != 0
+ then
+ die "git stash clear with parameters is unimplemented"
+ fi
if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
then
- git update-ref -d refs/stash $current
+ git update-ref -d $ref_stash $current
fi
}
-save_stash () {
+create_stash () {
stash_msg="$1"
if no_changes
then
- echo >&2 'No local changes to save'
exit 0
fi
- test -f "$GIT_DIR/logs/$ref_stash" ||
- clear_stash || die "Cannot initialize stash"
-
- # Make sure the reflog for stash is kept.
- : >>"$GIT_DIR/logs/$ref_stash"
# state of the base commit
if b_commit=$(git rev-parse --verify HEAD)
then
- head=$(git log --abbrev-commit --pretty=oneline -n 1 HEAD)
+ head=$(git log --no-color --abbrev-commit --pretty=oneline -n 1 HEAD --)
else
die "You do not have the initial commit yet"
fi
@@ -84,10 +83,27 @@ save_stash () {
w_commit=$(printf '%s\n' "$stash_msg" |
git commit-tree $w_tree -p $b_commit -p $i_commit) ||
die "Cannot record working tree state"
+}
+
+save_stash () {
+ stash_msg="$1"
+
+ if no_changes
+ then
+ echo 'No local changes to save'
+ exit 0
+ fi
+ test -f "$GIT_DIR/logs/$ref_stash" ||
+ clear_stash || die "Cannot initialize stash"
+
+ create_stash "$stash_msg"
+
+ # Make sure the reflog for stash is kept.
+ : >>"$GIT_DIR/logs/$ref_stash"
git update-ref -m "$stash_msg" $ref_stash $w_commit ||
die "Cannot save the current status"
- printf >&2 'Saved "%s"\n' "$stash_msg"
+ printf 'Saved working directory and index state "%s"\n' "$stash_msg"
}
have_stash () {
@@ -96,7 +112,7 @@ have_stash () {
list_stash () {
have_stash || return 0
- git log --pretty=oneline -g "$@" $ref_stash |
+ git log --no-color --pretty=oneline -g "$@" $ref_stash -- |
sed -n -e 's/^[.0-9a-f]* refs\///p'
}
@@ -139,7 +155,7 @@ apply_stash () {
unstashed_index_tree=
if test -n "$unstash_index" && test "$b_tree" != "$i_tree"
then
- git diff --binary $s^2^..$s^2 | git apply --cached
+ git diff-tree --binary $s^2^..$s^2 | git apply --cached
test $? -ne 0 &&
die 'Conflicts in index. Try without --index.'
unstashed_index_tree=$(git-write-tree) ||
@@ -162,7 +178,7 @@ apply_stash () {
git read-tree "$unstashed_index_tree"
else
a="$TMP-added" &&
- git diff --cached --name-only --diff-filter=A $c_tree >"$a" &&
+ 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"
@@ -195,21 +211,33 @@ show)
shift
show_stash "$@"
;;
+save)
+ shift
+ save_stash "$*" && git-reset --hard
+ ;;
apply)
shift
apply_stash "$@"
;;
clear)
- clear_stash
+ shift
+ clear_stash "$@"
;;
-help | usage)
- usage
+create)
+ if test $# -gt 0 && test "$1" = create
+ then
+ shift
+ fi
+ create_stash "$*" && echo "$w_commit"
;;
*)
- if test $# -gt 0 && test "$1" = save
+ if test $# -eq 0
then
- shift
+ save_stash &&
+ echo '(To restore them type "git stash apply")' &&
+ git-reset --hard
+ else
+ usage
fi
- save_stash "$*" && git-reset --hard
;;
esac
diff --git a/git-submodule.sh b/git-submodule.sh
index 4aaaaab0d8..a6aaf40b0a 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -5,14 +5,12 @@
# Copyright (c) 2007 Lars Hjemli
USAGE='[--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]'
+OPTIONS_SPEC=
. git-sh-setup
require_work_tree
-add=
+command=
branch=
-init=
-update=
-status=
quiet=
cached=
@@ -73,7 +71,7 @@ resolve_relative_url ()
module_name()
{
# Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
- re=$(printf '%s' "$1" | sed -e 's/\([^a-zA-Z0-9_]\)/\\\1/g')
+ re=$(printf '%s' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
name=$( GIT_CONFIG=.gitmodules \
git config --get-regexp '^submodule\..*\.path$' |
sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
@@ -85,9 +83,9 @@ module_name()
#
# Clone a submodule
#
-# Prior to calling, modules_update checks that a possibly existing
+# Prior to calling, cmd_update checks that a possibly existing
# path is not a git repository.
-# Likewise, module_add checks that path does not exist at all,
+# Likewise, cmd_add checks that path does not exist at all,
# since it is the location of a new submodule.
#
module_clone()
@@ -120,8 +118,34 @@ module_clone()
#
# optional branch is stored in global branch variable
#
-module_add()
+cmd_add()
{
+ # parse $args after "submodule ... add".
+ while test $# -ne 0
+ do
+ case "$1" in
+ -b | --branch)
+ case "$2" in '') usage ;; esac
+ branch=$2
+ shift
+ ;;
+ -q|--quiet)
+ quiet=1
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done
+
repo=$1
path=$2
@@ -157,7 +181,7 @@ module_add()
die "'$path' already exists in the index"
module_clone "$path" "$realrepo" || exit
- (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
+ (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
die "Unable to checkout submodule '$path'"
git add "$path" ||
die "Failed to add submodule '$path'"
@@ -173,8 +197,29 @@ module_add()
#
# $@ = requested paths (default to all)
#
-modules_init()
+cmd_init()
{
+ # parse $args after "submodule ... init".
+ while test $# -ne 0
+ do
+ case "$1" in
+ -q|--quiet)
+ quiet=1
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done
+
git ls-files --stage -- "$@" | grep -e '^160000 ' |
while read mode sha1 stage path
do
@@ -206,8 +251,29 @@ modules_init()
#
# $@ = requested paths (default to all)
#
-modules_update()
+cmd_update()
{
+ # parse $args after "submodule ... update".
+ while test $# -ne 0
+ do
+ case "$1" in
+ -q|--quiet)
+ quiet=1
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done
+
git ls-files --stage -- "$@" | grep -e '^160000 ' |
while read mode sha1 stage path
do
@@ -227,14 +293,14 @@ modules_update()
module_clone "$path" "$url" || exit
subsha1=
else
- subsha1=$(unset GIT_DIR && cd "$path" &&
+ subsha1=$(unset GIT_DIR; cd "$path" &&
git rev-parse --verify HEAD) ||
die "Unable to find current revision in submodule path '$path'"
fi
if test "$subsha1" != "$sha1"
then
- (unset GIT_DIR && cd "$path" && git-fetch &&
+ (unset GIT_DIR; cd "$path" && git-fetch &&
git-checkout -q "$sha1") ||
die "Unable to checkout '$sha1' in submodule path '$path'"
@@ -245,7 +311,7 @@ modules_update()
set_name_rev () {
revname=$( (
- unset GIT_DIR &&
+ unset GIT_DIR
cd "$1" && {
git describe "$2" 2>/dev/null ||
git describe --tags "$2" 2>/dev/null ||
@@ -265,8 +331,32 @@ set_name_rev () {
#
# $@ = requested paths (default to all)
#
-modules_list()
+cmd_status()
{
+ # parse $args after "submodule ... status".
+ while test $# -ne 0
+ do
+ case "$1" in
+ -q|--quiet)
+ quiet=1
+ ;;
+ --cached)
+ cached=1
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done
+
git ls-files --stage -- "$@" | grep -e '^160000 ' |
while read mode sha1 stage path
do
@@ -284,7 +374,7 @@ modules_list()
else
if test -z "$cached"
then
- sha1=$(unset GIT_DIR && cd "$path" && git rev-parse --verify HEAD)
+ sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
set_name_rev "$path" "$sha1"
fi
say "+$sha1 $path$revname"
@@ -292,20 +382,17 @@ modules_list()
done
}
-while test $# != 0
+# This loop parses the command line arguments to find the
+# subcommand name to dispatch. Parsing of the subcommand specific
+# options are primarily done by the subcommand implementations.
+# Subcommand specific options such as --branch and --cached are
+# parsed here as well, for backward compatibility.
+
+while test $# != 0 && test -z "$command"
do
case "$1" in
- add)
- add=1
- ;;
- init)
- init=1
- ;;
- update)
- update=1
- ;;
- status)
- status=1
+ add | init | update | status)
+ command=$1
;;
-q|--quiet)
quiet=1
@@ -334,30 +421,19 @@ do
shift
done
-case "$add,$branch" in
-1,*)
- ;;
-,)
- ;;
-,*)
+# No command word defaults to "status"
+test -n "$command" || command=status
+
+# "-b branch" is accepted only by "add"
+if test -n "$branch" && test "$command" != add
+then
usage
- ;;
-esac
-
-case "$add,$init,$update,$status,$cached" in
-1,,,,)
- module_add "$@"
- ;;
-,1,,,)
- modules_init "$@"
- ;;
-,,1,,)
- modules_update "$@"
- ;;
-,,,*,*)
- modules_list "$@"
- ;;
-*)
+fi
+
+# "--cached" is accepted only by "status"
+if test -n "$cached" && test "$command" != status
+then
usage
- ;;
-esac
+fi
+
+"cmd_$command" "$@"
diff --git a/git-svn.perl b/git-svn.perl
index 22bb47b34d..05fb3582d9 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -35,6 +35,7 @@ push @Git::SVN::Ra::ISA, 'SVN::Ra';
push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
use Carp qw/croak/;
+use Digest::MD5;
use IO::File qw//;
use File::Basename qw/dirname basename/;
use File::Path qw/mkpath/;
@@ -64,7 +65,7 @@ my ($_stdin, $_help, $_edit,
$_template, $_shared,
$_version, $_fetch_all, $_no_rebase,
$_merge, $_strategy, $_dry_run, $_local,
- $_prefix, $_no_checkout, $_verbose);
+ $_prefix, $_no_checkout, $_url, $_verbose);
$Git::SVN::_follow_parent = 1;
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
'config-dir=s' => \$Git::SVN::Ra::config_dir,
@@ -80,6 +81,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
'quiet|q' => \$_q,
'repack-flags|repack-args|repack-opts=s' =>
\$Git::SVN::_repack_flags,
+ 'use-log-author' => \$Git::SVN::_use_log_author,
%remote_opts );
my ($_trunk, $_tags, $_branches, $_stdlayout);
@@ -141,6 +143,9 @@ my %cmd = (
'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
{ 'revision|r=i' => \$_revision
} ],
+ 'show-externals' => [ \&cmd_show_externals, "Show svn:externals listings",
+ { 'revision|r=i' => \$_revision
+ } ],
'multi-fetch' => [ \&cmd_multi_fetch,
"Deprecated alias for $0 fetch --all",
{ 'revision|r=s' => \$_revision, %fc_opts } ],
@@ -177,6 +182,13 @@ my %cmd = (
'file|F=s' => \$_file,
'revision|r=s' => \$_revision,
%cmt_opts } ],
+ 'info' => [ \&cmd_info,
+ "Show info about the latest SVN revision
+ on the current branch",
+ { 'url' => \$_url, } ],
+ 'blame' => [ \&Git::SVN::Log::cmd_blame,
+ "Show what revision and author last modified each line of a file",
+ {} ],
);
my $cmd;
@@ -188,25 +200,8 @@ for (my $i = 0; $i < @ARGV; $i++) {
}
};
-my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
-
-read_repo_config(\%opts);
-Getopt::Long::Configure('pass_through') if ($cmd && $cmd eq 'log');
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
- 'minimize-connections' => \$Git::SVN::Migration::_minimize,
- 'id|i=s' => \$Git::SVN::default_ref_id,
- 'svn-remote|remote|R=s' => sub {
- $Git::SVN::no_reuse_existing = 1;
- $Git::SVN::default_repo_id = $_[1] });
-exit 1 if (!$rv && $cmd && $cmd ne 'log');
-
-usage(0) if $_help;
-version() if $_version;
-usage(1) unless defined $cmd;
-load_authors() if $_authors;
-
-# make sure we're always running
-unless ($cmd =~ /(?:clone|init|multi-init)$/) {
+# make sure we're always running at the top-level working directory
+unless ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
unless (-d $ENV{GIT_DIR}) {
if ($git_dir_user_set) {
die "GIT_DIR=$ENV{GIT_DIR} explicitly set, ",
@@ -226,6 +221,24 @@ unless ($cmd =~ /(?:clone|init|multi-init)$/) {
$ENV{GIT_DIR} = $git_dir;
}
}
+
+my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
+
+read_repo_config(\%opts);
+Getopt::Long::Configure('pass_through') if ($cmd && $cmd eq 'log');
+my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
+ 'minimize-connections' => \$Git::SVN::Migration::_minimize,
+ 'id|i=s' => \$Git::SVN::default_ref_id,
+ 'svn-remote|remote|R=s' => sub {
+ $Git::SVN::no_reuse_existing = 1;
+ $Git::SVN::default_repo_id = $_[1] });
+exit 1 if (!$rv && $cmd && $cmd ne 'log');
+
+usage(0) if $_help;
+version() if $_version;
+usage(1) unless defined $cmd;
+load_authors() if $_authors;
+
unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) {
Git::SVN::Migration::migration_check();
}
@@ -252,7 +265,7 @@ Usage: $0 <command> [options] [arguments]\n
next if $cmd && $cmd ne $_;
next if /^multi-/; # don't show deprecated commands
print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n";
- foreach (keys %{$cmd{$_}->[2]}) {
+ foreach (sort keys %{$cmd{$_}->[2]}) {
# mixed-case options are for .git/config only
next if /[A-Z]/ && /^[a-z]+$/i;
# prints out arguments as they should be passed:
@@ -386,10 +399,14 @@ sub cmd_set_tree {
}
$gs->set_tree($_) foreach @revs;
print "Done committing ",scalar @revs," revisions to SVN\n";
+ unlink $gs->{index};
}
sub cmd_dcommit {
my $head = shift;
+ 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";
$head ||= 'HEAD';
my @refs;
my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
@@ -404,9 +421,10 @@ sub cmd_dcommit {
warn "Attempting to commit more than one change while ",
"--no-rebase is enabled.\n",
"If these changes depend on each other, re-running ",
- "without --no-rebase will be required."
+ "without --no-rebase may be required."
}
- foreach my $d (@$linear_refs) {
+ while (1) {
+ my $d = shift @$linear_refs or last;
unless (defined $last_rev) {
(undef, $last_rev, undef) = cmt_metadata("$d~1");
unless (defined $last_rev) {
@@ -421,6 +439,9 @@ sub cmd_dcommit {
my %ed_opts = ( r => $last_rev,
log => get_commit_entry($d)->{log},
ra => Git::SVN::Ra->new($gs->full_url),
+ config => SVN::Core::config_get_config(
+ $Git::SVN::Ra::config_dir
+ ),
tree_a => "$d~1",
tree_b => $d,
editor_cb => sub {
@@ -435,18 +456,19 @@ sub cmd_dcommit {
$parents->{$d};
}
$_fetch_all ? $gs->fetch_all : $gs->fetch;
+ $last_rev = $cmt_rev;
next if $_no_rebase;
# we always want to rebase against the current HEAD,
# not any head that was passed to us
- my @diff = command('diff-tree', 'HEAD',
+ my @diff = command('diff-tree', $d,
$gs->refname, '--');
my @finish;
if (@diff) {
@finish = rebase_cmd();
- print STDERR "W: HEAD and ", $gs->refname,
+ print STDERR "W: $d and ", $gs->refname,
" differ, using @finish:\n",
- "@diff";
+ join("\n", @diff), "\n";
} else {
print "No changes between current HEAD and ",
$gs->refname,
@@ -455,9 +477,48 @@ sub cmd_dcommit {
@finish = qw/reset --mixed/;
}
command_noisy(@finish, $gs->refname);
- $last_rev = $cmt_rev;
+ if (@diff) {
+ @refs = ();
+ my ($url_, $rev_, $uuid_, $gs_) =
+ working_head_info($head, \@refs);
+ my ($linear_refs_, $parents_) =
+ linearize_history($gs_, \@refs);
+ if (scalar(@$linear_refs) !=
+ scalar(@$linear_refs_)) {
+ fatal "# of revisions changed ",
+ "\nbefore:\n",
+ join("\n", @$linear_refs),
+ "\n\nafter:\n",
+ join("\n", @$linear_refs_), "\n",
+ 'If you are attempting to commit ',
+ "merges, try running:\n\t",
+ 'git rebase --interactive',
+ '--preserve-merges ',
+ $gs->refname,
+ "\nBefore dcommitting";
+ }
+ if ($url_ ne $url) {
+ fatal "URL mismatch after rebase: ",
+ "$url_ != $url";
+ }
+ if ($uuid_ ne $uuid) {
+ fatal "uuid mismatch after rebase: ",
+ "$uuid_ != $uuid";
+ }
+ # remap parents
+ my (%p, @l, $i);
+ for ($i = 0; $i < scalar @$linear_refs; $i++) {
+ my $new = $linear_refs_->[$i] or next;
+ $p{$new} =
+ $parents->{$linear_refs->[$i]};
+ push @l, $new;
+ }
+ $parents = \%p;
+ $linear_refs = \@l;
+ }
}
}
+ unlink $gs->{index};
}
sub cmd_find_rev {
@@ -473,7 +534,7 @@ sub cmd_find_rev {
"$head history\n";
}
my $desired_revision = substr($revision_or_hash, 1);
- $result = $gs->rev_db_get($desired_revision);
+ $result = $gs->rev_map_get($desired_revision);
} else {
my (undef, $rev, undef) = cmt_metadata($revision_or_hash);
$result = $rev;
@@ -494,6 +555,8 @@ sub cmd_rebase {
exit 1;
}
unless ($_local) {
+ # rebase will checkout for us, so no need to do it explicitly
+ $_no_checkout = 'true';
$_fetch_all ? $gs->fetch_all : $gs->fetch;
}
command_noisy(rebase_cmd(), $gs->refname);
@@ -514,6 +577,21 @@ sub cmd_show_ignore {
});
}
+sub cmd_show_externals {
+ my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+ $gs ||= Git::SVN->new;
+ my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
+ $gs->prop_walk($gs->{path}, $r, sub {
+ my ($gs, $path, $props) = @_;
+ print STDOUT "\n# $path\n";
+ my $s = $props->{'svn:externals'} or return;
+ $s =~ s/[\r\n]+/\n/g;
+ chomp $s;
+ $s =~ s#^#$path#gm;
+ print STDOUT "$s\n";
+ });
+}
+
sub cmd_create_ignore {
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
$gs ||= Git::SVN->new;
@@ -537,6 +615,23 @@ sub cmd_create_ignore {
});
}
+sub canonicalize_path {
+ my ($path) = @_;
+ my $dot_slash_added = 0;
+ if (substr($path, 0, 1) ne "/") {
+ $path = "./" . $path;
+ $dot_slash_added = 1;
+ }
+ # File::Spec->canonpath doesn't collapse x/../y into y (for a
+ # good reason), so let's do this manually.
+ $path =~ s#/+#/#g;
+ $path =~ s#/\.(?:/|$)#/#g;
+ $path =~ s#/[^/]+/\.\.##g;
+ $path =~ s#/$##g;
+ $path =~ s#^\./## if $dot_slash_added;
+ return $path;
+}
+
# get_svnprops(PATH)
# ------------------
# Helper for cmd_propget and cmd_proplist below.
@@ -554,12 +649,7 @@ sub get_svnprops {
# canonicalize the path (otherwise libsvn will abort or fail to
# find the file)
- # File::Spec->canonpath doesn't collapse x/../y into y (for a
- # good reason), so let's do this manually.
- $path =~ s#/+#/#g;
- $path =~ s#/\.(?:/|$)#/#g;
- $path =~ s#/[^/]+/\.\.##g;
- $path =~ s#/$##g;
+ $path = canonicalize_path($path);
my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
my $props;
@@ -690,6 +780,114 @@ sub cmd_commit_diff {
}
}
+sub cmd_info {
+ my $path = canonicalize_path(shift or ".");
+ unless (scalar(@_) == 0) {
+ die "Too many arguments specified\n";
+ }
+
+ my ($file_type, $diff_status) = find_file_type_and_diff_status($path);
+
+ if (!$file_type && !$diff_status) {
+ print STDERR "$path: (Not a versioned resource)\n\n";
+ return;
+ }
+
+ my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+ unless ($gs) {
+ die "Unable to determine upstream SVN information from ",
+ "working tree history\n";
+ }
+ my $full_url = $url . ($path eq "." ? "" : "/$path");
+
+ if ($_url) {
+ print $full_url, "\n";
+ return;
+ }
+
+ my $result = "Path: $path\n";
+ $result .= "Name: " . basename($path) . "\n" if $file_type ne "dir";
+ $result .= "URL: " . $full_url . "\n";
+
+ eval {
+ my $repos_root = $gs->repos_root;
+ Git::SVN::remove_username($repos_root);
+ $result .= "Repository Root: $repos_root\n";
+ };
+ if ($@) {
+ $result .= "Repository Root: (offline)\n";
+ }
+ $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A";
+ $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
+
+ $result .= "Node Kind: " .
+ ($file_type eq "dir" ? "directory" : "file") . "\n";
+
+ my $schedule = $diff_status eq "A"
+ ? "add"
+ : ($diff_status eq "D" ? "delete" : "normal");
+ $result .= "Schedule: $schedule\n";
+
+ if ($diff_status eq "A") {
+ print $result, "\n";
+ return;
+ }
+
+ my ($lc_author, $lc_rev, $lc_date_utc);
+ my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $path);
+ my $log = command_output_pipe(@args);
+ my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
+ while (<$log>) {
+ if (/^${esc_color}author (.+) <[^>]+> (\d+) ([\-\+]?\d+)$/o) {
+ $lc_author = $1;
+ $lc_date_utc = Git::SVN::Log::parse_git_date($2, $3);
+ } elsif (/^${esc_color} (git-svn-id:.+)$/o) {
+ (undef, $lc_rev, undef) = ::extract_metadata($1);
+ }
+ }
+ close $log;
+
+ Git::SVN::Log::set_local_timezone();
+
+ $result .= "Last Changed Author: $lc_author\n";
+ $result .= "Last Changed Rev: $lc_rev\n";
+ $result .= "Last Changed Date: " .
+ Git::SVN::Log::format_svn_date($lc_date_utc) . "\n";
+
+ if ($file_type ne "dir") {
+ my $text_last_updated_date =
+ ($diff_status eq "D" ? $lc_date_utc : (stat $path)[9]);
+ $result .=
+ "Text Last Updated: " .
+ Git::SVN::Log::format_svn_date($text_last_updated_date) .
+ "\n";
+ my $checksum;
+ if ($diff_status eq "D") {
+ my ($fh, $ctx) =
+ command_output_pipe(qw(cat-file blob), "HEAD:$path");
+ if ($file_type eq "link") {
+ my $file_name = <$fh>;
+ $checksum = md5sum("link $file_name");
+ } else {
+ $checksum = md5sum($fh);
+ }
+ command_close_pipe($fh, $ctx);
+ } elsif ($file_type eq "link") {
+ my $file_name =
+ command(qw(cat-file blob), "HEAD:$path");
+ $checksum =
+ md5sum("link " . $file_name);
+ } else {
+ open FILE, "<", $path or die $!;
+ $checksum = md5sum(\*FILE);
+ close FILE or die $!;
+ }
+ $result .= "Checksum: " . $checksum . "\n";
+ }
+
+ print $result, "\n";
+}
+
########################### utility functions #########################
sub rebase_cmd {
@@ -935,12 +1133,12 @@ sub working_head_info {
if (defined $url && defined $rev) {
next if $max{$url} and $max{$url} < $rev;
if (my $gs = Git::SVN->find_by_url($url)) {
- my $c = $gs->rev_db_get($rev);
+ my $c = $gs->rev_map_get($rev);
if ($c && $c eq $hash) {
close $fh; # break the pipe
return ($url, $rev, $uuid, $gs);
} else {
- $max{$url} ||= $gs->rev_db_max;
+ $max{$url} ||= $gs->rev_map_max;
}
}
}
@@ -997,18 +1195,63 @@ sub linearize_history {
(\@linear_refs, \%parents);
}
+sub find_file_type_and_diff_status {
+ my ($path) = @_;
+ return ('dir', '') if $path eq '.';
+
+ my $diff_output =
+ command_oneline(qw(diff --cached --name-status --), $path) || "";
+ my $diff_status = (split(' ', $diff_output))[0] || "";
+
+ my $ls_tree = command_oneline(qw(ls-tree HEAD), $path) || "";
+
+ return (undef, undef) if !$diff_status && !$ls_tree;
+
+ if ($diff_status eq "A") {
+ return ("link", $diff_status) if -l $path;
+ return ("dir", $diff_status) if -d $path;
+ return ("file", $diff_status);
+ }
+
+ my $mode = (split(' ', $ls_tree))[0] || "";
+
+ return ("link", $diff_status) if $mode eq "120000";
+ return ("dir", $diff_status) if $mode eq "040000";
+ return ("file", $diff_status);
+}
+
+sub md5sum {
+ my $arg = shift;
+ my $ref = ref $arg;
+ my $md5 = Digest::MD5->new();
+ if ($ref eq 'GLOB' || $ref eq 'IO::File') {
+ $md5->addfile($arg) or croak $!;
+ } elsif ($ref eq 'SCALAR') {
+ $md5->add($$arg) or croak $!;
+ } elsif (!$ref) {
+ $md5->add($arg) or croak $!;
+ } else {
+ ::fatal "Can't provide MD5 hash for unknown ref type: '", $ref, "'";
+ }
+ return $md5->hexdigest();
+}
+
package Git::SVN;
use strict;
use warnings;
+use Fcntl qw/:DEFAULT :seek/;
+use constant rev_map_fmt => 'NH40';
use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
$_repack $_repack_flags $_use_svm_props $_head
- $_use_svnsync_props $no_reuse_existing $_minimize_url/;
+ $_use_svnsync_props $no_reuse_existing $_minimize_url
+ $_use_log_author/;
use Carp qw/croak/;
use File::Path qw/mkpath/;
use File::Copy qw/copy/;
use IPC::Open3;
-my $_repack_nr;
+my ($_gc_nr, $_gc_period);
+
# properties that we do not log:
my %SKIP_PROP;
BEGIN {
@@ -1044,8 +1287,11 @@ BEGIN {
}
}
-my %LOCKFILES;
-END { unlink keys %LOCKFILES if %LOCKFILES }
+my (%LOCKFILES, %INDEX_FILES);
+END {
+ unlink keys %LOCKFILES if %LOCKFILES;
+ unlink keys %INDEX_FILES if %INDEX_FILES;
+}
sub resolve_local_globs {
my ($url, $fetch, $glob_spec) = @_;
@@ -1127,7 +1373,7 @@ sub fetch_all {
if ($fetch) {
foreach my $p (sort keys %$fetch) {
my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p);
- my $lr = $gs->rev_db_max;
+ my $lr = $gs->rev_map_max;
if (defined $lr) {
$base = $lr if ($lr < $base);
}
@@ -1166,10 +1412,9 @@ sub read_all_remotes {
}
sub init_vars {
- if (defined $_repack) {
- $_repack = 1000 if ($_repack <= 0);
- $_repack_nr = $_repack;
- $_repack_flags ||= '-d';
+ $_gc_nr = $_gc_period = 1000;
+ if (defined $_repack || defined $_repack_flags) {
+ warn "Repack options are obsolete; they have no effect.\n";
}
}
@@ -1516,10 +1761,16 @@ sub svnsync {
# see if we have it in our config, first:
eval {
my $section = "svn-remote.$self->{repo_id}";
- $svnsync = {
- url => tmp_config('--get', "$section.svnsync-url"),
- uuid => tmp_config('--get', "$section.svnsync-uuid"),
- }
+
+ my $url = tmp_config('--get', "$section.svnsync-url");
+ ($url) = ($url =~ m{^([a-z\+]+://\S+)$}) or
+ die "doesn't look right - svn:sync-from-url is '$url'\n";
+
+ my $uuid = tmp_config('--get', "$section.svnsync-uuid");
+ ($uuid) = ($uuid =~ m{^([0-9a-f\-]{30,})$}) or
+ die "doesn't look right - svn:sync-from-uuid is '$uuid'\n";
+
+ $svnsync = { url => $url, uuid => $uuid }
};
if ($svnsync && $svnsync->{url} && $svnsync->{uuid}) {
return $self->{svnsync} = $svnsync;
@@ -1530,11 +1781,11 @@ sub svnsync {
my $rp = $self->ra->rev_proplist(0);
my $url = $rp->{'svn:sync-from-url'} or die $err . "url\n";
- $url =~ m{^[a-z\+]+://} or
+ ($url) = ($url =~ m{^([a-z\+]+://\S+)$}) or
die "doesn't look right - svn:sync-from-url is '$url'\n";
my $uuid = $rp->{'svn:sync-from-uuid'} or die $err . "uuid\n";
- $uuid =~ m{^[0-9a-f\-]{30,}$} or
+ ($uuid) = ($uuid =~ m{^([0-9a-f\-]{30,})$}) or
die "doesn't look right - svn:sync-from-uuid is '$uuid'\n";
my $section = "svn-remote.$self->{repo_id}";
@@ -1561,9 +1812,24 @@ sub ra_uuid {
$self->{ra_uuid};
}
+sub _set_repos_root {
+ my ($self, $repos_root) = @_;
+ my $k = "svn-remote.$self->{repo_id}.reposRoot";
+ $repos_root ||= $self->ra->{repos_root};
+ tmp_config($k, $repos_root);
+ $repos_root;
+}
+
+sub repos_root {
+ my ($self) = @_;
+ my $k = "svn-remote.$self->{repo_id}.reposRoot";
+ eval { tmp_config('--get', $k) } || $self->_set_repos_root;
+}
+
sub ra {
my ($self) = shift;
my $ra = Git::SVN::Ra->new($self->{url});
+ $self->_set_repos_root($ra->{repos_root});
if ($self->use_svm_props && !$self->{svm}) {
if ($self->no_metadata) {
die "Can't have both 'noMetadata' and ",
@@ -1601,6 +1867,7 @@ sub rel_path {
sub prop_walk {
my ($self, $path, $rev, $sub) = @_;
+ $path =~ s#^/##;
my ($dirent, undef, $props) = $self->ra->get_dir($path, $rev);
$path =~ s#^/*#/#g;
my $p = $path;
@@ -1647,38 +1914,20 @@ sub last_rev_commit {
return ($rev, $c);
}
}
- my $db_path = $self->db_path;
- unless (-e $db_path) {
+ my $map_path = $self->map_path;
+ unless (-e $map_path) {
($self->{last_rev}, $self->{last_commit}) = (undef, undef);
return (undef, undef);
}
- my $offset = -41; # from tail
- my $rl;
- open my $fh, '<', $db_path or croak "$db_path not readable: $!\n";
- sysseek($fh, $offset, 2); # don't care for errors
- sysread($fh, $rl, 41) == 41 or return (undef, undef);
- chomp $rl;
- while (('0' x40) eq $rl && sysseek($fh, 0, 1) != 0) {
- $offset -= 41;
- sysseek($fh, $offset, 2); # don't care for errors
- sysread($fh, $rl, 41) == 41 or return (undef, undef);
- chomp $rl;
- }
- if ($c && $c ne $rl) {
- die "$db_path and ", $self->refname,
- " inconsistent!:\n$c != $rl\n";
- }
- my $rev = sysseek($fh, 0, 1) or croak $!;
- $rev = ($rev - 41) / 41;
- close $fh or croak $!;
- ($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
- return ($rev, $c);
+ my ($rev, $commit) = $self->rev_map_max(1);
+ ($self->{last_rev}, $self->{last_commit}) = ($rev, $commit);
+ return ($rev, $commit);
}
sub get_fetch_range {
my ($self, $min, $max) = @_;
$max ||= $self->ra->get_latest_revnum;
- $min ||= $self->rev_db_max;
+ $min ||= $self->rev_map_max;
(++$min, $max);
}
@@ -1815,6 +2064,47 @@ sub full_url {
$self->{url} . (length $self->{path} ? '/' . $self->{path} : '');
}
+
+sub set_commit_header_env {
+ my ($log_entry) = @_;
+ my %env;
+ foreach my $ned (qw/NAME EMAIL DATE/) {
+ foreach my $ac (qw/AUTHOR COMMITTER/) {
+ $env{"GIT_${ac}_${ned}"} = $ENV{"GIT_${ac}_${ned}"};
+ }
+ }
+
+ $ENV{GIT_AUTHOR_NAME} = $log_entry->{name};
+ $ENV{GIT_AUTHOR_EMAIL} = $log_entry->{email};
+ $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
+
+ $ENV{GIT_COMMITTER_NAME} = (defined $log_entry->{commit_name})
+ ? $log_entry->{commit_name}
+ : $log_entry->{name};
+ $ENV{GIT_COMMITTER_EMAIL} = (defined $log_entry->{commit_email})
+ ? $log_entry->{commit_email}
+ : $log_entry->{email};
+ \%env;
+}
+
+sub restore_commit_header_env {
+ my ($env) = @_;
+ foreach my $ned (qw/NAME EMAIL DATE/) {
+ foreach my $ac (qw/AUTHOR COMMITTER/) {
+ my $k = "GIT_${ac}_${ned}";
+ if (defined $env->{$k}) {
+ $ENV{$k} = $env->{$k};
+ } else {
+ delete $ENV{$k};
+ }
+ }
+ }
+}
+
+sub gc {
+ command_noisy('gc', '--auto');
+};
+
sub do_git_commit {
my ($self, $log_entry) = @_;
my $lr = $self->last_rev;
@@ -1823,15 +2113,11 @@ sub do_git_commit {
" was r$lr, but we are about to fetch: ",
"r$log_entry->{revision}!\n";
}
- if (my $c = $self->rev_db_get($log_entry->{revision})) {
+ if (my $c = $self->rev_map_get($log_entry->{revision})) {
croak "$log_entry->{revision} = $c already exists! ",
"Why are we refetching it?\n";
}
- $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name};
- $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} =
- $log_entry->{email};
- $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
-
+ my $old_env = set_commit_header_env($log_entry);
my $tree = $log_entry->{tree};
if (!defined $tree) {
$tree = $self->tmp_index_do(sub {
@@ -1846,6 +2132,7 @@ sub do_git_commit {
defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
or croak $!;
print $msg_fh $log_entry->{log} or croak $!;
+ restore_commit_header_env($old_env);
unless ($self->no_metadata) {
print $msg_fh "\ngit-svn-id: $log_entry->{metadata}\n"
or croak $!;
@@ -1860,23 +2147,20 @@ sub do_git_commit {
die "Failed to commit, invalid sha1: $commit\n";
}
- $self->rev_db_set($log_entry->{revision}, $commit, 1);
+ $self->rev_map_set($log_entry->{revision}, $commit, 1);
$self->{last_rev} = $log_entry->{revision};
$self->{last_commit} = $commit;
print "r$log_entry->{revision}";
if (defined $log_entry->{svm_revision}) {
print " (\@$log_entry->{svm_revision})";
- $self->rev_db_set($log_entry->{svm_revision}, $commit,
+ $self->rev_map_set($log_entry->{svm_revision}, $commit,
0, $self->svm_uuid);
}
print " = $commit ($self->{ref_id})\n";
- if (defined $_repack && (--$_repack_nr == 0)) {
- $_repack_nr = $_repack;
- # repack doesn't use any arguments with spaces in them, does it?
- print "Running git repack $_repack_flags ...\n";
- command_noisy('repack', split(/\s+/, $_repack_flags));
- print "Done repacking\n";
+ if (--$_gc_nr == 0) {
+ $_gc_nr = $_gc_period;
+ gc();
}
return $commit;
}
@@ -1948,7 +2232,12 @@ sub find_parent_branch {
# 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";
- $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1);
+ my ($u, $p) = ($new_url, '');
+ if ($u =~ s#^\Q$url\E(/|$)##) {
+ $p = $u;
+ $u = $url;
+ }
+ $gs = Git::SVN->init($u, $p, $self->{repo_id}, $ref_id, 1);
}
my ($r0, $parent) = $gs->find_rev_before($r, 1);
if (!defined $r0 || !defined $parent) {
@@ -2119,7 +2408,26 @@ sub make_log_entry {
$log_entry{log} .= "\n";
my $author = $log_entry{author} = check_author($log_entry{author});
my ($name, $email) = defined $::users{$author} ? @{$::users{$author}}
- : ($author, undef);
+ : ($author, undef);
+
+ my ($commit_name, $commit_email) = ($name, $email);
+ if ($_use_log_author) {
+ my $name_field;
+ if ($log_entry{log} =~ /From:\s+(.*\S)\s*\n/i) {
+ $name_field = $1;
+ } elsif ($log_entry{log} =~ /Signed-off-by:\s+(.*\S)\s*\n/i) {
+ $name_field = $1;
+ }
+ if (!defined $name_field) {
+ #
+ } elsif ($name_field =~ /(.*?)\s+<(.*)>/) {
+ ($name, $email) = ($1, $2);
+ } elsif ($name_field =~ /(.*)@/) {
+ ($name, $email) = ($1, $name_field);
+ } else {
+ ($name, $email) = ($name_field, 'unknown');
+ }
+ }
if (defined $headrev && $self->use_svm_props) {
if ($self->rewrite_root) {
die "Can't have both 'useSvmProps' and 'rewriteRoot' ",
@@ -2142,23 +2450,28 @@ sub make_log_entry {
remove_username($full_url);
$log_entry{metadata} = "$full_url\@$r $uuid";
$log_entry{svm_revision} = $r;
- $email ||= "$author\@$uuid"
+ $email ||= "$author\@$uuid";
+ $commit_email ||= "$author\@$uuid";
} elsif ($self->use_svnsync_props) {
my $full_url = $self->svnsync->{url};
$full_url .= "/$self->{path}" if length $self->{path};
remove_username($full_url);
my $uuid = $self->svnsync->{uuid};
$log_entry{metadata} = "$full_url\@$rev $uuid";
- $email ||= "$author\@$uuid"
+ $email ||= "$author\@$uuid";
+ $commit_email ||= "$author\@$uuid";
} else {
my $url = $self->metadata_url;
remove_username($url);
$log_entry{metadata} = "$url\@$rev " .
$self->ra->get_uuid;
$email ||= "$author\@" . $self->ra->get_uuid;
+ $commit_email ||= "$author\@" . $self->ra->get_uuid;
}
$log_entry{name} = $name;
$log_entry{email} = $email;
+ $log_entry{commit_name} = $commit_name;
+ $log_entry{commit_email} = $commit_email;
\%log_entry;
}
@@ -2194,25 +2507,44 @@ sub set_tree {
}
}
+sub rebuild_from_rev_db {
+ my ($self, $path) = @_;
+ my $r = -1;
+ open my $fh, '<', $path or croak "open: $!";
+ while (<$fh>) {
+ length($_) == 41 or croak "inconsistent size in ($_) != 41";
+ chomp($_);
+ ++$r;
+ next if $_ eq ('0' x 40);
+ $self->rev_map_set($r, $_);
+ print "r$r = $_\n";
+ }
+ close $fh or croak "close: $!";
+ unlink $path or croak "unlink: $!";
+}
+
sub rebuild {
my ($self) = @_;
- my $db_path = $self->db_path;
- return if (-e $db_path && ! -z $db_path);
+ my $map_path = $self->map_path;
+ return if (-e $map_path && ! -z $map_path);
return unless ::verify_ref($self->refname.'^0');
- if (-f $self->{db_root}) {
- rename $self->{db_root}, $db_path or die
- "rename $self->{db_root} => $db_path failed: $!\n";
- my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#);
- symlink $base, $self->{db_root} or die
- "symlink $base => $self->{db_root} failed: $!\n";
+ if ($self->use_svm_props || $self->no_metadata) {
+ my $rev_db = $self->rev_db_path;
+ $self->rebuild_from_rev_db($rev_db);
+ if ($self->use_svm_props) {
+ my $svm_rev_db = $self->rev_db_path($self->svm_uuid);
+ $self->rebuild_from_rev_db($svm_rev_db);
+ }
+ $self->unlink_rev_db_symlink;
return;
}
- print "Rebuilding $db_path ...\n";
- my ($log, $ctx) = command_output_pipe("log", '--no-color', $self->refname);
- my $latest;
+ print "Rebuilding $map_path ...\n";
+ my ($log, $ctx) =
+ command_output_pipe(qw/rev-list --pretty=raw --no-color --reverse/,
+ $self->refname, '--');
my $full_url = $self->full_url;
remove_username($full_url);
- my $svn_uuid;
+ my $svn_uuid = $self->ra_uuid;
my $c;
while (<$log>) {
if ( m{^commit ($::sha1)$} ) {
@@ -2228,46 +2560,85 @@ sub rebuild {
# if we merged or otherwise started elsewhere, this is
# how we break out of it
- if ((defined $svn_uuid && ($uuid ne $svn_uuid)) ||
+ if (($uuid ne $svn_uuid) ||
($full_url && $url && ($url ne $full_url))) {
next;
}
- $latest ||= $rev;
- $svn_uuid ||= $uuid;
- $self->rev_db_set($rev, $c);
+ $self->rev_map_set($rev, $c);
print "r$rev = $c\n";
}
command_close_pipe($log, $ctx);
- print "Done rebuilding $db_path\n";
+ print "Done rebuilding $map_path\n";
+ my $rev_db_path = $self->rev_db_path;
+ if (-f $self->rev_db_path) {
+ unlink $self->rev_db_path or croak "unlink: $!";
+ }
+ $self->unlink_rev_db_symlink;
}
-# rev_db:
+# rev_map:
# Tie::File seems to be prone to offset errors if revisions get sparse,
# it's not that fast, either. Tie::File is also not in Perl 5.6. So
# one of my favorite modules is out :< Next up would be one of the DBM
-# modules, but I'm not sure which is most portable... So I'll just
-# go with something that's plain-text, but still capable of
-# being randomly accessed. So here's my ultra-simple fixed-width
-# database. All records are 40 characters + "\n", so it's easy to seek
-# to a revision: (41 * rev) is the byte offset.
-# A record of 40 0s denotes an empty revision.
-# And yes, it's still pretty fast (faster than Tie::File).
+# modules, but I'm not sure which is most portable...
+#
+# This is the replacement for the rev_db format, which was too big
+# and inefficient for large repositories with a lot of sparse history
+# (mainly tags)
+#
+# The format is this:
+# - 24 bytes for every record,
+# * 4 bytes for the integer representing an SVN revision number
+# * 20 bytes representing the sha1 of a git commit
+# - No empty padding records like the old format
+# (except the last record, which can be overwritten)
+# - new records are written append-only since SVN revision numbers
+# increase monotonically
+# - lookups on SVN revision number are done via a binary search
+# - Piping the file to xxd -c24 is a good way of dumping it for
+# viewing or editing (piped back through xxd -r), should the need
+# ever arise.
+# - The last record can be padding revision with an all-zero sha1
+# This is used to optimize fetch performance when using multiple
+# "fetch" directives in .git/config
+#
# These files are disposable unless noMetadata or useSvmProps is set
-sub _rev_db_set {
+sub _rev_map_set {
my ($fh, $rev, $commit) = @_;
- my $offset = $rev * 41;
- # assume that append is the common case:
- seek $fh, 0, 2 or croak $!;
- my $pos = tell $fh;
- if ($pos < $offset) {
- for (1 .. (($offset - $pos) / 41)) {
- print $fh (('0' x 40),"\n") or croak $!;
+
+ my $size = (stat($fh))[7];
+ ($size % 24) == 0 or croak "inconsistent size: $size";
+
+ my $wr_offset = 0;
+ if ($size > 0) {
+ sysseek($fh, -24, SEEK_END) or croak "seek: $!";
+ my $read = sysread($fh, my $buf, 24) or croak "read: $!";
+ $read == 24 or croak "read only $read bytes (!= 24)";
+ my ($last_rev, $last_commit) = unpack(rev_map_fmt, $buf);
+ if ($last_commit eq ('0' x40)) {
+ if ($size >= 48) {
+ sysseek($fh, -48, SEEK_END) or croak "seek: $!";
+ $read = sysread($fh, $buf, 24) or
+ croak "read: $!";
+ $read == 24 or
+ croak "read only $read bytes (!= 24)";
+ ($last_rev, $last_commit) =
+ unpack(rev_map_fmt, $buf);
+ if ($last_commit eq ('0' x40)) {
+ croak "inconsistent .rev_map\n";
+ }
+ }
+ if ($last_rev >= $rev) {
+ croak "last_rev is higher!: $last_rev >= $rev";
+ }
+ $wr_offset = -24;
}
}
- seek $fh, $offset, 0 or croak $!;
- print $fh $commit,"\n" or croak $!;
+ sysseek($fh, $wr_offset, SEEK_END) or croak "seek: $!";
+ syswrite($fh, pack(rev_map_fmt, $rev, $commit), 24) == 24 or
+ croak "write: $!";
}
sub mkfile {
@@ -2280,10 +2651,10 @@ sub mkfile {
}
}
-sub rev_db_set {
+sub rev_map_set {
my ($self, $rev, $commit, $update_ref, $uuid) = @_;
length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
- my $db = $self->db_path($uuid);
+ my $db = $self->map_path($uuid);
my $db_lock = "$db.lock";
my $sig;
if ($update_ref) {
@@ -2298,16 +2669,18 @@ sub rev_db_set {
# and we can't afford to lose it because rebuild() won't work
if ($self->use_svm_props || $self->no_metadata) {
$sync = 1;
- copy($db, $db_lock) or die "rev_db_set(@_): ",
+ copy($db, $db_lock) or die "rev_map_set(@_): ",
"Failed to copy: ",
"$db => $db_lock ($!)\n";
} else {
- rename $db, $db_lock or die "rev_db_set(@_): ",
+ rename $db, $db_lock or die "rev_map_set(@_): ",
"Failed to rename: ",
"$db => $db_lock ($!)\n";
}
- open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n";
- _rev_db_set($fh, $rev, $commit);
+
+ sysopen(my $fh, $db_lock, O_RDWR | O_CREAT)
+ or croak "Couldn't open $db_lock: $!\n";
+ _rev_map_set($fh, $rev, $commit);
if ($sync) {
$fh->flush or die "Couldn't flush $db_lock: $!\n";
$fh->sync or die "Couldn't sync $db_lock: $!\n";
@@ -2318,7 +2691,7 @@ sub rev_db_set {
command_noisy('update-ref', '-m', "r$rev",
$self->refname, $commit);
}
- rename $db_lock, $db or die "rev_db_set(@_): ", "Failed to rename: ",
+ rename $db_lock, $db or die "rev_map_set(@_): ", "Failed to rename: ",
"$db_lock => $db ($!)\n";
delete $LOCKFILES{$db_lock};
if ($update_ref) {
@@ -2328,36 +2701,88 @@ sub rev_db_set {
}
}
-sub rev_db_max {
- my ($self) = @_;
+# If want_commit, this will return an array of (rev, commit) where
+# commit _must_ be a valid commit in the archive.
+# Otherwise, it'll return the max revision (whether or not the
+# commit is valid or just a 0x40 placeholder).
+sub rev_map_max {
+ my ($self, $want_commit) = @_;
$self->rebuild;
- my $db_path = $self->db_path;
- my @stat = stat $db_path or return 0;
- ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n";
- my $max = $stat[7] / 41;
- (($max > 0) ? $max - 1 : 0);
+ my $map_path = $self->map_path;
+ stat $map_path or return $want_commit ? (0, undef) : 0;
+ sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!";
+ my $size = (stat($fh))[7];
+ ($size % 24) == 0 or croak "inconsistent size: $size";
+
+ if ($size == 0) {
+ close $fh or croak "close: $!";
+ return $want_commit ? (0, undef) : 0;
+ }
+
+ sysseek($fh, -24, SEEK_END) or croak "seek: $!";
+ sysread($fh, my $buf, 24) == 24 or croak "read: $!";
+ my ($r, $c) = unpack(rev_map_fmt, $buf);
+ if ($want_commit && $c eq ('0' x40)) {
+ if ($size < 48) {
+ return $want_commit ? (0, undef) : 0;
+ }
+ sysseek($fh, -48, SEEK_END) or croak "seek: $!";
+ sysread($fh, $buf, 24) == 24 or croak "read: $!";
+ ($r, $c) = unpack(rev_map_fmt, $buf);
+ if ($c eq ('0'x40)) {
+ croak "Penultimate record is all-zeroes in $map_path";
+ }
+ }
+ close $fh or croak "close: $!";
+ $want_commit ? ($r, $c) : $r;
}
-sub rev_db_get {
+sub rev_map_get {
my ($self, $rev, $uuid) = @_;
- my $ret;
- my $offset = $rev * 41;
- my $db_path = $self->db_path($uuid);
- return undef unless -e $db_path;
- open my $fh, '<', $db_path or croak $!;
- if (sysseek($fh, $offset, 0) == $offset) {
- my $read = sysread($fh, $ret, 40);
- $ret = undef if ($read != 40 || $ret eq ('0'x40));
+ my $map_path = $self->map_path($uuid);
+ return undef unless -e $map_path;
+
+ sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!";
+ my $size = (stat($fh))[7];
+ ($size % 24) == 0 or croak "inconsistent size: $size";
+
+ if ($size == 0) {
+ close $fh or croak "close: $fh";
+ return undef;
+ }
+
+ my ($l, $u) = (0, $size - 24);
+ my ($r, $c, $buf);
+
+ while ($l <= $u) {
+ my $i = int(($l/24 + $u/24) / 2) * 24;
+ sysseek($fh, $i, SEEK_SET) or croak "seek: $!";
+ sysread($fh, my $buf, 24) == 24 or croak "read: $!";
+ my ($r, $c) = unpack('NH40', $buf);
+
+ if ($r < $rev) {
+ $l = $i + 24;
+ } elsif ($r > $rev) {
+ $u = $i - 24;
+ } else { # $r == $rev
+ close($fh) or croak "close: $!";
+ return $c eq ('0' x 40) ? undef : $c;
+ }
}
- close $fh or croak $!;
- $ret;
+ close($fh) or croak "close: $!";
+ undef;
}
+# Finds the first svn revision that exists on (if $eq_ok is true) or
+# before $rev for the current branch. It will not search any lower
+# than $min_rev. Returns the git commit hash and svn revision number
+# if found, else (undef, undef).
sub find_rev_before {
- my ($self, $rev, $eq_ok) = @_;
+ my ($self, $rev, $eq_ok, $min_rev) = @_;
--$rev unless $eq_ok;
- while ($rev > 0) {
- if (my $c = $self->rev_db_get($rev)) {
+ $min_rev ||= 1;
+ while ($rev >= $min_rev) {
+ if (my $c = $self->rev_map_get($rev)) {
return ($rev, $c);
}
--$rev;
@@ -2365,6 +2790,23 @@ sub find_rev_before {
return (undef, undef);
}
+# Finds the first svn revision that exists on (if $eq_ok is true) or
+# after $rev for the current branch. It will not search any higher
+# than $max_rev. Returns the git commit hash and svn revision number
+# if found, else (undef, undef).
+sub find_rev_after {
+ my ($self, $rev, $eq_ok, $max_rev) = @_;
+ ++$rev unless $eq_ok;
+ $max_rev ||= $self->rev_map_max;
+ while ($rev <= $max_rev) {
+ if (my $c = $self->rev_map_get($rev)) {
+ return ($rev, $c);
+ }
+ ++$rev;
+ }
+ return (undef, undef);
+}
+
sub _new {
my ($class, $repo_id, $ref_id, $path) = @_;
unless (defined $repo_id && length $repo_id) {
@@ -2380,13 +2822,32 @@ sub _new {
bless {
ref_id => $ref_id, dir => $dir, index => "$dir/index",
path => $path, config => "$ENV{GIT_DIR}/svn/config",
- db_root => "$dir/.rev_db", repo_id => $repo_id }, $class;
+ map_root => "$dir/.rev_map", repo_id => $repo_id }, $class;
+}
+
+# for read-only access of old .rev_db formats
+sub unlink_rev_db_symlink {
+ my ($self) = @_;
+ my $link = $self->rev_db_path;
+ $link =~ s/\.[\w-]+$// or croak "missing UUID at the end of $link";
+ if (-l $link) {
+ unlink $link or croak "unlink: $link failed!";
+ }
}
-sub db_path {
+sub rev_db_path {
+ my ($self, $uuid) = @_;
+ my $db_path = $self->map_path($uuid);
+ $db_path =~ s{/\.rev_map\.}{/\.rev_db\.}
+ or croak "map_path: $db_path does not contain '/.rev_map.' !";
+ $db_path;
+}
+
+# the new replacement for .rev_db
+sub map_path {
my ($self, $uuid) = @_;
$uuid ||= $self->ra_uuid;
- "$self->{db_root}.$uuid";
+ "$self->{map_root}.$uuid";
}
sub uri_encode {
@@ -2542,7 +3003,6 @@ use strict;
use warnings;
use Carp qw/croak/;
use IO::File qw//;
-use Digest::MD5;
# file baton members: path, mode_a, mode_b, pool, fh, blob, base
sub new {
@@ -2631,6 +3091,20 @@ sub add_file {
sub add_directory {
my ($self, $path, $cp_path, $cp_rev) = @_;
+ my $gpath = $self->git_path($path);
+ if ($gpath eq '') {
+ my ($ls, $ctx) = command_output_pipe(qw/ls-tree
+ -r --name-only -z/,
+ $self->{c});
+ local $/ = "\0";
+ while (<$ls>) {
+ chomp;
+ $self->{gii}->remove($_);
+ print "\tD\t$_\n" unless $::_q;
+ }
+ command_close_pipe($ls, $ctx);
+ $self->{empty}->{$path} = 0;
+ }
my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
delete $self->{empty}->{$dir};
$self->{empty}->{$path} = 1;
@@ -2694,9 +3168,7 @@ sub apply_textdelta {
if (defined $exp) {
seek $base, 0, 0 or croak $!;
- my $md5 = Digest::MD5->new;
- $md5->addfile($base);
- my $got = $md5->hexdigest;
+ my $got = ::md5sum($base);
die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
"expected: $exp\n",
" got: $got\n" if ($got ne $exp);
@@ -2715,9 +3187,7 @@ sub close_file {
if (my $fh = $fb->{fh}) {
if (defined $exp) {
seek($fh, 0, 0) or croak $!;
- my $md5 = Digest::MD5->new;
- $md5->addfile($fh);
- my $got = $md5->hexdigest;
+ my $got = ::md5sum($fh);
if ($got ne $exp) {
die "Checksum mismatch: $path\n",
"expected: $exp\n got: $got\n";
@@ -2725,9 +3195,15 @@ sub close_file {
}
sysseek($fh, 0, 0) or croak $!;
if ($fb->{mode_b} == 120000) {
- sysread($fh, my $buf, 5) == 5 or croak $!;
- $buf eq 'link ' or die "$path has mode 120000",
- "but is not a link\n";
+ eval {
+ sysread($fh, my $buf, 5) == 5 or croak $!;
+ $buf eq 'link ' or die "$path has mode 120000",
+ " but is not a link";
+ };
+ if ($@) {
+ warn "$@\n";
+ sysseek($fh, 0, 0) or croak $!;
+ }
}
defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n";
if (!$pid) {
@@ -2769,7 +3245,6 @@ use strict;
use warnings;
use Carp qw/croak/;
use IO::File;
-use Digest::MD5;
sub new {
my ($class, $opts) = @_;
@@ -3073,11 +3548,9 @@ sub chg_file {
$fh->flush == 0 or croak $!;
seek $fh, 0, 0 or croak $!;
- my $md5 = Digest::MD5->new;
- $md5->addfile($fh) or croak $!;
+ my $exp = ::md5sum($fh);
seek $fh, 0, 0 or croak $!;
- my $exp = $md5->hexdigest;
my $pool = SVN::Pool->new;
my $atd = $self->apply_textdelta($fbat, undef, $pool);
my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
@@ -3180,6 +3653,25 @@ sub _auth_providers () {
]
}
+sub escape_uri_only {
+ my ($uri) = @_;
+ my @tmp;
+ foreach (split m{/}, $uri) {
+ s/([^\w.-])/sprintf("%%%02X",ord($1))/eg;
+ push @tmp, $_;
+ }
+ join('/', @tmp);
+}
+
+sub escape_url {
+ my ($url) = @_;
+ if ($url =~ m#^(https?)://([^/]+)(.*)$#) {
+ my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
+ $url = "$scheme://$domain$uri";
+ }
+ $url;
+}
+
sub new {
my ($class, $url) = @_;
$url =~ s!/+$!!;
@@ -3212,10 +3704,11 @@ sub new {
$Git::SVN::Prompt::_no_auth_cache = 1;
}
} # no warnings 'once'
- my $self = SVN::Ra->new(url => $url, auth => $baton,
+ my $self = SVN::Ra->new(url => escape_url($url), auth => $baton,
config => $config,
pool => SVN::Pool->new,
auth_provider_callbacks => $callbacks);
+ $self->{url} = $url;
$self->{svn_path} = $url;
$self->{repos_root} = $self->get_repos_root;
$self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##;
@@ -3341,7 +3834,7 @@ sub gs_do_switch {
my $full_url = $self->{url};
my $old_url = $full_url;
- $full_url .= "/$path" if length $path;
+ $full_url .= '/' . escape_uri_only($path) if length $path;
my ($ra, $reparented);
if ($old_url ne $full_url) {
if ($old_url !~ m#^svn(\+ssh)?://#) {
@@ -3458,7 +3951,7 @@ sub gs_fetch_loop_common {
foreach my $gs ($self->match_globs(\%exists, $paths,
$globs, $r)) {
- if ($gs->rev_db_max >= $r) {
+ if ($gs->rev_map_max >= $r) {
next;
}
next unless $gs->match_paths($paths, $r);
@@ -3470,6 +3963,7 @@ sub gs_fetch_loop_common {
if ($log_entry) {
$gs->do_git_commit($log_entry);
}
+ $INDEX_FILES{$gs->{index}} = 1;
}
foreach my $g (@$globs) {
my $k = "svn-remote.$g->{remote}." .
@@ -3487,8 +3981,9 @@ sub gs_fetch_loop_common {
# pre-fill the .rev_db since it'll eventually get filled in
# with '0' x40 if something new gets committed
foreach my $gs (@$gsv) {
- next if defined $gs->rev_db_get($max);
- $gs->rev_db_set($max, 0 x40);
+ next if $gs->rev_map_max >= $max;
+ next if defined $gs->rev_map_get($max);
+ $gs->rev_map_set($max, 0 x40);
}
foreach my $g (@$globs) {
my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev";
@@ -3499,6 +3994,7 @@ sub gs_fetch_loop_common {
$max += $inc;
$max = $head if ($max > $head);
}
+ Git::SVN::gc();
}
sub match_globs {
@@ -3606,6 +4102,10 @@ sub skip_unknown_revs {
warn "W: Ignoring error from SVN, path probably ",
"does not exist: ($errno): ",
$err->expanded_message,"\n";
+ warn "W: Do not be alarmed at the above message ",
+ "git-svn is just searching aggressively for ",
+ "old history.\n",
+ "This may take a while on large repositories\n";
$ignored_err{$err_key} = 1;
}
return;
@@ -3635,6 +4135,7 @@ package Git::SVN::Log;
use strict;
use warnings;
use POSIX qw/strftime/;
+use constant commit_log_separator => ('-' x 72) . "\n";
use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
%rusers $show_commit $incremental/;
my $l_fmt;
@@ -3663,39 +4164,7 @@ sub cmt_showable {
}
sub log_use_color {
- return 1 if $color;
- my ($dc, $dcvar);
- $dcvar = 'color.diff';
- $dc = `git-config --get $dcvar`;
- if ($dc eq '') {
- # nothing at all; fallback to "diff.color"
- $dcvar = 'diff.color';
- $dc = `git-config --get $dcvar`;
- }
- chomp($dc);
- if ($dc eq 'auto') {
- my $pc;
- $pc = `git-config --get color.pager`;
- if ($pc eq '') {
- # does not have it -- fallback to pager.color
- $pc = `git-config --bool --get pager.color`;
- }
- else {
- $pc = `git-config --bool --get color.pager`;
- if ($?) {
- $pc = 'false';
- }
- }
- chomp($pc);
- if (-t *STDOUT || (defined $pager && $pc eq 'true')) {
- return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
- }
- return 0;
- }
- return 0 if $dc eq 'never';
- return 1 if $dc eq 'always';
- chomp($dc = `git-config --bool --get $dcvar`);
- return ($dc eq 'true');
+ return $color || Git->repository->get_colorbool('color.diff');
}
sub git_svn_log_cmd {
@@ -3724,23 +4193,23 @@ sub git_svn_log_cmd {
push @cmd, @log_opts;
if (defined $r_max && $r_max == $r_min) {
push @cmd, '--max-count=1';
- if (my $c = $gs->rev_db_get($r_max)) {
+ if (my $c = $gs->rev_map_get($r_max)) {
push @cmd, $c;
}
} elsif (defined $r_max) {
- my ($c_min, $c_max);
- $c_max = $gs->rev_db_get($r_max);
- $c_min = $gs->rev_db_get($r_min);
- if (defined $c_min && defined $c_max) {
- if ($r_max > $r_max) {
- push @cmd, "$c_min..$c_max";
- } else {
- push @cmd, "$c_max..$c_min";
- }
- } elsif ($r_max > $r_min) {
- push @cmd, $c_max;
+ if ($r_max < $r_min) {
+ ($r_min, $r_max) = ($r_max, $r_min);
+ }
+ my (undef, $c_max) = $gs->find_rev_before($r_max, 1, $r_min);
+ my (undef, $c_min) = $gs->find_rev_after($r_min, 1, $r_max);
+ # If there are no commits in the range, both $c_max and $c_min
+ # will be undefined. If there is at least 1 commit in the
+ # range, both will be defined.
+ return () if !defined $c_min || !defined $c_max;
+ if ($c_min eq $c_max) {
+ push @cmd, '--max-count=1', $c_min;
} else {
- push @cmd, $c_min;
+ push @cmd, '--boundary', "$c_min..$c_max";
}
}
return (@cmd, @files);
@@ -3754,6 +4223,7 @@ sub config_pager {
} elsif (length $pager == 0 || $pager eq 'cat') {
$pager = undef;
}
+ $ENV{GIT_PAGER_IN_USE} = defined($pager);
}
sub run_pager {
@@ -3770,6 +4240,29 @@ sub run_pager {
exec $pager or ::fatal "Can't run pager: $! ($pager)";
}
+sub format_svn_date {
+ return strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", localtime(shift));
+}
+
+sub parse_git_date {
+ my ($t, $tz) = @_;
+ # Date::Parse isn't in the standard Perl distro :(
+ if ($tz =~ s/^\+//) {
+ $t += tz_to_s_offset($tz);
+ } elsif ($tz =~ s/^\-//) {
+ $t -= tz_to_s_offset($tz);
+ }
+ return $t;
+}
+
+sub set_local_timezone {
+ if (defined $TZ) {
+ $ENV{TZ} = $TZ;
+ } else {
+ delete $ENV{TZ};
+ }
+}
+
sub tz_to_s_offset {
my ($tz) = @_;
$tz =~ s/(\d\d)$//;
@@ -3790,13 +4283,7 @@ sub get_author_info {
$dest->{t} = $t;
$dest->{tz} = $tz;
$dest->{a} = $au;
- # Date::Parse isn't in the standard Perl distro :(
- if ($tz =~ s/^\+//) {
- $t += tz_to_s_offset($tz);
- } elsif ($tz =~ s/^\-//) {
- $t -= tz_to_s_offset($tz);
- }
- $dest->{t_utc} = $t;
+ $dest->{t_utc} = parse_git_date($t, $tz);
}
sub process_commit {
@@ -3848,10 +4335,9 @@ sub show_commit_changed_paths {
sub show_commit_normal {
my ($c) = @_;
- print '-' x72, "\nr$c->{r} | ";
+ print commit_log_separator, "r$c->{r} | ";
print "$c->{c} | " if $show_commit;
- print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
- localtime($c->{t_utc})), ' | ';
+ print "$c->{a} | ", format_svn_date($c->{t_utc}), ' | ';
my $nr_line = 0;
if (my $l = $c->{l}) {
@@ -3891,11 +4377,7 @@ sub cmd_show_log {
my (@args) = @_;
my ($r_min, $r_max);
my $r_last = -1; # prevent dupes
- if (defined $TZ) {
- $ENV{TZ} = $TZ;
- } else {
- delete $ENV{TZ};
- }
+ set_local_timezone();
if (defined $::_revision) {
if ($::_revision =~ /^(\d+):(\d+)$/) {
($r_min, $r_max) = ($1, $2);
@@ -3909,12 +4391,16 @@ sub cmd_show_log {
config_pager();
@args = git_svn_log_cmd($r_min, $r_max, @args);
+ if (!@args) {
+ print commit_log_separator unless $incremental || $oneline;
+ return;
+ }
my $log = command_output_pipe(@args);
run_pager();
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};
@@ -3957,14 +4443,30 @@ sub cmd_show_log {
process_commit($c, $r_min, $r_max, \@k);
}
if (@k) {
- my $swap = $r_max;
- $r_max = $r_min;
- $r_min = $swap;
+ ($r_min, $r_max) = ($r_max, $r_min);
process_commit($_, $r_min, $r_max) foreach reverse @k;
}
out:
close $log;
- print '-' x72,"\n" unless $incremental || $oneline;
+ print commit_log_separator unless $incremental || $oneline;
+}
+
+sub cmd_blame {
+ my $path = shift;
+
+ config_pager();
+ run_pager();
+
+ my ($fh, $ctx) = command_output_pipe('blame', @_, $path);
+ while (my $line = <$fh>) {
+ if ($line =~ /^\^?([[:xdigit:]]+)\s/) {
+ my (undef, $rev, undef) = ::cmt_metadata($1);
+ $rev = sprintf('%-10s', $rev);
+ $line =~ s/^\^?[[:xdigit:]]+(\s)/$rev$1/;
+ }
+ print $line;
+ }
+ command_close_pipe($fh, $ctx);
}
package Git::SVN::Migration;
@@ -3991,6 +4493,16 @@ package Git::SVN::Migration;
# --use-separate-remotes option in git-clone (now default)
# - we do not automatically migrate to this (following
# the example set by core git)
+#
+# v5 layout: .rev_db.$UUID => .rev_map.$UUID
+# - newer, more-efficient format that uses 24-bytes per record
+# with no filler space.
+# - use xxd -c24 < .rev_map.$UUID to view and debug
+# - This is a one-way migration, repositories updated to the
+# new format will not be able to use old git-svn without
+# rebuilding the .rev_db. Rebuilding the rev_db is not
+# possible if noMetadata or useSvmProps are set; but should
+# be no problem for users that use the (sensible) defaults.
use strict;
use warnings;
use Carp qw/croak/;
diff --git a/git-web--browse.sh b/git-web--browse.sh
new file mode 100755
index 0000000000..1023b90859
--- /dev/null
+++ b/git-web--browse.sh
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# This program launch a web browser on the html page
+# describing a git command.
+#
+# Copyright (c) 2007 Christian Couder
+# Copyright (c) 2006 Theodore Y. Ts'o
+#
+# This file is heavily stolen from git-mergetool.sh, by
+# Theodore Y. Ts'o (thanks) that is:
+#
+# Copyright (c) 2006 Theodore Y. Ts'o
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Junio C Hamano or any other official
+# git maintainer.
+#
+
+USAGE='[--browser=browser|--tool=browser] [--config=conf.var] url/file ...'
+
+# This must be capable of running outside of git directory, so
+# the vanilla git-sh-setup should not be used.
+NONGIT_OK=Yes
+. git-sh-setup
+
+valid_tool() {
+ case "$1" in
+ firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open)
+ ;; # happy
+ *)
+ return 1
+ ;;
+ esac
+}
+
+init_browser_path() {
+ browser_path=$(git config "browser.$1.path")
+ test -z "$browser_path" && browser_path="$1"
+}
+
+while test $# != 0
+do
+ case "$1" in
+ -b|--browser*|-t|--tool*)
+ case "$#,$1" in
+ *,*=*)
+ browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
+ 1,*)
+ usage ;;
+ *)
+ browser="$2"
+ shift ;;
+ esac
+ ;;
+ -c|--config*)
+ case "$#,$1" in
+ *,*=*)
+ conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
+ 1,*)
+ usage ;;
+ *)
+ conf="$2"
+ shift ;;
+ esac
+ ;;
+ --)
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ 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
+fi
+
+if test -z "$browser" ; then
+ if test -n "$DISPLAY"; then
+ browser_candidates="firefox iceweasel konqueror w3m links lynx dillo"
+ if test "$KDE_FULL_SESSION" = "true"; then
+ browser_candidates="konqueror $browser_candidates"
+ fi
+ else
+ browser_candidates="w3m links lynx"
+ fi
+ # SECURITYSESSIONID indicates an OS X GUI login session
+ if test -n "$SECURITYSESSIONID"; then
+ browser_candidates="open $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'."
+
+ init_browser_path "$browser"
+
+ if ! 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)
+ # 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 "$@" &
+ ;;
+ konqueror)
+ case "$(basename "$browser_path")" in
+ 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)
+ eval "$browser_path" newTab "$@"
+ ;;
+ *)
+ "$browser_path" "$@" &
+ ;;
+ esac
+ ;;
+ w3m|links|lynx|open)
+ eval "$browser_path" "$@"
+ ;;
+ dillo)
+ "$browser_path" "$@" &
+ ;;
+esac
diff --git a/git.c b/git.c
index 9eaca1d671..0cb86884d7 100644
--- a/git.c
+++ b/git.c
@@ -6,28 +6,6 @@
const char git_usage_string[] =
"git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
-static void prepend_to_path(const char *dir, int len)
-{
- const char *old_path = getenv("PATH");
- char *path;
- int path_len = len;
-
- if (!old_path)
- old_path = "/usr/local/bin:/usr/bin:/bin";
-
- path_len = len + strlen(old_path) + 1;
-
- path = xmalloc(path_len + 1);
-
- memcpy(path, dir, len);
- path[len] = ':';
- memcpy(path + len + 1, old_path, path_len - len);
-
- setenv("PATH", path, 1);
-
- free(path);
-}
-
static int handle_options(const char*** argv, int* argc, int* envchanged)
{
int handled = 0;
@@ -51,7 +29,7 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
if (!prefixcmp(cmd, "--exec-path")) {
cmd += 11;
if (*cmd == '=')
- git_set_exec_path(cmd + 1);
+ git_set_argv_exec_path(cmd + 1);
else {
puts(git_exec_path());
exit(0);
@@ -115,6 +93,8 @@ static char *alias_string;
static int git_alias_config(const char *var, const char *value)
{
if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) {
+ if (!value)
+ return config_error_nonbool(var);
alias_string = xstrdup(value);
}
return 0;
@@ -191,7 +171,7 @@ static int handle_alias(int *argcp, const char ***argv)
strbuf_init(&buf, PATH_MAX);
strbuf_addstr(&buf, alias_string);
- sq_quote_argv(&buf, (*argv) + 1, *argcp - 1, PATH_MAX);
+ sq_quote_argv(&buf, (*argv) + 1, PATH_MAX);
free(alias_string);
alias_string = buf.buf;
}
@@ -220,7 +200,7 @@ static int handle_alias(int *argcp, const char ***argv)
if (!strcmp(alias_command, new_argv[0]))
die("recursive alias: %s", alias_command);
- trace_argv_printf(new_argv, count,
+ trace_argv_printf(new_argv,
"trace: alias expansion: %s =>",
alias_command);
@@ -271,19 +251,14 @@ static int run_command(struct cmd_struct *p, int argc, const char **argv)
prefix = setup_git_directory();
if (p->option & USE_PAGER)
setup_pager();
- if (p->option & NEED_WORK_TREE) {
- const char *work_tree = get_git_work_tree();
- const char *git_dir = get_git_dir();
- if (!is_absolute_path(git_dir))
- set_git_dir(make_absolute_path(git_dir));
- if (!work_tree || chdir(work_tree))
- die("%s must be run in a work tree", p->cmd);
- }
- trace_argv_printf(argv, argc, "trace: built-in: git");
+ if (p->option & NEED_WORK_TREE)
+ setup_work_tree();
+
+ trace_argv_printf(argv, "trace: built-in: git");
status = p->fn(argc, argv, prefix);
if (status)
- return status;
+ return status & 0xff;
/* Somebody closed stdout? */
if (fstat(fileno(stdout), &st))
@@ -320,6 +295,8 @@ static void handle_internal_command(int argc, const char **argv)
{ "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
{ "cherry", cmd_cherry, RUN_SETUP },
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
+ { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
+ { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config },
{ "count-objects", cmd_count_objects, RUN_SETUP },
@@ -328,6 +305,9 @@ static void handle_internal_command(int argc, const char **argv)
{ "diff-files", cmd_diff_files },
{ "diff-index", cmd_diff_index, RUN_SETUP },
{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+ { "fast-export", cmd_fast_export, RUN_SETUP },
+ { "fetch", cmd_fetch, RUN_SETUP },
+ { "fetch-pack", cmd_fetch_pack, RUN_SETUP },
{ "fetch--tool", cmd_fetch__tool, RUN_SETUP },
{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
{ "for-each-ref", cmd_for_each_ref, RUN_SETUP },
@@ -338,18 +318,24 @@ static void handle_internal_command(int argc, const char **argv)
{ "get-tar-commit-id", cmd_get_tar_commit_id },
{ "grep", cmd_grep, RUN_SETUP | USE_PAGER },
{ "help", cmd_help },
+#ifndef NO_CURL
+ { "http-fetch", cmd_http_fetch, RUN_SETUP },
+#endif
{ "init", cmd_init_db },
{ "init-db", cmd_init_db },
{ "log", cmd_log, RUN_SETUP | USE_PAGER },
{ "ls-files", cmd_ls_files, RUN_SETUP },
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
+ { "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
+ { "merge-ours", cmd_merge_ours, RUN_SETUP },
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
+ { "peek-remote", cmd_ls_remote },
{ "pickaxe", cmd_blame, RUN_SETUP },
{ "prune", cmd_prune, RUN_SETUP },
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
@@ -360,13 +346,14 @@ static void handle_internal_command(int argc, const char **argv)
{ "rerere", cmd_rerere, RUN_SETUP },
{ "reset", cmd_reset, RUN_SETUP },
{ "rev-list", cmd_rev_list, RUN_SETUP },
- { "rev-parse", cmd_rev_parse, RUN_SETUP },
+ { "rev-parse", cmd_rev_parse },
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
- { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
- { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
+ { "rm", cmd_rm, RUN_SETUP },
+ { "send-pack", cmd_send_pack, RUN_SETUP },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP | USE_PAGER },
+ { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
{ "stripspace", cmd_stripspace },
{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
{ "tag", cmd_tag, RUN_SETUP },
@@ -403,19 +390,17 @@ int main(int argc, const char **argv)
{
const char *cmd = argv[0] ? argv[0] : "git-help";
char *slash = strrchr(cmd, '/');
- const char *exec_path = NULL;
+ const char *cmd_path = NULL;
int done_alias = 0;
/*
* Take the basename of argv[0] as the command
* name, and the dirname as the default exec_path
- * if it's an absolute path and we don't have
- * anything better.
+ * if we don't have anything better.
*/
if (slash) {
*slash++ = 0;
- if (*cmd == '/')
- exec_path = cmd;
+ cmd_path = cmd;
cmd = slash;
}
@@ -444,23 +429,20 @@ int main(int argc, const char **argv)
if (!prefixcmp(argv[0], "--"))
argv[0] += 2;
} else {
- /* Default command: "help" */
- argv[0] = "help";
- argc = 1;
+ /* The user didn't specify a command; give them help */
+ printf("usage: %s\n\n", git_usage_string);
+ list_common_cmds_help();
+ exit(1);
}
cmd = argv[0];
/*
- * We execute external git command via execv_git_cmd(),
- * which looks at "--exec-path" option, GIT_EXEC_PATH
- * environment, and $(gitexecdir) in Makefile while built,
- * in this order. For scripted commands, we prepend
- * the value of the exec_path variable to the PATH.
+ * We use PATH to find git commands, but we prepend some higher
+ * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH
+ * environment, and the $(gitexecdir) from the Makefile at build
+ * time.
*/
- if (exec_path)
- prepend_to_path(exec_path, strlen(exec_path));
- exec_path = git_exec_path();
- prepend_to_path(exec_path, strlen(exec_path));
+ setup_path(cmd_path);
while (1) {
/* See if it's an internal command */
diff --git a/git.spec.in b/git.spec.in
index bdb293d990..97a26be29a 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -1,104 +1,116 @@
# Pass --without docs to rpmbuild if you don't want the documentation
-%define python_path /usr/bin/python
-
Name: git
Version: @@VERSION@@
Release: 1%{?dist}
-Summary: Git core and tools
+Summary: Core git tools
License: GPL
Group: Development/Tools
URL: http://kernel.org/pub/software/scm/git/
Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
-BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
+BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel, gettext %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, perl-Git
+
+Requires: perl-Git = %{version}-%{release}
+Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat
+Provides: git-core = %{version}-%{release}
+Obsoletes: git-core <= 1.5.4.2
+Obsoletes: git-p4
%description
Git is a fast, scalable, distributed revision control system with an
unusually rich command set that provides both high-level operations
and full access to internals.
-This is a dummy package which brings in all subpackages.
+The git rpm installs the core tools with minimal dependencies. To
+install all git packages, including tools for integrating with other
+SCMs, install the git-all meta-package.
-%package core
-Summary: Core git tools
+%package all
+Summary: Meta-package to pull in all git tools
Group: Development/Tools
-Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat
-Obsoletes: git-p4
-%description core
+Requires: git = %{version}-%{release}
+Requires: git-svn = %{version}-%{release}
+Requires: git-cvs = %{version}-%{release}
+Requires: git-arch = %{version}-%{release}
+Requires: git-email = %{version}-%{release}
+Requires: gitk = %{version}-%{release}
+Requires: git-gui = %{version}-%{release}
+Obsoletes: git <= 1.5.4.2
+
+%description all
Git is a fast, scalable, distributed revision control system with an
unusually rich command set that provides both high-level operations
and full access to internals.
-These are the core tools with minimal dependencies.
+This is a dummy package which brings in all subpackages.
%package svn
Summary: Git tools for importing Subversion repositories
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, subversion
+Requires: git = %{version}-%{release}, subversion
%description svn
Git tools for importing Subversion repositories.
%package cvs
Summary: Git tools for importing CVS repositories
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, cvs, cvsps
+Requires: git = %{version}-%{release}, cvs, cvsps
%description cvs
Git tools for importing CVS repositories.
%package arch
Summary: Git tools for importing Arch repositories
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, tla
+Requires: git = %{version}-%{release}, tla
%description arch
Git tools for importing Arch repositories.
%package email
Summary: Git tools for sending email
Group: Development/Tools
-Requires: git-core = %{version}-%{release}
+Requires: git = %{version}-%{release}
%description email
Git tools for sending email.
%package gui
Summary: Git GUI tool
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, tk >= 8.4
+Requires: git = %{version}-%{release}, tk >= 8.4
%description gui
Git GUI tool
%package -n gitk
Summary: Git revision tree visualiser ('gitk')
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, tk >= 8.4
+Requires: git = %{version}-%{release}, tk >= 8.4
%description -n gitk
Git revision tree visualiser ('gitk')
%package -n perl-Git
Summary: Perl interface to Git
Group: Development/Libraries
-Requires: git-core = %{version}-%{release}
+Requires: git = %{version}-%{release}
Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
BuildRequires: perl(Error)
%description -n perl-Git
Perl interface to Git
+%define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-core-%{version}
+
%prep
%setup -q
%build
make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" \
- ETC_GITCONFIG=/etc/gitconfig \
- prefix=%{_prefix} all %{!?_without_docs: doc}
+ %{path_settings} \
+ all %{!?_without_docs: doc}
%install
rm -rf $RPM_BUILD_ROOT
make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \
- prefix=%{_prefix} mandir=%{_mandir} \
- ETC_GITCONFIG=/etc/gitconfig \
- PYTHON_PATH=%{python_path} \
+ %{path_settings} \
INSTALLDIRS=vendor install %{!?_without_docs: install-doc}
find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
@@ -115,8 +127,12 @@ rm -rf $RPM_BUILD_ROOT%{_mandir}
%clean
rm -rf $RPM_BUILD_ROOT
-%files
-# These are no files in the root package
+%files -f bin-man-doc-files
+%defattr(-,root,root)
+%{_datadir}/git-core/
+%doc README COPYING Documentation/*.txt
+%{!?_without_docs: %doc Documentation/*.html Documentation/howto}
+%{!?_without_docs: %doc Documentation/technical}
%files svn
%defattr(-,root,root)
@@ -160,20 +176,32 @@ rm -rf $RPM_BUILD_ROOT
%defattr(-,root,root)
%doc Documentation/*gitk*.txt
%{_bindir}/*gitk*
+%{_datadir}/gitk/
%{!?_without_docs: %{_mandir}/man1/*gitk*.1*}
%{!?_without_docs: %doc Documentation/*gitk*.html }
%files -n perl-Git -f perl-files
%defattr(-,root,root)
-%files core -f bin-man-doc-files
-%defattr(-,root,root)
-%{_datadir}/git-core/
-%doc README COPYING Documentation/*.txt
-%{!?_without_docs: %doc Documentation/*.html Documentation/howto}
-%{!?_without_docs: %doc Documentation/technical}
+%files all
+# No files for you!
%changelog
+* Fri Feb 15 2008 Kristian Høgsberg <krh@redhat.com>
+- Rename git-core to just git and rename meta package from git to git-all.
+
+* Sun Feb 03 2008 James Bowes <jbowes@dangerouslyinc.com>
+- Add a BuildRequires for gettext
+
+* Fri Jan 11 2008 Junio C Hamano <gitster@pobox.com>
+- Include gitk message files
+
+* Sun Jan 06 2008 James Bowes <jbowes@dangerouslyinc.com>
+- Make the metapackage require the same version of the subpackages.
+
+* Wed Dec 12 2007 Junio C Hamano <gitster@pobox.com>
+- Adjust htmldir to point at /usr/share/doc/git-core-$version/
+
* Sun Jul 15 2007 Sean Estabrooks <seanlkml@sympatico.ca>
- Removed p4import.
diff --git a/gitk-git/Makefile b/gitk-git/Makefile
new file mode 100644
index 0000000000..ae2b80b108
--- /dev/null
+++ b/gitk-git/Makefile
@@ -0,0 +1,63 @@
+# The default target of this Makefile is...
+all::
+
+prefix ?= $(HOME)
+bindir ?= $(prefix)/bin
+sharedir ?= $(prefix)/share
+gitk_libdir ?= $(sharedir)/gitk/lib
+msgsdir ?= $(gitk_libdir)/msgs
+msgsdir_SQ = $(subst ','\'',$(msgsdir))
+
+TCLTK_PATH ?= wish
+INSTALL ?= install
+RM ?= rm -f
+
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+bindir_SQ = $(subst ','\'',$(bindir))
+TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
+
+## po-file creation rules
+XGETTEXT ?= xgettext
+ifdef NO_MSGFMT
+ MSGFMT ?= $(TCL_PATH) po/po2msg.sh
+else
+ MSGFMT ?= msgfmt
+endif
+
+PO_TEMPLATE = po/gitk.pot
+ALL_POFILES = $(wildcard po/*.po)
+ALL_MSGFILES = $(subst .po,.msg,$(ALL_POFILES))
+
+ifndef V
+ QUIET = @
+ QUIET_GEN = $(QUIET)echo ' ' GEN $@ &&
+endif
+
+all:: gitk-wish $(ALL_MSGFILES)
+
+install:: all
+ $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
+ $(INSTALL) -d '$(DESTDIR_SQ)$(msgsdir_SQ)'
+ $(foreach p,$(ALL_MSGFILES), $(INSTALL) $p '$(DESTDIR_SQ)$(msgsdir_SQ)' &&) true
+
+uninstall::
+ $(foreach p,$(ALL_MSGFILES), $(RM) '$(DESTDIR_SQ)$(msgsdir_SQ)'/$(notdir $p) &&) true
+ $(RM) '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
+
+clean::
+ $(RM) gitk-wish po/*.msg
+
+gitk-wish: gitk
+ $(QUIET_GEN)$(RM) $@ $@+ && \
+ sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
+ chmod +x $@+ && \
+ mv -f $@+ $@
+
+$(PO_TEMPLATE): gitk
+ $(XGETTEXT) -kmc -LTcl -o $@ gitk
+update-po:: $(PO_TEMPLATE)
+ $(foreach p, $(ALL_POFILES), echo Updating $p ; msgmerge -U $p $(PO_TEMPLATE) ; )
+$(ALL_MSGFILES): %.msg : %.po
+ @echo Generating catalog $@
+ $(MSGFMT) --statistics --tcl $< -l $(basename $(notdir $<)) -d $(dir $@)
+
diff --git a/gitk b/gitk-git/gitk
index 300fdceb35..f1f21e97bf 100755..100644
--- a/gitk
+++ b/gitk-git/gitk
@@ -82,31 +82,41 @@ proc dorunq {} {
proc start_rev_list {view} {
global startmsecs
global commfd leftover tclencoding datemode
- global viewargs viewfiles commitidx
- global lookingforhead showlocalchanges
+ global viewargs viewfiles commitidx viewcomplete vnextroot
+ global showlocalchanges commitinterest mainheadid
+ global progressdirn progresscoords proglastnc curview
set startmsecs [clock clicks -milliseconds]
set commitidx($view) 0
+ set viewcomplete($view) 0
+ set vnextroot($view) 0
set order "--topo-order"
if {$datemode} {
set order "--date-order"
}
if {[catch {
- set fd [open [concat | git log -z --pretty=raw $order --parents \
+ set fd [open [concat | git log --no-color -z --pretty=raw $order --parents \
--boundary $viewargs($view) "--" $viewfiles($view)] r]
} err]} {
- error_popup "Error executing git rev-list: $err"
+ error_popup "[mc "Error executing git rev-list:"] $err"
exit 1
}
set commfd($view) $fd
set leftover($view) {}
- set lookingforhead $showlocalchanges
+ if {$showlocalchanges} {
+ lappend commitinterest($mainheadid) {dodiffindex}
+ }
fconfigure $fd -blocking 0 -translation lf -eofchar {}
if {$tclencoding != {}} {
fconfigure $fd -encoding $tclencoding
}
filerun $fd [list getcommitlines $fd $view]
- nowbusy $view
+ nowbusy $view [mc "Reading"]
+ if {$view == $curview} {
+ set progressdirn 1
+ set progresscoords {0 0}
+ set proglastnc 0
+ }
}
proc stop_rev_list {} {
@@ -123,20 +133,34 @@ proc stop_rev_list {} {
}
proc getcommits {} {
- global phase canv mainfont curview
+ global phase canv curview
set phase getcommits
initlayout
start_rev_list $curview
- show_status "Reading commits..."
+ show_status [mc "Reading commits..."]
+}
+
+# This makes a string representation of a positive integer which
+# sorts as a string in numerical order
+proc strrep {n} {
+ if {$n < 16} {
+ return [format "%x" $n]
+ } elseif {$n < 256} {
+ return [format "x%.2x" $n]
+ } elseif {$n < 65536} {
+ return [format "y%.4x" $n]
+ }
+ return [format "z%.8x" $n]
}
proc getcommitlines {fd view} {
- global commitlisted
+ global commitlisted commitinterest
global leftover commfd
- global displayorder commitidx commitrow commitdata
+ global displayorder commitidx viewcomplete commitrow commitdata
global parentlist children curview hlview
global vparentlist vdisporder vcmitlisted
+ global ordertok vnextroot idpending
set stuff [read $fd 500000]
# git log doesn't terminate the last commit with a null...
@@ -147,9 +171,29 @@ proc getcommitlines {fd view} {
if {![eof $fd]} {
return 1
}
- global viewname
+ # Check if we have seen any ids listed as parents that haven't
+ # appeared in the list
+ foreach vid [array names idpending "$view,*"] {
+ # should only get here if git log is buggy
+ set id [lindex [split $vid ","] 1]
+ set commitrow($vid) $commitidx($view)
+ incr commitidx($view)
+ if {$view == $curview} {
+ lappend parentlist {}
+ lappend displayorder $id
+ lappend commitlisted 0
+ } else {
+ lappend vparentlist($view) {}
+ lappend vdisporder($view) $id
+ lappend vcmitlisted($view) 0
+ }
+ }
+ set viewcomplete($view) 1
+ global viewname progresscoords
unset commfd($view)
notbusy $view
+ set progresscoords {0 0}
+ adjustprogress
# set it blocking so we wait for the process to terminate
fconfigure $fd -blocking 1
if {[catch {close $fd} err]} {
@@ -196,11 +240,12 @@ proc getcommitlines {fd view} {
set listed 1
if {$j >= 0 && [string match "commit *" $cmit]} {
set ids [string range $cmit 7 [expr {$j - 1}]]
- if {[string match {[-<>]*} $ids]} {
+ if {[string match {[-^<>]*} $ids]} {
switch -- [string index $ids 0] {
"-" {set listed 0}
- "<" {set listed 2}
- ">" {set listed 3}
+ "^" {set listed 2}
+ "<" {set listed 3}
+ ">" {set listed 4}
}
set ids [string range $ids 1 end]
}
@@ -217,18 +262,39 @@ proc getcommitlines {fd view} {
if {[string length $shortcmit] > 80} {
set shortcmit "[string range $shortcmit 0 80]..."
}
- error_popup "Can't parse git log output: {$shortcmit}"
+ error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
exit 1
}
set id [lindex $ids 0]
+ if {![info exists ordertok($view,$id)]} {
+ set otok "o[strrep $vnextroot($view)]"
+ incr vnextroot($view)
+ set ordertok($view,$id) $otok
+ } else {
+ set otok $ordertok($view,$id)
+ unset idpending($view,$id)
+ }
if {$listed} {
set olds [lrange $ids 1 end]
- set i 0
- foreach p $olds {
- if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
- lappend children($view,$p) $id
+ if {[llength $olds] == 1} {
+ set p [lindex $olds 0]
+ lappend children($view,$p) $id
+ if {![info exists ordertok($view,$p)]} {
+ set ordertok($view,$p) $ordertok($view,$id)
+ set idpending($view,$p) 1
+ }
+ } else {
+ set i 0
+ foreach p $olds {
+ if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
+ lappend children($view,$p) $id
+ }
+ if {![info exists ordertok($view,$p)]} {
+ set ordertok($view,$p) "$otok[strrep $i]]"
+ set idpending($view,$p) 1
+ }
+ incr i
}
- incr i
}
} else {
set olds {}
@@ -248,24 +314,54 @@ proc getcommitlines {fd view} {
lappend vdisporder($view) $id
lappend vcmitlisted($view) $listed
}
+ if {[info exists commitinterest($id)]} {
+ foreach script $commitinterest($id) {
+ eval [string map [list "%I" $id] $script]
+ }
+ unset commitinterest($id)
+ }
set gotsome 1
}
if {$gotsome} {
run chewcommits $view
+ if {$view == $curview} {
+ # update progress bar
+ global progressdirn progresscoords proglastnc
+ set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}]
+ set proglastnc $commitidx($view)
+ set l [lindex $progresscoords 0]
+ set r [lindex $progresscoords 1]
+ if {$progressdirn} {
+ set r [expr {$r + $inc}]
+ if {$r >= 1.0} {
+ set r 1.0
+ set progressdirn 0
+ }
+ if {$r > 0.2} {
+ set l [expr {$r - 0.2}]
+ }
+ } else {
+ set l [expr {$l - $inc}]
+ if {$l <= 0.0} {
+ set l 0.0
+ set progressdirn 1
+ }
+ set r [expr {$l + 0.2}]
+ }
+ set progresscoords [list $l $r]
+ adjustprogress
+ }
}
return 2
}
proc chewcommits {view} {
- global curview hlview commfd
+ global curview hlview viewcomplete
global selectedline pending_select
- set more 0
if {$view == $curview} {
- set allread [expr {![info exists commfd($view)]}]
- set tlimit [expr {[clock clicks -milliseconds] + 50}]
- set more [layoutmore $tlimit $allread]
- if {$allread && !$more} {
+ layoutmore
+ if {$viewcomplete($view)} {
global displayorder commitidx phase
global numcommits startmsecs
@@ -277,7 +373,7 @@ proc chewcommits {view} {
#set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
#puts "overall $ms ms for $numcommits commits"
} else {
- show_status "No commits selected"
+ show_status [mc "No commits selected"]
}
notbusy layout
set phase {}
@@ -286,7 +382,7 @@ proc chewcommits {view} {
if {[info exists hlview] && $view == $hlview} {
vhighlightmore
}
- return $more
+ return 0
}
proc readcommit {id} {
@@ -295,7 +391,7 @@ proc readcommit {id} {
}
proc updatecommits {} {
- global viewdata curview phase displayorder
+ global viewdata curview phase displayorder ordertok idpending
global children commitrow selectedline thickerline showneartags
if {$phase ne {}} {
@@ -306,6 +402,10 @@ proc updatecommits {} {
foreach id $displayorder {
catch {unset children($n,$id)}
catch {unset commitrow($n,$id)}
+ catch {unset ordertok($n,$id)}
+ }
+ foreach vid [array names idpending "$n,*"] {
+ unset idpending($vid)
}
set curview -1
catch {unset selectedline}
@@ -384,7 +484,7 @@ proc getcommit {id} {
} else {
readcommit $id
if {![info exists commitinfo($id)]} {
- set commitinfo($id) {"No commit information available"}
+ set commitinfo($id) [list [mc "No commit information available"]]
}
}
return 1
@@ -483,7 +583,7 @@ proc removehead {id name} {
proc show_error {w top msg} {
message $w.m -text $msg -justify center -aspect 400
pack $w.m -side top -fill x -padx 20 -pady 20
- button $w.ok -text OK -command "destroy $top"
+ button $w.ok -text [mc OK] -command "destroy $top"
pack $w.ok -side bottom -fill x
bind $top <Visibility> "grab $top; focus $top"
bind $top <Key-Return> "destroy $top"
@@ -505,57 +605,71 @@ proc confirm_popup msg {
wm transient $w .
message $w.m -text $msg -justify center -aspect 400
pack $w.m -side top -fill x -padx 20 -pady 20
- button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+ button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
pack $w.ok -side left -fill x
- button $w.cancel -text Cancel -command "destroy $w"
+ button $w.cancel -text [mc Cancel] -command "destroy $w"
pack $w.cancel -side right -fill x
bind $w <Visibility> "grab $w; focus $w"
tkwait window $w
return $confirm_ok
}
+proc setoptions {} {
+ option add *Panedwindow.showHandle 1 startupFile
+ option add *Panedwindow.sashRelief raised startupFile
+ option add *Button.font uifont startupFile
+ option add *Checkbutton.font uifont startupFile
+ option add *Radiobutton.font uifont startupFile
+ option add *Menu.font uifont startupFile
+ option add *Menubutton.font uifont startupFile
+ option add *Label.font uifont startupFile
+ option add *Message.font uifont startupFile
+ option add *Entry.font uifont startupFile
+}
+
proc makewindow {} {
global canv canv2 canv3 linespc charspc ctext cflist
- global textfont mainfont uifont tabstop
+ global tabstop
global findtype findtypemenu findloc findstring fstring geometry
global entries sha1entry sha1string sha1but
global diffcontextstring diffcontext
+ global ignorespace
global maincursor textcursor curtextcursor
global rowctxmenu fakerowmenu mergemax wrapcomment
global highlight_files gdttype
global searchstring sstring
global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
- global headctxmenu
+ global headctxmenu progresscanv progressitem progresscoords statusw
+ global fprogitem fprogcoord lastprogupdate progupdatepending
+ global rprogitem rprogcoord
+ global have_tk85
menu .bar
- .bar add cascade -label "File" -menu .bar.file
- .bar configure -font $uifont
+ .bar add cascade -label [mc "File"] -menu .bar.file
menu .bar.file
- .bar.file add command -label "Update" -command updatecommits
- .bar.file add command -label "Reread references" -command rereadrefs
- .bar.file add command -label "List references" -command showrefs
- .bar.file add command -label "Quit" -command doquit
- .bar.file configure -font $uifont
+ .bar.file add command -label [mc "Update"] -command updatecommits
+ .bar.file add command -label [mc "Reread references"] -command rereadrefs
+ .bar.file add command -label [mc "List references"] -command showrefs
+ .bar.file add command -label [mc "Quit"] -command doquit
menu .bar.edit
- .bar add cascade -label "Edit" -menu .bar.edit
- .bar.edit add command -label "Preferences" -command doprefs
- .bar.edit configure -font $uifont
-
- menu .bar.view -font $uifont
- .bar add cascade -label "View" -menu .bar.view
- .bar.view add command -label "New view..." -command {newview 0}
- .bar.view add command -label "Edit view..." -command editview \
+ .bar add cascade -label [mc "Edit"] -menu .bar.edit
+ .bar.edit add command -label [mc "Preferences"] -command doprefs
+
+ menu .bar.view
+ .bar add cascade -label [mc "View"] -menu .bar.view
+ .bar.view add command -label [mc "New view..."] -command {newview 0}
+ .bar.view add command -label [mc "Edit view..."] -command editview \
-state disabled
- .bar.view add command -label "Delete view" -command delview -state disabled
+ .bar.view add command -label [mc "Delete view"] -command delview -state disabled
.bar.view add separator
- .bar.view add radiobutton -label "All files" -command {showview 0} \
+ .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
-variable selectedview -value 0
menu .bar.help
- .bar add cascade -label "Help" -menu .bar.help
- .bar.help add command -label "About gitk" -command about
- .bar.help add command -label "Key bindings" -command keys
- .bar.help configure -font $uifont
+ .bar add cascade -label [mc "Help"] -menu .bar.help
+ .bar.help add command -label [mc "About gitk"] -command about
+ .bar.help add command -label [mc "Key bindings"] -command keys
+ .bar.help configure
. configure -menu .bar
# the gui has upper and lower half, parts of a paned window.
@@ -611,11 +725,11 @@ proc makewindow {} {
set sha1entry .tf.bar.sha1
set entries $sha1entry
set sha1but .tf.bar.sha1label
- button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
- -command gotocommit -width 8 -font $uifont
+ button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
+ -command gotocommit -width 8
$sha1but conf -disabledforeground [$sha1but cget -foreground]
pack .tf.bar.sha1label -side left
- entry $sha1entry -width 40 -font $textfont -textvariable sha1string
+ entry $sha1entry -width 40 -font textfont -textvariable sha1string
trace add variable sha1string write sha1change
pack $sha1entry -side left -pady 2
@@ -642,62 +756,55 @@ proc makewindow {} {
-state disabled -width 26
pack .tf.bar.rightbut -side left -fill y
- button .tf.bar.findbut -text "Find" -command dofind -font $uifont
- pack .tf.bar.findbut -side left
+ # Status label and progress bar
+ set statusw .tf.bar.status
+ label $statusw -width 15 -relief sunken
+ pack $statusw -side left -padx 5
+ set h [expr {[font metrics uifont -linespace] + 2}]
+ set progresscanv .tf.bar.progress
+ canvas $progresscanv -relief sunken -height $h -borderwidth 2
+ set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
+ set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
+ set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
+ pack $progresscanv -side right -expand 1 -fill x
+ set progresscoords {0 0}
+ set fprogcoord 0
+ set rprogcoord 0
+ bind $progresscanv <Configure> adjustprogress
+ set lastprogupdate [clock clicks -milliseconds]
+ set progupdatepending 0
+
+ # build up the bottom bar of upper window
+ label .tf.lbar.flabel -text "[mc "Find"] "
+ button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
+ button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
+ label .tf.lbar.flab2 -text " [mc "commit"] "
+ pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
+ -side left -fill y
+ set gdttype [mc "containing:"]
+ set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
+ [mc "containing:"] \
+ [mc "touching paths:"] \
+ [mc "adding/removing string:"]]
+ trace add variable gdttype write gdttype_change
+ pack .tf.lbar.gdttype -side left -fill y
+
set findstring {}
- set fstring .tf.bar.findstring
+ set fstring .tf.lbar.findstring
lappend entries $fstring
- entry $fstring -width 30 -font $textfont -textvariable findstring
+ entry $fstring -width 30 -font textfont -textvariable findstring
trace add variable findstring write find_change
- pack $fstring -side left -expand 1 -fill x -in .tf.bar
- set findtype Exact
- set findtypemenu [tk_optionMenu .tf.bar.findtype \
- findtype Exact IgnCase Regexp]
- trace add variable findtype write find_change
- .tf.bar.findtype configure -font $uifont
- .tf.bar.findtype.menu configure -font $uifont
- set findloc "All fields"
- tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
- Comments Author Committer
+ set findtype [mc "Exact"]
+ set findtypemenu [tk_optionMenu .tf.lbar.findtype \
+ findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
+ trace add variable findtype write findcom_change
+ set findloc [mc "All fields"]
+ tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
+ [mc "Comments"] [mc "Author"] [mc "Committer"]
trace add variable findloc write find_change
- .tf.bar.findloc configure -font $uifont
- .tf.bar.findloc.menu configure -font $uifont
- pack .tf.bar.findloc -side right
- pack .tf.bar.findtype -side right
-
- # build up the bottom bar of upper window
- label .tf.lbar.flabel -text "Highlight: Commits " \
- -font $uifont
- pack .tf.lbar.flabel -side left -fill y
- set gdttype "touching paths:"
- set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
- "adding/removing string:"]
- trace add variable gdttype write hfiles_change
- $gm conf -font $uifont
- .tf.lbar.gdttype conf -font $uifont
- pack .tf.lbar.gdttype -side left -fill y
- entry .tf.lbar.fent -width 25 -font $textfont \
- -textvariable highlight_files
- trace add variable highlight_files write hfiles_change
- lappend entries .tf.lbar.fent
- pack .tf.lbar.fent -side left -fill x -expand 1
- label .tf.lbar.vlabel -text " OR in view" -font $uifont
- pack .tf.lbar.vlabel -side left -fill y
- global viewhlmenu selectedhlview
- set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
- $viewhlmenu entryconf None -command delvhighlight
- $viewhlmenu conf -font $uifont
- .tf.lbar.vhl conf -font $uifont
- pack .tf.lbar.vhl -side left -fill y
- label .tf.lbar.rlabel -text " OR " -font $uifont
- pack .tf.lbar.rlabel -side left -fill y
- global highlight_related
- set m [tk_optionMenu .tf.lbar.relm highlight_related None \
- "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
- $m conf -font $uifont
- .tf.lbar.relm conf -font $uifont
- trace add variable highlight_related write vrel_change
- pack .tf.lbar.relm -side left -fill y
+ pack .tf.lbar.findloc -side right
+ pack .tf.lbar.findtype -side right
+ pack $fstring -side left -expand 1 -fill x
# Finish putting the upper half of the viewer together
pack .tf.lbar -in .tf -side bottom -fill x
@@ -721,24 +828,22 @@ proc makewindow {} {
frame .bleft.top
frame .bleft.mid
- button .bleft.top.search -text "Search" -command dosearch \
- -font $uifont
+ button .bleft.top.search -text [mc "Search"] -command dosearch
pack .bleft.top.search -side left -padx 5
set sstring .bleft.top.sstring
- entry $sstring -width 20 -font $textfont -textvariable searchstring
+ entry $sstring -width 20 -font textfont -textvariable searchstring
lappend entries $sstring
trace add variable searchstring write incrsearch
pack $sstring -side left -expand 1 -fill x
- radiobutton .bleft.mid.diff -text "Diff" \
+ radiobutton .bleft.mid.diff -text [mc "Diff"] \
-command changediffdisp -variable diffelide -value {0 0}
- radiobutton .bleft.mid.old -text "Old version" \
+ radiobutton .bleft.mid.old -text [mc "Old version"] \
-command changediffdisp -variable diffelide -value {0 1}
- radiobutton .bleft.mid.new -text "New version" \
+ radiobutton .bleft.mid.new -text [mc "New version"] \
-command changediffdisp -variable diffelide -value {1 0}
- label .bleft.mid.labeldiffcontext -text " Lines of context: " \
- -font $uifont
+ label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
- spinbox .bleft.mid.diffcontext -width 5 -font $textfont \
+ spinbox .bleft.mid.diffcontext -width 5 -font textfont \
-from 1 -increment 1 -to 10000000 \
-validate all -validatecommand "diffcontextvalidate %P" \
-textvariable diffcontextstring
@@ -746,11 +851,16 @@ proc makewindow {} {
trace add variable diffcontextstring write diffcontextchange
lappend entries .bleft.mid.diffcontext
pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
+ checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
+ -command changeignorespace -variable ignorespace
+ pack .bleft.mid.ignspace -side left -padx 5
set ctext .bleft.ctext
text $ctext -background $bgcolor -foreground $fgcolor \
- -tabs "[expr {$tabstop * $charspc}]" \
- -state disabled -font $textfont \
+ -state disabled -font textfont \
-yscrollcommand scrolltext -wrap none
+ if {$have_tk85} {
+ $ctext conf -tabstyle wordprocessor
+ }
scrollbar .bleft.sb -command "$ctext yview"
pack .bleft.top -side top -fill x
pack .bleft.mid -side top -fill x
@@ -760,7 +870,7 @@ proc makewindow {} {
lappend fglist $ctext
$ctext tag conf comment -wrap $wrapcomment
- $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
+ $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
$ctext tag conf hunksep -fore [lindex $diffcolors 2]
$ctext tag conf d0 -fore [lindex $diffcolors 0]
$ctext tag conf d1 -fore [lindex $diffcolors 1]
@@ -782,8 +892,8 @@ proc makewindow {} {
$ctext tag conf m15 -fore "#ff70b0"
$ctext tag conf mmax -fore darkgrey
set mergemax 16
- $ctext tag conf mresult -font [concat $textfont bold]
- $ctext tag conf msep -font [concat $textfont bold]
+ $ctext tag conf mresult -font textfontbold
+ $ctext tag conf msep -font textfontbold
$ctext tag conf found -back yellow
.pwbottom add .bleft
@@ -792,20 +902,18 @@ proc makewindow {} {
# lower right
frame .bright
frame .bright.mode
- radiobutton .bright.mode.patch -text "Patch" \
+ radiobutton .bright.mode.patch -text [mc "Patch"] \
-command reselectline -variable cmitmode -value "patch"
- .bright.mode.patch configure -font $uifont
- radiobutton .bright.mode.tree -text "Tree" \
+ radiobutton .bright.mode.tree -text [mc "Tree"] \
-command reselectline -variable cmitmode -value "tree"
- .bright.mode.tree configure -font $uifont
grid .bright.mode.patch .bright.mode.tree -sticky ew
pack .bright.mode -side top -fill x
set cflist .bright.cfiles
- set indent [font measure $mainfont "nn"]
+ set indent [font measure mainfont "nn"]
text $cflist \
-selectbackground $selectbgcolor \
-background $bgcolor -foreground $fgcolor \
- -font $mainfont \
+ -font mainfont \
-tabs [list $indent [expr {2 * $indent}]] \
-yscrollcommand ".bright.sb set" \
-cursor [. cget -cursor] \
@@ -817,7 +925,7 @@ proc makewindow {} {
pack $cflist -side left -fill both -expand 1
$cflist tag configure highlight \
-background [$cflist cget -selectbackground]
- $cflist tag configure bold -font [concat $mainfont bold]
+ $cflist tag configure bold -font mainfontbold
.pwbottom add .bright
.ctop add .pwbottom
@@ -843,6 +951,12 @@ proc makewindow {} {
} else {
bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
+ if {[tk windowingsystem] eq "aqua"} {
+ bindall <MouseWheel> {
+ set delta [expr {- (%D)}]
+ allcanvs yview scroll $delta units
+ }
+ }
}
bindall <2> "canvscan mark %W %x %y"
bindall <B2-Motion> "canvscan dragto %W %x %y"
@@ -850,8 +964,8 @@ proc makewindow {} {
bindkey <End> sellastline
bind . <Key-Up> "selnextline -1"
bind . <Key-Down> "selnextline 1"
- bind . <Shift-Key-Up> "next_highlight -1"
- bind . <Shift-Key-Down> "next_highlight 1"
+ bind . <Shift-Key-Up> "dofind -1 0"
+ bind . <Shift-Key-Down> "dofind 1 0"
bindkey <Key-Right> "goforw"
bindkey <Key-Left> "goback"
bind . <Key-Prior> "selnextpage -1"
@@ -876,23 +990,24 @@ proc makewindow {} {
bindkey b "$ctext yview scroll -1 pages"
bindkey d "$ctext yview scroll 18 units"
bindkey u "$ctext yview scroll -18 units"
- bindkey / {findnext 1}
- bindkey <Key-Return> {findnext 0}
- bindkey ? findprev
+ bindkey / {dofind 1 1}
+ bindkey <Key-Return> {dofind 1 1}
+ bindkey ? {dofind -1 1}
bindkey f nextfile
bindkey <F5> updatecommits
bind . <$M1B-q> doquit
- bind . <$M1B-f> dofind
- bind . <$M1B-g> {findnext 0}
+ bind . <$M1B-f> {dofind 1 1}
+ bind . <$M1B-g> {dofind 1 0}
bind . <$M1B-r> dosearchback
bind . <$M1B-s> dosearch
bind . <$M1B-equal> {incrfont 1}
+ bind . <$M1B-plus> {incrfont 1}
bind . <$M1B-KP_Add> {incrfont 1}
bind . <$M1B-minus> {incrfont -1}
bind . <$M1B-KP_Subtract> {incrfont -1}
wm protocol . WM_DELETE_WINDOW doquit
bind . <Button-1> "click %W"
- bind $fstring <Key-Return> dofind
+ bind $fstring <Key-Return> {dofind 1 1}
bind $sha1entry <Key-Return> gotocommit
bind $sha1entry <<PasteSelection>> clearsha1
bind $cflist <1> {sel_flist %W %x %y; break}
@@ -906,43 +1021,43 @@ proc makewindow {} {
set rowctxmenu .rowctxmenu
menu $rowctxmenu -tearoff 0
- $rowctxmenu add command -label "Diff this -> selected" \
+ $rowctxmenu add command -label [mc "Diff this -> selected"] \
-command {diffvssel 0}
- $rowctxmenu add command -label "Diff selected -> this" \
+ $rowctxmenu add command -label [mc "Diff selected -> this"] \
-command {diffvssel 1}
- $rowctxmenu add command -label "Make patch" -command mkpatch
- $rowctxmenu add command -label "Create tag" -command mktag
- $rowctxmenu add command -label "Write commit to file" -command writecommit
- $rowctxmenu add command -label "Create new branch" -command mkbranch
- $rowctxmenu add command -label "Cherry-pick this commit" \
+ $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
+ $rowctxmenu add command -label [mc "Create tag"] -command mktag
+ $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
+ $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
+ $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
-command cherrypick
- $rowctxmenu add command -label "Reset HEAD branch to here" \
+ $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
-command resethead
set fakerowmenu .fakerowmenu
menu $fakerowmenu -tearoff 0
- $fakerowmenu add command -label "Diff this -> selected" \
+ $fakerowmenu add command -label [mc "Diff this -> selected"] \
-command {diffvssel 0}
- $fakerowmenu add command -label "Diff selected -> this" \
+ $fakerowmenu add command -label [mc "Diff selected -> this"] \
-command {diffvssel 1}
- $fakerowmenu add command -label "Make patch" -command mkpatch
-# $fakerowmenu add command -label "Commit" -command {mkcommit 0}
-# $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
-# $fakerowmenu add command -label "Revert local changes" -command revertlocal
+ $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
+# $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
+# $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
+# $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
set headctxmenu .headctxmenu
menu $headctxmenu -tearoff 0
- $headctxmenu add command -label "Check out this branch" \
+ $headctxmenu add command -label [mc "Check out this branch"] \
-command cobranch
- $headctxmenu add command -label "Remove this branch" \
+ $headctxmenu add command -label [mc "Remove this branch"] \
-command rmbranch
global flist_menu
set flist_menu .flistctxmenu
menu $flist_menu -tearoff 0
- $flist_menu add command -label "Highlight this too" \
+ $flist_menu add command -label [mc "Highlight this too"] \
-command {flist_hl 0}
- $flist_menu add command -label "Highlight this only" \
+ $flist_menu add command -label [mc "Highlight this only"] \
-command {flist_hl 1}
}
@@ -1008,12 +1123,45 @@ proc click {w} {
focus .
}
+# Adjust the progress bar for a change in requested extent or canvas size
+proc adjustprogress {} {
+ global progresscanv progressitem progresscoords
+ global fprogitem fprogcoord lastprogupdate progupdatepending
+ global rprogitem rprogcoord
+
+ set w [expr {[winfo width $progresscanv] - 4}]
+ set x0 [expr {$w * [lindex $progresscoords 0]}]
+ set x1 [expr {$w * [lindex $progresscoords 1]}]
+ set h [winfo height $progresscanv]
+ $progresscanv coords $progressitem $x0 0 $x1 $h
+ $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
+ $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
+ set now [clock clicks -milliseconds]
+ if {$now >= $lastprogupdate + 100} {
+ set progupdatepending 0
+ update
+ } elseif {!$progupdatepending} {
+ set progupdatepending 1
+ after [expr {$lastprogupdate + 100 - $now}] doprogupdate
+ }
+}
+
+proc doprogupdate {} {
+ global lastprogupdate progupdatepending
+
+ if {$progupdatepending} {
+ set progupdatepending 0
+ set lastprogupdate [clock clicks -milliseconds]
+ update
+ }
+}
+
proc savestuff {w} {
- global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
+ global canv canv2 canv3 mainfont textfont uifont tabstop
global stuffsaved findmergefiles maxgraphpct
global maxwidth showneartags showlocalchanges
global viewname viewfiles viewargs viewperm nextviewnum
- global cmitmode wrapcomment datetimeformat
+ global cmitmode wrapcomment datetimeformat limitdiffs
global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
if {$stuffsaved} return
@@ -1032,6 +1180,7 @@ proc savestuff {w} {
puts $f [list set showneartags $showneartags]
puts $f [list set showlocalchanges $showlocalchanges]
puts $f [list set datetimeformat $datetimeformat]
+ puts $f [list set limitdiffs $limitdiffs]
puts $f [list set bgcolor $bgcolor]
puts $f [list set fgcolor $fgcolor]
puts $f [list set colors $colors]
@@ -1134,26 +1283,23 @@ proc about {} {
return
}
toplevel $w
- wm title $w "About gitk"
- message $w.m -text {
+ wm title $w [mc "About gitk"]
+ message $w.m -text [mc "
Gitk - a commit viewer for git
-Copyright © 2005-2006 Paul Mackerras
+Copyright © 2005-2006 Paul Mackerras
-Use and redistribute under the terms of the GNU General Public License} \
+Use and redistribute under the terms of the GNU General Public License"] \
-justify center -aspect 400 -border 2 -bg white -relief groove
pack $w.m -side top -fill x -padx 2 -pady 2
- $w.m configure -font $uifont
- button $w.ok -text Close -command "destroy $w" -default active
+ button $w.ok -text [mc "Close"] -command "destroy $w" -default active
pack $w.ok -side bottom
- $w.ok configure -font $uifont
bind $w <Visibility> "focus $w.ok"
bind $w <Key-Escape> "destroy $w"
bind $w <Key-Return> "destroy $w"
}
proc keys {} {
- global uifont
set w .keys
if {[winfo exists $w]} {
raise $w
@@ -1165,52 +1311,50 @@ proc keys {} {
set M1T Ctrl
}
toplevel $w
- wm title $w "Gitk key bindings"
+ wm title $w [mc "Gitk key bindings"]
message $w.m -text "
-Gitk key bindings:
-
-<$M1T-Q> Quit
-<Home> Move to first commit
-<End> Move to last commit
-<Up>, p, i Move up one commit
-<Down>, n, k Move down one commit
-<Left>, z, j Go back in history list
-<Right>, x, l Go forward in history list
-<PageUp> Move up one page in commit list
-<PageDown> Move down one page in commit list
-<$M1T-Home> Scroll to top of commit list
-<$M1T-End> Scroll to bottom of commit list
-<$M1T-Up> Scroll commit list up one line
-<$M1T-Down> Scroll commit list down one line
-<$M1T-PageUp> Scroll commit list up one page
-<$M1T-PageDown> Scroll commit list down one page
-<Shift-Up> Move to previous highlighted line
-<Shift-Down> Move to next highlighted line
-<Delete>, b Scroll diff view up one page
-<Backspace> Scroll diff view up one page
-<Space> Scroll diff view down one page
-u Scroll diff view up 18 lines
-d Scroll diff view down 18 lines
-<$M1T-F> Find
-<$M1T-G> Move to next find hit
-<Return> Move to next find hit
-/ Move to next find hit, or redo find
-? Move to previous find hit
-f Scroll diff view to next file
-<$M1T-S> Search for next hit in diff view
-<$M1T-R> Search for previous hit in diff view
-<$M1T-KP+> Increase font size
-<$M1T-plus> Increase font size
-<$M1T-KP-> Decrease font size
-<$M1T-minus> Decrease font size
-<F5> Update
+[mc "Gitk key bindings:"]
+
+[mc "<%s-Q> Quit" $M1T]
+[mc "<Home> Move to first commit"]
+[mc "<End> Move to last commit"]
+[mc "<Up>, p, i Move up one commit"]
+[mc "<Down>, n, k Move down one commit"]
+[mc "<Left>, z, j Go back in history list"]
+[mc "<Right>, x, l Go forward in history list"]
+[mc "<PageUp> Move up one page in commit list"]
+[mc "<PageDown> Move down one page in commit list"]
+[mc "<%s-Home> Scroll to top of commit list" $M1T]
+[mc "<%s-End> Scroll to bottom of commit list" $M1T]
+[mc "<%s-Up> Scroll commit list up one line" $M1T]
+[mc "<%s-Down> Scroll commit list down one line" $M1T]
+[mc "<%s-PageUp> Scroll commit list up one page" $M1T]
+[mc "<%s-PageDown> Scroll commit list down one page" $M1T]
+[mc "<Shift-Up> Find backwards (upwards, later commits)"]
+[mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
+[mc "<Delete>, b Scroll diff view up one page"]
+[mc "<Backspace> Scroll diff view up one page"]
+[mc "<Space> Scroll diff view down one page"]
+[mc "u Scroll diff view up 18 lines"]
+[mc "d Scroll diff view down 18 lines"]
+[mc "<%s-F> Find" $M1T]
+[mc "<%s-G> Move to next find hit" $M1T]
+[mc "<Return> Move to next find hit"]
+[mc "/ Move to next find hit, or redo find"]
+[mc "? Move to previous find hit"]
+[mc "f Scroll diff view to next file"]
+[mc "<%s-S> Search for next hit in diff view" $M1T]
+[mc "<%s-R> Search for previous hit in diff view" $M1T]
+[mc "<%s-KP+> Increase font size" $M1T]
+[mc "<%s-plus> Increase font size" $M1T]
+[mc "<%s-KP-> Decrease font size" $M1T]
+[mc "<%s-minus> Decrease font size" $M1T]
+[mc "<F5> Update"]
" \
-justify left -bg white -border 2 -relief groove
pack $w.m -side top -fill both -padx 2 -pady 2
- $w.m configure -font $uifont
- button $w.ok -text Close -command "destroy $w" -default active
+ button $w.ok -text [mc "Close"] -command "destroy $w" -default active
pack $w.ok -side bottom
- $w.ok configure -font $uifont
bind $w <Visibility> "focus $w.ok"
bind $w <Key-Escape> "destroy $w"
bind $w <Key-Return> "destroy $w"
@@ -1583,6 +1727,7 @@ proc pop_flist_menu {w X Y x y} {
global ctext cflist cmitmode flist_menu flist_menu_file
global treediffs diffids
+ stopfinding
set l [lindex [split [$w index "@$x,$y"] "."] 0]
if {$l <= 1} return
if {$cmitmode eq "tree"} {
@@ -1596,14 +1741,15 @@ proc pop_flist_menu {w X Y x y} {
}
proc flist_hl {only} {
- global flist_menu_file highlight_files
+ global flist_menu_file findstring gdttype
set x [shellquote $flist_menu_file]
- if {$only || $highlight_files eq {}} {
- set highlight_files $x
+ if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
+ set findstring $x
} else {
- append highlight_files " " $x
+ append findstring " " $x
}
+ set gdttype [mc "touching paths:"]
}
# Functions for adding and removing shell-type quoting
@@ -1703,7 +1849,7 @@ proc shellsplit {str} {
# Code to implement multiple views
proc newview {ishighlight} {
- global nextviewnum newviewname newviewperm uifont newishighlight
+ global nextviewnum newviewname newviewperm newishighlight
global newviewargs revtreeargs
set newishighlight $ishighlight
@@ -1715,7 +1861,7 @@ proc newview {ishighlight} {
set newviewname($nextviewnum) "View $nextviewnum"
set newviewperm($nextviewnum) 0
set newviewargs($nextviewnum) [shellarglist $revtreeargs]
- vieweditor $top $nextviewnum "Gitk view definition"
+ vieweditor $top $nextviewnum [mc "Gitk view definition"]
}
proc editview {} {
@@ -1735,27 +1881,26 @@ proc editview {} {
}
proc vieweditor {top n title} {
- global newviewname newviewperm viewfiles
- global uifont
+ global newviewname newviewperm viewfiles bgcolor
toplevel $top
wm title $top $title
- label $top.nl -text "Name" -font $uifont
- entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
+ label $top.nl -text [mc "Name"]
+ entry $top.name -width 20 -textvariable newviewname($n)
grid $top.nl $top.name -sticky w -pady 5
- checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
- -font $uifont
+ checkbutton $top.perm -text [mc "Remember this view"] \
+ -variable newviewperm($n)
grid $top.perm - -pady 5 -sticky w
- message $top.al -aspect 1000 -font $uifont \
- -text "Commits to include (arguments to git rev-list):"
+ message $top.al -aspect 1000 \
+ -text [mc "Commits to include (arguments to git rev-list):"]
grid $top.al - -sticky w -pady 5
entry $top.args -width 50 -textvariable newviewargs($n) \
- -background white -font $uifont
+ -background $bgcolor
grid $top.args - -sticky ew -padx 5
- message $top.l -aspect 1000 -font $uifont \
- -text "Enter files and directories to include, one per line:"
+ message $top.l -aspect 1000 \
+ -text [mc "Enter files and directories to include, one per line:"]
grid $top.l - -sticky w
- text $top.t -width 40 -height 10 -background white -font $uifont
+ text $top.t -width 40 -height 10 -background $bgcolor -font uifont
if {[info exists viewfiles($n)]} {
foreach f $viewfiles($n) {
$top.t insert end $f
@@ -1766,10 +1911,8 @@ proc vieweditor {top n title} {
}
grid $top.t - -sticky ew -padx 5
frame $top.buts
- button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
- -font $uifont
- button $top.buts.can -text "Cancel" -command [list destroy $top] \
- -font $uifont
+ button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
+ button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
grid $top.buts.ok $top.buts.can
grid columnconfigure $top.buts 0 -weight 1 -uniform a
grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -1788,10 +1931,10 @@ proc doviewmenu {m first cmd op argv} {
}
proc allviewmenus {n op args} {
- global viewhlmenu
+ # global viewhlmenu
doviewmenu .bar.view 5 [list showview $n] $op $args
- doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
+ # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
}
proc newviewok {top n} {
@@ -1802,7 +1945,7 @@ proc newviewok {top n} {
if {[catch {
set newargs [shellsplit $newviewargs($n)]
} err]} {
- error_popup "Error in commit selection arguments: $err"
+ error_popup "[mc "Error in commit selection arguments:"] $err"
wm raise $top
focus $top
return
@@ -1834,8 +1977,8 @@ proc newviewok {top n} {
set viewname($n) $newviewname($n)
doviewmenu .bar.view 5 [list showview $n] \
entryconf [list -label $viewname($n)]
- doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
- entryconf [list -label $viewname($n) -value $viewname($n)]
+ # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
+ # entryconf [list -label $viewname($n) -value $viewname($n)]
}
if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
set viewfiles($n) $files
@@ -1853,7 +1996,7 @@ proc delview {} {
if {$curview == 0} return
if {[info exists hlview] && $hlview == $curview} {
- set selectedhlview None
+ set selectedhlview [mc "None"]
unset hlview
}
allviewmenus $curview delete
@@ -1867,8 +2010,8 @@ proc addviewmenu {n} {
.bar.view add radiobutton -label $viewname($n) \
-command [list showview $n] -variable selectedview -value $n
- $viewhlmenu add radiobutton -label $viewname($n) \
- -command [list addvhighlight $n] -variable selectedhlview
+ #$viewhlmenu add radiobutton -label $viewname($n) \
+ # -command [list addvhighlight $n] -variable selectedhlview
}
proc flatten {var} {
@@ -1892,17 +2035,17 @@ proc unflatten {var l} {
proc showview {n} {
global curview viewdata viewfiles
- global displayorder parentlist rowidlist rowoffsets
+ global displayorder parentlist rowidlist rowisopt rowfinal
global colormap rowtextx commitrow nextcolor canvxmax
- global numcommits rowrangelist commitlisted idrowranges rowchk
+ global numcommits commitlisted
global selectedline currentid canv canvy0
global treediffs
global pending_select phase
- global commitidx rowlaidout rowoptim
+ global commitidx
global commfd
global selectedview selectfirst
global vparentlist vdisporder vcmitlisted
- global hlview selectedhlview
+ global hlview selectedhlview commitinterest
if {$n == $curview} return
set selid {}
@@ -1928,29 +2071,27 @@ proc showview {n} {
set vparentlist($curview) $parentlist
set vdisporder($curview) $displayorder
set vcmitlisted($curview) $commitlisted
- if {$phase ne {}} {
+ if {$phase ne {} ||
+ ![info exists viewdata($curview)] ||
+ [lindex $viewdata($curview) 0] ne {}} {
set viewdata($curview) \
- [list $phase $rowidlist $rowoffsets $rowrangelist \
- [flatten idrowranges] [flatten idinlist] \
- $rowlaidout $rowoptim $numcommits]
- } elseif {![info exists viewdata($curview)]
- || [lindex $viewdata($curview) 0] ne {}} {
- set viewdata($curview) \
- [list {} $rowidlist $rowoffsets $rowrangelist]
+ [list $phase $rowidlist $rowisopt $rowfinal]
}
}
catch {unset treediffs}
clear_display
if {[info exists hlview] && $hlview == $n} {
unset hlview
- set selectedhlview None
+ set selectedhlview [mc "None"]
}
+ catch {unset commitinterest}
set curview $n
set selectedview $n
- .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
- .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
+ .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
+ .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
+ run refill_reflist
if {![info exists viewdata($n)]} {
if {$selid ne {}} {
set pending_select $selid
@@ -1965,19 +2106,9 @@ proc showview {n} {
set parentlist $vparentlist($n)
set commitlisted $vcmitlisted($n)
set rowidlist [lindex $v 1]
- set rowoffsets [lindex $v 2]
- set rowrangelist [lindex $v 3]
- if {$phase eq {}} {
- set numcommits [llength $displayorder]
- catch {unset idrowranges}
- } else {
- unflatten idrowranges [lindex $v 4]
- unflatten idinlist [lindex $v 5]
- set rowlaidout [lindex $v 6]
- set rowoptim [lindex $v 7]
- set numcommits [lindex $v 8]
- catch {unset rowchk}
- }
+ set rowisopt [lindex $v 2]
+ set rowfinal [lindex $v 3]
+ set numcommits $commitidx($n)
catch {unset colormap}
catch {unset rowtextx}
@@ -2015,13 +2146,12 @@ proc showview {n} {
}
if {$phase ne {}} {
if {$phase eq "getcommits"} {
- show_status "Reading commits..."
+ show_status [mc "Reading commits..."]
}
run chewcommits $n
} elseif {$numcommits == 0} {
- show_status "No commits selected"
+ show_status [mc "No commits selected"]
}
- run refill_reflist
}
# Stuff relating to the highlighting facility
@@ -2073,12 +2203,12 @@ proc bolden_name {row font} {
}
proc unbolden {} {
- global mainfont boldrows
+ global boldrows
set stillbold {}
foreach row $boldrows {
if {![ishighlighted $row]} {
- bolden $row $mainfont
+ bolden $row mainfont
} else {
lappend stillbold $row
}
@@ -2094,7 +2224,7 @@ proc addvhighlight {n} {
}
set hlview $n
if {$n != $curview && ![info exists viewdata($n)]} {
- set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
+ set viewdata($n) [list getcommits {{}} 0 0 0]
set vparentlist($n) {}
set vdisporder($n) {}
set vcmitlisted($n) {}
@@ -2117,9 +2247,8 @@ proc delvhighlight {} {
proc vhighlightmore {} {
global hlview vhl_done commitidx vhighlights
- global displayorder vdisporder curview mainfont
+ global displayorder vdisporder curview
- set font [concat $mainfont bold]
set max $commitidx($hlview)
if {$hlview == $curview} {
set disp $displayorder
@@ -2135,7 +2264,7 @@ proc vhighlightmore {} {
set row $commitrow($curview,$id)
if {$r0 <= $row && $row <= $r1} {
if {![highlighted $row]} {
- bolden $row $font
+ bolden $row mainfontbold
}
set vhighlights($row) 1
}
@@ -2145,11 +2274,11 @@ proc vhighlightmore {} {
}
proc askvhighlight {row id} {
- global hlview vhighlights commitrow iddrawn mainfont
+ global hlview vhighlights commitrow iddrawn
if {[info exists commitrow($hlview,$id)]} {
if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
- bolden $row [concat $mainfont bold]
+ bolden $row mainfontbold
}
set vhighlights($row) 1
} else {
@@ -2157,9 +2286,9 @@ proc askvhighlight {row id} {
}
}
-proc hfiles_change {name ix op} {
+proc hfiles_change {} {
global highlight_files filehighlight fhighlights fh_serial
- global mainfont highlight_paths
+ global highlight_paths gdttype
if {[info exists filehighlight]} {
# delete previous highlights
@@ -2177,6 +2306,69 @@ proc hfiles_change {name ix op} {
}
}
+proc gdttype_change {name ix op} {
+ global gdttype highlight_files findstring findpattern
+
+ stopfinding
+ if {$findstring ne {}} {
+ if {$gdttype eq [mc "containing:"]} {
+ if {$highlight_files ne {}} {
+ set highlight_files {}
+ hfiles_change
+ }
+ findcom_change
+ } else {
+ if {$findpattern ne {}} {
+ set findpattern {}
+ findcom_change
+ }
+ set highlight_files $findstring
+ hfiles_change
+ }
+ drawvisible
+ }
+ # enable/disable findtype/findloc menus too
+}
+
+proc find_change {name ix op} {
+ global gdttype findstring highlight_files
+
+ stopfinding
+ if {$gdttype eq [mc "containing:"]} {
+ findcom_change
+ } else {
+ if {$highlight_files ne $findstring} {
+ set highlight_files $findstring
+ hfiles_change
+ }
+ }
+ drawvisible
+}
+
+proc findcom_change args {
+ global nhighlights boldnamerows
+ global findpattern findtype findstring gdttype
+
+ stopfinding
+ # delete previous highlights, if any
+ foreach row $boldnamerows {
+ bolden_name $row mainfont
+ }
+ set boldnamerows {}
+ catch {unset nhighlights}
+ unbolden
+ unmarkmatches
+ if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
+ set findpattern {}
+ } elseif {$findtype eq [mc "Regexp"]} {
+ set findpattern $findstring
+ } else {
+ set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
+ $findstring]
+ set findpattern "*$e*"
+ }
+}
+
proc makepatterns {l} {
set ret {}
foreach e $l {
@@ -2194,13 +2386,16 @@ proc makepatterns {l} {
proc do_file_hl {serial} {
global highlight_files filehighlight highlight_paths gdttype fhl_list
- if {$gdttype eq "touching paths:"} {
+ if {$gdttype eq [mc "touching paths:"]} {
if {[catch {set paths [shellsplit $highlight_files]}]} return
set highlight_paths [makepatterns $paths]
highlight_filelist
set gdtargs [concat -- $paths]
- } else {
+ } elseif {$gdttype eq [mc "adding/removing string:"]} {
set gdtargs [list "-S$highlight_files"]
+ } else {
+ # must be "containing:", i.e. we're searching commit info
+ return
}
set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
set filehighlight [open $cmd r+]
@@ -2230,8 +2425,8 @@ proc askfilehighlight {row id} {
}
proc readfhighlight {} {
- global filehighlight fhighlights commitrow curview mainfont iddrawn
- global fhl_list
+ global filehighlight fhighlights commitrow curview iddrawn
+ global fhl_list find_dirn
if {![info exists filehighlight]} {
return 0
@@ -2252,7 +2447,7 @@ proc readfhighlight {} {
if {![info exists commitrow($curview,$line)]} continue
set row $commitrow($curview,$line)
if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
- bolden $row [concat $mainfont bold]
+ bolden $row mainfontbold
}
set fhighlights($row) 1
}
@@ -2263,36 +2458,18 @@ proc readfhighlight {} {
unset filehighlight
return 0
}
- next_hlcont
- return 1
-}
-
-proc find_change {name ix op} {
- global nhighlights mainfont boldnamerows
- global findstring findpattern findtype
-
- # delete previous highlights, if any
- foreach row $boldnamerows {
- bolden_name $row $mainfont
- }
- set boldnamerows {}
- catch {unset nhighlights}
- unbolden
- unmarkmatches
- if {$findtype ne "Regexp"} {
- set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
- $findstring]
- set findpattern "*$e*"
+ if {[info exists find_dirn]} {
+ run findmore
}
- drawvisible
+ return 1
}
proc doesmatch {f} {
- global findtype findstring findpattern
+ global findtype findpattern
- if {$findtype eq "Regexp"} {
- return [regexp $findstring $f]
- } elseif {$findtype eq "IgnCase"} {
+ if {$findtype eq [mc "Regexp"]} {
+ return [regexp $findpattern $f]
+ } elseif {$findtype eq [mc "IgnCase"]} {
return [string match -nocase $findpattern $f]
} else {
return [string match $findpattern $f]
@@ -2300,7 +2477,7 @@ proc doesmatch {f} {
}
proc askfindhighlight {row id} {
- global nhighlights commitinfo iddrawn mainfont
+ global nhighlights commitinfo iddrawn
global findloc
global markingmatches
@@ -2309,11 +2486,11 @@ proc askfindhighlight {row id} {
}
set info $commitinfo($id)
set isbold 0
- set fldtypes {Headline Author Date Committer CDate Comments}
+ set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
foreach f $info ty $fldtypes {
- if {($findloc eq "All fields" || $findloc eq $ty) &&
+ if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
[doesmatch $f]} {
- if {$ty eq "Author"} {
+ if {$ty eq [mc "Author"]} {
set isbold 2
break
}
@@ -2321,11 +2498,10 @@ proc askfindhighlight {row id} {
}
}
if {$isbold && [info exists iddrawn($id)]} {
- set f [concat $mainfont bold]
if {![ishighlighted $row]} {
- bolden $row $f
+ bolden $row mainfontbold
if {$isbold > 1} {
- bolden_name $row $f
+ bolden_name $row mainfontbold
}
}
if {$markingmatches} {
@@ -2342,14 +2518,14 @@ proc markrowmatches {row id} {
set author [lindex $commitinfo($id) 1]
$canv delete match$row
$canv2 delete match$row
- if {$findloc eq "All fields" || $findloc eq "Headline"} {
+ if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
set m [findmatches $headline]
if {$m ne {}} {
markmatches $canv $row $headline $linehtag($row) $m \
[$canv itemcget $linehtag($row) -font] $row
}
}
- if {$findloc eq "All fields" || $findloc eq "Author"} {
+ if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
set m [findmatches $author]
if {$m ne {}} {
markmatches $canv2 $row $author $linentag($row) $m \
@@ -2362,7 +2538,7 @@ proc vrel_change {name ix op} {
global highlight_related
rhighlight_none
- if {$highlight_related ne "None"} {
+ if {$highlight_related ne [mc "None"]} {
run drawvisible
}
}
@@ -2376,7 +2552,7 @@ proc rhighlight_sel {a} {
set desc_todo [list $a]
catch {unset ancestor}
set anc_todo [list $a]
- if {$highlight_related ne "None"} {
+ if {$highlight_related ne [mc "None"]} {
rhighlight_none
run drawvisible
}
@@ -2454,111 +2630,36 @@ proc is_ancestor {a} {
}
proc askrelhighlight {row id} {
- global descendent highlight_related iddrawn mainfont rhighlights
+ global descendent highlight_related iddrawn rhighlights
global selectedline ancestor
if {![info exists selectedline]} return
set isbold 0
- if {$highlight_related eq "Descendent" ||
- $highlight_related eq "Not descendent"} {
+ if {$highlight_related eq [mc "Descendant"] ||
+ $highlight_related eq [mc "Not descendant"]} {
if {![info exists descendent($id)]} {
is_descendent $id
}
- if {$descendent($id) == ($highlight_related eq "Descendent")} {
+ if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
set isbold 1
}
- } elseif {$highlight_related eq "Ancestor" ||
- $highlight_related eq "Not ancestor"} {
+ } elseif {$highlight_related eq [mc "Ancestor"] ||
+ $highlight_related eq [mc "Not ancestor"]} {
if {![info exists ancestor($id)]} {
is_ancestor $id
}
- if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
+ if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
set isbold 1
}
}
if {[info exists iddrawn($id)]} {
if {$isbold && ![ishighlighted $row]} {
- bolden $row [concat $mainfont bold]
+ bolden $row mainfontbold
}
}
set rhighlights($row) $isbold
}
-proc next_hlcont {} {
- global fhl_row fhl_dirn displayorder numcommits
- global vhighlights fhighlights nhighlights rhighlights
- global hlview filehighlight findstring highlight_related
-
- if {![info exists fhl_dirn] || $fhl_dirn == 0} return
- set row $fhl_row
- while {1} {
- if {$row < 0 || $row >= $numcommits} {
- bell
- set fhl_dirn 0
- return
- }
- set id [lindex $displayorder $row]
- if {[info exists hlview]} {
- if {![info exists vhighlights($row)]} {
- askvhighlight $row $id
- }
- if {$vhighlights($row) > 0} break
- }
- if {$findstring ne {}} {
- if {![info exists nhighlights($row)]} {
- askfindhighlight $row $id
- }
- if {$nhighlights($row) > 0} break
- }
- if {$highlight_related ne "None"} {
- if {![info exists rhighlights($row)]} {
- askrelhighlight $row $id
- }
- if {$rhighlights($row) > 0} break
- }
- if {[info exists filehighlight]} {
- if {![info exists fhighlights($row)]} {
- # ask for a few more while we're at it...
- set r $row
- for {set n 0} {$n < 100} {incr n} {
- if {![info exists fhighlights($r)]} {
- askfilehighlight $r [lindex $displayorder $r]
- }
- incr r $fhl_dirn
- if {$r < 0 || $r >= $numcommits} break
- }
- flushhighlights
- }
- if {$fhighlights($row) < 0} {
- set fhl_row $row
- return
- }
- if {$fhighlights($row) > 0} break
- }
- incr row $fhl_dirn
- }
- set fhl_dirn 0
- selectline $row 1
-}
-
-proc next_highlight {dirn} {
- global selectedline fhl_row fhl_dirn
- global hlview filehighlight findstring highlight_related
-
- if {![info exists selectedline]} return
- if {!([info exists hlview] || $findstring ne {} ||
- $highlight_related ne "None" || [info exists filehighlight])} return
- set fhl_row [expr {$selectedline + $dirn}]
- set fhl_dirn $dirn
- next_hlcont
-}
-
-proc cancel_next_highlight {} {
- global fhl_dirn
-
- set fhl_dirn 0
-}
-
# Graph layout functions
proc shortids {ids} {
@@ -2575,108 +2676,43 @@ proc shortids {ids} {
return $res
}
-proc incrange {l x o} {
- set n [llength $l]
- while {$x < $n} {
- set e [lindex $l $x]
- if {$e ne {}} {
- lset l $x [expr {$e + $o}]
- }
- incr x
- }
- return $l
-}
-
proc ntimes {n o} {
set ret {}
- for {} {$n > 0} {incr n -1} {
- lappend ret $o
- }
- return $ret
-}
-
-proc usedinrange {id l1 l2} {
- global children commitrow curview
-
- if {[info exists commitrow($curview,$id)]} {
- set r $commitrow($curview,$id)
- if {$l1 <= $r && $r <= $l2} {
- return [expr {$r - $l1 + 1}]
- }
- }
- set kids $children($curview,$id)
- foreach c $kids {
- set r $commitrow($curview,$c)
- if {$l1 <= $r && $r <= $l2} {
- return [expr {$r - $l1 + 1}]
+ set o [list $o]
+ for {set mask 1} {$mask <= $n} {incr mask $mask} {
+ if {($n & $mask) != 0} {
+ set ret [concat $ret $o]
}
+ set o [concat $o $o]
}
- return 0
+ return $ret
}
-proc sanity {row {full 0}} {
- global rowidlist rowoffsets
+# Work out where id should go in idlist so that order-token
+# values increase from left to right
+proc idcol {idlist id {i 0}} {
+ global ordertok curview
- set col -1
- set ids [lindex $rowidlist $row]
- foreach id $ids {
- incr col
- if {$id eq {}} continue
- if {$col < [llength $ids] - 1 &&
- [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
- puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
- }
- set o [lindex $rowoffsets $row $col]
- set y $row
- set x $col
- while {$o ne {}} {
- incr y -1
- incr x $o
- if {[lindex $rowidlist $y $x] != $id} {
- puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
- puts " id=[shortids $id] check started at row $row"
- for {set i $row} {$i >= $y} {incr i -1} {
- puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
- }
- break
- }
- if {!$full} break
- set o [lindex $rowoffsets $y $x]
+ set t $ordertok($curview,$id)
+ if {$i >= [llength $idlist] ||
+ $t < $ordertok($curview,[lindex $idlist $i])} {
+ if {$i > [llength $idlist]} {
+ set i [llength $idlist]
}
- }
-}
-
-proc makeuparrow {oid x y z} {
- global rowidlist rowoffsets uparrowlen idrowranges displayorder
-
- for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
- incr y -1
- incr x $z
- set off0 [lindex $rowoffsets $y]
- for {set x0 $x} {1} {incr x0} {
- if {$x0 >= [llength $off0]} {
- set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
- break
- }
- set z [lindex $off0 $x0]
- if {$z ne {}} {
- incr x0 $z
- break
- }
+ while {[incr i -1] >= 0 &&
+ $t < $ordertok($curview,[lindex $idlist $i])} {}
+ incr i
+ } else {
+ if {$t > $ordertok($curview,[lindex $idlist $i])} {
+ while {[incr i] < [llength $idlist] &&
+ $t >= $ordertok($curview,[lindex $idlist $i])} {}
}
- set z [expr {$x0 - $x}]
- lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
- lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
}
- set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
- lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
- lappend idrowranges($oid) [lindex $displayorder $y]
+ return $i
}
proc initlayout {} {
- global rowidlist rowoffsets displayorder commitlisted
- global rowlaidout rowoptim
- global idinlist rowchk rowrangelist idrowranges
+ global rowidlist rowisopt rowfinal displayorder commitlisted
global numcommits canvxmax canv
global nextcolor
global parentlist
@@ -2687,18 +2723,13 @@ proc initlayout {} {
set displayorder {}
set commitlisted {}
set parentlist {}
- set rowrangelist {}
set nextcolor 0
- set rowidlist {{}}
- set rowoffsets {{}}
- catch {unset idinlist}
- catch {unset rowchk}
- set rowlaidout 0
- set rowoptim 0
+ set rowidlist {}
+ set rowisopt {}
+ set rowfinal {}
set canvxmax [$canv cget -width]
catch {unset colormap}
catch {unset rowtextx}
- catch {unset idrowranges}
set selectfirst 1
}
@@ -2730,61 +2761,19 @@ proc visiblerows {} {
return [list $r0 $r1]
}
-proc layoutmore {tmax allread} {
- global rowlaidout rowoptim commitidx numcommits optim_delay
- global uparrowlen curview rowidlist idinlist
+proc layoutmore {} {
+ global commitidx viewcomplete numcommits
+ global uparrowlen downarrowlen mingaplen curview
- set showlast 0
- set showdelay $optim_delay
- set optdelay [expr {$uparrowlen + 1}]
- while {1} {
- if {$rowoptim - $showdelay > $numcommits} {
- showstuff [expr {$rowoptim - $showdelay}] $showlast
- } elseif {$rowlaidout - $optdelay > $rowoptim} {
- set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
- if {$nr > 100} {
- set nr 100
- }
- optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
- incr rowoptim $nr
- } elseif {$commitidx($curview) > $rowlaidout} {
- set nr [expr {$commitidx($curview) - $rowlaidout}]
- # may need to increase this threshold if uparrowlen or
- # mingaplen are increased...
- if {$nr > 150} {
- set nr 150
- }
- set row $rowlaidout
- set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
- if {$rowlaidout == $row} {
- return 0
- }
- } elseif {$allread} {
- set optdelay 0
- set nrows $commitidx($curview)
- if {[lindex $rowidlist $nrows] ne {} ||
- [array names idinlist] ne {}} {
- layouttail
- set rowlaidout $commitidx($curview)
- } elseif {$rowoptim == $nrows} {
- set showdelay 0
- set showlast 1
- if {$numcommits == $nrows} {
- return 0
- }
- }
- } else {
- return 0
- }
- if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
- return 1
- }
+ set show $commitidx($curview)
+ if {$show > $numcommits || $viewcomplete($curview)} {
+ showstuff $show $viewcomplete($curview)
}
}
proc showstuff {canshow last} {
global numcommits commitrow pending_select selectedline curview
- global lookingforhead mainheadid displayorder selectfirst
+ global mainheadid displayorder selectfirst
global lastscrollset commitinterest
if {$numcommits == 0} {
@@ -2792,15 +2781,6 @@ proc showstuff {canshow last} {
set phase "incrdraw"
allcanvs delete all
}
- for {set l $numcommits} {$l < $canshow} {incr l} {
- set id [lindex $displayorder $l]
- if {[info exists commitinterest($id)]} {
- foreach script $commitinterest($id) {
- eval [string map [list "%I" $id] $script]
- }
- unset commitinterest($id)
- }
- }
set r0 $numcommits
set prev $numcommits
set numcommits $canshow
@@ -2831,28 +2811,22 @@ proc showstuff {canshow last} {
set selectfirst 0
}
}
- if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
- && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
- set lookingforhead 0
- dodiffindex
- }
}
proc doshowlocalchanges {} {
- global lookingforhead curview mainheadid phase commitrow
+ global curview mainheadid phase commitrow
if {[info exists commitrow($curview,$mainheadid)] &&
($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
dodiffindex
} elseif {$phase ne {}} {
- set lookingforhead 1
+ lappend commitinterest($mainheadid) {}
}
}
proc dohidelocalchanges {} {
- global lookingforhead localfrow localirow lserial
+ global localfrow localirow lserial
- set lookingforhead 0
if {$localfrow >= 0} {
removerow $localfrow
set localfrow -1
@@ -2869,8 +2843,9 @@ proc dohidelocalchanges {} {
# spawn off a process to do git diff-index --cached HEAD
proc dodiffindex {} {
- global localirow localfrow lserial
+ global localirow localfrow lserial showlocalchanges
+ if {!$showlocalchanges} return
incr lserial
set localfrow -1
set localirow -1
@@ -2903,7 +2878,7 @@ proc readdiffindex {fd serial} {
if {$isdiff && $serial == $lserial && $localirow == -1} {
# add the line for the changes in the index to the graph
set localirow $commitrow($curview,$mainheadid)
- set hl "Local changes checked in to index but not committed"
+ set hl [mc "Local changes checked in to index but not committed"]
set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
set commitdata($nullid2) "\n $hl\n"
insertrow $localirow $nullid2
@@ -2933,7 +2908,7 @@ proc readdifffiles {fd serial} {
} else {
set localfrow $commitrow($curview,$mainheadid)
}
- set hl "Local uncommitted changes, not checked in to index"
+ set hl [mc "Local uncommitted changes, not checked in to index"]
set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
set commitdata($nullid) "\n $hl\n"
insertrow $localfrow $nullid
@@ -2941,207 +2916,325 @@ proc readdifffiles {fd serial} {
return 0
}
-proc layoutrows {row endrow last} {
- global rowidlist rowoffsets displayorder
- global uparrowlen downarrowlen maxwidth mingaplen
- global children parentlist
- global idrowranges
- global commitidx curview
- global idinlist rowchk rowrangelist
+proc nextuse {id row} {
+ global commitrow curview children
- set idlist [lindex $rowidlist $row]
- set offs [lindex $rowoffsets $row]
- while {$row < $endrow} {
- set id [lindex $displayorder $row]
- set nev [expr {[llength $idlist] - $maxwidth + 1}]
- foreach p [lindex $parentlist $row] {
- if {![info exists idinlist($p)] || !$idinlist($p)} {
- incr nev
- }
- }
- if {$nev > 0} {
- if {!$last &&
- $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
- for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
- set i [lindex $idlist $x]
- if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
- set r [usedinrange $i [expr {$row - $downarrowlen}] \
- [expr {$row + $uparrowlen + $mingaplen}]]
- if {$r == 0} {
- set idlist [lreplace $idlist $x $x]
- set offs [lreplace $offs $x $x]
- set offs [incrange $offs $x 1]
- set idinlist($i) 0
- set rm1 [expr {$row - 1}]
- lappend idrowranges($i) [lindex $displayorder $rm1]
- if {[incr nev -1] <= 0} break
- continue
- }
- set rowchk($i) [expr {$row + $r}]
- }
+ if {[info exists children($curview,$id)]} {
+ foreach kid $children($curview,$id) {
+ if {![info exists commitrow($curview,$kid)]} {
+ return -1
+ }
+ if {$commitrow($curview,$kid) > $row} {
+ return $commitrow($curview,$kid)
}
- lset rowidlist $row $idlist
- lset rowoffsets $row $offs
}
- set oldolds {}
- set newolds {}
- foreach p [lindex $parentlist $row] {
- if {![info exists idinlist($p)]} {
- lappend newolds $p
- } elseif {!$idinlist($p)} {
- lappend oldolds $p
+ }
+ if {[info exists commitrow($curview,$id)]} {
+ return $commitrow($curview,$id)
+ }
+ return -1
+}
+
+proc prevuse {id row} {
+ global commitrow curview children
+
+ set ret -1
+ if {[info exists children($curview,$id)]} {
+ foreach kid $children($curview,$id) {
+ if {![info exists commitrow($curview,$kid)]} break
+ if {$commitrow($curview,$kid) < $row} {
+ set ret $commitrow($curview,$kid)
}
- set idinlist($p) 1
}
- set col [lsearch -exact $idlist $id]
- if {$col < 0} {
- set col [llength $idlist]
- lappend idlist $id
- lset rowidlist $row $idlist
- set z {}
- if {$children($curview,$id) ne {}} {
- set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
- unset idinlist($id)
- }
- lappend offs $z
- lset rowoffsets $row $offs
- if {$z ne {}} {
- makeuparrow $id $col $row $z
+ }
+ return $ret
+}
+
+proc make_idlist {row} {
+ global displayorder parentlist uparrowlen downarrowlen mingaplen
+ global commitidx curview ordertok children commitrow
+
+ set r [expr {$row - $mingaplen - $downarrowlen - 1}]
+ if {$r < 0} {
+ set r 0
+ }
+ set ra [expr {$row - $downarrowlen}]
+ if {$ra < 0} {
+ set ra 0
+ }
+ set rb [expr {$row + $uparrowlen}]
+ if {$rb > $commitidx($curview)} {
+ set rb $commitidx($curview)
+ }
+ set ids {}
+ for {} {$r < $ra} {incr r} {
+ set nextid [lindex $displayorder [expr {$r + 1}]]
+ foreach p [lindex $parentlist $r] {
+ if {$p eq $nextid} continue
+ set rn [nextuse $p $r]
+ if {$rn >= $row &&
+ $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
+ lappend ids [list $ordertok($curview,$p) $p]
}
- } else {
- unset idinlist($id)
- }
- set ranges {}
- if {[info exists idrowranges($id)]} {
- set ranges $idrowranges($id)
- lappend ranges $id
- unset idrowranges($id)
- }
- lappend rowrangelist $ranges
- incr row
- set offs [ntimes [llength $idlist] 0]
- set l [llength $newolds]
- set idlist [eval lreplace \$idlist $col $col $newolds]
- set o 0
- if {$l != 1} {
- set offs [lrange $offs 0 [expr {$col - 1}]]
- foreach x $newolds {
- lappend offs {}
- incr o -1
- }
- incr o
- set tmp [expr {[llength $idlist] - [llength $offs]}]
- if {$tmp > 0} {
- set offs [concat $offs [ntimes $tmp $o]]
+ }
+ }
+ for {} {$r < $row} {incr r} {
+ set nextid [lindex $displayorder [expr {$r + 1}]]
+ foreach p [lindex $parentlist $r] {
+ if {$p eq $nextid} continue
+ set rn [nextuse $p $r]
+ if {$rn < 0 || $rn >= $row} {
+ lappend ids [list $ordertok($curview,$p) $p]
}
- } else {
- lset offs $col {}
}
- foreach i $newolds {
- set idrowranges($i) $id
+ }
+ set id [lindex $displayorder $row]
+ lappend ids [list $ordertok($curview,$id) $id]
+ while {$r < $rb} {
+ foreach p [lindex $parentlist $r] {
+ set firstkid [lindex $children($curview,$p) 0]
+ if {$commitrow($curview,$firstkid) < $row} {
+ lappend ids [list $ordertok($curview,$p) $p]
+ }
}
- incr col $l
- foreach oid $oldolds {
- set idlist [linsert $idlist $col $oid]
- set offs [linsert $offs $col $o]
- makeuparrow $oid $col $row $o
- incr col
+ incr r
+ set id [lindex $displayorder $r]
+ if {$id ne {}} {
+ set firstkid [lindex $children($curview,$id) 0]
+ if {$firstkid ne {} && $commitrow($curview,$firstkid) < $row} {
+ lappend ids [list $ordertok($curview,$id) $id]
+ }
}
- lappend rowidlist $idlist
- lappend rowoffsets $offs
}
- return $row
+ set idlist {}
+ foreach idx [lsort -unique $ids] {
+ lappend idlist [lindex $idx 1]
+ }
+ return $idlist
}
-proc addextraid {id row} {
- global displayorder commitrow commitinfo
- global commitidx commitlisted
- global parentlist children curview
+proc rowsequal {a b} {
+ while {[set i [lsearch -exact $a {}]] >= 0} {
+ set a [lreplace $a $i $i]
+ }
+ while {[set i [lsearch -exact $b {}]] >= 0} {
+ set b [lreplace $b $i $i]
+ }
+ return [expr {$a eq $b}]
+}
- incr commitidx($curview)
- lappend displayorder $id
- lappend commitlisted 0
- lappend parentlist {}
- set commitrow($curview,$id) $row
- readcommit $id
- if {![info exists commitinfo($id)]} {
- set commitinfo($id) {"No commit information available"}
+proc makeupline {id row rend col} {
+ global rowidlist uparrowlen downarrowlen mingaplen
+
+ for {set r $rend} {1} {set r $rstart} {
+ set rstart [prevuse $id $r]
+ if {$rstart < 0} return
+ if {$rstart < $row} break
}
- if {![info exists children($curview,$id)]} {
- set children($curview,$id) {}
+ if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
+ set rstart [expr {$rend - $uparrowlen - 1}]
+ }
+ for {set r $rstart} {[incr r] <= $row} {} {
+ set idlist [lindex $rowidlist $r]
+ if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
+ set col [idcol $idlist $id $col]
+ lset rowidlist $r [linsert $idlist $col $id]
+ changedrow $r
+ }
}
}
-proc layouttail {} {
- global rowidlist rowoffsets idinlist commitidx curview
- global idrowranges rowrangelist
+proc layoutrows {row endrow} {
+ global rowidlist rowisopt rowfinal displayorder
+ global uparrowlen downarrowlen maxwidth mingaplen
+ global children parentlist
+ global commitidx viewcomplete curview commitrow
- set row $commitidx($curview)
- set idlist [lindex $rowidlist $row]
- while {$idlist ne {}} {
- set col [expr {[llength $idlist] - 1}]
- set id [lindex $idlist $col]
- addextraid $id $row
- catch {unset idinlist($id)}
- lappend idrowranges($id) $id
- lappend rowrangelist $idrowranges($id)
- unset idrowranges($id)
- incr row
- set offs [ntimes $col 0]
- set idlist [lreplace $idlist $col $col]
- lappend rowidlist $idlist
- lappend rowoffsets $offs
- }
-
- foreach id [array names idinlist] {
- unset idinlist($id)
- addextraid $id $row
- lset rowidlist $row [list $id]
- lset rowoffsets $row 0
- makeuparrow $id 0 $row 0
- lappend idrowranges($id) $id
- lappend rowrangelist $idrowranges($id)
- unset idrowranges($id)
- incr row
- lappend rowidlist {}
- lappend rowoffsets {}
+ set idlist {}
+ if {$row > 0} {
+ set rm1 [expr {$row - 1}]
+ foreach id [lindex $rowidlist $rm1] {
+ if {$id ne {}} {
+ lappend idlist $id
+ }
+ }
+ set final [lindex $rowfinal $rm1]
+ }
+ for {} {$row < $endrow} {incr row} {
+ set rm1 [expr {$row - 1}]
+ if {$rm1 < 0 || $idlist eq {}} {
+ set idlist [make_idlist $row]
+ set final 1
+ } else {
+ set id [lindex $displayorder $rm1]
+ set col [lsearch -exact $idlist $id]
+ set idlist [lreplace $idlist $col $col]
+ foreach p [lindex $parentlist $rm1] {
+ if {[lsearch -exact $idlist $p] < 0} {
+ set col [idcol $idlist $p $col]
+ set idlist [linsert $idlist $col $p]
+ # if not the first child, we have to insert a line going up
+ if {$id ne [lindex $children($curview,$p) 0]} {
+ makeupline $p $rm1 $row $col
+ }
+ }
+ }
+ set id [lindex $displayorder $row]
+ if {$row > $downarrowlen} {
+ set termrow [expr {$row - $downarrowlen - 1}]
+ foreach p [lindex $parentlist $termrow] {
+ set i [lsearch -exact $idlist $p]
+ if {$i < 0} continue
+ set nr [nextuse $p $termrow]
+ if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
+ set idlist [lreplace $idlist $i $i]
+ }
+ }
+ }
+ set col [lsearch -exact $idlist $id]
+ if {$col < 0} {
+ set col [idcol $idlist $id]
+ set idlist [linsert $idlist $col $id]
+ if {$children($curview,$id) ne {}} {
+ makeupline $id $rm1 $row $col
+ }
+ }
+ set r [expr {$row + $uparrowlen - 1}]
+ if {$r < $commitidx($curview)} {
+ set x $col
+ foreach p [lindex $parentlist $r] {
+ if {[lsearch -exact $idlist $p] >= 0} continue
+ set fk [lindex $children($curview,$p) 0]
+ if {$commitrow($curview,$fk) < $row} {
+ set x [idcol $idlist $p $x]
+ set idlist [linsert $idlist $x $p]
+ }
+ }
+ if {[incr r] < $commitidx($curview)} {
+ set p [lindex $displayorder $r]
+ if {[lsearch -exact $idlist $p] < 0} {
+ set fk [lindex $children($curview,$p) 0]
+ if {$fk ne {} && $commitrow($curview,$fk) < $row} {
+ set x [idcol $idlist $p $x]
+ set idlist [linsert $idlist $x $p]
+ }
+ }
+ }
+ }
+ }
+ if {$final && !$viewcomplete($curview) &&
+ $row + $uparrowlen + $mingaplen + $downarrowlen
+ >= $commitidx($curview)} {
+ set final 0
+ }
+ set l [llength $rowidlist]
+ if {$row == $l} {
+ lappend rowidlist $idlist
+ lappend rowisopt 0
+ lappend rowfinal $final
+ } elseif {$row < $l} {
+ if {![rowsequal $idlist [lindex $rowidlist $row]]} {
+ lset rowidlist $row $idlist
+ changedrow $row
+ }
+ lset rowfinal $row $final
+ } else {
+ set pad [ntimes [expr {$row - $l}] {}]
+ set rowidlist [concat $rowidlist $pad]
+ lappend rowidlist $idlist
+ set rowfinal [concat $rowfinal $pad]
+ lappend rowfinal $final
+ set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
+ }
+ }
+ return $row
+}
+
+proc changedrow {row} {
+ global displayorder iddrawn rowisopt need_redisplay
+
+ set l [llength $rowisopt]
+ if {$row < $l} {
+ lset rowisopt $row 0
+ if {$row + 1 < $l} {
+ lset rowisopt [expr {$row + 1}] 0
+ if {$row + 2 < $l} {
+ lset rowisopt [expr {$row + 2}] 0
+ }
+ }
+ }
+ set id [lindex $displayorder $row]
+ if {[info exists iddrawn($id)]} {
+ set need_redisplay 1
}
}
proc insert_pad {row col npad} {
- global rowidlist rowoffsets
+ global rowidlist
set pad [ntimes $npad {}]
- lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
- set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
- lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
+ set idlist [lindex $rowidlist $row]
+ set bef [lrange $idlist 0 [expr {$col - 1}]]
+ set aft [lrange $idlist $col end]
+ set i [lsearch -exact $aft {}]
+ if {$i > 0} {
+ set aft [lreplace $aft $i $i]
+ }
+ lset rowidlist $row [concat $bef $pad $aft]
+ changedrow $row
}
proc optimize_rows {row col endrow} {
- global rowidlist rowoffsets displayorder
+ global rowidlist rowisopt displayorder curview children
- for {} {$row < $endrow} {incr row} {
- set idlist [lindex $rowidlist $row]
- set offs [lindex $rowoffsets $row]
+ if {$row < 1} {
+ set row 1
+ }
+ for {} {$row < $endrow} {incr row; set col 0} {
+ if {[lindex $rowisopt $row]} continue
set haspad 0
- for {} {$col < [llength $offs]} {incr col} {
- if {[lindex $idlist $col] eq {}} {
+ set y0 [expr {$row - 1}]
+ set ym [expr {$row - 2}]
+ set idlist [lindex $rowidlist $row]
+ set previdlist [lindex $rowidlist $y0]
+ if {$idlist eq {} || $previdlist eq {}} continue
+ if {$ym >= 0} {
+ set pprevidlist [lindex $rowidlist $ym]
+ if {$pprevidlist eq {}} continue
+ } else {
+ set pprevidlist {}
+ }
+ set x0 -1
+ set xm -1
+ for {} {$col < [llength $idlist]} {incr col} {
+ set id [lindex $idlist $col]
+ if {[lindex $previdlist $col] eq $id} continue
+ if {$id eq {}} {
set haspad 1
continue
}
- set z [lindex $offs $col]
- if {$z eq {}} continue
+ set x0 [lsearch -exact $previdlist $id]
+ if {$x0 < 0} continue
+ set z [expr {$x0 - $col}]
set isarrow 0
- set x0 [expr {$col + $z}]
- set y0 [expr {$row - 1}]
- set z0 [lindex $rowoffsets $y0 $x0]
+ set z0 {}
+ if {$ym >= 0} {
+ set xm [lsearch -exact $pprevidlist $id]
+ if {$xm >= 0} {
+ set z0 [expr {$xm - $x0}]
+ }
+ }
if {$z0 eq {}} {
- set id [lindex $idlist $col]
- set ranges [rowranges $id]
- if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
+ # if row y0 is the first child of $id then it's not an arrow
+ if {[lindex $children($curview,$id) 0] ne
+ [lindex $displayorder $y0]} {
set isarrow 1
}
}
+ if {!$isarrow && $id ne [lindex $displayorder $row] &&
+ [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
+ set isarrow 1
+ }
# Looking at lines from this row to the previous row,
# make them go straight up if they end in an arrow on
# the previous row; otherwise make them go straight up
@@ -3150,43 +3243,32 @@ proc optimize_rows {row col endrow} {
# Line currently goes left too much;
# insert pads in the previous row, then optimize it
set npad [expr {-1 - $z + $isarrow}]
- set offs [incrange $offs $col $npad]
insert_pad $y0 $x0 $npad
if {$y0 > 0} {
optimize_rows $y0 $x0 $row
}
- set z [lindex $offs $col]
- set x0 [expr {$col + $z}]
- set z0 [lindex $rowoffsets $y0 $x0]
+ set previdlist [lindex $rowidlist $y0]
+ set x0 [lsearch -exact $previdlist $id]
+ set z [expr {$x0 - $col}]
+ if {$z0 ne {}} {
+ set pprevidlist [lindex $rowidlist $ym]
+ set xm [lsearch -exact $pprevidlist $id]
+ set z0 [expr {$xm - $x0}]
+ }
} elseif {$z > 1 || ($z > 0 && $isarrow)} {
# Line currently goes right too much;
- # insert pads in this line and adjust the next's rowoffsets
+ # insert pads in this line
set npad [expr {$z - 1 + $isarrow}]
- set y1 [expr {$row + 1}]
- set offs2 [lindex $rowoffsets $y1]
- set x1 -1
- foreach z $offs2 {
- incr x1
- if {$z eq {} || $x1 + $z < $col} continue
- if {$x1 + $z > $col} {
- incr npad
- }
- lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
- break
- }
- set pad [ntimes $npad {}]
- set idlist [eval linsert \$idlist $col $pad]
- set tmp [eval linsert \$offs $col $pad]
+ insert_pad $row $col $npad
+ set idlist [lindex $rowidlist $row]
incr col $npad
- set offs [incrange $tmp $col [expr {-$npad}]]
- set z [lindex $offs $col]
+ set z [expr {$x0 - $col}]
set haspad 1
}
- if {$z0 eq {} && !$isarrow} {
+ if {$z0 eq {} && !$isarrow && $ym >= 0} {
# this line links to its first child on row $row-2
- set rm2 [expr {$row - 2}]
- set id [lindex $displayorder $rm2]
- set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
+ set id [lindex $displayorder $ym]
+ set xc [lsearch -exact $pprevidlist $id]
if {$xc >= 0} {
set z0 [expr {$xc - $x0}]
}
@@ -3194,52 +3276,35 @@ proc optimize_rows {row col endrow} {
# avoid lines jigging left then immediately right
if {$z0 ne {} && $z < 0 && $z0 > 0} {
insert_pad $y0 $x0 1
- set offs [incrange $offs $col 1]
- optimize_rows $y0 [expr {$x0 + 1}] $row
+ incr x0
+ optimize_rows $y0 $x0 $row
+ set previdlist [lindex $rowidlist $y0]
}
}
if {!$haspad} {
- set o {}
# Find the first column that doesn't have a line going right
for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
- set o [lindex $offs $col]
- if {$o eq {}} {
+ set id [lindex $idlist $col]
+ if {$id eq {}} break
+ set x0 [lsearch -exact $previdlist $id]
+ if {$x0 < 0} {
# check if this is the link to the first child
- set id [lindex $idlist $col]
- set ranges [rowranges $id]
- if {$ranges ne {} && $row == [lindex $ranges 0]} {
+ set kid [lindex $displayorder $y0]
+ if {[lindex $children($curview,$id) 0] eq $kid} {
# it is, work out offset to child
- set y0 [expr {$row - 1}]
- set id [lindex $displayorder $y0]
- set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
- if {$x0 >= 0} {
- set o [expr {$x0 - $col}]
- }
+ set x0 [lsearch -exact $previdlist $kid]
}
}
- if {$o eq {} || $o <= 0} break
+ if {$x0 <= $col} break
}
# Insert a pad at that column as long as it has a line and
- # isn't the last column, and adjust the next row' offsets
- if {$o ne {} && [incr col] < [llength $idlist]} {
- set y1 [expr {$row + 1}]
- set offs2 [lindex $rowoffsets $y1]
- set x1 -1
- foreach z $offs2 {
- incr x1
- if {$z eq {} || $x1 + $z < $col} continue
- lset rowoffsets $y1 [incrange $offs2 $x1 1]
- break
- }
+ # isn't the last column
+ if {$x0 >= 0 && [incr col] < [llength $idlist]} {
set idlist [linsert $idlist $col {}]
- set tmp [linsert $offs $col {}]
- incr col
- set offs [incrange $tmp $col -1]
+ lset rowidlist $row $idlist
+ changedrow $row
}
}
- lset rowidlist $row $idlist
- lset rowoffsets $row $offs
- set col 0
}
}
@@ -3264,51 +3329,64 @@ proc linewidth {id} {
}
proc rowranges {id} {
- global phase idrowranges commitrow rowlaidout rowrangelist curview
-
- set ranges {}
- if {$phase eq {} ||
- ([info exists commitrow($curview,$id)]
- && $commitrow($curview,$id) < $rowlaidout)} {
- set ranges [lindex $rowrangelist $commitrow($curview,$id)]
- } elseif {[info exists idrowranges($id)]} {
- set ranges $idrowranges($id)
- }
- set linenos {}
- foreach rid $ranges {
- lappend linenos $commitrow($curview,$rid)
- }
- if {$linenos ne {}} {
- lset linenos 0 [expr {[lindex $linenos 0] + 1}]
- }
- return $linenos
-}
-
-# work around tk8.4 refusal to draw arrows on diagonal segments
-proc adjarrowhigh {coords} {
- global linespc
-
- set x0 [lindex $coords 0]
- set x1 [lindex $coords 2]
- if {$x0 != $x1} {
- set y0 [lindex $coords 1]
- set y1 [lindex $coords 3]
- if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
- # we have a nearby vertical segment, just trim off the diag bit
- set coords [lrange $coords 2 end]
+ global commitrow curview children uparrowlen downarrowlen
+ global rowidlist
+
+ set kids $children($curview,$id)
+ if {$kids eq {}} {
+ return {}
+ }
+ set ret {}
+ lappend kids $id
+ foreach child $kids {
+ if {![info exists commitrow($curview,$child)]} break
+ set row $commitrow($curview,$child)
+ if {![info exists prev]} {
+ lappend ret [expr {$row + 1}]
} else {
- set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
- set xi [expr {$x0 - $slope * $linespc / 2}]
- set yi [expr {$y0 - $linespc / 2}]
- set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
+ if {$row <= $prevrow} {
+ puts "oops children out of order [shortids $id] $row < [shortids $prev] $prevrow"
+ }
+ # see if the line extends the whole way from prevrow to row
+ if {$row > $prevrow + $uparrowlen + $downarrowlen &&
+ [lsearch -exact [lindex $rowidlist \
+ [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
+ # it doesn't, see where it ends
+ set r [expr {$prevrow + $downarrowlen}]
+ if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
+ while {[incr r -1] > $prevrow &&
+ [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
+ } else {
+ while {[incr r] <= $row &&
+ [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
+ incr r -1
+ }
+ lappend ret $r
+ # see where it starts up again
+ set r [expr {$row - $uparrowlen}]
+ if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
+ while {[incr r] < $row &&
+ [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
+ } else {
+ while {[incr r -1] >= $prevrow &&
+ [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
+ incr r
+ }
+ lappend ret $r
+ }
+ }
+ if {$child eq $id} {
+ lappend ret $row
}
+ set prev $id
+ set prevrow $row
}
- return $coords
+ return $ret
}
proc drawlineseg {id row endrow arrowlow} {
global rowidlist displayorder iddrawn linesegs
- global canv colormap linespc curview maxlinelen
+ global canv colormap linespc curview maxlinelen parentlist
set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
set le [expr {$row + 1}]
@@ -3383,9 +3461,11 @@ proc drawlineseg {id row endrow arrowlow} {
set itl [lindex $lines [expr {$i-1}] 2]
set al [$canv itemcget $itl -arrow]
set arrowlow [expr {$al eq "last" || $al eq "both"}]
- } elseif {$arrowlow &&
- [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
- set arrowlow 0
+ } elseif {$arrowlow} {
+ if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
+ [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
+ set arrowlow 0
+ }
}
set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
for {set y $le} {[incr y -1] > $row} {} {
@@ -3404,8 +3484,19 @@ proc drawlineseg {id row endrow arrowlow} {
set xc [lsearch -exact [lindex $rowidlist $row] $ch]
if {$xc < 0} {
puts "oops: drawlineseg: child $ch not on row $row"
- } else {
- if {$xc < $x - 1} {
+ } elseif {$xc != $x} {
+ if {($arrowhigh && $le == $row + 1) || $dir == 0} {
+ set d [expr {int(0.5 * $linespc)}]
+ set x1 [xc $row $x]
+ if {$xc < $x} {
+ set x2 [expr {$x1 - $d}]
+ } else {
+ set x2 [expr {$x1 + $d}]
+ }
+ set y2 [yc $row]
+ set y1 [expr {$y2 + $d}]
+ lappend coords $x1 $y1 $x2 $y2
+ } elseif {$xc < $x - 1} {
lappend coords [xc $row [expr {$x-1}]] [yc $row]
} elseif {$xc > $x + 1} {
lappend coords [xc $row [expr {$x+1}]] [yc $row]
@@ -3416,23 +3507,9 @@ proc drawlineseg {id row endrow arrowlow} {
} else {
set xn [xc $row $xp]
set yn [yc $row]
- # work around tk8.4 refusal to draw arrows on diagonal segments
- if {$arrowlow && $xn != [lindex $coords end-1]} {
- if {[llength $coords] < 4 ||
- [lindex $coords end-3] != [lindex $coords end-1] ||
- [lindex $coords end] - $yn > 2 * $linespc} {
- set xn [xc $row [expr {$xp - 0.5 * $dir}]]
- set yo [yc [expr {$row + 0.5}]]
- lappend coords $xn $yo $xn $yn
- }
- } else {
- lappend coords $xn $yn
- }
+ lappend coords $xn $yn
}
if {!$joinhigh} {
- if {$arrowhigh} {
- set coords [adjarrowhigh $coords]
- }
assigncolor $id
set t [$canv create line $coords -width [linewidth $id] \
-fill $colormap($id) -tags lines.$id -arrow $arrow]
@@ -3456,9 +3533,6 @@ proc drawlineseg {id row endrow arrowlow} {
set coords [concat $coords $clow]
if {!$joinhigh} {
lset lines [expr {$i-1}] 1 $le
- if {$arrowhigh} {
- set coords [adjarrowhigh $coords]
- }
} else {
# coalesce two pieces
$canv delete $ith
@@ -3478,7 +3552,7 @@ proc drawlineseg {id row endrow arrowlow} {
proc drawparentlinks {id row} {
global rowidlist canv colormap curview parentlist
- global idpos
+ global idpos linespc
set rowids [lindex $rowidlist $row]
set col [lsearch -exact $rowids $id]
@@ -3488,6 +3562,8 @@ proc drawparentlinks {id row} {
set x [xc $row $col]
set y [yc $row]
set y2 [yc $row2]
+ set d [expr {int(0.5 * $linespc)}]
+ set ymid [expr {$y + $d}]
set ids [lindex $rowidlist $row2]
# rmx = right-most X coord used
set rmx 0
@@ -3501,19 +3577,37 @@ proc drawparentlinks {id row} {
if {$x2 > $rmx} {
set rmx $x2
}
- if {[lsearch -exact $rowids $p] < 0} {
+ set j [lsearch -exact $rowids $p]
+ if {$j < 0} {
# drawlineseg will do this one for us
continue
}
assigncolor $p
# should handle duplicated parents here...
set coords [list $x $y]
- if {$i < $col - 1} {
- lappend coords [xc $row [expr {$i + 1}]] $y
- } elseif {$i > $col + 1} {
- lappend coords [xc $row [expr {$i - 1}]] $y
+ if {$i != $col} {
+ # if attaching to a vertical segment, draw a smaller
+ # slant for visual distinctness
+ if {$i == $j} {
+ if {$i < $col} {
+ lappend coords [expr {$x2 + $d}] $y $x2 $ymid
+ } else {
+ lappend coords [expr {$x2 - $d}] $y $x2 $ymid
+ }
+ } elseif {$i < $col && $i < $j} {
+ # segment slants towards us already
+ lappend coords [xc $row $j] $y
+ } else {
+ if {$i < $col - 1} {
+ lappend coords [expr {$x2 + $linespc}] $y
+ } elseif {$i > $col + 1} {
+ lappend coords [expr {$x2 - $linespc}] $y
+ }
+ lappend coords $x2 $y2
+ }
+ } else {
+ lappend coords $x2 $y2
}
- lappend coords $x2 $y2
set t [$canv create line $coords -width [linewidth $p] \
-fill $colormap($p) -tags lines.$p]
$canv lower $t
@@ -3535,26 +3629,26 @@ proc drawcmittext {id row col} {
global linespc canv canv2 canv3 canvy0 fgcolor curview
global commitlisted commitinfo rowidlist parentlist
global rowtextx idpos idtags idheads idotherrefs
- global linehtag linentag linedtag
- global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
+ global linehtag linentag linedtag selectedline
+ global canvxmax boldrows boldnamerows fgcolor nullid nullid2
- # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
+ # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
set listed [lindex $commitlisted $row]
if {$id eq $nullid} {
set ofill red
} elseif {$id eq $nullid2} {
set ofill green
} else {
- set ofill [expr {$listed != 0? "blue": "white"}]
+ set ofill [expr {$listed != 0 ? $listed == 2 ? "gray" : "blue" : "white"}]
}
set x [xc $row $col]
set y [yc $row]
set orad [expr {$linespc / 3}]
- if {$listed <= 1} {
+ if {$listed <= 2} {
set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
[expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
-fill $ofill -outline $fgcolor -width 1 -tags circle]
- } elseif {$listed == 2} {
+ } elseif {$listed == 3} {
# triangle pointing left for left-side commits
set t [$canv create polygon \
[expr {$x - $orad}] $y \
@@ -3593,15 +3687,15 @@ proc drawcmittext {id row col} {
set name [lindex $commitinfo($id) 1]
set date [lindex $commitinfo($id) 2]
set date [formatdate $date]
- set font $mainfont
- set nfont $mainfont
+ set font mainfont
+ set nfont mainfont
set isbold [ishighlighted $row]
if {$isbold > 0} {
lappend boldrows $row
- lappend font bold
+ set font mainfontbold
if {$isbold > 1} {
lappend boldnamerows $row
- lappend nfont bold
+ set nfont mainfontbold
}
}
set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
@@ -3610,8 +3704,11 @@ proc drawcmittext {id row col} {
set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
-text $name -font $nfont -tags text]
set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
- -text $date -font $mainfont -tags text]
- set xr [expr {$xt + [font measure $mainfont $headline]}]
+ -text $date -font mainfont -tags text]
+ if {[info exists selectedline] && $selectedline == $row} {
+ make_secsel $row
+ }
+ set xr [expr {$xt + [font measure $font $headline]}]
if {$xr > $canvxmax} {
set canvxmax $xr
setcanvscroll
@@ -3619,10 +3716,10 @@ proc drawcmittext {id row col} {
}
proc drawcmitrow {row} {
- global displayorder rowidlist
+ global displayorder rowidlist nrows_drawn
global iddrawn markingmatches
global commitinfo parentlist numcommits
- global filehighlight fhighlights findstring nhighlights
+ global filehighlight fhighlights findpattern nhighlights
global hlview vhighlights
global highlight_related rhighlights
@@ -3635,10 +3732,10 @@ proc drawcmitrow {row} {
if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
askfilehighlight $row $id
}
- if {$findstring ne {} && ![info exists nhighlights($row)]} {
+ if {$findpattern ne {} && ![info exists nhighlights($row)]} {
askfindhighlight $row $id
}
- if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
+ if {$highlight_related ne [mc "None"] && ![info exists rhighlights($row)]} {
askrelhighlight $row $id
}
if {![info exists iddrawn($id)]} {
@@ -3653,6 +3750,7 @@ proc drawcmitrow {row} {
assigncolor $id
drawcmittext $id $row $col
set iddrawn($id) 1
+ incr nrows_drawn
}
if {$markingmatches} {
markrowmatches $row $id
@@ -3660,8 +3758,8 @@ proc drawcmitrow {row} {
}
proc drawcommits {row {endrow {}}} {
- global numcommits iddrawn displayorder curview
- global parentlist rowidlist
+ global numcommits iddrawn displayorder curview need_redisplay
+ global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
if {$row < 0} {
set row 0
@@ -3673,6 +3771,35 @@ proc drawcommits {row {endrow {}}} {
set endrow [expr {$numcommits - 1}]
}
+ set rl1 [expr {$row - $downarrowlen - 3}]
+ if {$rl1 < 0} {
+ set rl1 0
+ }
+ set ro1 [expr {$row - 3}]
+ if {$ro1 < 0} {
+ set ro1 0
+ }
+ set r2 [expr {$endrow + $uparrowlen + 3}]
+ if {$r2 > $numcommits} {
+ set r2 $numcommits
+ }
+ for {set r $rl1} {$r < $r2} {incr r} {
+ if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
+ if {$rl1 < $r} {
+ layoutrows $rl1 $r
+ }
+ set rl1 [expr {$r + 1}]
+ }
+ }
+ if {$rl1 < $r} {
+ layoutrows $rl1 $r
+ }
+ optimize_rows $ro1 0 $r2
+ if {$need_redisplay || $nrows_drawn > 2000} {
+ clear_display
+ drawvisible
+ }
+
# make the lines join to already-drawn rows either side
set r [expr {$row - 1}]
if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
@@ -3689,34 +3816,23 @@ proc drawcommits {row {endrow {}}} {
drawcmitrow $r
if {$r == $er} break
set nextid [lindex $displayorder [expr {$r + 1}]]
- if {$wasdrawn && [info exists iddrawn($nextid)]} {
- catch {unset prevlines}
- continue
- }
+ if {$wasdrawn && [info exists iddrawn($nextid)]} continue
drawparentlinks $id $r
- if {[info exists lineends($r)]} {
- foreach lid $lineends($r) {
- unset prevlines($lid)
- }
- }
set rowids [lindex $rowidlist $r]
foreach lid $rowids {
if {$lid eq {}} continue
+ if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
if {$lid eq $id} {
# see if this is the first child of any of its parents
foreach p [lindex $parentlist $r] {
if {[lsearch -exact $rowids $p] < 0} {
# make this line extend up to the child
- set le [drawlineseg $p $r $er 0]
- lappend lineends($le) $p
- set prevlines($p) 1
+ set lineend($p) [drawlineseg $p $r $er 0]
}
}
- } elseif {![info exists prevlines($lid)]} {
- set le [drawlineseg $lid $r $er 1]
- lappend lineends($le) $lid
- set prevlines($lid) 1
+ } else {
+ set lineend($lid) [drawlineseg $lid $r $er 1]
}
}
}
@@ -3740,7 +3856,7 @@ proc drawvisible {} {
}
proc clear_display {} {
- global iddrawn linesegs
+ global iddrawn linesegs need_redisplay nrows_drawn
global vhighlights fhighlights nhighlights rhighlights
allcanvs delete all
@@ -3750,10 +3866,12 @@ proc clear_display {} {
catch {unset fhighlights}
catch {unset nhighlights}
catch {unset rhighlights}
+ set need_redisplay 0
+ set nrows_drawn 0
}
proc findcrossings {id} {
- global rowidlist parentlist numcommits rowoffsets displayorder
+ global rowidlist parentlist numcommits displayorder
set cross {}
set ccross {}
@@ -3762,12 +3880,9 @@ proc findcrossings {id} {
set e [expr {$numcommits - 1}]
}
if {$e <= $s} continue
- set x [lsearch -exact [lindex $rowidlist $e] $id]
- if {$x < 0} {
- puts "findcrossings: oops, no [shortids $id] in row $e"
- continue
- }
for {set row $e} {[incr row -1] >= $s} {} {
+ set x [lsearch -exact [lindex $rowidlist $row] $id]
+ if {$x < 0} break
set olds [lindex $parentlist $row]
set kid [lindex $displayorder $row]
set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
@@ -3785,9 +3900,6 @@ proc findcrossings {id} {
}
}
}
- set inc [lindex $rowoffsets $row $x]
- if {$inc eq {}} break
- incr x $inc
}
}
return [concat $ccross {{}} $cross]
@@ -3868,7 +3980,7 @@ proc bindline {t id} {
proc drawtags {id x xt y1} {
global idtags idheads idotherrefs mainhead
global linespc lthickness
- global canv mainfont commitrow rowtextx curview fgcolor bgcolor
+ global canv commitrow rowtextx curview fgcolor bgcolor
set marks {}
set ntags 0
@@ -3897,9 +4009,9 @@ proc drawtags {id x xt y1} {
foreach tag $marks {
incr i
if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
- set wid [font measure [concat $mainfont bold] $tag]
+ set wid [font measure mainfontbold $tag]
} else {
- set wid [font measure $mainfont $tag]
+ set wid [font measure mainfont $tag]
}
lappend xvals $xt
lappend wvals $wid
@@ -3911,7 +4023,7 @@ proc drawtags {id x xt y1} {
foreach tag $marks x $xvals wid $wvals {
set xl [expr {$x + $delta}]
set xr [expr {$x + $delta + $wid + $lthickness}]
- set font $mainfont
+ set font mainfont
if {[incr ntags -1] >= 0} {
# draw a tag
set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
@@ -3924,7 +4036,7 @@ proc drawtags {id x xt y1} {
if {[incr nheads -1] >= 0} {
set col green
if {$tag eq $mainhead} {
- lappend font bold
+ set font mainfontbold
}
} else {
set col "#ddddff"
@@ -3933,7 +4045,7 @@ proc drawtags {id x xt y1} {
$canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
-width 1 -outline black -fill $col -tags tag.$id
if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
- set rwid [font measure $mainfont $remoteprefix]
+ set rwid [font measure mainfont $remoteprefix]
set xi [expr {$x + 1}]
set yti [expr {$yt + 1}]
set xri [expr {$x + $rwid}]
@@ -3965,10 +4077,10 @@ proc xcoord {i level ln} {
}
proc show_status {msg} {
- global canv mainfont fgcolor
+ global canv fgcolor
clear_display
- $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
+ $canv create text 3 3 -anchor nw -text $msg -font mainfont \
-tags text -fill $fgcolor
}
@@ -3977,9 +4089,9 @@ proc show_status {msg} {
# on that row and below will move down one row.
proc insertrow {row newcmit} {
global displayorder parentlist commitlisted children
- global commitrow curview rowidlist rowoffsets numcommits
- global rowrangelist rowlaidout rowoptim numcommits
- global selectedline rowchk commitidx
+ global commitrow curview rowidlist rowisopt rowfinal numcommits
+ global numcommits
+ global selectedline commitidx ordertok
if {$row >= $numcommits} {
puts "oops, inserting new row $row but only have $numcommits rows"
@@ -3999,45 +4111,24 @@ proc insertrow {row newcmit} {
set commitrow($curview,$id) $r
}
incr commitidx($curview)
+ set ordertok($curview,$newcmit) $ordertok($curview,$p)
- set idlist [lindex $rowidlist $row]
- set offs [lindex $rowoffsets $row]
- set newoffs {}
- foreach x $idlist {
- if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
- lappend newoffs {}
- } else {
- lappend newoffs 0
- }
- }
- if {[llength $kids] == 1} {
- set col [lsearch -exact $idlist $p]
- lset idlist $col $newcmit
- } else {
- set col [llength $idlist]
- lappend idlist $newcmit
- lappend offs {}
- lset rowoffsets $row $offs
- }
- set rowidlist [linsert $rowidlist $row $idlist]
- set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
-
- set rowrangelist [linsert $rowrangelist $row {}]
- if {[llength $kids] > 1} {
- set rp1 [expr {$row + 1}]
- set ranges [lindex $rowrangelist $rp1]
- if {$ranges eq {}} {
- set ranges [list $newcmit $p]
- } elseif {[lindex $ranges end-1] eq $p} {
- lset ranges end-1 $newcmit
+ if {$row < [llength $rowidlist]} {
+ set idlist [lindex $rowidlist $row]
+ if {$idlist ne {}} {
+ if {[llength $kids] == 1} {
+ set col [lsearch -exact $idlist $p]
+ lset idlist $col $newcmit
+ } else {
+ set col [llength $idlist]
+ lappend idlist $newcmit
+ }
}
- lset rowrangelist $rp1 $ranges
+ set rowidlist [linsert $rowidlist $row $idlist]
+ set rowisopt [linsert $rowisopt $row 0]
+ set rowfinal [linsert $rowfinal $row [lindex $rowfinal $row]]
}
- catch {unset rowchk}
-
- incr rowlaidout
- incr rowoptim
incr numcommits
if {[info exists selectedline] && $selectedline >= $row} {
@@ -4049,9 +4140,9 @@ proc insertrow {row newcmit} {
# Remove a commit that was inserted with insertrow on row $row.
proc removerow {row} {
global displayorder parentlist commitlisted children
- global commitrow curview rowidlist rowoffsets numcommits
- global rowrangelist idrowranges rowlaidout rowoptim numcommits
- global linesegends selectedline rowchk commitidx
+ global commitrow curview rowidlist rowisopt rowfinal numcommits
+ global numcommits
+ global linesegends selectedline commitidx
if {$row >= $numcommits} {
puts "oops, removing row $row but only have $numcommits rows"
@@ -4076,27 +4167,12 @@ proc removerow {row} {
}
incr commitidx($curview) -1
- set rowidlist [lreplace $rowidlist $row $row]
- set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
- if {$kids ne {}} {
- set offs [lindex $rowoffsets $row]
- set offs [lreplace $offs end end]
- lset rowoffsets $row $offs
- }
-
- set rowrangelist [lreplace $rowrangelist $row $row]
- if {[llength $kids] > 0} {
- set ranges [lindex $rowrangelist $row]
- if {[lindex $ranges end-1] eq $id} {
- set ranges [lreplace $ranges end-1 end]
- lset rowrangelist $row $ranges
- }
+ if {$row < [llength $rowidlist]} {
+ set rowidlist [lreplace $rowidlist $row $row]
+ set rowisopt [lreplace $rowisopt $row $row]
+ set rowfinal [lreplace $rowfinal $row $row]
}
- catch {unset rowchk}
-
- incr rowlaidout -1
- incr rowoptim -1
incr numcommits -1
if {[info exists selectedline] && $selectedline > $row} {
@@ -4116,20 +4192,30 @@ proc settextcursor {c} {
set curtextcursor $c
}
-proc nowbusy {what} {
- global isbusy
+proc nowbusy {what {name {}}} {
+ global isbusy busyname statusw
if {[array names isbusy] eq {}} {
. config -cursor watch
settextcursor watch
}
set isbusy($what) 1
+ set busyname($what) $name
+ if {$name ne {}} {
+ $statusw conf -text $name
+ }
}
proc notbusy {what} {
- global isbusy maincursor textcursor
+ global isbusy maincursor textcursor busyname statusw
- catch {unset isbusy($what)}
+ catch {
+ unset isbusy($what)
+ if {$busyname($what) ne {} &&
+ [$statusw cget -text] eq $busyname($what)} {
+ $statusw conf -text {}
+ }
+ }
if {[array names isbusy] eq {}} {
. config -cursor $maincursor
settextcursor $textcursor
@@ -4138,11 +4224,11 @@ proc notbusy {what} {
proc findmatches {f} {
global findtype findstring
- if {$findtype == "Regexp"} {
+ if {$findtype == [mc "Regexp"]} {
set matches [regexp -indices -all -inline $findstring $f]
} else {
set fs $findstring
- if {$findtype == "IgnCase"} {
+ if {$findtype == [mc "IgnCase"]} {
set f [string tolower $f]
set fs [string tolower $fs]
}
@@ -4157,153 +4243,154 @@ proc findmatches {f} {
return $matches
}
-proc dofind {{rev 0}} {
+proc dofind {{dirn 1} {wrap 1}} {
global findstring findstartline findcurline selectedline numcommits
+ global gdttype filehighlight fh_serial find_dirn findallowwrap
- unmarkmatches
- cancel_next_highlight
+ if {[info exists find_dirn]} {
+ if {$find_dirn == $dirn} return
+ stopfinding
+ }
focus .
if {$findstring eq {} || $numcommits == 0} return
if {![info exists selectedline]} {
- set findstartline [lindex [visiblerows] $rev]
+ set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
} else {
set findstartline $selectedline
}
set findcurline $findstartline
- nowbusy finding
- if {!$rev} {
- run findmore
- } else {
- if {$findcurline == 0} {
- set findcurline $numcommits
- }
- incr findcurline -1
- run findmorerev
+ nowbusy finding [mc "Searching"]
+ if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
+ after cancel do_file_hl $fh_serial
+ do_file_hl $fh_serial
}
+ set find_dirn $dirn
+ set findallowwrap $wrap
+ run findmore
}
-proc findnext {restart} {
- global findcurline
- if {![info exists findcurline]} {
- if {$restart} {
- dofind
- } else {
- bell
- }
- } else {
- run findmore
- nowbusy finding
- }
-}
+proc stopfinding {} {
+ global find_dirn findcurline fprogcoord
-proc findprev {} {
- global findcurline
- if {![info exists findcurline]} {
- dofind 1
- } else {
- run findmorerev
- nowbusy finding
+ if {[info exists find_dirn]} {
+ unset find_dirn
+ unset findcurline
+ notbusy finding
+ set fprogcoord 0
+ adjustprogress
}
}
proc findmore {} {
- global commitdata commitinfo numcommits findstring findpattern findloc
+ global commitdata commitinfo numcommits findpattern findloc
global findstartline findcurline displayorder
+ global find_dirn gdttype fhighlights fprogcoord
+ global findallowwrap
- set fldtypes {Headline Author Date Committer CDate Comments}
- set l [expr {$findcurline + 1}]
- if {$l >= $numcommits} {
- set l 0
+ if {![info exists find_dirn]} {
+ return 0
}
- if {$l <= $findstartline} {
- set lim [expr {$findstartline + 1}]
+ set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
+ set l $findcurline
+ set moretodo 0
+ if {$find_dirn > 0} {
+ incr l
+ if {$l >= $numcommits} {
+ set l 0
+ }
+ if {$l <= $findstartline} {
+ set lim [expr {$findstartline + 1}]
+ } else {
+ set lim $numcommits
+ set moretodo $findallowwrap
+ }
} else {
- set lim $numcommits
- }
- if {$lim - $l > 500} {
- set lim [expr {$l + 500}]
- }
- set last 0
- for {} {$l < $lim} {incr l} {
- set id [lindex $displayorder $l]
- # shouldn't happen unless git log doesn't give all the commits...
- if {![info exists commitdata($id)]} continue
- if {![doesmatch $commitdata($id)]} continue
- if {![info exists commitinfo($id)]} {
- getcommit $id
+ if {$l == 0} {
+ set l $numcommits
}
- set info $commitinfo($id)
- foreach f $info ty $fldtypes {
- if {($findloc eq "All fields" || $findloc eq $ty) &&
- [doesmatch $f]} {
- findselectline $l
- notbusy finding
- return 0
+ incr l -1
+ if {$l >= $findstartline} {
+ set lim [expr {$findstartline - 1}]
+ } else {
+ set lim -1
+ set moretodo $findallowwrap
+ }
+ }
+ set n [expr {($lim - $l) * $find_dirn}]
+ if {$n > 500} {
+ set n 500
+ set moretodo 1
+ }
+ set found 0
+ set domore 1
+ if {$gdttype eq [mc "containing:"]} {
+ for {} {$n > 0} {incr n -1; incr l $find_dirn} {
+ set id [lindex $displayorder $l]
+ # shouldn't happen unless git log doesn't give all the commits...
+ if {![info exists commitdata($id)]} continue
+ if {![doesmatch $commitdata($id)]} continue
+ if {![info exists commitinfo($id)]} {
+ getcommit $id
+ }
+ set info $commitinfo($id)
+ foreach f $info ty $fldtypes {
+ if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
+ [doesmatch $f]} {
+ set found 1
+ break
+ }
}
+ if {$found} break
}
- }
- if {$l == $findstartline + 1} {
- bell
- unset findcurline
- notbusy finding
- return 0
- }
- set findcurline [expr {$l - 1}]
- return 1
-}
-
-proc findmorerev {} {
- global commitdata commitinfo numcommits findstring findpattern findloc
- global findstartline findcurline displayorder
-
- set fldtypes {Headline Author Date Committer CDate Comments}
- set l $findcurline
- if {$l == 0} {
- set l $numcommits
- }
- incr l -1
- if {$l >= $findstartline} {
- set lim [expr {$findstartline - 1}]
} else {
- set lim -1
- }
- if {$l - $lim > 500} {
- set lim [expr {$l - 500}]
- }
- set last 0
- for {} {$l > $lim} {incr l -1} {
- set id [lindex $displayorder $l]
- if {![doesmatch $commitdata($id)]} continue
- if {![info exists commitinfo($id)]} {
- getcommit $id
- }
- set info $commitinfo($id)
- foreach f $info ty $fldtypes {
- if {($findloc eq "All fields" || $findloc eq $ty) &&
- [doesmatch $f]} {
- findselectline $l
- notbusy finding
- return 0
+ for {} {$n > 0} {incr n -1; incr l $find_dirn} {
+ set id [lindex $displayorder $l]
+ if {![info exists fhighlights($l)]} {
+ askfilehighlight $l $id
+ if {$domore} {
+ set domore 0
+ set findcurline [expr {$l - $find_dirn}]
+ }
+ } elseif {$fhighlights($l)} {
+ set found $domore
+ break
}
}
}
- if {$l == -1} {
- bell
+ if {$found || ($domore && !$moretodo)} {
unset findcurline
+ unset find_dirn
notbusy finding
+ set fprogcoord 0
+ adjustprogress
+ if {$found} {
+ findselectline $l
+ } else {
+ bell
+ }
return 0
}
- set findcurline [expr {$l + 1}]
- return 1
+ if {!$domore} {
+ flushhighlights
+ } else {
+ set findcurline [expr {$l - $find_dirn}]
+ }
+ set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
+ if {$n < 0} {
+ incr n $numcommits
+ }
+ set fprogcoord [expr {$n * 1.0 / $numcommits}]
+ adjustprogress
+ return $domore
}
proc findselectline {l} {
- global findloc commentend ctext findcurline markingmatches
+ global findloc commentend ctext findcurline markingmatches gdttype
set markingmatches 1
set findcurline $l
selectline $l 1
- if {$findloc == "All fields" || $findloc == "Comments"} {
+ if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
# highlight the matches in the comments
set f [$ctext get 1.0 $commentend]
set matches [findmatches $f]
@@ -4341,12 +4428,11 @@ proc markmatches {canv l str tag matches font row} {
}
proc unmarkmatches {} {
- global findids markingmatches findcurline
+ global markingmatches
allcanvs delete matches
- catch {unset findids}
set markingmatches 0
- catch {unset findcurline}
+ stopfinding
}
proc selcanvline {w x y} {
@@ -4382,7 +4468,7 @@ proc commit_descriptor {p} {
# append some text to the ctext widget, and make any SHA1 ID
# that we know about be a clickable link.
proc appendwithlinks {text tags} {
- global ctext commitrow linknum curview
+ global ctext commitrow linknum curview pendinglinks
set start [$ctext index "end - 1c"]
$ctext insert end $text $tags
@@ -4391,17 +4477,49 @@ proc appendwithlinks {text tags} {
set s [lindex $l 0]
set e [lindex $l 1]
set linkid [string range $text $s $e]
- if {![info exists commitrow($curview,$linkid)]} continue
incr e
- $ctext tag add link "$start + $s c" "$start + $e c"
+ $ctext tag delete link$linknum
$ctext tag add link$linknum "$start + $s c" "$start + $e c"
- $ctext tag bind link$linknum <1> \
- [list selectline $commitrow($curview,$linkid) 1]
+ setlink $linkid link$linknum
incr linknum
}
- $ctext tag conf link -foreground blue -underline 1
- $ctext tag bind link <Enter> { %W configure -cursor hand2 }
- $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+}
+
+proc setlink {id lk} {
+ global curview commitrow ctext pendinglinks commitinterest
+
+ if {[info exists commitrow($curview,$id)]} {
+ $ctext tag conf $lk -foreground blue -underline 1
+ $ctext tag bind $lk <1> [list selectline $commitrow($curview,$id) 1]
+ $ctext tag bind $lk <Enter> {linkcursor %W 1}
+ $ctext tag bind $lk <Leave> {linkcursor %W -1}
+ } else {
+ lappend pendinglinks($id) $lk
+ lappend commitinterest($id) {makelink %I}
+ }
+}
+
+proc makelink {id} {
+ global pendinglinks
+
+ if {![info exists pendinglinks($id)]} return
+ foreach lk $pendinglinks($id) {
+ setlink $id $lk
+ }
+ unset pendinglinks($id)
+}
+
+proc linkcursor {w inc} {
+ global linkentercount curtextcursor
+
+ if {[incr linkentercount $inc] > 0} {
+ $w configure -cursor hand2
+ } else {
+ $w configure -cursor $curtextcursor
+ if {$linkentercount < 0} {
+ set linkentercount 0
+ }
+ }
}
proc viewnextline {dir} {
@@ -4448,15 +4566,7 @@ proc appendrefs {pos ids var} {
$ctext tag delete $lk
$ctext insert $pos $sep
$ctext insert $pos [lindex $ti 0] $lk
- if {[info exists commitrow($curview,$id)]} {
- $ctext tag conf $lk -foreground blue
- $ctext tag bind $lk <1> \
- [list selectline $commitrow($curview,$id) 1]
- $ctext tag conf $lk -underline 1
- $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
- $ctext tag bind $lk <Leave> \
- { %W configure -cursor $curtextcursor }
- }
+ setlink $id $lk
set sep ", "
}
}
@@ -4514,9 +4624,27 @@ proc dispnexttag {} {
}
}
+proc make_secsel {l} {
+ global linehtag linentag linedtag canv canv2 canv3
+
+ if {![info exists linehtag($l)]} return
+ $canv delete secsel
+ set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
+ -tags secsel -fill [$canv cget -selectbackground]]
+ $canv lower $t
+ $canv2 delete secsel
+ set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
+ -tags secsel -fill [$canv2 cget -selectbackground]]
+ $canv2 lower $t
+ $canv3 delete secsel
+ set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
+ -tags secsel -fill [$canv3 cget -selectbackground]]
+ $canv3 lower $t
+}
+
proc selectline {l isnew} {
- global canv canv2 canv3 ctext commitinfo selectedline
- global displayorder linehtag linentag linedtag
+ global canv ctext commitinfo selectedline
+ global displayorder
global canvy0 linespc parentlist children curview
global currentid sha1entry
global commentend idtags linknum
@@ -4526,8 +4654,8 @@ proc selectline {l isnew} {
catch {unset pending_select}
$canv delete hover
normalline
- cancel_next_highlight
unsel_reflist
+ stopfinding
if {$l < 0 || $l >= $numcommits} return
set y [expr {$canvy0 + $l * $linespc}]
set ymax [lindex [$canv cget -scrollregion] 3]
@@ -4565,19 +4693,7 @@ proc selectline {l isnew} {
drawvisible
}
- if {![info exists linehtag($l)]} return
- $canv delete secsel
- set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
- -tags secsel -fill [$canv cget -selectbackground]]
- $canv lower $t
- $canv2 delete secsel
- set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
- -tags secsel -fill [$canv2 cget -selectbackground]]
- $canv2 lower $t
- $canv3 delete secsel
- set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
- -tags secsel -fill [$canv3 cget -selectbackground]]
- $canv3 lower $t
+ make_secsel $l
if {$isnew} {
addtohistory [list selectline $l 0]
@@ -4598,11 +4714,11 @@ proc selectline {l isnew} {
set linknum 0
set info $commitinfo($id)
set date [formatdate [lindex $info 2]]
- $ctext insert end "Author: [lindex $info 1] $date\n"
+ $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
set date [formatdate [lindex $info 4]]
- $ctext insert end "Committer: [lindex $info 3] $date\n"
+ $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
if {[info exists idtags($id)]} {
- $ctext insert end "Tags:"
+ $ctext insert end [mc "Tags:"]
foreach tag $idtags($id) {
$ctext insert end " $tag"
}
@@ -4619,18 +4735,18 @@ proc selectline {l isnew} {
} else {
set tag m$np
}
- $ctext insert end "Parent: " $tag
+ $ctext insert end "[mc "Parent"]: " $tag
appendwithlinks [commit_descriptor $p] {}
incr np
}
} else {
foreach p $olds {
- append headers "Parent: [commit_descriptor $p]"
+ append headers "[mc "Parent"]: [commit_descriptor $p]"
}
}
foreach c $children($curview,$id) {
- append headers "Child: [commit_descriptor $c]"
+ append headers "[mc "Child"]: [commit_descriptor $c]"
}
# make anything that looks like a SHA1 ID be a clickable link
@@ -4639,13 +4755,13 @@ proc selectline {l isnew} {
if {![info exists allcommits]} {
getallcommits
}
- $ctext insert end "Branch: "
+ $ctext insert end "[mc "Branch"]: "
$ctext mark set branch "end -1c"
$ctext mark gravity branch left
- $ctext insert end "\nFollows: "
+ $ctext insert end "\n[mc "Follows"]: "
$ctext mark set follows "end -1c"
$ctext mark gravity follows left
- $ctext insert end "\nPrecedes: "
+ $ctext insert end "\n[mc "Precedes"]: "
$ctext mark set precedes "end -1c"
$ctext mark gravity precedes left
$ctext insert end "\n"
@@ -4662,7 +4778,7 @@ proc selectline {l isnew} {
$ctext conf -state disabled
set commentend [$ctext index "end - 1c"]
- init_flist "Comments"
+ init_flist [mc "Comments"]
if {$cmitmode eq "tree"} {
gettree $id
} elseif {[llength $olds] <= 1} {
@@ -4720,7 +4836,6 @@ proc unselectline {} {
catch {unset currentid}
allcanvs delete secsel
rhighlight_none
- cancel_next_highlight
}
proc reselectline {} {
@@ -4889,6 +5004,7 @@ proc showfile {f} {
$ctext insert end "$f\n" filesep
$ctext config -state disabled
$ctext yview $commentend
+ settabs 0
}
proc getblobline {bf id} {
@@ -4914,22 +5030,27 @@ proc getblobline {bf id} {
}
proc mergediff {id l} {
- global diffmergeid diffopts mdifffd
+ global diffmergeid mdifffd
global diffids
+ global diffcontext
global parentlist
+ global limitdiffs viewfiles curview
set diffmergeid $id
set diffids $id
# this doesn't seem to actually affect anything...
- set env(GIT_DIFF_OPTS) $diffopts
- set cmd [concat | git diff-tree --no-commit-id --cc $id]
+ set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
+ if {$limitdiffs && $viewfiles($curview) ne {}} {
+ set cmd [concat $cmd -- $viewfiles($curview)]
+ }
if {[catch {set mdf [open $cmd r]} err]} {
- error_popup "Error getting merge diffs: $err"
+ error_popup "[mc "Error getting merge diffs:"] $err"
return
}
fconfigure $mdf -blocking 0
set mdifffd($id) $mdf
set np [llength [lindex $parentlist $l]]
+ settabs $np
filerun $mdf [list getmergediffline $mdf $id $np]
}
@@ -5007,6 +5128,7 @@ proc getmergediffline {mdf id np} {
proc startdiff {ids} {
global treediffs diffids treepending diffmergeid nullid nullid2
+ settabs 1
set diffids $ids
catch {unset diffmergeid}
if {![info exists treediffs($ids)] ||
@@ -5020,8 +5142,27 @@ proc startdiff {ids} {
}
}
+proc path_filter {filter name} {
+ foreach p $filter {
+ set l [string length $p]
+ if {[string index $p end] eq "/"} {
+ if {[string compare -length $l $p $name] == 0} {
+ return 1
+ }
+ } else {
+ if {[string compare -length $l $p $name] == 0 &&
+ ([string length $name] == $l ||
+ [string index $name $l] eq "/")} {
+ return 1
+ }
+ }
+ }
+ return 0
+}
+
proc addtocflist {ids} {
- global treediffs cflist
+ global treediffs
+
add_flist $treediffs($ids)
getblobdiffs $ids
}
@@ -5078,7 +5219,7 @@ proc gettreediffs {ids} {
proc gettreediffline {gdtf ids} {
global treediff treediffs treepending diffids diffmergeid
- global cmitmode
+ global cmitmode viewfiles curview limitdiffs
set nr 0
while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
@@ -5095,7 +5236,17 @@ proc gettreediffline {gdtf ids} {
return [expr {$nr >= 1000? 2: 1}]
}
close $gdtf
- set treediffs($ids) $treediff
+ if {$limitdiffs && $viewfiles($curview) ne {}} {
+ set flist {}
+ foreach f $treediff {
+ if {[path_filter $viewfiles($curview) $f]} {
+ lappend flist $f
+ }
+ }
+ set treediffs($ids) $flist
+ } else {
+ set treediffs($ids) $treediff
+ }
unset treepending
if {$cmitmode eq "tree"} {
gettree $diffids
@@ -5125,13 +5276,25 @@ proc diffcontextchange {n1 n2 op} {
}
}
+proc changeignorespace {} {
+ reselectline
+}
+
proc getblobdiffs {ids} {
- global diffopts blobdifffd diffids env
+ global blobdifffd diffids env
global diffinhdr treediffs
global diffcontext
+ global ignorespace
+ global limitdiffs viewfiles curview
- set env(GIT_DIFF_OPTS) $diffopts
- if {[catch {set bdf [open [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] r]} err]} {
+ set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
+ if {$ignorespace} {
+ append cmd " -w"
+ }
+ if {$limitdiffs && $viewfiles($curview) ne {}} {
+ set cmd [concat $cmd -- $viewfiles($curview)]
+ }
+ if {[catch {set bdf [open $cmd r]} err]} {
puts "error getting diffs: $err"
return
}
@@ -5215,8 +5378,7 @@ proc getblobdiffline {bdf ids} {
set diffinhdr 0
} elseif {$diffinhdr} {
- if {![string compare -length 12 "rename from " $line] ||
- ![string compare -length 10 "copy from " $line]} {
+ if {![string compare -length 12 "rename from " $line]} {
set fname [string range $line [expr 6 + [string first " from " $line] ] end]
if {[string index $fname 0] eq "\""} {
set fname [lindex $fname 0]
@@ -5297,6 +5459,7 @@ proc nextfile {} {
proc clear_ctext {{first 1.0}} {
global ctext smarktop smarkbot
+ global pendinglinks
set l [lindex [split $first .] 0]
if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
@@ -5306,6 +5469,26 @@ proc clear_ctext {{first 1.0}} {
set smarkbot $l
}
$ctext delete $first end
+ if {$first eq "1.0"} {
+ catch {unset pendinglinks}
+ }
+}
+
+proc settabs {{firstab {}}} {
+ global firsttabstop tabstop ctext have_tk85
+
+ if {$firstab ne {} && $have_tk85} {
+ set firsttabstop $firstab
+ }
+ set w [font measure textfont "0"]
+ if {$firsttabstop != 0} {
+ $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
+ [expr {($firsttabstop + 2 * $tabstop) * $w}]]
+ } elseif {$have_tk85 || $tabstop != 8} {
+ $ctext conf -tabs [expr {$tabstop * $w}]
+ } else {
+ $ctext conf -tabs {}
+ }
}
proc incrsearch {name ix op} {
@@ -5428,11 +5611,11 @@ proc scrolltext {f0 f1} {
}
proc setcoords {} {
- global linespc charspc canvx0 canvy0 mainfont
+ global linespc charspc canvx0 canvy0
global xspc1 xspc2 lthickness
- set linespc [font metrics $mainfont -linespace]
- set charspc [font measure $mainfont "m"]
+ set linespc [font metrics mainfont -linespace]
+ set charspc [font measure mainfont "m"]
set canvy0 [expr {int(3 + 0.5 * $linespc)}]
set canvx0 [expr {int(3 + 0.5 * $linespc)}]
set lthickness [expr {int($linespc / 9) + 1}]
@@ -5457,26 +5640,75 @@ proc redisplay {} {
}
}
+proc parsefont {f n} {
+ global fontattr
+
+ set fontattr($f,family) [lindex $n 0]
+ set s [lindex $n 1]
+ if {$s eq {} || $s == 0} {
+ set s 10
+ } elseif {$s < 0} {
+ set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
+ }
+ set fontattr($f,size) $s
+ set fontattr($f,weight) normal
+ set fontattr($f,slant) roman
+ foreach style [lrange $n 2 end] {
+ switch -- $style {
+ "normal" -
+ "bold" {set fontattr($f,weight) $style}
+ "roman" -
+ "italic" {set fontattr($f,slant) $style}
+ }
+ }
+}
+
+proc fontflags {f {isbold 0}} {
+ global fontattr
+
+ return [list -family $fontattr($f,family) -size $fontattr($f,size) \
+ -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
+ -slant $fontattr($f,slant)]
+}
+
+proc fontname {f} {
+ global fontattr
+
+ set n [list $fontattr($f,family) $fontattr($f,size)]
+ if {$fontattr($f,weight) eq "bold"} {
+ lappend n "bold"
+ }
+ if {$fontattr($f,slant) eq "italic"} {
+ lappend n "italic"
+ }
+ return $n
+}
+
proc incrfont {inc} {
global mainfont textfont ctext canv phase cflist showrefstop
- global charspc tabstop
- global stopped entries
+ global stopped entries fontattr
+
unmarkmatches
- set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
- set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
+ set s $fontattr(mainfont,size)
+ incr s $inc
+ if {$s < 1} {
+ set s 1
+ }
+ set fontattr(mainfont,size) $s
+ font config mainfont -size $s
+ font config mainfontbold -size $s
+ set mainfont [fontname mainfont]
+ set s $fontattr(textfont,size)
+ incr s $inc
+ if {$s < 1} {
+ set s 1
+ }
+ set fontattr(textfont,size) $s
+ font config textfont -size $s
+ font config textfontbold -size $s
+ set textfont [fontname textfont]
setcoords
- $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
- $cflist conf -font $textfont
- $ctext tag conf filesep -font [concat $textfont bold]
- foreach e $entries {
- $e conf -font $mainfont
- }
- if {$phase eq "getcommits"} {
- $canv itemconf textitems -font $mainfont
- }
- if {[info exists showrefstop] && [winfo exists $showrefstop]} {
- $showrefstop.list conf -font $mainfont
- }
+ settabs
redisplay
}
@@ -5497,9 +5729,9 @@ proc sha1change {n1 n2 op} {
}
if {[$sha1but cget -state] == $state} return
if {$state == "normal"} {
- $sha1but conf -state normal -relief raised -text "Goto: "
+ $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
} else {
- $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
+ $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
}
}
@@ -5524,7 +5756,7 @@ proc gotocommit {} {
}
if {$matches ne {}} {
if {[llength $matches] > 1} {
- error_popup "Short SHA1 id $id is ambiguous"
+ error_popup [mc "Short SHA1 id %s is ambiguous" $id]
return
}
set id [lindex $matches 0]
@@ -5536,11 +5768,11 @@ proc gotocommit {} {
return
}
if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
- set type "SHA1 id"
+ set msg [mc "SHA1 id %s is not known" $sha1string]
} else {
- set type "Tag/Head"
+ set msg [mc "Tag/Head %s is not known" $sha1string]
}
- error_popup "$type $sha1string is not known"
+ error_popup $msg
}
proc lineenter {x y id} {
@@ -5587,7 +5819,7 @@ proc lineleave {id} {
proc linehover {} {
global hoverx hovery hoverid hovertimer
global canv linespc lthickness
- global commitinfo mainfont
+ global commitinfo
set text [lindex $commitinfo($hoverid) 0]
set ymax [lindex [$canv cget -scrollregion] 3]
@@ -5597,13 +5829,13 @@ proc linehover {} {
set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
set x0 [expr {$x - 2 * $lthickness}]
set y0 [expr {$y - 2 * $lthickness}]
- set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
+ set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
set y1 [expr {$y + $linespc + 2 * $lthickness}]
set t [$canv create rectangle $x0 $y0 $x1 $y1 \
-fill \#ffff80 -outline black -width 1 -tags hover]
$canv raise $t
set t [$canv create text $x $y -anchor nw -text $text -tags hover \
- -font $mainfont]
+ -font mainfont]
$canv raise $t
}
@@ -5641,7 +5873,7 @@ proc arrowjump {id n y} {
}
proc lineclick {x y id isnew} {
- global ctext commitinfo children canv thickerline curview
+ global ctext commitinfo children canv thickerline curview commitrow
if {![info exists commitinfo($id)] && ![getcommit $id]} return
unmarkmatches
@@ -5669,32 +5901,30 @@ proc lineclick {x y id isnew} {
# fill the details pane with info about this line
$ctext conf -state normal
clear_ctext
- $ctext tag conf link -foreground blue -underline 1
- $ctext tag bind link <Enter> { %W configure -cursor hand2 }
- $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
- $ctext insert end "Parent:\t"
- $ctext insert end $id [list link link0]
- $ctext tag bind link0 <1> [list selbyid $id]
+ settabs 0
+ $ctext insert end "[mc "Parent"]:\t"
+ $ctext insert end $id link0
+ setlink $id link0
set info $commitinfo($id)
$ctext insert end "\n\t[lindex $info 0]\n"
- $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
+ $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
set date [formatdate [lindex $info 2]]
- $ctext insert end "\tDate:\t$date\n"
+ $ctext insert end "\t[mc "Date"]:\t$date\n"
set kids $children($curview,$id)
if {$kids ne {}} {
- $ctext insert end "\nChildren:"
+ $ctext insert end "\n[mc "Children"]:"
set i 0
foreach child $kids {
incr i
if {![info exists commitinfo($child)] && ![getcommit $child]} continue
set info $commitinfo($child)
$ctext insert end "\n\t"
- $ctext insert end $child [list link link$i]
- $ctext tag bind link$i <1> [list selbyid $child]
+ $ctext insert end $child link$i
+ setlink $child link$i
$ctext insert end "\n\t[lindex $info 0]"
- $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
+ $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
set date [formatdate [lindex $info 2]]
- $ctext insert end "\n\tDate:\t$date\n"
+ $ctext insert end "\n\t[mc "Date"]:\t$date\n"
}
}
$ctext conf -state disabled
@@ -5729,6 +5959,7 @@ proc rowmenu {x y id} {
global rowctxmenu commitrow selectedline rowmenuid curview
global nullid nullid2 fakerowmenu mainhead
+ stopfinding
set rowmenuid $id
if {![info exists selectedline]
|| $commitrow($curview,$id) eq $selectedline} {
@@ -5738,13 +5969,13 @@ proc rowmenu {x y id} {
}
if {$id ne $nullid && $id ne $nullid2} {
set menu $rowctxmenu
- $menu entryconfigure 7 -label "Reset $mainhead branch to here"
+ $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
} else {
set menu $fakerowmenu
}
- $menu entryconfigure "Diff this*" -state $state
- $menu entryconfigure "Diff selected*" -state $state
- $menu entryconfigure "Make patch" -state $state
+ $menu entryconfigure [mc "Diff this -> selected"] -state $state
+ $menu entryconfigure [mc "Diff selected -> this"] -state $state
+ $menu entryconfigure [mc "Make patch"] -state $state
tk_popup $menu $x $y
}
@@ -5769,18 +6000,15 @@ proc doseldiff {oldid newid} {
$ctext conf -state normal
clear_ctext
- init_flist "Top"
- $ctext insert end "From "
- $ctext tag conf link -foreground blue -underline 1
- $ctext tag bind link <Enter> { %W configure -cursor hand2 }
- $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
- $ctext tag bind link0 <1> [list selbyid $oldid]
- $ctext insert end $oldid [list link link0]
+ init_flist [mc "Top"]
+ $ctext insert end "[mc "From"] "
+ $ctext insert end $oldid link0
+ setlink $oldid link0
$ctext insert end "\n "
$ctext insert end [lindex $commitinfo($oldid) 0]
- $ctext insert end "\n\nTo "
- $ctext tag bind link1 <1> [list selbyid $newid]
- $ctext insert end $newid [list link link1]
+ $ctext insert end "\n\n[mc "To"] "
+ $ctext insert end $newid link1
+ setlink $newid link1
$ctext insert end "\n "
$ctext insert end [lindex $commitinfo($newid) 0]
$ctext insert end "\n"
@@ -5801,9 +6029,9 @@ proc mkpatch {} {
set patchtop $top
catch {destroy $top}
toplevel $top
- label $top.title -text "Generate patch"
+ label $top.title -text [mc "Generate patch"]
grid $top.title - -pady 10
- label $top.from -text "From:"
+ label $top.from -text [mc "From:"]
entry $top.fromsha1 -width 40 -relief flat
$top.fromsha1 insert 0 $oldid
$top.fromsha1 conf -state readonly
@@ -5812,7 +6040,7 @@ proc mkpatch {} {
$top.fromhead insert 0 $oldhead
$top.fromhead conf -state readonly
grid x $top.fromhead -sticky w
- label $top.to -text "To:"
+ label $top.to -text [mc "To:"]
entry $top.tosha1 -width 40 -relief flat
$top.tosha1 insert 0 $newid
$top.tosha1 conf -state readonly
@@ -5821,16 +6049,16 @@ proc mkpatch {} {
$top.tohead insert 0 $newhead
$top.tohead conf -state readonly
grid x $top.tohead -sticky w
- button $top.rev -text "Reverse" -command mkpatchrev -padx 5
+ button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
grid $top.rev x -pady 10
- label $top.flab -text "Output file:"
+ label $top.flab -text [mc "Output file:"]
entry $top.fname -width 60
$top.fname insert 0 [file normalize "patch$patchnum.patch"]
incr patchnum
grid $top.flab $top.fname -sticky w
frame $top.buts
- button $top.buts.gen -text "Generate" -command mkpatchgo
- button $top.buts.can -text "Cancel" -command mkpatchcan
+ button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
+ button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
grid $top.buts.gen $top.buts.can
grid columnconfigure $top.buts 0 -weight 1 -uniform a
grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -5861,9 +6089,11 @@ proc mkpatchgo {} {
set newid [$patchtop.tosha1 get]
set fname [$patchtop.fname get]
set cmd [diffcmd [list $oldid $newid] -p]
+ # trim off the initial "|"
+ set cmd [lrange $cmd 1 end]
lappend cmd >$fname &
if {[catch {eval exec $cmd} err]} {
- error_popup "Error creating patch: $err"
+ error_popup "[mc "Error creating patch:"] $err"
}
catch {destroy $patchtop}
unset patchtop
@@ -5883,9 +6113,9 @@ proc mktag {} {
set mktagtop $top
catch {destroy $top}
toplevel $top
- label $top.title -text "Create tag"
+ label $top.title -text [mc "Create tag"]
grid $top.title - -pady 10
- label $top.id -text "ID:"
+ label $top.id -text [mc "ID:"]
entry $top.sha1 -width 40 -relief flat
$top.sha1 insert 0 $rowmenuid
$top.sha1 conf -state readonly
@@ -5894,12 +6124,12 @@ proc mktag {} {
$top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
$top.head conf -state readonly
grid x $top.head -sticky w
- label $top.tlab -text "Tag name:"
+ label $top.tlab -text [mc "Tag name:"]
entry $top.tag -width 60
grid $top.tlab $top.tag -sticky w
frame $top.buts
- button $top.buts.gen -text "Create" -command mktaggo
- button $top.buts.can -text "Cancel" -command mktagcan
+ button $top.buts.gen -text [mc "Create"] -command mktaggo
+ button $top.buts.can -text [mc "Cancel"] -command mktagcan
grid $top.buts.gen $top.buts.can
grid columnconfigure $top.buts 0 -weight 1 -uniform a
grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -5913,21 +6143,17 @@ proc domktag {} {
set id [$mktagtop.sha1 get]
set tag [$mktagtop.tag get]
if {$tag == {}} {
- error_popup "No tag name specified"
+ error_popup [mc "No tag name specified"]
return
}
if {[info exists tagids($tag)]} {
- error_popup "Tag \"$tag\" already exists"
+ error_popup [mc "Tag \"%s\" already exists" $tag]
return
}
if {[catch {
- set dir [gitdir]
- set fname [file join $dir "refs/tags" $tag]
- set f [open $fname w]
- puts $f $id
- close $f
+ exec git tag $tag $id
} err]} {
- error_popup "Error creating tag: $err"
+ error_popup "[mc "Error creating tag:"] $err"
return
}
@@ -5941,7 +6167,7 @@ proc domktag {} {
proc redrawtags {id} {
global canv linehtag commitrow idpos selectedline curview
- global mainfont canvxmax iddrawn
+ global canvxmax iddrawn
if {![info exists commitrow($curview,$id)]} return
if {![info exists iddrawn($id)]} return
@@ -5950,7 +6176,7 @@ proc redrawtags {id} {
set xt [eval drawtags $id $idpos($id)]
$canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
- set xr [expr {$xt + [font measure $mainfont $text]}]
+ set xr [expr {$xt + [font measure mainfont $text]}]
if {$xr > $canvxmax} {
set canvxmax $xr
setcanvscroll
@@ -5980,9 +6206,9 @@ proc writecommit {} {
set wrcomtop $top
catch {destroy $top}
toplevel $top
- label $top.title -text "Write commit to file"
+ label $top.title -text [mc "Write commit to file"]
grid $top.title - -pady 10
- label $top.id -text "ID:"
+ label $top.id -text [mc "ID:"]
entry $top.sha1 -width 40 -relief flat
$top.sha1 insert 0 $rowmenuid
$top.sha1 conf -state readonly
@@ -5991,16 +6217,16 @@ proc writecommit {} {
$top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
$top.head conf -state readonly
grid x $top.head -sticky w
- label $top.clab -text "Command:"
+ label $top.clab -text [mc "Command:"]
entry $top.cmd -width 60 -textvariable wrcomcmd
grid $top.clab $top.cmd -sticky w -pady 10
- label $top.flab -text "Output file:"
+ label $top.flab -text [mc "Output file:"]
entry $top.fname -width 60
$top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
grid $top.flab $top.fname -sticky w
frame $top.buts
- button $top.buts.gen -text "Write" -command wrcomgo
- button $top.buts.can -text "Cancel" -command wrcomcan
+ button $top.buts.gen -text [mc "Write"] -command wrcomgo
+ button $top.buts.can -text [mc "Cancel"] -command wrcomcan
grid $top.buts.gen $top.buts.can
grid columnconfigure $top.buts 0 -weight 1 -uniform a
grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -6015,7 +6241,7 @@ proc wrcomgo {} {
set cmd "echo $id | [$wrcomtop.cmd get]"
set fname [$wrcomtop.fname get]
if {[catch {exec sh -c $cmd >$fname &} err]} {
- error_popup "Error writing commit: $err"
+ error_popup "[mc "Error writing commit:"] $err"
}
catch {destroy $wrcomtop}
unset wrcomtop
@@ -6034,19 +6260,19 @@ proc mkbranch {} {
set top .makebranch
catch {destroy $top}
toplevel $top
- label $top.title -text "Create new branch"
+ label $top.title -text [mc "Create new branch"]
grid $top.title - -pady 10
- label $top.id -text "ID:"
+ label $top.id -text [mc "ID:"]
entry $top.sha1 -width 40 -relief flat
$top.sha1 insert 0 $rowmenuid
$top.sha1 conf -state readonly
grid $top.id $top.sha1 -sticky w
- label $top.nlab -text "Name:"
+ label $top.nlab -text [mc "Name:"]
entry $top.name -width 40
grid $top.nlab $top.name -sticky w
frame $top.buts
- button $top.buts.go -text "Create" -command [list mkbrgo $top]
- button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
+ button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
+ button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
grid $top.buts.go $top.buts.can
grid columnconfigure $top.buts 0 -weight 1 -uniform a
grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -6060,7 +6286,7 @@ proc mkbrgo {top} {
set name [$top.name get]
set id [$top.sha1 get]
if {$name eq {}} {
- error_popup "Please specify a name for the new branch"
+ error_popup [mc "Please specify a name for the new branch"]
return
}
catch {destroy $top}
@@ -6089,11 +6315,12 @@ proc cherrypick {} {
set oldhead [exec git rev-parse HEAD]
set dheads [descheads $rowmenuid]
if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
- set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
- included in branch $mainhead -- really re-apply it?"]
+ set ok [confirm_popup [mc "Commit %s is already\
+ included in branch %s -- really re-apply it?" \
+ [string range $rowmenuid 0 7] $mainhead]]
if {!$ok} return
}
- nowbusy cherrypick
+ nowbusy cherrypick [mc "Cherry-picking"]
update
# Unfortunately git-cherry-pick writes stuff to stderr even when
# no error occurs, and exec takes that as an indication of error...
@@ -6105,7 +6332,7 @@ proc cherrypick {} {
set newhead [exec git rev-parse HEAD]
if {$newhead eq $oldhead} {
notbusy cherrypick
- error_popup "No changes committed"
+ error_popup [mc "No changes committed"]
return
}
addnewchild $newhead $oldhead
@@ -6123,34 +6350,33 @@ proc cherrypick {} {
proc resethead {} {
global mainheadid mainhead rowmenuid confirm_ok resettype
- global showlocalchanges
set confirm_ok 0
set w ".confirmreset"
toplevel $w
wm transient $w .
- wm title $w "Confirm reset"
+ wm title $w [mc "Confirm reset"]
message $w.m -text \
- "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
+ [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
-justify center -aspect 1000
pack $w.m -side top -fill x -padx 20 -pady 20
frame $w.f -relief sunken -border 2
- message $w.f.rt -text "Reset type:" -aspect 1000
+ message $w.f.rt -text [mc "Reset type:"] -aspect 1000
grid $w.f.rt -sticky w
set resettype mixed
radiobutton $w.f.soft -value soft -variable resettype -justify left \
- -text "Soft: Leave working tree and index untouched"
+ -text [mc "Soft: Leave working tree and index untouched"]
grid $w.f.soft -sticky w
radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
- -text "Mixed: Leave working tree untouched, reset index"
+ -text [mc "Mixed: Leave working tree untouched, reset index"]
grid $w.f.mixed -sticky w
radiobutton $w.f.hard -value hard -variable resettype -justify left \
- -text "Hard: Reset working tree and index\n(discard ALL local changes)"
+ -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
grid $w.f.hard -sticky w
pack $w.f -side top -fill x
- button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+ button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
pack $w.ok -side left -fill x -padx 20 -pady 20
- button $w.cancel -text Cancel -command "destroy $w"
+ button $w.cancel -text [mc Cancel] -command "destroy $w"
pack $w.cancel -side right -fill x -padx 20 -pady 20
bind $w <Visibility> "grab $w; focus $w"
tkwait window $w
@@ -6160,32 +6386,23 @@ proc resethead {} {
error_popup $err
} else {
dohidelocalchanges
- set w ".resetprogress"
- filerun $fd [list readresetstat $fd $w]
- toplevel $w
- wm transient $w
- wm title $w "Reset progress"
- message $w.m -text "Reset in progress, please wait..." \
- -justify center -aspect 1000
- pack $w.m -side top -fill x -padx 20 -pady 5
- canvas $w.c -width 150 -height 20 -bg white
- $w.c create rect 0 0 0 20 -fill green -tags rect
- pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
- nowbusy reset
+ filerun $fd [list readresetstat $fd]
+ nowbusy reset [mc "Resetting"]
}
}
-proc readresetstat {fd w} {
- global mainhead mainheadid showlocalchanges
+proc readresetstat {fd} {
+ global mainhead mainheadid showlocalchanges rprogcoord
if {[gets $fd line] >= 0} {
if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
- set x [expr {($m * 150) / $n}]
- $w.c coords rect 0 0 $x 20
+ set rprogcoord [expr {1.0 * $m / $n}]
+ adjustprogress
}
return 1
}
- destroy $w
+ set rprogcoord 0
+ adjustprogress
notbusy reset
if {[catch {close $fd} err]} {
error_popup $err
@@ -6209,6 +6426,7 @@ proc readresetstat {fd w} {
proc headmenu {x y id head} {
global headmenuid headmenuhead headctxmenu mainhead
+ stopfinding
set headmenuid $id
set headmenuhead $head
set state normal
@@ -6226,7 +6444,7 @@ proc cobranch {} {
# check the tree is clean first??
set oldmainhead $mainhead
- nowbusy checkout
+ nowbusy checkout [mc "Checking out"]
update
dohidelocalchanges
if {[catch {
@@ -6256,14 +6474,14 @@ proc rmbranch {} {
set id $headmenuid
# this check shouldn't be needed any more...
if {$head eq $mainhead} {
- error_popup "Cannot delete the currently checked-out branch"
+ error_popup [mc "Cannot delete the currently checked-out branch"]
return
}
set dheads [descheads $id]
if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
# the stuff on this branch isn't on any other branch
- if {![confirm_popup "The commits on branch $head aren't on any other\
- branch.\nReally delete branch $head?"]} return
+ if {![confirm_popup [mc "The commits on branch %s aren't on any other\
+ branch.\nReally delete branch %s?" $head $head]]} return
}
nowbusy rmbranch
update
@@ -6282,8 +6500,8 @@ proc rmbranch {} {
# Display a list of tags and heads
proc showrefs {} {
- global showrefstop bgcolor fgcolor selectbgcolor mainfont
- global bglist fglist uifont reflistfilter reflist maincursor
+ global showrefstop bgcolor fgcolor selectbgcolor
+ global bglist fglist reflistfilter reflist maincursor
set top .showrefs
set showrefstop $top
@@ -6293,9 +6511,9 @@ proc showrefs {} {
return
}
toplevel $top
- wm title $top "Tags and heads: [file tail [pwd]]"
+ wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
text $top.list -background $bgcolor -foreground $fgcolor \
- -selectbackground $selectbgcolor -font $mainfont \
+ -selectbackground $selectbgcolor -font mainfont \
-xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
-width 30 -height 20 -cursor $maincursor \
-spacing1 1 -spacing3 1 -state disabled
@@ -6307,15 +6525,14 @@ proc showrefs {} {
grid $top.list $top.ysb -sticky nsew
grid $top.xsb x -sticky ew
frame $top.f
- label $top.f.l -text "Filter: " -font $uifont
- entry $top.f.e -width 20 -textvariable reflistfilter -font $uifont
+ label $top.f.l -text "[mc "Filter"]: "
+ entry $top.f.e -width 20 -textvariable reflistfilter
set reflistfilter "*"
trace add variable reflistfilter write reflistfilter_change
pack $top.f.e -side right -fill x -expand 1
pack $top.f.l -side left
grid $top.f - -sticky ew -pady 2
- button $top.close -command [list destroy $top] -text "Close" \
- -font $uifont
+ button $top.close -command [list destroy $top] -text [mc "Close"]
grid $top.close -
grid columnconfigure $top 0 -weight 1
grid rowconfigure $top 0 -weight 1
@@ -6438,25 +6655,59 @@ proc refill_reflist {} {
# Stuff for finding nearby tags
proc getallcommits {} {
- global allcommits allids nbmp nextarc seeds
+ global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
+ global idheads idtags idotherrefs allparents tagobjid
if {![info exists allcommits]} {
- set allids {}
- set nbmp 0
set nextarc 0
set allcommits 0
set seeds {}
+ set allcwait 0
+ set cachedarcs 0
+ set allccache [file join [gitdir] "gitk.cache"]
+ if {![catch {
+ set f [open $allccache r]
+ set allcwait 1
+ getcache $f
+ }]} return
}
- set cmd [concat | git rev-list --all --parents]
- foreach id $seeds {
- lappend cmd "^$id"
+ if {$allcwait} {
+ return
+ }
+ set cmd [list | git rev-list --parents]
+ set allcupdate [expr {$seeds ne {}}]
+ if {!$allcupdate} {
+ set ids "--all"
+ } else {
+ set refs [concat [array names idheads] [array names idtags] \
+ [array names idotherrefs]]
+ set ids {}
+ set tagobjs {}
+ foreach name [array names tagobjid] {
+ lappend tagobjs $tagobjid($name)
+ }
+ foreach id [lsort -unique $refs] {
+ if {![info exists allparents($id)] &&
+ [lsearch -exact $tagobjs $id] < 0} {
+ lappend ids $id
+ }
+ }
+ if {$ids ne {}} {
+ foreach id $seeds {
+ lappend ids "^$id"
+ }
+ }
+ }
+ if {$ids ne {}} {
+ set fd [open [concat $cmd $ids] r]
+ fconfigure $fd -blocking 0
+ incr allcommits
+ nowbusy allcommits
+ filerun $fd [list getallclines $fd]
+ } else {
+ dispneartags 0
}
- set fd [open $cmd r]
- fconfigure $fd -blocking 0
- incr allcommits
- nowbusy allcommits
- filerun $fd [list getallclines $fd]
}
# Since most commits have 1 parent and 1 child, we group strings of
@@ -6475,10 +6726,10 @@ proc getallcommits {} {
# coming from descendents, and "outgoing" means going towards ancestors.
proc getallclines {fd} {
- global allids allparents allchildren idtags idheads nextarc nbmp
+ global allparents allchildren idtags idheads nextarc
global arcnos arcids arctags arcout arcend arcstart archeads growing
- global seeds allcommits
-
+ global seeds allcommits cachedarcs allcupdate
+
set nid 0
while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
set id [lindex $line 0]
@@ -6486,7 +6737,7 @@ proc getallclines {fd} {
# seen it already
continue
}
- lappend allids $id
+ set cachedarcs 0
set olds [lrange $line 1 end]
set allparents($id) $olds
if {![info exists allchildren($id)]} {
@@ -6517,7 +6768,6 @@ proc getallclines {fd} {
continue
}
}
- incr nbmp
foreach a $arcnos($id) {
lappend arcids($a) $id
set arcend($a) $id
@@ -6557,9 +6807,28 @@ proc getallclines {fd} {
if {![eof $fd]} {
return [expr {$nid >= 1000? 2: 1}]
}
- close $fd
+ set cacheok 1
+ if {[catch {
+ fconfigure $fd -blocking 1
+ close $fd
+ } err]} {
+ # got an error reading the list of commits
+ # if we were updating, try rereading the whole thing again
+ if {$allcupdate} {
+ incr allcommits -1
+ dropcache $err
+ return
+ }
+ error_popup "[mc "Error reading commit topology information;\
+ branch and preceding/following tag information\
+ will be incomplete."]\n($err)"
+ set cacheok 0
+ }
if {[incr allcommits -1] == 0} {
notbusy allcommits
+ if {$cacheok} {
+ run savecache
+ }
}
dispneartags 0
return 0
@@ -6583,7 +6852,7 @@ proc recalcarc {a} {
}
proc splitarc {p} {
- global arcnos arcids nextarc nbmp arctags archeads idtags idheads
+ global arcnos arcids nextarc arctags archeads idtags idheads
global arcstart arcend arcout allparents growing
set a $arcnos($p)
@@ -6615,7 +6884,6 @@ proc splitarc {p} {
set growing($na) 1
unset growing($a)
}
- incr nbmp
foreach id $tail {
if {[llength $arcnos($id)] == 1} {
@@ -6639,17 +6907,15 @@ proc splitarc {p} {
# Update things for a new commit added that is a child of one
# existing commit. Used when cherry-picking.
proc addnewchild {id p} {
- global allids allparents allchildren idtags nextarc nbmp
+ global allparents allchildren idtags nextarc
global arcnos arcids arctags arcout arcend arcstart archeads growing
global seeds allcommits
- if {![info exists allcommits]} return
- lappend allids $id
+ if {![info exists allcommits] || ![info exists arcnos($p)]} return
set allparents($id) [list $p]
set allchildren($id) {}
set arcnos($id) {}
lappend seeds $id
- incr nbmp
lappend allchildren($p) $id
set a [incr nextarc]
set arcstart($a) $id
@@ -6664,6 +6930,172 @@ proc addnewchild {id p} {
set arcout($id) [list $a]
}
+# This implements a cache for the topology information.
+# The cache saves, for each arc, the start and end of the arc,
+# the ids on the arc, and the outgoing arcs from the end.
+proc readcache {f} {
+ global arcnos arcids arcout arcstart arcend arctags archeads nextarc
+ global idtags idheads allparents cachedarcs possible_seeds seeds growing
+ global allcwait
+
+ set a $nextarc
+ set lim $cachedarcs
+ if {$lim - $a > 500} {
+ set lim [expr {$a + 500}]
+ }
+ if {[catch {
+ if {$a == $lim} {
+ # finish reading the cache and setting up arctags, etc.
+ set line [gets $f]
+ if {$line ne "1"} {error "bad final version"}
+ close $f
+ foreach id [array names idtags] {
+ if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
+ [llength $allparents($id)] == 1} {
+ set a [lindex $arcnos($id) 0]
+ if {$arctags($a) eq {}} {
+ recalcarc $a
+ }
+ }
+ }
+ foreach id [array names idheads] {
+ if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
+ [llength $allparents($id)] == 1} {
+ set a [lindex $arcnos($id) 0]
+ if {$archeads($a) eq {}} {
+ recalcarc $a
+ }
+ }
+ }
+ foreach id [lsort -unique $possible_seeds] {
+ if {$arcnos($id) eq {}} {
+ lappend seeds $id
+ }
+ }
+ set allcwait 0
+ } else {
+ while {[incr a] <= $lim} {
+ set line [gets $f]
+ if {[llength $line] != 3} {error "bad line"}
+ set s [lindex $line 0]
+ set arcstart($a) $s
+ lappend arcout($s) $a
+ if {![info exists arcnos($s)]} {
+ lappend possible_seeds $s
+ set arcnos($s) {}
+ }
+ set e [lindex $line 1]
+ if {$e eq {}} {
+ set growing($a) 1
+ } else {
+ set arcend($a) $e
+ if {![info exists arcout($e)]} {
+ set arcout($e) {}
+ }
+ }
+ set arcids($a) [lindex $line 2]
+ foreach id $arcids($a) {
+ lappend allparents($s) $id
+ set s $id
+ lappend arcnos($id) $a
+ }
+ if {![info exists allparents($s)]} {
+ set allparents($s) {}
+ }
+ set arctags($a) {}
+ set archeads($a) {}
+ }
+ set nextarc [expr {$a - 1}]
+ }
+ } err]} {
+ dropcache $err
+ return 0
+ }
+ if {!$allcwait} {
+ getallcommits
+ }
+ return $allcwait
+}
+
+proc getcache {f} {
+ global nextarc cachedarcs possible_seeds
+
+ if {[catch {
+ set line [gets $f]
+ if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
+ # make sure it's an integer
+ set cachedarcs [expr {int([lindex $line 1])}]
+ if {$cachedarcs < 0} {error "bad number of arcs"}
+ set nextarc 0
+ set possible_seeds {}
+ run readcache $f
+ } err]} {
+ dropcache $err
+ }
+ return 0
+}
+
+proc dropcache {err} {
+ global allcwait nextarc cachedarcs seeds
+
+ #puts "dropping cache ($err)"
+ foreach v {arcnos arcout arcids arcstart arcend growing \
+ arctags archeads allparents allchildren} {
+ global $v
+ catch {unset $v}
+ }
+ set allcwait 0
+ set nextarc 0
+ set cachedarcs 0
+ set seeds {}
+ getallcommits
+}
+
+proc writecache {f} {
+ global cachearc cachedarcs allccache
+ global arcstart arcend arcnos arcids arcout
+
+ set a $cachearc
+ set lim $cachedarcs
+ if {$lim - $a > 1000} {
+ set lim [expr {$a + 1000}]
+ }
+ if {[catch {
+ while {[incr a] <= $lim} {
+ if {[info exists arcend($a)]} {
+ puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
+ } else {
+ puts $f [list $arcstart($a) {} $arcids($a)]
+ }
+ }
+ } err]} {
+ catch {close $f}
+ catch {file delete $allccache}
+ #puts "writing cache failed ($err)"
+ return 0
+ }
+ set cachearc [expr {$a - 1}]
+ if {$a > $cachedarcs} {
+ puts $f "1"
+ close $f
+ return 0
+ }
+ return 1
+}
+
+proc savecache {} {
+ global nextarc cachedarcs cachearc allccache
+
+ if {$nextarc == $cachedarcs} return
+ set cachearc 0
+ set cachedarcs $nextarc
+ catch {
+ set f [open $allccache w]
+ puts $f [list 1 $cachedarcs]
+ run writecache $f
+ }
+}
+
# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
# or 0 if neither is true.
proc anc_or_desc {a b} {
@@ -7361,6 +7793,7 @@ proc showtag {tag isnew} {
}
$ctext conf -state normal
clear_ctext
+ settabs 0
set linknum 0
if {![info exists tagcontents($tag)]} {
catch {
@@ -7370,7 +7803,7 @@ proc showtag {tag isnew} {
if {[info exists tagcontents($tag)]} {
set text $tagcontents($tag)
} else {
- set text "Tag: $tag\nId: $tagids($tag)"
+ set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
}
appendwithlinks $text {}
$ctext conf -state disabled
@@ -7384,11 +7817,133 @@ proc doquit {} {
destroy .
}
+proc mkfontdisp {font top which} {
+ global fontattr fontpref $font
+
+ set fontpref($font) [set $font]
+ button $top.${font}but -text $which -font optionfont \
+ -command [list choosefont $font $which]
+ label $top.$font -relief flat -font $font \
+ -text $fontattr($font,family) -justify left
+ grid x $top.${font}but $top.$font -sticky w
+}
+
+proc choosefont {font which} {
+ global fontparam fontlist fonttop fontattr
+
+ set fontparam(which) $which
+ set fontparam(font) $font
+ set fontparam(family) [font actual $font -family]
+ set fontparam(size) $fontattr($font,size)
+ set fontparam(weight) $fontattr($font,weight)
+ set fontparam(slant) $fontattr($font,slant)
+ set top .gitkfont
+ set fonttop $top
+ if {![winfo exists $top]} {
+ font create sample
+ eval font config sample [font actual $font]
+ toplevel $top
+ wm title $top [mc "Gitk font chooser"]
+ label $top.l -textvariable fontparam(which)
+ pack $top.l -side top
+ set fontlist [lsort [font families]]
+ frame $top.f
+ listbox $top.f.fam -listvariable fontlist \
+ -yscrollcommand [list $top.f.sb set]
+ bind $top.f.fam <<ListboxSelect>> selfontfam
+ scrollbar $top.f.sb -command [list $top.f.fam yview]
+ pack $top.f.sb -side right -fill y
+ pack $top.f.fam -side left -fill both -expand 1
+ pack $top.f -side top -fill both -expand 1
+ frame $top.g
+ spinbox $top.g.size -from 4 -to 40 -width 4 \
+ -textvariable fontparam(size) \
+ -validatecommand {string is integer -strict %s}
+ checkbutton $top.g.bold -padx 5 \
+ -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
+ -variable fontparam(weight) -onvalue bold -offvalue normal
+ checkbutton $top.g.ital -padx 5 \
+ -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
+ -variable fontparam(slant) -onvalue italic -offvalue roman
+ pack $top.g.size $top.g.bold $top.g.ital -side left
+ pack $top.g -side top
+ canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
+ -background white
+ $top.c create text 100 25 -anchor center -text $which -font sample \
+ -fill black -tags text
+ bind $top.c <Configure> [list centertext $top.c]
+ pack $top.c -side top -fill x
+ frame $top.buts
+ button $top.buts.ok -text [mc "OK"] -command fontok -default active
+ button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
+ grid $top.buts.ok $top.buts.can
+ grid columnconfigure $top.buts 0 -weight 1 -uniform a
+ grid columnconfigure $top.buts 1 -weight 1 -uniform a
+ pack $top.buts -side bottom -fill x
+ trace add variable fontparam write chg_fontparam
+ } else {
+ raise $top
+ $top.c itemconf text -text $which
+ }
+ set i [lsearch -exact $fontlist $fontparam(family)]
+ if {$i >= 0} {
+ $top.f.fam selection set $i
+ $top.f.fam see $i
+ }
+}
+
+proc centertext {w} {
+ $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
+}
+
+proc fontok {} {
+ global fontparam fontpref prefstop
+
+ set f $fontparam(font)
+ set fontpref($f) [list $fontparam(family) $fontparam(size)]
+ if {$fontparam(weight) eq "bold"} {
+ lappend fontpref($f) "bold"
+ }
+ if {$fontparam(slant) eq "italic"} {
+ lappend fontpref($f) "italic"
+ }
+ set w $prefstop.$f
+ $w conf -text $fontparam(family) -font $fontpref($f)
+
+ fontcan
+}
+
+proc fontcan {} {
+ global fonttop fontparam
+
+ if {[info exists fonttop]} {
+ catch {destroy $fonttop}
+ catch {font delete sample}
+ unset fonttop
+ unset fontparam
+ }
+}
+
+proc selfontfam {} {
+ global fonttop fontparam
+
+ set i [$fonttop.f.fam curselection]
+ if {$i ne {}} {
+ set fontparam(family) [$fonttop.f.fam get $i]
+ }
+}
+
+proc chg_fontparam {v sub op} {
+ global fontparam
+
+ font config sample -$sub $fontparam($sub)
+}
+
proc doprefs {} {
- global maxwidth maxgraphpct diffopts
+ global maxwidth maxgraphpct
global oldprefs prefstop showneartags showlocalchanges
global bgcolor fgcolor ctext diffcolors selectbgcolor
- global uifont tabstop
+ global tabstop limitdiffs
set top .gitkprefs
set prefstop $top
@@ -7396,82 +7951,85 @@ proc doprefs {} {
raise $top
return
}
- foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
+ foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
+ limitdiffs tabstop} {
set oldprefs($v) [set $v]
}
toplevel $top
- wm title $top "Gitk preferences"
- label $top.ldisp -text "Commit list display options"
- $top.ldisp configure -font $uifont
+ wm title $top [mc "Gitk preferences"]
+ label $top.ldisp -text [mc "Commit list display options"]
grid $top.ldisp - -sticky w -pady 10
label $top.spacer -text " "
- label $top.maxwidthl -text "Maximum graph width (lines)" \
+ label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
-font optionfont
spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
- label $top.maxpctl -text "Maximum graph width (% of pane)" \
+ label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
-font optionfont
spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
grid x $top.maxpctl $top.maxpct -sticky w
frame $top.showlocal
- label $top.showlocal.l -text "Show local changes" -font optionfont
+ label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
checkbutton $top.showlocal.b -variable showlocalchanges
pack $top.showlocal.b $top.showlocal.l -side left
grid x $top.showlocal -sticky w
- label $top.ddisp -text "Diff display options"
- $top.ddisp configure -font $uifont
+ label $top.ddisp -text [mc "Diff display options"]
grid $top.ddisp - -sticky w -pady 10
- label $top.diffoptl -text "Options for diff program" \
- -font optionfont
- entry $top.diffopt -width 20 -textvariable diffopts
- grid x $top.diffoptl $top.diffopt -sticky w
+ label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
+ spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
+ grid x $top.tabstopl $top.tabstop -sticky w
frame $top.ntag
- label $top.ntag.l -text "Display nearby tags" -font optionfont
+ label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
checkbutton $top.ntag.b -variable showneartags
pack $top.ntag.b $top.ntag.l -side left
grid x $top.ntag -sticky w
- label $top.tabstopl -text "tabstop" -font optionfont
- spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
- grid x $top.tabstopl $top.tabstop -sticky w
+ frame $top.ldiff
+ label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
+ checkbutton $top.ldiff.b -variable limitdiffs
+ pack $top.ldiff.b $top.ldiff.l -side left
+ grid x $top.ldiff -sticky w
- label $top.cdisp -text "Colors: press to choose"
- $top.cdisp configure -font $uifont
+ label $top.cdisp -text [mc "Colors: press to choose"]
grid $top.cdisp - -sticky w -pady 10
label $top.bg -padx 40 -relief sunk -background $bgcolor
- button $top.bgbut -text "Background" -font optionfont \
+ button $top.bgbut -text [mc "Background"] -font optionfont \
-command [list choosecolor bgcolor 0 $top.bg background setbg]
grid x $top.bgbut $top.bg -sticky w
label $top.fg -padx 40 -relief sunk -background $fgcolor
- button $top.fgbut -text "Foreground" -font optionfont \
+ button $top.fgbut -text [mc "Foreground"] -font optionfont \
-command [list choosecolor fgcolor 0 $top.fg foreground setfg]
grid x $top.fgbut $top.fg -sticky w
label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
- button $top.diffoldbut -text "Diff: old lines" -font optionfont \
+ button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
-command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
[list $ctext tag conf d0 -foreground]]
grid x $top.diffoldbut $top.diffold -sticky w
label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
- button $top.diffnewbut -text "Diff: new lines" -font optionfont \
+ button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
-command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
[list $ctext tag conf d1 -foreground]]
grid x $top.diffnewbut $top.diffnew -sticky w
label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
- button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
+ button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
-command [list choosecolor diffcolors 2 $top.hunksep \
"diff hunk header" \
[list $ctext tag conf hunksep -foreground]]
grid x $top.hunksepbut $top.hunksep -sticky w
label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
- button $top.selbgbut -text "Select bg" -font optionfont \
+ button $top.selbgbut -text [mc "Select bg"] -font optionfont \
-command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
grid x $top.selbgbut $top.selbgsep -sticky w
+ label $top.cfont -text [mc "Fonts: press to choose"]
+ grid $top.cfont - -sticky w -pady 10
+ mkfontdisp mainfont $top [mc "Main font"]
+ mkfontdisp textfont $top [mc "Diff display font"]
+ mkfontdisp uifont $top [mc "User interface font"]
+
frame $top.buts
- button $top.buts.ok -text "OK" -command prefsok -default active
- $top.buts.ok configure -font $uifont
- button $top.buts.can -text "Cancel" -command prefscan -default normal
- $top.buts.can configure -font $uifont
+ button $top.buts.ok -text [mc "OK"] -command prefsok -default active
+ button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
grid $top.buts.ok $top.buts.can
grid columnconfigure $top.buts 0 -weight 1 -uniform a
grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -7483,7 +8041,7 @@ proc choosecolor {v vi w x cmd} {
global $v
set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
- -title "Gitk: choose color for $x"]
+ -title [mc "Gitk: choose color for %s" $x]]
if {$c eq {}} return
$w conf -background $c
lset $v $vi $c
@@ -7519,24 +8077,48 @@ proc setfg {c} {
}
proc prefscan {} {
- global maxwidth maxgraphpct diffopts
- global oldprefs prefstop showneartags showlocalchanges
+ global oldprefs prefstop
- foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
+ foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
+ limitdiffs tabstop} {
+ global $v
set $v $oldprefs($v)
}
catch {destroy $prefstop}
unset prefstop
+ fontcan
}
proc prefsok {} {
global maxwidth maxgraphpct
global oldprefs prefstop showneartags showlocalchanges
- global charspc ctext tabstop
+ global fontpref mainfont textfont uifont
+ global limitdiffs treediffs
catch {destroy $prefstop}
unset prefstop
- $ctext configure -tabs "[expr {$tabstop * $charspc}]"
+ fontcan
+ set fontchanged 0
+ if {$mainfont ne $fontpref(mainfont)} {
+ set mainfont $fontpref(mainfont)
+ parsefont mainfont $mainfont
+ eval font configure mainfont [fontflags mainfont]
+ eval font configure mainfontbold [fontflags mainfont 1]
+ setcoords
+ set fontchanged 1
+ }
+ if {$textfont ne $fontpref(textfont)} {
+ set textfont $fontpref(textfont)
+ parsefont textfont $textfont
+ eval font configure textfont [fontflags textfont]
+ eval font configure textfontbold [fontflags textfont 1]
+ }
+ if {$uifont ne $fontpref(uifont)} {
+ set uifont $fontpref(uifont)
+ parsefont uifont $uifont
+ eval font configure uifont [fontflags uifont]
+ }
+ settabs
if {$showlocalchanges != $oldprefs(showlocalchanges)} {
if {$showlocalchanges} {
doshowlocalchanges
@@ -7544,10 +8126,15 @@ proc prefsok {} {
dohidelocalchanges
}
}
- if {$maxwidth != $oldprefs(maxwidth)
+ if {$limitdiffs != $oldprefs(limitdiffs)} {
+ # treediffs elements are limited by path
+ catch {unset treediffs}
+ }
+ if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
|| $maxgraphpct != $oldprefs(maxgraphpct)} {
redisplay
- } elseif {$showneartags != $oldprefs(showneartags)} {
+ } elseif {$showneartags != $oldprefs(showneartags) ||
+ $limitdiffs != $oldprefs(limitdiffs)} {
reselectline
}
}
@@ -7833,9 +8420,15 @@ proc tcl_encoding {enc} {
return {}
}
+# First check that Tcl/Tk is recent enough
+if {[catch {package require Tk 8.4} err]} {
+ show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
+ Gitk requires at least Tcl/Tk 8.4."]
+ exit 1
+}
+
# defaults...
set datemode 0
-set diffopts "-U 5 -p"
set wrcomcmd "git diff-tree --stdin -p --pretty"
set gitencoding {}
@@ -7859,15 +8452,16 @@ set maxgraphpct 50
set maxwidth 16
set revlistorder 0
set fastdate 0
-set uparrowlen 7
-set downarrowlen 7
-set mingaplen 30
+set uparrowlen 5
+set downarrowlen 5
+set mingaplen 100
set cmitmode "patch"
set wrapcomment "none"
set showneartags 1
set maxrefs 20
set maxlinelen 200
set showlocalchanges 1
+set limitdiffs 1
set datetimeformat "%Y-%m-%d %H:%M:%S"
set colors {green red blue magenta darkgrey brown orange}
@@ -7875,22 +8469,56 @@ set bgcolor white
set fgcolor black
set diffcolors {red "#00a000" blue}
set diffcontext 3
+set ignorespace 0
set selectbgcolor gray85
+## For msgcat loading, first locate the installation location.
+if { [info exists ::env(GITK_MSGSDIR)] } {
+ ## Msgsdir was manually set in the environment.
+ set gitk_msgsdir $::env(GITK_MSGSDIR)
+} else {
+ ## Let's guess the prefix from argv0.
+ set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
+ set gitk_libdir [file join $gitk_prefix share gitk lib]
+ set gitk_msgsdir [file join $gitk_libdir msgs]
+ unset gitk_prefix
+}
+
+## Internationalization (i18n) through msgcat and gettext. See
+## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
+package require msgcat
+namespace import ::msgcat::mc
+## And eventually load the actual message catalog
+::msgcat::mcload $gitk_msgsdir
+
catch {source ~/.gitk}
font create optionfont -family sans-serif -size -12
+parsefont mainfont $mainfont
+eval font create mainfont [fontflags mainfont]
+eval font create mainfontbold [fontflags mainfont 1]
+
+parsefont textfont $textfont
+eval font create textfont [fontflags textfont]
+eval font create textfontbold [fontflags textfont 1]
+
+parsefont uifont $uifont
+eval font create uifont [fontflags uifont]
+
+setoptions
+
# check that we can find a .git directory somewhere...
if {[catch {set gitdir [gitdir]}]} {
- show_error {} . "Cannot find a git repository here."
+ show_error {} . [mc "Cannot find a git repository here."]
exit 1
}
if {![file isdirectory $gitdir]} {
- show_error {} . "Cannot find the git directory \"$gitdir\"."
+ show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
exit 1
}
+set mergeonly 0
set revtreeargs {}
set cmdline_files {}
set i 0
@@ -7898,6 +8526,10 @@ foreach arg $argv {
switch -- $arg {
"" { }
"-d" { set datemode 1 }
+ "--merge" {
+ set mergeonly 1
+ lappend revtreeargs $arg
+ }
"--" {
set cmdline_files [lrange $argv [expr {$i + 1}] end]
break
@@ -7921,8 +8553,8 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} {
# with git log and git rev-list, check revtreeargs for filenames.
foreach arg $revtreeargs {
if {[file exists $arg]} {
- show_error {} . "Ambiguous argument '$arg': both revision\
- and filename"
+ show_error {} . [mc "Ambiguous argument '%s': both revision\
+ and filename" $arg]
exit 1
}
}
@@ -7933,14 +8565,49 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} {
if {$i > 0} {
set err [string range $err [expr {$i + 6}] end]
}
- show_error {} . "Bad arguments to gitk:\n$err"
+ show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
+ exit 1
+ }
+}
+
+if {$mergeonly} {
+ # find the list of unmerged files
+ set mlist {}
+ set nr_unmerged 0
+ if {[catch {
+ set fd [open "| git ls-files -u" r]
+ } err]} {
+ show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
+ exit 1
+ }
+ while {[gets $fd line] >= 0} {
+ set i [string first "\t" $line]
+ if {$i < 0} continue
+ set fname [string range $line [expr {$i+1}] end]
+ if {[lsearch -exact $mlist $fname] >= 0} continue
+ incr nr_unmerged
+ if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} {
+ lappend mlist $fname
+ }
+ }
+ catch {close $fd}
+ if {$mlist eq {}} {
+ if {$nr_unmerged == 0} {
+ show_error {} . [mc "No files selected: --merge specified but\
+ no files are unmerged."]
+ } else {
+ show_error {} . [mc "No files selected: --merge specified but\
+ no unmerged files are within file limit."]
+ }
exit 1
}
+ set cmdline_files $mlist
}
set nullid "0000000000000000000000000000000000000000"
set nullid2 "0000000000000000000000000000000000000001"
+set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
set runq {}
set history {}
@@ -7948,18 +8615,23 @@ set historyindex 0
set fh_serial 0
set nhl_names {}
set highlight_paths {}
+set findpattern {}
set searchdirn -forwards
set boldrows {}
set boldnamerows {}
set diffelide {0 0}
set markingmatches 0
-
-set optim_delay 16
+set linkentercount 0
+set need_redisplay 0
+set nrows_drawn 0
+set firsttabstop 0
set nextviewnum 1
set curview 0
set selectedview 0
-set selectedhlview None
+set selectedhlview [mc "None"]
+set highlight_related [mc "None"]
+set highlight_files {}
set viewfiles(0) {}
set viewperm(0) 0
set viewargs(0) {}
@@ -7968,7 +8640,6 @@ set cmdlineok 0
set stopped 0
set stuffsaved 0
set patchnum 0
-set lookingforhead 0
set localirow -1
set localfrow -1
set lserial 0
@@ -7984,13 +8655,13 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} {
set curview 1
set selectedview 1
set nextviewnum 2
- set viewname(1) "Command line"
+ set viewname(1) [mc "Command line"]
set viewfiles(1) $cmdline_files
set viewargs(1) $revtreeargs
set viewperm(1) 0
addviewmenu 1
- .bar.view entryconf Edit* -state normal
- .bar.view entryconf Delete* -state normal
+ .bar.view entryconf [mc "Edit view..."] -state normal
+ .bar.view entryconf [mc "Delete view"] -state normal
}
if {[info exists permviews]} {
diff --git a/gitk-git/po/.gitignore b/gitk-git/po/.gitignore
new file mode 100644
index 0000000000..e358dd1903
--- /dev/null
+++ b/gitk-git/po/.gitignore
@@ -0,0 +1 @@
+*.msg
diff --git a/gitk-git/po/de.po b/gitk-git/po/de.po
new file mode 100644
index 0000000000..5ee2fca8b2
--- /dev/null
+++ b/gitk-git/po/de.po
@@ -0,0 +1,725 @@
+# Translation of gitk to German.
+# Copyright (C) 2007 Paul Mackerras.
+# This file is distributed under the same license as the gitk package.
+# Christian Stimming <stimming@tuhh.de>, 2007
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-09 22:20+0100\n"
+"PO-Revision-Date: 2008-01-09 22:21+0100\n"
+"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
+"Language-Team: German\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:101
+msgid "Error executing git rev-list:"
+msgstr "Fehler beim Ausführen von git-rev-list:"
+
+#: gitk:114
+msgid "Reading"
+msgstr "Lesen"
+
+#: gitk:141 gitk:2143
+msgid "Reading commits..."
+msgstr "Versionen lesen..."
+
+#: gitk:264
+msgid "Can't parse git log output:"
+msgstr "Git log Ausgabe kann nicht erkannt werden:"
+
+#: gitk:375 gitk:2147
+msgid "No commits selected"
+msgstr "Keine Versionen ausgewählt."
+
+#: gitk:486
+msgid "No commit information available"
+msgstr "Keine Versionsinformation verfügbar"
+
+#: gitk:585 gitk:607 gitk:1908 gitk:6366 gitk:7866 gitk:8020
+msgid "OK"
+msgstr "Ok"
+
+#: gitk:609 gitk:1909 gitk:6046 gitk:6117 gitk:6218 gitk:6264 gitk:6368
+#: gitk:7867 gitk:8021
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: gitk:646
+msgid "File"
+msgstr "Datei"
+
+#: gitk:648
+msgid "Update"
+msgstr "Aktualisieren"
+
+#: gitk:649
+msgid "Reread references"
+msgstr "Zweige neu laden"
+
+#: gitk:650
+msgid "List references"
+msgstr "Zweige auflisten"
+
+#: gitk:651
+msgid "Quit"
+msgstr "Beenden"
+
+#: gitk:653
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: gitk:654
+msgid "Preferences"
+msgstr "Einstellungen"
+
+#: gitk:657
+msgid "View"
+msgstr "Ansicht"
+
+#: gitk:658
+msgid "New view..."
+msgstr "Neue Ansicht..."
+
+#: gitk:659 gitk:2085 gitk:8651
+msgid "Edit view..."
+msgstr "Ansicht bearbeiten..."
+
+#: gitk:661 gitk:2086 gitk:8652
+msgid "Delete view"
+msgstr "Ansicht löschen"
+
+#: gitk:663
+msgid "All files"
+msgstr "Alle Dateien"
+
+#: gitk:667
+msgid "Help"
+msgstr "Hilfe"
+
+#: gitk:668 gitk:1280
+msgid "About gitk"
+msgstr "Ãœber gitk"
+
+#: gitk:669
+msgid "Key bindings"
+msgstr "Tastenkürzel"
+
+#: gitk:726
+msgid "SHA1 ID: "
+msgstr "SHA1:"
+
+#: gitk:776
+msgid "Find"
+msgstr "Suche"
+
+#: gitk:777
+msgid "next"
+msgstr "nächste"
+
+#: gitk:778
+msgid "prev"
+msgstr "vorige"
+
+#: gitk:779
+msgid "commit"
+msgstr "Version"
+
+#: gitk:782 gitk:784 gitk:2308 gitk:2331 gitk:2355 gitk:4257 gitk:4320
+msgid "containing:"
+msgstr "enthaltend:"
+
+#: gitk:785 gitk:1741 gitk:1746 gitk:2383
+msgid "touching paths:"
+msgstr "Pfad betreffend:"
+
+#: gitk:786 gitk:2388
+msgid "adding/removing string:"
+msgstr "String dazu/löschen:"
+
+#: gitk:795 gitk:797
+msgid "Exact"
+msgstr "Exakt"
+
+#: gitk:797 gitk:2466 gitk:4225
+msgid "IgnCase"
+msgstr "Kein Groß/Klein"
+
+#: gitk:797 gitk:2357 gitk:2464 gitk:4221
+msgid "Regexp"
+msgstr "Regexp"
+
+#: gitk:799 gitk:800 gitk:2485 gitk:2515 gitk:2522 gitk:4331 gitk:4387
+msgid "All fields"
+msgstr "Alle Felder"
+
+#: gitk:800 gitk:2483 gitk:2515 gitk:4287
+msgid "Headline"
+msgstr "Ãœberschrift"
+
+#: gitk:801 gitk:2483 gitk:4287 gitk:4387 gitk:4775
+msgid "Comments"
+msgstr "Beschreibung"
+
+#: gitk:801 gitk:2483 gitk:2487 gitk:2522 gitk:4287 gitk:4711 gitk:5895
+#: gitk:5910
+msgid "Author"
+msgstr "Autor"
+
+#: gitk:801 gitk:2483 gitk:4287 gitk:4713
+msgid "Committer"
+msgstr "Eintragender"
+
+#: gitk:829
+msgid "Search"
+msgstr "Suche"
+
+#: gitk:836
+msgid "Diff"
+msgstr "Vergleich"
+
+#: gitk:838
+msgid "Old version"
+msgstr "Alte Version"
+
+#: gitk:840
+msgid "New version"
+msgstr "Neue Version"
+
+#: gitk:842
+msgid "Lines of context"
+msgstr "Kontextzeilen"
+
+#: gitk:900
+msgid "Patch"
+msgstr "Patch"
+
+#: gitk:902
+msgid "Tree"
+msgstr "Baum"
+
+#: gitk:1018 gitk:1033 gitk:5961
+msgid "Diff this -> selected"
+msgstr "Vergleich diese -> gewählte"
+
+#: gitk:1020 gitk:1035 gitk:5962
+msgid "Diff selected -> this"
+msgstr "Vergleich gewählte -> diese"
+
+#: gitk:1022 gitk:1037 gitk:5963
+msgid "Make patch"
+msgstr "Patch erstellen"
+
+#: gitk:1023 gitk:6101
+msgid "Create tag"
+msgstr "Markierung erstellen"
+
+#: gitk:1024 gitk:6198
+msgid "Write commit to file"
+msgstr "Version in Datei schreiben"
+
+#: gitk:1025 gitk:6252
+msgid "Create new branch"
+msgstr "Neuen Zweig erstellen"
+
+#: gitk:1026
+msgid "Cherry-pick this commit"
+msgstr "Diese Version pflücken"
+
+#: gitk:1028
+msgid "Reset HEAD branch to here"
+msgstr "HEAD-Zweig auf diese Version zurücksetzen"
+
+#: gitk:1044
+msgid "Check out this branch"
+msgstr "Auf diesen Zweig umstellen"
+
+#: gitk:1046
+msgid "Remove this branch"
+msgstr "Zweig löschen"
+
+#: gitk:1052
+msgid "Highlight this too"
+msgstr "Diesen auch hervorheben"
+
+#: gitk:1054
+msgid "Highlight this only"
+msgstr "Nur diesen hervorheben"
+
+#: gitk:1281
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2006 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - eine Visualisierung der Git Historie\n"
+"\n"
+"Copyright © 2005-2006 Paul Mackerras\n"
+"\n"
+"Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public "
+"License\n"
+" "
+
+#: gitk:1289 gitk:1350 gitk:6524
+msgid "Close"
+msgstr "Schließen"
+
+#: gitk:1308
+msgid "Gitk key bindings"
+msgstr "Gitk Tastaturbelegung"
+
+#: gitk:1858
+msgid "Gitk view definition"
+msgstr "Gitk Ansichten"
+
+#: gitk:1882
+msgid "Name"
+msgstr "Name"
+
+#: gitk:1885
+msgid "Remember this view"
+msgstr "Diese Ansicht speichern"
+
+#: gitk:1889
+msgid "Commits to include (arguments to git rev-list):"
+msgstr "Versionen anzeigen (Argumente von git-rev-list):"
+
+#: gitk:1895
+msgid "Enter files and directories to include, one per line:"
+msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):"
+
+#: gitk:1942
+msgid "Error in commit selection arguments:"
+msgstr "Fehler in den ausgewählten Versionen:"
+
+#: gitk:1993 gitk:2079 gitk:2535 gitk:2549 gitk:3732 gitk:8620 gitk:8621
+msgid "None"
+msgstr "Keine"
+
+#: gitk:2483 gitk:4287 gitk:5897 gitk:5912
+msgid "Date"
+msgstr "Datum"
+
+#: gitk:2483 gitk:4287
+msgid "CDate"
+msgstr "Eintragedatum"
+
+#: gitk:2632 gitk:2637
+msgid "Descendant"
+msgstr "Abkömmling"
+
+#: gitk:2633
+msgid "Not descendant"
+msgstr "Nicht Abkömmling"
+
+#: gitk:2640 gitk:2645
+msgid "Ancestor"
+msgstr "Vorgänger"
+
+#: gitk:2641
+msgid "Not ancestor"
+msgstr "Nicht Vorgänger"
+
+#: gitk:2875
+msgid "Local changes checked in to index but not committed"
+msgstr "Lokale Änderungen bereitgestellt, aber nicht eingetragen"
+
+#: gitk:2905
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Lokale Änderungen, nicht bereitgestellt"
+
+#: gitk:4256
+msgid "Searching"
+msgstr "Suchen"
+
+#: gitk:4715
+msgid "Tags:"
+msgstr "Markierungen:"
+
+#: gitk:4732 gitk:4738 gitk:5890
+msgid "Parent"
+msgstr "Eltern"
+
+#: gitk:4743
+msgid "Child"
+msgstr "Kind"
+
+#: gitk:4752
+msgid "Branch"
+msgstr "Zweig"
+
+#: gitk:4755
+msgid "Follows"
+msgstr "Folgt auf"
+
+#: gitk:4758
+msgid "Precedes"
+msgstr "Vorgänger von"
+
+#: gitk:5040
+msgid "Error getting merge diffs:"
+msgstr "Fehler beim Laden des Vergleichs:"
+
+#: gitk:5717
+msgid "Goto:"
+msgstr "Gehe zu:"
+
+#: gitk:5719
+msgid "SHA1 ID:"
+msgstr "SHA1 Kennung:"
+
+#: gitk:5744
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "Kurze SHA1-Kennung »%s« ist mehrdeutig"
+
+#: gitk:5756
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "SHA1-Kennung »%s« unbekannt"
+
+#: gitk:5758
+#, tcl-format
+msgid "Tag/Head %s is not known"
+msgstr "Markierung/Zweig »%s« ist unbekannt"
+
+#: gitk:5900
+msgid "Children"
+msgstr "Kinder"
+
+#: gitk:5957
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Zweig »%s« hierher zurücksetzen"
+
+#: gitk:5988
+msgid "Top"
+msgstr "Oben"
+
+#: gitk:5989
+msgid "From"
+msgstr "Von"
+
+#: gitk:5994
+msgid "To"
+msgstr "bis"
+
+#: gitk:6017
+msgid "Generate patch"
+msgstr "Patch erstellen"
+
+#: gitk:6019
+msgid "From:"
+msgstr "Von:"
+
+#: gitk:6028
+msgid "To:"
+msgstr "bis:"
+
+#: gitk:6037
+msgid "Reverse"
+msgstr "Umgekehrt"
+
+#: gitk:6039 gitk:6212
+msgid "Output file:"
+msgstr "Ausgabedatei:"
+
+#: gitk:6045
+msgid "Generate"
+msgstr "Erzeugen"
+
+#: gitk:6081
+msgid "Error creating patch:"
+msgstr "Fehler beim Patch erzeugen:"
+
+#: gitk:6103 gitk:6200 gitk:6254
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:6112
+msgid "Tag name:"
+msgstr "Markierungsname:"
+
+#: gitk:6116 gitk:6263
+msgid "Create"
+msgstr "Erstellen"
+
+#: gitk:6131
+msgid "No tag name specified"
+msgstr "Kein Markierungsname angegeben"
+
+#: gitk:6135
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Markierung »%s« existiert bereits."
+
+#: gitk:6145
+msgid "Error creating tag:"
+msgstr "Fehler bei Markierung erstellen:"
+
+#: gitk:6209
+msgid "Command:"
+msgstr "Kommando:"
+
+#: gitk:6217
+msgid "Write"
+msgstr "Schreiben"
+
+#: gitk:6233
+msgid "Error writing commit:"
+msgstr "Fehler beim Version eintragen:"
+
+#: gitk:6259
+msgid "Name:"
+msgstr "Name:"
+
+#: gitk:6278
+msgid "Please specify a name for the new branch"
+msgstr "Bitte geben Sie einen Namen für den neuen Zweig an."
+
+#: gitk:6307
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr ""
+"Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut "
+"eintragen?"
+
+#: gitk:6312
+msgid "Cherry-picking"
+msgstr "Version pflücken"
+
+#: gitk:6324
+msgid "No changes committed"
+msgstr "Keine Änderungen eingetragen"
+
+#: gitk:6347
+msgid "Confirm reset"
+msgstr "Zurücksetzen bestätigen"
+
+#: gitk:6349
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Zweig »%s« auf »%s« zurücksetzen?"
+
+#: gitk:6353
+msgid "Reset type:"
+msgstr "Art des Zurücksetzens:"
+
+#: gitk:6357
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Harmlos: Arbeitskopie und Bereitstellung unverändert"
+
+#: gitk:6360
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr ""
+"Gemischt: Arbeitskopie unverändert,\n"
+"Bereitstellung zurückgesetzt"
+
+#: gitk:6363
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hart: Arbeitskopie und Bereitstellung\n"
+"(Alle lokalen Änderungen werden gelöscht)"
+
+#: gitk:6379
+msgid "Resetting"
+msgstr "Zurücksetzen"
+
+#: gitk:6436
+msgid "Checking out"
+msgstr "Umstellen"
+
+#: gitk:6466
+msgid "Cannot delete the currently checked-out branch"
+msgstr ""
+"Der Zweig, auf den die Arbeitskopie momentan umgestellt ist, kann nicht "
+"gelöscht werden."
+
+#: gitk:6472
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"Die Versionen auf Zweig »%s« existieren auf keinem anderen Zweig.\n"
+"Zweig »%s« trotzdem löschen?"
+
+#: gitk:6503
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Markierungen und Zweige: %s"
+
+#: gitk:6517
+msgid "Filter"
+msgstr "Filtern"
+
+#: gitk:6811
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Fehler beim Lesen der Strukturinformationen; Zweige und Vorgänger/Nachfolger "
+"Informationen werden unvollständig sein."
+
+#: gitk:7795
+msgid "Tag"
+msgstr "Markierung"
+
+#: gitk:7795
+msgid "Id"
+msgstr "Id"
+
+#: gitk:7835
+msgid "Gitk font chooser"
+msgstr "Gitk Schriften wählen"
+
+#: gitk:7852
+msgid "B"
+msgstr "F"
+
+#: gitk:7855
+msgid "I"
+msgstr "K"
+
+#: gitk:7948
+msgid "Gitk preferences"
+msgstr "Gitk Einstellungen"
+
+#: gitk:7949
+msgid "Commit list display options"
+msgstr "Anzeige Versionsliste"
+
+#: gitk:7952
+msgid "Maximum graph width (lines)"
+msgstr "Maximale Graphenbreite (Zeilen)"
+
+#: gitk:7956
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Maximale Graphenbreite (% des Fensters)"
+
+#: gitk:7961
+msgid "Show local changes"
+msgstr "Lokale Änderungen anzeigen"
+
+#: gitk:7966
+msgid "Diff display options"
+msgstr "Anzeige Vergleich"
+
+#: gitk:7968
+msgid "Tab spacing"
+msgstr "Tabulatorbreite"
+
+#: gitk:7972
+msgid "Display nearby tags"
+msgstr "Naheliegende Ãœberschriften anzeigen"
+
+#: gitk:7977
+msgid "Limit diffs to listed paths"
+msgstr "Vergleich nur für angezeigte Pfade"
+
+#: gitk:7982
+msgid "Colors: press to choose"
+msgstr "Farben: Klicken zum Wählen"
+
+#: gitk:7985
+msgid "Background"
+msgstr "Vordergrund"
+
+#: gitk:7989
+msgid "Foreground"
+msgstr "Hintergrund"
+
+#: gitk:7993
+msgid "Diff: old lines"
+msgstr "Vergleich: Alte Zeilen"
+
+#: gitk:7998
+msgid "Diff: new lines"
+msgstr "Vergleich: Neue Zeilen"
+
+#: gitk:8003
+msgid "Diff: hunk header"
+msgstr "Vergleich: Änderungstitel"
+
+#: gitk:8009
+msgid "Select bg"
+msgstr "Hintergrundfarbe Auswählen"
+
+#: gitk:8013
+msgid "Fonts: press to choose"
+msgstr "Schriftart: Klicken zum Wählen"
+
+#: gitk:8015
+msgid "Main font"
+msgstr "Programmschriftart"
+
+#: gitk:8016
+msgid "Diff display font"
+msgstr "Vergleich"
+
+#: gitk:8017
+msgid "User interface font"
+msgstr "Beschriftungen"
+
+#: gitk:8033
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: Farbe wählen für %s"
+
+#: gitk:8414
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr ""
+"Gitk läuft nicht mit dieser Version von Tcl/Tk.\n"
+"Gitk benötigt mindestens Tcl/Tk 8.4."
+
+#: gitk:8501
+msgid "Cannot find a git repository here."
+msgstr "Kein Git-Projektarchiv gefunden."
+
+#: gitk:8505
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Git-Verzeichnis »%s« wurde nicht gefunden."
+
+#: gitk:8544
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Mehrdeutige Angabe »%s«: Sowohl Version als auch Dateiname existiert."
+
+#: gitk:8556
+msgid "Bad arguments to gitk:"
+msgstr "Falsche Kommandozeilen-Parameter für gitk:"
+
+#: gitk:8568
+msgid "Couldn't get list of unmerged files:"
+msgstr "Liste der nicht-zusammengeführten Dateien nicht gefunden:"
+
+#: gitk:8584
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Keine Dateien ausgewähle: --merge angegeben, es existieren aber keine nicht-"
+"zusammengeführten Dateien."
+
+#: gitk:8587
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Keine Dateien ausgewähle: --merge angegeben, aber keine nicht-"
+"zusammengeführten Dateien sind in der Dateiauswahl."
+
+#: gitk:8646
+msgid "Command line"
+msgstr "Kommandozeile"
diff --git a/gitk-git/po/po2msg.sh b/gitk-git/po/po2msg.sh
new file mode 100644
index 0000000000..c63248e375
--- /dev/null
+++ b/gitk-git/po/po2msg.sh
@@ -0,0 +1,133 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec tclsh "$0" -- "$@"
+
+# This is a really stupid program, which serves as an alternative to
+# msgfmt. It _only_ translates to Tcl mode, does _not_ validate the
+# input, and does _not_ output any statistics.
+
+proc u2a {s} {
+ set res ""
+ foreach i [split $s ""] {
+ scan $i %c c
+ if {$c<128} {
+ # escape '[', '\' and ']'
+ if {$c == 0x5b || $c == 0x5d} {
+ append res "\\"
+ }
+ append res $i
+ } else {
+ append res \\u[format %04.4x $c]
+ }
+ }
+ return $res
+}
+
+set output_directory "."
+set lang "dummy"
+set files [list]
+set show_statistics 0
+
+# parse options
+for {set i 0} {$i < $argc} {incr i} {
+ set arg [lindex $argv $i]
+ if {$arg == "--statistics"} {
+ incr show_statistics
+ continue
+ }
+ if {$arg == "--tcl"} {
+ # we know
+ continue
+ }
+ if {$arg == "-l"} {
+ incr i
+ set lang [lindex $argv $i]
+ continue
+ }
+ if {$arg == "-d"} {
+ incr i
+ set tmp [lindex $argv $i]
+ regsub "\[^/\]$" $tmp "&/" output_directory
+ continue
+ }
+ lappend files $arg
+}
+
+proc flush_msg {} {
+ global msgid msgstr mode lang out fuzzy
+ global translated_count fuzzy_count not_translated_count
+
+ if {![info exists msgid] || $mode == ""} {
+ return
+ }
+ set mode ""
+ if {$fuzzy == 1} {
+ incr fuzzy_count
+ set fuzzy 0
+ return
+ }
+
+ if {$msgid == ""} {
+ set prefix "set ::msgcat::header"
+ } else {
+ if {$msgstr == ""} {
+ incr not_translated_count
+ return
+ }
+ set prefix "::msgcat::mcset $lang \"[u2a $msgid]\""
+ incr translated_count
+ }
+
+ puts $out "$prefix \"[u2a $msgstr]\""
+}
+
+set fuzzy 0
+set translated_count 0
+set fuzzy_count 0
+set not_translated_count 0
+foreach file $files {
+ regsub "^.*/\(\[^/\]*\)\.po$" $file "$output_directory\\1.msg" outfile
+ set in [open $file "r"]
+ fconfigure $in -encoding utf-8
+ set out [open $outfile "w"]
+
+ set mode ""
+ while {[gets $in line] >= 0} {
+ if {[regexp "^#" $line]} {
+ if {[regexp ", fuzzy" $line]} {
+ set fuzzy 1
+ } else {
+ flush_msg
+ }
+ continue
+ } elseif {[regexp "^msgid \"(.*)\"$" $line dummy match]} {
+ flush_msg
+ set msgid $match
+ set mode "msgid"
+ } elseif {[regexp "^msgstr \"(.*)\"$" $line dummy match]} {
+ set msgstr $match
+ set mode "msgstr"
+ } elseif {$line == ""} {
+ flush_msg
+ } elseif {[regexp "^\"(.*)\"$" $line dummy match]} {
+ if {$mode == "msgid"} {
+ append msgid $match
+ } elseif {$mode == "msgstr"} {
+ append msgstr $match
+ } else {
+ puts stderr "I do not know what to do: $match"
+ }
+ } else {
+ puts stderr "Cannot handle $line"
+ }
+ }
+ flush_msg
+ close $in
+ close $out
+}
+
+if {$show_statistics} {
+ puts [concat "$translated_count translated messages, " \
+ "$fuzzy_count fuzzy ones, " \
+ "$not_translated_count untranslated ones."]
+}
diff --git a/gitweb/INSTALL b/gitweb/INSTALL
index 6328e26f56..9cd5b0a2b1 100644
--- a/gitweb/INSTALL
+++ b/gitweb/INSTALL
@@ -116,7 +116,7 @@ GITWEB_CONFIG file:
$feature{'pickaxe'}{'default'} = [1];
$feature{'pickaxe'}{'override'} = 1;
- $feature{'snapshot'}{'default'} = ['x-gzip', 'gz', 'gzip'];
+ $feature{'snapshot'}{'default'} = ['zip', 'tgz'];
$feature{'snapshot'}{'override'} = 1;
diff --git a/gitweb/README b/gitweb/README
index 7186cede2f..2163071047 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -10,28 +10,96 @@ From the git version 1.4.0 gitweb is bundled with git.
How to configure gitweb for your local system
---------------------------------------------
+See also "Build time configuration" section in INSTALL
+file for gitweb (in gitweb/INSTALL).
+
You can specify the following configuration variables when building GIT:
+ * GIT_BINDIR
+ Points out where to find git executable. You should set up it to
+ the place where 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 servers name.
+ 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.
+ 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 INSTALL file for gitweb. [Default: /pub/git]
+ * GITWEB_PROJECT_MAXDEPTH
+ The filesystem traversing limit for getting projects 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
+ find depth. [Default: 2007]
* GITWEB_LIST
- points to a directory to scan for projects (defaults to project root)
- or to a file for explicit listing of projects.
+ 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.
+ 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 precendence over URL composed from base URL and project name.
+ Note that you can setup multiple base URLs (for example one for
+ git:// protocol access, one for http:// access) from gitweb config
+ file. [No default]
* GITWEB_CSS
- Points to the location where you put gitweb.css on your web server.
+ Points to the location where you put gitweb.css on your web server
+ (or to be more generic URI of gitweb stylesheet). Relative to base
+ URI of gitweb. Note that you can setup multiple stylesheets from
+ gitweb config file. [Default: gitweb.css]
* GITWEB_LOGO
- Points to the location where you put git-logo.png on your web server.
+ 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: 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: git-favicon.png]
* GITWEB_CONFIG
- This file will be loaded using 'require' and can be used to override any
- of the options above as well as some other options - see the top of
- 'gitweb.cgi' for their full list and description. If the environment
- $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the
- environment variable will be loaded instead of the file
- specified when gitweb.cgi was created.
+ 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]
Runtime gitweb configuration
@@ -39,11 +107,139 @@ Runtime gitweb configuration
You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG`
(defaults to 'gitweb_config.perl' in the same directory as the CGI).
-See the top of 'gitweb.cgi' for the list of variables and some description.
The most notable thing that is not configurable at compile time are the
-optional features, stored in the '%features' variable. You can find further
-description on how to reconfigure the default features setting in your
-`GITWEB_CONFIG` or per-project in `project.git/config` inside 'gitweb.cgi'.
+optional features, stored in the '%features' variable.
+
+Ultimate description on how to reconfigure the default features setting
+in your `GITWEB_CONFIG` or per-project in `project.git/config` can be found
+as comments inside 'gitweb.cgi'.
+
+See also "Gitweb config file" (with example of gitweb config file), and
+"Gitweb repositories" sections in INSTALL file for gitweb.
+
+
+Gitweb config file is [fragment] of perl code. You can set variables
+using "our $variable = value"; text from "#" character until the end
+of a line is ignored. See perlsyn(1) man page for details.
+
+Below there is list of vaiables which you might want to set in gitweb config.
+See the top of 'gitweb.cgi' for the full list of variables and their
+descriptions.
+
+Gitweb config file variables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can set, among others, the following variables in gitweb config files
+(with the exception of $projectroot and $projects_list this list does
+not include variables usually directly set during build):
+ * $GIT
+ Cure git executable to use. By default set to "$GIT_BINDIR/git", which
+ in turn is by default set to "$(bindir)/git". If you use git from binary
+ package, set this to "/usr/bin/git". This can just be "git" if your
+ webserver has a sensible PATH. If you have multiple git versions
+ installed it can be used to choose which one to use.
+ * $version
+ Gitweb version, set automatically when creating gitweb.cgi from
+ gitweb.perl. You might want to modify it if you are running modified
+ gitweb.
+ * $projectroot
+ Absolute filesystem path which will be prepended to project path;
+ the path to repository is $projectroot/$project. Set to
+ $GITWEB_PROJECTROOT during installation. This variable have to be
+ set correctly for gitweb to find repositories.
+ * $projects_list
+ Source of projects list, either directory to scan, or text file
+ with list of repositories (in the "<URI-encoded repository path> SPC
+ <URI-encoded repository owner>" format). Set to $GITWEB_LIST
+ during installation. If empty, $projectroot is used to scan for
+ repositories.
+ * $my_url, $my_uri
+ URL and absolute URL of gitweb script; you might need to set those
+ variables if you are using 'pathinfo' feature: see also below.
+ * $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; you might
+ need to set it up to [base] gitweb URI if you use 'pathinfo' feature
+ (alternative format of the URLs, with project name embedded directly
+ in the path part of URL).
+ * @stylesheets
+ List of URIs of stylesheets (relative to base URI of a page). You
+ might specify more than one stylesheet, for example use gitweb.css
+ as base, with site specific modifications in separate stylesheet
+ to make it easier to upgrade gitweb. You can add 'site' stylesheet
+ for example by using
+ push @stylesheets, "gitweb-site.css";
+ in gitweb config file.
+ * $logo_url, $logo_label
+ URI and label (title) of GIT logo link (or your site logo, if you choose
+ to use different logo image). By default they point to git homepage;
+ in the past they pointed to git documentation at www.kernel.org.
+ * $projects_list_description_width
+ The width (in characters) of the projects list "Description" column.
+ Longer descriptions will be cut (trying to cut at word boundary);
+ 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.
+ * @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".
+ You can setup multiple base URLs (for example one for git:// protocol
+ access, and one for http:// "dumb" protocol access). Note that per
+ repository configuration in 'cloneurl' file, or as values of gitweb.url
+ project config.
+ * $default_blob_plain_mimetype
+ Default mimetype for blob_plain (raw) view, if mimetype checking
+ doesn't result in some other type; by default 'text/plain'.
+ * $default_text_plain_charset
+ Default charset for text files. If not set, web serwer configuration
+ would be used.
+ * $mimetypes_file
+ File to use for (filename extension based) guessing of MIME types before
+ trying /etc/mime.types. Path, if relative, is taken currently as taken
+ relative to current git repositoy.
+ * $fallback_encoding
+ Gitweb assumes this charset if line contains non-UTF-8 characters.
+ Fallback decoding is used without error checking, so it can be even
+ 'utf-8'. Value mist be valid encodig; see Encoding::Supported(3pm) man
+ page for a list. By default 'latin1', aka. 'iso-8859-1'.
+ * @diff_opts
+ Rename detection options for git-diff and git-diff-tree. By default
+ ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or
+ set it to () if you don't want to have renames detection.
+
+Per-repository gitweb configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can also configure individual repositories shown in gitweb by creating
+file in the GIT_DIR of git repository, or by setting some repo configuration
+variable (in GIT_DIR/config).
+
+You can use the following files in repository:
+ * README.html
+ A .html file (HTML fragment) which is included on the gitweb project
+ summary page inside <div> block element. You can use it for longer
+ description of a project, to provide links for example to projects
+ homepage, etc.
+ * description (or gitweb.description)
+ Short (shortened by default to 25 characters in the projects list page)
+ single line description of a project (of a repository). Plain text file;
+ HTML will be escaped. By default set to
+ Unnamed repository; edit this file to name it for gitweb.
+ from the template during creating repository. You can use
+ gitweb.description repo configuration variable, but the file takes
+ precendence.
+ * 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
+ gitweb.url repository configuration variable for that, but the file
+ takes precendence.
+ * gitweb.owner
+ You can use the gitweb.owner repository configuration variable to set
+ repository's owner. It is displayed in the project list and summary
+ page. If it's not set, filesystem directory's owner is used.
+ * various gitweb.* config variables (in config)
+ Read description of %feature hash for detailed list, and some
+ descriptions.
Webserver configuration
diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css
index 1b8887987f..446a1c333b 100644
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
@@ -85,6 +85,10 @@ div.title, a.title {
color: #000000;
}
+div.readme {
+ padding: 8px;
+}
+
a.title:hover {
background-color: #d9d8d1;
}
@@ -170,14 +174,10 @@ a.text:hover {
table {
padding: 8px 4px;
-}
-
-table.project_list {
border-spacing: 0;
}
table.diff_tree {
- border-spacing: 0;
font-family: monospace;
}
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 48e21dad6c..326e27cf88 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -613,6 +613,15 @@ sub href(%) {
$params{'project'} = $project unless exists $params{'project'};
+ if ($params{-replay}) {
+ while (my ($name, $symbol) = each %mapping) {
+ if (!exists $params{$name}) {
+ # to allow for multivalued params we use arrayref form
+ $params{$name} = [ $cgi->param($symbol) ];
+ }
+ }
+ }
+
my ($use_pathinfo) = gitweb_check_feature('pathinfo');
if ($use_pathinfo) {
# use PATH_INFO for project name
@@ -686,10 +695,9 @@ sub validate_refname {
# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
sub to_utf8 {
my $str = shift;
- my $res;
- eval { $res = decode_utf8($str, Encode::FB_CROAK); };
- if (defined $res) {
- return $res;
+ if (utf8::valid($str)) {
+ utf8::decode($str);
+ return $str;
} else {
return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
}
@@ -745,29 +753,40 @@ sub esc_path {
# Make control characters "printable", using character escape codes (CEC)
sub quot_cec {
my $cntrl = shift;
+ my %opts = @_;
my %es = ( # character escape codes, aka escape sequences
- "\t" => '\t', # tab (HT)
- "\n" => '\n', # line feed (LF)
- "\r" => '\r', # carrige return (CR)
- "\f" => '\f', # form feed (FF)
- "\b" => '\b', # backspace (BS)
- "\a" => '\a', # alarm (bell) (BEL)
- "\e" => '\e', # escape (ESC)
- "\013" => '\v', # vertical tab (VT)
- "\000" => '\0', # nul character (NUL)
- );
+ "\t" => '\t', # tab (HT)
+ "\n" => '\n', # line feed (LF)
+ "\r" => '\r', # carrige return (CR)
+ "\f" => '\f', # form feed (FF)
+ "\b" => '\b', # backspace (BS)
+ "\a" => '\a', # alarm (bell) (BEL)
+ "\e" => '\e', # escape (ESC)
+ "\013" => '\v', # vertical tab (VT)
+ "\000" => '\0', # nul character (NUL)
+ );
my $chr = ( (exists $es{$cntrl})
? $es{$cntrl}
: sprintf('\%03o', ord($cntrl)) );
- return "<span class=\"cntrl\">$chr</span>";
+ if ($opts{-nohtml}) {
+ return $chr;
+ } else {
+ return "<span class=\"cntrl\">$chr</span>";
+ }
}
# Alternatively use unicode control pictures codepoints,
# Unicode "printable representation" (PR)
sub quot_upr {
my $cntrl = shift;
+ my %opts = @_;
+
my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
- return "<span class=\"cntrl\">$chr</span>";
+ if ($opts{-nohtml}) {
+ return $chr;
+ } else {
+ return "<span class=\"cntrl\">$chr</span>";
+ }
}
# git may return quoted and escaped filenames
@@ -792,7 +811,7 @@ sub unquote {
return chr(oct($seq));
} elsif (exists $es{$seq}) {
# C escape sequence, aka character escape code
- return $es{$seq}
+ return $es{$seq};
}
# quoted ordinary character
return $seq;
@@ -846,6 +865,23 @@ sub chop_str {
return "$body$tail";
}
+# takes the same arguments as chop_str, but also wraps a <span> around the
+# result with a title attribute if it does get chopped. Additionally, the
+# string is HTML-escaped.
+sub chop_and_escape_str {
+ my $str = shift;
+ my $len = shift;
+ my $add_len = shift || 10;
+
+ my $chopped = chop_str($str, $len, $add_len);
+ if ($chopped eq $str) {
+ return esc_html($chopped);
+ } else {
+ $str =~ s/([[:cntrl:]])/?/g;
+ return $cgi->span({-title=>$str}, esc_html($chopped));
+ }
+}
+
## ----------------------------------------------------------------------
## functions returning short strings
@@ -1406,20 +1442,121 @@ sub git_get_type {
return $type;
}
+# repository configuration
+our $config_file = '';
+our %config;
+
+# store multiple values for single key as anonymous array reference
+# single values stored directly in the hash, not as [ <value> ]
+sub hash_set_multi {
+ my ($hash, $key, $value) = @_;
+
+ if (!exists $hash->{$key}) {
+ $hash->{$key} = $value;
+ } elsif (!ref $hash->{$key}) {
+ $hash->{$key} = [ $hash->{$key}, $value ];
+ } else {
+ push @{$hash->{$key}}, $value;
+ }
+}
+
+# return hash of git project configuration
+# optionally limited to some section, e.g. 'gitweb'
+sub git_parse_project_config {
+ my $section_regexp = shift;
+ my %config;
+
+ local $/ = "\0";
+
+ open my $fh, "-|", git_cmd(), "config", '-z', '-l',
+ or return;
+
+ while (my $keyval = <$fh>) {
+ chomp $keyval;
+ my ($key, $value) = split(/\n/, $keyval, 2);
+
+ hash_set_multi(\%config, $key, $value)
+ if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
+ }
+ close $fh;
+
+ return %config;
+}
+
+# convert config value to boolean, 'true' or 'false'
+# no value, number > 0, 'true' and 'yes' values are true
+# rest of values are treated as false (never as error)
+sub config_to_bool {
+ my $val = shift;
+
+ # strip leading and trailing whitespace
+ $val =~ s/^\s+//;
+ $val =~ s/\s+$//;
+
+ return (!defined $val || # section.key
+ ($val =~ /^\d+$/ && $val) || # section.key = 1
+ ($val =~ /^(?:true|yes)$/i)); # section.key = true
+}
+
+# convert config value to simple decimal number
+# an optional value suffix of 'k', 'm', or 'g' will cause the value
+# to be multiplied by 1024, 1048576, or 1073741824
+sub config_to_int {
+ my $val = shift;
+
+ # strip leading and trailing whitespace
+ $val =~ s/^\s+//;
+ $val =~ s/\s+$//;
+
+ if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
+ $unit = lc($unit);
+ # unknown unit is treated as 1
+ return $num * ($unit eq 'g' ? 1073741824 :
+ $unit eq 'm' ? 1048576 :
+ $unit eq 'k' ? 1024 : 1);
+ }
+ return $val;
+}
+
+# convert config value to array reference, if needed
+sub config_to_multi {
+ my $val = shift;
+
+ return ref($val) ? $val : (defined($val) ? [ $val ] : []);
+}
+
sub git_get_project_config {
my ($key, $type) = @_;
+ # key sanity check
return unless ($key);
$key =~ s/^gitweb\.//;
return if ($key =~ m/\W/);
- my @x = (git_cmd(), 'config');
- if (defined $type) { push @x, $type; }
- push @x, "--get";
- push @x, "gitweb.$key";
- my $val = qx(@x);
- chomp $val;
- return ($val);
+ # type sanity check
+ if (defined $type) {
+ $type =~ s/^--//;
+ $type = undef
+ unless ($type eq 'bool' || $type eq 'int');
+ }
+
+ # get config
+ if (!defined $config_file ||
+ $config_file ne "$git_dir/config") {
+ %config = git_parse_project_config('gitweb');
+ $config_file = "$git_dir/config";
+ }
+
+ # ensure given type
+ if (!defined $type) {
+ return $config{"gitweb.$key"};
+ } elsif ($type eq 'bool') {
+ # backward compatibility: 'git config --bool' returns true/false
+ return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
+ } elsif ($type eq 'int') {
+ return config_to_int($config{"gitweb.$key"});
+ }
+ return $config{"gitweb.$key"};
}
# get hash of given path at given ref
@@ -1479,7 +1616,9 @@ sub git_get_path_by_hash {
sub git_get_project_description {
my $path = shift;
- open my $fd, "$projectroot/$path/description" or return undef;
+ $git_dir = "$projectroot/$path";
+ open my $fd, "$git_dir/description"
+ or return git_get_project_config('description');
my $descr = <$fd>;
close $fd;
if (defined $descr) {
@@ -1491,7 +1630,11 @@ sub git_get_project_description {
sub git_get_project_url_list {
my $path = shift;
- open my $fd, "$projectroot/$path/cloneurl" or return;
+ $git_dir = "$projectroot/$path";
+ open my $fd, "$git_dir/cloneurl"
+ or return wantarray ?
+ @{ config_to_multi(git_get_project_config('url')) } :
+ config_to_multi(git_get_project_config('url'));
my @git_project_url_list = map { chomp; $_ } <$fd>;
close $fd;
@@ -1627,6 +1770,7 @@ sub git_get_project_owner {
my $owner;
return undef unless $project;
+ $git_dir = "$projectroot/$project";
if (!defined $gitweb_project_owner) {
git_get_project_list_from_file();
@@ -1635,8 +1779,11 @@ sub git_get_project_owner {
if (exists $gitweb_project_owner->{$project}) {
$owner = $gitweb_project_owner->{$project};
}
+ if (!defined $owner){
+ $owner = git_get_project_config('owner');
+ }
if (!defined $owner) {
- $owner = get_file_owner("$projectroot/$project");
+ $owner = get_file_owner("$git_dir");
}
return $owner;
@@ -1723,7 +1870,7 @@ sub parse_date {
$date{'mday-time'} = sprintf "%d %s %02d:%02d",
$mday, $months[$mon], $hour ,$min;
$date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
- 1900+$year, $mon, $mday, $hour ,$min, $sec;
+ 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);
@@ -1973,12 +2120,12 @@ sub parse_difftree_raw_line {
$res{'to_mode'} = $2;
$res{'from_id'} = $3;
$res{'to_id'} = $4;
- $res{'status'} = $5;
+ $res{'status'} = $res{'status_str'} = $5;
$res{'similarity'} = $6;
if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
} else {
- $res{'file'} = unquote($7);
+ $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
}
}
# '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
@@ -1989,6 +2136,7 @@ sub parse_difftree_raw_line {
$res{'to_mode'} = pop @{$res{'from_mode'}};
$res{'from_id'} = [ split(' ', $3) ];
$res{'to_id'} = pop @{$res{'from_id'}};
+ $res{'status_str'} = $4;
$res{'status'} = [ split('', $4) ];
$res{'to_file'} = unquote($5);
}
@@ -2000,6 +2148,19 @@ sub parse_difftree_raw_line {
return wantarray ? %res : \%res;
}
+# wrapper: return parsed line of git-diff-tree "raw" output
+# (the argument might be raw line, or parsed info)
+sub parsed_difftree_line {
+ my $line_or_ref = shift;
+
+ if (ref($line_or_ref) eq "HASH") {
+ # pre-parsed (or generated by hand)
+ return $line_or_ref;
+ } else {
+ return parse_difftree_raw_line($line_or_ref);
+ }
+}
+
# parse line of git-ls-tree output
sub parse_ls_tree_line ($;%) {
my $line = shift;
@@ -2032,7 +2193,10 @@ sub parse_from_to_diffinfo {
fill_from_file_info($diffinfo, @parents)
unless exists $diffinfo->{'from_file'};
for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
- $from->{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
+ $from->{'file'}[$i] =
+ defined $diffinfo->{'from_file'}[$i] ?
+ $diffinfo->{'from_file'}[$i] :
+ $diffinfo->{'to_file'};
if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
$from->{'href'}[$i] = href(action=>"blob",
hash_base=>$parents[$i],
@@ -2043,7 +2207,8 @@ sub parse_from_to_diffinfo {
}
}
} else {
- $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
+ # ordinary (not combined) diff
+ $from->{'file'} = $diffinfo->{'from_file'};
if ($diffinfo->{'status'} ne "A") { # not new (added) file
$from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
hash=>$diffinfo->{'from_id'},
@@ -2053,7 +2218,7 @@ sub parse_from_to_diffinfo {
}
}
- $to->{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+ $to->{'file'} = $diffinfo->{'to_file'};
if (!is_deleted($diffinfo)) { # file exists in result
$to->{'href'} = href(action=>"blob", hash_base=>$hash,
hash=>$diffinfo->{'to_id'},
@@ -2083,6 +2248,7 @@ sub git_get_heads_list {
my ($hash, $name, $title) = split(' ', $refinfo, 3);
my ($committer, $epoch, $tz) =
($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
+ $ref_item{'fullname'} = $name;
$name =~ s!^refs/heads/!!;
$ref_item{'name'} = $name;
@@ -2120,6 +2286,7 @@ sub git_get_tags_list {
my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
my ($creator, $epoch, $tz) =
($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
+ $ref_item{'fullname'} = $name;
$name =~ s!^refs/tags/!!;
$ref_item{'type'} = $type;
@@ -2474,7 +2641,7 @@ sub format_paging_nav {
if ($page > 0) {
$paging_nav .= " &sdot; " .
- $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1),
+ $cgi->a({-href => href(-replay=>1, page=>$page-1),
-accesskey => "p", -title => "Alt-p"}, "prev");
} else {
$paging_nav .= " &sdot; prev";
@@ -2482,7 +2649,7 @@ sub format_paging_nav {
if ($nrevs >= (100 * ($page+1)-1)) {
$paging_nav .= " &sdot; " .
- $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1),
+ $cgi->a({-href => href(-replay=>1, page=>$page+1),
-accesskey => "n", -title => "Alt-n"}, "next");
} else {
$paging_nav .= " &sdot; next";
@@ -2766,6 +2933,7 @@ sub git_print_tree_entry {
## ......................................................................
## functions printing large fragments of HTML
+# get pre-image filenames for merge (combined) diff
sub fill_from_file_info {
my ($diff, @parents) = @_;
@@ -2782,28 +2950,25 @@ sub fill_from_file_info {
return $diff;
}
-# parameters can be strings, or references to arrays of strings
-sub from_ids_eq {
- my ($a, $b) = @_;
+# is current raw difftree line of file deletion
+sub is_deleted {
+ my $diffinfo = shift;
- if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) {
- for (my $i = 0; $i < @$a; ++$i) {
- return 0 unless ($a->[$i] eq $b->[$i]);
- }
- return 1;
- } elsif (!ref($a) && !ref($b)) {
- return $a eq $b;
- } else {
- return 0;
- }
+ return $diffinfo->{'status_str'} =~ /D/;
}
-sub is_deleted {
- my $diffinfo = shift;
+# does patch correspond to [previous] difftree raw line
+# $diffinfo - hashref of parsed raw diff format
+# $patchinfo - hashref of parsed patch diff format
+# (the same keys as in $diffinfo)
+sub is_patch_split {
+ my ($diffinfo, $patchinfo) = @_;
- return $diffinfo->{'to_id'} eq ('0' x 40);
+ return defined $diffinfo && defined $patchinfo
+ && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
}
+
sub git_difftree_body {
my ($difftree, $hash, @parents) = @_;
my ($parent) = $parents[0];
@@ -2840,13 +3005,7 @@ sub git_difftree_body {
my $alternate = 1;
my $patchno = 0;
foreach my $line (@{$difftree}) {
- my $diff;
- if (ref($line) eq "HASH") {
- # pre-parsed (or generated by hand)
- $diff = $line;
- } else {
- $diff = parse_difftree_raw_line($line);
- }
+ my $diff = parsed_difftree_line($line);
if ($alternate) {
print "<tr class=\"dark\">\n";
@@ -3117,10 +3276,12 @@ sub git_patchset_body {
my ($fd, $difftree, $hash, @hash_parents) = @_;
my ($hash_parent) = $hash_parents[0];
+ my $is_combined = (@hash_parents > 1);
my $patch_idx = 0;
my $patch_number = 0;
my $patch_line;
my $diffinfo;
+ my $to_name;
my (%from, %to);
print "<div class=\"patchset\">\n";
@@ -3134,73 +3295,46 @@ sub git_patchset_body {
PATCH:
while ($patch_line) {
- my @diff_header;
- my ($from_id, $to_id);
- # git diff header
- #assert($patch_line =~ m/^diff /) if DEBUG;
- #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
- $patch_number++;
- push @diff_header, $patch_line;
-
- # extended diff header
- EXTENDED_HEADER:
- while ($patch_line = <$fd>) {
- chomp $patch_line;
-
- last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
-
- if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
- $from_id = $1;
- $to_id = $2;
- } elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
- $from_id = [ split(',', $1) ];
- $to_id = $2;
- }
-
- push @diff_header, $patch_line;
+ # parse "git diff" header line
+ if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
+ # $1 is from_name, which we do not use
+ $to_name = unquote($2);
+ $to_name =~ s!^b/!!;
+ } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
+ # $1 is 'cc' or 'combined', which we do not use
+ $to_name = unquote($2);
+ } else {
+ $to_name = undef;
}
- my $last_patch_line = $patch_line;
# check if current patch belong to current raw line
# and parse raw git-diff line if needed
- if (defined $diffinfo &&
- defined $from_id && defined $to_id &&
- from_ids_eq($diffinfo->{'from_id'}, $from_id) &&
- $diffinfo->{'to_id'} eq $to_id) {
+ if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
# this is continuation of a split patch
print "<div class=\"patch cont\">\n";
} else {
# advance raw git-diff output if needed
$patch_idx++ if defined $diffinfo;
- # compact combined diff output can have some patches skipped
- # find which patch (using pathname of result) we are at now
- my $to_name;
- if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) {
- $to_name = $1;
- }
+ # read and prepare patch information
+ $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
- do {
- # read and prepare patch information
- if (ref($difftree->[$patch_idx]) eq "HASH") {
- # pre-parsed (or generated by hand)
- $diffinfo = $difftree->[$patch_idx];
- } else {
- $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
- }
-
- # check if current raw line has no patch (it got simplified)
- if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) {
+ # compact combined diff output can have some patches skipped
+ # find which patch (using pathname of result) we are at now;
+ if ($is_combined) {
+ while ($to_name ne $diffinfo->{'to_file'}) {
print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
format_diff_cc_simplified($diffinfo, @hash_parents) .
"</div>\n"; # class="patch"
$patch_idx++;
$patch_number++;
+
+ last if $patch_idx > $#$difftree;
+ $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
}
- } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} ||
- $patch_idx > $#$difftree);
+ }
# modifies %from, %to hashes
parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
@@ -3210,30 +3344,36 @@ sub git_patchset_body {
print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
}
+ # git diff header
+ #assert($patch_line =~ m/^diff /) if DEBUG;
+ #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
+ $patch_number++;
# print "git diff" header
- $patch_line = shift @diff_header;
print format_git_diff_header_line($patch_line, $diffinfo,
\%from, \%to);
# print extended diff header
- print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
+ print "<div class=\"diff extended_header\">\n";
EXTENDED_HEADER:
- foreach $patch_line (@diff_header) {
+ while ($patch_line = <$fd>) {
+ chomp $patch_line;
+
+ last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
+
print format_extended_diff_header_line($patch_line, $diffinfo,
\%from, \%to);
}
- print "</div>\n" if (@diff_header > 0); # class="diff extended_header"
+ print "</div>\n"; # class="diff extended_header"
# from-file/to-file diff header
- $patch_line = $last_patch_line;
if (! $patch_line) {
print "</div>\n"; # class="patch"
last PATCH;
}
next PATCH if ($patch_line =~ m/^diff /);
#assert($patch_line =~ m/^---/) if DEBUG;
- #assert($patch_line eq $last_patch_line) if DEBUG;
+ my $last_patch_line = $patch_line;
$patch_line = <$fd>;
chomp $patch_line;
#assert($patch_line =~ m/^\+\+\+/) if DEBUG;
@@ -3258,16 +3398,11 @@ sub git_patchset_body {
# for compact combined (--cc) format, with chunk and patch simpliciaction
# patchset might be empty, but there might be unprocessed raw lines
- for ($patch_idx++ if $patch_number > 0;
+ for (++$patch_idx if $patch_number > 0;
$patch_idx < @$difftree;
- $patch_idx++) {
+ ++$patch_idx) {
# read and prepare patch information
- if (ref($difftree->[$patch_idx]) eq "HASH") {
- # pre-parsed (or generated by hand)
- $diffinfo = $difftree->[$patch_idx];
- } else {
- $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
- }
+ $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
# generate anchor for "patch" links in difftree / whatchanged part
print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
@@ -3395,7 +3530,7 @@ sub git_project_list_body {
"<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-class => "list", -title => $pr->{'descr_long'}},
esc_html($pr->{'descr'})) . "</td>\n" .
- "<td><i>" . esc_html(chop_str($pr->{'owner'}, 15)) . "</i></td>\n";
+ "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
print "<td class=\"". age_class($pr->{'age'}) . "\">" .
(defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
"<td class=\"link\">" .
@@ -3425,7 +3560,7 @@ sub git_shortlog_body {
$from = 0 unless defined $from;
$to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
- print "<table class=\"shortlog\" cellspacing=\"0\">\n";
+ print "<table class=\"shortlog\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my %co = %{$commitlist->[$i]};
@@ -3437,9 +3572,10 @@ sub git_shortlog_body {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
+ my $author = chop_and_escape_str($co{'author_name'}, 10);
# git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
- "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
+ "<td><i>" . $author . "</i></td>\n" .
"<td>";
print format_subject_html($co{'title'}, $co{'title_short'},
href(action=>"commit", hash=>$commit), $ref);
@@ -3470,7 +3606,7 @@ sub git_history_body {
$from = 0 unless defined $from;
$to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
- print "<table class=\"history\" cellspacing=\"0\">\n";
+ print "<table class=\"history\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my %co = %{$commitlist->[$i]};
@@ -3487,9 +3623,10 @@ sub git_history_body {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
+ # shortlog uses chop_str($co{'author_name'}, 10)
+ my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
- # shortlog uses chop_str($co{'author_name'}, 10)
- "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
+ "<td><i>" . $author . "</i></td>\n" .
"<td>";
# originally git_history used chop_str($co{'title'}, 50)
print format_subject_html($co{'title'}, $co{'title_short'},
@@ -3529,7 +3666,7 @@ sub git_tags_body {
$from = 0 unless defined $from;
$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
- print "<table class=\"tags\" cellspacing=\"0\">\n";
+ print "<table class=\"tags\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my $entry = $taglist->[$i];
@@ -3570,8 +3707,8 @@ sub git_tags_body {
"<td class=\"link\">" . " | " .
$cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
if ($tag{'reftype'} eq "commit") {
- print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") .
- " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log");
+ print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
+ " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
} elsif ($tag{'reftype'} eq "blob") {
print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
}
@@ -3592,7 +3729,7 @@ sub git_heads_body {
$from = 0 unless defined $from;
$to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
- print "<table class=\"heads\" cellspacing=\"0\">\n";
+ print "<table class=\"heads\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my $entry = $headlist->[$i];
@@ -3606,13 +3743,13 @@ sub git_heads_body {
$alternate ^= 1;
print "<td><i>$ref{'age'}</i></td>\n" .
($curr ? "<td class=\"current_head\">" : "<td>") .
- $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}),
+ $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
-class => "list name"},esc_html($ref{'name'})) .
"</td>\n" .
"<td class=\"link\">" .
- $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " .
- $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") .
+ $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") .
"</td>\n" .
"</tr>";
}
@@ -3629,7 +3766,7 @@ sub git_search_grep_body {
$from = 0 unless defined $from;
$to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
- print "<table class=\"grep\" cellspacing=\"0\">\n";
+ print "<table class=\"commit_search\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my %co = %{$commitlist->[$i]};
@@ -3643,27 +3780,36 @@ sub git_search_grep_body {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
+ 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>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+ "<td><i>" . $author . "</i></td>\n" .
"<td>" .
- $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
- esc_html(chop_str($co{'title'}, 50)) . "<br/>");
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
+ -class => "list subject"},
+ chop_and_escape_str($co{'title'}, 50) . "<br/>");
my $comment = $co{'comment'};
foreach my $line (@$comment) {
if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
- my $lead = esc_html($1) || "";
- $lead = chop_str($lead, 30, 10);
- my $match = esc_html($2) || "";
- my $trail = esc_html($3) || "";
- $trail = chop_str($trail, 30, 10);
- my $text = "$lead<span class=\"match\">$match</span>$trail";
- print chop_str($text, 80, 5) . "<br/>\n";
+ my ($lead, $match, $trail) = ($1, $2, $3);
+ $match = chop_str($match, 70, 5); # in case match is very long
+ my $contextlen = (80 - len($match))/2; # is left for the remainder
+ $contextlen = 30 if ($contextlen > 30); # but not too much
+ $lead = chop_str($lead, $contextlen, 10);
+ $trail = chop_str($trail, $contextlen, 10);
+
+ $lead = esc_html($lead);
+ $match = esc_html($match);
+ $trail = esc_html($trail);
+
+ print "$lead<span class=\"match\">$match</span>$trail<br />";
}
}
print "</td>\n" .
"<td class=\"link\">" .
$cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
" | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
+ " | " .
$cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
print "</td>\n" .
"</tr>\n";
@@ -3769,7 +3915,7 @@ sub git_summary {
git_print_page_nav('summary','', $head);
print "<div class=\"title\">&nbsp;</div>\n";
- print "<table cellspacing=\"0\">\n" .
+ print "<table class=\"projects_list\">\n" .
"<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
"<tr><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
if (defined $cd{'rfc2822'}) {
@@ -3790,8 +3936,10 @@ sub git_summary {
if (-s "$projectroot/$project/README.html") {
if (open my $fd, "$projectroot/$project/README.html") {
- print "<div class=\"title\">readme</div>\n";
+ print "<div class=\"title\">readme</div>\n" .
+ "<div class=\"readme\">\n";
print $_ while (<$fd>);
+ print "\n</div>\n"; # class="readme"
close $fd;
}
}
@@ -3843,7 +3991,7 @@ sub git_tag {
git_print_header_div('commit', esc_html($tag{'name'}), $hash);
print "<div class=\"title_text\">\n" .
- "<table cellspacing=\"0\">\n" .
+ "<table class=\"object_header\">\n" .
"<tr>\n" .
"<td>object</td>\n" .
"<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
@@ -3896,11 +4044,11 @@ sub git_blame2 {
or die_error(undef, "Open git-blame failed");
git_header_html();
my $formats_nav =
- $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
+ $cgi->a({-href => href(action=>"blob", -replay=>1)},
"blob") .
" | " .
- $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
- "history") .
+ $cgi->a({-href => href(action=>"history", -replay=>1)},
+ "history") .
" | " .
$cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
"HEAD");
@@ -4163,7 +4311,7 @@ sub git_blob {
open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
or die_error(undef, "Couldn't cat $file_name, $hash");
my $mimetype = blob_mimetype($fd, $file_name);
- if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)!) {
+ if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
close $fd;
return git_blob_plain($mimetype);
}
@@ -4176,18 +4324,15 @@ sub git_blob {
if (defined $file_name) {
if ($have_blame) {
$formats_nav .=
- $cgi->a({-href => href(action=>"blame", hash_base=>$hash_base,
- hash=>$hash, file_name=>$file_name)},
+ $cgi->a({-href => href(action=>"blame", -replay=>1)},
"blame") .
" | ";
}
$formats_nav .=
- $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
- hash=>$hash, file_name=>$file_name)},
+ $cgi->a({-href => href(action=>"history", -replay=>1)},
"history") .
" | " .
- $cgi->a({-href => href(action=>"blob_plain",
- hash=>$hash, file_name=>$file_name)},
+ $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
"raw") .
" | " .
$cgi->a({-href => href(action=>"blob",
@@ -4195,7 +4340,8 @@ sub git_blob {
"HEAD");
} else {
$formats_nav .=
- $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw");
+ $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
+ "raw");
}
git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
@@ -4206,16 +4352,7 @@ sub git_blob {
}
git_print_page_path($file_name, "blob", $hash_base);
print "<div class=\"page_body\">\n";
- if ($mimetype =~ m!^text/!) {
- my $nr;
- while (my $line = <$fd>) {
- chomp $line;
- $nr++;
- $line = untabify($line);
- printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
- $nr, $nr, $nr, esc_html($line, -nbsp=>1);
- }
- } elsif ($mimetype =~ m!^image/!) {
+ if ($mimetype =~ m!^image/!) {
print qq!<img type="$mimetype"!;
if ($file_name) {
print qq! alt="$file_name" title="$file_name"!;
@@ -4224,6 +4361,15 @@ sub git_blob {
href(action=>"blob_plain", hash=>$hash,
hash_base=>$hash_base, file_name=>$file_name) .
qq!" />\n!;
+ } else {
+ my $nr;
+ while (my $line = <$fd>) {
+ chomp $line;
+ $nr++;
+ $line = untabify($line);
+ printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
+ $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+ }
}
close $fd
or print "Reading blob failed.\n";
@@ -4258,8 +4404,7 @@ sub git_tree {
my @views_nav = ();
if (defined $file_name) {
push @views_nav,
- $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
- hash=>$hash, file_name=>$file_name)},
+ $cgi->a({-href => href(action=>"history", -replay=>1)},
"history"),
$cgi->a({-href => href(action=>"tree",
hash_base=>"HEAD", file_name=>$file_name)},
@@ -4286,7 +4431,7 @@ sub git_tree {
}
git_print_page_path($file_name, 'tree', $hash_base);
print "<div class=\"page_body\">\n";
- print "<table cellspacing=\"0\">\n";
+ print "<table class=\"tree\">\n";
my $alternate = 1;
# '..' (top directory) link if possible
if (defined $hash_base &&
@@ -4433,7 +4578,7 @@ sub git_log {
}
if ($#commitlist >= 100) {
print "<div class=\"page_nav\">\n";
- print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1),
+ print $cgi->a({-href => href(-replay=>1, page=>$page+1),
-accesskey => "n", -title => "Alt-n"}, "next");
print "</div>\n";
}
@@ -4508,7 +4653,7 @@ sub git_commit {
git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
}
print "<div class=\"title_text\">\n" .
- "<table cellspacing=\"0\">\n";
+ "<table class=\"object_header\">\n";
print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n".
"<tr>" .
"<td></td><td> $ad{'rfc2822'}";
@@ -4665,8 +4810,8 @@ sub git_blobdiff {
}
%diffinfo = parse_difftree_raw_line($difftree[0]);
- $file_parent ||= $diffinfo{'from_file'} || $file_name || $diffinfo{'file'};
- $file_name ||= $diffinfo{'to_file'} || $diffinfo{'file'};
+ $file_parent ||= $diffinfo{'from_file'} || $file_name;
+ $file_name ||= $diffinfo{'to_file'};
$hash_parent ||= $diffinfo{'from_id'};
$hash ||= $diffinfo{'to_id'};
@@ -4727,10 +4872,7 @@ sub git_blobdiff {
# header
if ($format eq 'html') {
my $formats_nav =
- $cgi->a({-href => href(action=>"blobdiff_plain",
- hash=>$hash, hash_parent=>$hash_parent,
- hash_base=>$hash_base, hash_parent_base=>$hash_parent_base,
- file_name=>$file_name, file_parent=>$file_parent)},
+ $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
"raw");
git_header_html(undef, $expires);
if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -4804,8 +4946,7 @@ sub git_commitdiff {
my $formats_nav;
if ($format eq 'html') {
$formats_nav =
- $cgi->a({-href => href(action=>"commitdiff_plain",
- hash=>$hash, hash_parent=>$hash_parent)},
+ $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
"raw");
if (defined $hash_parent &&
@@ -4928,16 +5069,15 @@ sub git_commitdiff {
-expires => $expires,
-content_disposition => 'inline; filename="' . "$filename" . '"');
my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
- print <<TEXT;
-From: $co{'author'}
-Date: $ad{'rfc2822'} ($ad{'tz_local'})
-Subject: $co{'title'}
-TEXT
+ print "From: " . to_utf8($co{'author'}) . "\n";
+ print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
+ print "Subject: " . to_utf8($co{'title'}) . "\n";
+
print "X-Git-Tag: $tagname\n" if $tagname;
print "X-Git-Url: " . $cgi->self_url() . "\n\n";
foreach my $line (@{$co{'comment'}}) {
- print "$line\n";
+ print to_utf8($line) . "\n";
}
print "---\n\n";
}
@@ -5000,27 +5140,20 @@ sub git_history {
file_name=>$file_name)},
"first");
$paging_nav .= " &sdot; " .
- $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
- file_name=>$file_name, page=>$page-1),
+ $cgi->a({-href => href(-replay=>1, page=>$page-1),
-accesskey => "p", -title => "Alt-p"}, "prev");
} else {
$paging_nav .= "first";
$paging_nav .= " &sdot; prev";
}
- if ($#commitlist >= 100) {
- $paging_nav .= " &sdot; " .
- $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
- file_name=>$file_name, page=>$page+1),
- -accesskey => "n", -title => "Alt-n"}, "next");
- } else {
- $paging_nav .= " &sdot; next";
- }
my $next_link = '';
if ($#commitlist >= 100) {
$next_link =
- $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
- file_name=>$file_name, page=>$page+1),
+ $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();
@@ -5090,30 +5223,23 @@ sub git_search {
searchtext=>$searchtext, searchtype=>$searchtype)},
"first");
$paging_nav .= " &sdot; " .
- $cgi->a({-href => href(action=>"search", hash=>$hash,
- searchtext=>$searchtext, searchtype=>$searchtype,
- page=>$page-1),
+ $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) {
- $paging_nav .= " &sdot; " .
- $cgi->a({-href => href(action=>"search", hash=>$hash,
- searchtext=>$searchtext, searchtype=>$searchtype,
- page=>$page+1),
+ $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";
}
- my $next_link = '';
+
if ($#commitlist >= 100) {
- $next_link =
- $cgi->a({-href => href(action=>"search", hash=>$hash,
- searchtext=>$searchtext, searchtype=>$searchtype,
- page=>$page+1),
- -accesskey => "n", -title => "Alt-n"}, "next");
}
git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
@@ -5125,7 +5251,7 @@ sub git_search {
git_print_page_nav('','', $hash,$co{'tree'},$hash);
git_print_header_div('commit', esc_html($co{'title'}), $hash);
- print "<table cellspacing=\"0\">\n";
+ print "<table class=\"pickaxe search\">\n";
my $alternate = 1;
$/ = "\n";
my $git_command = git_cmd_str();
@@ -5157,12 +5283,13 @@ sub git_search {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
+ 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>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+ "<td><i>" . $author . "</i></td>\n" .
"<td>" .
$cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
-class => "list subject"},
- esc_html(chop_str($co{'title'}, 50)) . "<br/>");
+ chop_and_escape_str($co{'title'}, 50) . "<br/>");
while (my $setref = shift @files) {
my %set = %$setref;
print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
@@ -5191,7 +5318,7 @@ sub git_search {
git_print_page_nav('','', $hash,$co{'tree'},$hash);
git_print_header_div('commit', esc_html($co{'title'}), $hash);
- print "<table cellspacing=\"0\">\n";
+ print "<table class=\"grep_search\">\n";
my $alternate = 1;
my $matches = 0;
$/ = "\n";
@@ -5311,7 +5438,7 @@ sub git_shortlog {
my $next_link = '';
if ($#commitlist >= 100) {
$next_link =
- $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1),
+ $cgi->a({-href => href(-replay=>1, page=>$page+1),
-accesskey => "n", -title => "Alt-n"}, "next");
}
@@ -5459,7 +5586,7 @@ XML
or next;
# print element (entry, item)
- my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
+ my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
if ($format eq 'rss') {
print "<item>\n" .
"<title>" . esc_html($co{'title'}) . "</title>\n" .
diff --git a/hash-object.c b/hash-object.c
index 18f5017f51..0a58f3f126 100644
--- a/hash-object.c
+++ b/hash-object.c
@@ -42,6 +42,8 @@ int main(int argc, char **argv)
int prefix_length = -1;
int no_more_flags = 0;
+ git_config(git_default_config);
+
for (i = 1 ; i < argc; i++) {
if (!no_more_flags && argv[i][0] == '-') {
if (!strcmp(argv[i], "-t")) {
diff --git a/hash.c b/hash.c
new file mode 100644
index 0000000000..d9ec82fa66
--- /dev/null
+++ b/hash.c
@@ -0,0 +1,110 @@
+/*
+ * Some generic hashing helpers.
+ */
+#include "cache.h"
+#include "hash.h"
+
+/*
+ * Look up a hash entry in the hash table. Return the pointer to
+ * the existing entry, or the empty slot if none existed. The caller
+ * can then look at the (*ptr) to see whether it existed or not.
+ */
+static struct hash_table_entry *lookup_hash_entry(unsigned int hash, struct hash_table *table)
+{
+ unsigned int size = table->size, nr = hash % size;
+ struct hash_table_entry *array = table->array;
+
+ while (array[nr].ptr) {
+ if (array[nr].hash == hash)
+ break;
+ nr++;
+ if (nr >= size)
+ nr = 0;
+ }
+ return array + nr;
+}
+
+
+/*
+ * Insert a new hash entry pointer into the table.
+ *
+ * If that hash entry already existed, return the pointer to
+ * the existing entry (and the caller can create a list of the
+ * pointers or do anything else). If it didn't exist, return
+ * NULL (and the caller knows the pointer has been inserted).
+ */
+static void **insert_hash_entry(unsigned int hash, void *ptr, struct hash_table *table)
+{
+ struct hash_table_entry *entry = lookup_hash_entry(hash, table);
+
+ if (!entry->ptr) {
+ entry->ptr = ptr;
+ entry->hash = hash;
+ table->nr++;
+ return NULL;
+ }
+ return &entry->ptr;
+}
+
+static void grow_hash_table(struct hash_table *table)
+{
+ unsigned int i;
+ unsigned int old_size = table->size, new_size;
+ struct hash_table_entry *old_array = table->array, *new_array;
+
+ new_size = alloc_nr(old_size);
+ new_array = xcalloc(sizeof(struct hash_table_entry), new_size);
+ table->size = new_size;
+ table->array = new_array;
+ table->nr = 0;
+ for (i = 0; i < old_size; i++) {
+ unsigned int hash = old_array[i].hash;
+ void *ptr = old_array[i].ptr;
+ if (ptr)
+ insert_hash_entry(hash, ptr, table);
+ }
+ free(old_array);
+}
+
+void *lookup_hash(unsigned int hash, struct hash_table *table)
+{
+ if (!table->array)
+ return NULL;
+ return lookup_hash_entry(hash, table)->ptr;
+}
+
+void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table)
+{
+ unsigned int nr = table->nr;
+ if (nr >= table->size/2)
+ grow_hash_table(table);
+ return insert_hash_entry(hash, ptr, table);
+}
+
+int for_each_hash(struct hash_table *table, int (*fn)(void *))
+{
+ int sum = 0;
+ unsigned int i;
+ unsigned int size = table->size;
+ struct hash_table_entry *array = table->array;
+
+ for (i = 0; i < size; i++) {
+ void *ptr = array->ptr;
+ array++;
+ if (ptr) {
+ int val = fn(ptr);
+ if (val < 0)
+ return val;
+ sum += val;
+ }
+ }
+ return sum;
+}
+
+void free_hash(struct hash_table *table)
+{
+ free(table->array);
+ table->array = NULL;
+ table->size = 0;
+ table->nr = 0;
+}
diff --git a/hash.h b/hash.h
new file mode 100644
index 0000000000..a8b0fbb5b5
--- /dev/null
+++ b/hash.h
@@ -0,0 +1,43 @@
+#ifndef HASH_H
+#define HASH_H
+
+/*
+ * These are some simple generic hash table helper functions.
+ * Not necessarily suitable for all users, but good for things
+ * where you want to just keep track of a list of things, and
+ * have a good hash to use on them.
+ *
+ * It keeps the hash table at roughly 50-75% free, so the memory
+ * cost of the hash table itself is roughly
+ *
+ * 3 * 2*sizeof(void *) * nr_of_objects
+ *
+ * bytes.
+ *
+ * FIXME: on 64-bit architectures, we waste memory. It would be
+ * good to have just 32-bit pointers, requiring a special allocator
+ * for hashed entries or something.
+ */
+struct hash_table_entry {
+ unsigned int hash;
+ void *ptr;
+};
+
+struct hash_table {
+ unsigned int size, nr;
+ struct hash_table_entry *array;
+};
+
+extern void *lookup_hash(unsigned int hash, struct hash_table *table);
+extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table);
+extern int for_each_hash(struct hash_table *table, int (*fn)(void *));
+extern void free_hash(struct hash_table *table);
+
+static inline void init_hash(struct hash_table *table)
+{
+ table->size = 0;
+ table->nr = 0;
+ table->array = NULL;
+}
+
+#endif
diff --git a/help.c b/help.c
index 1cd33ece6b..8143dcdd38 100644
--- a/help.c
+++ b/help.c
@@ -7,7 +7,42 @@
#include "builtin.h"
#include "exec_cmd.h"
#include "common-cmds.h"
-#include <sys/ioctl.h>
+
+static const char *help_default_format;
+
+static enum help_format {
+ man_format,
+ info_format,
+ web_format,
+} help_format = man_format;
+
+static void parse_help_format(const char *format)
+{
+ if (!format) {
+ help_format = man_format;
+ return;
+ }
+ if (!strcmp(format, "man")) {
+ help_format = man_format;
+ return;
+ }
+ if (!strcmp(format, "info")) {
+ help_format = info_format;
+ return;
+ }
+ if (!strcmp(format, "web") || !strcmp(format, "html")) {
+ help_format = web_format;
+ return;
+ }
+ die("unrecognized help format '%s'", format);
+}
+
+static int git_help_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "help.format"))
+ return git_config_string(&help_default_format, var, value);
+ return git_default_config(var, value);
+}
/* most GUI terminals set COLUMNS (although some don't export it) */
static int term_columns(void)
@@ -37,24 +72,25 @@ static inline void mput_char(char c, unsigned int num)
putchar(c);
}
-static struct cmdname {
- size_t len;
- char name[1];
-} **cmdname;
-static int cmdname_alloc, cmdname_cnt;
+static struct cmdnames {
+ int alloc;
+ int cnt;
+ struct cmdname {
+ size_t len;
+ char name[1];
+ } **names;
+} main_cmds, other_cmds;
-static void add_cmdname(const char *name, int len)
+static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
{
- struct cmdname *ent;
- if (cmdname_alloc <= cmdname_cnt) {
- cmdname_alloc = cmdname_alloc + 200;
- cmdname = xrealloc(cmdname, cmdname_alloc * sizeof(*cmdname));
- }
- ent = xmalloc(sizeof(*ent) + len);
+ struct cmdname *ent = xmalloc(sizeof(*ent) + len);
+
ent->len = len;
memcpy(ent->name, name, len);
ent->name[len] = 0;
- cmdname[cmdname_cnt++] = ent;
+
+ ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
+ cmds->names[cmds->cnt++] = ent;
}
static int cmdname_compare(const void *a_, const void *b_)
@@ -64,7 +100,43 @@ static int cmdname_compare(const void *a_, const void *b_)
return strcmp(a->name, b->name);
}
-static void pretty_print_string_list(struct cmdname **cmdname, int longest)
+static void uniq(struct cmdnames *cmds)
+{
+ int i, j;
+
+ if (!cmds->cnt)
+ return;
+
+ for (i = j = 1; i < cmds->cnt; i++)
+ if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name))
+ cmds->names[j++] = cmds->names[i];
+
+ cmds->cnt = j;
+}
+
+static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
+{
+ int ci, cj, ei;
+ int cmp;
+
+ ci = cj = ei = 0;
+ while (ci < cmds->cnt && ei < excludes->cnt) {
+ cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
+ if (cmp < 0)
+ cmds->names[cj++] = cmds->names[ci++];
+ else if (cmp == 0)
+ ci++, ei++;
+ else if (cmp > 0)
+ ei++;
+ }
+
+ while (ci < cmds->cnt)
+ cmds->names[cj++] = cmds->names[ci++];
+
+ cmds->cnt = cj;
+}
+
+static void pretty_print_string_list(struct cmdnames *cmds, int longest)
{
int cols = 1, rows;
int space = longest + 1; /* min 1 SP between words */
@@ -73,9 +145,7 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest)
if (space < max_cols)
cols = max_cols / space;
- rows = (cmdname_cnt + cols - 1) / cols;
-
- qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
+ rows = (cmds->cnt + cols - 1) / cols;
for (i = 0; i < rows; i++) {
printf(" ");
@@ -83,71 +153,112 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest)
for (j = 0; j < cols; j++) {
int n = j * rows + i;
int size = space;
- if (n >= cmdname_cnt)
+ if (n >= cmds->cnt)
break;
- if (j == cols-1 || n + rows >= cmdname_cnt)
+ if (j == cols-1 || n + rows >= cmds->cnt)
size = 1;
- printf("%-*s", size, cmdname[n]->name);
+ printf("%-*s", size, cmds->names[n]->name);
}
putchar('\n');
}
}
-static void list_commands(const char *exec_path, const char *pattern)
+static unsigned int list_commands_in_dir(struct cmdnames *cmds,
+ const char *path)
{
unsigned int longest = 0;
- char path[PATH_MAX];
- int dirlen;
- DIR *dir = opendir(exec_path);
+ const char *prefix = "git-";
+ int prefix_len = strlen(prefix);
+ DIR *dir = opendir(path);
struct dirent *de;
- if (!dir) {
- fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
- exit(1);
- }
-
- dirlen = strlen(exec_path);
- if (PATH_MAX - 20 < dirlen) {
- fprintf(stderr, "git: insanely long exec-path '%s'\n",
- exec_path);
- exit(1);
- }
-
- memcpy(path, exec_path, dirlen);
- path[dirlen++] = '/';
+ if (!dir || chdir(path))
+ return 0;
while ((de = readdir(dir)) != NULL) {
struct stat st;
int entlen;
- if (prefixcmp(de->d_name, "git-"))
+ if (prefixcmp(de->d_name, prefix))
continue;
- strcpy(path+dirlen, de->d_name);
- if (stat(path, &st) || /* stat, not lstat */
+
+ if (stat(de->d_name, &st) || /* stat, not lstat */
!S_ISREG(st.st_mode) ||
!(st.st_mode & S_IXUSR))
continue;
- entlen = strlen(de->d_name);
+ entlen = strlen(de->d_name) - prefix_len;
if (has_extension(de->d_name, ".exe"))
entlen -= 4;
if (longest < entlen)
longest = entlen;
- add_cmdname(de->d_name + 4, entlen-4);
+ add_cmdname(cmds, de->d_name + prefix_len, entlen);
}
closedir(dir);
- printf("git commands available in '%s'\n", exec_path);
- printf("----------------------------");
- mput_char('-', strlen(exec_path));
- putchar('\n');
- pretty_print_string_list(cmdname, longest - 4);
- putchar('\n');
+ return longest;
}
-static void list_common_cmds_help(void)
+static void list_commands(void)
+{
+ unsigned int longest = 0;
+ unsigned int len;
+ const char *env_path = getenv("PATH");
+ char *paths, *path, *colon;
+ const char *exec_path = git_exec_path();
+
+ if (exec_path)
+ longest = list_commands_in_dir(&main_cmds, exec_path);
+
+ if (!env_path) {
+ fprintf(stderr, "PATH not set\n");
+ exit(1);
+ }
+
+ path = paths = xstrdup(env_path);
+ while (1) {
+ if ((colon = strchr(path, ':')))
+ *colon = 0;
+
+ len = list_commands_in_dir(&other_cmds, path);
+ if (len > longest)
+ longest = len;
+
+ if (!colon)
+ break;
+ path = colon + 1;
+ }
+ free(paths);
+
+ qsort(main_cmds.names, main_cmds.cnt,
+ sizeof(*main_cmds.names), cmdname_compare);
+ uniq(&main_cmds);
+
+ qsort(other_cmds.names, other_cmds.cnt,
+ sizeof(*other_cmds.names), cmdname_compare);
+ uniq(&other_cmds);
+ exclude_cmds(&other_cmds, &main_cmds);
+
+ if (main_cmds.cnt) {
+ printf("available git commands in '%s'\n", exec_path);
+ printf("----------------------------");
+ mput_char('-', strlen(exec_path));
+ putchar('\n');
+ pretty_print_string_list(&main_cmds, longest);
+ putchar('\n');
+ }
+
+ if (other_cmds.cnt) {
+ printf("git commands available from elsewhere on your $PATH\n");
+ printf("---------------------------------------------------\n");
+ pretty_print_string_list(&other_cmds, longest);
+ putchar('\n');
+ }
+}
+
+void list_common_cmds_help(void)
{
int i, longest = 0;
@@ -162,31 +273,84 @@ static void list_common_cmds_help(void)
mput_char(' ', longest - strlen(common_cmds[i].name));
puts(common_cmds[i].help);
}
- puts("(use 'git help -a' to get a list of all installed git commands)");
}
-static void show_man_page(const char *git_cmd)
+static const char *cmd_to_page(const char *git_cmd)
{
- const char *page;
-
- if (!prefixcmp(git_cmd, "git"))
- page = git_cmd;
+ if (!git_cmd)
+ return "git";
+ else if (!prefixcmp(git_cmd, "git"))
+ return git_cmd;
else {
int page_len = strlen(git_cmd) + 4;
char *p = xmalloc(page_len + 1);
strcpy(p, "git-");
strcpy(p + 4, git_cmd);
p[page_len] = 0;
- page = p;
+ return p;
}
+}
+
+static void setup_man_path(void)
+{
+ struct strbuf new_path;
+ const char *old_path = getenv("MANPATH");
+
+ strbuf_init(&new_path, 0);
+
+ /* We should always put ':' after our path. If there is no
+ * old_path, the ':' at the end will let 'man' to try
+ * system-wide paths after ours to find the manual page. If
+ * there is old_path, we need ':' as delimiter. */
+ strbuf_addstr(&new_path, GIT_MAN_PATH);
+ strbuf_addch(&new_path, ':');
+ if (old_path)
+ strbuf_addstr(&new_path, old_path);
+
+ setenv("MANPATH", new_path.buf, 1);
+ strbuf_release(&new_path);
+}
+
+static void show_man_page(const char *git_cmd)
+{
+ const char *page = cmd_to_page(git_cmd);
+ setup_man_path();
execlp("man", "man", page, NULL);
}
+static void show_info_page(const char *git_cmd)
+{
+ const char *page = cmd_to_page(git_cmd);
+ setenv("INFOPATH", GIT_INFO_PATH, 1);
+ execlp("info", "info", "gitman", page, NULL);
+}
+
+static void get_html_page_path(struct strbuf *page_path, const char *page)
+{
+ struct stat st;
+
+ /* Check that we have a git documentation directory. */
+ if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
+ die("'%s': not a documentation directory.", GIT_HTML_PATH);
+
+ strbuf_init(page_path, 0);
+ strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
+}
+
+static void show_html_page(const char *git_cmd)
+{
+ const char *page = cmd_to_page(git_cmd);
+ struct strbuf page_path; /* it leaks but we exec bellow */
+
+ get_html_page_path(&page_path, page);
+
+ execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
+}
+
void help_unknown_cmd(const char *cmd)
{
- printf("git: '%s' is not a git-command\n\n", cmd);
- list_common_cmds_help();
+ fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
exit(1);
}
@@ -198,24 +362,51 @@ int cmd_version(int argc, const char **argv, const char *prefix)
int cmd_help(int argc, const char **argv, const char *prefix)
{
- const char *help_cmd = argc > 1 ? argv[1] : NULL;
- const char *exec_path = git_exec_path();
+ const char *help_cmd = argv[1];
- if (!help_cmd) {
+ if (argc < 2) {
printf("usage: %s\n\n", git_usage_string);
list_common_cmds_help();
- exit(1);
+ exit(0);
}
- else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
+ if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
printf("usage: %s\n\n", git_usage_string);
- if(exec_path)
- list_commands(exec_path, "git-*");
- exit(1);
+ list_commands();
+ }
+
+ else if (!strcmp(help_cmd, "--web") || !strcmp(help_cmd, "-w")) {
+ show_html_page(argc > 2 ? argv[2] : NULL);
+ }
+
+ else if (!strcmp(help_cmd, "--info") || !strcmp(help_cmd, "-i")) {
+ show_info_page(argc > 2 ? argv[2] : NULL);
}
- else
- show_man_page(help_cmd);
+ else if (!strcmp(help_cmd, "--man") || !strcmp(help_cmd, "-m")) {
+ show_man_page(argc > 2 ? argv[2] : NULL);
+ }
+
+ else {
+ int nongit;
+
+ setup_git_directory_gently(&nongit);
+ git_config(git_help_config);
+ if (help_default_format)
+ parse_help_format(help_default_format);
+
+ switch (help_format) {
+ case man_format:
+ show_man_page(help_cmd);
+ break;
+ case info_format:
+ show_info_page(help_cmd);
+ break;
+ case web_format:
+ show_html_page(help_cmd);
+ break;
+ }
+ }
return 0;
}
diff --git a/http-push.c b/http-push.c
index 276e1eb1d9..0beb7406c3 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1,7 +1,6 @@
#include "cache.h"
#include "commit.h"
#include "pack.h"
-#include "fetch.h"
#include "tag.h"
#include "blob.h"
#include "http.h"
@@ -14,7 +13,7 @@
#include <expat.h>
static const char http_push_usage[] =
-"git-http-push [--all] [--force] [--verbose] <remote> [<head>...]\n";
+"git-http-push [--all] [--dry-run] [--force] [--verbose] <remote> [<head>...]\n";
#ifndef XML_STATUS_OK
enum XML_Status {
@@ -76,11 +75,11 @@ static int aborted;
static signed char remote_dir_exists[256];
static struct curl_slist *no_pragma_header;
-static struct curl_slist *default_headers;
static int push_verbosely;
-static int push_all;
+static int push_all = MATCH_REFS_NONE;
static int force_all;
+static int dry_run;
static struct object_list *objects;
@@ -433,7 +432,7 @@ static void start_fetch_packed(struct transfer_request *request)
packfile = fopen(request->tmpfile, "a");
if (!packfile) {
fprintf(stderr, "Unable to open local file %s for pack",
- filename);
+ request->tmpfile);
remote->can_update_info_refs = 0;
free(url);
return;
@@ -495,10 +494,11 @@ static void start_put(struct transfer_request *request)
memset(&stream, 0, sizeof(stream));
deflateInit(&stream, zlib_compression_level);
size = deflateBound(&stream, len + hdrlen);
- request->buffer.buffer = xmalloc(size);
+ strbuf_init(&request->buffer.buf, size);
+ request->buffer.posn = 0;
/* Compress it */
- stream.next_out = request->buffer.buffer;
+ stream.next_out = (unsigned char *)request->buffer.buf.buf;
stream.avail_out = size;
/* First header.. */
@@ -515,8 +515,7 @@ static void start_put(struct transfer_request *request)
deflateEnd(&stream);
free(unpacked);
- request->buffer.size = stream.total_out;
- request->buffer.posn = 0;
+ request->buffer.buf.len = stream.total_out;
request->url = xmalloc(strlen(remote->url) +
strlen(request->lock->token) + 51);
@@ -538,7 +537,7 @@ static void start_put(struct transfer_request *request)
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.size);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
@@ -795,38 +794,27 @@ static void finish_request(struct transfer_request *request)
}
#ifdef USE_CURL_MULTI
-void fill_active_slots(void)
+static int fill_active_slot(void *unused)
{
struct transfer_request *request = request_queue_head;
- struct transfer_request *next;
- struct active_request_slot *slot = active_queue_head;
- int num_transfers;
if (aborted)
- return;
+ return 0;
- while (active_requests < max_requests && request != NULL) {
- next = request->next;
+ for (request = request_queue_head; request; request = request->next) {
if (request->state == NEED_FETCH) {
start_fetch_loose(request);
+ return 1;
} else if (pushing && request->state == NEED_PUSH) {
if (remote_dir_exists[request->obj->sha1[0]] == 1) {
start_put(request);
} else {
start_mkcol(request);
}
- curl_multi_perform(curlm, &num_transfers);
- }
- request = next;
- }
-
- while (slot != NULL) {
- if (!slot->in_use && slot->curl != NULL) {
- curl_easy_cleanup(slot->curl);
- slot->curl = NULL;
+ return 1;
}
- slot = slot->next;
}
+ return 0;
}
#endif
@@ -936,11 +924,14 @@ static int fetch_index(unsigned char *sha1)
hex);
}
} else {
+ free(url);
return error("Unable to start request");
}
- if (has_pack_index(sha1))
+ if (has_pack_index(sha1)) {
+ free(url);
return 0;
+ }
if (push_verbosely)
fprintf(stderr, "Getting index for pack %s\n", hex);
@@ -950,9 +941,11 @@ static int fetch_index(unsigned char *sha1)
filename = sha1_pack_index_name(sha1);
snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
indexfile = fopen(tmpfile, "a");
- if (!indexfile)
+ if (!indexfile) {
+ free(url);
return error("Unable to open local file %s for pack index",
- filename);
+ tmpfile);
+ }
slot = get_active_slot();
slot->results = &results;
@@ -1014,18 +1007,13 @@ static int fetch_indices(void)
{
unsigned char sha1[20];
char *url;
- struct buffer buffer;
+ struct strbuf buffer = STRBUF_INIT;
char *data;
int i = 0;
struct active_request_slot *slot;
struct slot_results results;
- data = xcalloc(1, 4096);
- buffer.size = 4096;
- buffer.posn = 0;
- buffer.buffer = data;
-
if (push_verbosely)
fprintf(stderr, "Getting pack list\n");
@@ -1041,7 +1029,7 @@ static int fetch_indices(void)
if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result != CURLE_OK) {
- free(buffer.buffer);
+ strbuf_release(&buffer);
free(url);
if (results.http_code == 404)
return 0;
@@ -1049,18 +1037,18 @@ static int fetch_indices(void)
return error("%s", curl_errorstr);
}
} else {
- free(buffer.buffer);
+ strbuf_release(&buffer);
free(url);
return error("Unable to start request");
}
free(url);
- data = buffer.buffer;
- while (i < buffer.posn) {
+ data = buffer.buf;
+ while (i < buffer.len) {
switch (data[i]) {
case 'P':
i++;
- if (i + 52 < buffer.posn &&
+ if (i + 52 < buffer.len &&
!prefixcmp(data + i, " pack-") &&
!prefixcmp(data + i + 46, ".pack\n")) {
get_sha1_hex(data + i + 6, sha1);
@@ -1075,89 +1063,10 @@ static int fetch_indices(void)
i++;
}
- free(buffer.buffer);
+ strbuf_release(&buffer);
return 0;
}
-static inline int needs_quote(int ch)
-{
- if (((ch >= 'A') && (ch <= 'Z'))
- || ((ch >= 'a') && (ch <= 'z'))
- || ((ch >= '0') && (ch <= '9'))
- || (ch == '/')
- || (ch == '-')
- || (ch == '.'))
- return 0;
- return 1;
-}
-
-static inline int hex(int v)
-{
- if (v < 10) return '0' + v;
- else return 'A' + v - 10;
-}
-
-static char *quote_ref_url(const char *base, const char *ref)
-{
- const char *cp;
- char *dp, *qref;
- int len, baselen, ch;
-
- baselen = strlen(base);
- len = baselen + 1;
- for (cp = ref; (ch = *cp) != 0; cp++, len++)
- if (needs_quote(ch))
- len += 2; /* extra two hex plus replacement % */
- qref = xmalloc(len);
- memcpy(qref, base, baselen);
- for (cp = ref, dp = qref + baselen; (ch = *cp) != 0; cp++) {
- if (needs_quote(ch)) {
- *dp++ = '%';
- *dp++ = hex((ch >> 4) & 0xF);
- *dp++ = hex(ch & 0xF);
- }
- else
- *dp++ = ch;
- }
- *dp = 0;
-
- return qref;
-}
-
-int fetch_ref(char *ref, unsigned char *sha1)
-{
- char *url;
- char hex[42];
- struct buffer buffer;
- char *base = remote->url;
- struct active_request_slot *slot;
- struct slot_results results;
- buffer.size = 41;
- buffer.posn = 0;
- buffer.buffer = hex;
- hex[41] = '\0';
-
- url = quote_ref_url(base, ref);
- slot = get_active_slot();
- slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- if (start_active_slot(slot)) {
- run_active_slot(slot);
- if (results.curl_result != CURLE_OK)
- return error("Couldn't get %s for %s\n%s",
- url, ref, curl_errorstr);
- } else {
- return error("Unable to start request");
- }
-
- hex[40] = '\0';
- get_sha1_hex(hex, sha1);
- return 0;
-}
-
static void one_remote_object(const char *hex)
{
unsigned char sha1[20];
@@ -1278,16 +1187,12 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
{
struct active_request_slot *slot;
struct slot_results results;
- struct buffer out_buffer;
- struct buffer in_buffer;
- char *out_data;
- char *in_data;
+ struct buffer out_buffer = { STRBUF_INIT, 0 };
+ struct strbuf in_buffer = STRBUF_INIT;
char *url;
char *ep;
char timeout_header[25];
struct remote_lock *lock = NULL;
- XML_Parser parser = XML_ParserCreate(NULL);
- enum XML_Status result;
struct curl_slist *dav_headers = NULL;
struct xml_ctx ctx;
@@ -1323,16 +1228,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
ep = strchr(ep + 1, '/');
}
- out_buffer.size = strlen(LOCK_REQUEST) + strlen(git_default_email) - 2;
- out_data = xmalloc(out_buffer.size + 1);
- snprintf(out_data, out_buffer.size + 1, LOCK_REQUEST, git_default_email);
- out_buffer.posn = 0;
- out_buffer.buffer = out_data;
-
- in_buffer.size = 4096;
- in_data = xmalloc(in_buffer.size);
- in_buffer.posn = 0;
- in_buffer.buffer = in_data;
+ strbuf_addf(&out_buffer.buf, LOCK_REQUEST, git_default_email);
sprintf(timeout_header, "Timeout: Second-%ld", timeout);
dav_headers = curl_slist_append(dav_headers, timeout_header);
@@ -1341,7 +1237,7 @@ 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.size);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
@@ -1356,6 +1252,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result == CURLE_OK) {
+ XML_Parser parser = XML_ParserCreate(NULL);
+ enum XML_Status result;
ctx.name = xcalloc(10, 1);
ctx.len = 0;
ctx.cdata = NULL;
@@ -1365,8 +1263,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
XML_SetElementHandler(parser, xml_start_tag,
xml_end_tag);
XML_SetCharacterDataHandler(parser, xml_cdata);
- result = XML_Parse(parser, in_buffer.buffer,
- in_buffer.posn, 1);
+ result = XML_Parse(parser, in_buffer.buf,
+ in_buffer.len, 1);
free(ctx.name);
if (result != XML_STATUS_OK) {
fprintf(stderr, "XML error: %s\n",
@@ -1374,14 +1272,15 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
XML_GetErrorCode(parser)));
lock->timeout = -1;
}
+ XML_ParserFree(parser);
}
} else {
fprintf(stderr, "Unable to start LOCK request\n");
}
curl_slist_free_all(dav_headers);
- free(out_data);
- free(in_data);
+ strbuf_release(&out_buffer.buf);
+ strbuf_release(&in_buffer);
if (lock->token == NULL || lock->timeout <= 0) {
if (lock->token != NULL)
@@ -1532,12 +1431,8 @@ static void remote_ls(const char *path, int flags,
char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
struct active_request_slot *slot;
struct slot_results results;
- struct buffer in_buffer;
- struct buffer out_buffer;
- char *in_data;
- char *out_data;
- XML_Parser parser = XML_ParserCreate(NULL);
- enum XML_Status result;
+ struct strbuf in_buffer = STRBUF_INIT;
+ struct buffer out_buffer = { STRBUF_INIT, 0 };
struct curl_slist *dav_headers = NULL;
struct xml_ctx ctx;
struct remote_ls_ctx ls;
@@ -1551,16 +1446,7 @@ static void remote_ls(const char *path, int flags,
sprintf(url, "%s%s", remote->url, path);
- out_buffer.size = strlen(PROPFIND_ALL_REQUEST);
- out_data = xmalloc(out_buffer.size + 1);
- snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);
- out_buffer.posn = 0;
- out_buffer.buffer = out_data;
-
- in_buffer.size = 4096;
- in_data = xmalloc(in_buffer.size);
- in_buffer.posn = 0;
- in_buffer.buffer = in_data;
+ strbuf_addf(&out_buffer.buf, PROPFIND_ALL_REQUEST);
dav_headers = curl_slist_append(dav_headers, "Depth: 1");
dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
@@ -1568,7 +1454,7 @@ 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.size);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
@@ -1580,6 +1466,8 @@ static void remote_ls(const char *path, int flags,
if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result == CURLE_OK) {
+ XML_Parser parser = XML_ParserCreate(NULL);
+ enum XML_Status result;
ctx.name = xcalloc(10, 1);
ctx.len = 0;
ctx.cdata = NULL;
@@ -1589,8 +1477,8 @@ static void remote_ls(const char *path, int flags,
XML_SetElementHandler(parser, xml_start_tag,
xml_end_tag);
XML_SetCharacterDataHandler(parser, xml_cdata);
- result = XML_Parse(parser, in_buffer.buffer,
- in_buffer.posn, 1);
+ result = XML_Parse(parser, in_buffer.buf,
+ in_buffer.len, 1);
free(ctx.name);
if (result != XML_STATUS_OK) {
@@ -1598,6 +1486,7 @@ static void remote_ls(const char *path, int flags,
XML_ErrorString(
XML_GetErrorCode(parser)));
}
+ XML_ParserFree(parser);
}
} else {
fprintf(stderr, "Unable to start PROPFIND request\n");
@@ -1605,8 +1494,8 @@ static void remote_ls(const char *path, int flags,
free(ls.path);
free(url);
- free(out_data);
- free(in_buffer.buffer);
+ strbuf_release(&out_buffer.buf);
+ strbuf_release(&in_buffer);
curl_slist_free_all(dav_headers);
}
@@ -1627,29 +1516,13 @@ static int locking_available(void)
{
struct active_request_slot *slot;
struct slot_results results;
- struct buffer in_buffer;
- struct buffer out_buffer;
- char *in_data;
- char *out_data;
- XML_Parser parser = XML_ParserCreate(NULL);
- enum XML_Status result;
+ struct strbuf in_buffer = STRBUF_INIT;
+ struct buffer out_buffer = { STRBUF_INIT, 0 };
struct curl_slist *dav_headers = NULL;
struct xml_ctx ctx;
int lock_flags = 0;
- out_buffer.size =
- strlen(PROPFIND_SUPPORTEDLOCK_REQUEST) +
- strlen(remote->url) - 2;
- out_data = xmalloc(out_buffer.size + 1);
- snprintf(out_data, out_buffer.size + 1,
- PROPFIND_SUPPORTEDLOCK_REQUEST, remote->url);
- out_buffer.posn = 0;
- out_buffer.buffer = out_data;
-
- in_buffer.size = 4096;
- in_data = xmalloc(in_buffer.size);
- in_buffer.posn = 0;
- in_buffer.buffer = in_data;
+ strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, remote->url);
dav_headers = curl_slist_append(dav_headers, "Depth: 0");
dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
@@ -1657,7 +1530,7 @@ 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.size);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
@@ -1669,6 +1542,8 @@ static int locking_available(void)
if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result == CURLE_OK) {
+ XML_Parser parser = XML_ParserCreate(NULL);
+ enum XML_Status result;
ctx.name = xcalloc(10, 1);
ctx.len = 0;
ctx.cdata = NULL;
@@ -1677,8 +1552,8 @@ static int locking_available(void)
XML_SetUserData(parser, &ctx);
XML_SetElementHandler(parser, xml_start_tag,
xml_end_tag);
- result = XML_Parse(parser, in_buffer.buffer,
- in_buffer.posn, 1);
+ result = XML_Parse(parser, in_buffer.buf,
+ in_buffer.len, 1);
free(ctx.name);
if (result != XML_STATUS_OK) {
@@ -1687,13 +1562,22 @@ static int locking_available(void)
XML_GetErrorCode(parser)));
lock_flags = 0;
}
+ XML_ParserFree(parser);
+ if (!lock_flags)
+ error("Error: no DAV locking support on %s",
+ remote->url);
+
+ } else {
+ error("Cannot access URL %s, return code %d",
+ remote->url, results.curl_result);
+ lock_flags = 0;
}
} else {
- fprintf(stderr, "Unable to start PROPFIND request\n");
+ error("Unable to start PROPFIND request on %s", remote->url);
}
- free(out_data);
- free(in_buffer.buffer);
+ strbuf_release(&out_buffer.buf);
+ strbuf_release(&in_buffer);
curl_slist_free_all(dav_headers);
return lock_flags;
@@ -1750,12 +1634,19 @@ static struct object_list **process_tree(struct tree *tree,
init_tree_desc(&desc, tree->buffer, tree->size);
- while (tree_entry(&desc, &entry)) {
- if (S_ISDIR(entry.mode))
+ while (tree_entry(&desc, &entry))
+ switch (object_type(entry.mode)) {
+ case OBJ_TREE:
p = process_tree(lookup_tree(entry.sha1), p, &me, name);
- else
+ break;
+ case OBJ_BLOB:
p = process_blob(lookup_blob(entry.sha1), p, &me, name);
- }
+ break;
+ default:
+ /* Subproject commit - not in this repository */
+ break;
+ }
+
free(tree->buffer);
tree->buffer = NULL;
return p;
@@ -1811,30 +1702,20 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
{
struct active_request_slot *slot;
struct slot_results results;
- char *out_data;
char *if_header;
- struct buffer out_buffer;
+ struct buffer out_buffer = { STRBUF_INIT, 0 };
struct curl_slist *dav_headers = NULL;
- int i;
if_header = xmalloc(strlen(lock->token) + 25);
sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
dav_headers = curl_slist_append(dav_headers, if_header);
- out_buffer.size = 41;
- out_data = xmalloc(out_buffer.size + 1);
- i = snprintf(out_data, out_buffer.size + 1, "%s\n", sha1_to_hex(sha1));
- if (i != out_buffer.size) {
- fprintf(stderr, "Unable to initialize PUT request body\n");
- return 0;
- }
- out_buffer.posn = 0;
- out_buffer.buffer = out_data;
+ strbuf_addf(&out_buffer.buf, "%s\n", sha1_to_hex(sha1));
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.size);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
@@ -1845,7 +1726,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
if (start_active_slot(slot)) {
run_active_slot(slot);
- free(out_data);
+ strbuf_release(&out_buffer.buf);
free(if_header);
if (results.curl_result != CURLE_OK) {
fprintf(stderr,
@@ -1855,7 +1736,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
return 0;
}
} else {
- free(out_data);
+ strbuf_release(&out_buffer.buf);
free(if_header);
fprintf(stderr, "Unable to start PUT request\n");
return 0;
@@ -1886,7 +1767,8 @@ static void one_remote_ref(char *refname)
struct object *obj;
int len = strlen(refname) + 1;
- if (fetch_ref(refname, remote_sha1) != 0) {
+ if (http_fetch_ref(remote->url, refname + 5 /* "refs/" */,
+ remote_sha1) != 0) {
fprintf(stderr,
"Unable to fetch ref %s from %s\n",
refname, remote->url);
@@ -2012,13 +1894,14 @@ static void mark_edges_uninteresting(struct commit_list *list)
static void add_remote_info_ref(struct remote_ls_ctx *ls)
{
- struct buffer *buf = (struct buffer *)ls->userData;
+ struct strbuf *buf = (struct strbuf *)ls->userData;
unsigned char remote_sha1[20];
struct object *o;
int len;
char *ref_info;
- if (fetch_ref(ls->dentry_name, remote_sha1) != 0) {
+ if (http_fetch_ref(remote->url, ls->dentry_name + 5 /* "refs/" */,
+ remote_sha1) != 0) {
fprintf(stderr,
"Unable to fetch ref %s from %s\n",
ls->dentry_name, remote->url);
@@ -2057,17 +1940,14 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls)
static void update_remote_info_refs(struct remote_lock *lock)
{
- struct buffer buffer;
+ struct buffer buffer = { STRBUF_INIT, 0 };
struct active_request_slot *slot;
struct slot_results results;
char *if_header;
struct curl_slist *dav_headers = NULL;
- buffer.buffer = xcalloc(1, 4096);
- buffer.size = 4096;
- buffer.posn = 0;
remote_ls("refs/", (PROCESS_FILES | RECURSIVE),
- add_remote_info_ref, &buffer);
+ add_remote_info_ref, &buffer.buf);
if (!aborted) {
if_header = xmalloc(strlen(lock->token) + 25);
sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
@@ -2076,7 +1956,7 @@ 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.posn);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
@@ -2085,8 +1965,6 @@ static void update_remote_info_refs(struct remote_lock *lock)
curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
- buffer.posn = 0;
-
if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result != CURLE_OK) {
@@ -2097,7 +1975,7 @@ static void update_remote_info_refs(struct remote_lock *lock)
}
free(if_header);
}
- free(buffer.buffer);
+ strbuf_release(&buffer.buf);
}
static int remote_exists(const char *path)
@@ -2105,43 +1983,41 @@ static int remote_exists(const char *path)
char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
struct active_request_slot *slot;
struct slot_results results;
+ int ret = -1;
sprintf(url, "%s%s", remote->url, path);
- slot = get_active_slot();
+ slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
- if (start_active_slot(slot)) {
+ if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.http_code == 404)
- return 0;
+ ret = 0;
else if (results.curl_result == CURLE_OK)
- return 1;
+ ret = 1;
else
fprintf(stderr, "HEAD HTTP error %ld\n", results.http_code);
} else {
fprintf(stderr, "Unable to start HEAD request\n");
}
- return -1;
+ free(url);
+ return ret;
}
static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
{
char *url;
- struct buffer buffer;
+ struct strbuf buffer = STRBUF_INIT;
struct active_request_slot *slot;
struct slot_results results;
url = xmalloc(strlen(remote->url) + strlen(path) + 1);
sprintf(url, "%s%s", remote->url, path);
- buffer.size = 4096;
- buffer.posn = 0;
- buffer.buffer = xmalloc(buffer.size);
-
slot = get_active_slot();
slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
@@ -2164,17 +2040,17 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
*symref = NULL;
hashclr(sha1);
- if (buffer.posn == 0)
+ if (buffer.len == 0)
return;
/* If it's a symref, set the refname; otherwise try for a sha1 */
- if (!prefixcmp((char *)buffer.buffer, "ref: ")) {
- *symref = xmemdupz((char *)buffer.buffer + 5, buffer.posn - 6);
+ if (!prefixcmp((char *)buffer.buf, "ref: ")) {
+ *symref = xmemdupz((char *)buffer.buf + 5, buffer.len - 6);
} else {
- get_sha1_hex(buffer.buffer, sha1);
+ get_sha1_hex(buffer.buf, sha1);
}
- free(buffer.buffer);
+ strbuf_release(&buffer);
}
static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
@@ -2252,7 +2128,11 @@ static int delete_remote_branch(char *pattern, int force)
/* Remote branch must be an ancestor of remote HEAD */
if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
- return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern);
+ return error("The branch '%s' is not an ancestor "
+ "of your current HEAD.\n"
+ "If you are sure you want to delete it,"
+ " run:\n\t'git http-push -D %s %s'",
+ remote_ref->name, remote->url, pattern);
}
}
@@ -2296,6 +2176,7 @@ int main(int argc, char **argv)
int i;
int new_refs;
struct ref *ref;
+ char *rewritten_url = NULL;
setup_git_directory();
@@ -2307,13 +2188,17 @@ int main(int argc, char **argv)
if (*arg == '-') {
if (!strcmp(arg, "--all")) {
- push_all = 1;
+ push_all = MATCH_REFS_ALL;
continue;
}
if (!strcmp(arg, "--force")) {
force_all = 1;
continue;
}
+ if (!strcmp(arg, "--dry-run")) {
+ dry_run = 1;
+ continue;
+ }
if (!strcmp(arg, "--verbose")) {
push_verbosely = 1;
continue;
@@ -2343,6 +2228,10 @@ int main(int argc, char **argv)
break;
}
+#ifndef USE_CURL_MULTI
+ die("git-push is not available for http/https repository when not compiled with USE_CURL_MULTI");
+#endif
+
if (!remote->url)
usage(http_push_usage);
@@ -2354,15 +2243,17 @@ int main(int argc, char **argv)
http_init();
no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
- default_headers = curl_slist_append(default_headers, "Range:");
- default_headers = curl_slist_append(default_headers, "Destination:");
- default_headers = curl_slist_append(default_headers, "If:");
- default_headers = curl_slist_append(default_headers,
- "Pragma: no-cache");
+
+ if (remote->url && remote->url[strlen(remote->url)-1] != '/') {
+ rewritten_url = malloc(strlen(remote->url)+2);
+ strcpy(rewritten_url, remote->url);
+ strcat(rewritten_url, "/");
+ remote->url = rewritten_url;
+ ++remote->path_len;
+ }
/* Verify DAV compliance/lock support */
if (!locking_available()) {
- fprintf(stderr, "Error: no DAV locking support on remote repo %s\n", remote->url);
rc = 1;
goto cleanup;
}
@@ -2375,6 +2266,11 @@ int main(int argc, char **argv)
info_ref_lock = lock_remote("info/refs", LOCK_TIME);
if (info_ref_lock)
remote->can_update_info_refs = 1;
+ else {
+ fprintf(stderr, "Error: cannot lock existing info/refs\n");
+ rc = 1;
+ goto cleanup;
+ }
}
if (remote->has_info_packs)
fetch_indices();
@@ -2396,11 +2292,14 @@ int main(int argc, char **argv)
if (!remote_tail)
remote_tail = &remote_refs;
if (match_refs(local_refs, remote_refs, &remote_tail,
- nr_refspec, refspec, push_all))
- return -1;
+ nr_refspec, (const char **) refspec, push_all)) {
+ rc = -1;
+ goto cleanup;
+ }
if (!remote_refs) {
fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
- return 0;
+ rc = 0;
+ goto cleanup;
}
new_refs = 0;
@@ -2424,16 +2323,17 @@ int main(int argc, char **argv)
if (!has_sha1_file(ref->old_sha1) ||
!ref_newer(ref->peer_ref->new_sha1,
ref->old_sha1)) {
- /* We do not have the remote ref, or
+ /*
+ * We do not have the remote ref, or
* we know that the remote ref is not
* an ancestor of what we are trying to
* push. Either way this can be losing
* commits at the remote end and likely
* we were not up to date to begin with.
*/
- error("remote '%s' is not a strict "
- "subset of local ref '%s'. "
- "maybe you are not up-to-date and "
+ error("remote '%s' is not an ancestor of\n"
+ "local '%s'.\n"
+ "Maybe you are not up-to-date and "
"need to pull first?",
ref->name,
ref->peer_ref->name);
@@ -2455,7 +2355,8 @@ int main(int argc, char **argv)
if (strcmp(ref->name, ref->peer_ref->name))
fprintf(stderr, " using '%s'", ref->peer_ref->name);
fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex);
-
+ if (dry_run)
+ continue;
/* Lock remote branch ref */
ref_lock = lock_remote(ref->name, LOCK_TIME);
@@ -2489,7 +2390,8 @@ int main(int argc, char **argv)
/* Generate a list of objects that need to be pushed */
pushing = 0;
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
mark_edges_uninteresting(revs.commits);
objects_to_send = get_delta(&revs, ref_lock);
finish_all_active_slots();
@@ -2502,16 +2404,19 @@ int main(int argc, char **argv)
objects_to_send);
#ifdef USE_CURL_MULTI
fill_active_slots();
+ add_fill_function(NULL, fill_active_slot);
#endif
- finish_all_active_slots();
+ do {
+ finish_all_active_slots();
+#ifdef USE_CURL_MULTI
+ fill_active_slots();
+#endif
+ } while (request_queue_head && !aborted);
/* Update the remote branch if all went well */
- if (aborted || !update_remote(ref->new_sha1, ref_lock)) {
+ if (aborted || !update_remote(ref->new_sha1, ref_lock))
rc = 1;
- goto unlock;
- }
- unlock:
if (!rc)
fprintf(stderr, " done\n");
unlock_remote(ref_lock);
@@ -2522,19 +2427,21 @@ int main(int argc, char **argv)
if (remote->has_info_refs && new_refs) {
if (info_ref_lock && remote->can_update_info_refs) {
fprintf(stderr, "Updating remote server info\n");
- update_remote_info_refs(info_ref_lock);
+ if (!dry_run)
+ update_remote_info_refs(info_ref_lock);
} else {
fprintf(stderr, "Unable to update server info\n");
}
}
- if (info_ref_lock)
- unlock_remote(info_ref_lock);
cleanup:
+ if (rewritten_url)
+ free(rewritten_url);
+ if (info_ref_lock)
+ unlock_remote(info_ref_lock);
free(remote);
curl_slist_free_all(no_pragma_header);
- curl_slist_free_all(default_headers);
http_cleanup();
diff --git a/http-fetch.c b/http-walker.c
index 202fae0ba8..2c3786870e 100644
--- a/http-fetch.c
+++ b/http-walker.c
@@ -1,19 +1,12 @@
#include "cache.h"
#include "commit.h"
#include "pack.h"
-#include "fetch.h"
+#include "walker.h"
#include "http.h"
#define PREV_BUF_SIZE 4096
#define RANGE_HEADER_SIZE 30
-static int commits_on_stdin;
-
-static int got_alternates = -1;
-static int corrupt_object_found;
-
-static struct curl_slist *no_pragma_header;
-
struct alt_base
{
char *base;
@@ -22,8 +15,6 @@ struct alt_base
struct alt_base *next;
};
-static struct alt_base *alt;
-
enum object_request_state {
WAITING,
ABORTED,
@@ -33,6 +24,7 @@ enum object_request_state {
struct object_request
{
+ struct walker *walker;
unsigned char sha1[20];
struct alt_base *repo;
char *url;
@@ -53,13 +45,21 @@ struct object_request
};
struct alternates_request {
+ struct walker *walker;
const char *base;
char *url;
- struct buffer *buffer;
+ struct strbuf *buffer;
struct active_request_slot *slot;
int http_specific;
};
+struct walker_data {
+ const char *url;
+ int got_alternates;
+ struct alt_base *alt;
+ struct curl_slist *no_pragma_header;
+};
+
static struct object_request *object_queue_head;
static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
@@ -90,24 +90,12 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
return size;
}
-static int missing__target(int code, int result)
-{
- return /* file:// URL -- do we ever use one??? */
- (result == CURLE_FILE_COULDNT_READ_FILE) ||
- /* http:// and https:// URL */
- (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||
- /* ftp:// URL */
- (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)
- ;
-}
-
-#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
-
-static void fetch_alternates(const char *base);
+static void fetch_alternates(struct walker *walker, const char *base);
static void process_object_response(void *callback_data);
-static void start_object_request(struct object_request *obj_req)
+static void start_object_request(struct walker *walker,
+ struct object_request *obj_req)
{
char *hex = sha1_to_hex(obj_req->sha1);
char prevfile[PATH_MAX];
@@ -120,6 +108,7 @@ static void start_object_request(struct object_request *obj_req)
char range[RANGE_HEADER_SIZE];
struct curl_slist *range_header = NULL;
struct active_request_slot *slot;
+ struct walker_data *data = walker->data;
snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename);
unlink(prevfile);
@@ -212,12 +201,12 @@ static void start_object_request(struct object_request *obj_req)
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
/* If we have successfully processed data from a previous fetch
attempt, only fetch the data we don't already have. */
if (prev_posn>0) {
- if (get_verbosely)
+ if (walker->get_verbosely)
fprintf(stderr,
"Resuming fetch of object %s at byte %ld\n",
hex, prev_posn);
@@ -268,13 +257,16 @@ static void finish_object_request(struct object_request *obj_req)
move_temp_to_file(obj_req->tmpfile, obj_req->filename);
if (obj_req->rename == 0)
- pull_say("got %s\n", sha1_to_hex(obj_req->sha1));
+ walker_say(obj_req->walker, "got %s\n", sha1_to_hex(obj_req->sha1));
}
static void process_object_response(void *callback_data)
{
struct object_request *obj_req =
(struct object_request *)callback_data;
+ struct walker *walker = obj_req->walker;
+ struct walker_data *data = walker->data;
+ struct alt_base *alt = data->alt;
obj_req->curl_result = obj_req->slot->curl_result;
obj_req->http_code = obj_req->slot->http_code;
@@ -283,13 +275,13 @@ static void process_object_response(void *callback_data)
/* Use alternates if necessary */
if (missing_target(obj_req)) {
- fetch_alternates(alt->base);
+ fetch_alternates(walker, alt->base);
if (obj_req->repo->next != NULL) {
obj_req->repo =
obj_req->repo->next;
close(obj_req->local);
obj_req->local = -1;
- start_object_request(obj_req);
+ start_object_request(walker, obj_req);
return;
}
}
@@ -317,42 +309,35 @@ static void release_object_request(struct object_request *obj_req)
}
#ifdef USE_CURL_MULTI
-void fill_active_slots(void)
+static int fill_active_slot(struct walker *walker)
{
- struct object_request *obj_req = object_queue_head;
- struct active_request_slot *slot = active_queue_head;
- int num_transfers;
+ struct object_request *obj_req;
- while (active_requests < max_requests && obj_req != NULL) {
+ for (obj_req = object_queue_head; obj_req; obj_req = obj_req->next) {
if (obj_req->state == WAITING) {
if (has_sha1_file(obj_req->sha1))
obj_req->state = COMPLETE;
- else
- start_object_request(obj_req);
- curl_multi_perform(curlm, &num_transfers);
- }
- obj_req = obj_req->next;
- }
-
- while (slot != NULL) {
- if (!slot->in_use && slot->curl != NULL) {
- curl_easy_cleanup(slot->curl);
- slot->curl = NULL;
+ else {
+ start_object_request(walker, obj_req);
+ return 1;
+ }
}
- slot = slot->next;
}
+ return 0;
}
#endif
-void prefetch(unsigned char *sha1)
+static void prefetch(struct walker *walker, unsigned char *sha1)
{
struct object_request *newreq;
struct object_request *tail;
+ struct walker_data *data = walker->data;
char *filename = sha1_file_name(sha1);
newreq = xmalloc(sizeof(*newreq));
+ newreq->walker = walker;
hashcpy(newreq->sha1, sha1);
- newreq->repo = alt;
+ newreq->repo = data->alt;
newreq->url = NULL;
newreq->local = -1;
newreq->state = WAITING;
@@ -378,7 +363,7 @@ void prefetch(unsigned char *sha1)
#endif
}
-static int fetch_index(struct alt_base *repo, unsigned char *sha1)
+static int fetch_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
{
char *hex = sha1_to_hex(sha1);
char *filename;
@@ -387,6 +372,7 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
long prev_posn = 0;
char range[RANGE_HEADER_SIZE];
struct curl_slist *range_header = NULL;
+ struct walker_data *data = walker->data;
FILE *indexfile;
struct active_request_slot *slot;
@@ -395,7 +381,7 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
if (has_pack_index(sha1))
return 0;
- if (get_verbosely)
+ if (walker->get_verbosely)
fprintf(stderr, "Getting index for pack %s\n", hex);
url = xmalloc(strlen(repo->base) + 64);
@@ -406,21 +392,21 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
indexfile = fopen(tmpfile, "a");
if (!indexfile)
return error("Unable to open local file %s for pack index",
- filename);
+ tmpfile);
slot = get_active_slot();
slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
slot->local = indexfile;
/* If there is data present from a previous transfer attempt,
resume where it left off */
prev_posn = ftell(indexfile);
if (prev_posn>0) {
- if (get_verbosely)
+ if (walker->get_verbosely)
fprintf(stderr,
"Resuming fetch of index for pack %s at byte %ld\n",
hex, prev_posn);
@@ -446,13 +432,13 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
return move_temp_to_file(tmpfile, filename);
}
-static int setup_index(struct alt_base *repo, unsigned char *sha1)
+static int setup_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
{
struct packed_git *new_pack;
if (has_pack_file(sha1))
return 0; /* don't list this as something we can get */
- if (fetch_index(repo, sha1))
+ if (fetch_index(walker, repo, sha1))
return -1;
new_pack = parse_pack_index(sha1);
@@ -465,8 +451,10 @@ static void process_alternates_response(void *callback_data)
{
struct alternates_request *alt_req =
(struct alternates_request *)callback_data;
+ struct walker *walker = alt_req->walker;
+ struct walker_data *cdata = walker->data;
struct active_request_slot *slot = alt_req->slot;
- struct alt_base *tail = alt;
+ struct alt_base *tail = cdata->alt;
const char *base = alt_req->base;
static const char null_byte = '\0';
char *data;
@@ -474,7 +462,7 @@ static void process_alternates_response(void *callback_data)
if (alt_req->http_specific) {
if (slot->curl_result != CURLE_OK ||
- !alt_req->buffer->posn) {
+ !alt_req->buffer->len) {
/* Try reusing the slot to get non-http alternates */
alt_req->http_specific = 0;
@@ -487,7 +475,7 @@ static void process_alternates_response(void *callback_data)
if (slot->finished != NULL)
(*slot->finished) = 0;
if (!start_active_slot(slot)) {
- got_alternates = -1;
+ cdata->got_alternates = -1;
slot->in_use = 0;
if (slot->finished != NULL)
(*slot->finished) = 1;
@@ -496,18 +484,18 @@ static void process_alternates_response(void *callback_data)
}
} else if (slot->curl_result != CURLE_OK) {
if (!missing_target(slot)) {
- got_alternates = -1;
+ cdata->got_alternates = -1;
return;
}
}
fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
- alt_req->buffer->posn--;
- data = alt_req->buffer->buffer;
+ alt_req->buffer->len--;
+ data = alt_req->buffer->buf;
- while (i < alt_req->buffer->posn) {
+ while (i < alt_req->buffer->len) {
int posn = i;
- while (posn < alt_req->buffer->posn && data[posn] != '\n')
+ while (posn < alt_req->buffer->len && data[posn] != '\n')
posn++;
if (data[posn] == '\n') {
int okay = 0;
@@ -573,7 +561,7 @@ static void process_alternates_response(void *callback_data)
memcpy(target + serverlen, data + i,
posn - i - 7);
target[serverlen + posn - i - 7] = 0;
- if (get_verbosely)
+ if (walker->get_verbosely)
fprintf(stderr,
"Also look at %s\n", target);
newalt = xmalloc(sizeof(*newalt));
@@ -590,39 +578,34 @@ static void process_alternates_response(void *callback_data)
i = posn + 1;
}
- got_alternates = 1;
+ cdata->got_alternates = 1;
}
-static void fetch_alternates(const char *base)
+static void fetch_alternates(struct walker *walker, const char *base)
{
- struct buffer buffer;
+ struct strbuf buffer = STRBUF_INIT;
char *url;
- char *data;
struct active_request_slot *slot;
struct alternates_request alt_req;
+ struct walker_data *cdata = walker->data;
/* If another request has already started fetching alternates,
wait for them to arrive and return to processing this request's
curl message */
#ifdef USE_CURL_MULTI
- while (got_alternates == 0) {
+ while (cdata->got_alternates == 0) {
step_active_slots();
}
#endif
/* Nothing to do if they've already been fetched */
- if (got_alternates == 1)
+ if (cdata->got_alternates == 1)
return;
/* Start the fetch */
- got_alternates = 0;
-
- data = xmalloc(4096);
- buffer.size = 4096;
- buffer.posn = 0;
- buffer.buffer = data;
+ cdata->got_alternates = 0;
- if (get_verbosely)
+ if (walker->get_verbosely)
fprintf(stderr, "Getting alternates list for %s\n", base);
url = xmalloc(strlen(base) + 31);
@@ -632,6 +615,7 @@ static void fetch_alternates(const char *base)
may fail and need to have alternates loaded before continuing */
slot = get_active_slot();
slot->callback_func = process_alternates_response;
+ alt_req.walker = walker;
slot->callback_data = &alt_req;
curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
@@ -647,19 +631,20 @@ static void fetch_alternates(const char *base)
if (start_active_slot(slot))
run_active_slot(slot);
else
- got_alternates = -1;
+ cdata->got_alternates = -1;
- free(data);
+ strbuf_release(&buffer);
free(url);
}
-static int fetch_indices(struct alt_base *repo)
+static int fetch_indices(struct walker *walker, struct alt_base *repo)
{
unsigned char sha1[20];
char *url;
- struct buffer buffer;
+ struct strbuf buffer = STRBUF_INIT;
char *data;
int i = 0;
+ int ret = 0;
struct active_request_slot *slot;
struct slot_results results;
@@ -667,12 +652,7 @@ static int fetch_indices(struct alt_base *repo)
if (repo->got_indices)
return 0;
- data = xmalloc(4096);
- buffer.size = 4096;
- buffer.posn = 0;
- buffer.buffer = data;
-
- if (get_verbosely)
+ if (walker->get_verbosely)
fprintf(stderr, "Getting pack list for %s\n", repo->base);
url = xmalloc(strlen(repo->base) + 21);
@@ -689,46 +669,47 @@ static int fetch_indices(struct alt_base *repo)
if (results.curl_result != CURLE_OK) {
if (missing_target(&results)) {
repo->got_indices = 1;
- free(buffer.buffer);
- return 0;
+ goto cleanup;
} else {
repo->got_indices = 0;
- free(buffer.buffer);
- return error("%s", curl_errorstr);
+ ret = error("%s", curl_errorstr);
+ goto cleanup;
}
}
} else {
repo->got_indices = 0;
- free(buffer.buffer);
- return error("Unable to start request");
+ ret = error("Unable to start request");
+ goto cleanup;
}
- data = buffer.buffer;
- while (i < buffer.posn) {
+ data = buffer.buf;
+ while (i < buffer.len) {
switch (data[i]) {
case 'P':
i++;
- if (i + 52 <= buffer.posn &&
+ if (i + 52 <= buffer.len &&
!prefixcmp(data + i, " pack-") &&
!prefixcmp(data + i + 46, ".pack\n")) {
get_sha1_hex(data + i + 6, sha1);
- setup_index(repo, sha1);
+ setup_index(walker, repo, sha1);
i += 51;
break;
}
default:
- while (i < buffer.posn && data[i] != '\n')
+ while (i < buffer.len && data[i] != '\n')
i++;
}
i++;
}
- free(buffer.buffer);
repo->got_indices = 1;
- return 0;
+cleanup:
+ strbuf_release(&buffer);
+ free(url);
+ return ret;
}
-static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
+static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
{
char *url;
struct packed_git *target;
@@ -740,17 +721,18 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
long prev_posn = 0;
char range[RANGE_HEADER_SIZE];
struct curl_slist *range_header = NULL;
+ struct walker_data *data = walker->data;
struct active_request_slot *slot;
struct slot_results results;
- if (fetch_indices(repo))
+ if (fetch_indices(walker, repo))
return -1;
target = find_sha1_pack(sha1, repo->packs);
if (!target)
return -1;
- if (get_verbosely) {
+ if (walker->get_verbosely) {
fprintf(stderr, "Getting pack %s\n",
sha1_to_hex(target->sha1));
fprintf(stderr, " which contains %s\n",
@@ -766,21 +748,21 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
packfile = fopen(tmpfile, "a");
if (!packfile)
return error("Unable to open local file %s for pack",
- filename);
+ tmpfile);
slot = get_active_slot();
slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
slot->local = packfile;
/* If there is data present from a previous transfer attempt,
resume where it left off */
prev_posn = ftell(packfile);
if (prev_posn>0) {
- if (get_verbosely)
+ if (walker->get_verbosely)
fprintf(stderr,
"Resuming fetch of pack %s at byte %ld\n",
sha1_to_hex(target->sha1), prev_posn);
@@ -834,7 +816,7 @@ static void abort_object_request(struct object_request *obj_req)
release_object_request(obj_req);
}
-static int fetch_object(struct alt_base *repo, unsigned char *sha1)
+static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
{
char *hex = sha1_to_hex(sha1);
int ret = 0;
@@ -855,7 +837,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
step_active_slots();
}
#else
- start_object_request(obj_req);
+ start_object_request(walker, obj_req);
#endif
while (obj_req->state == ACTIVE) {
@@ -876,7 +858,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
obj_req->errorstr, obj_req->curl_result,
obj_req->http_code, hex);
} else if (obj_req->zret != Z_STREAM_END) {
- corrupt_object_found++;
+ walker->corrupt_object_found++;
ret = error("File %s (%s) corrupt", hex, obj_req->url);
} else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
ret = error("File %s has bad hash", hex);
@@ -889,176 +871,68 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
return ret;
}
-int fetch(unsigned char *sha1)
+static int fetch(struct walker *walker, unsigned char *sha1)
{
- struct alt_base *altbase = alt;
+ struct walker_data *data = walker->data;
+ struct alt_base *altbase = data->alt;
- if (!fetch_object(altbase, sha1))
+ if (!fetch_object(walker, altbase, sha1))
return 0;
while (altbase) {
- if (!fetch_pack(altbase, sha1))
+ if (!fetch_pack(walker, altbase, sha1))
return 0;
- fetch_alternates(alt->base);
+ fetch_alternates(walker, data->alt->base);
altbase = altbase->next;
}
return error("Unable to find %s under %s", sha1_to_hex(sha1),
- alt->base);
-}
-
-static inline int needs_quote(int ch)
-{
- if (((ch >= 'A') && (ch <= 'Z'))
- || ((ch >= 'a') && (ch <= 'z'))
- || ((ch >= '0') && (ch <= '9'))
- || (ch == '/')
- || (ch == '-')
- || (ch == '.'))
- return 0;
- return 1;
+ data->alt->base);
}
-static inline int hex(int v)
+static int fetch_ref(struct walker *walker, char *ref, unsigned char *sha1)
{
- if (v < 10) return '0' + v;
- else return 'A' + v - 10;
+ struct walker_data *data = walker->data;
+ return http_fetch_ref(data->alt->base, ref, sha1);
}
-static char *quote_ref_url(const char *base, const char *ref)
+static void cleanup(struct walker *walker)
{
- const char *cp;
- char *dp, *qref;
- int len, baselen, ch;
-
- baselen = strlen(base);
- len = baselen + 7; /* "/refs/" + NUL */
- for (cp = ref; (ch = *cp) != 0; cp++, len++)
- if (needs_quote(ch))
- len += 2; /* extra two hex plus replacement % */
- qref = xmalloc(len);
- memcpy(qref, base, baselen);
- memcpy(qref + baselen, "/refs/", 6);
- for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
- if (needs_quote(ch)) {
- *dp++ = '%';
- *dp++ = hex((ch >> 4) & 0xF);
- *dp++ = hex(ch & 0xF);
- }
- else
- *dp++ = ch;
- }
- *dp = 0;
-
- return qref;
-}
-
-int fetch_ref(char *ref, unsigned char *sha1)
-{
- char *url;
- char hex[42];
- struct buffer buffer;
- const char *base = alt->base;
- struct active_request_slot *slot;
- struct slot_results results;
- buffer.size = 41;
- buffer.posn = 0;
- buffer.buffer = hex;
- hex[41] = '\0';
-
- url = quote_ref_url(base, ref);
- slot = get_active_slot();
- slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- if (start_active_slot(slot)) {
- run_active_slot(slot);
- if (results.curl_result != CURLE_OK)
- return error("Couldn't get %s for %s\n%s",
- url, ref, curl_errorstr);
- } else {
- return error("Unable to start request");
- }
+ struct walker_data *data = walker->data;
+ http_cleanup();
- hex[40] = '\0';
- get_sha1_hex(hex, sha1);
- return 0;
+ curl_slist_free_all(data->no_pragma_header);
}
-int main(int argc, const char **argv)
+struct walker *get_http_walker(const char *url)
{
- int commits;
- const char **write_ref = NULL;
- char **commit_id;
- const char *url;
char *s;
- int arg = 1;
- int rc = 0;
-
- setup_git_directory();
- git_config(git_default_config);
-
- while (arg < argc && argv[arg][0] == '-') {
- if (argv[arg][1] == 't') {
- get_tree = 1;
- } else if (argv[arg][1] == 'c') {
- get_history = 1;
- } else if (argv[arg][1] == 'a') {
- get_all = 1;
- get_tree = 1;
- get_history = 1;
- } else if (argv[arg][1] == 'v') {
- get_verbosely = 1;
- } else if (argv[arg][1] == 'w') {
- write_ref = &argv[arg + 1];
- arg++;
- } else if (!strcmp(argv[arg], "--recover")) {
- get_recover = 1;
- } else if (!strcmp(argv[arg], "--stdin")) {
- commits_on_stdin = 1;
- }
- arg++;
- }
- if (argc < arg + 2 - commits_on_stdin) {
- usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
- return 1;
- }
- if (commits_on_stdin) {
- commits = pull_targets_stdin(&commit_id, &write_ref);
- } else {
- commit_id = (char **) &argv[arg++];
- commits = 1;
- }
- url = argv[arg];
+ struct walker_data *data = xmalloc(sizeof(struct walker_data));
+ struct walker *walker = xmalloc(sizeof(struct walker));
http_init();
- no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+ data->no_pragma_header = curl_slist_append(NULL, "Pragma:");
- alt = xmalloc(sizeof(*alt));
- alt->base = xmalloc(strlen(url) + 1);
- strcpy(alt->base, url);
- for (s = alt->base + strlen(alt->base) - 1; *s == '/'; --s)
+ data->alt = xmalloc(sizeof(*data->alt));
+ data->alt->base = xmalloc(strlen(url) + 1);
+ strcpy(data->alt->base, url);
+ for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s)
*s = 0;
- alt->got_indices = 0;
- alt->packs = NULL;
- alt->next = NULL;
- if (pull(commits, commit_id, write_ref, url))
- rc = 1;
-
- http_cleanup();
+ data->alt->got_indices = 0;
+ data->alt->packs = NULL;
+ data->alt->next = NULL;
+ data->got_alternates = -1;
- curl_slist_free_all(no_pragma_header);
+ walker->corrupt_object_found = 0;
+ walker->fetch = fetch;
+ walker->fetch_ref = fetch_ref;
+ walker->prefetch = prefetch;
+ walker->cleanup = cleanup;
+ walker->data = data;
- if (commits_on_stdin)
- pull_targets_free(commits, commit_id, write_ref);
+#ifdef USE_CURL_MULTI
+ add_fill_function(walker, (int (*)(void *)) fill_active_slot);
+#endif
- if (corrupt_object_found) {
- fprintf(stderr,
-"Some loose object were found to be corrupt, but they might be just\n"
-"a false '404 Not Found' error message sent with incorrect HTTP\n"
-"status code. Suggest running git-fsck.\n");
- }
- return rc;
+ return walker;
}
diff --git a/http.c b/http.c
index c6fb8ace9f..5925d07478 100644
--- a/http.c
+++ b/http.c
@@ -4,60 +4,55 @@ int data_received;
int active_requests = 0;
#ifdef USE_CURL_MULTI
-int max_requests = -1;
-CURLM *curlm;
+static int max_requests = -1;
+static CURLM *curlm;
#endif
#ifndef NO_CURL_EASY_DUPHANDLE
-CURL *curl_default;
+static CURL *curl_default;
#endif
char curl_errorstr[CURL_ERROR_SIZE];
-int curl_ssl_verify = -1;
-char *ssl_cert = NULL;
+static int curl_ssl_verify = -1;
+static char *ssl_cert = NULL;
#if LIBCURL_VERSION_NUM >= 0x070902
-char *ssl_key = NULL;
+static char *ssl_key = NULL;
#endif
#if LIBCURL_VERSION_NUM >= 0x070908
-char *ssl_capath = NULL;
+static char *ssl_capath = NULL;
#endif
-char *ssl_cainfo = NULL;
-long curl_low_speed_limit = -1;
-long curl_low_speed_time = -1;
-int curl_ftp_no_epsv = 0;
+static char *ssl_cainfo = NULL;
+static long curl_low_speed_limit = -1;
+static long curl_low_speed_time = -1;
+static int curl_ftp_no_epsv = 0;
+static char *curl_http_proxy = NULL;
-struct curl_slist *pragma_header;
+static struct curl_slist *pragma_header;
-struct active_request_slot *active_queue_head = NULL;
+static struct active_request_slot *active_queue_head = NULL;
size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
struct buffer *buffer)
{
size_t size = eltsize * nmemb;
- if (size > buffer->size - buffer->posn)
- size = buffer->size - buffer->posn;
- memcpy(ptr, (char *) buffer->buffer + buffer->posn, size);
+ if (size > buffer->buf.len - buffer->posn)
+ size = buffer->buf.len - buffer->posn;
+ memcpy(ptr, buffer->buf.buf + buffer->posn, size);
buffer->posn += size;
+
return size;
}
size_t fwrite_buffer(const void *ptr, size_t eltsize,
- size_t nmemb, struct buffer *buffer)
+ size_t nmemb, struct strbuf *buffer)
{
size_t size = eltsize * nmemb;
- if (size > buffer->size - buffer->posn) {
- buffer->size = buffer->size * 3 / 2;
- if (buffer->size < buffer->posn + size)
- buffer->size = buffer->posn + size;
- buffer->buffer = xrealloc(buffer->buffer, buffer->size);
- }
- memcpy((char *) buffer->buffer + buffer->posn, ptr, size);
- buffer->posn += size;
+ strbuf_add(buffer, ptr, size);
data_received++;
return size;
}
size_t fwrite_null(const void *ptr, size_t eltsize,
- size_t nmemb, struct buffer *buffer)
+ size_t nmemb, struct strbuf *buffer)
{
data_received++;
return eltsize * nmemb;
@@ -106,16 +101,18 @@ static int http_options(const char *var, const char *value)
if (!strcmp("http.sslcert", var)) {
if (ssl_cert == NULL) {
- ssl_cert = xmalloc(strlen(value)+1);
- strcpy(ssl_cert, value);
+ if (!value)
+ return config_error_nonbool(var);
+ ssl_cert = xstrdup(value);
}
return 0;
}
#if LIBCURL_VERSION_NUM >= 0x070902
if (!strcmp("http.sslkey", var)) {
if (ssl_key == NULL) {
- ssl_key = xmalloc(strlen(value)+1);
- strcpy(ssl_key, value);
+ if (!value)
+ return config_error_nonbool(var);
+ ssl_key = xstrdup(value);
}
return 0;
}
@@ -123,16 +120,18 @@ static int http_options(const char *var, const char *value)
#if LIBCURL_VERSION_NUM >= 0x070908
if (!strcmp("http.sslcapath", var)) {
if (ssl_capath == NULL) {
- ssl_capath = xmalloc(strlen(value)+1);
- strcpy(ssl_capath, value);
+ if (!value)
+ return config_error_nonbool(var);
+ ssl_capath = xstrdup(value);
}
return 0;
}
#endif
if (!strcmp("http.sslcainfo", var)) {
if (ssl_cainfo == NULL) {
- ssl_cainfo = xmalloc(strlen(value)+1);
- strcpy(ssl_cainfo, value);
+ if (!value)
+ return config_error_nonbool(var);
+ ssl_cainfo = xstrdup(value);
}
return 0;
}
@@ -160,6 +159,14 @@ static int http_options(const char *var, const char *value)
curl_ftp_no_epsv = git_config_bool(var, value);
return 0;
}
+ if (!strcmp("http.proxy", var)) {
+ if (curl_http_proxy == NULL) {
+ if (!value)
+ return config_error_nonbool(var);
+ curl_http_proxy = xstrdup(value);
+ }
+ return 0;
+ }
/* Fall back on the default ones */
return git_default_config(var, value);
@@ -205,6 +212,9 @@ static CURL* get_curl_handle(void)
if (curl_ftp_no_epsv)
curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
+ if (curl_http_proxy)
+ curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
+
return result;
}
@@ -276,6 +286,7 @@ void http_cleanup(void)
#endif
while (slot != NULL) {
+ struct active_request_slot *next = slot->next;
#ifdef USE_CURL_MULTI
if (slot->in_use) {
curl_easy_getinfo(slot->curl,
@@ -287,8 +298,10 @@ void http_cleanup(void)
#endif
if (slot->curl != NULL)
curl_easy_cleanup(slot->curl);
- slot = slot->next;
+ free(slot);
+ slot = next;
}
+ active_queue_head = NULL;
#ifndef NO_CURL_EASY_DUPHANDLE
curl_easy_cleanup(curl_default);
@@ -300,7 +313,7 @@ void http_cleanup(void)
curl_global_cleanup();
curl_slist_free_all(pragma_header);
- pragma_header = NULL;
+ pragma_header = NULL;
}
struct active_request_slot *get_active_slot(void)
@@ -356,7 +369,6 @@ 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_HTTPHEADER, NULL);
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);
@@ -372,6 +384,7 @@ int start_active_slot(struct active_request_slot *slot)
{
#ifdef USE_CURL_MULTI
CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+ int num_transfers;
if (curlm_result != CURLM_OK &&
curlm_result != CURLM_CALL_MULTI_PERFORM) {
@@ -379,11 +392,60 @@ int start_active_slot(struct active_request_slot *slot)
slot->in_use = 0;
return 0;
}
+
+ /*
+ * We know there must be something to do, since we just added
+ * something.
+ */
+ curl_multi_perform(curlm, &num_transfers);
#endif
return 1;
}
#ifdef USE_CURL_MULTI
+struct fill_chain {
+ void *data;
+ int (*fill)(void *);
+ struct fill_chain *next;
+};
+
+static struct fill_chain *fill_cfg = NULL;
+
+void add_fill_function(void *data, int (*fill)(void *))
+{
+ struct fill_chain *new = malloc(sizeof(*new));
+ struct fill_chain **linkp = &fill_cfg;
+ new->data = data;
+ new->fill = fill;
+ new->next = NULL;
+ while (*linkp)
+ linkp = &(*linkp)->next;
+ *linkp = new;
+}
+
+void fill_active_slots(void)
+{
+ struct active_request_slot *slot = active_queue_head;
+
+ while (active_requests < max_requests) {
+ struct fill_chain *fill;
+ for (fill = fill_cfg; fill; fill = fill->next)
+ if (fill->fill(fill->data))
+ break;
+
+ if (!fill)
+ break;
+ }
+
+ while (slot != NULL) {
+ if (!slot->in_use && slot->curl != NULL) {
+ curl_easy_cleanup(slot->curl);
+ slot->curl = NULL;
+ }
+ slot = slot->next;
+ }
+}
+
void step_active_slots(void)
{
int num_transfers;
@@ -444,8 +506,8 @@ void run_active_slot(struct active_request_slot *slot)
static void closedown_active_slot(struct active_request_slot *slot)
{
- active_requests--;
- slot->in_use = 0;
+ active_requests--;
+ slot->in_use = 0;
}
void release_active_slot(struct active_request_slot *slot)
@@ -466,7 +528,7 @@ void release_active_slot(struct active_request_slot *slot)
static void finish_active_slot(struct active_request_slot *slot)
{
closedown_active_slot(slot);
- curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
+ curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
if (slot->finished != NULL)
(*slot->finished) = 1;
@@ -477,10 +539,10 @@ static void finish_active_slot(struct active_request_slot *slot)
slot->results->http_code = slot->http_code;
}
- /* Run callback if appropriate */
- if (slot->callback_func != NULL) {
- slot->callback_func(slot->callback_data);
- }
+ /* Run callback if appropriate */
+ if (slot->callback_func != NULL) {
+ slot->callback_func(slot->callback_data);
+ }
}
void finish_all_active_slots(void)
@@ -495,3 +557,85 @@ void finish_all_active_slots(void)
slot = slot->next;
}
}
+
+static inline int needs_quote(int ch)
+{
+ if (((ch >= 'A') && (ch <= 'Z'))
+ || ((ch >= 'a') && (ch <= 'z'))
+ || ((ch >= '0') && (ch <= '9'))
+ || (ch == '/')
+ || (ch == '-')
+ || (ch == '.'))
+ return 0;
+ return 1;
+}
+
+static inline int hex(int v)
+{
+ if (v < 10) return '0' + v;
+ else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+ const char *cp;
+ char *dp, *qref;
+ int len, baselen, ch;
+
+ baselen = strlen(base);
+ len = baselen + 7; /* "/refs/" + NUL */
+ for (cp = ref; (ch = *cp) != 0; cp++, len++)
+ if (needs_quote(ch))
+ len += 2; /* extra two hex plus replacement % */
+ qref = xmalloc(len);
+ memcpy(qref, base, baselen);
+ memcpy(qref + baselen, "/refs/", 6);
+ for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
+ if (needs_quote(ch)) {
+ *dp++ = '%';
+ *dp++ = hex((ch >> 4) & 0xF);
+ *dp++ = hex(ch & 0xF);
+ }
+ else
+ *dp++ = ch;
+ }
+ *dp = 0;
+
+ return qref;
+}
+
+int http_fetch_ref(const char *base, const char *ref, unsigned char *sha1)
+{
+ char *url;
+ struct strbuf buffer = STRBUF_INIT;
+ struct active_request_slot *slot;
+ struct slot_results results;
+ int ret;
+
+ url = quote_ref_url(base, ref);
+ slot = get_active_slot();
+ slot->results = &results;
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (results.curl_result == CURLE_OK) {
+ strbuf_rtrim(&buffer);
+ if (buffer.len == 40)
+ ret = get_sha1_hex(buffer.buf, sha1);
+ else
+ ret = 1;
+ } else {
+ ret = error("Couldn't get %s for %s\n%s",
+ url, ref, curl_errorstr);
+ }
+ } else {
+ ret = error("Unable to start request");
+ }
+
+ strbuf_release(&buffer);
+ free(url);
+ return ret;
+}
diff --git a/http.h b/http.h
index 69b6b667d9..9bab2c8821 100644
--- a/http.h
+++ b/http.h
@@ -6,6 +6,16 @@
#include <curl/curl.h>
#include <curl/easy.h>
+#include "strbuf.h"
+
+/*
+ * We detect based on the cURL version if multi-transfer is
+ * usable in this implementation and define this symbol accordingly.
+ * This is not something Makefile should set nor users should pass
+ * via CFLAGS.
+ */
+#undef USE_CURL_MULTI
+
#if LIBCURL_VERSION_NUM >= 0x071000
#define USE_CURL_MULTI
#define DEFAULT_MAX_REQUESTS 5
@@ -48,18 +58,17 @@ struct active_request_slot
struct buffer
{
- size_t posn;
- size_t size;
- void *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,
struct buffer *buffer);
extern size_t fwrite_buffer(const void *ptr, size_t eltsize,
- size_t nmemb, struct buffer *buffer);
+ size_t nmemb, struct strbuf *buffer);
extern size_t fwrite_null(const void *ptr, size_t eltsize,
- size_t nmemb, struct buffer *buffer);
+ size_t nmemb, struct strbuf *buffer);
/* Slot lifecycle functions */
extern struct active_request_slot *get_active_slot(void);
@@ -70,6 +79,7 @@ extern void release_active_slot(struct active_request_slot *slot);
#ifdef USE_CURL_MULTI
extern void fill_active_slots(void);
+extern void add_fill_function(void *data, int (*fill)(void *));
extern void step_active_slots(void);
#endif
@@ -79,30 +89,21 @@ extern void http_cleanup(void);
extern int data_received;
extern int active_requests;
-#ifdef USE_CURL_MULTI
-extern int max_requests;
-extern CURLM *curlm;
-#endif
-#ifndef NO_CURL_EASY_DUPHANDLE
-extern CURL *curl_default;
-#endif
extern char curl_errorstr[CURL_ERROR_SIZE];
-extern int curl_ssl_verify;
-extern char *ssl_cert;
-#if LIBCURL_VERSION_NUM >= 0x070902
-extern char *ssl_key;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-extern char *ssl_capath;
-#endif
-extern char *ssl_cainfo;
-extern long curl_low_speed_limit;
-extern long curl_low_speed_time;
-
-extern struct curl_slist *pragma_header;
-extern struct curl_slist *no_range_header;
-
-extern struct active_request_slot *active_queue_head;
+static inline int missing__target(int code, int result)
+{
+ return /* file:// URL -- do we ever use one??? */
+ (result == CURLE_FILE_COULDNT_READ_FILE) ||
+ /* http:// and https:// URL */
+ (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||
+ /* ftp:// URL */
+ (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)
+ ;
+}
+
+#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
+
+extern int http_fetch_ref(const char *base, const char *ref, unsigned char *sha1);
#endif /* HTTP_H */
diff --git a/ident.c b/ident.c
index 9b2a852cb0..b839dcf5f0 100644
--- a/ident.c
+++ b/ident.c
@@ -113,25 +113,15 @@ static int add_raw(char *buf, size_t size, int offset, const char *str)
static int crud(unsigned char c)
{
- static char crud_array[256];
- static int crud_array_initialized = 0;
-
- if (!crud_array_initialized) {
- int k;
-
- for (k = 0; k <= 31; ++k) crud_array[k] = 1;
- crud_array[' '] = 1;
- crud_array['.'] = 1;
- crud_array[','] = 1;
- crud_array[':'] = 1;
- crud_array[';'] = 1;
- crud_array['<'] = 1;
- crud_array['>'] = 1;
- crud_array['"'] = 1;
- crud_array['\''] = 1;
- crud_array_initialized = 1;
- }
- return crud_array[c];
+ return c <= 32 ||
+ c == '.' ||
+ c == ',' ||
+ c == ':' ||
+ c == ';' ||
+ c == '<' ||
+ c == '>' ||
+ c == '"' ||
+ c == '\'';
}
/*
@@ -162,7 +152,7 @@ static int copy(char *buf, size_t size, int offset, const char *src)
/*
* Copy the rest to the buffer, but avoid the special
* characters '\n' '<' and '>' that act as delimiters on
- * a identification line
+ * an identification line
*/
for (i = 0; i < len; i++) {
c = *src++;
@@ -185,7 +175,7 @@ static const char *env_hint =
"\n"
"Run\n"
"\n"
-" git config --global user.email \"you@email.com\"\n"
+" git config --global user.email \"you@example.com\"\n"
" git config --global user.name \"Your Name\"\n"
"\n"
"to set your account\'s default identity.\n"
@@ -193,11 +183,14 @@ static const char *env_hint =
"\n";
const char *fmt_ident(const char *name, const char *email,
- const char *date_str, int error_on_no_name)
+ const char *date_str, int flag)
{
static char buffer[1000];
char date[50];
int i;
+ int error_on_no_name = (flag & IDENT_ERROR_ON_NO_NAME);
+ int warn_on_no_name = (flag & IDENT_WARN_ON_NO_NAME);
+ int name_addr_only = (flag & IDENT_NO_DATE);
setup_ident();
if (!name)
@@ -208,12 +201,12 @@ const char *fmt_ident(const char *name, const char *email,
if (!*name) {
struct passwd *pw;
- if (0 <= error_on_no_name &&
+ if ((warn_on_no_name || error_on_no_name) &&
name == git_default_name && env_hint) {
fprintf(stderr, env_hint, au_env, co_env);
env_hint = NULL; /* warn only once, for "git-var -l" */
}
- if (0 < error_on_no_name)
+ if (error_on_no_name)
die("empty ident %s <%s> not allowed", name, email);
pw = getpwuid(getuid());
if (!pw)
@@ -224,32 +217,41 @@ const char *fmt_ident(const char *name, const char *email,
}
strcpy(date, git_default_date);
- if (date_str)
+ if (!name_addr_only && date_str)
parse_date(date_str, date, sizeof(date));
i = copy(buffer, sizeof(buffer), 0, name);
i = add_raw(buffer, sizeof(buffer), i, " <");
i = copy(buffer, sizeof(buffer), i, email);
- i = add_raw(buffer, sizeof(buffer), i, "> ");
- i = copy(buffer, sizeof(buffer), i, date);
+ if (!name_addr_only) {
+ i = add_raw(buffer, sizeof(buffer), i, "> ");
+ i = copy(buffer, sizeof(buffer), i, date);
+ } else {
+ i = add_raw(buffer, sizeof(buffer), i, ">");
+ }
if (i >= sizeof(buffer))
die("Impossibly long personal identifier");
buffer[i] = 0;
return buffer;
}
-const char *git_author_info(int error_on_no_name)
+const char *fmt_name(const char *name, const char *email)
+{
+ return fmt_ident(name, email, NULL, IDENT_ERROR_ON_NO_NAME | IDENT_NO_DATE);
+}
+
+const char *git_author_info(int flag)
{
return fmt_ident(getenv("GIT_AUTHOR_NAME"),
getenv("GIT_AUTHOR_EMAIL"),
getenv("GIT_AUTHOR_DATE"),
- error_on_no_name);
+ flag);
}
-const char *git_committer_info(int error_on_no_name)
+const char *git_committer_info(int flag)
{
return fmt_ident(getenv("GIT_COMMITTER_NAME"),
getenv("GIT_COMMITTER_EMAIL"),
getenv("GIT_COMMITTER_DATE"),
- error_on_no_name);
+ flag);
}
diff --git a/imap-send.c b/imap-send.c
index a429a76a63..9025d9aa3e 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1254,6 +1254,10 @@ git_imap_config(const char *key, const char *val)
if (strncmp( key, imap_key, sizeof imap_key - 1 ))
return 0;
+
+ if (!val)
+ return config_error_nonbool(key);
+
key += sizeof imap_key - 1;
if (!strcmp( "folder", key )) {
diff --git a/index-pack.c b/index-pack.c
index db58e05041..9fd6982a97 100644
--- a/index-pack.c
+++ b/index-pack.c
@@ -46,7 +46,7 @@ static int nr_resolved_deltas;
static int from_stdin;
static int verbose;
-static struct progress progress;
+static struct progress *progress;
/* We always read in 4kB chunks. */
static unsigned char input_buffer[4096];
@@ -88,6 +88,8 @@ static void *fill(int min)
die("read error on input: %s", strerror(errno));
}
input_len += ret;
+ if (from_stdin)
+ display_throughput(progress, consumed_bytes + input_len);
} while (input_len < min);
return input_buffer;
}
@@ -106,7 +108,7 @@ static void use(int bytes)
consumed_bytes += bytes;
}
-static const char *open_pack_file(const char *pack_name)
+static char *open_pack_file(char *pack_name)
{
if (from_stdin) {
input_fd = 0;
@@ -254,7 +256,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
static void *get_data_from_pack(struct object_entry *obj)
{
- unsigned long from = obj[0].idx.offset + obj[0].hdr_size;
+ off_t from = obj[0].idx.offset + obj[0].hdr_size;
unsigned long len = obj[1].idx.offset - from;
unsigned long rdy = 0;
unsigned char *src, *data;
@@ -406,7 +408,9 @@ static void parse_pack_objects(unsigned char *sha1)
* - remember base (SHA1 or offset) for all deltas.
*/
if (verbose)
- start_progress(&progress, "Indexing %u objects...", "", nr_objects);
+ progress = start_progress(
+ from_stdin ? "Receiving objects" : "Indexing objects",
+ nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
data = unpack_raw_entry(obj, &delta->base);
@@ -418,12 +422,10 @@ static void parse_pack_objects(unsigned char *sha1)
} else
sha1_object(data, obj->size, obj->type, obj->idx.sha1);
free(data);
- if (verbose)
- display_progress(&progress, i+1);
+ display_progress(progress, i+1);
}
objects[i].idx.offset = consumed_bytes;
- if (verbose)
- stop_progress(&progress);
+ stop_progress(&progress);
/* Check pack integrity */
flush();
@@ -455,7 +457,7 @@ static void parse_pack_objects(unsigned char *sha1)
* for some more deltas.
*/
if (verbose)
- start_progress(&progress, "Resolving %u deltas...", "", nr_deltas);
+ progress = start_progress("Resolving deltas", nr_deltas);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
union delta_base base;
@@ -486,8 +488,7 @@ static void parse_pack_objects(unsigned char *sha1)
obj->size, obj->type);
}
free(data);
- if (verbose)
- display_progress(&progress, nr_resolved_deltas);
+ display_progress(progress, nr_resolved_deltas);
}
}
@@ -594,8 +595,7 @@ static void fix_unresolved_deltas(int nr_unresolved)
die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
append_obj_to_pack(d->base.sha1, data, size, type);
free(data);
- if (verbose)
- display_progress(&progress, nr_resolved_deltas);
+ display_progress(progress, nr_resolved_deltas);
}
free(sorted_by_pos);
}
@@ -683,18 +683,31 @@ 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)
+{
+ if (!strcmp(k, "pack.indexversion")) {
+ pack_idx_default_version = git_config_int(k, v);
+ if (pack_idx_default_version > 2)
+ die("bad pack.indexversion=%d", pack_idx_default_version);
+ return 0;
+ }
+ return git_default_config(k, v);
+}
+
int main(int argc, char **argv)
{
int i, fix_thin_pack = 0;
- const char *curr_pack, *pack_name = NULL;
- const char *curr_index, *index_name = NULL;
+ char *curr_pack, *pack_name = NULL;
+ char *curr_index, *index_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;
unsigned char sha1[20];
+ git_config(git_index_pack_config);
+
for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
+ char *arg = argv[i];
if (*arg == '-') {
if (!strcmp(arg, "--stdin")) {
@@ -774,12 +787,12 @@ int main(int argc, char **argv)
deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
parse_pack_objects(sha1);
if (nr_deltas == nr_resolved_deltas) {
- if (verbose)
- stop_progress(&progress);
+ stop_progress(&progress);
/* Flush remaining pack final 20-byte SHA1. */
flush();
} else {
if (fix_thin_pack) {
+ char msg[48];
int nr_unresolved = nr_deltas - nr_resolved_deltas;
int nr_objects_initial = nr_objects;
if (nr_unresolved <= 0)
@@ -788,13 +801,11 @@ int main(int argc, char **argv)
(nr_objects + nr_unresolved + 1)
* sizeof(*objects));
fix_unresolved_deltas(nr_unresolved);
- if (verbose) {
- stop_progress(&progress);
- fprintf(stderr, "%d objects were added to complete this thin pack.\n",
- nr_objects - nr_objects_initial);
- }
+ sprintf(msg, "completed with %d local objects",
+ nr_objects - nr_objects_initial);
+ stop_progress_msg(&progress, msg);
fixup_pack_header_footer(output_fd, sha1,
- curr_pack, nr_objects);
+ curr_pack, nr_objects);
}
if (nr_deltas != nr_resolved_deltas)
die("pack has %d unresolved deltas",
@@ -815,6 +826,10 @@ int main(int argc, char **argv)
free(objects);
free(index_name_buf);
free(keep_name_buf);
+ if (pack_name == NULL)
+ free(curr_pack);
+ if (index_name == NULL)
+ free(curr_index);
return 0;
}
diff --git a/list-objects.c b/list-objects.c
index e5c88c278f..c8b8375e49 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -18,6 +18,8 @@ static void process_blob(struct rev_info *revs,
if (!revs->blob_objects)
return;
+ if (!obj)
+ die("bad blob object");
if (obj->flags & (UNINTERESTING | SEEN))
return;
obj->flags |= SEEN;
@@ -69,6 +71,8 @@ static void process_tree(struct rev_info *revs,
if (!revs->tree_objects)
return;
+ if (!obj)
+ die("bad tree object");
if (obj->flags & (UNINTERESTING | SEEN))
return;
if (parse_tree(tree) < 0)
@@ -170,4 +174,11 @@ void traverse_commit_list(struct rev_info *revs,
}
for (i = 0; i < objects.nr; i++)
show_object(&objects.objects[i]);
+ free(objects.objects);
+ if (revs->pending.nr) {
+ free(revs->pending.objects);
+ revs->pending.nr = 0;
+ revs->pending.alloc = 0;
+ revs->pending.objects = NULL;
+ }
}
diff --git a/local-fetch.c b/local-fetch.c
deleted file mode 100644
index bf7ec6c2a3..0000000000
--- a/local-fetch.c
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2005 Junio C Hamano
- */
-#include "cache.h"
-#include "commit.h"
-#include "fetch.h"
-
-static int use_link;
-static int use_symlink;
-static int use_filecopy = 1;
-static int commits_on_stdin;
-
-static const char *path; /* "Remote" git repository */
-
-void prefetch(unsigned char *sha1)
-{
-}
-
-static struct packed_git *packs;
-
-static void setup_index(unsigned char *sha1)
-{
- struct packed_git *new_pack;
- char filename[PATH_MAX];
- strcpy(filename, path);
- strcat(filename, "/objects/pack/pack-");
- strcat(filename, sha1_to_hex(sha1));
- strcat(filename, ".idx");
- new_pack = parse_pack_index_file(sha1, filename);
- new_pack->next = packs;
- packs = new_pack;
-}
-
-static int setup_indices(void)
-{
- DIR *dir;
- struct dirent *de;
- char filename[PATH_MAX];
- unsigned char sha1[20];
- sprintf(filename, "%s/objects/pack/", path);
- dir = opendir(filename);
- if (!dir)
- return -1;
- while ((de = readdir(dir)) != NULL) {
- int namelen = strlen(de->d_name);
- if (namelen != 50 ||
- !has_extension(de->d_name, ".pack"))
- continue;
- get_sha1_hex(de->d_name + 5, sha1);
- setup_index(sha1);
- }
- closedir(dir);
- return 0;
-}
-
-static int copy_file(const char *source, char *dest, const char *hex,
- int warn_if_not_exists)
-{
- safe_create_leading_directories(dest);
- if (use_link) {
- if (!link(source, dest)) {
- pull_say("link %s\n", hex);
- return 0;
- }
- /* If we got ENOENT there is no point continuing. */
- if (errno == ENOENT) {
- if (!warn_if_not_exists)
- return -1;
- return error("does not exist %s", source);
- }
- }
- if (use_symlink) {
- struct stat st;
- if (stat(source, &st)) {
- if (!warn_if_not_exists && errno == ENOENT)
- return -1;
- return error("cannot stat %s: %s", source,
- strerror(errno));
- }
- if (!symlink(source, dest)) {
- pull_say("symlink %s\n", hex);
- return 0;
- }
- }
- if (use_filecopy) {
- int ifd, ofd, status = 0;
-
- ifd = open(source, O_RDONLY);
- if (ifd < 0) {
- if (!warn_if_not_exists && errno == ENOENT)
- return -1;
- return error("cannot open %s", source);
- }
- ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (ofd < 0) {
- close(ifd);
- return error("cannot open %s", dest);
- }
- status = copy_fd(ifd, ofd);
- close(ofd);
- if (status)
- return error("cannot write %s", dest);
- pull_say("copy %s\n", hex);
- return 0;
- }
- return error("failed to copy %s with given copy methods.", hex);
-}
-
-static int fetch_pack(const unsigned char *sha1)
-{
- struct packed_git *target;
- char filename[PATH_MAX];
- if (setup_indices())
- return -1;
- target = find_sha1_pack(sha1, packs);
- if (!target)
- return error("Couldn't find %s: not separate or in any pack",
- sha1_to_hex(sha1));
- if (get_verbosely) {
- fprintf(stderr, "Getting pack %s\n",
- sha1_to_hex(target->sha1));
- fprintf(stderr, " which contains %s\n",
- sha1_to_hex(sha1));
- }
- sprintf(filename, "%s/objects/pack/pack-%s.pack",
- path, sha1_to_hex(target->sha1));
- copy_file(filename, sha1_pack_name(target->sha1),
- sha1_to_hex(target->sha1), 1);
- sprintf(filename, "%s/objects/pack/pack-%s.idx",
- path, sha1_to_hex(target->sha1));
- copy_file(filename, sha1_pack_index_name(target->sha1),
- sha1_to_hex(target->sha1), 1);
- install_packed_git(target);
- return 0;
-}
-
-static int fetch_file(const unsigned char *sha1)
-{
- static int object_name_start = -1;
- static char filename[PATH_MAX];
- char *hex = sha1_to_hex(sha1);
- char *dest_filename = sha1_file_name(sha1);
-
- if (object_name_start < 0) {
- strcpy(filename, path); /* e.g. git.git */
- strcat(filename, "/objects/");
- object_name_start = strlen(filename);
- }
- filename[object_name_start+0] = hex[0];
- filename[object_name_start+1] = hex[1];
- filename[object_name_start+2] = '/';
- strcpy(filename + object_name_start + 3, hex + 2);
- return copy_file(filename, dest_filename, hex, 0);
-}
-
-int fetch(unsigned char *sha1)
-{
- if (has_sha1_file(sha1))
- return 0;
- else
- return fetch_file(sha1) && fetch_pack(sha1);
-}
-
-int fetch_ref(char *ref, unsigned char *sha1)
-{
- static int ref_name_start = -1;
- static char filename[PATH_MAX];
- static char hex[41];
- int ifd;
-
- if (ref_name_start < 0) {
- sprintf(filename, "%s/refs/", path);
- ref_name_start = strlen(filename);
- }
- strcpy(filename + ref_name_start, ref);
- ifd = open(filename, O_RDONLY);
- if (ifd < 0) {
- close(ifd);
- return error("cannot open %s", filename);
- }
- if (read_in_full(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) {
- close(ifd);
- return error("cannot read from %s", filename);
- }
- close(ifd);
- pull_say("ref %s\n", sha1_to_hex(sha1));
- return 0;
-}
-
-static const char local_pull_usage[] =
-"git-local-fetch [-c] [-t] [-a] [-v] [-w filename] [--recover] [-l] [-s] [-n] [--stdin] commit-id path";
-
-/*
- * By default we only use file copy.
- * If -l is specified, a hard link is attempted.
- * If -s is specified, then a symlink is attempted.
- * If -n is _not_ specified, then a regular file-to-file copy is done.
- */
-int main(int argc, const char **argv)
-{
- int commits;
- const char **write_ref = NULL;
- char **commit_id;
- int arg = 1;
-
- setup_git_directory();
- git_config(git_default_config);
-
- while (arg < argc && argv[arg][0] == '-') {
- if (argv[arg][1] == 't')
- get_tree = 1;
- else if (argv[arg][1] == 'c')
- get_history = 1;
- else if (argv[arg][1] == 'a') {
- get_all = 1;
- get_tree = 1;
- get_history = 1;
- }
- else if (argv[arg][1] == 'l')
- use_link = 1;
- else if (argv[arg][1] == 's')
- use_symlink = 1;
- else if (argv[arg][1] == 'n')
- use_filecopy = 0;
- else if (argv[arg][1] == 'v')
- get_verbosely = 1;
- else if (argv[arg][1] == 'w')
- write_ref = &argv[++arg];
- else if (!strcmp(argv[arg], "--recover"))
- get_recover = 1;
- else if (!strcmp(argv[arg], "--stdin"))
- commits_on_stdin = 1;
- else
- usage(local_pull_usage);
- arg++;
- }
- if (argc < arg + 2 - commits_on_stdin)
- usage(local_pull_usage);
- if (commits_on_stdin) {
- commits = pull_targets_stdin(&commit_id, &write_ref);
- } else {
- commit_id = (char **) &argv[arg++];
- commits = 1;
- }
- path = argv[arg];
-
- if (pull(commits, commit_id, write_ref, path))
- return 1;
-
- if (commits_on_stdin)
- pull_targets_free(commits, commit_id, write_ref);
-
- return 0;
-}
diff --git a/lockfile.c b/lockfile.c
index 9a1f64d8d7..663f18f9c4 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -12,8 +12,11 @@ static void remove_lock_file(void)
while (lock_file_list) {
if (lock_file_list->owner == me &&
- lock_file_list->filename[0])
+ lock_file_list->filename[0]) {
+ if (lock_file_list->fd >= 0)
+ close(lock_file_list->fd);
unlink(lock_file_list->filename);
+ }
lock_file_list = lock_file_list->next;
}
}
@@ -92,7 +95,7 @@ static char *resolve_symlink(char *p, size_t s)
return p;
}
- if (link[0] == '/') {
+ if (is_absolute_path(link)) {
/* absolute path simply replaces p */
if (link_len < s)
strcpy(p, link);
@@ -120,8 +123,6 @@ static char *resolve_symlink(char *p, size_t s)
static int lock_file(struct lock_file *lk, const char *path)
{
- int fd;
-
if (strlen(path) >= sizeof(lk->filename)) return -1;
strcpy(lk->filename, path);
/*
@@ -130,8 +131,8 @@ static int lock_file(struct lock_file *lk, const char *path)
*/
resolve_symlink(lk->filename, sizeof(lk->filename)-5);
strcat(lk->filename, ".lock");
- fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
- if (0 <= fd) {
+ lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
+ if (0 <= lk->fd) {
if (!lock_file_list) {
signal(SIGINT, remove_lock_file_on_signal);
atexit(remove_lock_file);
@@ -148,7 +149,7 @@ static int lock_file(struct lock_file *lk, const char *path)
}
else
lk->filename[0] = 0;
- return fd;
+ return lk->fd;
}
int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on_error)
@@ -159,16 +160,26 @@ int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on
return fd;
}
+int close_lock_file(struct lock_file *lk)
+{
+ int fd = lk->fd;
+ lk->fd = -1;
+ return close(fd);
+}
+
int commit_lock_file(struct lock_file *lk)
{
char result_file[PATH_MAX];
- int i;
+ size_t i;
+ if (lk->fd >= 0 && close_lock_file(lk))
+ return -1;
strcpy(result_file, lk->filename);
i = strlen(result_file) - 5; /* .lock */
result_file[i] = 0;
- i = rename(lk->filename, result_file);
+ if (rename(lk->filename, result_file))
+ return -1;
lk->filename[0] = 0;
- return i;
+ return 0;
}
int hold_locked_index(struct lock_file *lk, int die_on_error)
@@ -184,9 +195,12 @@ void set_alternate_index_output(const char *name)
int commit_locked_index(struct lock_file *lk)
{
if (alternate_index_output) {
- int result = rename(lk->filename, alternate_index_output);
+ if (lk->fd >= 0 && close_lock_file(lk))
+ return -1;
+ if (rename(lk->filename, alternate_index_output))
+ return -1;
lk->filename[0] = 0;
- return result;
+ return 0;
}
else
return commit_lock_file(lk);
@@ -194,7 +208,10 @@ int commit_locked_index(struct lock_file *lk)
void rollback_lock_file(struct lock_file *lk)
{
- if (lk->filename[0])
+ if (lk->filename[0]) {
+ if (lk->fd >= 0)
+ close(lk->fd);
unlink(lk->filename);
+ }
lk->filename[0] = 0;
}
diff --git a/log-tree.c b/log-tree.c
index 62edd34455..e9ba6df9d2 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -15,7 +15,7 @@ static void show_parents(struct commit *commit, int abbrev)
}
}
-static void show_decorations(struct commit *commit)
+void show_decorations(struct commit *commit)
{
const char *prefix;
struct name_decoration *decoration;
@@ -125,6 +125,18 @@ static unsigned int digits_in_number(unsigned int number)
return result;
}
+static int has_non_ascii(const char *s)
+{
+ int ch;
+ if (!s)
+ return 0;
+ while ((ch = *s++) != '\0') {
+ if (non_ascii(ch))
+ return 1;
+ }
+ return 0;
+}
+
void show_log(struct rev_info *opt, const char *sep)
{
struct strbuf msgbuf;
@@ -137,10 +149,12 @@ void show_log(struct rev_info *opt, const char *sep)
opt->loginfo = NULL;
if (!opt->verbose_header) {
- if (opt->left_right) {
- if (commit->object.flags & BOUNDARY)
- putchar('-');
- else if (commit->object.flags & SYMMETRIC_LEFT)
+ 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('>');
@@ -233,12 +247,13 @@ void show_log(struct rev_info *opt, const char *sep)
opt->diffopt.stat_sep = buffer;
}
} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
- fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
- stdout);
+ fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
if (opt->commit_format != CMIT_FMT_ONELINE)
fputs("commit ", stdout);
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('<');
@@ -254,8 +269,7 @@ void show_log(struct rev_info *opt, const char *sep)
diff_unique_abbrev(parent->object.sha1,
abbrev_commit));
show_decorations(commit);
- printf("%s",
- diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
+ printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
if (opt->reflog_info) {
show_reflog_message(opt->reflog_info,
@@ -268,12 +282,16 @@ void show_log(struct rev_info *opt, const char *sep)
}
}
+ if (!commit->buffer)
+ return;
+
/*
* And then the pretty-printed message itself
*/
strbuf_init(&msgbuf, 0);
pretty_print_commit(opt->commit_format, commit, &msgbuf,
- abbrev, subject, extra_headers, opt->date_mode);
+ abbrev, subject, extra_headers, opt->date_mode,
+ has_non_ascii(opt->add_signoff));
if (opt->add_signoff)
append_signoff(&msgbuf, opt->add_signoff);
diff --git a/log-tree.h b/log-tree.h
index e82b56a20d..b33f7cd7ac 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -12,5 +12,6 @@ int log_tree_diff_flush(struct rev_info *);
int log_tree_commit(struct rev_info *, struct commit *);
int log_tree_opt_parse(struct rev_info *, const char **, int);
void show_log(struct rev_info *opt, const char *sep);
+void show_decorations(struct commit *commit);
#endif
diff --git a/mailmap.c b/mailmap.c
index 8714167059..f0172552e4 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -42,9 +42,10 @@ int read_mailmap(struct path_list *map, const char *filename, char **repo_abbrev
continue;
if (right_bracket == left_bracket + 1)
continue;
- for (end_of_name = left_bracket; end_of_name != buffer
- && isspace(end_of_name[-1]); end_of_name--)
- /* keep on looking */
+ for (end_of_name = left_bracket;
+ end_of_name != buffer && isspace(end_of_name[-1]);
+ end_of_name--)
+ ; /* keep on looking */
if (end_of_name == buffer)
continue;
name = xmalloc(end_of_name - buffer + 1);
diff --git a/match-trees.c b/match-trees.c
index d7e29c4d1d..0fd6df7d6e 100644
--- a/match-trees.c
+++ b/match-trees.c
@@ -132,7 +132,7 @@ static void match_trees(const unsigned char *hash1,
const unsigned char *hash2,
int *best_score,
char **best_match,
- char *base,
+ const char *base,
int recurse_limit)
{
struct tree_desc one;
diff --git a/merge-file.c b/merge-file.c
index 1e031eafe0..2a939c9dd8 100644
--- a/merge-file.c
+++ b/merge-file.c
@@ -71,7 +71,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
res->size = 0;
ecb.priv = res;
- return xdl_diff(f1, f2, &xpp, &xecfg, &ecb);
+ return xdi_diff(f1, f2, &xpp, &xecfg, &ecb);
}
void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
diff --git a/merge-index.c b/merge-index.c
index fa719cb0b1..7491c56ad2 100644
--- a/merge-index.c
+++ b/merge-index.c
@@ -48,7 +48,7 @@ static int merge_entry(int pos, const char *path)
break;
found++;
strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
- sprintf(ownbuf[stage], "%o", ntohl(ce->ce_mode));
+ sprintf(ownbuf[stage], "%o", ce->ce_mode);
arguments[stage] = hexbuf[stage];
arguments[stage + 4] = ownbuf[stage];
} while (++pos < active_nr);
@@ -91,7 +91,7 @@ int main(int argc, char **argv)
signal(SIGCHLD, SIG_DFL);
if (argc < 3)
- usage("git-merge-index [-o] [-q] <merge-program> (-a | <filename>*)");
+ usage("git-merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
setup_git_directory();
read_cache();
diff --git a/merge-recursive.c b/merge-recursive.c
index 4a5c77c3b6..55ef76f5a5 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -289,7 +289,7 @@ static int get_files_dirs(struct tree *tree)
}
/*
- * Returns a index_entry instance which doesn't have to correspond to
+ * Returns an index_entry instance which doesn't have to correspond to
* a real cache entry in Git's index.
*/
static struct stage_data *insert_stage_data(const char *path,
@@ -333,7 +333,7 @@ static struct path_list *get_unmerged(void)
item->util = xcalloc(1, sizeof(struct stage_data));
}
e = item->util;
- e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode);
+ e->stages[ce_stage(ce)].mode = ce->ce_mode;
hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1);
}
@@ -366,7 +366,7 @@ static struct path_list *get_renames(struct tree *tree,
renames = xcalloc(1, sizeof(struct path_list));
diff_setup(&opts);
- opts.recursive = 1;
+ DIFF_OPT_SET(&opts, RECURSIVE);
opts.detect_rename = DIFF_DETECT_RENAME;
opts.rename_limit = rename_limit;
opts.output_format = DIFF_FORMAT_NO_OUTPUT;
@@ -549,6 +549,10 @@ static void update_file_flags(const unsigned char *sha,
void *buf;
unsigned long size;
+ if (S_ISGITLINK(mode))
+ die("cannot read object %s '%s': It is a submodule!",
+ sha1_to_hex(sha), path);
+
buf = read_sha1_file(sha, &type, &size);
if (!buf)
die("cannot read object %s '%s'", sha1_to_hex(sha), path);
@@ -840,8 +844,9 @@ static int read_merge_config(const char *var, const char *value)
int namelen;
if (!strcmp(var, "merge.default")) {
- if (value)
- default_ll_merge = strdup(value);
+ if (!value)
+ return config_error_nonbool(var);
+ default_ll_merge = strdup(value);
return 0;
}
@@ -874,14 +879,14 @@ static int read_merge_config(const char *var, const char *value)
if (!strcmp("name", ep)) {
if (!value)
- return error("%s: lacks value", var);
+ return config_error_nonbool(var);
fn->description = strdup(value);
return 0;
}
if (!strcmp("driver", ep)) {
if (!value)
- return error("%s: lacks value", var);
+ return config_error_nonbool(var);
/*
* merge.<name>.driver specifies the command line:
*
@@ -904,7 +909,7 @@ static int read_merge_config(const char *var, const char *value)
if (!strcmp("recursive", ep)) {
if (!value)
- return error("%s: lacks value", var);
+ return config_error_nonbool(var);
fn->recursive = strdup(value);
return 0;
}
@@ -1046,14 +1051,16 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
free(result_buf.ptr);
result.clean = (merge_status == 0);
- } else {
- if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode)))
- die("cannot merge modes?");
-
+ } else if (S_ISGITLINK(a->mode)) {
+ result.clean = 0;
+ hashcpy(result.sha, a->sha1);
+ } else if (S_ISLNK(a->mode)) {
hashcpy(result.sha, a->sha1);
if (!sha_eq(a->sha1, b->sha1))
result.clean = 0;
+ } else {
+ die("unsupported object type in the tree");
}
}
@@ -1461,10 +1468,13 @@ static int process_entry(const char *path, struct stage_data *entry,
mfi = merge_file(&o, &a, &b,
branch1, branch2);
+ clean_merge = mfi.clean;
if (mfi.clean)
update_file(1, mfi.sha, mfi.mode, path);
+ else if (S_ISGITLINK(mfi.mode))
+ output(1, "CONFLICT (submodule): Merge conflict in %s "
+ "- needs %s", path, sha1_to_hex(b.sha1));
else {
- clean_merge = 0;
output(1, "CONFLICT (%s): Merge conflict in %s",
reason, path);
@@ -1572,7 +1582,7 @@ static int merge(struct commit *h1,
{
struct commit_list *iter;
struct commit *merged_common_ancestors;
- struct tree *mrtree;
+ struct tree *mrtree = mrtree;
int clean;
if (show(4)) {
@@ -1663,6 +1673,8 @@ static struct commit *get_ref(const char *ref)
if (get_sha1(ref, sha1))
die("Could not resolve ref '%s'", ref);
object = deref_tag(parse_object(sha1), ref, strlen(ref));
+ if (!object)
+ return NULL;
if (object->type == OBJ_TREE)
return make_virtual_commit((struct tree*)object,
better_branch_name(ref));
@@ -1744,7 +1756,7 @@ int main(int argc, char *argv[])
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
- close(index_fd) || commit_locked_index(lock)))
+ commit_locked_index(lock)))
die ("unable to write %s", get_index_file());
return clean ? 0: 1;
diff --git a/merge-tree.c b/merge-tree.c
index 7d4f628444..e08324686c 100644
--- a/merge-tree.c
+++ b/merge-tree.c
@@ -119,7 +119,7 @@ static void show_diff(struct merge_list *entry)
if (!dst.ptr)
size = 0;
dst.size = size;
- xdl_diff(&src, &dst, &xpp, &xecfg, &ecb);
+ xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
free(src.ptr);
free(dst.ptr);
}
diff --git a/object.c b/object.c
index 16793d9958..50b6528001 100644
--- a/object.c
+++ b/object.c
@@ -136,29 +136,42 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
struct object *obj;
int eaten = 0;
+ obj = NULL;
if (type == OBJ_BLOB) {
struct blob *blob = lookup_blob(sha1);
- parse_blob_buffer(blob, buffer, size);
- obj = &blob->object;
+ if (blob) {
+ if (parse_blob_buffer(blob, buffer, size))
+ return NULL;
+ obj = &blob->object;
+ }
} else if (type == OBJ_TREE) {
struct tree *tree = lookup_tree(sha1);
- obj = &tree->object;
- if (!tree->object.parsed) {
- parse_tree_buffer(tree, buffer, size);
- eaten = 1;
+ if (tree) {
+ obj = &tree->object;
+ if (!tree->object.parsed) {
+ if (parse_tree_buffer(tree, buffer, size))
+ return NULL;
+ eaten = 1;
+ }
}
} else if (type == OBJ_COMMIT) {
struct commit *commit = lookup_commit(sha1);
- parse_commit_buffer(commit, buffer, size);
- if (!commit->buffer) {
- commit->buffer = buffer;
- eaten = 1;
+ if (commit) {
+ if (parse_commit_buffer(commit, buffer, size))
+ return NULL;
+ if (!commit->buffer) {
+ commit->buffer = buffer;
+ eaten = 1;
+ }
+ obj = &commit->object;
}
- obj = &commit->object;
} else if (type == OBJ_TAG) {
struct tag *tag = lookup_tag(sha1);
- parse_tag_buffer(tag, buffer, size);
- obj = &tag->object;
+ if (tag) {
+ if (parse_tag_buffer(tag, buffer, size))
+ return NULL;
+ obj = &tag->object;
+ }
} else {
warning("object %s has unknown type id %d\n", sha1_to_hex(sha1), type);
obj = NULL;
diff --git a/pack-write.c b/pack-write.c
index e59b197e5e..665e2b29b8 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -17,7 +17,8 @@ static int sha1_compare(const void *_a, const void *_b)
* 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)
+char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
+ int nr_objects, unsigned char *sha1)
{
struct sha1file *f;
struct pack_idx_entry **sorted_by_sha, **list, **last;
@@ -179,3 +180,29 @@ void fixup_pack_header_footer(int pack_fd,
SHA1_Final(pack_file_sha1, &c);
write_or_die(pack_fd, pack_file_sha1, 20);
}
+
+char *index_pack_lockfile(int ip_out)
+{
+ int len, s;
+ char packname[46];
+
+ /*
+ * The first thing we expects from index-pack's output
+ * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
+ * %40s is the newly created pack SHA1 name. In the "keep"
+ * case, we need it to remove the corresponding .keep file
+ * later on. If we don't get that then tough luck with it.
+ */
+ for (len = 0;
+ len < 46 && (s = xread(ip_out, packname+len, 46-len)) > 0;
+ len += s);
+ if (len == 46 && packname[45] == '\n' &&
+ memcmp(packname, "keep\t", 5) == 0) {
+ char path[PATH_MAX];
+ packname[45] = 0;
+ snprintf(path, sizeof(path), "%s/pack/pack-%s.keep",
+ get_object_directory(), packname + 5);
+ return xstrdup(path);
+ }
+ return NULL;
+}
diff --git a/pack.h b/pack.h
index f357c9f428..b31b37608d 100644
--- a/pack.h
+++ b/pack.h
@@ -55,10 +55,11 @@ 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 char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
extern int verify_pack(struct packed_git *, int);
extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t);
+extern char *index_pack_lockfile(int fd);
#define PH_ERROR_EOF (-1)
#define PH_ERROR_PACK_SIGNATURE (-2)
diff --git a/pager.c b/pager.c
index 8bac9d9903..ca002f9f79 100644
--- a/pager.c
+++ b/pager.c
@@ -1,12 +1,12 @@
#include "cache.h"
-#include <sys/select.h>
-
/*
* This is split up from the rest of git so that we might do
* something different on Windows, for example.
*/
+static int spawned_pager;
+
static void run_pager(const char *pager)
{
/*
@@ -43,7 +43,7 @@ void setup_pager(void)
else if (!*pager || !strcmp(pager, "cat"))
return;
- pager_in_use = 1; /* means we are emitting to terminal */
+ spawned_pager = 1; /* means we are emitting to terminal */
if (pipe(fd) < 0)
return;
@@ -57,6 +57,7 @@ void setup_pager(void)
/* return in the child */
if (!pid) {
dup2(fd[1], 1);
+ dup2(fd[1], 2);
close(fd[0]);
close(fd[1]);
return;
@@ -72,3 +73,14 @@ void setup_pager(void)
die("unable to execute pager '%s'", pager);
exit(255);
}
+
+int pager_in_use(void)
+{
+ const char *env;
+
+ if (spawned_pager)
+ return 1;
+
+ env = getenv("GIT_PAGER_IN_USE");
+ return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0;
+}
diff --git a/parse-options.c b/parse-options.c
new file mode 100644
index 0000000000..d9562ba504
--- /dev/null
+++ b/parse-options.c
@@ -0,0 +1,397 @@
+#include "git-compat-util.h"
+#include "parse-options.h"
+
+#define OPT_SHORT 1
+#define OPT_UNSET 2
+
+struct optparse_t {
+ const char **argv;
+ int argc;
+ const char *opt;
+};
+
+static inline const char *get_arg(struct optparse_t *p)
+{
+ if (p->opt) {
+ const char *res = p->opt;
+ p->opt = NULL;
+ return res;
+ }
+ p->argc--;
+ return *++p->argv;
+}
+
+static inline const char *skip_prefix(const char *str, const char *prefix)
+{
+ size_t len = strlen(prefix);
+ return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
+static int opterror(const struct option *opt, const char *reason, int flags)
+{
+ if (flags & OPT_SHORT)
+ return error("switch `%c' %s", opt->short_name, reason);
+ if (flags & OPT_UNSET)
+ return error("option `no-%s' %s", opt->long_name, reason);
+ return error("option `%s' %s", opt->long_name, reason);
+}
+
+static int get_value(struct optparse_t *p,
+ const struct option *opt, int flags)
+{
+ const char *s, *arg;
+ const int unset = flags & OPT_UNSET;
+
+ if (unset && p->opt)
+ 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_SET_INT:
+ case OPTION_SET_PTR:
+ return opterror(opt, "takes no value", flags);
+ default:
+ break;
+ }
+ }
+
+ arg = p->opt ? p->opt : (p->argc > 1 ? p->argv[1] : NULL);
+ switch (opt->type) {
+ case OPTION_BIT:
+ if (unset)
+ *(int *)opt->value &= ~opt->defval;
+ else
+ *(int *)opt->value |= opt->defval;
+ return 0;
+
+ case OPTION_BOOLEAN:
+ *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
+ return 0;
+
+ case OPTION_SET_INT:
+ *(int *)opt->value = unset ? 0 : opt->defval;
+ return 0;
+
+ case OPTION_SET_PTR:
+ *(void **)opt->value = unset ? NULL : (void *)opt->defval;
+ return 0;
+
+ case OPTION_STRING:
+ if (unset) {
+ *(const char **)opt->value = NULL;
+ return 0;
+ }
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ *(const char **)opt->value = (const char *)opt->defval;
+ return 0;
+ }
+ if (!arg)
+ return opterror(opt, "requires a value", flags);
+ *(const char **)opt->value = get_arg(p);
+ return 0;
+
+ case OPTION_CALLBACK:
+ if (unset)
+ return (*opt->callback)(opt, NULL, 1);
+ if (opt->flags & PARSE_OPT_NOARG)
+ return (*opt->callback)(opt, NULL, 0);
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+ return (*opt->callback)(opt, NULL, 0);
+ if (!arg)
+ return opterror(opt, "requires a value", flags);
+ return (*opt->callback)(opt, get_arg(p), 0);
+
+ case OPTION_INTEGER:
+ if (unset) {
+ *(int *)opt->value = 0;
+ return 0;
+ }
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ *(int *)opt->value = opt->defval;
+ return 0;
+ }
+ if (!arg)
+ return opterror(opt, "requires a value", flags);
+ *(int *)opt->value = strtol(get_arg(p), (char **)&s, 10);
+ if (*s)
+ return opterror(opt, "expects a numerical value", flags);
+ return 0;
+
+ default:
+ die("should not happen, someone must be hit on the forehead");
+ }
+}
+
+static int parse_short_opt(struct optparse_t *p, const struct option *options)
+{
+ for (; options->type != OPTION_END; options++) {
+ if (options->short_name == *p->opt) {
+ p->opt = p->opt[1] ? p->opt + 1 : NULL;
+ return get_value(p, options, OPT_SHORT);
+ }
+ }
+ return error("unknown switch `%c'", *p->opt);
+}
+
+static int parse_long_opt(struct optparse_t *p, const char *arg,
+ const struct option *options)
+{
+ const char *arg_end = strchr(arg, '=');
+ const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
+ int abbrev_flags = 0, ambiguous_flags = 0;
+
+ if (!arg_end)
+ arg_end = arg + strlen(arg);
+
+ for (; options->type != OPTION_END; options++) {
+ const char *rest;
+ int flags = 0;
+
+ if (!options->long_name)
+ continue;
+
+ rest = skip_prefix(arg, options->long_name);
+ if (!rest) {
+ /* abbreviated? */
+ if (!strncmp(options->long_name, arg, arg_end - arg)) {
+is_abbreviated:
+ if (abbrev_option) {
+ /*
+ * If this is abbreviated, it is
+ * ambiguous. So when there is no
+ * exact match later, we need to
+ * error out.
+ */
+ ambiguous_option = abbrev_option;
+ ambiguous_flags = abbrev_flags;
+ }
+ if (!(flags & OPT_UNSET) && *arg_end)
+ p->opt = arg_end + 1;
+ abbrev_option = options;
+ abbrev_flags = flags;
+ continue;
+ }
+ /* negated and abbreviated very much? */
+ if (!prefixcmp("no-", arg)) {
+ flags |= OPT_UNSET;
+ goto is_abbreviated;
+ }
+ /* negated? */
+ if (strncmp(arg, "no-", 3))
+ continue;
+ flags |= OPT_UNSET;
+ rest = skip_prefix(arg + 3, options->long_name);
+ /* abbreviated and negated? */
+ if (!rest && !prefixcmp(options->long_name, arg + 3))
+ goto is_abbreviated;
+ if (!rest)
+ continue;
+ }
+ if (*rest) {
+ if (*rest != '=')
+ continue;
+ p->opt = rest + 1;
+ }
+ return get_value(p, options, flags);
+ }
+
+ if (ambiguous_option)
+ return error("Ambiguous option: %s "
+ "(could be --%s%s or --%s%s)",
+ arg,
+ (ambiguous_flags & OPT_UNSET) ? "no-" : "",
+ ambiguous_option->long_name,
+ (abbrev_flags & OPT_UNSET) ? "no-" : "",
+ abbrev_option->long_name);
+ if (abbrev_option)
+ return get_value(p, abbrev_option, abbrev_flags);
+ return error("unknown option `%s'", arg);
+}
+
+void check_typos(const char *arg, const struct option *options)
+{
+ if (strlen(arg) < 3)
+ return;
+
+ if (!prefixcmp(arg, "no-")) {
+ error ("did you mean `--%s` (with two dashes ?)", arg);
+ exit(129);
+ }
+
+ for (; options->type != OPTION_END; options++) {
+ if (!options->long_name)
+ continue;
+ if (!prefixcmp(options->long_name, arg)) {
+ error ("did you mean `--%s` (with two dashes ?)", arg);
+ exit(129);
+ }
+ }
+}
+
+static NORETURN void usage_with_options_internal(const char * const *,
+ const struct option *, int);
+
+int parse_options(int argc, const char **argv, const struct option *options,
+ const char * const usagestr[], int flags)
+{
+ struct optparse_t args = { argv + 1, argc - 1, NULL };
+ int j = 0;
+
+ for (; args.argc; args.argc--, args.argv++) {
+ const char *arg = args.argv[0];
+
+ if (*arg != '-' || !arg[1]) {
+ argv[j++] = args.argv[0];
+ continue;
+ }
+
+ if (arg[1] != '-') {
+ args.opt = arg + 1;
+ if (*args.opt == 'h')
+ usage_with_options(usagestr, options);
+ if (parse_short_opt(&args, options) < 0)
+ usage_with_options(usagestr, options);
+ if (args.opt)
+ check_typos(arg + 1, options);
+ while (args.opt) {
+ if (*args.opt == 'h')
+ usage_with_options(usagestr, options);
+ if (parse_short_opt(&args, options) < 0)
+ usage_with_options(usagestr, options);
+ }
+ continue;
+ }
+
+ if (!arg[2]) { /* "--" */
+ if (!(flags & PARSE_OPT_KEEP_DASHDASH)) {
+ args.argc--;
+ args.argv++;
+ }
+ break;
+ }
+
+ if (!strcmp(arg + 2, "help-all"))
+ usage_with_options_internal(usagestr, options, 1);
+ if (!strcmp(arg + 2, "help"))
+ usage_with_options(usagestr, options);
+ if (parse_long_opt(&args, arg + 2, options))
+ usage_with_options(usagestr, options);
+ }
+
+ memmove(argv + j, args.argv, args.argc * sizeof(*argv));
+ argv[j + args.argc] = NULL;
+ return j + args.argc;
+}
+
+#define USAGE_OPTS_WIDTH 24
+#define USAGE_GAP 2
+
+void usage_with_options_internal(const char * const *usagestr,
+ const struct option *opts, int full)
+{
+ fprintf(stderr, "usage: %s\n", *usagestr++);
+ while (*usagestr && **usagestr)
+ fprintf(stderr, " or: %s\n", *usagestr++);
+ while (*usagestr)
+ fprintf(stderr, " %s\n", *usagestr++);
+
+ if (opts->type != OPTION_GROUP)
+ fputc('\n', stderr);
+
+ for (; opts->type != OPTION_END; opts++) {
+ size_t pos;
+ int pad;
+
+ if (opts->type == OPTION_GROUP) {
+ fputc('\n', stderr);
+ if (*opts->help)
+ fprintf(stderr, "%s\n", opts->help);
+ continue;
+ }
+ if (!full && (opts->flags & PARSE_OPT_HIDDEN))
+ continue;
+
+ pos = fprintf(stderr, " ");
+ if (opts->short_name)
+ pos += fprintf(stderr, "-%c", opts->short_name);
+ if (opts->long_name && opts->short_name)
+ pos += fprintf(stderr, ", ");
+ if (opts->long_name)
+ pos += fprintf(stderr, "--%s", opts->long_name);
+
+ switch (opts->type) {
+ case OPTION_INTEGER:
+ if (opts->flags & PARSE_OPT_OPTARG)
+ pos += fprintf(stderr, " [<n>]");
+ else
+ pos += fprintf(stderr, " <n>");
+ break;
+ case OPTION_CALLBACK:
+ if (opts->flags & PARSE_OPT_NOARG)
+ break;
+ /* FALLTHROUGH */
+ case OPTION_STRING:
+ if (opts->argh) {
+ if (opts->flags & PARSE_OPT_OPTARG)
+ pos += fprintf(stderr, " [<%s>]", opts->argh);
+ else
+ pos += fprintf(stderr, " <%s>", opts->argh);
+ } else {
+ if (opts->flags & PARSE_OPT_OPTARG)
+ pos += fprintf(stderr, " [...]");
+ else
+ pos += fprintf(stderr, " ...");
+ }
+ break;
+ default: /* OPTION_{BIT,BOOLEAN,SET_INT,SET_PTR} */
+ break;
+ }
+
+ if (pos <= USAGE_OPTS_WIDTH)
+ pad = USAGE_OPTS_WIDTH - pos;
+ else {
+ fputc('\n', stderr);
+ pad = USAGE_OPTS_WIDTH;
+ }
+ fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
+ }
+ fputc('\n', stderr);
+
+ exit(129);
+}
+
+void usage_with_options(const char * const *usagestr,
+ const struct option *opts)
+{
+ usage_with_options_internal(usagestr, opts, 0);
+}
+
+/*----- some often used options -----*/
+#include "cache.h"
+
+int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
+{
+ int v;
+
+ if (!arg) {
+ v = unset ? 0 : DEFAULT_ABBREV;
+ } else {
+ v = strtol(arg, (char **)&arg, 10);
+ if (*arg)
+ return opterror(opt, "expects a numerical value", 0);
+ if (v && v < MINIMUM_ABBREV)
+ v = MINIMUM_ABBREV;
+ else if (v > 40)
+ v = 40;
+ }
+ *(int *)(opt->value) = v;
+ return 0;
+}
diff --git a/parse-options.h b/parse-options.h
new file mode 100644
index 0000000000..102ac31fb7
--- /dev/null
+++ b/parse-options.h
@@ -0,0 +1,119 @@
+#ifndef PARSE_OPTIONS_H
+#define PARSE_OPTIONS_H
+
+enum parse_opt_type {
+ /* special types */
+ OPTION_END,
+ OPTION_GROUP,
+ /* options with no arguments */
+ OPTION_BIT,
+ OPTION_BOOLEAN, /* _INCR would have been a better name */
+ OPTION_SET_INT,
+ OPTION_SET_PTR,
+ /* options with arguments (usually) */
+ OPTION_STRING,
+ OPTION_INTEGER,
+ OPTION_CALLBACK,
+};
+
+enum parse_opt_flags {
+ PARSE_OPT_KEEP_DASHDASH = 1,
+};
+
+enum parse_opt_option_flags {
+ PARSE_OPT_OPTARG = 1,
+ PARSE_OPT_NOARG = 2,
+ PARSE_OPT_NONEG = 4,
+ PARSE_OPT_HIDDEN = 8,
+};
+
+struct option;
+typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
+
+/*
+ * `type`::
+ * holds the type of the option, you must have an OPTION_END last in your
+ * array.
+ *
+ * `short_name`::
+ * the character to use as a short option name, '\0' if none.
+ *
+ * `long_name`::
+ * the long option name, without the leading dashes, NULL if none.
+ *
+ * `value`::
+ * stores pointers to the values to be filled.
+ *
+ * `argh`::
+ * token to explain the kind of argument this option wants. Keep it
+ * homogenous across the repository.
+ *
+ * `help`::
+ * the short help associated to what the option does.
+ * Must never be NULL (except for OPTION_END).
+ * OPTION_GROUP uses this pointer to store the group header.
+ *
+ * `flags`::
+ * mask of parse_opt_option_flags.
+ * PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs)
+ * PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs
+ * PARSE_OPT_NONEG: says that this option cannot be negated
+ * PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in
+ * the long one.
+ *
+ * `callback`::
+ * pointer to the callback to use for OPTION_CALLBACK.
+ *
+ * `defval`::
+ * default value to fill (*->value) with for PARSE_OPT_OPTARG.
+ * OPTION_{BIT,SET_INT,SET_PTR} store the {mask,integer,pointer} to put in
+ * the value when met.
+ * CALLBACKS can use it like they want.
+ */
+struct option {
+ enum parse_opt_type type;
+ int short_name;
+ const char *long_name;
+ void *value;
+ const char *argh;
+ const char *help;
+
+ int flags;
+ parse_opt_cb *callback;
+ intptr_t defval;
+};
+
+#define OPT_END() { OPTION_END }
+#define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
+#define OPT_BIT(s, l, v, h, b) { OPTION_BIT, (s), (l), (v), NULL, (h), 0, NULL, (b) }
+#define OPT_BOOLEAN(s, l, v, h) { OPTION_BOOLEAN, (s), (l), (v), NULL, (h) }
+#define OPT_SET_INT(s, l, v, h, i) { OPTION_SET_INT, (s), (l), (v), NULL, (h), 0, NULL, (i) }
+#define OPT_SET_PTR(s, l, v, h, p) { OPTION_SET_PTR, (s), (l), (v), NULL, (h), 0, NULL, (p) }
+#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), NULL, (h) }
+#define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) }
+#define OPT_CALLBACK(s, l, v, a, h, f) \
+ { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
+
+/* parse_options() will filter out the processed options and leave the
+ * non-option argments in argv[].
+ * Returns the number of arguments left in argv[].
+ */
+extern int parse_options(int argc, const char **argv,
+ const struct option *options,
+ const char * const usagestr[], int flags);
+
+extern NORETURN void usage_with_options(const char * const *usagestr,
+ const struct option *options);
+
+/*----- some often used options -----*/
+extern int parse_opt_abbrev_cb(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__DRY_RUN(var) OPT_BOOLEAN('n', "dry-run", (var), "dry run")
+#define OPT__ABBREV(var) \
+ { OPTION_CALLBACK, 0, "abbrev", (var), "n", \
+ "use <n> digits to display SHA-1s", \
+ PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
+
+#endif
diff --git a/patch-ids.c b/patch-ids.c
index a288fac992..3be5d3165e 100644
--- a/patch-ids.c
+++ b/patch-ids.c
@@ -121,7 +121,7 @@ int init_patch_ids(struct patch_ids *ids)
{
memset(ids, 0, sizeof(*ids));
diff_setup(&ids->diffopts);
- ids->diffopts.recursive = 1;
+ DIFF_OPT_SET(&ids->diffopts, RECURSIVE);
if (diff_setup_done(&ids->diffopts) < 0)
return error("diff_setup_done failed");
return 0;
diff --git a/peek-remote.c b/peek-remote.c
deleted file mode 100644
index ceb787170e..0000000000
--- a/peek-remote.c
+++ /dev/null
@@ -1,75 +0,0 @@
-#include "cache.h"
-#include "refs.h"
-#include "pkt-line.h"
-
-static const char peek_remote_usage[] =
-"git-peek-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>";
-static const char *uploadpack = "git-upload-pack";
-
-static int peek_remote(int fd[2], unsigned flags)
-{
- struct ref *ref;
-
- get_remote_heads(fd[0], &ref, 0, NULL, flags);
- packet_flush(fd[1]);
-
- while (ref) {
- printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
- ref = ref->next;
- }
- return 0;
-}
-
-int main(int argc, char **argv)
-{
- int i, ret;
- char *dest = NULL;
- int fd[2];
- pid_t pid;
- int nongit = 0;
- unsigned flags = 0;
-
- setup_git_directory_gently(&nongit);
-
- for (i = 1; i < argc; i++) {
- char *arg = argv[i];
-
- if (*arg == '-') {
- if (!prefixcmp(arg, "--upload-pack=")) {
- uploadpack = arg + 14;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- uploadpack = arg + 7;
- continue;
- }
- if (!strcmp("--tags", arg)) {
- flags |= REF_TAGS;
- continue;
- }
- if (!strcmp("--heads", arg)) {
- flags |= REF_HEADS;
- continue;
- }
- if (!strcmp("--refs", arg)) {
- flags |= REF_NORMAL;
- continue;
- }
- usage(peek_remote_usage);
- }
- dest = arg;
- break;
- }
-
- if (!dest || i != argc - 1)
- usage(peek_remote_usage);
-
- pid = git_connect(fd, dest, uploadpack, 0);
- if (pid < 0)
- return 1;
- ret = peek_remote(fd, flags);
- close(fd[0]);
- close(fd[1]);
- ret |= finish_connect(pid);
- return !!ret;
-}
diff --git a/perl/Git.pm b/perl/Git.pm
index 3f4080cbf8..a2812ea612 100644
--- a/perl/Git.pm
+++ b/perl/Git.pm
@@ -549,6 +549,72 @@ sub config_bool {
};
}
+=item config_int ( VARIABLE )
+
+Retrieve the integer configuration C<VARIABLE>. The return value
+is simple decimal number. An optional value suffix of 'k', 'm',
+or 'g' in the config file will cause the value to be multiplied
+by 1024, 1048576 (1024^2), or 1073741824 (1024^3) prior to output.
+It would return C<undef> if configuration variable is not defined,
+
+Must be called on a repository instance.
+
+This currently wraps command('config') so it is not so fast.
+
+=cut
+
+sub config_int {
+ my ($self, $var) = @_;
+ $self->repo_path()
+ or throw Error::Simple("not a repository");
+
+ try {
+ return $self->command_oneline('config', '--int', '--get', $var);
+ } catch Git::Error::Command with {
+ my $E = shift;
+ if ($E->value() == 1) {
+ # Key not found.
+ return undef;
+ } else {
+ throw $E;
+ }
+ };
+}
+
+=item get_colorbool ( NAME )
+
+Finds if color should be used for NAMEd operation from the configuration,
+and returns boolean (true for "use color", false for "do not use color").
+
+=cut
+
+sub get_colorbool {
+ my ($self, $var) = @_;
+ my $stdout_to_tty = (-t STDOUT) ? "true" : "false";
+ my $use_color = $self->command_oneline('config', '--get-colorbool',
+ $var, $stdout_to_tty);
+ return ($use_color eq 'true');
+}
+
+=item get_color ( SLOT, COLOR )
+
+Finds color for SLOT from the configuration, while defaulting to COLOR,
+and returns the ANSI color escape sequence:
+
+ print $repo->get_color("color.interactive.prompt", "underline blue white");
+ print "some text";
+ print $repo->get_color("", "normal");
+
+=cut
+
+sub get_color {
+ my ($self, $slot, $default) = @_;
+ my $color = $self->command_oneline('config', '--get-color', $slot, $default);
+ if (!defined $color) {
+ $color = "";
+ }
+ return $color;
+}
=item ident ( TYPE | IDENTSTR )
@@ -812,7 +878,7 @@ sub _cmd_exec {
$self->wc_subdir() and chdir($self->wc_subdir());
}
_execv_git_cmd(@args);
- die "exec failed: $!";
+ die qq[exec "@args" failed: $!];
}
# Execute the given Git command ($_[0]) with arguments ($_[1..])
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index 6aecd897f8..320253eb8e 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -17,9 +17,6 @@ if ($@ || $Error::VERSION < 0.15009) {
$pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm';
}
-my %extra;
-$extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR};
-
# redirect stdout, otherwise the message "Writing perl.mak for Git"
# disrupts the output for the target 'instlibdir'
open STDOUT, ">&STDERR";
@@ -29,6 +26,5 @@ WriteMakefile(
VERSION_FROM => 'Git.pm',
PM => \%pm,
MAKEFILE => 'perl.mak',
- INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3',
- %extra
+ INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
);
diff --git a/pretty.c b/pretty.c
new file mode 100644
index 0000000000..997f5837d5
--- /dev/null
+++ b/pretty.c
@@ -0,0 +1,813 @@
+#include "cache.h"
+#include "commit.h"
+#include "utf8.h"
+#include "diff.h"
+#include "revision.h"
+
+static struct cmt_fmt_map {
+ const char *n;
+ size_t cmp_len;
+ enum cmit_fmt v;
+} cmt_fmts[] = {
+ { "raw", 1, CMIT_FMT_RAW },
+ { "medium", 1, CMIT_FMT_MEDIUM },
+ { "short", 1, CMIT_FMT_SHORT },
+ { "email", 1, CMIT_FMT_EMAIL },
+ { "full", 5, CMIT_FMT_FULL },
+ { "fuller", 5, CMIT_FMT_FULLER },
+ { "oneline", 1, CMIT_FMT_ONELINE },
+ { "format:", 7, CMIT_FMT_USERFORMAT},
+};
+
+static char *user_format;
+
+enum cmit_fmt get_commit_format(const char *arg)
+{
+ int i;
+
+ if (!arg || !*arg)
+ return CMIT_FMT_DEFAULT;
+ if (*arg == '=')
+ arg++;
+ if (!prefixcmp(arg, "format:")) {
+ if (user_format)
+ free(user_format);
+ user_format = xstrdup(arg + 7);
+ return CMIT_FMT_USERFORMAT;
+ }
+ for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
+ if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
+ !strncmp(arg, cmt_fmts[i].n, strlen(arg)))
+ return cmt_fmts[i].v;
+ }
+
+ die("invalid --pretty format: %s", arg);
+}
+
+/*
+ * Generic support for pretty-printing the header
+ */
+static int get_one_line(const char *msg)
+{
+ int ret = 0;
+
+ for (;;) {
+ char c = *msg++;
+ if (!c)
+ break;
+ ret++;
+ if (c == '\n')
+ break;
+ }
+ return ret;
+}
+
+/* High bit set, or ISO-2022-INT */
+int non_ascii(int ch)
+{
+ ch = (ch & 0xff);
+ return ((ch & 0x80) || (ch == 0x1b));
+}
+
+static int is_rfc2047_special(char ch)
+{
+ return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
+}
+
+static void add_rfc2047(struct strbuf *sb, const char *line, int len,
+ const char *encoding)
+{
+ int i, last;
+
+ for (i = 0; i < len; i++) {
+ int ch = line[i];
+ if (non_ascii(ch))
+ goto needquote;
+ if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
+ goto needquote;
+ }
+ strbuf_add(sb, 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++) {
+ unsigned ch = line[i] & 0xFF;
+ /*
+ * 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);
+ strbuf_addf(sb, "=%02X", ch);
+ last = i + 1;
+ }
+ }
+ strbuf_add(sb, line + last, len - last);
+ strbuf_addstr(sb, "?=");
+}
+
+static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
+ const char *line, enum date_mode dmode,
+ const char *encoding)
+{
+ char *date;
+ int namelen;
+ unsigned long time;
+ int tz;
+ const char *filler = " ";
+
+ if (fmt == CMIT_FMT_ONELINE)
+ return;
+ date = strchr(line, '>');
+ if (!date)
+ return;
+ namelen = ++date - line;
+ time = strtoul(date, &date, 10);
+ tz = strtol(date, NULL, 10);
+
+ if (fmt == CMIT_FMT_EMAIL) {
+ char *name_tail = strchr(line, '<');
+ int display_name_length;
+ if (!name_tail)
+ return;
+ while (line < name_tail && isspace(name_tail[-1]))
+ name_tail--;
+ display_name_length = name_tail - line;
+ filler = "";
+ strbuf_addstr(sb, "From: ");
+ add_rfc2047(sb, line, display_name_length, encoding);
+ 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,
+ filler, namelen, line);
+ }
+ switch (fmt) {
+ case CMIT_FMT_MEDIUM:
+ strbuf_addf(sb, "Date: %s\n", show_date(time, tz, dmode));
+ 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));
+ break;
+ default:
+ /* notin' */
+ break;
+ }
+}
+
+static int is_empty_line(const char *line, int *len_p)
+{
+ int len = *len_p;
+ while (len && isspace(line[len-1]))
+ len--;
+ *len_p = len;
+ return !len;
+}
+
+static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
+ const struct commit *commit, int abbrev)
+{
+ struct commit_list *parent = commit->parents;
+
+ if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
+ !parent || !parent->next)
+ return;
+
+ strbuf_addstr(sb, "Merge:");
+
+ while (parent) {
+ struct commit *p = parent->item;
+ const char *hex = NULL;
+ const char *dots;
+ if (abbrev)
+ hex = find_unique_abbrev(p->object.sha1, abbrev);
+ if (!hex)
+ hex = sha1_to_hex(p->object.sha1);
+ dots = (abbrev && strlen(hex) != 40) ? "..." : "";
+ parent = parent->next;
+
+ strbuf_addf(sb, " %s%s", hex, dots);
+ }
+ strbuf_addch(sb, '\n');
+}
+
+static char *get_header(const struct commit *commit, const char *key)
+{
+ int key_len = strlen(key);
+ const char *line = commit->buffer;
+
+ for (;;) {
+ const char *eol = strchr(line, '\n'), *next;
+
+ if (line == eol)
+ return NULL;
+ if (!eol) {
+ eol = line + strlen(line);
+ next = NULL;
+ } else
+ next = eol + 1;
+ if (eol - line > key_len &&
+ !strncmp(line, key, key_len) &&
+ line[key_len] == ' ') {
+ return xmemdupz(line + key_len + 1, eol - line - key_len - 1);
+ }
+ line = next;
+ }
+}
+
+static char *replace_encoding_header(char *buf, const char *encoding)
+{
+ struct strbuf tmp;
+ size_t start, len;
+ char *cp = buf;
+
+ /* guess if there is an encoding header before a \n\n */
+ while (strncmp(cp, "encoding ", strlen("encoding "))) {
+ cp = strchr(cp, '\n');
+ if (!cp || *++cp == '\n')
+ return buf;
+ }
+ start = cp - buf;
+ cp = strchr(cp, '\n');
+ if (!cp)
+ return buf; /* should not happen but be defensive */
+ len = cp + 1 - (buf + start);
+
+ strbuf_init(&tmp, 0);
+ strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);
+ if (is_encoding_utf8(encoding)) {
+ /* we have re-coded to UTF-8; drop the header */
+ strbuf_remove(&tmp, start, len);
+ } else {
+ /* just replaces XXXX in 'encoding XXXX\n' */
+ strbuf_splice(&tmp, start + strlen("encoding "),
+ len - strlen("encoding \n"),
+ encoding, strlen(encoding));
+ }
+ return strbuf_detach(&tmp, NULL);
+}
+
+static char *logmsg_reencode(const struct commit *commit,
+ const char *output_encoding)
+{
+ static const char *utf8 = "utf-8";
+ const char *use_encoding;
+ char *encoding;
+ char *out;
+
+ if (!*output_encoding)
+ return NULL;
+ encoding = get_header(commit, "encoding");
+ use_encoding = encoding ? encoding : utf8;
+ if (!strcmp(use_encoding, output_encoding))
+ if (encoding) /* we'll strip encoding header later */
+ out = xstrdup(commit->buffer);
+ else
+ return NULL; /* nothing to do */
+ else
+ out = reencode_string(commit->buffer,
+ output_encoding, use_encoding);
+ if (out)
+ out = replace_encoding_header(out, output_encoding);
+
+ free(encoding);
+ return out;
+}
+
+static size_t format_person_part(struct strbuf *sb, char part,
+ const char *msg, int len)
+{
+ /* currently all placeholders have same length */
+ const int placeholder_len = 2;
+ int start, end, tz = 0;
+ unsigned long date = 0;
+ char *ep;
+
+ /* advance 'end' to point to email start delimiter */
+ for (end = 0; end < len && msg[end] != '<'; end++)
+ ; /* do nothing */
+
+ /*
+ * When end points at the '<' that we found, it should have
+ * matching '>' later, which means 'end' must be strictly
+ * below len - 1.
+ */
+ if (end >= len - 2)
+ goto skip;
+
+ if (part == 'n') { /* name */
+ while (end > 0 && isspace(msg[end - 1]))
+ end--;
+ strbuf_add(sb, msg, end);
+ return placeholder_len;
+ }
+ start = ++end; /* save email start position */
+
+ /* advance 'end' to point to email end delimiter */
+ for ( ; end < len && msg[end] != '>'; end++)
+ ; /* do nothing */
+
+ if (end >= len)
+ goto skip;
+
+ if (part == 'e') { /* email */
+ strbuf_add(sb, msg + start, end - start);
+ return placeholder_len;
+ }
+
+ /* advance 'start' to point to date start delimiter */
+ for (start = end + 1; start < len && isspace(msg[start]); start++)
+ ; /* do nothing */
+ if (start >= len)
+ goto skip;
+ date = strtoul(msg + start, &ep, 10);
+ if (msg + start == ep)
+ goto skip;
+
+ if (part == 't') { /* date, UNIX timestamp */
+ strbuf_add(sb, msg + start, ep - (msg + start));
+ return placeholder_len;
+ }
+
+ /* parse tz */
+ for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
+ ; /* do nothing */
+ if (start + 1 < len) {
+ tz = strtoul(msg + start + 1, NULL, 10);
+ if (msg[start] == '-')
+ tz = -tz;
+ }
+
+ switch (part) {
+ case 'd': /* date */
+ strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));
+ return placeholder_len;
+ case 'D': /* date, RFC2822 style */
+ strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
+ return placeholder_len;
+ case 'r': /* date, relative */
+ strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));
+ return placeholder_len;
+ case 'i': /* date, ISO 8601 */
+ strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));
+ return placeholder_len;
+ }
+
+skip:
+ /*
+ * bogus commit, 'sb' cannot be updated, but we still need to
+ * compute a valid return value.
+ */
+ if (part == 'n' || part == 'e' || part == 't' || part == 'd'
+ || part == 'D' || part == 'r' || part == 'i')
+ return placeholder_len;
+
+ return 0; /* unknown placeholder */
+}
+
+struct chunk {
+ size_t off;
+ size_t len;
+};
+
+struct format_commit_context {
+ const struct commit *commit;
+
+ /* These offsets are relative to the start of the commit message. */
+ int commit_header_parsed;
+ struct chunk subject;
+ struct chunk author;
+ struct chunk committer;
+ struct chunk encoding;
+ size_t body_off;
+
+ /* The following ones are relative to the result struct strbuf. */
+ struct chunk abbrev_commit_hash;
+ struct chunk abbrev_tree_hash;
+ struct chunk abbrev_parent_hashes;
+};
+
+static int add_again(struct strbuf *sb, struct chunk *chunk)
+{
+ if (chunk->len) {
+ strbuf_adddup(sb, chunk->off, chunk->len);
+ return 1;
+ }
+
+ /*
+ * We haven't seen this chunk before. Our caller is surely
+ * going to add it the hard way now. Remember the most likely
+ * start of the to-be-added chunk: the current end of the
+ * struct strbuf.
+ */
+ chunk->off = sb->len;
+ return 0;
+}
+
+static void parse_commit_header(struct format_commit_context *context)
+{
+ const char *msg = context->commit->buffer;
+ int i;
+ enum { HEADER, SUBJECT, BODY } state;
+
+ for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
+ int eol;
+ for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
+ ; /* do nothing */
+
+ if (state == SUBJECT) {
+ context->subject.off = i;
+ context->subject.len = eol - i;
+ i = eol;
+ }
+ if (i == eol) {
+ state++;
+ /* strip empty lines */
+ while (msg[eol] == '\n' && msg[eol + 1] == '\n')
+ eol++;
+ } else if (!prefixcmp(msg + i, "author ")) {
+ context->author.off = i + 7;
+ context->author.len = eol - i - 7;
+ } else if (!prefixcmp(msg + i, "committer ")) {
+ context->committer.off = i + 10;
+ context->committer.len = eol - i - 10;
+ } else if (!prefixcmp(msg + i, "encoding ")) {
+ context->encoding.off = i + 9;
+ context->encoding.len = eol - i - 9;
+ }
+ i = eol;
+ if (!msg[i])
+ break;
+ }
+ context->body_off = i;
+ context->commit_header_parsed = 1;
+}
+
+static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
+ void *context)
+{
+ struct format_commit_context *c = context;
+ const struct commit *commit = c->commit;
+ const char *msg = commit->buffer;
+ struct commit_list *p;
+
+ /* these are independent of the commit */
+ switch (placeholder[0]) {
+ case 'C':
+ if (!prefixcmp(placeholder + 1, "red")) {
+ strbuf_addstr(sb, "\033[31m");
+ return 4;
+ } else if (!prefixcmp(placeholder + 1, "green")) {
+ strbuf_addstr(sb, "\033[32m");
+ return 6;
+ } else if (!prefixcmp(placeholder + 1, "blue")) {
+ strbuf_addstr(sb, "\033[34m");
+ return 5;
+ } else if (!prefixcmp(placeholder + 1, "reset")) {
+ strbuf_addstr(sb, "\033[m");
+ return 6;
+ } else
+ return 0;
+ case 'n': /* newline */
+ strbuf_addch(sb, '\n');
+ return 1;
+ }
+
+ /* these depend on the commit */
+ if (!commit->object.parsed)
+ parse_object(commit->object.sha1);
+
+ switch (placeholder[0]) {
+ case 'H': /* commit hash */
+ strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
+ return 1;
+ case 'h': /* abbreviated commit hash */
+ if (add_again(sb, &c->abbrev_commit_hash))
+ return 1;
+ strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
+ DEFAULT_ABBREV));
+ c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
+ return 1;
+ case 'T': /* tree hash */
+ strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));
+ return 1;
+ case 't': /* abbreviated tree hash */
+ if (add_again(sb, &c->abbrev_tree_hash))
+ return 1;
+ strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
+ DEFAULT_ABBREV));
+ c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
+ return 1;
+ case 'P': /* parent hashes */
+ for (p = commit->parents; p; p = p->next) {
+ if (p != commit->parents)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1));
+ }
+ return 1;
+ case 'p': /* abbreviated parent hashes */
+ if (add_again(sb, &c->abbrev_parent_hashes))
+ return 1;
+ for (p = commit->parents; p; p = p->next) {
+ if (p != commit->parents)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, find_unique_abbrev(
+ p->item->object.sha1, DEFAULT_ABBREV));
+ }
+ c->abbrev_parent_hashes.len = sb->len -
+ c->abbrev_parent_hashes.off;
+ return 1;
+ case 'm': /* left/right/bottom */
+ strbuf_addch(sb, (commit->object.flags & BOUNDARY)
+ ? '-'
+ : (commit->object.flags & SYMMETRIC_LEFT)
+ ? '<'
+ : '>');
+ return 1;
+ }
+
+ /* For the rest we have to parse the commit header. */
+ if (!c->commit_header_parsed)
+ parse_commit_header(c);
+
+ switch (placeholder[0]) {
+ case 's': /* subject */
+ strbuf_add(sb, msg + c->subject.off, c->subject.len);
+ return 1;
+ case 'a': /* author ... */
+ return format_person_part(sb, placeholder[1],
+ msg + c->author.off, c->author.len);
+ case 'c': /* committer ... */
+ return format_person_part(sb, placeholder[1],
+ msg + c->committer.off, c->committer.len);
+ case 'e': /* encoding */
+ strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
+ return 1;
+ case 'b': /* body */
+ strbuf_addstr(sb, msg + c->body_off);
+ return 1;
+ }
+ return 0; /* unknown placeholder */
+}
+
+void format_commit_message(const struct commit *commit,
+ const void *format, struct strbuf *sb)
+{
+ struct format_commit_context context;
+
+ memset(&context, 0, sizeof(context));
+ context.commit = commit;
+ strbuf_expand(sb, format, format_commit_item, &context);
+}
+
+static void pp_header(enum cmit_fmt fmt,
+ int abbrev,
+ enum date_mode dmode,
+ const char *encoding,
+ const struct commit *commit,
+ const char **msg_p,
+ struct strbuf *sb)
+{
+ int parents_shown = 0;
+
+ for (;;) {
+ const char *line = *msg_p;
+ int linelen = get_one_line(*msg_p);
+
+ if (!linelen)
+ return;
+ *msg_p += linelen;
+
+ if (linelen == 1)
+ /* End of header */
+ return;
+
+ if (fmt == CMIT_FMT_RAW) {
+ strbuf_add(sb, line, linelen);
+ continue;
+ }
+
+ if (!memcmp(line, "parent ", 7)) {
+ if (linelen != 48)
+ die("bad parent line in commit");
+ continue;
+ }
+
+ if (!parents_shown) {
+ struct commit_list *parent;
+ int num;
+ for (parent = commit->parents, num = 0;
+ parent;
+ parent = parent->next, num++)
+ ;
+ /* with enough slop */
+ strbuf_grow(sb, num * 50 + 20);
+ add_merge_info(fmt, sb, commit, abbrev);
+ parents_shown = 1;
+ }
+
+ /*
+ * MEDIUM == DEFAULT shows only author with dates.
+ * FULL shows both authors but not dates.
+ * FULLER shows both authors and dates.
+ */
+ if (!memcmp(line, "author ", 7)) {
+ strbuf_grow(sb, linelen + 80);
+ add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+ }
+ if (!memcmp(line, "committer ", 10) &&
+ (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
+ strbuf_grow(sb, linelen + 80);
+ add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+ }
+ }
+}
+
+static void pp_title_line(enum cmit_fmt fmt,
+ const char **msg_p,
+ struct strbuf *sb,
+ const char *subject,
+ const char *after_subject,
+ const char *encoding,
+ int plain_non_ascii)
+{
+ struct strbuf title;
+
+ strbuf_init(&title, 80);
+
+ for (;;) {
+ const char *line = *msg_p;
+ int linelen = get_one_line(line);
+
+ *msg_p += linelen;
+ if (!linelen || is_empty_line(line, &linelen))
+ break;
+
+ strbuf_grow(&title, linelen + 2);
+ if (title.len) {
+ if (fmt == CMIT_FMT_EMAIL) {
+ strbuf_addch(&title, '\n');
+ }
+ strbuf_addch(&title, ' ');
+ }
+ strbuf_add(&title, line, linelen);
+ }
+
+ strbuf_grow(sb, title.len + 1024);
+ if (subject) {
+ strbuf_addstr(sb, subject);
+ add_rfc2047(sb, title.buf, title.len, encoding);
+ } else {
+ strbuf_addbuf(sb, &title);
+ }
+ strbuf_addch(sb, '\n');
+
+ if (plain_non_ascii) {
+ const char *header_fmt =
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=%s\n"
+ "Content-Transfer-Encoding: 8bit\n";
+ strbuf_addf(sb, header_fmt, encoding);
+ }
+ if (after_subject) {
+ strbuf_addstr(sb, after_subject);
+ }
+ if (fmt == CMIT_FMT_EMAIL) {
+ strbuf_addch(sb, '\n');
+ }
+ strbuf_release(&title);
+}
+
+static void pp_remainder(enum cmit_fmt fmt,
+ const char **msg_p,
+ struct strbuf *sb,
+ int indent)
+{
+ int first = 1;
+ for (;;) {
+ const char *line = *msg_p;
+ int linelen = get_one_line(line);
+ *msg_p += linelen;
+
+ if (!linelen)
+ break;
+
+ if (is_empty_line(line, &linelen)) {
+ if (first)
+ continue;
+ if (fmt == CMIT_FMT_SHORT)
+ break;
+ }
+ first = 0;
+
+ strbuf_grow(sb, linelen + indent + 20);
+ if (indent) {
+ memset(sb->buf + sb->len, ' ', indent);
+ strbuf_setlen(sb, sb->len + indent);
+ }
+ strbuf_add(sb, line, linelen);
+ strbuf_addch(sb, '\n');
+ }
+}
+
+void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
+ struct strbuf *sb, int abbrev,
+ const char *subject, const char *after_subject,
+ enum date_mode dmode, int plain_non_ascii)
+{
+ unsigned long beginning_of_body;
+ int indent = 4;
+ const char *msg = commit->buffer;
+ char *reencoded;
+ const char *encoding;
+
+ if (fmt == CMIT_FMT_USERFORMAT) {
+ format_commit_message(commit, user_format, sb);
+ return;
+ }
+
+ encoding = (git_log_output_encoding
+ ? git_log_output_encoding
+ : git_commit_encoding);
+ if (!encoding)
+ encoding = "utf-8";
+ reencoded = logmsg_reencode(commit, encoding);
+ if (reencoded) {
+ msg = reencoded;
+ }
+
+ if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+ indent = 0;
+
+ /* After-subject is used to pass in Content-Type: multipart
+ * MIME header; in that case we do not have to do the
+ * plaintext content type even if the commit message has
+ * non 7-bit ASCII character. Otherwise, check if we need
+ * to say this is not a 7-bit ASCII.
+ */
+ if (fmt == CMIT_FMT_EMAIL && !after_subject) {
+ int i, ch, in_body;
+
+ for (in_body = i = 0; (ch = msg[i]); i++) {
+ if (!in_body) {
+ /* author could be non 7-bit ASCII but
+ * the log may be so; skip over the
+ * header part first.
+ */
+ if (ch == '\n' && msg[i+1] == '\n')
+ in_body = 1;
+ }
+ else if (non_ascii(ch)) {
+ plain_non_ascii = 1;
+ break;
+ }
+ }
+ }
+
+ pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
+ if (fmt != CMIT_FMT_ONELINE && !subject) {
+ strbuf_addch(sb, '\n');
+ }
+
+ /* Skip excess blank lines at the beginning of body, if any... */
+ for (;;) {
+ int linelen = get_one_line(msg);
+ int ll = linelen;
+ if (!linelen)
+ break;
+ if (!is_empty_line(msg, &ll))
+ break;
+ msg += linelen;
+ }
+
+ /* These formats treat the title line specially. */
+ if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+ pp_title_line(fmt, &msg, sb, subject,
+ after_subject, encoding, plain_non_ascii);
+
+ beginning_of_body = sb->len;
+ if (fmt != CMIT_FMT_ONELINE)
+ pp_remainder(fmt, &msg, sb, indent);
+ strbuf_rtrim(sb);
+
+ /* Make sure there is an EOLN for the non-oneline case */
+ if (fmt != CMIT_FMT_ONELINE)
+ strbuf_addch(sb, '\n');
+
+ /*
+ * The caller may append additional body text in e-mail
+ * 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)
+ strbuf_addch(sb, '\n');
+ free(reencoded);
+}
diff --git a/progress.c b/progress.c
index 4344f4eed5..d19f80c0bb 100644
--- a/progress.c
+++ b/progress.c
@@ -1,6 +1,40 @@
+/*
+ * Simple text-based progress display module for GIT
+ *
+ * Copyright (c) 2007 by Nicolas Pitre <nico@cam.org>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
#include "git-compat-util.h"
#include "progress.h"
+#define TP_IDX_MAX 8
+
+struct throughput {
+ off_t curr_total;
+ off_t prev_total;
+ struct timeval prev_tv;
+ unsigned int avg_bytes;
+ unsigned int avg_misecs;
+ unsigned int last_bytes[TP_IDX_MAX];
+ unsigned int last_misecs[TP_IDX_MAX];
+ unsigned int idx;
+ char display[32];
+};
+
+struct progress {
+ const char *title;
+ int last_value;
+ unsigned total;
+ unsigned last_percent;
+ unsigned delay;
+ unsigned delayed_percent_treshold;
+ struct throughput *throughput;
+};
+
static volatile sig_atomic_t progress_update;
static void progress_interval(int signum)
@@ -35,10 +69,11 @@ static void clear_progress_signal(void)
progress_update = 0;
}
-int display_progress(struct progress *progress, unsigned n)
+static int display(struct progress *progress, unsigned n, const char *done)
{
+ const char *eol, *tp;
+
if (progress->delay) {
- char buf[80];
if (!progress_update || --progress->delay)
return 0;
if (progress->total) {
@@ -51,60 +86,173 @@ int display_progress(struct progress *progress, unsigned n)
return 0;
}
}
- if (snprintf(buf, sizeof(buf),
- progress->delayed_title, progress->total))
- fprintf(stderr, "%s\n", buf);
}
+
+ progress->last_value = n;
+ tp = (progress->throughput) ? progress->throughput->display : "";
+ eol = done ? done : " \r";
if (progress->total) {
unsigned percent = n * 100 / progress->total;
if (percent != progress->last_percent || progress_update) {
progress->last_percent = percent;
- fprintf(stderr, "%s%4u%% (%u/%u) done\r",
- progress->prefix, percent, n, progress->total);
+ fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
+ progress->title, percent, n,
+ progress->total, tp, eol);
+ fflush(stderr);
progress_update = 0;
- progress->need_lf = 1;
return 1;
}
} else if (progress_update) {
- fprintf(stderr, "%s%u\r", progress->prefix, n);
+ fprintf(stderr, "%s: %u%s%s", progress->title, n, tp, eol);
+ fflush(stderr);
progress_update = 0;
- progress->need_lf = 1;
return 1;
}
+
return 0;
}
-void start_progress(struct progress *progress, const char *title,
- const char *prefix, unsigned total)
+static void throughput_string(struct throughput *tp, off_t total,
+ unsigned int rate)
{
- char buf[80];
- progress->prefix = prefix;
- progress->total = total;
- progress->last_percent = -1;
- progress->delay = 0;
- progress->need_lf = 0;
- if (snprintf(buf, sizeof(buf), title, total))
- fprintf(stderr, "%s\n", buf);
- set_progress_signal();
+ int l = sizeof(tp->display);
+ if (total > 1 << 30) {
+ l -= snprintf(tp->display, l, ", %u.%2.2u GiB",
+ (int)(total >> 30),
+ (int)(total & ((1 << 30) - 1)) / 10737419);
+ } else if (total > 1 << 20) {
+ l -= snprintf(tp->display, l, ", %u.%2.2u MiB",
+ (int)(total >> 20),
+ ((int)(total & ((1 << 20) - 1)) * 100) >> 20);
+ } else if (total > 1 << 10) {
+ l -= snprintf(tp->display, l, ", %u.%2.2u KiB",
+ (int)(total >> 10),
+ ((int)(total & ((1 << 10) - 1)) * 100) >> 10);
+ } else {
+ l -= snprintf(tp->display, l, ", %u bytes", (int)total);
+ }
+ if (rate)
+ snprintf(tp->display + sizeof(tp->display) - l, l,
+ " | %u KiB/s", rate);
+}
+
+void display_throughput(struct progress *progress, off_t total)
+{
+ struct throughput *tp;
+ struct timeval tv;
+ unsigned int misecs;
+
+ if (!progress)
+ return;
+ tp = progress->throughput;
+
+ gettimeofday(&tv, NULL);
+
+ if (!tp) {
+ progress->throughput = tp = calloc(1, sizeof(*tp));
+ if (tp) {
+ tp->prev_total = tp->curr_total = total;
+ tp->prev_tv = tv;
+ }
+ return;
+ }
+ tp->curr_total = total;
+
+ /*
+ * We have x = bytes and y = microsecs. We want z = KiB/s:
+ *
+ * z = (x / 1024) / (y / 1000000)
+ * z = x / y * 1000000 / 1024
+ * z = x / (y * 1024 / 1000000)
+ * z = x / y'
+ *
+ * To simplify things we'll keep track of misecs, or 1024th of a sec
+ * obtained with:
+ *
+ * y' = y * 1024 / 1000000
+ * y' = y / (1000000 / 1024)
+ * y' = y / 977
+ */
+ misecs = (tv.tv_sec - tp->prev_tv.tv_sec) * 1024;
+ misecs += (int)(tv.tv_usec - tp->prev_tv.tv_usec) / 977;
+
+ if (misecs > 512) {
+ unsigned int count, rate;
+
+ count = total - tp->prev_total;
+ tp->prev_total = total;
+ tp->prev_tv = tv;
+ tp->avg_bytes += count;
+ tp->avg_misecs += misecs;
+ rate = tp->avg_bytes / tp->avg_misecs;
+ tp->avg_bytes -= tp->last_bytes[tp->idx];
+ tp->avg_misecs -= tp->last_misecs[tp->idx];
+ tp->last_bytes[tp->idx] = count;
+ tp->last_misecs[tp->idx] = misecs;
+ tp->idx = (tp->idx + 1) % TP_IDX_MAX;
+
+ throughput_string(tp, total, rate);
+ if (progress->last_value != -1 && progress_update)
+ display(progress, progress->last_value, NULL);
+ }
+}
+
+int display_progress(struct progress *progress, unsigned n)
+{
+ return progress ? display(progress, n, NULL) : 0;
}
-void start_progress_delay(struct progress *progress, const char *title,
- const char *prefix, unsigned total,
- unsigned percent_treshold, unsigned delay)
+struct progress *start_progress_delay(const char *title, unsigned total,
+ unsigned percent_treshold, unsigned delay)
{
- progress->prefix = prefix;
+ struct progress *progress = malloc(sizeof(*progress));
+ if (!progress) {
+ /* unlikely, but here's a good fallback */
+ fprintf(stderr, "%s...\n", title);
+ fflush(stderr);
+ return NULL;
+ }
+ progress->title = title;
progress->total = total;
+ progress->last_value = -1;
progress->last_percent = -1;
progress->delayed_percent_treshold = percent_treshold;
- progress->delayed_title = title;
progress->delay = delay;
- progress->need_lf = 0;
+ progress->throughput = NULL;
set_progress_signal();
+ return progress;
}
-void stop_progress(struct progress *progress)
+struct progress *start_progress(const char *title, unsigned total)
{
+ return start_progress_delay(title, total, 0, 0);
+}
+
+void stop_progress(struct progress **p_progress)
+{
+ stop_progress_msg(p_progress, "done");
+}
+
+void stop_progress_msg(struct progress **p_progress, const char *msg)
+{
+ struct progress *progress = *p_progress;
+ if (!progress)
+ return;
+ *p_progress = NULL;
+ if (progress->last_value != -1) {
+ /* Force the last update */
+ char buf[strlen(msg) + 5];
+ struct throughput *tp = progress->throughput;
+ if (tp) {
+ unsigned int rate = !tp->avg_misecs ? 0 :
+ tp->avg_bytes / tp->avg_misecs;
+ throughput_string(tp, tp->curr_total, rate);
+ }
+ progress_update = 1;
+ sprintf(buf, ", %s.\n", msg);
+ display(progress, progress->last_value, buf);
+ }
clear_progress_signal();
- if (progress->need_lf)
- fputc('\n', stderr);
+ free(progress->throughput);
+ free(progress);
}
diff --git a/progress.h b/progress.h
index a7c17ca7c4..611e4c4d42 100644
--- a/progress.h
+++ b/progress.h
@@ -1,22 +1,14 @@
#ifndef PROGRESS_H
#define PROGRESS_H
-struct progress {
- const char *prefix;
- unsigned total;
- unsigned last_percent;
- unsigned delay;
- unsigned delayed_percent_treshold;
- const char *delayed_title;
- int need_lf;
-};
+struct progress;
+void display_throughput(struct progress *progress, off_t total);
int display_progress(struct progress *progress, unsigned n);
-void start_progress(struct progress *progress, const char *title,
- const char *prefix, unsigned total);
-void start_progress_delay(struct progress *progress, const char *title,
- const char *prefix, unsigned total,
- unsigned percent_treshold, unsigned delay);
-void stop_progress(struct progress *progress);
+struct progress *start_progress(const char *title, unsigned total);
+struct progress *start_progress_delay(const char *title, unsigned total,
+ unsigned percent_treshold, unsigned delay);
+void stop_progress(struct progress **progress);
+void stop_progress_msg(struct progress **progress, const char *msg);
#endif
diff --git a/quote.c b/quote.c
index 482be05b7a..d061626c34 100644
--- a/quote.c
+++ b/quote.c
@@ -26,7 +26,7 @@ void sq_quote_buf(struct strbuf *dst, const char *src)
strbuf_addch(dst, '\'');
while (*src) {
- size_t len = strcspn(src, "'\\");
+ size_t len = strcspn(src, "'!");
strbuf_add(dst, src, len);
src += len;
while (need_bs_quote(*src)) {
@@ -56,20 +56,13 @@ void sq_quote_print(FILE *stream, const char *src)
fputc('\'', stream);
}
-void sq_quote_argv(struct strbuf *dst, const char** argv, int count,
- size_t maxlen)
+void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen)
{
int i;
- /* Count argv if needed. */
- if (count < 0) {
- for (count = 0; argv[count]; count++)
- ; /* just counting */
- }
-
/* Copy into destination buffer. */
- strbuf_grow(dst, 32 * count);
- for (i = 0; i < count; ++i) {
+ strbuf_grow(dst, 255);
+ for (i = 0; argv[i]; ++i) {
strbuf_addch(dst, ' ');
sq_quote_buf(dst, argv[i]);
if (maxlen && dst->len > maxlen)
@@ -131,7 +124,8 @@ static signed char const sq_lookup[256] = {
/* 0x80 */ /* set to 0 */
};
-static inline int sq_must_quote(char c) {
+static inline int sq_must_quote(char c)
+{
return sq_lookup[(unsigned char)c] + quote_path_fully > 0;
}
@@ -219,6 +213,22 @@ size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, int nodq)
return quote_c_style_counted(name, -1, sb, fp, nodq);
}
+void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path, int nodq)
+{
+ if (quote_c_style(prefix, NULL, NULL, 0) ||
+ quote_c_style(path, NULL, NULL, 0)) {
+ if (!nodq)
+ strbuf_addch(sb, '"');
+ quote_c_style(prefix, sb, NULL, 1);
+ quote_c_style(path, sb, NULL, 1);
+ if (!nodq)
+ strbuf_addch(sb, '"');
+ } else {
+ strbuf_addstr(sb, prefix);
+ strbuf_addstr(sb, path);
+ }
+}
+
void write_name_quoted(const char *name, FILE *fp, int terminator)
{
if (terminator) {
diff --git a/quote.h b/quote.h
index 4287990998..4da110ec01 100644
--- a/quote.h
+++ b/quote.h
@@ -31,8 +31,7 @@
extern void sq_quote_print(FILE *stream, const char *src);
extern void sq_quote_buf(struct strbuf *, const char *src);
-extern void sq_quote_argv(struct strbuf *, const char **argv, int count,
- size_t maxlen);
+extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen);
/* This unwraps what sq_quote() produces in place, but returns
* NULL if the input does not look like what sq_quote would have
@@ -42,6 +41,7 @@ extern char *sq_dequote(char *);
extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq);
+extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);
extern void write_name_quoted(const char *name, FILE *, int terminator);
extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,
diff --git a/reachable.c b/reachable.c
index 6383401e2d..3b1c18ff9b 100644
--- a/reachable.c
+++ b/reachable.c
@@ -15,6 +15,8 @@ static void process_blob(struct blob *blob,
{
struct object *obj = &blob->object;
+ if (!blob)
+ die("bad blob object");
if (obj->flags & SEEN)
return;
obj->flags |= SEEN;
@@ -39,6 +41,8 @@ static void process_tree(struct tree *tree,
struct name_entry entry;
struct name_path me;
+ if (!tree)
+ die("bad tree object");
if (obj->flags & SEEN)
return;
obj->flags |= SEEN;
@@ -79,7 +83,8 @@ static void process_tag(struct tag *tag, struct object_array *p, const char *nam
if (parse_tag(tag) < 0)
die("bad tag object %s", sha1_to_hex(obj->sha1));
- add_object(tag->tagged, p, NULL, name);
+ if (tag->tagged)
+ add_object(tag->tagged, p, NULL, name);
}
static void walk_commit_list(struct rev_info *revs)
@@ -150,7 +155,8 @@ static int add_one_reflog(const char *path, const unsigned char *sha1, int flag,
static void add_one_tree(const unsigned char *sha1, struct rev_info *revs)
{
struct tree *tree = lookup_tree(sha1);
- add_pending_object(revs, &tree->object, "");
+ if (tree)
+ add_pending_object(revs, &tree->object, "");
}
static void add_cache_tree(struct cache_tree *it, struct rev_info *revs)
@@ -176,7 +182,7 @@ static void add_cache_refs(struct rev_info *revs)
* lookup_blob() on them, to avoid populating the hash table
* with invalid information
*/
- if (S_ISGITLINK(ntohl(active_cache[i]->ce_mode)))
+ if (S_ISGITLINK(active_cache[i]->ce_mode))
continue;
lookup_blob(active_cache[i]->sha1);
@@ -215,6 +221,7 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
* Set up the revision walk - this will move all commits
* from the pending list to the commit walking list.
*/
- prepare_revision_walk(revs);
+ if (prepare_revision_walk(revs))
+ die("revision walk setup failed");
walk_commit_list(revs);
}
diff --git a/read-cache.c b/read-cache.c
index 56202d13df..e45f4b3d61 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -23,6 +23,90 @@
struct index_state the_index;
+static unsigned int hash_name(const char *name, int namelen)
+{
+ unsigned int hash = 0x123;
+
+ do {
+ unsigned char c = *name++;
+ hash = hash*101 + c;
+ } while (--namelen);
+ return hash;
+}
+
+static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
+{
+ void **pos;
+ unsigned int hash = hash_name(ce->name, ce_namelen(ce));
+
+ pos = insert_hash(hash, ce, &istate->name_hash);
+ if (pos) {
+ ce->next = *pos;
+ *pos = ce;
+ }
+}
+
+static void lazy_init_name_hash(struct index_state *istate)
+{
+ int nr;
+
+ if (istate->name_hash_initialized)
+ return;
+ for (nr = 0; nr < istate->cache_nr; nr++)
+ hash_index_entry(istate, istate->cache[nr]);
+ istate->name_hash_initialized = 1;
+}
+
+static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
+{
+ istate->cache[nr] = ce;
+ if (istate->name_hash_initialized)
+ hash_index_entry(istate, ce);
+}
+
+/*
+ * We don't actually *remove* it, we can just mark it invalid so that
+ * we won't find it in lookups.
+ *
+ * Not only would we have to search the lists (simple enough), but
+ * we'd also have to rehash other hash buckets in case this makes the
+ * hash bucket empty (common). So it's much better to just mark
+ * it.
+ */
+static void remove_hash_entry(struct index_state *istate, struct cache_entry *ce)
+{
+ ce->ce_flags |= CE_UNHASHED;
+}
+
+static void replace_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
+{
+ struct cache_entry *old = istate->cache[nr];
+
+ if (ce != old) {
+ remove_hash_entry(istate, old);
+ set_index_entry(istate, nr, ce);
+ }
+ istate->cache_changed = 1;
+}
+
+int index_name_exists(struct index_state *istate, const char *name, int namelen)
+{
+ unsigned int hash = hash_name(name, namelen);
+ struct cache_entry *ce;
+
+ lazy_init_name_hash(istate);
+ ce = lookup_hash(hash, &istate->name_hash);
+
+ while (ce) {
+ if (!(ce->ce_flags & CE_UNHASHED)) {
+ if (!cache_name_compare(name, namelen, ce->name, ce->ce_flags))
+ return 1;
+ }
+ ce = ce->next;
+ }
+ return 0;
+}
+
/*
* This only updates the "non-critical" parts of the directory
* cache, ie the parts that aren't tracked by GIT, and only used
@@ -30,20 +114,19 @@ struct index_state the_index;
*/
void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
{
- ce->ce_ctime.sec = htonl(st->st_ctime);
- ce->ce_mtime.sec = htonl(st->st_mtime);
-#ifdef USE_NSEC
- ce->ce_ctime.nsec = htonl(st->st_ctim.tv_nsec);
- ce->ce_mtime.nsec = htonl(st->st_mtim.tv_nsec);
-#endif
- ce->ce_dev = htonl(st->st_dev);
- ce->ce_ino = htonl(st->st_ino);
- ce->ce_uid = htonl(st->st_uid);
- ce->ce_gid = htonl(st->st_gid);
- ce->ce_size = htonl(st->st_size);
+ ce->ce_ctime = st->st_ctime;
+ ce->ce_mtime = st->st_mtime;
+ ce->ce_dev = st->st_dev;
+ ce->ce_ino = st->st_ino;
+ ce->ce_uid = st->st_uid;
+ ce->ce_gid = st->st_gid;
+ ce->ce_size = st->st_size;
if (assume_unchanged)
- ce->ce_flags |= htons(CE_VALID);
+ ce->ce_flags |= CE_VALID;
+
+ if (S_ISREG(st->st_mode))
+ ce_mark_uptodate(ce);
}
static int ce_compare_data(struct cache_entry *ce, struct stat *st)
@@ -116,7 +199,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
return DATA_CHANGED;
break;
case S_IFDIR:
- if (S_ISGITLINK(ntohl(ce->ce_mode)))
+ if (S_ISGITLINK(ce->ce_mode))
return 0;
default:
return TYPE_CHANGED;
@@ -128,14 +211,17 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
{
unsigned int changed = 0;
- switch (ntohl(ce->ce_mode) & S_IFMT) {
+ if (ce->ce_flags & CE_REMOVE)
+ return MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED;
+
+ switch (ce->ce_mode & S_IFMT) {
case S_IFREG:
changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0;
/* We consider only the owner x bit to be relevant for
* "mode changes"
*/
if (trust_executable_bit &&
- (0100 & (ntohl(ce->ce_mode) ^ st->st_mode)))
+ (0100 & (ce->ce_mode ^ st->st_mode)))
changed |= MODE_CHANGED;
break;
case S_IFLNK:
@@ -150,29 +236,17 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
changed |= DATA_CHANGED;
return changed;
default:
- die("internal error: ce_mode is %o", ntohl(ce->ce_mode));
+ die("internal error: ce_mode is %o", ce->ce_mode);
}
- if (ce->ce_mtime.sec != htonl(st->st_mtime))
- changed |= MTIME_CHANGED;
- if (ce->ce_ctime.sec != htonl(st->st_ctime))
- changed |= CTIME_CHANGED;
-
-#ifdef USE_NSEC
- /*
- * nsec seems unreliable - not all filesystems support it, so
- * as long as it is in the inode cache you get right nsec
- * but after it gets flushed, you get zero nsec.
- */
- if (ce->ce_mtime.nsec != htonl(st->st_mtim.tv_nsec))
+ if (ce->ce_mtime != (unsigned int) st->st_mtime)
changed |= MTIME_CHANGED;
- if (ce->ce_ctime.nsec != htonl(st->st_ctim.tv_nsec))
+ if (ce->ce_ctime != (unsigned int) st->st_ctime)
changed |= CTIME_CHANGED;
-#endif
- if (ce->ce_uid != htonl(st->st_uid) ||
- ce->ce_gid != htonl(st->st_gid))
+ if (ce->ce_uid != (unsigned int) st->st_uid ||
+ ce->ce_gid != (unsigned int) st->st_gid)
changed |= OWNER_CHANGED;
- if (ce->ce_ino != htonl(st->st_ino))
+ if (ce->ce_ino != (unsigned int) st->st_ino)
changed |= INODE_CHANGED;
#ifdef USE_STDEV
@@ -181,28 +255,35 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
* clients will have different views of what "device"
* the filesystem is on
*/
- if (ce->ce_dev != htonl(st->st_dev))
+ if (ce->ce_dev != (unsigned int) st->st_dev)
changed |= INODE_CHANGED;
#endif
- if (ce->ce_size != htonl(st->st_size))
+ if (ce->ce_size != (unsigned int) st->st_size)
changed |= DATA_CHANGED;
return changed;
}
+static int is_racy_timestamp(struct index_state *istate, struct cache_entry *ce)
+{
+ return (istate->timestamp &&
+ ((unsigned int)istate->timestamp) <= ce->ce_mtime);
+}
+
int ie_match_stat(struct index_state *istate,
- struct cache_entry *ce, struct stat *st, int options)
+ struct cache_entry *ce, struct stat *st,
+ unsigned int options)
{
unsigned int changed;
- int ignore_valid = options & 01;
- int assume_racy_is_modified = options & 02;
+ int ignore_valid = options & CE_MATCH_IGNORE_VALID;
+ int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
/*
* If it's marked as always valid in the index, it's
* valid whatever the checked-out copy says.
*/
- if (!ignore_valid && (ce->ce_flags & htons(CE_VALID)))
+ if (!ignore_valid && (ce->ce_flags & CE_VALID))
return 0;
changed = ce_match_stat_basic(ce, st);
@@ -223,9 +304,7 @@ int ie_match_stat(struct index_state *istate,
* whose mtime are the same as the index file timestamp more
* carefully than others.
*/
- if (!changed &&
- istate->timestamp &&
- istate->timestamp <= ntohl(ce->ce_mtime.sec)) {
+ if (!changed && is_racy_timestamp(istate, ce)) {
if (assume_racy_is_modified)
changed |= DATA_CHANGED;
else
@@ -236,10 +315,11 @@ int ie_match_stat(struct index_state *istate,
}
int ie_modified(struct index_state *istate,
- struct cache_entry *ce, struct stat *st, int really)
+ struct cache_entry *ce, struct stat *st, unsigned int options)
{
int changed, changed_fs;
- changed = ie_match_stat(istate, ce, st, really);
+
+ changed = ie_match_stat(istate, ce, st, options);
if (!changed)
return 0;
/*
@@ -253,7 +333,7 @@ int ie_modified(struct index_state *istate,
* the length field is zero. For other cases the ce_size
* should match the SHA1 recorded in the index entry.
*/
- if ((changed & DATA_CHANGED) && ce->ce_size != htonl(0))
+ if ((changed & DATA_CHANGED) && ce->ce_size != 0)
return changed;
changed_fs = ce_modified_check_fs(ce, st);
@@ -316,7 +396,7 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen)
while (last > first) {
int next = (last + first) >> 1;
struct cache_entry *ce = istate->cache[next];
- int cmp = cache_name_compare(name, namelen, ce->name, ntohs(ce->ce_flags));
+ int cmp = cache_name_compare(name, namelen, ce->name, ce->ce_flags);
if (!cmp)
return next;
if (cmp < 0) {
@@ -331,6 +411,9 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen)
/* Remove entry, return true if there are more entries to go.. */
int remove_index_entry_at(struct index_state *istate, int pos)
{
+ struct cache_entry *ce = istate->cache[pos];
+
+ remove_hash_entry(istate, ce);
istate->cache_changed = 1;
istate->cache_nr--;
if (pos >= istate->cache_nr)
@@ -385,6 +468,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
int size, namelen, pos;
struct stat st;
struct cache_entry *ce;
+ unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
if (lstat(path, &st))
die("%s: unable to stat (%s)", path, strerror(errno));
@@ -400,7 +484,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
size = cache_entry_size(namelen);
ce = xcalloc(1, size);
memcpy(ce->name, path, namelen);
- ce->ce_flags = htons(namelen);
+ ce->ce_flags = namelen;
fill_stat_cache_info(ce, &st);
if (trust_executable_bit && has_symlinks)
@@ -419,9 +503,10 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
pos = index_name_pos(istate, ce->name, namelen);
if (0 <= pos &&
!ce_stage(istate->cache[pos]) &&
- !ie_modified(istate, istate->cache[pos], &st, 1)) {
+ !ie_match_stat(istate, istate->cache[pos], &st, ce_option)) {
/* Nothing changed, really */
free(ce);
+ ce_mark_uptodate(istate->cache[pos]);
return 0;
}
@@ -578,7 +663,7 @@ static int has_file_name(struct index_state *istate,
continue;
if (p->name[len] != '/')
continue;
- if (!ce_stage(p) && !p->ce_mode)
+ if (p->ce_flags & CE_REMOVE)
continue;
retval = -1;
if (!ok_to_replace)
@@ -611,7 +696,7 @@ static int has_dir_name(struct index_state *istate,
}
len = slash - name;
- pos = index_name_pos(istate, name, ntohs(create_ce_flags(len, stage)));
+ pos = index_name_pos(istate, name, create_ce_flags(len, stage));
if (pos >= 0) {
/*
* Found one, but not so fast. This could
@@ -621,7 +706,7 @@ static int has_dir_name(struct index_state *istate,
* it is Ok to have a directory at the same
* path.
*/
- if (stage || istate->cache[pos]->ce_mode) {
+ if (!(istate->cache[pos]->ce_flags & CE_REMOVE)) {
retval = -1;
if (!ok_to_replace)
break;
@@ -643,8 +728,9 @@ static int has_dir_name(struct index_state *istate,
(p->name[len] != '/') ||
memcmp(p->name, name, len))
break; /* not our subdirectory */
- if (ce_stage(p) == stage && (stage || p->ce_mode))
- /* p is at the same stage as our entry, and
+ if (ce_stage(p) == stage && !(p->ce_flags & CE_REMOVE))
+ /*
+ * p is at the same stage as our entry, and
* is a subdirectory of what we are looking
* at, so we cannot have conflicts at our
* level or anything shorter.
@@ -674,7 +760,7 @@ static int check_file_directory_conflict(struct index_state *istate,
/*
* When ce is an "I am going away" entry, we allow it to be added
*/
- if (!ce_stage(ce) && !ce->ce_mode)
+ if (ce->ce_flags & CE_REMOVE)
return 0;
/*
@@ -699,12 +785,11 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
cache_tree_invalidate_path(istate->cache_tree, ce->name);
- pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
+ pos = index_name_pos(istate, ce->name, ce->ce_flags);
/* existing match? Just replace it. */
if (pos >= 0) {
- istate->cache_changed = 1;
- istate->cache[pos] = ce;
+ replace_index_entry(istate, pos, ce);
return 0;
}
pos = -pos-1;
@@ -731,7 +816,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
if (!ok_to_replace)
return error("'%s' appears as both a file and as a directory",
ce->name);
- pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
+ pos = index_name_pos(istate, ce->name, ce->ce_flags);
pos = -pos-1;
}
return pos + 1;
@@ -764,7 +849,7 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
memmove(istate->cache + pos + 1,
istate->cache + pos,
(istate->cache_nr - pos - 1) * sizeof(ce));
- istate->cache[pos] = ce;
+ set_index_entry(istate, pos, ce);
istate->cache_changed = 1;
return 0;
}
@@ -781,11 +866,16 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
* to link up the stat cache details with the proper files.
*/
static struct cache_entry *refresh_cache_ent(struct index_state *istate,
- struct cache_entry *ce, int really, int *err)
+ struct cache_entry *ce,
+ unsigned int options, int *err)
{
struct stat st;
struct cache_entry *updated;
int changed, size;
+ int ignore_valid = options & CE_MATCH_IGNORE_VALID;
+
+ if (ce_uptodate(ce))
+ return ce;
if (lstat(ce->name, &st) < 0) {
if (err)
@@ -793,16 +883,30 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
return NULL;
}
- changed = ie_match_stat(istate, ce, &st, really);
+ changed = ie_match_stat(istate, ce, &st, options);
if (!changed) {
- if (really && assume_unchanged &&
- !(ce->ce_flags & htons(CE_VALID)))
+ /*
+ * The path is unchanged. If we were told to ignore
+ * valid bit, then we did the actual stat check and
+ * found that the entry is unmodified. If the entry
+ * is not marked VALID, this is the place to mark it
+ * valid again, under "assume unchanged" mode.
+ */
+ if (ignore_valid && assume_unchanged &&
+ !(ce->ce_flags & CE_VALID))
; /* mark this one VALID again */
- else
+ else {
+ /*
+ * We do not mark the index itself "modified"
+ * because CE_UPTODATE flag is in-core only;
+ * we are not going to write this change out.
+ */
+ ce_mark_uptodate(ce);
return ce;
+ }
}
- if (ie_modified(istate, ce, &st, really)) {
+ if (ie_modified(istate, ce, &st, options)) {
if (err)
*err = EINVAL;
return NULL;
@@ -812,15 +916,15 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
updated = xmalloc(size);
memcpy(updated, ce, size);
fill_stat_cache_info(updated, &st);
-
- /* In this case, if really is not set, we should leave
- * CE_VALID bit alone. Otherwise, paths marked with
- * --no-assume-unchanged (i.e. things to be edited) will
- * reacquire CE_VALID bit automatically, which is not
- * really what we want.
+ /*
+ * If ignore_valid is not set, we should leave CE_VALID bit
+ * alone. Otherwise, paths marked with --no-assume-unchanged
+ * (i.e. things to be edited) will reacquire CE_VALID bit
+ * automatically, which is not really what we want.
*/
- if (!really && assume_unchanged && !(ce->ce_flags & htons(CE_VALID)))
- updated->ce_flags &= ~htons(CE_VALID);
+ if (!ignore_valid && assume_unchanged &&
+ !(ce->ce_flags & CE_VALID))
+ updated->ce_flags &= ~CE_VALID;
return updated;
}
@@ -833,6 +937,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
int allow_unmerged = (flags & REFRESH_UNMERGED) != 0;
int quiet = (flags & REFRESH_QUIET) != 0;
int not_new = (flags & REFRESH_IGNORE_MISSING) != 0;
+ unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0;
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce, *new;
@@ -854,7 +959,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
continue;
- new = refresh_cache_ent(istate, ce, really, &cache_errno);
+ new = refresh_cache_ent(istate, ce, options, &cache_errno);
if (new == ce)
continue;
if (!new) {
@@ -864,7 +969,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
/* If we are doing --really-refresh that
* means the index is not valid anymore.
*/
- ce->ce_flags &= ~htons(CE_VALID);
+ ce->ce_flags &= ~CE_VALID;
istate->cache_changed = 1;
}
if (quiet)
@@ -873,11 +978,8 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
has_errors = 1;
continue;
}
- istate->cache_changed = 1;
- /* You can NOT just free istate->cache[i] here, since it
- * might not be necessarily malloc()ed but can also come
- * from mmap(). */
- istate->cache[i] = new;
+
+ replace_index_entry(istate, i, new);
}
return has_errors;
}
@@ -926,16 +1028,58 @@ int read_index(struct index_state *istate)
return read_index_from(istate, get_index_file());
}
+static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce)
+{
+ size_t len;
+
+ ce->ce_ctime = ntohl(ondisk->ctime.sec);
+ ce->ce_mtime = ntohl(ondisk->mtime.sec);
+ ce->ce_dev = ntohl(ondisk->dev);
+ ce->ce_ino = ntohl(ondisk->ino);
+ ce->ce_mode = ntohl(ondisk->mode);
+ ce->ce_uid = ntohl(ondisk->uid);
+ ce->ce_gid = ntohl(ondisk->gid);
+ ce->ce_size = ntohl(ondisk->size);
+ /* On-disk flags are just 16 bits */
+ ce->ce_flags = ntohs(ondisk->flags);
+ hashcpy(ce->sha1, ondisk->sha1);
+
+ len = ce->ce_flags & CE_NAMEMASK;
+ if (len == CE_NAMEMASK)
+ len = strlen(ondisk->name);
+ /*
+ * NEEDSWORK: If the original index is crafted, this copy could
+ * go unchecked.
+ */
+ memcpy(ce->name, ondisk->name, len + 1);
+}
+
+static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
+{
+ long per_entry;
+
+ per_entry = sizeof(struct cache_entry) - sizeof(struct ondisk_cache_entry);
+
+ /*
+ * Alignment can cause differences. This should be "alignof", but
+ * since that's a gcc'ism, just use the size of a pointer.
+ */
+ per_entry += sizeof(void *);
+ return ondisk_size + entries*per_entry;
+}
+
/* remember to discard_cache() before reading a different cache! */
int read_index_from(struct index_state *istate, const char *path)
{
int fd, i;
struct stat st;
- unsigned long offset;
+ unsigned long src_offset, dst_offset;
struct cache_header *hdr;
+ void *mmap;
+ size_t mmap_size;
errno = EBUSY;
- if (istate->mmap)
+ if (istate->alloc)
return istate->cache_nr;
errno = ENOENT;
@@ -951,31 +1095,47 @@ int read_index_from(struct index_state *istate, const char *path)
die("cannot stat the open index (%s)", strerror(errno));
errno = EINVAL;
- istate->mmap_size = xsize_t(st.st_size);
- if (istate->mmap_size < sizeof(struct cache_header) + 20)
+ mmap_size = xsize_t(st.st_size);
+ if (mmap_size < sizeof(struct cache_header) + 20)
die("index file smaller than expected");
- istate->mmap = xmmap(NULL, istate->mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
close(fd);
+ if (mmap == MAP_FAILED)
+ die("unable to map index file");
- hdr = istate->mmap;
- if (verify_hdr(hdr, istate->mmap_size) < 0)
+ hdr = mmap;
+ if (verify_hdr(hdr, mmap_size) < 0)
goto unmap;
istate->cache_nr = ntohl(hdr->hdr_entries);
istate->cache_alloc = alloc_nr(istate->cache_nr);
istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
- offset = sizeof(*hdr);
+ /*
+ * The disk format is actually larger than the in-memory format,
+ * due to space for nsec etc, so even though the in-memory one
+ * has room for a few more flags, we can allocate using the same
+ * index size
+ */
+ istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr));
+
+ src_offset = sizeof(*hdr);
+ dst_offset = 0;
for (i = 0; i < istate->cache_nr; i++) {
+ struct ondisk_cache_entry *disk_ce;
struct cache_entry *ce;
- ce = (struct cache_entry *)((char *)(istate->mmap) + offset);
- offset = offset + ce_size(ce);
- istate->cache[i] = ce;
+ disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
+ ce = (struct cache_entry *)((char *)istate->alloc + dst_offset);
+ convert_from_disk(disk_ce, ce);
+ set_index_entry(istate, i, ce);
+
+ src_offset += ondisk_ce_size(ce);
+ dst_offset += ce_size(ce);
}
istate->timestamp = st.st_mtime;
- while (offset <= istate->mmap_size - 20 - 8) {
+ while (src_offset <= mmap_size - 20 - 8) {
/* After an array of active_nr index entries,
* there can be arbitrary number of extended
* sections, each of which is prefixed with
@@ -983,40 +1143,37 @@ int read_index_from(struct index_state *istate, const char *path)
* in 4-byte network byte order.
*/
unsigned long extsize;
- memcpy(&extsize, (char *)(istate->mmap) + offset + 4, 4);
+ memcpy(&extsize, (char *)mmap + src_offset + 4, 4);
extsize = ntohl(extsize);
if (read_index_extension(istate,
- ((const char *) (istate->mmap)) + offset,
- (char *) (istate->mmap) + offset + 8,
+ (const char *) mmap + src_offset,
+ (char *) mmap + src_offset + 8,
extsize) < 0)
goto unmap;
- offset += 8;
- offset += extsize;
+ src_offset += 8;
+ src_offset += extsize;
}
+ munmap(mmap, mmap_size);
return istate->cache_nr;
unmap:
- munmap(istate->mmap, istate->mmap_size);
+ munmap(mmap, mmap_size);
errno = EINVAL;
die("index file corrupt");
}
int discard_index(struct index_state *istate)
{
- int ret;
-
istate->cache_nr = 0;
istate->cache_changed = 0;
istate->timestamp = 0;
+ free_hash(&istate->name_hash);
cache_tree_free(&(istate->cache_tree));
- if (istate->mmap == NULL)
- return 0;
- ret = munmap(istate->mmap, istate->mmap_size);
- istate->mmap = NULL;
- istate->mmap_size = 0;
+ free(istate->alloc);
+ istate->alloc = NULL;
/* no need to throw away allocated active_cache */
- return ret;
+ return 0;
}
#define WRITE_BUFFER_SIZE 8192
@@ -1128,10 +1285,32 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
* file, and never calls us, so the cached size information
* for "frotz" stays 6 which does not match the filesystem.
*/
- ce->ce_size = htonl(0);
+ ce->ce_size = 0;
}
}
+static int ce_write_entry(SHA_CTX *c, int fd, struct cache_entry *ce)
+{
+ int size = ondisk_ce_size(ce);
+ struct ondisk_cache_entry *ondisk = xcalloc(1, size);
+
+ ondisk->ctime.sec = htonl(ce->ce_ctime);
+ ondisk->ctime.nsec = 0;
+ ondisk->mtime.sec = htonl(ce->ce_mtime);
+ ondisk->mtime.nsec = 0;
+ ondisk->dev = htonl(ce->ce_dev);
+ ondisk->ino = htonl(ce->ce_ino);
+ ondisk->mode = htonl(ce->ce_mode);
+ ondisk->uid = htonl(ce->ce_uid);
+ ondisk->gid = htonl(ce->ce_gid);
+ ondisk->size = htonl(ce->ce_size);
+ hashcpy(ondisk->sha1, ce->sha1);
+ ondisk->flags = htons(ce->ce_flags);
+ memcpy(ondisk->name, ce->name, ce_namelen(ce));
+
+ return ce_write(c, fd, ondisk, size);
+}
+
int write_index(struct index_state *istate, int newfd)
{
SHA_CTX c;
@@ -1141,7 +1320,7 @@ int write_index(struct index_state *istate, int newfd)
int entries = istate->cache_nr;
for (i = removed = 0; i < entries; i++)
- if (!cache[i]->ce_mode)
+ if (cache[i]->ce_flags & CE_REMOVE)
removed++;
hdr.hdr_signature = htonl(CACHE_SIGNATURE);
@@ -1154,12 +1333,11 @@ int write_index(struct index_state *istate, int newfd)
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
- if (!ce->ce_mode)
+ if (ce->ce_flags & CE_REMOVE)
continue;
- if (istate->timestamp &&
- istate->timestamp <= ntohl(ce->ce_mtime.sec))
+ if (is_racy_timestamp(istate, ce))
ce_smudge_racily_clean_entry(ce);
- if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
+ if (ce_write_entry(&c, newfd, ce) < 0)
return -1;
}
diff --git a/receive-pack.c b/receive-pack.c
index d3c422be58..3267495832 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -165,8 +165,9 @@ static const char *update(struct command *cmd)
unsigned char *new_sha1 = cmd->new_sha1;
struct ref_lock *lock;
- if (!prefixcmp(name, "refs/") && check_ref_format(name + 5)) {
- error("refusing to create funny ref '%s' locally", name);
+ /* only refs/... are allowed */
+ if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+ error("refusing to create funny ref '%s' remotely", name);
return "funny refname";
}
@@ -178,11 +179,21 @@ static const char *update(struct command *cmd)
if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
!is_null_sha1(old_sha1) &&
!prefixcmp(name, "refs/heads/")) {
+ struct object *old_object, *new_object;
struct commit *old_commit, *new_commit;
struct commit_list *bases, *ent;
- old_commit = (struct commit *)parse_object(old_sha1);
- new_commit = (struct commit *)parse_object(new_sha1);
+ old_object = parse_object(old_sha1);
+ new_object = parse_object(new_sha1);
+
+ if (!old_object || !new_object ||
+ old_object->type != OBJ_COMMIT ||
+ new_object->type != OBJ_COMMIT) {
+ error("bad sha1 objects for %s", name);
+ return "bad ref";
+ }
+ old_commit = (struct commit *)old_object;
+ new_commit = (struct commit *)new_object;
bases = get_merge_bases(old_commit, new_commit, 1);
for (ent = bases; ent; ent = ent->next)
if (!hashcmp(old_sha1, ent->item->object.sha1))
@@ -200,12 +211,14 @@ static const char *update(struct command *cmd)
}
if (is_null_sha1(new_sha1)) {
+ if (!parse_object(old_sha1)) {
+ warning ("Allowing deletion of corrupt ref.");
+ old_sha1 = NULL;
+ }
if (delete_ref(name, old_sha1)) {
error("failed to delete %s", name);
return "failed to delete";
}
- fprintf(stderr, "%s: %s -> deleted\n", name,
- sha1_to_hex(old_sha1));
return NULL; /* good */
}
else {
@@ -217,8 +230,6 @@ static const char *update(struct command *cmd)
if (write_ref_sha1(lock, new_sha1, "push")) {
return "failed to write"; /* error() already called */
}
- fprintf(stderr, "%s: %s -> %s\n", name,
- sha1_to_hex(old_sha1), sha1_to_hex(new_sha1));
return NULL; /* good */
}
}
@@ -382,9 +393,8 @@ static const char *unpack(void)
}
} else {
const char *keeper[6];
- int s, len, status;
+ int s, status;
char keep_arg[256];
- char packname[46];
struct child_process ip;
s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
@@ -403,26 +413,7 @@ static const char *unpack(void)
ip.git_cmd = 1;
if (start_command(&ip))
return "index-pack fork failed";
-
- /*
- * The first thing we expects from index-pack's output
- * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
- * %40s is the newly created pack SHA1 name. In the "keep"
- * case, we need it to remove the corresponding .keep file
- * later on. If we don't get that then tough luck with it.
- */
- for (len = 0;
- len < 46 && (s = xread(ip.out, packname+len, 46-len)) > 0;
- len += s);
- if (len == 46 && packname[45] == '\n' &&
- memcmp(packname, "keep\t", 5) == 0) {
- char path[PATH_MAX];
- packname[45] = 0;
- snprintf(path, sizeof(path), "%s/pack/pack-%s.keep",
- get_object_directory(), packname + 5);
- pack_lockfile = xstrdup(path);
- }
-
+ pack_lockfile = index_pack_lockfile(ip.out);
status = finish_command(&ip);
if (!status) {
reprepare_packed_git();
diff --git a/refs.c b/refs.c
index 07e260c8a1..67d2a502af 100644
--- a/refs.c
+++ b/refs.c
@@ -2,6 +2,7 @@
#include "refs.h"
#include "object.h"
#include "tag.h"
+#include "dir.h"
/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
#define REF_KNOWS_PEELED 04
@@ -579,18 +580,6 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data)
return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
}
-/* NEEDSWORK: This is only used by ssh-upload and it should go; the
- * caller should do resolve_ref or read_ref like everybody else. Or
- * maybe everybody else should use get_ref_sha1() instead of doing
- * read_ref().
- */
-int get_ref_sha1(const char *ref, unsigned char *sha1)
-{
- if (check_ref_format(ref))
- return -1;
- return read_ref(mkpath("refs/%s", ref), sha1);
-}
-
/*
* Make sure "ref" is something reasonable to have under ".git/refs/";
* We do not like it if:
@@ -624,34 +613,70 @@ int check_ref_format(const char *ref)
while ((ch = *cp++) == '/')
; /* tolerate duplicated slashes */
if (!ch)
- return -1; /* should not end with slashes */
+ /* should not end with slashes */
+ return CHECK_REF_FORMAT_ERROR;
/* we are at the beginning of the path component */
if (ch == '.')
- return -1;
+ return CHECK_REF_FORMAT_ERROR;
bad_type = bad_ref_char(ch);
if (bad_type) {
- return (bad_type == 2 && !*cp) ? -3 : -1;
+ return (bad_type == 2 && !*cp)
+ ? CHECK_REF_FORMAT_WILDCARD
+ : CHECK_REF_FORMAT_ERROR;
}
/* scan the rest of the path component */
while ((ch = *cp++) != 0) {
bad_type = bad_ref_char(ch);
if (bad_type) {
- return (bad_type == 2 && !*cp) ? -3 : -1;
+ return (bad_type == 2 && !*cp)
+ ? CHECK_REF_FORMAT_WILDCARD
+ : CHECK_REF_FORMAT_ERROR;
}
if (ch == '/')
break;
if (ch == '.' && *cp == '.')
- return -1;
+ return CHECK_REF_FORMAT_ERROR;
}
level++;
if (!ch) {
if (level < 2)
- return -2; /* at least of form "heads/blah" */
- return 0;
+ return CHECK_REF_FORMAT_ONELEVEL;
+ return CHECK_REF_FORMAT_OK;
+ }
+ }
+}
+
+const char *ref_rev_parse_rules[] = {
+ "%.*s",
+ "refs/%.*s",
+ "refs/tags/%.*s",
+ "refs/heads/%.*s",
+ "refs/remotes/%.*s",
+ "refs/remotes/%.*s/HEAD",
+ NULL
+};
+
+const char *ref_fetch_rules[] = {
+ "%.*s",
+ "refs/%.*s",
+ "refs/heads/%.*s",
+ NULL
+};
+
+int refname_match(const char *abbrev_name, const char *full_name, const char **rules)
+{
+ const char **p;
+ const int abbrev_name_len = strlen(abbrev_name);
+
+ for (p = rules; *p; p++) {
+ if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
+ return 1;
}
}
+
+ return 0;
}
static struct ref_lock *verify_lock(struct ref_lock *lock,
@@ -671,57 +696,23 @@ static struct ref_lock *verify_lock(struct ref_lock *lock,
return lock;
}
-static int remove_empty_dir_recursive(char *path, int len)
-{
- DIR *dir = opendir(path);
- struct dirent *e;
- int ret = 0;
-
- if (!dir)
- return -1;
- if (path[len-1] != '/')
- path[len++] = '/';
- while ((e = readdir(dir)) != NULL) {
- struct stat st;
- int namlen;
- if ((e->d_name[0] == '.') &&
- ((e->d_name[1] == 0) ||
- ((e->d_name[1] == '.') && e->d_name[2] == 0)))
- continue; /* "." and ".." */
-
- namlen = strlen(e->d_name);
- if ((len + namlen < PATH_MAX) &&
- strcpy(path + len, e->d_name) &&
- !lstat(path, &st) &&
- S_ISDIR(st.st_mode) &&
- !remove_empty_dir_recursive(path, len + namlen))
- continue; /* happy */
-
- /* path too long, stat fails, or non-directory still exists */
- ret = -1;
- break;
- }
- closedir(dir);
- if (!ret) {
- path[len] = 0;
- ret = rmdir(path);
- }
- return ret;
-}
-
-static int remove_empty_directories(char *file)
+static int remove_empty_directories(const char *file)
{
/* we want to create a file but there is a directory there;
* if that is an empty directory (or a directory that contains
* only empty directories), remove them.
*/
- char path[PATH_MAX];
- int len = strlen(file);
+ struct strbuf path;
+ int result;
- if (len >= PATH_MAX) /* path too long ;-) */
- return -1;
- strcpy(path, file);
- return remove_empty_dir_recursive(path, len);
+ strbuf_init(&path, 20);
+ strbuf_addstr(&path, file);
+
+ result = remove_dir_recursively(&path, 1);
+
+ strbuf_release(&path);
+
+ return result;
}
static int is_refname_available(const char *ref, const char *oldref,
@@ -830,9 +821,13 @@ struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags)
{
- if (check_ref_format(ref) == -1)
+ switch (check_ref_format(ref)) {
+ default:
return NULL;
- return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
+ case 0:
+ case CHECK_REF_FORMAT_ONELEVEL:
+ return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
+ }
}
static struct lock_file packlock;
@@ -869,7 +864,6 @@ static int repack_without_ref(const char *refname)
die("too long a refname '%s'", list->name);
write_or_die(fd, line, len);
}
- close(fd);
return commit_lock_file(&packlock);
}
@@ -1024,14 +1018,27 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
return 1;
}
+static int close_ref(struct ref_lock *lock)
+{
+ if (close_lock_file(lock->lk))
+ return -1;
+ lock->lock_fd = -1;
+ return 0;
+}
+
+static int commit_ref(struct ref_lock *lock)
+{
+ if (commit_lock_file(lock->lk))
+ return -1;
+ lock->lock_fd = -1;
+ return 0;
+}
+
void unlock_ref(struct ref_lock *lock)
{
- if (lock->lock_fd >= 0) {
- close(lock->lock_fd);
- /* Do not free lock->lk -- atexit() still looks at them */
- if (lock->lk)
- rollback_lock_file(lock->lk);
- }
+ /* Do not free lock->lk -- atexit() still looks at them */
+ if (lock->lk)
+ rollback_lock_file(lock->lk);
free(lock->ref_name);
free(lock->orig_ref_name);
free(lock);
@@ -1108,7 +1115,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
adjust_shared_perm(log_file);
msglen = msg ? strlen(msg) : 0;
- committer = git_committer_info(-1);
+ committer = git_committer_info(0);
maxlen = strlen(committer) + msglen + 100;
logrec = xmalloc(maxlen);
len = sprintf(logrec, "%s %s %s\n",
@@ -1124,10 +1131,16 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
return 0;
}
+static int is_branch(const char *refname)
+{
+ return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
+}
+
int write_ref_sha1(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg)
{
static char term = '\n';
+ struct object *o;
if (!lock)
return -1;
@@ -1135,9 +1148,22 @@ int write_ref_sha1(struct ref_lock *lock,
unlock_ref(lock);
return 0;
}
+ o = parse_object(sha1);
+ if (!o) {
+ error("Trying to write ref %s with nonexistant object %s",
+ lock->ref_name, sha1_to_hex(sha1));
+ unlock_ref(lock);
+ return -1;
+ }
+ if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
+ error("Trying to write non-commit object %s to branch %s",
+ sha1_to_hex(sha1), lock->ref_name);
+ unlock_ref(lock);
+ return -1;
+ }
if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
write_in_full(lock->lock_fd, &term, 1) != 1
- || close(lock->lock_fd) < 0) {
+ || close_ref(lock) < 0) {
error("Couldn't write %s", lock->lk->filename);
unlock_ref(lock);
return -1;
@@ -1170,12 +1196,11 @@ int write_ref_sha1(struct ref_lock *lock,
!strcmp(head_ref, lock->ref_name))
log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
}
- if (commit_lock_file(lock->lk)) {
+ if (commit_ref(lock)) {
error("Couldn't set %s", lock->ref_name);
unlock_ref(lock);
return -1;
}
- lock->lock_fd = -1;
unlock_ref(lock);
return 0;
}
@@ -1478,3 +1503,11 @@ int update_ref(const char *action, const char *refname,
}
return 0;
}
+
+struct ref *find_ref_by_name(struct ref *list, const char *name)
+{
+ for ( ; list; list = list->next)
+ if (!strcmp(list->name, name))
+ return list;
+ return NULL;
+}
diff --git a/refs.h b/refs.h
index 6eb98a4caf..9cd16f8295 100644
--- a/refs.h
+++ b/refs.h
@@ -26,9 +26,6 @@ extern int for_each_remote_ref(each_ref_fn, void *);
extern int peel_ref(const char *, unsigned char *);
-/** Reads the refs file specified into sha1 **/
-extern int get_ref_sha1(const char *ref, unsigned char *sha1);
-
/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
@@ -55,7 +52,10 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
*/
extern int for_each_reflog(each_ref_fn, void *);
-/** Returns 0 if target has the right format for a ref. **/
+#define CHECK_REF_FORMAT_OK 0
+#define CHECK_REF_FORMAT_ERROR (-1)
+#define CHECK_REF_FORMAT_ONELEVEL (-2)
+#define CHECK_REF_FORMAT_WILDCARD (-3)
extern int check_ref_format(const char *target);
/** rename ref, return 0 on success **/
diff --git a/remote.c b/remote.c
index bb774d0bcc..6b56473f5b 100644
--- a/remote.c
+++ b/remote.c
@@ -5,6 +5,12 @@
static struct remote **remotes;
static int allocated_remotes;
+static struct branch **branches;
+static int allocated_branches;
+
+static struct branch *current_branch;
+static const char *default_remote_name;
+
#define BUF_SIZE (2048)
static char buffer[BUF_SIZE];
@@ -26,13 +32,13 @@ static void add_fetch_refspec(struct remote *remote, const char *ref)
remote->fetch_refspec_nr = nr;
}
-static void add_uri(struct remote *remote, const char *uri)
+static void add_url(struct remote *remote, const char *url)
{
- int nr = remote->uri_nr + 1;
- remote->uri =
- xrealloc(remote->uri, nr * sizeof(char *));
- remote->uri[nr-1] = uri;
- remote->uri_nr = nr;
+ int nr = remote->url_nr + 1;
+ remote->url =
+ xrealloc(remote->url, nr * sizeof(char *));
+ remote->url[nr-1] = url;
+ remote->url_nr = nr;
}
static struct remote *make_remote(const char *name, int len)
@@ -67,6 +73,54 @@ static struct remote *make_remote(const char *name, int len)
return remotes[empty];
}
+static void add_merge(struct branch *branch, const char *name)
+{
+ int nr = branch->merge_nr + 1;
+ branch->merge_name =
+ xrealloc(branch->merge_name, nr * sizeof(char *));
+ branch->merge_name[nr-1] = name;
+ branch->merge_nr = nr;
+}
+
+static struct branch *make_branch(const char *name, int len)
+{
+ int i, empty = -1;
+ char *refname;
+
+ for (i = 0; i < allocated_branches; i++) {
+ if (!branches[i]) {
+ if (empty < 0)
+ empty = i;
+ } else {
+ if (len ? (!strncmp(name, branches[i]->name, len) &&
+ !branches[i]->name[len]) :
+ !strcmp(name, branches[i]->name))
+ return branches[i];
+ }
+ }
+
+ if (empty < 0) {
+ empty = allocated_branches;
+ allocated_branches += allocated_branches ? allocated_branches : 1;
+ branches = xrealloc(branches,
+ sizeof(*branches) * allocated_branches);
+ memset(branches + empty, 0,
+ (allocated_branches - empty) * sizeof(*branches));
+ }
+ branches[empty] = xcalloc(1, sizeof(struct branch));
+ if (len)
+ branches[empty]->name = xstrndup(name, len);
+ else
+ branches[empty]->name = xstrdup(name);
+ refname = malloc(strlen(name) + strlen("refs/heads/") + 1);
+ strcpy(refname, "refs/heads/");
+ strcpy(refname + strlen("refs/heads/"),
+ branches[empty]->name);
+ branches[empty]->refname = refname;
+
+ return branches[empty];
+}
+
static void read_remotes_file(struct remote *remote)
{
FILE *f = fopen(git_path("remotes/%s", remote->name), "r");
@@ -100,7 +154,7 @@ static void read_remotes_file(struct remote *remote)
switch (value_list) {
case 0:
- add_uri(remote, xstrdup(s));
+ add_url(remote, xstrdup(s));
break;
case 1:
add_push_refspec(remote, xstrdup(s));
@@ -116,6 +170,8 @@ static void read_remotes_file(struct remote *remote)
static void read_branches_file(struct remote *remote)
{
const char *slash = strchr(remote->name, '/');
+ char *frag;
+ char *branch;
int n = slash ? slash - remote->name : 1000;
FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
char *s, *p;
@@ -141,23 +197,44 @@ static void read_branches_file(struct remote *remote)
strcpy(p, s);
if (slash)
strcat(p, slash);
- add_uri(remote, p);
+ frag = strchr(p, '#');
+ if (frag) {
+ *(frag++) = '\0';
+ branch = xmalloc(strlen(frag) + 12);
+ strcpy(branch, "refs/heads/");
+ strcat(branch, frag);
+ } else {
+ branch = "refs/heads/master";
+ }
+ add_url(remote, p);
+ add_fetch_refspec(remote, branch);
+ remote->fetch_tags = 1; /* always auto-follow */
}
-static char *default_remote_name = NULL;
-static const char *current_branch = NULL;
-static int current_branch_len = 0;
-
static int handle_config(const char *key, const char *value)
{
const char *name;
const char *subkey;
struct remote *remote;
- if (!prefixcmp(key, "branch.") && current_branch &&
- !strncmp(key + 7, current_branch, current_branch_len) &&
- !strcmp(key + 7 + current_branch_len, ".remote")) {
- free(default_remote_name);
- default_remote_name = xstrdup(value);
+ struct branch *branch;
+ if (!prefixcmp(key, "branch.")) {
+ name = key + 7;
+ subkey = strrchr(name, '.');
+ if (!subkey)
+ return 0;
+ branch = make_branch(name, subkey - name);
+ if (!strcmp(subkey, ".remote")) {
+ if (!value)
+ return config_error_nonbool(key);
+ branch->remote_name = xstrdup(value);
+ if (branch == current_branch)
+ default_remote_name = branch->remote_name;
+ } else if (!strcmp(subkey, ".merge")) {
+ if (!value)
+ return config_error_nonbool(key);
+ add_merge(branch, xstrdup(value));
+ }
+ return 0;
}
if (prefixcmp(key, "remote."))
return 0;
@@ -186,7 +263,7 @@ static int handle_config(const char *key, const char *value)
return 0; /* ignore unknown booleans */
}
if (!strcmp(subkey, ".url")) {
- add_uri(remote, xstrdup(value));
+ add_url(remote, xstrdup(value));
} else if (!strcmp(subkey, ".push")) {
add_push_refspec(remote, xstrdup(value));
} else if (!strcmp(subkey, ".fetch")) {
@@ -196,6 +273,16 @@ static int handle_config(const char *key, const char *value)
remote->receivepack = xstrdup(value);
else
error("more than one receivepack given, using the first");
+ } else if (!strcmp(subkey, ".uploadpack")) {
+ if (!remote->uploadpack)
+ remote->uploadpack = xstrdup(value);
+ else
+ error("more than one uploadpack given, using the first");
+ } else if (!strcmp(subkey, ".tagopt")) {
+ if (!strcmp(value, "--no-tags"))
+ remote->fetch_tags = -1;
+ } else if (!strcmp(subkey, ".proxy")) {
+ remote->http_proxy = xstrdup(value);
}
return 0;
}
@@ -212,13 +299,13 @@ static void read_config(void)
head_ref = resolve_ref("HEAD", sha1, 0, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
!prefixcmp(head_ref, "refs/heads/")) {
- current_branch = head_ref + strlen("refs/heads/");
- current_branch_len = strlen(current_branch);
+ current_branch =
+ make_branch(head_ref + strlen("refs/heads/"), 0);
}
git_config(handle_config);
}
-static struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
+struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
{
int i;
struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
@@ -256,6 +343,16 @@ static struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
return rs;
}
+static int valid_remote_nick(const char *name)
+{
+ if (!name[0] || /* not empty */
+ (name[0] == '.' && /* not "." */
+ (!name[1] || /* not ".." */
+ (name[1] == '.' && !name[2]))))
+ return 0;
+ return !strchr(name, '/'); /* no slash */
+}
+
struct remote *remote_get(const char *name)
{
struct remote *ret;
@@ -264,15 +361,15 @@ struct remote *remote_get(const char *name)
if (!name)
name = default_remote_name;
ret = make_remote(name, 0);
- if (name[0] != '/') {
- if (!ret->uri)
+ if (valid_remote_nick(name)) {
+ if (!ret->url)
read_remotes_file(ret);
- if (!ret->uri)
+ if (!ret->url)
read_branches_file(ret);
}
- if (!ret->uri)
- add_uri(ret, name);
- if (!ret->uri)
+ if (!ret->url)
+ add_url(ret, name);
+ if (!ret->url)
return NULL;
ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec);
ret->push = parse_ref_spec(ret->push_refspec_nr, ret->push_refspec);
@@ -298,11 +395,38 @@ int for_each_remote(each_remote_fn fn, void *priv)
return result;
}
-int remote_has_uri(struct remote *remote, const char *uri)
+void ref_remove_duplicates(struct ref *ref_map)
+{
+ struct ref **posn;
+ struct ref *next;
+ for (; ref_map; ref_map = ref_map->next) {
+ if (!ref_map->peer_ref)
+ continue;
+ posn = &ref_map->next;
+ while (*posn) {
+ if ((*posn)->peer_ref &&
+ !strcmp((*posn)->peer_ref->name,
+ ref_map->peer_ref->name)) {
+ if (strcmp((*posn)->name, ref_map->name))
+ die("%s tracks both %s and %s",
+ ref_map->peer_ref->name,
+ (*posn)->name, ref_map->name);
+ next = (*posn)->next;
+ free((*posn)->peer_ref);
+ free(*posn);
+ *posn = next;
+ } else {
+ posn = &(*posn)->next;
+ }
+ }
+ }
+}
+
+int remote_has_url(struct remote *remote, const char *url)
{
int i;
- for (i = 0; i < remote->uri_nr; i++) {
- if (!strcmp(remote->uri[i], uri))
+ for (i = 0; i < remote->url_nr; i++) {
+ if (!strcmp(remote->url[i], url))
return 1;
}
return 0;
@@ -315,7 +439,7 @@ int remote_find_tracking(struct remote *remote, struct refspec *refspec)
int i;
if (find_src) {
- if (refspec->dst == NULL)
+ if (!refspec->dst)
return error("find_tracking: need either src or dst");
needle = refspec->dst;
result = &refspec->src;
@@ -357,6 +481,26 @@ struct ref *alloc_ref(unsigned namelen)
return ret;
}
+static struct ref *copy_ref(const struct ref *ref)
+{
+ struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
+ memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1);
+ ret->next = NULL;
+ return ret;
+}
+
+struct ref *copy_ref_list(const struct ref *ref)
+{
+ struct ref *ret = NULL;
+ struct ref **tail = &ret;
+ while (ref) {
+ *tail = copy_ref(ref);
+ ref = ref->next;
+ tail = &((*tail)->next);
+ }
+ return ret;
+}
+
void free_refs(struct ref *ref)
{
struct ref *next;
@@ -383,10 +527,7 @@ static int count_refspec_match(const char *pattern,
char *name = refs->name;
int namelen = strlen(name);
- if (namelen < patlen ||
- memcmp(name + namelen - patlen, pattern, patlen))
- continue;
- if (namelen != patlen && name[namelen - patlen - 1] != '/')
+ if (!refname_match(pattern, name, ref_rev_parse_rules))
continue;
/* A match is "weak" if it is with refs outside
@@ -489,23 +630,23 @@ static int match_explicit(struct ref *src, struct ref *dst,
* way to delete 'other' ref at the remote end.
*/
matched_src = try_explicit_object_name(rs->src);
- if (matched_src)
- break;
- error("src refspec %s does not match any.",
- rs->src);
+ if (!matched_src)
+ error("src refspec %s does not match any.", rs->src);
break;
default:
matched_src = NULL;
- error("src refspec %s matches more than one.",
- rs->src);
+ error("src refspec %s matches more than one.", rs->src);
break;
}
if (!matched_src)
errs = 1;
- if (dst_value == NULL)
+ if (!dst_value) {
+ if (!matched_src)
+ return errs;
dst_value = matched_src->name;
+ }
switch (count_refspec_match(dst_value, dst, &matched_dst)) {
case 1:
@@ -524,7 +665,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
dst_value);
break;
}
- if (errs || matched_dst == NULL)
+ if (errs || !matched_dst)
return 1;
if (matched_dst->peer_ref) {
errs = 1;
@@ -548,14 +689,6 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
return -errs;
}
-static struct ref *find_ref_by_name(struct ref *list, const char *name)
-{
- for ( ; list; list = list->next)
- if (!strcmp(list->name, name))
- return list;
- return NULL;
-}
-
static const struct refspec *check_pattern_match(const struct refspec *rs,
int rs_nr,
const struct ref *src)
@@ -574,10 +707,12 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
* without thinking.
*/
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
- int nr_refspec, char **refspec, int all)
+ int nr_refspec, const char **refspec, int flags)
{
struct refspec *rs =
parse_ref_spec(nr_refspec, (const char **) refspec);
+ int send_all = flags & MATCH_REFS_ALL;
+ int send_mirror = flags & MATCH_REFS_MIRROR;
if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
return -1;
@@ -594,7 +729,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
if (!pat)
continue;
}
- else if (prefixcmp(src->name, "refs/heads/"))
+ else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
/*
* "matching refs"; traditionally we pushed everything
* including refs outside refs/heads/ hierarchy, but
@@ -615,10 +750,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
if (dst_peer && dst_peer->peer_ref)
/* We're already sending something to this ref. */
goto free_name;
- if (!dst_peer && !nr_refspec && !all)
- /* Remote doesn't have it, and we have no
+
+ if (!dst_peer && !nr_refspec && !(send_all || send_mirror))
+ /*
+ * Remote doesn't have it, and we have no
* explicit pattern, and we don't have
- * --all. */
+ * --all nor --mirror.
+ */
goto free_name;
if (!dst_peer) {
/* Create a new one and link it */
@@ -626,8 +764,161 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
hashcpy(dst_peer->new_sha1, src->new_sha1);
}
dst_peer->peer_ref = src;
+ if (pat)
+ dst_peer->force = pat->force;
free_name:
free(dst_name);
}
return 0;
}
+
+struct branch *branch_get(const char *name)
+{
+ struct branch *ret;
+
+ read_config();
+ if (!name || !*name || !strcmp(name, "HEAD"))
+ ret = current_branch;
+ else
+ ret = make_branch(name, 0);
+ if (ret && ret->remote_name) {
+ ret->remote = remote_get(ret->remote_name);
+ if (ret->merge_nr) {
+ int i;
+ ret->merge = xcalloc(sizeof(*ret->merge),
+ ret->merge_nr);
+ for (i = 0; i < ret->merge_nr; i++) {
+ ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
+ ret->merge[i]->src = xstrdup(ret->merge_name[i]);
+ remote_find_tracking(ret->remote,
+ ret->merge[i]);
+ }
+ }
+ }
+ return ret;
+}
+
+int branch_has_merge_config(struct branch *branch)
+{
+ return branch && !!branch->merge;
+}
+
+int branch_merge_matches(struct branch *branch,
+ int i,
+ const char *refname)
+{
+ if (!branch || i < 0 || i >= branch->merge_nr)
+ return 0;
+ return refname_match(branch->merge[i]->src, refname, ref_fetch_rules);
+}
+
+static struct ref *get_expanded_map(const struct ref *remote_refs,
+ const struct refspec *refspec)
+{
+ const struct ref *ref;
+ struct ref *ret = NULL;
+ struct ref **tail = &ret;
+
+ int remote_prefix_len = strlen(refspec->src);
+ int local_prefix_len = strlen(refspec->dst);
+
+ for (ref = remote_refs; ref; ref = ref->next) {
+ if (strchr(ref->name, '^'))
+ continue; /* a dereference item */
+ if (!prefixcmp(ref->name, refspec->src)) {
+ const char *match;
+ struct ref *cpy = copy_ref(ref);
+ match = ref->name + remote_prefix_len;
+
+ cpy->peer_ref = alloc_ref(local_prefix_len +
+ strlen(match) + 1);
+ sprintf(cpy->peer_ref->name, "%s%s",
+ refspec->dst, match);
+ if (refspec->force)
+ cpy->peer_ref->force = 1;
+ *tail = cpy;
+ tail = &cpy->next;
+ }
+ }
+
+ return ret;
+}
+
+static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const char *name)
+{
+ const struct ref *ref;
+ for (ref = refs; ref; ref = ref->next) {
+ if (refname_match(name, ref->name, ref_fetch_rules))
+ return ref;
+ }
+ return NULL;
+}
+
+struct ref *get_remote_ref(const struct ref *remote_refs, const char *name)
+{
+ const struct ref *ref = find_ref_by_name_abbrev(remote_refs, name);
+
+ if (!ref)
+ return NULL;
+
+ return copy_ref(ref);
+}
+
+static struct ref *get_local_ref(const char *name)
+{
+ struct ref *ret;
+ if (!name)
+ return NULL;
+
+ if (!prefixcmp(name, "refs/")) {
+ ret = alloc_ref(strlen(name) + 1);
+ strcpy(ret->name, name);
+ return ret;
+ }
+
+ if (!prefixcmp(name, "heads/") ||
+ !prefixcmp(name, "tags/") ||
+ !prefixcmp(name, "remotes/")) {
+ ret = alloc_ref(strlen(name) + 6);
+ sprintf(ret->name, "refs/%s", name);
+ return ret;
+ }
+
+ ret = alloc_ref(strlen(name) + 12);
+ sprintf(ret->name, "refs/heads/%s", name);
+ return ret;
+}
+
+int get_fetch_map(const struct ref *remote_refs,
+ const struct refspec *refspec,
+ struct ref ***tail,
+ int missing_ok)
+{
+ struct ref *ref_map, *rm;
+
+ if (refspec->pattern) {
+ ref_map = get_expanded_map(remote_refs, refspec);
+ } else {
+ const char *name = refspec->src[0] ? refspec->src : "HEAD";
+
+ ref_map = get_remote_ref(remote_refs, name);
+ if (!missing_ok && !ref_map)
+ die("Couldn't find remote ref %s", name);
+ if (ref_map) {
+ ref_map->peer_ref = get_local_ref(refspec->dst);
+ if (ref_map->peer_ref && refspec->force)
+ ref_map->peer_ref->force = 1;
+ }
+ }
+
+ for (rm = ref_map; rm; rm = rm->next) {
+ if (rm->peer_ref && check_ref_format(rm->peer_ref->name + 5))
+ die("* refusing to create funny ref '%s' locally",
+ rm->peer_ref->name);
+ }
+
+ if (ref_map)
+ tail_link_ref(ref_map, tail);
+
+ return 0;
+}
diff --git a/remote.h b/remote.h
index 17b8b5b5d5..86e036d610 100644
--- a/remote.h
+++ b/remote.h
@@ -4,8 +4,8 @@
struct remote {
const char *name;
- const char **uri;
- int uri_nr;
+ const char **url;
+ int url_nr;
const char **push_refspec;
struct refspec *push;
@@ -15,7 +15,21 @@ struct remote {
struct refspec *fetch;
int fetch_refspec_nr;
+ /*
+ * -1 to never fetch tags
+ * 0 to auto-follow tags on heuristic (default)
+ * 1 to always auto-follow tags
+ * 2 to always fetch tags
+ */
+ int fetch_tags;
+
const char *receivepack;
+ const char *uploadpack;
+
+ /*
+ * for curl remotes only
+ */
+ char *http_proxy;
};
struct remote *remote_get(const char *name);
@@ -23,7 +37,7 @@ struct remote *remote_get(const char *name);
typedef int each_remote_fn(struct remote *remote, void *priv);
int for_each_remote(each_remote_fn fn, void *priv);
-int remote_has_uri(struct remote *remote, const char *uri);
+int remote_has_url(struct remote *remote, const char *url);
struct refspec {
unsigned force : 1;
@@ -35,17 +49,69 @@ struct refspec {
struct ref *alloc_ref(unsigned namelen);
+struct ref *copy_ref_list(const struct ref *ref);
+
+int check_ref_type(const struct ref *ref, int flags);
+
/*
* Frees the entire list and peers of elements.
*/
void free_refs(struct ref *ref);
+/*
+ * Removes and frees any duplicate refs in the map.
+ */
+void ref_remove_duplicates(struct ref *ref_map);
+
+struct refspec *parse_ref_spec(int nr_refspec, const char **refspec);
+
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
- int nr_refspec, char **refspec, int all);
+ int nr_refspec, const char **refspec, int all);
+
+/*
+ * Given a list of the remote refs and the specification of things to
+ * fetch, makes a (separate) list of the refs to fetch and the local
+ * refs to store into.
+ *
+ * *tail is the pointer to the tail pointer of the list of results
+ * beforehand, and will be set to the tail pointer of the list of
+ * results afterward.
+ *
+ * missing_ok is usually false, but when we are adding branch.$name.merge
+ * it is Ok if the branch is not at the remote anymore.
+ */
+int get_fetch_map(const struct ref *remote_refs, const struct refspec *refspec,
+ struct ref ***tail, int missing_ok);
+
+struct ref *get_remote_ref(const struct ref *remote_refs, const char *name);
/*
* For the given remote, reads the refspec's src and sets the other fields.
*/
int remote_find_tracking(struct remote *remote, struct refspec *refspec);
+struct branch {
+ const char *name;
+ const char *refname;
+
+ const char *remote_name;
+ struct remote *remote;
+
+ const char **merge_name;
+ struct refspec **merge;
+ int merge_nr;
+};
+
+struct branch *branch_get(const char *name);
+
+int branch_has_merge_config(struct branch *branch);
+int branch_merge_matches(struct branch *, int n, const char *);
+
+/* Flags to match_refs. */
+enum match_refs_flags {
+ MATCH_REFS_NONE = 0,
+ MATCH_REFS_ALL = (1 << 0),
+ MATCH_REFS_MIRROR = (1 << 1),
+};
+
#endif
diff --git a/revision.c b/revision.c
index e76da0d448..d3e8658104 100644
--- a/revision.c
+++ b/revision.c
@@ -10,6 +10,8 @@
#include "reflog-walk.h"
#include "patch-ids.h"
+volatile show_early_output_fn_t show_early_output;
+
static char *path_name(struct name_path *path, const char *name)
{
struct name_path *p;
@@ -44,6 +46,8 @@ void add_object(struct object *obj,
static void mark_blob_uninteresting(struct blob *blob)
{
+ if (!blob)
+ return;
if (blob->object.flags & UNINTERESTING)
return;
blob->object.flags |= UNINTERESTING;
@@ -55,6 +59,8 @@ void mark_tree_uninteresting(struct tree *tree)
struct name_entry entry;
struct object *obj = &tree->object;
+ if (!tree)
+ return;
if (obj->flags & UNINTERESTING)
return;
obj->flags |= UNINTERESTING;
@@ -65,10 +71,17 @@ void mark_tree_uninteresting(struct tree *tree)
init_tree_desc(&desc, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
- if (S_ISDIR(entry.mode))
+ switch (object_type(entry.mode)) {
+ case OBJ_TREE:
mark_tree_uninteresting(lookup_tree(entry.sha1));
- else
+ break;
+ case OBJ_BLOB:
mark_blob_uninteresting(lookup_blob(entry.sha1));
+ break;
+ default:
+ /* Subproject commit - not in this repository */
+ break;
+ }
}
/*
@@ -130,6 +143,18 @@ void add_pending_object(struct rev_info *revs, struct object *obj, const char *n
add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
}
+void add_head_to_pending(struct rev_info *revs)
+{
+ unsigned char sha1[20];
+ struct object *obj;
+ if (get_sha1("HEAD", sha1))
+ return;
+ obj = parse_object(sha1);
+ if (!obj)
+ return;
+ add_pending_object(revs, obj, "HEAD");
+}
+
static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
{
struct object *object;
@@ -152,6 +177,8 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
struct tag *tag = (struct tag *) object;
if (revs->tag_objects && !(flags & UNINTERESTING))
add_pending_object(revs, object, tag->tag);
+ if (!tag->tagged)
+ die("bad tag");
object = parse_object(tag->tagged->sha1);
if (!object)
die("bad object %s", sha1_to_hex(tag->tagged->sha1));
@@ -250,7 +277,7 @@ static void file_add_remove(struct diff_options *options,
}
tree_difference = diff;
if (tree_difference == REV_TREE_DIFFERENT)
- options->has_changes = 1;
+ DIFF_OPT_SET(options, HAS_CHANGES);
}
static void file_change(struct diff_options *options,
@@ -260,7 +287,7 @@ static void file_change(struct diff_options *options,
const char *base, const char *path)
{
tree_difference = REV_TREE_DIFFERENT;
- options->has_changes = 1;
+ DIFF_OPT_SET(options, HAS_CHANGES);
}
static int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
@@ -270,7 +297,7 @@ static int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree
if (!t2)
return REV_TREE_DIFFERENT;
tree_difference = REV_TREE_SAME;
- revs->pruning.has_changes = 0;
+ DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
&revs->pruning) < 0)
return REV_TREE_DIFFERENT;
@@ -294,7 +321,7 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
init_tree_desc(&empty, "", 0);
tree_difference = REV_TREE_SAME;
- revs->pruning.has_changes = 0;
+ DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
retval = diff_tree(&empty, &real, "", &revs->pruning);
free(tree);
@@ -306,15 +333,28 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
struct commit_list **pp, *parent;
int tree_changed = 0, tree_same = 0;
+ /*
+ * If we don't do pruning, everything is interesting
+ */
+ if (!revs->prune)
+ return;
+
if (!commit->tree)
return;
if (!commit->parents) {
- if (!rev_same_tree_as_empty(revs, commit->tree))
- commit->object.flags |= TREECHANGE;
+ if (rev_same_tree_as_empty(revs, commit->tree))
+ commit->object.flags |= TREESAME;
return;
}
+ /*
+ * Normal non-merge commit? If we don't want to make the
+ * history dense, we consider it always to be a change..
+ */
+ if (!revs->dense && !commit->parents->next)
+ return;
+
pp = &commit->parents;
while ((parent = *pp) != NULL) {
struct commit *p = parent->item;
@@ -338,6 +378,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
}
parent->next = NULL;
commit->parents = parent;
+ commit->object.flags |= TREESAME;
return;
case REV_TREE_NEW:
@@ -366,7 +407,8 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
}
if (tree_changed && !tree_same)
- commit->object.flags |= TREECHANGE;
+ return;
+ commit->object.flags |= TREESAME;
}
static int add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
@@ -413,8 +455,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
* simplify the commit history and find the parent
* that has no differences in the path set if one exists.
*/
- if (revs->prune_fn)
- revs->prune_fn(revs, commit);
+ try_to_simplify_commit(revs, commit);
if (revs->no_walk)
return 0;
@@ -523,6 +564,12 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
free_patch_ids(&ids);
}
+static void add_to_list(struct commit_list **p, struct commit *commit, struct commit_list *n)
+{
+ p = &commit_list_insert(commit, p)->next;
+ *p = n;
+}
+
static int limit_list(struct rev_info *revs)
{
struct commit_list *list = revs->commits;
@@ -533,6 +580,7 @@ static int limit_list(struct rev_info *revs)
struct commit_list *entry = list;
struct commit *commit = list->item;
struct object *obj = &commit->object;
+ show_early_output_fn_t show;
list = list->next;
free(entry);
@@ -543,13 +591,24 @@ static int limit_list(struct rev_info *revs)
return -1;
if (obj->flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
- if (everybody_uninteresting(list))
+ if (everybody_uninteresting(list)) {
+ if (revs->show_all)
+ add_to_list(p, commit, list);
break;
- continue;
+ }
+ if (!revs->show_all)
+ continue;
}
if (revs->min_age != -1 && (commit->date > revs->min_age))
continue;
p = &commit_list_insert(commit, p)->next;
+
+ show = show_early_output;
+ if (!show)
+ continue;
+
+ show(revs, newlist);
+ show_early_output = NULL;
}
if (revs->cherry_pick)
cherry_pick_list(newlist, revs);
@@ -642,6 +701,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
it = get_reference(revs, arg, sha1, 0);
if (it->type != OBJ_TAG)
break;
+ if (!((struct tag*)it)->tagged)
+ return 0;
hashcpy(sha1, ((struct tag*)it)->tagged->sha1);
}
if (it->type != OBJ_COMMIT)
@@ -662,8 +723,8 @@ void init_revisions(struct rev_info *revs, const char *prefix)
revs->abbrev = DEFAULT_ABBREV;
revs->ignore_merges = 1;
revs->simplify_history = 1;
- revs->pruning.recursive = 1;
- revs->pruning.quiet = 1;
+ DIFF_OPT_SET(&revs->pruning, RECURSIVE);
+ DIFF_OPT_SET(&revs->pruning, QUIET);
revs->pruning.add_remove = file_add_remove;
revs->pruning.change = file_change;
revs->lifo = 1;
@@ -674,12 +735,6 @@ void init_revisions(struct rev_info *revs, const char *prefix)
revs->skip_count = -1;
revs->max_count = -1;
- revs->prune_fn = NULL;
- revs->prune_data = NULL;
-
- revs->topo_setter = topo_sort_default_setter;
- revs->topo_getter = topo_sort_default_getter;
-
revs->commit_format = CMIT_FMT_DEFAULT;
diff_setup(&revs->diffopt);
@@ -994,6 +1049,18 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
revs->topo_order = 1;
continue;
}
+ if (!prefixcmp(arg, "--early-output")) {
+ int count = 100;
+ switch (arg[14]) {
+ case '=':
+ count = atoi(arg+15);
+ /* Fallthrough */
+ case 0:
+ revs->topo_order = 1;
+ revs->early_output = count;
+ continue;
+ }
+ }
if (!strcmp(arg, "--parents")) {
revs->parents = 1;
continue;
@@ -1006,6 +1073,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
revs->dense = 0;
continue;
}
+ if (!strcmp(arg, "--show-all")) {
+ revs->show_all = 1;
+ continue;
+ }
if (!strcmp(arg, "--remove-empty")) {
revs->remove_empty_trees = 1;
continue;
@@ -1054,13 +1125,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
}
if (!strcmp(arg, "-r")) {
revs->diff = 1;
- revs->diffopt.recursive = 1;
+ DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
continue;
}
if (!strcmp(arg, "-t")) {
revs->diff = 1;
- revs->diffopt.recursive = 1;
- revs->diffopt.tree_in_recursive = 1;
+ DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
+ DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE);
continue;
}
if (!strcmp(arg, "-m")) {
@@ -1241,8 +1312,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT)
revs->diff = 1;
- /* Pickaxe and rename following needs diffs */
- if (revs->diffopt.pickaxe || revs->diffopt.follow_renames)
+ /* Pickaxe, diff-filter and rename following need diffs */
+ if (revs->diffopt.pickaxe ||
+ revs->diffopt.filter ||
+ DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
revs->diff = 1;
if (revs->topo_order)
@@ -1251,8 +1324,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (revs->prune_data) {
diff_tree_setup_paths(revs->prune_data, &revs->pruning);
/* Can't prune commits with rename following: the paths change.. */
- if (!revs->diffopt.follow_renames)
- revs->prune_fn = try_to_simplify_commit;
+ if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
+ revs->prune = 1;
if (!revs->full_diff)
diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
}
@@ -1303,9 +1376,7 @@ int prepare_revision_walk(struct rev_info *revs)
if (limit_list(revs) < 0)
return -1;
if (revs->topo_order)
- sort_in_topological_order_fn(&revs->commits, revs->lifo,
- revs->topo_setter,
- revs->topo_getter);
+ sort_in_topological_order(&revs->commits, revs->lifo);
return 0;
}
@@ -1324,7 +1395,9 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
return rewrite_one_error;
if (p->parents && p->parents->next)
return rewrite_one_ok;
- if (p->object.flags & (TREECHANGE | UNINTERESTING))
+ if (p->object.flags & UNINTERESTING)
+ return rewrite_one_ok;
+ if (!(p->object.flags & TREESAME))
return rewrite_one_ok;
if (!p->parents)
return rewrite_one_noparents;
@@ -1381,6 +1454,38 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
commit->buffer, strlen(commit->buffer));
}
+enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+ if (commit->object.flags & SHOWN)
+ return commit_ignore;
+ if (revs->unpacked && has_sha1_pack(commit->object.sha1, revs->ignore_packed))
+ return commit_ignore;
+ if (revs->show_all)
+ return commit_show;
+ if (commit->object.flags & UNINTERESTING)
+ 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 (!commit_match(commit, revs))
+ return commit_ignore;
+ if (revs->prune && revs->dense) {
+ /* Commit without changes? */
+ if (commit->object.flags & TREESAME) {
+ /* drop merges unless we want parenthood */
+ if (!revs->parents)
+ return commit_ignore;
+ /* non-merge - always ignore it */
+ if (!commit->parents || !commit->parents->next)
+ return commit_ignore;
+ }
+ if (revs->parents && rewrite_parents(revs, commit) < 0)
+ return commit_error;
+ }
+ return commit_show;
+}
+
static struct commit *get_revision_1(struct rev_info *revs)
{
if (!revs->commits)
@@ -1408,36 +1513,15 @@ static struct commit *get_revision_1(struct rev_info *revs)
if (add_parents_to_list(revs, commit, &revs->commits) < 0)
return NULL;
}
- if (commit->object.flags & SHOWN)
- continue;
- if (revs->unpacked && has_sha1_pack(commit->object.sha1,
- revs->ignore_packed))
- continue;
-
- if (commit->object.flags & UNINTERESTING)
- continue;
- if (revs->min_age != -1 && (commit->date > revs->min_age))
- continue;
- if (revs->no_merges &&
- commit->parents && commit->parents->next)
+ switch (simplify_commit(revs, commit)) {
+ case commit_ignore:
continue;
- if (!commit_match(commit, revs))
- continue;
- if (revs->prune_fn && revs->dense) {
- /* Commit without changes? */
- if (!(commit->object.flags & TREECHANGE)) {
- /* drop merges unless we want parenthood */
- if (!revs->parents)
- continue;
- /* non-merge - always ignore it */
- if (!commit->parents || !commit->parents->next)
- continue;
- }
- if (revs->parents && rewrite_parents(revs, commit) < 0)
- return NULL;
+ case commit_error:
+ return NULL;
+ default:
+ return commit;
}
- return commit;
} while (revs->commits);
return NULL;
}
diff --git a/revision.h b/revision.h
index 98a0a8f3fa..b5f01f8309 100644
--- a/revision.h
+++ b/revision.h
@@ -3,19 +3,18 @@
#define SEEN (1u<<0)
#define UNINTERESTING (1u<<1)
-#define TREECHANGE (1u<<2)
+#define TREESAME (1u<<2)
#define SHOWN (1u<<3)
#define TMP_MARK (1u<<4) /* for isolated cases; clean after use */
#define BOUNDARY (1u<<5)
#define CHILD_SHOWN (1u<<6)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
+#define TOPOSORT (1u<<9) /* In the active toposort list.. */
struct rev_info;
struct log_info;
-typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit);
-
struct rev_info {
/* Starting list */
struct commit_list *commits;
@@ -27,12 +26,14 @@ struct rev_info {
/* Basic information */
const char *prefix;
void *prune_data;
- prune_fn_t *prune_fn;
+ unsigned int early_output;
/* Traversal flags */
unsigned int dense:1,
+ prune:1,
no_merges:1,
no_walk:1,
+ show_all:1,
remove_empty_trees:1,
simplify_history:1,
lifo:1,
@@ -96,9 +97,6 @@ struct rev_info {
struct diff_options diffopt;
struct diff_options pruning;
- topo_sort_set_fn_t topo_setter;
- topo_sort_get_fn_t topo_getter;
-
struct reflog_walk_info *reflog_info;
};
@@ -107,6 +105,8 @@ struct rev_info {
#define REV_TREE_DIFFERENT 2
/* revision.c */
+typedef void (*show_early_output_fn_t)(struct rev_info *, struct commit_list *);
+volatile show_early_output_fn_t show_early_output;
extern void init_revisions(struct rev_info *revs, const char *prefix);
extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
@@ -131,4 +131,14 @@ extern void add_object(struct object *obj,
extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
+extern void add_head_to_pending(struct rev_info *);
+
+enum commit_action {
+ commit_ignore,
+ commit_show,
+ commit_error
+};
+
+extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
+
#endif
diff --git a/rsh.c b/rsh.c
deleted file mode 100644
index 016d72ead7..0000000000
--- a/rsh.c
+++ /dev/null
@@ -1,79 +0,0 @@
-#include "cache.h"
-#include "rsh.h"
-#include "quote.h"
-
-#define COMMAND_SIZE 4096
-
-int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
- char *url, int rmt_argc, char **rmt_argv)
-{
- char *host;
- char *path;
- int sv[2];
- int i;
- pid_t pid;
- struct strbuf cmd;
-
- if (!strcmp(url, "-")) {
- *fd_in = 0;
- *fd_out = 1;
- return 0;
- }
-
- host = strstr(url, "//");
- if (host) {
- host += 2;
- path = strchr(host, '/');
- } else {
- host = url;
- path = strchr(host, ':');
- if (path)
- *(path++) = '\0';
- }
- if (!path) {
- return error("Bad URL: %s", url);
- }
-
- /* $GIT_RSH <host> "env GIT_DIR=<path> <remote_prog> <args...>" */
- strbuf_init(&cmd, COMMAND_SIZE);
- strbuf_addstr(&cmd, "env ");
- strbuf_addstr(&cmd, GIT_DIR_ENVIRONMENT "=");
- sq_quote_buf(&cmd, path);
- strbuf_addch(&cmd, ' ');
- sq_quote_buf(&cmd, remote_prog);
-
- for (i = 0 ; i < rmt_argc ; i++) {
- strbuf_addch(&cmd, ' ');
- sq_quote_buf(&cmd, rmt_argv[i]);
- }
-
- strbuf_addstr(&cmd, " -");
-
- if (cmd.len >= COMMAND_SIZE)
- return error("Command line too long");
-
- if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv))
- return error("Couldn't create socket");
-
- pid = fork();
- if (pid < 0)
- return error("Couldn't fork");
- if (!pid) {
- const char *ssh, *ssh_basename;
- ssh = getenv("GIT_SSH");
- if (!ssh) ssh = "ssh";
- ssh_basename = strrchr(ssh, '/');
- if (!ssh_basename)
- ssh_basename = ssh;
- else
- ssh_basename++;
- close(sv[1]);
- dup2(sv[0], 0);
- dup2(sv[0], 1);
- execlp(ssh, ssh_basename, host, cmd.buf, NULL);
- }
- close(sv[0]);
- *fd_in = sv[1];
- *fd_out = sv[1];
- return 0;
-}
diff --git a/rsh.h b/rsh.h
deleted file mode 100644
index ee2f499291..0000000000
--- a/rsh.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef RSH_H
-#define RSH_H
-
-int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
- char *url, int rmt_argc, char **rmt_argv);
-
-#endif
diff --git a/run-command.c b/run-command.c
index 7e779d33ee..476d00c218 100644
--- a/run-command.c
+++ b/run-command.c
@@ -17,8 +17,8 @@ static inline void dup_devnull(int to)
int start_command(struct child_process *cmd)
{
- int need_in, need_out;
- int fdin[2], fdout[2];
+ int need_in, need_out, need_err;
+ int fdin[2], fdout[2], fderr[2];
need_in = !cmd->no_stdin && cmd->in < 0;
if (need_in) {
@@ -41,12 +41,26 @@ int start_command(struct child_process *cmd)
cmd->close_out = 1;
}
+ need_err = !cmd->no_stderr && cmd->err < 0;
+ if (need_err) {
+ if (pipe(fderr) < 0) {
+ if (need_in)
+ close_pair(fdin);
+ if (need_out)
+ close_pair(fdout);
+ return -ERR_RUN_COMMAND_PIPE;
+ }
+ cmd->err = fderr[0];
+ }
+
cmd->pid = fork();
if (cmd->pid < 0) {
if (need_in)
close_pair(fdin);
if (need_out)
close_pair(fdout);
+ if (need_err)
+ close_pair(fderr);
return -ERR_RUN_COMMAND_FORK;
}
@@ -73,6 +87,13 @@ int start_command(struct child_process *cmd)
close(cmd->out);
}
+ if (cmd->no_stderr)
+ dup_devnull(2);
+ else if (need_err) {
+ dup2(fderr[1], 2);
+ close_pair(fderr);
+ }
+
if (cmd->dir && chdir(cmd->dir))
die("exec %s: cd to %s failed (%s)", cmd->argv[0],
cmd->dir, strerror(errno));
@@ -102,19 +123,17 @@ int start_command(struct child_process *cmd)
else if (cmd->out > 1)
close(cmd->out);
+ if (need_err)
+ close(fderr[1]);
+
return 0;
}
-int finish_command(struct child_process *cmd)
+static int wait_or_whine(pid_t pid)
{
- if (cmd->close_in)
- close(cmd->in);
- if (cmd->close_out)
- close(cmd->out);
-
for (;;) {
int status, code;
- pid_t waiting = waitpid(cmd->pid, &status, 0);
+ pid_t waiting = waitpid(pid, &status, 0);
if (waiting < 0) {
if (errno == EINTR)
@@ -122,7 +141,7 @@ int finish_command(struct child_process *cmd)
error("waitpid failed (%s)", strerror(errno));
return -ERR_RUN_COMMAND_WAITPID;
}
- if (waiting != cmd->pid)
+ if (waiting != pid)
return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
if (WIFSIGNALED(status))
return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
@@ -136,6 +155,15 @@ int finish_command(struct child_process *cmd)
}
}
+int finish_command(struct child_process *cmd)
+{
+ if (cmd->close_in)
+ close(cmd->in);
+ if (cmd->close_out)
+ close(cmd->out);
+ return wait_or_whine(cmd->pid);
+}
+
int run_command(struct child_process *cmd)
{
int code = start_command(cmd);
@@ -178,3 +206,34 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
cmd.env = env;
return run_command(&cmd);
}
+
+int start_async(struct async *async)
+{
+ int pipe_out[2];
+
+ if (pipe(pipe_out) < 0)
+ return error("cannot create pipe: %s", strerror(errno));
+
+ async->pid = fork();
+ if (async->pid < 0) {
+ error("fork (async) failed: %s", strerror(errno));
+ close_pair(pipe_out);
+ return -1;
+ }
+ if (!async->pid) {
+ close(pipe_out[0]);
+ exit(!!async->proc(pipe_out[1], async->data));
+ }
+ async->out = pipe_out[0];
+ close(pipe_out[1]);
+ return 0;
+}
+
+int finish_async(struct async *async)
+{
+ int ret = 0;
+
+ if (wait_or_whine(async->pid))
+ ret = error("waitpid (async) failed");
+ return ret;
+}
diff --git a/run-command.h b/run-command.h
index 7958eb1e0b..1fc781d766 100644
--- a/run-command.h
+++ b/run-command.h
@@ -16,12 +16,14 @@ struct child_process {
pid_t pid;
int in;
int out;
+ int err;
const char *dir;
const char *const *env;
unsigned close_in:1;
unsigned close_out:1;
unsigned no_stdin:1;
unsigned no_stdout:1;
+ unsigned no_stderr:1;
unsigned git_cmd:1; /* if this is to be git sub-command */
unsigned stdout_to_stderr:1;
};
@@ -42,4 +44,26 @@ int run_command_v_opt_cd(const char **argv, int opt, const char *dir);
*/
int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
+/*
+ * The purpose of the following functions is to feed a pipe by running
+ * a function asynchronously and providing output that the caller reads.
+ *
+ * It is expected that no synchronization and mutual exclusion between
+ * the caller and the feed function is necessary so that the function
+ * can run in a thread without interfering with the caller.
+ */
+struct async {
+ /*
+ * proc writes to fd and closes it;
+ * returns 0 on success, non-zero on failure
+ */
+ int (*proc)(int fd, void *data);
+ void *data;
+ int out; /* caller reads from here and closes it */
+ pid_t pid;
+};
+
+int start_async(struct async *async);
+int finish_async(struct async *async);
+
#endif
diff --git a/send-pack.c b/send-pack.c
deleted file mode 100644
index c1807f0794..0000000000
--- a/send-pack.c
+++ /dev/null
@@ -1,445 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "run-command.h"
-#include "remote.h"
-
-static const char send_pack_usage[] =
-"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
-" --all and explicit <ref> specification are mutually exclusive.";
-static const char *receivepack = "git-receive-pack";
-static int verbose;
-static int send_all;
-static int force_update;
-static int use_thin_pack;
-static int dry_run;
-
-/*
- * Make a pack stream and spit it out into file descriptor fd
- */
-static int pack_objects(int fd, struct ref *refs)
-{
- /*
- * The child becomes pack-objects --revs; we feed
- * the revision parameters to it via its stdin and
- * let its stdout go back to the other end.
- */
- const char *args[] = {
- "pack-objects",
- "--all-progress",
- "--revs",
- "--stdout",
- NULL,
- NULL,
- };
- struct child_process po;
-
- if (use_thin_pack)
- args[4] = "--thin";
- memset(&po, 0, sizeof(po));
- po.argv = args;
- po.in = -1;
- po.out = fd;
- po.git_cmd = 1;
- if (start_command(&po))
- die("git-pack-objects failed (%s)", strerror(errno));
-
- /*
- * We feed the pack-objects we just spawned with revision
- * parameters by writing to the pipe.
- */
- while (refs) {
- char buf[42];
-
- if (!is_null_sha1(refs->old_sha1) &&
- has_sha1_file(refs->old_sha1)) {
- memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
- buf[0] = '^';
- buf[41] = '\n';
- if (!write_or_whine(po.in, buf, 42,
- "send-pack: send refs"))
- break;
- }
- if (!is_null_sha1(refs->new_sha1)) {
- memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
- buf[40] = '\n';
- if (!write_or_whine(po.in, buf, 41,
- "send-pack: send refs"))
- break;
- }
- refs = refs->next;
- }
-
- if (finish_command(&po))
- return error("pack-objects died with strange error");
- return 0;
-}
-
-static void unmark_and_free(struct commit_list *list, unsigned int mark)
-{
- while (list) {
- struct commit_list *temp = list;
- temp->item->object.flags &= ~mark;
- list = temp->next;
- free(temp);
- }
-}
-
-static int ref_newer(const unsigned char *new_sha1,
- const unsigned char *old_sha1)
-{
- struct object *o;
- struct commit *old, *new;
- struct commit_list *list, *used;
- int found = 0;
-
- /* Both new and old must be commit-ish and new is descendant of
- * old. Otherwise we require --force.
- */
- o = deref_tag(parse_object(old_sha1), NULL, 0);
- if (!o || o->type != OBJ_COMMIT)
- return 0;
- old = (struct commit *) o;
-
- o = deref_tag(parse_object(new_sha1), NULL, 0);
- if (!o || o->type != OBJ_COMMIT)
- return 0;
- new = (struct commit *) o;
-
- if (parse_commit(new) < 0)
- return 0;
-
- used = list = NULL;
- commit_list_insert(new, &list);
- while (list) {
- new = pop_most_recent_commit(&list, 1);
- commit_list_insert(new, &used);
- if (new == old) {
- found = 1;
- break;
- }
- }
- unmark_and_free(list, 1);
- unmark_and_free(used, 1);
- return found;
-}
-
-static struct ref *local_refs, **local_tail;
-static struct ref *remote_refs, **remote_tail;
-
-static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct ref *ref;
- int len = strlen(refname) + 1;
- ref = xcalloc(1, sizeof(*ref) + len);
- hashcpy(ref->new_sha1, sha1);
- memcpy(ref->name, refname, len);
- *local_tail = ref;
- local_tail = &ref->next;
- return 0;
-}
-
-static void get_local_heads(void)
-{
- local_tail = &local_refs;
- for_each_ref(one_local_ref, NULL);
-}
-
-static int receive_status(int in)
-{
- char line[1000];
- int ret = 0;
- int len = packet_read_line(in, line, sizeof(line));
- if (len < 10 || memcmp(line, "unpack ", 7)) {
- fprintf(stderr, "did not receive status back\n");
- return -1;
- }
- if (memcmp(line, "unpack ok\n", 10)) {
- fputs(line, stderr);
- ret = -1;
- }
- while (1) {
- len = packet_read_line(in, line, sizeof(line));
- if (!len)
- break;
- if (len < 3 ||
- (memcmp(line, "ok", 2) && memcmp(line, "ng", 2))) {
- fprintf(stderr, "protocol error: %s\n", line);
- ret = -1;
- break;
- }
- if (!memcmp(line, "ok", 2))
- continue;
- fputs(line, stderr);
- ret = -1;
- }
- return ret;
-}
-
-static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec)
-{
- struct ref *ref;
- int new_refs;
- int ret = 0;
- int ask_for_status_report = 0;
- int allow_deleting_refs = 0;
- int expect_status_report = 0;
-
- /* No funny business with the matcher */
- remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
- get_local_heads();
-
- /* Does the other end support the reporting? */
- if (server_supports("report-status"))
- ask_for_status_report = 1;
- if (server_supports("delete-refs"))
- allow_deleting_refs = 1;
-
- /* match them up */
- if (!remote_tail)
- remote_tail = &remote_refs;
- if (match_refs(local_refs, remote_refs, &remote_tail,
- nr_refspec, refspec, send_all))
- return -1;
-
- if (!remote_refs) {
- fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
- "Perhaps you should specify a branch such as 'master'.\n");
- return 0;
- }
-
- /*
- * Finally, tell the other end!
- */
- new_refs = 0;
- for (ref = remote_refs; ref; ref = ref->next) {
- char old_hex[60], *new_hex;
- int will_delete_ref;
-
- if (!ref->peer_ref)
- continue;
-
-
- will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
- if (will_delete_ref && !allow_deleting_refs) {
- error("remote does not support deleting refs");
- ret = -2;
- continue;
- }
- if (!will_delete_ref &&
- !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
- if (verbose)
- fprintf(stderr, "'%s': up-to-date\n", ref->name);
- continue;
- }
-
- /* This part determines what can overwrite what.
- * The rules are:
- *
- * (0) you can always use --force or +A:B notation to
- * selectively force individual ref pairs.
- *
- * (1) if the old thing does not exist, it is OK.
- *
- * (2) if you do not have the old thing, you are not allowed
- * to overwrite it; you would not know what you are losing
- * otherwise.
- *
- * (3) if both new and old are commit-ish, and new is a
- * descendant of old, it is OK.
- *
- * (4) regardless of all of the above, removing :B is
- * always allowed.
- */
-
- if (!force_update &&
- !will_delete_ref &&
- !is_null_sha1(ref->old_sha1) &&
- !ref->force) {
- if (!has_sha1_file(ref->old_sha1) ||
- !ref_newer(ref->peer_ref->new_sha1,
- ref->old_sha1)) {
- /* We do not have the remote ref, or
- * we know that the remote ref is not
- * an ancestor of what we are trying to
- * push. Either way this can be losing
- * commits at the remote end and likely
- * we were not up to date to begin with.
- */
- error("remote '%s' is not a strict "
- "subset of local ref '%s'. "
- "maybe you are not up-to-date and "
- "need to pull first?",
- ref->name,
- ref->peer_ref->name);
- ret = -2;
- continue;
- }
- }
- hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
- if (!will_delete_ref)
- new_refs++;
- strcpy(old_hex, sha1_to_hex(ref->old_sha1));
- new_hex = sha1_to_hex(ref->new_sha1);
-
- if (!dry_run) {
- if (ask_for_status_report) {
- packet_write(out, "%s %s %s%c%s",
- old_hex, new_hex, ref->name, 0,
- "report-status");
- ask_for_status_report = 0;
- expect_status_report = 1;
- }
- else
- packet_write(out, "%s %s %s",
- old_hex, new_hex, ref->name);
- }
- if (will_delete_ref)
- fprintf(stderr, "deleting '%s'\n", ref->name);
- else {
- fprintf(stderr, "updating '%s'", ref->name);
- if (strcmp(ref->name, ref->peer_ref->name))
- fprintf(stderr, " using '%s'",
- ref->peer_ref->name);
- fprintf(stderr, "\n from %s\n to %s\n",
- old_hex, new_hex);
- }
- if (remote && !dry_run) {
- struct refspec rs;
- rs.src = ref->name;
- rs.dst = NULL;
- if (!remote_find_tracking(remote, &rs)) {
- fprintf(stderr, " Also local %s\n", rs.dst);
- if (will_delete_ref) {
- if (delete_ref(rs.dst, NULL)) {
- error("Failed to delete");
- }
- } else
- update_ref("update by push", rs.dst,
- ref->new_sha1, NULL, 0, 0);
- free(rs.dst);
- }
- }
- }
-
- packet_flush(out);
- if (new_refs && !dry_run)
- ret = pack_objects(out, remote_refs);
- close(out);
-
- if (expect_status_report) {
- if (receive_status(in))
- ret = -4;
- }
-
- if (!new_refs && ret == 0)
- fprintf(stderr, "Everything up-to-date\n");
- return ret;
-}
-
-static void verify_remote_names(int nr_heads, char **heads)
-{
- int i;
-
- for (i = 0; i < nr_heads; i++) {
- const char *remote = strchr(heads[i], ':');
-
- remote = remote ? (remote + 1) : heads[i];
- switch (check_ref_format(remote)) {
- case 0: /* ok */
- case -2: /* ok but a single level -- that is fine for
- * a match pattern.
- */
- case -3: /* ok but ends with a pattern-match character */
- continue;
- }
- die("remote part of refspec is not a valid name in %s",
- heads[i]);
- }
-}
-
-int main(int argc, char **argv)
-{
- int i, nr_heads = 0;
- char *dest = NULL;
- char **heads = NULL;
- int fd[2], ret;
- pid_t pid;
- char *remote_name = NULL;
- struct remote *remote = NULL;
-
- setup_git_directory();
- git_config(git_default_config);
-
- argv++;
- for (i = 1; i < argc; i++, argv++) {
- char *arg = *argv;
-
- if (*arg == '-') {
- if (!prefixcmp(arg, "--receive-pack=")) {
- receivepack = arg + 15;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- receivepack = arg + 7;
- continue;
- }
- if (!prefixcmp(arg, "--remote=")) {
- remote_name = arg + 9;
- continue;
- }
- if (!strcmp(arg, "--all")) {
- send_all = 1;
- continue;
- }
- if (!strcmp(arg, "--dry-run")) {
- dry_run = 1;
- continue;
- }
- if (!strcmp(arg, "--force")) {
- force_update = 1;
- continue;
- }
- if (!strcmp(arg, "--verbose")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(arg, "--thin")) {
- use_thin_pack = 1;
- continue;
- }
- usage(send_pack_usage);
- }
- if (!dest) {
- dest = arg;
- continue;
- }
- heads = argv;
- nr_heads = argc - i;
- break;
- }
- if (!dest)
- usage(send_pack_usage);
- if (heads && send_all)
- usage(send_pack_usage);
- verify_remote_names(nr_heads, heads);
-
- if (remote_name) {
- remote = remote_get(remote_name);
- if (!remote_has_uri(remote, dest)) {
- die("Destination %s is not a uri for %s",
- dest, remote_name);
- }
- }
-
- pid = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0);
- if (pid < 0)
- return 1;
- ret = send_pack(fd[0], fd[1], remote, nr_heads, heads);
- close(fd[0]);
- close(fd[1]);
- ret |= finish_connect(pid);
- return !!ret;
-}
diff --git a/send-pack.h b/send-pack.h
new file mode 100644
index 0000000000..8ff1dc3539
--- /dev/null
+++ b/send-pack.h
@@ -0,0 +1,18 @@
+#ifndef SEND_PACK_H
+#define SEND_PACK_H
+
+struct send_pack_args {
+ const char *receivepack;
+ unsigned verbose:1,
+ send_all:1,
+ send_mirror:1,
+ force_update:1,
+ use_thin_pack:1,
+ dry_run:1;
+};
+
+int send_pack(struct send_pack_args *args,
+ const char *dest, struct remote *remote,
+ int nr_heads, const char **heads);
+
+#endif
diff --git a/server-info.c b/server-info.c
index 0d1312ca56..c1c073b2f0 100644
--- a/server-info.c
+++ b/server-info.c
@@ -35,7 +35,7 @@ static int update_info_refs(int force)
safe_create_leading_directories(path0);
info_ref_fp = fopen(path1, "w");
if (!info_ref_fp)
- return error("unable to update %s", path0);
+ return error("unable to update %s", path1);
for_each_ref(add_info_ref, NULL);
fclose(info_ref_fp);
adjust_shared_perm(path1);
@@ -101,7 +101,7 @@ static int read_pack_info_file(const char *infofile)
while (fgets(line, sizeof(line), fp)) {
int len = strlen(line);
- if (line[len-1] == '\n')
+ if (len && line[len-1] == '\n')
line[--len] = 0;
if (!len)
diff --git a/setup.c b/setup.c
index 145eca50f4..dc247a84c4 100644
--- a/setup.c
+++ b/setup.c
@@ -4,51 +4,118 @@
static int inside_git_dir = -1;
static int inside_work_tree = -1;
-const char *prefix_path(const char *prefix, int len, const char *path)
+static int sanitary_path_copy(char *dst, const char *src)
{
- const char *orig = path;
+ char *dst0 = dst;
+
+ if (*src == '/') {
+ *dst++ = '/';
+ while (*src == '/')
+ src++;
+ }
+
for (;;) {
- char c;
- if (*path != '.')
- break;
- c = path[1];
- /* "." */
- if (!c) {
- path++;
- break;
+ char c = *src;
+
+ /*
+ * A path component that begins with . could be
+ * special:
+ * (1) "." and ends -- ignore and terminate.
+ * (2) "./" -- ignore them, eat slash and continue.
+ * (3) ".." and ends -- strip one and terminate.
+ * (4) "../" -- strip one, eat slash and continue.
+ */
+ if (c == '.') {
+ switch (src[1]) {
+ case '\0':
+ /* (1) */
+ src++;
+ break;
+ case '/':
+ /* (2) */
+ src += 2;
+ while (*src == '/')
+ src++;
+ continue;
+ case '.':
+ switch (src[2]) {
+ case '\0':
+ /* (3) */
+ src += 2;
+ goto up_one;
+ case '/':
+ /* (4) */
+ src += 3;
+ while (*src == '/')
+ src++;
+ goto up_one;
+ }
+ }
}
- /* "./" */
+
+ /* copy up to the next '/', and eat all '/' */
+ while ((c = *src++) != '\0' && c != '/')
+ *dst++ = c;
if (c == '/') {
- path += 2;
- continue;
- }
- if (c != '.')
- break;
- c = path[2];
- if (!c)
- path += 2;
- else if (c == '/')
- path += 3;
- else
+ *dst++ = c;
+ while (c == '/')
+ c = *src++;
+ src--;
+ } else if (!c)
break;
- /* ".." and "../" */
- /* Remove last component of the prefix */
- do {
- if (!len)
- die("'%s' is outside repository", orig);
- len--;
- } while (len && prefix[len-1] != '/');
continue;
+
+ up_one:
+ /*
+ * dst0..dst is prefix portion, and dst[-1] is '/';
+ * go up one level.
+ */
+ dst -= 2; /* go past trailing '/' if any */
+ if (dst < dst0)
+ return -1;
+ while (1) {
+ if (dst <= dst0)
+ break;
+ c = *dst--;
+ if (c == '/') {
+ dst += 2;
+ break;
+ }
+ }
}
- if (len) {
- int speclen = strlen(path);
- char *n = xmalloc(speclen + len + 1);
+ *dst = '\0';
+ return 0;
+}
- memcpy(n, prefix, len);
- memcpy(n + len, path, speclen+1);
- path = n;
+const 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 {
+ if (len)
+ memcpy(sanitized, prefix, len);
+ strcpy(sanitized + len, path);
}
- return path;
+ if (sanitary_path_copy(sanitized, sanitized))
+ goto error_out;
+ if (is_absolute_path(orig)) {
+ const char *work_tree = get_git_work_tree();
+ size_t len = strlen(work_tree);
+ size_t total = strlen(sanitized) + 1;
+ if (strncmp(sanitized, work_tree, len) ||
+ (sanitized[len] != '\0' && sanitized[len] != '/')) {
+ error_out:
+ error("'%s' is outside repository", orig);
+ free(sanitized);
+ return NULL;
+ }
+ if (sanitized[len] == '/')
+ len++;
+ memmove(sanitized, sanitized + len, total - len);
+ }
+ return sanitized;
}
/*
@@ -59,7 +126,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
{
static char path[PATH_MAX];
- if (!pfx || !*pfx || arg[0] == '/')
+ if (!pfx || !*pfx || is_absolute_path(arg))
return arg;
memcpy(path, pfx, pfx_len);
strcpy(path + pfx_len, arg);
@@ -114,7 +181,7 @@ void verify_non_filename(const char *prefix, const char *arg)
const char **get_pathspec(const char *prefix, const char **pathspec)
{
const char *entry = *pathspec;
- const char **p;
+ const char **src, **dst;
int prefixlen;
if (!prefix && !entry)
@@ -128,19 +195,26 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
}
/* Otherwise we have to re-write the entries.. */
- p = pathspec;
+ src = pathspec;
+ dst = pathspec;
prefixlen = prefix ? strlen(prefix) : 0;
- do {
- *p = prefix_path(prefix, prefixlen, entry);
- } while ((entry = *++p) != NULL);
- return (const char **) pathspec;
+ while (*src) {
+ const char *p = prefix_path(prefix, prefixlen, *src);
+ if (p)
+ *(dst++) = p;
+ src++;
+ }
+ *dst = NULL;
+ if (!*pathspec)
+ return NULL;
+ return pathspec;
}
/*
* Test if it looks like we're at a git directory.
* We want to see:
*
- * - either a objects/ directory _or_ the proper
+ * - either an objects/ directory _or_ the proper
* GIT_OBJECT_DIRECTORY environment variable
* - a refs/ directory
* - either a HEAD symlink or a HEAD file that is formatted as
@@ -206,6 +280,38 @@ static const char *set_work_tree(const char *dir)
return NULL;
}
+void setup_work_tree(void)
+{
+ const char *work_tree, *git_dir;
+ static int initialized = 0;
+
+ if (initialized)
+ return;
+ work_tree = get_git_work_tree();
+ git_dir = get_git_dir();
+ if (!is_absolute_path(git_dir))
+ set_git_dir(make_absolute_path(git_dir));
+ if (!work_tree || chdir(work_tree))
+ die("This operation must be run in a work tree");
+ initialized = 1;
+}
+
+static int check_repository_format_gently(int *nongit_ok)
+{
+ git_config(check_repository_format_version);
+ if (GIT_REPO_VERSION < repository_format_version) {
+ if (!nongit_ok)
+ die ("Expected git repo version <= %d, found %d",
+ GIT_REPO_VERSION, repository_format_version);
+ warning("Expected git repo version <= %d, found %d",
+ GIT_REPO_VERSION, repository_format_version);
+ warning("Please upgrade Git");
+ *nongit_ok = -1;
+ return -1;
+ }
+ return 0;
+}
+
/*
* 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.
@@ -230,8 +336,15 @@ const char *setup_git_directory_gently(int *nongit_ok)
static char buffer[1024 + 1];
const char *retval;
- if (!work_tree_env)
- return set_work_tree(gitdirenv);
+ 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)
@@ -270,6 +383,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
if (!work_tree_env)
inside_work_tree = 0;
setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+ check_repository_format_gently(nongit_ok);
return NULL;
}
chdir("..");
@@ -290,6 +404,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
if (!work_tree_env)
inside_work_tree = 1;
git_work_tree_cfg = xstrndup(cwd, offset);
+ if (check_repository_format_gently(nongit_ok))
+ return NULL;
if (offset == len)
return NULL;
@@ -330,6 +446,8 @@ int check_repository_format_version(const char *var, const char *value)
if (is_bare_repository_cfg == 1)
inside_work_tree = -1;
} else if (strcmp(var, "core.worktree") == 0) {
+ if (!value)
+ return config_error_nonbool(var);
if (git_work_tree_cfg)
free(git_work_tree_cfg);
git_work_tree_cfg = xstrdup(value);
@@ -340,17 +458,12 @@ int check_repository_format_version(const char *var, const char *value)
int check_repository_format(void)
{
- git_config(check_repository_format_version);
- if (GIT_REPO_VERSION < repository_format_version)
- die ("Expected git repo version <= %d, found %d",
- GIT_REPO_VERSION, repository_format_version);
- return 0;
+ return check_repository_format_gently(NULL);
}
const char *setup_git_directory(void)
{
const char *retval = setup_git_directory_gently(NULL);
- check_repository_format();
/* If the work tree is not the default one, recompute prefix */
if (inside_work_tree < 0) {
diff --git a/sha1_file.c b/sha1_file.c
index 83a06a7aed..1ddb96bb82 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -25,8 +25,10 @@
#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
const unsigned char null_sha1[20];
@@ -86,7 +88,7 @@ int safe_create_leading_directories(char *path)
char *pos = path;
struct stat st;
- if (*pos == '/')
+ if (is_absolute_path(path))
pos++;
while (pos) {
@@ -146,7 +148,7 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
/*
* NOTE! This returns a statically allocated buffer, so you have to be
- * careful about using it. Do a "xstrdup()" if you need to save the
+ * careful about using it. Do an "xstrdup()" if you need to save the
* filename.
*
* Also note that this returns the location for creating. Reading
@@ -253,7 +255,7 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative
int entlen = pfxlen + 43;
int base_len = -1;
- if (*entry != '/' && relative_base) {
+ if (!is_absolute_path(entry) && relative_base) {
/* Relative alt-odb */
if (base_len < 0)
base_len = strlen(relative_base) + 1;
@@ -262,7 +264,7 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative
}
ent = xmalloc(sizeof(*ent) + entlen);
- if (*entry != '/' && relative_base) {
+ if (!is_absolute_path(entry) && relative_base) {
memcpy(ent->base, relative_base, base_len - 1);
ent->base[base_len - 1] = '/';
memcpy(ent->base + base_len, entry, len);
@@ -333,7 +335,7 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
while (cp < ep && *cp != sep)
cp++;
if (last != cp) {
- if ((*last != '/') && depth) {
+ if (!is_absolute_path(last) && depth) {
error("%s: ignoring relative alternate object store %s",
relative_base, last);
} else {
@@ -423,9 +425,9 @@ void pack_report(void)
"pack_report: getpagesize() = %10" SZ_FMT "\n"
"pack_report: core.packedGitWindowSize = %10" SZ_FMT "\n"
"pack_report: core.packedGitLimit = %10" SZ_FMT "\n",
- (size_t) getpagesize(),
- packed_git_window_size,
- packed_git_limit);
+ sz_fmt(getpagesize()),
+ sz_fmt(packed_git_window_size),
+ sz_fmt(packed_git_limit));
fprintf(stderr,
"pack_report: pack_used_ctr = %10u\n"
"pack_report: pack_mmap_calls = %10u\n"
@@ -435,7 +437,7 @@ void pack_report(void)
pack_used_ctr,
pack_mmap_calls,
pack_open_windows, peak_pack_open_windows,
- pack_mapped, peak_pack_mapped);
+ sz_fmt(pack_mapped), sz_fmt(peak_pack_mapped));
}
static int check_packed_git_idx(const char *path, struct packed_git *p)
@@ -521,13 +523,15 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
munmap(idx_map, idx_size);
return error("wrong index v2 file size in %s", path);
}
- if (idx_size != min_size) {
- /* make sure we can deal with large pack offsets */
- off_t x = 0x7fffffffUL, y = 0xffffffffUL;
- if (x > (x + 1) || y > (y + 1)) {
- munmap(idx_map, idx_size);
- return error("pack too large for current definition of off_t in %s", path);
- }
+ if (idx_size != min_size &&
+ /*
+ * make sure we can deal with large pack offsets.
+ * 31-bit signed offset won't be enough, neither
+ * 32-bit unsigned one will be.
+ */
+ (sizeof(off_t) <= 4)) {
+ munmap(idx_map, idx_size);
+ return error("pack too large for current definition of off_t in %s", path);
}
}
@@ -607,6 +611,22 @@ void release_pack_memory(size_t need, int fd)
; /* nothing */
}
+void close_pack_windows(struct packed_git *p)
+{
+ while (p->windows) {
+ struct pack_window *w = p->windows;
+
+ if (w->inuse_cnt)
+ die("pack '%s' still has open windows to it",
+ p->pack_name);
+ munmap(w->base, w->len);
+ pack_mapped -= w->len;
+ pack_open_windows--;
+ p->windows = w->next;
+ free(w);
+ }
+}
+
void unuse_pack(struct pack_window **w_cursor)
{
struct pack_window *w = *w_cursor;
@@ -1825,6 +1845,15 @@ static struct cached_object {
} *cached_objects;
static int cached_object_nr, cached_object_alloc;
+static struct cached_object empty_tree = {
+ /* empty tree sha1: 4b825dc642cb6eb9a060e54bf8d69288fbee4904 */
+ "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60"
+ "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04",
+ OBJ_TREE,
+ "",
+ 0
+};
+
static struct cached_object *find_cached_object(const unsigned char *sha1)
{
int i;
@@ -1834,6 +1863,8 @@ static struct cached_object *find_cached_object(const unsigned char *sha1)
if (!hashcmp(co->sha1, sha1))
return co;
}
+ if (!hashcmp(sha1, empty_tree.sha1))
+ return &empty_tree;
return NULL;
}
@@ -1923,7 +1954,8 @@ void *read_object_with_reference(const unsigned char *sha1,
}
ref_length = strlen(ref_type);
- if (memcmp(buffer, ref_type, ref_length) ||
+ if (ref_length + 40 > isize ||
+ memcmp(buffer, ref_type, ref_length) ||
get_sha1_hex((char *) buffer + ref_length, actual_sha1)) {
free(buffer);
return NULL;
@@ -2338,7 +2370,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
struct strbuf nbuf;
strbuf_init(&nbuf, 0);
- if (convert_to_git(path, buf, size, &nbuf)) {
+ if (convert_to_git(path, buf, size, &nbuf,
+ write_object ? safe_crlf : 0)) {
munmap(buf, size);
buf = strbuf_detach(&nbuf, &size);
re_allocated = 1;
diff --git a/sha1_name.c b/sha1_name.c
index 2d727d54dc..c2805e736b 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -239,23 +239,13 @@ static int ambiguous_path(const char *path, int len)
return slash;
}
-static const char *ref_fmt[] = {
- "%.*s",
- "refs/%.*s",
- "refs/tags/%.*s",
- "refs/heads/%.*s",
- "refs/remotes/%.*s",
- "refs/remotes/%.*s/HEAD",
- NULL
-};
-
int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
{
const char **p, *r;
int refs_found = 0;
*ref = NULL;
- for (p = ref_fmt; *p; p++) {
+ for (p = ref_rev_parse_rules; *p; p++) {
unsigned char sha1_from_ref[20];
unsigned char *this_result;
@@ -277,7 +267,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
int logs_found = 0;
*log = NULL;
- for (p = ref_fmt; *p; p++) {
+ for (p = ref_rev_parse_rules; *p; p++) {
struct stat st;
unsigned char hash[20];
char path[PATH_MAX];
@@ -504,8 +494,11 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
return error("%.*s: expected %s type, but the object dereferences to %s type",
len, name, typename(expected_type),
typename(o->type));
+ if (!o)
+ return -1;
if (!o->parsed)
- parse_object(o->sha1);
+ if (!parse_object(o->sha1))
+ return -1;
}
}
return 0;
@@ -588,8 +581,11 @@ static int handle_one_ref(const char *path,
struct object *object = parse_object(sha1);
if (!object)
return 0;
- if (object->type == OBJ_TAG)
+ if (object->type == OBJ_TAG) {
object = deref_tag(object, path, strlen(path));
+ if (!object)
+ return 0;
+ }
if (object->type != OBJ_COMMIT)
return 0;
insert_by_date((struct commit *)object, list);
@@ -610,24 +606,36 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
{
struct commit_list *list = NULL, *backup = NULL, *l;
int retval = -1;
+ char *temp_commit_buffer = NULL;
if (prefix[0] == '!') {
if (prefix[1] != '!')
die ("Invalid search pattern: %s", prefix);
prefix++;
}
- if (!save_commit_buffer)
- return error("Could not expand oneline-name.");
for_each_ref(handle_one_ref, &list);
for (l = list; l; l = l->next)
commit_list_insert(l->item, &backup);
while (list) {
char *p;
struct commit *commit;
+ enum object_type type;
+ unsigned long size;
commit = pop_most_recent_commit(&list, ONELINE_SEEN);
- parse_object(commit->object.sha1);
- if (!commit->buffer || !(p = strstr(commit->buffer, "\n\n")))
+ if (!parse_object(commit->object.sha1))
+ continue;
+ if (temp_commit_buffer)
+ 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;
+ }
+ if (!(p = strstr(p, "\n\n")))
continue;
if (!prefixcmp(p + 2, prefix)) {
hashcpy(sha1, commit->object.sha1);
@@ -635,6 +643,8 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
break;
}
}
+ if (temp_commit_buffer)
+ free(temp_commit_buffer);
free_commit_list(list);
for (l = backup; l; l = l->next)
clear_commit_marks(l->item, ONELINE_SEEN);
@@ -692,7 +702,7 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
break;
if (ce_stage(ce) == stage) {
hashcpy(sha1, ce->sha1);
- *mode = ntohl(ce->ce_mode);
+ *mode = ce->ce_mode;
return 0;
}
pos++;
diff --git a/shallow.c b/shallow.c
index dbd9f5ad0a..4d90eda19e 100644
--- a/shallow.c
+++ b/shallow.c
@@ -56,7 +56,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
if (i < heads->nr) {
commit = (struct commit *)
deref_tag(heads->objects[i++].item, NULL, 0);
- if (commit->object.type != OBJ_COMMIT) {
+ if (!commit || commit->object.type != OBJ_COMMIT) {
commit = NULL;
continue;
}
@@ -70,7 +70,8 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
cur_depth = *(int *)commit->util;
}
}
- parse_commit(commit);
+ if (parse_commit(commit))
+ die("invalid commit");
commit->object.flags |= not_shallow_flag;
cur_depth++;
for (p = commit->parents, commit = NULL; p; p = p->next) {
diff --git a/shell.c b/shell.c
index cfe372b213..9826109d5b 100644
--- a/shell.c
+++ b/shell.c
@@ -24,17 +24,11 @@ static int do_cvs_cmd(const char *me, char *arg)
const char *cvsserver_argv[3] = {
"cvsserver", "server", NULL
};
- const char *oldpath = getenv("PATH");
- struct strbuf newpath = STRBUF_INIT;
if (!arg || strcmp(arg, "server"))
die("git-cvsserver only handles server: %s", arg);
- strbuf_addstr(&newpath, git_exec_path());
- strbuf_addch(&newpath, ':');
- strbuf_addstr(&newpath, oldpath);
-
- setenv("PATH", strbuf_detach(&newpath, NULL), 1);
+ setup_path(NULL);
return execv_git_cmd(cvsserver_argv);
}
diff --git a/show-index.c b/show-index.c
index 57ed9e87b7..7253991fff 100644
--- a/show-index.c
+++ b/show-index.c
@@ -68,7 +68,7 @@ int main(int argc, char **argv)
ntohl(off64[1]);
off64_nr++;
}
- printf("%llu %s (%08x)\n", (unsigned long long) offset,
+ printf("%" PRIuMAX " %s (%08x)\n", (uintmax_t) offset,
sha1_to_hex(entries[i].sha1),
ntohl(entries[i].crc));
}
diff --git a/sideband.c b/sideband.c
index 277fa3c10d..b6777812cb 100644
--- a/sideband.c
+++ b/sideband.c
@@ -11,13 +11,32 @@
* stream, aka "verbose"). A message over band #3 is a signal that
* the remote died unexpectedly. A flush() concludes the stream.
*/
+
+#define PREFIX "remote:"
+
+#define ANSI_SUFFIX "\033[K"
+#define DUMB_SUFFIX " "
+
+#define FIX_SIZE 10 /* large enough for any of the above */
+
int recv_sideband(const char *me, int in_stream, int out, int err)
{
- char buf[7 + LARGE_PACKET_MAX + 1];
- strcpy(buf, "remote:");
+ unsigned pf = strlen(PREFIX);
+ unsigned sf;
+ char buf[LARGE_PACKET_MAX + 2*FIX_SIZE];
+ char *suffix, *term;
+
+ memcpy(buf, PREFIX, pf);
+ term = getenv("TERM");
+ if (term && strcmp(term, "dumb"))
+ suffix = ANSI_SUFFIX;
+ else
+ suffix = DUMB_SUFFIX;
+ sf = strlen(suffix);
+
while (1) {
int band, len;
- len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX);
+ len = packet_read_line(in_stream, buf + pf, LARGE_PACKET_MAX);
if (len == 0)
break;
if (len < 1) {
@@ -25,20 +44,52 @@ int recv_sideband(const char *me, int in_stream, int out, int err)
safe_write(err, buf, len);
return SIDEBAND_PROTOCOL_ERROR;
}
- band = buf[7] & 0xff;
+ band = buf[pf] & 0xff;
len--;
switch (band) {
case 3:
- buf[7] = ' ';
- buf[8+len] = '\n';
- safe_write(err, buf, 8+len+1);
+ buf[pf] = ' ';
+ buf[pf+1+len] = '\n';
+ safe_write(err, buf, pf+1+len+1);
return SIDEBAND_REMOTE_ERROR;
case 2:
- buf[7] = ' ';
- safe_write(err, buf, 8+len);
+ buf[pf] = ' ';
+ len += pf+1;
+ while (1) {
+ int brk = pf+1;
+
+ /* Break the buffer into separate lines. */
+ while (brk < len) {
+ brk++;
+ if (buf[brk-1] == '\n' ||
+ buf[brk-1] == '\r')
+ break;
+ }
+
+ /*
+ * Let's insert a suffix to clear the end
+ * of the screen line, but only if current
+ * line data actually contains something.
+ */
+ if (brk > pf+1 + 1) {
+ char save[FIX_SIZE];
+ memcpy(save, buf + brk, sf);
+ buf[brk + sf - 1] = buf[brk - 1];
+ memcpy(buf + brk - 1, suffix, sf);
+ safe_write(err, buf, brk + sf);
+ memcpy(buf + brk, save, sf);
+ } else
+ safe_write(err, buf, brk);
+
+ if (brk < len) {
+ memmove(buf + pf+1, buf + brk, len - brk);
+ len = len - brk + pf+1;
+ } else
+ break;
+ }
continue;
case 1:
- safe_write(out, buf+8, len);
+ safe_write(out, buf + pf+1, len);
continue;
default:
len = sprintf(buf,
diff --git a/ssh-fetch.c b/ssh-fetch.c
deleted file mode 100644
index bdf51a7a14..0000000000
--- a/ssh-fetch.c
+++ /dev/null
@@ -1,166 +0,0 @@
-#ifndef COUNTERPART_ENV_NAME
-#define COUNTERPART_ENV_NAME "GIT_SSH_UPLOAD"
-#endif
-#ifndef COUNTERPART_PROGRAM_NAME
-#define COUNTERPART_PROGRAM_NAME "git-ssh-upload"
-#endif
-#ifndef MY_PROGRAM_NAME
-#define MY_PROGRAM_NAME "git-ssh-fetch"
-#endif
-
-#include "cache.h"
-#include "commit.h"
-#include "rsh.h"
-#include "fetch.h"
-#include "refs.h"
-
-static int fd_in;
-static int fd_out;
-
-static unsigned char remote_version;
-static unsigned char local_version = 1;
-
-static int prefetches;
-
-static struct object_list *in_transit;
-static struct object_list **end_of_transit = &in_transit;
-
-void prefetch(unsigned char *sha1)
-{
- char type = 'o';
- struct object_list *node;
- if (prefetches > 100) {
- fetch(in_transit->item->sha1);
- }
- node = xmalloc(sizeof(struct object_list));
- node->next = NULL;
- node->item = lookup_unknown_object(sha1);
- *end_of_transit = node;
- end_of_transit = &node->next;
- /* XXX: what if these writes fail? */
- write_in_full(fd_out, &type, 1);
- write_in_full(fd_out, sha1, 20);
- prefetches++;
-}
-
-static char conn_buf[4096];
-static size_t conn_buf_posn;
-
-int fetch(unsigned char *sha1)
-{
- int ret;
- signed char remote;
- struct object_list *temp;
-
- if (hashcmp(sha1, in_transit->item->sha1)) {
- /* we must have already fetched it to clean the queue */
- return has_sha1_file(sha1) ? 0 : -1;
- }
- prefetches--;
- temp = in_transit;
- in_transit = in_transit->next;
- if (!in_transit)
- end_of_transit = &in_transit;
- free(temp);
-
- if (conn_buf_posn) {
- remote = conn_buf[0];
- memmove(conn_buf, conn_buf + 1, --conn_buf_posn);
- } else {
- if (xread(fd_in, &remote, 1) < 1)
- return -1;
- }
- /* fprintf(stderr, "Got %d\n", remote); */
- if (remote < 0)
- return remote;
- ret = write_sha1_from_fd(sha1, fd_in, conn_buf, 4096, &conn_buf_posn);
- if (!ret)
- pull_say("got %s\n", sha1_to_hex(sha1));
- return ret;
-}
-
-static int get_version(void)
-{
- char type = 'v';
- if (write_in_full(fd_out, &type, 1) != 1 ||
- write_in_full(fd_out, &local_version, 1)) {
- return error("Couldn't request version from remote end");
- }
- if (xread(fd_in, &remote_version, 1) < 1) {
- return error("Couldn't read version from remote end");
- }
- return 0;
-}
-
-int fetch_ref(char *ref, unsigned char *sha1)
-{
- signed char remote;
- char type = 'r';
- int length = strlen(ref) + 1;
- if (write_in_full(fd_out, &type, 1) != 1 ||
- write_in_full(fd_out, ref, length) != length)
- return -1;
-
- if (read_in_full(fd_in, &remote, 1) != 1)
- return -1;
- if (remote < 0)
- return remote;
- if (read_in_full(fd_in, sha1, 20) != 20)
- return -1;
- return 0;
-}
-
-static const char ssh_fetch_usage[] =
- MY_PROGRAM_NAME
- " [-c] [-t] [-a] [-v] [--recover] [-w ref] commit-id url";
-int main(int argc, char **argv)
-{
- const char *write_ref = NULL;
- char *commit_id;
- char *url;
- int arg = 1;
- const char *prog;
-
- prog = getenv("GIT_SSH_PUSH");
- if (!prog) prog = "git-ssh-upload";
-
- setup_git_directory();
- git_config(git_default_config);
-
- while (arg < argc && argv[arg][0] == '-') {
- if (argv[arg][1] == 't') {
- get_tree = 1;
- } else if (argv[arg][1] == 'c') {
- get_history = 1;
- } else if (argv[arg][1] == 'a') {
- get_all = 1;
- get_tree = 1;
- get_history = 1;
- } else if (argv[arg][1] == 'v') {
- get_verbosely = 1;
- } else if (argv[arg][1] == 'w') {
- write_ref = argv[arg + 1];
- arg++;
- } else if (!strcmp(argv[arg], "--recover")) {
- get_recover = 1;
- }
- arg++;
- }
- if (argc < arg + 2) {
- usage(ssh_fetch_usage);
- return 1;
- }
- commit_id = argv[arg];
- url = argv[arg + 1];
-
- if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
- return 1;
-
- if (get_version())
- return 1;
-
- if (pull(1, &commit_id, &write_ref, url))
- return 1;
-
- return 0;
-}
diff --git a/ssh-pull.c b/ssh-pull.c
deleted file mode 100644
index 868ce4d41f..0000000000
--- a/ssh-pull.c
+++ /dev/null
@@ -1,4 +0,0 @@
-#define COUNTERPART_ENV_NAME "GIT_SSH_PUSH"
-#define COUNTERPART_PROGRAM_NAME "git-ssh-push"
-#define MY_PROGRAM_NAME "git-ssh-pull"
-#include "ssh-fetch.c"
diff --git a/ssh-push.c b/ssh-push.c
deleted file mode 100644
index a562df1b31..0000000000
--- a/ssh-push.c
+++ /dev/null
@@ -1,4 +0,0 @@
-#define COUNTERPART_ENV_NAME "GIT_SSH_PULL"
-#define COUNTERPART_PROGRAM_NAME "git-ssh-pull"
-#define MY_PROGRAM_NAME "git-ssh-push"
-#include "ssh-upload.c"
diff --git a/ssh-upload.c b/ssh-upload.c
deleted file mode 100644
index 20c35f03dd..0000000000
--- a/ssh-upload.c
+++ /dev/null
@@ -1,143 +0,0 @@
-#ifndef COUNTERPART_ENV_NAME
-#define COUNTERPART_ENV_NAME "GIT_SSH_FETCH"
-#endif
-#ifndef COUNTERPART_PROGRAM_NAME
-#define COUNTERPART_PROGRAM_NAME "git-ssh-fetch"
-#endif
-#ifndef MY_PROGRAM_NAME
-#define MY_PROGRAM_NAME "git-ssh-upload"
-#endif
-
-#include "cache.h"
-#include "rsh.h"
-#include "refs.h"
-
-static unsigned char local_version = 1;
-static unsigned char remote_version;
-
-static int verbose;
-
-static int serve_object(int fd_in, int fd_out) {
- ssize_t size;
- unsigned char sha1[20];
- signed char remote;
-
- size = read_in_full(fd_in, sha1, 20);
- if (size < 0) {
- perror("git-ssh-upload: read ");
- return -1;
- }
- if (!size)
- return -1;
-
- if (verbose)
- fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1));
-
- remote = 0;
-
- if (!has_sha1_file(sha1)) {
- fprintf(stderr, "git-ssh-upload: could not find %s\n",
- sha1_to_hex(sha1));
- remote = -1;
- }
-
- if (write_in_full(fd_out, &remote, 1) != 1)
- return 0;
-
- if (remote < 0)
- return 0;
-
- return write_sha1_to_fd(fd_out, sha1);
-}
-
-static int serve_version(int fd_in, int fd_out)
-{
- if (xread(fd_in, &remote_version, 1) < 1)
- return -1;
- write_in_full(fd_out, &local_version, 1);
- return 0;
-}
-
-static int serve_ref(int fd_in, int fd_out)
-{
- char ref[PATH_MAX];
- unsigned char sha1[20];
- int posn = 0;
- signed char remote = 0;
- do {
- if (posn >= PATH_MAX || xread(fd_in, ref + posn, 1) < 1)
- return -1;
- posn++;
- } while (ref[posn - 1]);
-
- if (verbose)
- fprintf(stderr, "Serving %s\n", ref);
-
- if (get_ref_sha1(ref, sha1))
- remote = -1;
- if (write_in_full(fd_out, &remote, 1) != 1)
- return 0;
- if (remote)
- return 0;
- write_in_full(fd_out, sha1, 20);
- return 0;
-}
-
-
-static void service(int fd_in, int fd_out) {
- char type;
- ssize_t retval;
- do {
- retval = xread(fd_in, &type, 1);
- if (retval < 1) {
- if (retval < 0)
- perror("git-ssh-upload: read ");
- return;
- }
- if (type == 'v' && serve_version(fd_in, fd_out))
- return;
- if (type == 'o' && serve_object(fd_in, fd_out))
- return;
- if (type == 'r' && serve_ref(fd_in, fd_out))
- return;
- } while (1);
-}
-
-static const char ssh_push_usage[] =
- MY_PROGRAM_NAME " [-c] [-t] [-a] [-w ref] commit-id url";
-
-int main(int argc, char **argv)
-{
- int arg = 1;
- char *commit_id;
- char *url;
- int fd_in, fd_out;
- const char *prog;
- unsigned char sha1[20];
- char hex[41];
-
- prog = getenv(COUNTERPART_ENV_NAME);
- if (!prog) prog = COUNTERPART_PROGRAM_NAME;
-
- setup_git_directory();
-
- while (arg < argc && argv[arg][0] == '-') {
- if (argv[arg][1] == 'w')
- arg++;
- arg++;
- }
- if (argc < arg + 2)
- usage(ssh_push_usage);
- commit_id = argv[arg];
- url = argv[arg + 1];
- if (get_sha1(commit_id, sha1))
- die("Not a valid object name %s", commit_id);
- memcpy(hex, sha1_to_hex(sha1), sizeof(hex));
- argv[arg] = hex;
-
- if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
- return 1;
-
- service(fd_in, fd_out);
- return 0;
-}
diff --git a/strbuf.c b/strbuf.c
index f4201e160d..4aed75265e 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -1,5 +1,14 @@
#include "cache.h"
+int prefixcmp(const char *str, const char *prefix)
+{
+ for (; ; str++, prefix++)
+ if (!*prefix)
+ return 0;
+ else if (*str != *prefix)
+ return (unsigned char)*prefix - (unsigned char)*str;
+}
+
/*
* Used as the default ->buf value, so that people can always assume
* buf is non NULL and ->buf is NUL terminated even for a freshly
@@ -106,17 +115,25 @@ void strbuf_add(struct strbuf *sb, const void *data, size_t len)
strbuf_setlen(sb, sb->len + len);
}
+void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
+{
+ strbuf_grow(sb, len);
+ memcpy(sb->buf + sb->len, sb->buf + pos, len);
+ strbuf_setlen(sb, sb->len + len);
+}
+
void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
{
int len;
va_list ap;
+ 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);
- if (len < 0) {
- len = 0;
- }
+ if (len < 0)
+ die("your vsnprintf is broken");
if (len > strbuf_avail(sb)) {
strbuf_grow(sb, len);
va_start(ap, fmt);
@@ -129,6 +146,27 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
strbuf_setlen(sb, sb->len + len);
}
+void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
+ void *context)
+{
+ for (;;) {
+ const char *percent;
+ size_t consumed;
+
+ percent = strchrnul(format, '%');
+ strbuf_add(sb, format, percent - format);
+ if (!*percent)
+ break;
+ format = percent + 1;
+
+ consumed = fn(sb, format, context);
+ if (consumed)
+ format += consumed;
+ else
+ strbuf_addch(sb, '%');
+ }
+}
+
size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
{
size_t res;
diff --git a/strbuf.h b/strbuf.h
index 9b9e861d3d..faec2291d9 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -23,12 +23,12 @@
* that way:
*
* strbuf_grow(sb, SOME_SIZE);
- * // ... here the memory areay starting at sb->buf, and of length
- * // sb_avail(sb) is all yours, and you are sure that sb_avail(sb) is at
- * // least 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 sb_avail(sb).
+ * 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.
@@ -101,6 +101,10 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) {
strbuf_add(sb, sb2->buf, sb2->len);
}
+extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
+
+typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
+extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
__attribute__((format(printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
@@ -113,5 +117,6 @@ extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
extern int strbuf_getline(struct strbuf *, FILE *, int);
extern void stripspace(struct strbuf *buf, int skip_comments);
+extern void launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
#endif /* STRBUF_H */
diff --git a/t/.gitattributes b/t/.gitattributes
new file mode 100644
index 0000000000..562b12e16e
--- /dev/null
+++ b/t/.gitattributes
@@ -0,0 +1 @@
+* -whitespace
diff --git a/t/README b/t/README
index 36f2517617..73ed11bfe2 100644
--- a/t/README
+++ b/t/README
@@ -160,14 +160,12 @@ library for your script to use.
- test_expect_failure <message> <script>
- This is the opposite of test_expect_success. If <script>
- yields success, test is considered a failure.
-
- Example:
-
- test_expect_failure \
- 'git-update-index without --add should fail adding.' \
- 'git-update-index should-be-empty'
+ This is NOT the opposite of test_expect_success, but is used
+ to mark a test that demonstrates a known breakage. Unlike
+ the usual test_expect_success tests, which say "ok" on
+ success and "FAIL" on failure, this will say "FIXED" on
+ success and "still broken" on failure. Failures from these
+ tests won't cause -i (immediate) to stop.
- test_debug <script>
diff --git a/t/diff-lib.sh b/t/diff-lib.sh
index 4624fe654c..7dc6d7eb1e 100644
--- a/t/diff-lib.sh
+++ b/t/diff-lib.sh
@@ -21,8 +21,8 @@ compare_diff_raw_z () {
# Also we do not check SHA1 hash generation in this test, which
# is a job for t0000-basic.sh
- tr '\0' '\012' <"$1" | sed -e "$sanitize_diff_raw_z" >.tmp-1
- tr '\0' '\012' <"$2" | sed -e "$sanitize_diff_raw_z" >.tmp-2
+ tr '\000' '\012' <"$1" | sed -e "$sanitize_diff_raw_z" >.tmp-1
+ tr '\000' '\012' <"$2" | sed -e "$sanitize_diff_raw_z" >.tmp-2
git diff .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
}
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index 8d4a447213..9ee35e7901 100644
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
@@ -82,3 +82,29 @@ stop_httpd () {
test -z "$SVN_HTTPD_PORT" && return
"$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop
}
+
+convert_to_rev_db () {
+ perl -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]";
+open my $rd, '<', $ARGV[0] or die "$!: couldn't open: $ARGV[0]";
+my $size = (stat($rd))[7];
+($size % 24) == 0 or die "Inconsistent size: $size";
+while (sysread($rd, my $buf, 24) == 24) {
+ my ($r, $c) = unpack('NH40', $buf);
+ my $offset = $r * 41;
+ seek $wr, 0, 2 or die $!;
+ my $pos = tell $wr;
+ if ($pos < $offset) {
+ for (1 .. (($offset - $pos) / 41)) {
+ print $wr (('0' x 40),"\n") or die $!;
+ }
+ }
+ seek $wr, $offset, 0 or die $!;
+ print $wr $c,"\n" or die $!;
+}
+close $wr or die $!;
+close $rd or die $!;
+EOF
+}
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 4e49d59065..92de088227 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -47,12 +47,24 @@ test_expect_success \
'test $(wc -l < full-of-directories) = 3'
################################################################
+# Test harness
+test_expect_success 'success is reported like this' '
+ :
+'
+test_expect_failure 'pretend we have a known breakage' '
+ false
+'
+test_expect_failure 'pretend we have fixed a known breakage' '
+ :
+'
+
+################################################################
# Basics of the basics
# updating a new file without --add should fail.
-test_expect_failure \
- 'git update-index without --add should fail adding.' \
- 'git update-index should-be-empty'
+test_expect_success 'git update-index without --add should fail adding.' '
+ ! git update-index should-be-empty
+'
# and with --add it should succeed, even if it is empty (it used to fail).
test_expect_success \
@@ -70,9 +82,9 @@ test_expect_success \
# Removing paths.
rm -f should-be-empty full-of-directories
-test_expect_failure \
- 'git update-index without --remove should fail removing.' \
- 'git update-index should-be-empty'
+test_expect_success 'git update-index without --remove should fail removing.' '
+ ! git update-index should-be-empty
+'
test_expect_success \
'git update-index with --remove should be able to remove.' \
@@ -204,9 +216,9 @@ test_expect_success \
'put invalid objects into the index.' \
'git update-index --index-info < badobjects'
-test_expect_failure \
- 'writing this tree without --missing-ok.' \
- 'git write-tree'
+test_expect_success 'writing this tree without --missing-ok.' '
+ ! git write-tree
+'
test_expect_success \
'writing this tree with --missing-ok.' \
@@ -297,4 +309,24 @@ test_expect_success 'absolute path works as expected' '
test "$sym" = "$(test-absolute-path $dir2/syml)"
'
+test_expect_success 'very long name in the index handled sanely' '
+
+ a=a && # 1
+ a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 16
+ a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 256
+ a=$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a$a && # 4096
+ a=${a}q &&
+
+ >path4 &&
+ git update-index --add path4 &&
+ (
+ git ls-files -s path4 |
+ sed -e "s/ .*/ /" |
+ tr -d "\012"
+ echo "$a"
+ ) | git update-index --index-info &&
+ len=$(git ls-files "a*" | wc -c) &&
+ test $len = 4098
+'
+
test_done
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index b14b3ec394..c015405f12 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 &&
+ unset GIT_DIR GIT_WORK_TREE
mkdir plain &&
cd plain &&
git init
@@ -35,7 +35,7 @@ test_expect_success 'plain' '
test_expect_success 'plain with GIT_WORK_TREE' '
if (
- unset GIT_DIR &&
+ unset GIT_DIR
mkdir plain-wt &&
cd plain-wt &&
GIT_WORK_TREE=$(pwd) git init
@@ -48,7 +48,7 @@ test_expect_success 'plain with GIT_WORK_TREE' '
test_expect_success 'plain bare' '
(
- unset GIT_DIR GIT_WORK_TREE GIT_CONFIG &&
+ unset GIT_DIR GIT_WORK_TREE GIT_CONFIG
mkdir plain-bare-1 &&
cd plain-bare-1 &&
git --bare init
@@ -58,7 +58,7 @@ test_expect_success 'plain bare' '
test_expect_success 'plain bare with GIT_WORK_TREE' '
if (
- unset GIT_DIR GIT_CONFIG &&
+ unset GIT_DIR GIT_CONFIG
mkdir plain-bare-2 &&
cd plain-bare-2 &&
GIT_WORK_TREE=$(pwd) git --bare init
@@ -72,7 +72,7 @@ test_expect_success 'plain bare with GIT_WORK_TREE' '
test_expect_success 'GIT_DIR bare' '
(
- unset GIT_CONFIG &&
+ unset GIT_CONFIG
mkdir git-dir-bare.git &&
GIT_DIR=git-dir-bare.git git init
) &&
@@ -82,7 +82,7 @@ test_expect_success 'GIT_DIR bare' '
test_expect_success 'GIT_DIR non-bare' '
(
- unset GIT_CONFIG &&
+ unset GIT_CONFIG
mkdir non-bare &&
cd non-bare &&
GIT_DIR=.git git init
@@ -93,7 +93,7 @@ test_expect_success 'GIT_DIR non-bare' '
test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' '
(
- unset GIT_CONFIG &&
+ unset GIT_CONFIG
mkdir git-dir-wt-1.git &&
GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-1.git git init
) &&
@@ -103,7 +103,7 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' '
test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' '
if (
- unset GIT_CONFIG &&
+ unset GIT_CONFIG
mkdir git-dir-wt-2.git &&
GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-2.git git --bare init
)
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
new file mode 100755
index 0000000000..47f08a46c2
--- /dev/null
+++ b/t/t0003-attributes.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description=gitattributes
+
+. ./test-lib.sh
+
+attr_check () {
+
+ path="$1"
+ expect="$2"
+
+ git check-attr test -- "$path" >actual &&
+ echo "$path: test: $2" >expect &&
+ diff -u expect actual
+
+}
+
+
+test_expect_success 'setup' '
+
+ mkdir -p a/b/d a/c &&
+ (
+ echo "f test=f"
+ ) >.gitattributes &&
+ (
+ echo "g test=a/g" &&
+ echo "b/g test=a/b/g"
+ ) >a/.gitattributes &&
+ (
+ echo "h test=a/b/h" &&
+ echo "d/* test=a/b/d/*"
+ ) >a/b/.gitattributes
+
+'
+
+test_expect_success 'attribute test' '
+
+ attr_check f f &&
+ attr_check a/f f &&
+ attr_check a/c/f f &&
+ attr_check a/g a/g &&
+ 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/*"
+
+'
+
+test_done
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
index 0807d9f01a..90ea081db6 100755
--- a/t/t0020-crlf.sh
+++ b/t/t0020-crlf.sh
@@ -5,7 +5,11 @@ test_description='CRLF conversion'
. ./test-lib.sh
q_to_nul () {
- tr Q '\0'
+ tr Q '\000'
+}
+
+q_to_cr () {
+ tr Q '\015'
}
append_cr () {
@@ -19,7 +23,7 @@ remove_cr () {
test_expect_success setup '
- git repo-config core.autocrlf false &&
+ git config core.autocrlf false &&
for w in Hello world how are you; do echo $w; done >one &&
mkdir dir &&
@@ -42,11 +46,65 @@ test_expect_success setup '
echo happy.
'
+test_expect_success 'safecrlf: autocrlf=input, all CRLF' '
+
+ git config core.autocrlf input &&
+ git config core.safecrlf true &&
+
+ for w in I am all CRLF; do echo $w; done | append_cr >allcrlf &&
+ ! git add allcrlf
+'
+
+test_expect_success 'safecrlf: autocrlf=input, mixed LF/CRLF' '
+
+ git config core.autocrlf input &&
+ git config core.safecrlf true &&
+
+ for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed &&
+ ! git add mixed
+'
+
+test_expect_success 'safecrlf: autocrlf=true, all LF' '
+
+ git config core.autocrlf true &&
+ git config core.safecrlf true &&
+
+ for w in I am all LF; do echo $w; done >alllf &&
+ ! git add alllf
+'
+
+test_expect_success 'safecrlf: autocrlf=true mixed LF/CRLF' '
+
+ git config core.autocrlf true &&
+ git config core.safecrlf true &&
+
+ for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed &&
+ ! git add mixed
+'
+
+test_expect_success 'safecrlf: print warning only once' '
+
+ git config core.autocrlf input &&
+ git config core.safecrlf warn &&
+
+ for w in I am all LF; do echo $w; done >doublewarn &&
+ git add doublewarn &&
+ git commit -m "nowarn" &&
+ for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >doublewarn &&
+ test $(git add doublewarn 2>&1 | grep "CRLF will be replaced by LF" | wc -l) = 1
+'
+
+test_expect_success 'switch off autocrlf, safecrlf, reset HEAD' '
+ git config core.autocrlf false &&
+ git config core.safecrlf false &&
+ git reset --hard HEAD^
+'
+
test_expect_success 'update with autocrlf=input' '
rm -f tmp one dir/two three &&
git read-tree --reset -u HEAD &&
- git repo-config core.autocrlf input &&
+ git config core.autocrlf input &&
for f in one dir/two
do
@@ -70,7 +128,7 @@ test_expect_success 'update with autocrlf=true' '
rm -f tmp one dir/two three &&
git read-tree --reset -u HEAD &&
- git repo-config core.autocrlf true &&
+ git config core.autocrlf true &&
for f in one dir/two
do
@@ -93,7 +151,7 @@ test_expect_success 'update with autocrlf=true' '
test_expect_success 'checkout with autocrlf=true' '
rm -f tmp one dir/two three &&
- git repo-config core.autocrlf true &&
+ git config core.autocrlf true &&
git read-tree --reset -u HEAD &&
for f in one dir/two
@@ -117,7 +175,7 @@ test_expect_success 'checkout with autocrlf=true' '
test_expect_success 'checkout with autocrlf=input' '
rm -f tmp one dir/two three &&
- git repo-config core.autocrlf input &&
+ git config core.autocrlf input &&
git read-tree --reset -u HEAD &&
for f in one dir/two
@@ -143,7 +201,7 @@ test_expect_success 'checkout with autocrlf=input' '
test_expect_success 'apply patch (autocrlf=input)' '
rm -f tmp one dir/two three &&
- git repo-config core.autocrlf input &&
+ git config core.autocrlf input &&
git read-tree --reset -u HEAD &&
git apply patch.file &&
@@ -156,7 +214,7 @@ test_expect_success 'apply patch (autocrlf=input)' '
test_expect_success 'apply patch --cached (autocrlf=input)' '
rm -f tmp one dir/two three &&
- git repo-config core.autocrlf input &&
+ git config core.autocrlf input &&
git read-tree --reset -u HEAD &&
git apply --cached patch.file &&
@@ -169,7 +227,7 @@ test_expect_success 'apply patch --cached (autocrlf=input)' '
test_expect_success 'apply patch --index (autocrlf=input)' '
rm -f tmp one dir/two three &&
- git repo-config core.autocrlf input &&
+ git config core.autocrlf input &&
git read-tree --reset -u HEAD &&
git apply --index patch.file &&
@@ -183,7 +241,7 @@ test_expect_success 'apply patch --index (autocrlf=input)' '
test_expect_success 'apply patch (autocrlf=true)' '
rm -f tmp one dir/two three &&
- git repo-config core.autocrlf true &&
+ git config core.autocrlf true &&
git read-tree --reset -u HEAD &&
git apply patch.file &&
@@ -196,7 +254,7 @@ test_expect_success 'apply patch (autocrlf=true)' '
test_expect_success 'apply patch --cached (autocrlf=true)' '
rm -f tmp one dir/two three &&
- git repo-config core.autocrlf true &&
+ git config core.autocrlf true &&
git read-tree --reset -u HEAD &&
git apply --cached patch.file &&
@@ -209,7 +267,7 @@ test_expect_success 'apply patch --cached (autocrlf=true)' '
test_expect_success 'apply patch --index (autocrlf=true)' '
rm -f tmp one dir/two three &&
- git repo-config core.autocrlf true &&
+ git config core.autocrlf true &&
git read-tree --reset -u HEAD &&
git apply --index patch.file &&
@@ -224,7 +282,7 @@ test_expect_success '.gitattributes says two is binary' '
rm -f tmp one dir/two three &&
echo "two -crlf" >.gitattributes &&
- git repo-config core.autocrlf true &&
+ git config core.autocrlf true &&
git read-tree --reset -u HEAD &&
if remove_cr dir/two >/dev/null
@@ -371,4 +429,11 @@ test_expect_success 'in-tree .gitattributes (4)' '
}
'
+test_expect_success 'invalid .gitattributes (must not crash)' '
+
+ echo "three +crlf" >>.gitattributes &&
+ git diff
+
+'
+
test_done
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index a839f4e074..cb860296ed 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -42,7 +42,12 @@ test_expect_success check '
git diff --raw --exit-code :test :test.i &&
id=$(git rev-parse --verify :test) &&
embedded=$(sed -ne "$script" test.i) &&
- test "z$id" = "z$embedded"
+ test "z$id" = "z$embedded" &&
+
+ git cat-file blob :test.t > test.r &&
+
+ ./rot13.sh < test.o > test.t &&
+ cmp test.r test.t
'
# If an expanded ident ever gets into the repository, we want to make sure that
diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh
index cad95f35ad..818c8621f2 100755
--- a/t/t0030-stripspace.sh
+++ b/t/t0030-stripspace.sh
@@ -243,14 +243,14 @@ test_expect_success \
test `printf "$ttt$sss$sss$sss" | git stripspace | wc -l` -gt 0
'
-test_expect_failure \
+test_expect_success \
'text plus spaces without newline at end should not show spaces' '
- printf "$ttt$sss" | git stripspace | grep -q " " ||
- printf "$ttt$ttt$sss" | git stripspace | grep -q " " ||
- printf "$ttt$ttt$ttt$sss" | git stripspace | grep -q " " ||
- printf "$ttt$sss$sss" | git stripspace | grep -q " " ||
- printf "$ttt$ttt$sss$sss" | git stripspace | grep -q " " ||
- printf "$ttt$sss$sss$sss" | git stripspace | grep -q " "
+ ! (printf "$ttt$sss" | git stripspace | grep -q " ") &&
+ ! (printf "$ttt$ttt$sss" | git stripspace | grep -q " ") &&
+ ! (printf "$ttt$ttt$ttt$sss" | git stripspace | grep -q " ") &&
+ ! (printf "$ttt$sss$sss" | git stripspace | grep -q " ") &&
+ ! (printf "$ttt$ttt$sss$sss" | git stripspace | grep -q " ") &&
+ ! (printf "$ttt$sss$sss$sss" | git stripspace | grep -q " ")
'
test_expect_success \
@@ -280,14 +280,14 @@ test_expect_success \
git diff expect actual
'
-test_expect_failure \
+test_expect_success \
'text plus spaces at end should not show spaces' '
- echo "$ttt$sss" | git stripspace | grep -q " " ||
- echo "$ttt$ttt$sss" | git stripspace | grep -q " " ||
- echo "$ttt$ttt$ttt$sss" | git stripspace | grep -q " " ||
- echo "$ttt$sss$sss" | git stripspace | grep -q " " ||
- echo "$ttt$ttt$sss$sss" | git stripspace | grep -q " " ||
- echo "$ttt$sss$sss$sss" | git stripspace | grep -q " "
+ ! (echo "$ttt$sss" | git stripspace | grep -q " ") &&
+ ! (echo "$ttt$ttt$sss" | git stripspace | grep -q " ") &&
+ ! (echo "$ttt$ttt$ttt$sss" | git stripspace | grep -q " ") &&
+ ! (echo "$ttt$sss$sss" | git stripspace | grep -q " ") &&
+ ! (echo "$ttt$ttt$sss$sss" | git stripspace | grep -q " ") &&
+ ! (echo "$ttt$sss$sss$sss" | git stripspace | grep -q " ")
'
test_expect_success \
@@ -339,13 +339,13 @@ test_expect_success \
git diff expect actual
'
-test_expect_failure \
+test_expect_success \
'spaces without newline at end should not show spaces' '
- printf "" | git stripspace | grep -q " " ||
- printf "$sss" | git stripspace | grep -q " " ||
- printf "$sss$sss" | git stripspace | grep -q " " ||
- printf "$sss$sss$sss" | git stripspace | grep -q " " ||
- printf "$sss$sss$sss$sss" | git stripspace | grep -q " "
+ ! (printf "" | git stripspace | grep -q " ") &&
+ ! (printf "$sss" | git stripspace | grep -q " ") &&
+ ! (printf "$sss$sss" | git stripspace | grep -q " ") &&
+ ! (printf "$sss$sss$sss" | git stripspace | grep -q " ") &&
+ ! (printf "$sss$sss$sss$sss" | git stripspace | grep -q " ")
'
test_expect_success \
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
new file mode 100755
index 0000000000..0e2933a984
--- /dev/null
+++ b/t/t0040-parse-options.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Schindelin
+#
+
+test_description='our own option parser'
+
+. ./test-lib.sh
+
+cat > expect.err << EOF
+usage: test-parse-options <options>
+
+ -b, --boolean get a boolean
+ -i, --integer <n> get a integer
+ -j <n> get a integer, too
+
+string options
+ -s, --string <string>
+ get a string
+ --string2 <str> get another string
+ --st <st> get another string (pervert ordering)
+ -o <str> get another string
+
+EOF
+
+test_expect_success 'test help' '
+ ! test-parse-options -h > output 2> output.err &&
+ test ! -s output &&
+ git diff expect.err output.err
+'
+
+cat > expect << EOF
+boolean: 2
+integer: 1729
+string: 123
+EOF
+
+test_expect_success 'short options' '
+ test-parse-options -s123 -b -i 1729 -b > output 2> output.err &&
+ git diff expect output &&
+ test ! -s output.err
+'
+cat > expect << EOF
+boolean: 2
+integer: 1729
+string: 321
+EOF
+
+test_expect_success 'long options' '
+ test-parse-options --boolean --integer 1729 --boolean --string2=321 \
+ > output 2> output.err &&
+ test ! -s output.err &&
+ git diff expect output
+'
+
+cat > expect << EOF
+boolean: 1
+integer: 13
+string: 123
+arg 00: a1
+arg 01: b1
+arg 02: --boolean
+EOF
+
+test_expect_success 'intermingled arguments' '
+ test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \
+ > output 2> output.err &&
+ test ! -s output.err &&
+ git diff expect output
+'
+
+cat > expect << EOF
+boolean: 0
+integer: 2
+string: (not set)
+EOF
+
+test_expect_success 'unambiguously abbreviated option' '
+ test-parse-options --int 2 --boolean --no-bo > output 2> output.err &&
+ test ! -s output.err &&
+ git diff expect output
+'
+
+test_expect_success 'unambiguously abbreviated option with "="' '
+ test-parse-options --int=2 > output 2> output.err &&
+ test ! -s output.err &&
+ git diff expect output
+'
+
+test_expect_success 'ambiguously abbreviated option' '
+ test-parse-options --strin 123;
+ test $? = 129
+'
+
+cat > expect << EOF
+boolean: 0
+integer: 0
+string: 123
+EOF
+
+test_expect_success 'non ambiguous option (after two options it abbreviates)' '
+ test-parse-options --st 123 > output 2> output.err &&
+ test ! -s output.err &&
+ git diff expect output
+'
+
+cat > expect.err << EOF
+error: did you mean \`--boolean\` (with two dashes ?)
+EOF
+
+test_expect_success 'detect possible typos' '
+ ! test-parse-options -boolean > output 2> output.err &&
+ test ! -s output &&
+ git diff expect.err output.err
+'
+
+test_done
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
index 37add1b504..6c065bfa21 100755
--- a/t/t1000-read-tree-m-3way.sh
+++ b/t/t1000-read-tree-m-3way.sh
@@ -210,12 +210,12 @@ DF (file) when tree B require DF to be a directory by having DF/DF
END_OF_CASE_TABLE
-test_expect_failure \
- '1 - must not have an entry not in A.' \
- "rm -f .git/index XX &&
+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 &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'2 - must match B in !O && !A && B case.' \
@@ -248,13 +248,14 @@ test_expect_success \
echo extra >>AN &&
git read-tree -m $tree_O $tree_A $tree_B"
-test_expect_failure \
- '3 (fail) - must match A in !O && A && !B case.' \
- "rm -f .git/index AN &&
+test_expect_success \
+ '3 (fail) - must match A in !O && A && !B case.' "
+ rm -f .git/index AN &&
cp .orig-A/AN AN &&
echo extra >>AN &&
git update-index --add AN &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'4 - must match and be up-to-date in !O && A && B && A!=B case.' \
@@ -264,21 +265,23 @@ test_expect_success \
git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
- "rm -f .git/index AA &&
+test_expect_success \
+ '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' "
+ rm -f .git/index AA &&
cp .orig-A/AA AA &&
git update-index --add AA &&
echo extra >>AA &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
-test_expect_failure \
- '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
- "rm -f .git/index AA &&
+test_expect_success \
+ '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' "
+ rm -f .git/index AA &&
cp .orig-A/AA AA &&
echo extra >>AA &&
git update-index --add AA &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'5 - must match in !O && A && B && A==B case.' \
@@ -297,34 +300,38 @@ test_expect_success \
git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '5 (fail) - must match A in !O && A && B && A==B case.' \
- "rm -f .git/index LL &&
+test_expect_success \
+ '5 (fail) - must match A in !O && A && B && A==B case.' "
+ rm -f .git/index LL &&
cp .orig-A/LL LL &&
echo extra >>LL &&
git update-index --add LL &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
-test_expect_failure \
- '6 - must not exist in O && !A && !B case' \
- "rm -f .git/index DD &&
+test_expect_success \
+ '6 - must not exist in O && !A && !B case' "
+ rm -f .git/index DD &&
echo DD >DD
git update-index --add DD &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
-test_expect_failure \
- '7 - must not exist in O && !A && B && O!=B case' \
- "rm -f .git/index DM &&
+test_expect_success \
+ '7 - must not exist in O && !A && B && O!=B case' "
+ rm -f .git/index DM &&
cp .orig-B/DM DM &&
git update-index --add DM &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
-test_expect_failure \
- '8 - must not exist in O && !A && B && O==B case' \
- "rm -f .git/index DN &&
+test_expect_success \
+ '8 - must not exist in O && !A && B && O==B case' "
+ rm -f .git/index DN &&
cp .orig-B/DN DN &&
git update-index --add DN &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'9 - must match and be up-to-date in O && A && !B && O!=A case' \
@@ -334,21 +341,23 @@ test_expect_success \
git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
- "rm -f .git/index MD &&
+test_expect_success \
+ '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' "
+ rm -f .git/index MD &&
cp .orig-A/MD MD &&
git update-index --add MD &&
echo extra >>MD &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
-test_expect_failure \
- '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
- "rm -f .git/index MD &&
+test_expect_success \
+ '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' "
+ rm -f .git/index MD &&
cp .orig-A/MD MD &&
echo extra >>MD &&
git update-index --add MD &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'10 - must match and be up-to-date in O && A && !B && O==A case' \
@@ -358,21 +367,23 @@ test_expect_success \
git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
- "rm -f .git/index ND &&
+test_expect_success \
+ '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' "
+ rm -f .git/index ND &&
cp .orig-A/ND ND &&
git update-index --add ND &&
echo extra >>ND &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
-test_expect_failure \
- '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
- "rm -f .git/index ND &&
+test_expect_success \
+ '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' "
+ rm -f .git/index ND &&
cp .orig-A/ND ND &&
echo extra >>ND &&
git update-index --add ND &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
@@ -382,21 +393,23 @@ test_expect_success \
git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
- "rm -f .git/index MM &&
+test_expect_success \
+ '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' "
+ rm -f .git/index MM &&
cp .orig-A/MM MM &&
git update-index --add MM &&
echo extra >>MM &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
-test_expect_failure \
- '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
- "rm -f .git/index MM &&
+test_expect_success \
+ '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' "
+ rm -f .git/index MM &&
cp .orig-A/MM MM &&
echo extra >>MM &&
git update-index --add MM &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'12 - must match A in O && A && B && O!=A && A==B case' \
@@ -415,13 +428,14 @@ test_expect_success \
git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '12 (fail) - must match A in O && A && B && O!=A && A==B case' \
- "rm -f .git/index SS &&
+test_expect_success \
+ '12 (fail) - must match A in O && A && B && O!=A && A==B case' "
+ rm -f .git/index SS &&
cp .orig-A/SS SS &&
echo extra >>SS &&
git update-index --add SS &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'13 - must match A in O && A && B && O!=A && O==B case' \
@@ -457,21 +471,23 @@ test_expect_success \
git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
- "rm -f .git/index NM &&
+test_expect_success \
+ '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' "
+ rm -f .git/index NM &&
cp .orig-A/NM NM &&
git update-index --add NM &&
echo extra >>NM &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
-test_expect_failure \
- '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
- "rm -f .git/index NM &&
+test_expect_success \
+ '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' "
+ rm -f .git/index NM &&
cp .orig-A/NM NM &&
echo extra >>NM &&
git update-index --add NM &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
test_expect_success \
'15 - must match A in O && A && B && O==A && O==B case' \
@@ -490,13 +506,14 @@ test_expect_success \
git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '15 (fail) - must match A in O && A && B && O==A && O==B case' \
- "rm -f .git/index NN &&
+test_expect_success \
+ '15 (fail) - must match A in O && A && B && O==A && O==B case' "
+ rm -f .git/index NN &&
cp .orig-A/NN NN &&
echo extra >>NN &&
git update-index --add NN &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ ! git read-tree -m $tree_O $tree_A $tree_B
+"
# #16
test_expect_success \
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index 991d3c5e9c..dcb3108c29 100755
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
@@ -101,8 +101,8 @@ echo "Play, play, play" >>hello
echo "Lots of fun" >>example
git commit -m 'Some fun.' -i hello example
-test_expect_failure 'git resolve now fails' '
- git merge -m "Merge work in mybranch" mybranch
+test_expect_success 'git resolve now fails' '
+ ! git merge -m "Merge work in mybranch" mybranch
'
cat > hello << EOF
@@ -156,6 +156,8 @@ test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.outp
test_expect_success 'git repack' 'git repack'
test_expect_success 'git prune-packed' 'git prune-packed'
-test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
+test_expect_success '-> only packed objects' '
+ ! find -type f .git/objects/[0-9a-f][0-9a-f]
+'
test_done
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 1d2bf2c060..4928a57114 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -71,6 +71,25 @@ EOF
test_expect_success 'non-match result' 'cmp .git/config expect'
+cat > .git/config <<\EOF
+[alpha]
+bar = foo
+[beta]
+baz = multiple \
+lines
+EOF
+
+test_expect_success 'unset with cont. lines' \
+ 'git config --unset beta.baz'
+
+cat > expect <<\EOF
+[alpha]
+bar = foo
+[beta]
+EOF
+
+test_expect_success 'unset with cont. lines is correct' 'cmp .git/config expect'
+
cat > .git/config << EOF
[beta] ; silly comment # another comment
noIndent= sillyValue ; 'nother silly comment
@@ -181,8 +200,9 @@ test_expect_success 'non-match' \
test_expect_success 'non-match value' \
'test wow = $(git config --get nextsection.nonewline !for)'
-test_expect_failure 'ambiguous get' \
- 'git config --get nextsection.nonewline'
+test_expect_success 'ambiguous get' '
+ ! git config --get nextsection.nonewline
+'
test_expect_success 'get multivar' \
'git config --get-all nextsection.nonewline'
@@ -202,13 +222,17 @@ EOF
test_expect_success 'multivar replace' 'cmp .git/config expect'
-test_expect_failure 'ambiguous value' 'git config nextsection.nonewline'
+test_expect_success 'ambiguous value' '
+ ! git config nextsection.nonewline
+'
-test_expect_failure 'ambiguous unset' \
- 'git config --unset nextsection.nonewline'
+test_expect_success 'ambiguous unset' '
+ ! git config --unset nextsection.nonewline
+'
-test_expect_failure 'invalid unset' \
- 'git config --unset somesection.nonewline'
+test_expect_success 'invalid unset' '
+ ! git config --unset somesection.nonewline
+'
git config --unset nextsection.nonewline "wow3$"
@@ -224,7 +248,7 @@ EOF
test_expect_success 'multivar unset' 'cmp .git/config expect'
-test_expect_failure 'invalid key' 'git config inval.2key blabla'
+test_expect_success 'invalid key' '! git config inval.2key blabla'
test_expect_success 'correct key' 'git config 123456.a123 987'
@@ -278,17 +302,40 @@ test_expect_success '--add' \
cat > .git/config << EOF
[novalue]
variable
+[emptyvalue]
+ variable =
EOF
test_expect_success 'get variable with no value' \
'git config --get novalue.variable ^$'
+test_expect_success 'get variable with empty value' \
+ 'git config --get emptyvalue.variable ^$'
+
echo novalue.variable > expect
test_expect_success 'get-regexp variable with no value' \
'git config --get-regexp novalue > output &&
cmp output expect'
+echo 'emptyvalue.variable ' > expect
+
+test_expect_success 'get-regexp variable with empty value' \
+ 'git config --get-regexp emptyvalue > output &&
+ cmp output expect'
+
+echo true > expect
+
+test_expect_success 'get bool variable with no value' \
+ 'git config --bool novalue.variable > output &&
+ cmp output expect'
+
+echo false > expect
+
+test_expect_success 'get bool variable with empty value' \
+ 'git config --bool emptyvalue.variable > output &&
+ cmp output expect'
+
git config > output 2>&1
test_expect_success 'no arguments, but no crash' \
@@ -382,8 +429,9 @@ EOF
test_expect_success "rename succeeded" "git diff expect .git/config"
-test_expect_failure "rename non-existing section" \
- 'git config --rename-section branch."world domination" branch.drei'
+test_expect_success "rename non-existing section" '
+ ! git config --rename-section branch."world domination" branch.drei
+'
test_expect_success "rename succeeded" "git diff expect .git/config"
@@ -448,6 +496,23 @@ test_expect_success numbers '
test z1048576 = "z$m"
'
+cat > expect <<EOF
+fatal: bad config value for 'aninvalid.unit' in .git/config
+EOF
+
+test_expect_success 'invalid unit' '
+
+ git config aninvalid.unit "1auto" &&
+ s=$(git config aninvalid.unit) &&
+ test "z1auto" = "z$s" &&
+ if git config --int --get aninvalid.unit 2>actual
+ then
+ echo config should have failed
+ false
+ fi &&
+ cmp actual expect
+'
+
cat > expect << EOF
true
false
@@ -477,14 +542,14 @@ test_expect_success bool '
done &&
cmp expect result'
-test_expect_failure 'invalid bool (--get)' '
+test_expect_success 'invalid bool (--get)' '
git config bool.nobool foobar &&
- git config --bool --get bool.nobool'
+ ! git config --bool --get bool.nobool'
-test_expect_failure 'invalid bool (set)' '
+test_expect_success 'invalid bool (set)' '
- git config --bool bool.nobool foobar'
+ ! git config --bool bool.nobool foobar'
rm .git/config
@@ -545,8 +610,9 @@ EOF
test_expect_success 'quoting' 'cmp .git/config expect'
-test_expect_failure 'key with newline' 'git config key.with\\\
-newline 123'
+test_expect_success 'key with newline' '
+ ! git config "key.with
+newline" 123'
test_expect_success 'value with newline' 'git config key.sub value.with\\\
newline'
@@ -591,12 +657,12 @@ Qsection.sub=section.val4
Qsection.sub=section.val5Q
EOF
-git config --null --list | tr '[\000]' 'Q' > result
+git config --null --list | tr '\000' 'Q' > result
echo >>result
test_expect_success '--null --list' 'cmp result expect'
-git config --null --get-regexp 'val[0-9]' | tr '[\000]' 'Q' > result
+git config --null --get-regexp 'val[0-9]' | tr '\000' 'Q' > result
echo >>result
test_expect_success '--null --get-regexp' 'cmp result expect'
diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh
new file mode 100755
index 0000000000..9be0770e76
--- /dev/null
+++ b/t/t1302-repo-version.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Nguyá»…n Thái Ngá»c Duy
+#
+
+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_create_repo "test"
+test_create_repo "test2"
+
+GIT_CONFIG=test2/.git/config git config core.repositoryformatversion 99 || exit 1
+
+test_expect_success 'gitdir selection on normal repos' '
+ (test "$(git config core.repositoryformatversion)" = 0 &&
+ cd test &&
+ test "$(git config core.repositoryformatversion)" = 0)'
+
+# 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)'
+
+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)'
+
+test_expect_success 'gitdir required mode on unsupported repo' '
+ (cd test2 && ! git apply --check --index ../test.patch)
+'
+
+test_done
diff --git a/t/t1303-wacky-config.sh b/t/t1303-wacky-config.sh
new file mode 100755
index 0000000000..99985dcd79
--- /dev/null
+++ b/t/t1303-wacky-config.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+test_description='Test wacky input to git config'
+. ./test-lib.sh
+
+setup() {
+ (printf "[section]\n" &&
+ printf " key = foo") >.git/config
+}
+
+check() {
+ echo "$2" >expected
+ git config --get "$1" >actual
+ git diff actual expected
+}
+
+test_expect_success 'modify same key' '
+ setup &&
+ git config section.key bar &&
+ check section.key bar
+'
+
+test_expect_success 'add key in same section' '
+ setup &&
+ git config section.other bar &&
+ check section.key foo &&
+ check section.other bar
+'
+
+test_expect_success 'add key in different section' '
+ setup &&
+ git config section2.key bar &&
+ check section.key foo &&
+ check section2.key bar
+'
+
+test_done
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index ce045b2a57..78cd41245b 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -7,12 +7,19 @@ test_description='Test git update-ref and basic ref logging'
. ./test-lib.sh
Z=0000000000000000000000000000000000000000
-A=1111111111111111111111111111111111111111
-B=2222222222222222222222222222222222222222
-C=3333333333333333333333333333333333333333
-D=4444444444444444444444444444444444444444
-E=5555555555555555555555555555555555555555
-F=6666666666666666666666666666666666666666
+
+test_expect_success setup '
+
+ for name in A B C D E F
+ do
+ test_tick &&
+ T=$(git write-tree) &&
+ sha1=$(echo $name | git commit-tree $T) &&
+ eval $name=$sha1
+ done
+
+'
+
m=refs/heads/master
n_dir=refs/heads/gu
n=$n_dir/fixes
@@ -44,23 +51,23 @@ test_expect_success \
test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
-test_expect_failure \
- '(not) create HEAD with old sha1' \
- "git update-ref HEAD $A $B"
-test_expect_failure \
- "(not) prior created .git/$m" \
- "test -f .git/$m"
+test_expect_success '(not) create HEAD with old sha1' "
+ ! git update-ref HEAD $A $B
+"
+test_expect_success "(not) prior created .git/$m" "
+ ! test -f .git/$m
+"
rm -f .git/$m
test_expect_success \
"create HEAD" \
"git update-ref HEAD $A"
-test_expect_failure \
- '(not) change HEAD with wrong SHA1' \
- "git update-ref HEAD $B $Z"
-test_expect_failure \
- "(not) changed .git/$m" \
- "test $B"' = $(cat .git/'"$m"')'
+test_expect_success '(not) change HEAD with wrong SHA1' "
+ ! git update-ref HEAD $B $Z
+"
+test_expect_success "(not) changed .git/$m" "
+ ! test $B"' = $(cat .git/'"$m"')
+'
rm -f .git/$m
: a repository with working tree always has reflog these days...
@@ -205,7 +212,7 @@ test_expect_success \
echo $h_TEST >.git/MERGE_HEAD &&
GIT_AUTHOR_DATE="2005-05-26 23:45" \
GIT_COMMITTER_DATE="2005-05-26 23:45" git-commit -F M &&
- h_MERGED=$(git rev-parse --verify HEAD)
+ h_MERGED=$(git rev-parse --verify HEAD) &&
rm -f M'
cat >expect <<EOF
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 12a53edfdc..24476bede5 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -199,6 +199,25 @@ test_expect_success 'delete' '
git reflog show master > output &&
test 3 = $(wc -l < output) &&
! grep dragon < output
+
+'
+
+test_expect_success 'prune --expire' '
+
+ before=$(git count-objects | sed "s/ .*//") &&
+ BLOB=$(echo aleph | 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 &&
+ git reset --hard &&
+ git prune --expire=1.hour.ago &&
+ test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+ test -f $BLOB_FILE &&
+ test-chmtime -86500 $BLOB_FILE &&
+ git prune --expire 1.day &&
+ test $before = $(git count-objects | sed "s/ .*//") &&
+ ! test -f $BLOB_FILE
+
'
test_done
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index e474b3f1d5..38a2bf09af 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -33,9 +33,9 @@ test_rev_parse() {
test_rev_parse toplevel false false true ''
cd .git || exit 1
-test_rev_parse .git/ true true false ''
+test_rev_parse .git/ false true false ''
cd objects || exit 1
-test_rev_parse .git/objects/ true true false ''
+test_rev_parse .git/objects/ false true false ''
cd ../.. || exit 1
mkdir -p sub/dir || exit 1
diff --git a/t/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh
index ac84335b0a..5141fab7cf 100755
--- a/t/t2000-checkout-cache-clash.sh
+++ b/t/t2000-checkout-cache-clash.sh
@@ -36,9 +36,9 @@ mkdir path0
date >path0/file0
date >path1
-test_expect_failure \
+test_expect_success \
'git checkout-index without -f should fail on conflicting work tree.' \
- 'git checkout-index -a'
+ '! git checkout-index -a'
test_expect_success \
'git checkout-index with -f should succeed.' \
diff --git a/t/t2002-checkout-cache-u.sh b/t/t2002-checkout-cache-u.sh
index f7a0055920..0f441bcef7 100755
--- a/t/t2002-checkout-cache-u.sh
+++ b/t/t2002-checkout-cache-u.sh
@@ -16,12 +16,12 @@ echo frotz >path0 &&
git update-index --add path0 &&
t=$(git write-tree)'
-test_expect_failure \
+test_expect_success \
'without -u, git checkout-index smudges stat information.' '
rm -f path0 &&
git read-tree $t &&
git checkout-index -f -a &&
-git diff-files | diff - /dev/null'
+! git diff-files | diff - /dev/null'
test_expect_success \
'with -u, git checkout-index picks up stat information from new files.' '
diff --git a/t/t2008-checkout-subdir.sh b/t/t2008-checkout-subdir.sh
new file mode 100755
index 0000000000..4a723dc0e5
--- /dev/null
+++ b/t/t2008-checkout-subdir.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 David Symonds
+
+test_description='git checkout from subdirectories'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ echo "base" > file0 &&
+ git add file0 &&
+ mkdir dir1 &&
+ echo "hello" > dir1/file1 &&
+ git add dir1/file1 &&
+ mkdir dir2 &&
+ echo "bonjour" > dir2/file2 &&
+ git add dir2/file2 &&
+ test_tick &&
+ git commit -m "populate tree"
+
+'
+
+test_expect_success 'remove and restore with relative path' '
+
+ (
+ cd dir1 &&
+ rm ../file0 &&
+ git checkout HEAD -- ../file0 &&
+ test "base" = "$(cat ../file0)" &&
+ rm ../dir2/file2 &&
+ git checkout HEAD -- ../dir2/file2 &&
+ test "bonjour" = "$(cat ../dir2/file2)" &&
+ rm ../file0 ./file1 &&
+ git checkout HEAD -- .. &&
+ test "base" = "$(cat ../file0)" &&
+ test "hello" = "$(cat file1)"
+ )
+
+'
+
+test_expect_success 'checkout with empty prefix' '
+
+ rm file0 &&
+ git checkout HEAD -- file0 &&
+ test "base" = "$(cat file0)"
+
+'
+
+test_expect_success 'checkout with simple prefix' '
+
+ rm dir1/file1 &&
+ git checkout HEAD -- dir1 &&
+ test "hello" = "$(cat dir1/file1)" &&
+ rm dir1/file1 &&
+ git checkout HEAD -- dir1/file1 &&
+ test "hello" = "$(cat dir1/file1)"
+
+'
+
+# This is not expected to work as ls-files was not designed
+# to deal with such. Enable it when ls-files is updated.
+: test_expect_success 'checkout with complex relative path' '
+
+ rm file1 &&
+ git checkout HEAD -- ../dir1/../dir1/file1 && test -f ./file1
+
+'
+
+test_expect_success 'relative path outside tree should fail' \
+ '! git checkout HEAD -- ../../Makefile'
+
+test_expect_success 'incorrect relative path to file should fail (1)' \
+ '! git checkout HEAD -- ../file0'
+
+test_expect_success 'incorrect relative path should fail (2)' \
+ '( cd dir1 && ! git checkout HEAD -- ./file0 )'
+
+test_expect_success 'incorrect relative path should fail (3)' \
+ '( cd dir1 && ! git checkout HEAD -- ../../file0 )'
+
+test_done
diff --git a/t/t2100-update-cache-badpath.sh b/t/t2100-update-cache-badpath.sh
index 04a1ed1a6b..9beaecd18b 100755
--- a/t/t2100-update-cache-badpath.sh
+++ b/t/t2100-update-cache-badpath.sh
@@ -44,8 +44,8 @@ date >path1/file1
for p in path0/file0 path1/file1 path2 path3
do
- test_expect_failure \
+ test_expect_success \
"git update-index to add conflicting path $p should fail." \
- "git update-index --add -- $p"
+ "! git update-index --add -- $p"
done
test_done
diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh
index eb1ced3c37..24f892f793 100755
--- a/t/t2200-add-update.sh
+++ b/t/t2200-add-update.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='git add -u with path limiting
+test_description='git add -u
This test creates a working tree state with three files:
@@ -9,7 +9,10 @@ This test creates a working tree state with three files:
dir/other (untracked)
and issues a git add -u with path limiting on "dir" to add
-only the updates to dir/sub.'
+only the updates to dir/sub.
+
+Also tested are "git add -u" without limiting, and "git add -u"
+without contents changes.'
. ./test-lib.sh
@@ -85,4 +88,27 @@ test_expect_success 'replace a file with a symlink' '
'
+test_expect_success 'add everything changed' '
+
+ git add -u &&
+ test -z "$(git diff-files)"
+
+'
+
+test_expect_success 'touch and then add -u' '
+
+ touch check &&
+ git add -u &&
+ test -z "$(git diff-files)"
+
+'
+
+test_expect_success 'touch and then add explicitly' '
+
+ touch check &&
+ git add check &&
+ test -z "$(git diff-files)"
+
+'
+
test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index ae0639d8f3..b4297bacf2 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -86,7 +86,7 @@ EOF
git config core.excludesFile excludes-file
-git runstatus | grep "^# " > output
+git status | grep "^# " > output
cat > expect << EOF
# .gitignore
@@ -99,4 +99,45 @@ EOF
test_expect_success 'git-status honours core.excludesfile' \
'diff -u expect output'
+test_expect_success 'trailing slash in exclude allows directory match(1)' '
+
+ git ls-files --others --exclude=one/ >output &&
+ if grep "^one/" output
+ then
+ echo Ooops
+ false
+ else
+ : happy
+ fi
+
+'
+
+test_expect_success 'trailing slash in exclude allows directory match (2)' '
+
+ git ls-files --others --exclude=one/two/ >output &&
+ if grep "^one/two/" output
+ then
+ echo Ooops
+ false
+ else
+ : happy
+ fi
+
+'
+
+test_expect_success 'trailing slash in exclude forces directory match (1)' '
+
+ >two
+ git ls-files --others --exclude=two/ >output &&
+ grep "^two" output
+
+'
+
+test_expect_success 'trailing slash in exclude forces directory match (2)' '
+
+ git ls-files --others --exclude=one/a.1/ >output &&
+ grep "^one/a.1" output
+
+'
+
test_done
diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh
index c83f820ad2..f4da869932 100755
--- a/t/t3020-ls-files-error-unmatch.sh
+++ b/t/t3020-ls-files-error-unmatch.sh
@@ -15,9 +15,9 @@ touch foo bar
git update-index --add foo bar
git-commit -m "add foo bar"
-test_expect_failure \
+test_expect_success \
'git ls-files --error-unmatch should fail with unmatched path.' \
- 'git ls-files --error-unmatch foo bar-does-not-match'
+ '! git ls-files --error-unmatch foo bar-does-not-match'
test_expect_success \
'git ls-files --error-unmatch should succeed eith matched paths.' \
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index ef1eeb7d8a..d21081d0f1 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -17,10 +17,11 @@ test_expect_success \
git-commit -m "Initial commit." &&
HEAD=$(git rev-parse --verify HEAD)'
-test_expect_failure \
- 'git branch --help should not have created a bogus branch' \
- 'git branch --help </dev/null >/dev/null 2>/dev/null || :
- test -f .git/refs/heads/--help'
+test_expect_success \
+ 'git branch --help should not have created a bogus branch' '
+ git branch --help </dev/null >/dev/null 2>/dev/null;
+ ! test -f .git/refs/heads/--help
+'
test_expect_success \
'git branch abc should create a branch' \
@@ -71,17 +72,17 @@ test_expect_success \
git branch -m n/n n
test -f .git/logs/refs/heads/n'
-test_expect_failure \
- 'git branch -m o/o o should fail when o/p exists' \
- 'git branch o/o &&
+test_expect_success 'git branch -m o/o o should fail when o/p exists' '
+ git branch o/o &&
git branch o/p &&
- git branch -m o/o o'
+ ! git branch -m o/o o
+'
-test_expect_failure \
- 'git branch -m q r/q should fail when r exists' \
- 'git branch q &&
- git branch r &&
- git branch -m q r/q'
+test_expect_success 'git branch -m q r/q should fail when r exists' '
+ git branch q &&
+ git branch r &&
+ ! git branch -m q r/q
+'
mv .git/config .git/config-saved
@@ -108,12 +109,13 @@ test_expect_success 'config information was renamed, too' \
"test $(git config branch.s.dummy) = Hello &&
! git config branch.s/s/dummy"
-test_expect_failure \
- 'git branch -m u v should fail when the reflog for u is a symlink' \
- 'git branch -l u &&
+test_expect_success \
+ 'git branch -m u v should fail when the reflog for u is a symlink' '
+ git branch -l u &&
mv .git/logs/refs/heads/u real-u &&
ln -s real-u .git/logs/refs/heads/u &&
- git branch -m u v'
+ ! git branch -m u v
+'
test_expect_success 'test tracking setup via --track' \
'git config remote.local.url . &&
diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh
new file mode 100755
index 0000000000..9ef593f0e1
--- /dev/null
+++ b/t/t3201-branch-contains.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+test_description='branch --contains <commit>'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ >file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ git branch side &&
+
+ echo 1 >file &&
+ test_tick &&
+ git commit -a -m "second on master" &&
+
+ git checkout side &&
+ echo 1 >file &&
+ test_tick &&
+ git commit -a -m "second on side" &&
+
+ git merge master
+
+'
+
+test_expect_success 'branch --contains=master' '
+
+ git branch --contains=master >actual &&
+ {
+ echo " master" && echo "* side"
+ } >expect &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'branch --contains master' '
+
+ git branch --contains master >actual &&
+ {
+ echo " master" && echo "* side"
+ } >expect &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'branch --contains=side' '
+
+ git branch --contains=side >actual &&
+ {
+ echo "* side"
+ } >expect &&
+ diff -u expect actual
+
+'
+
+test_done
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
index 4ddc6342a9..b64ccfbc5b 100755
--- a/t/t3210-pack-refs.sh
+++ b/t/t3210-pack-refs.sh
@@ -39,12 +39,12 @@ test_expect_success \
git show-ref b >result &&
diff expect result'
-test_expect_failure \
- 'git branch c/d should barf if branch c exists' \
- 'git branch c &&
+test_expect_success 'git branch c/d should barf if branch c exists' '
+ git branch c &&
git pack-refs --all &&
- rm .git/refs/heads/c &&
- git branch c/d'
+ rm -f .git/refs/heads/c &&
+ ! git branch c/d
+'
test_expect_success \
'see if a branch still exists after git pack-refs --prune' \
@@ -54,11 +54,11 @@ test_expect_success \
git show-ref e >result &&
diff expect result'
-test_expect_failure \
- 'see if git pack-refs --prune remove ref files' \
- 'git branch f &&
+test_expect_success 'see if git pack-refs --prune remove ref files' '
+ git branch f &&
git pack-refs --all --prune &&
- ls .git/refs/heads/f'
+ ! test -f .git/refs/heads/f
+'
test_expect_success \
'git branch g should work when git branch g/h has been deleted' \
@@ -69,11 +69,11 @@ test_expect_success \
git pack-refs --all &&
git branch -d g'
-test_expect_failure \
- 'git branch i/j/k should barf if branch i exists' \
- 'git branch i &&
+test_expect_success 'git branch i/j/k should barf if branch i exists' '
+ git branch i &&
git pack-refs --all --prune &&
- git branch i/j/k'
+ ! git branch i/j/k
+'
test_expect_success \
'test git branch k after branch k/l/m and k/lm have been deleted' \
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
index dc8c369310..98c133db50 100755
--- a/t/t3300-funny-names.sh
+++ b/t/t3300-funny-names.sh
@@ -54,7 +54,7 @@ echo 'just space
no-funny
tabs ," (dq) and spaces' >expected
test_expect_success 'git ls-files -z with-funny' \
- 'git ls-files -z | tr \\0 \\012 >current &&
+ 'git ls-files -z | tr \\000 \\012 >current &&
git diff expected current'
t1=`git write-tree`
@@ -83,11 +83,11 @@ test_expect_success 'git diff-tree with-funny' \
echo 'A
tabs ," (dq) and spaces' >expected
test_expect_success 'git diff-index -z with-funny' \
- 'git diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
+ 'git diff-index -z --name-status $t0 | tr \\000 \\012 >current &&
git diff expected current'
test_expect_success 'git diff-tree -z with-funny' \
- 'git diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
+ 'git diff-tree -z --name-status $t0 $t1 | tr \\000 \\012 >current &&
git diff expected current'
cat > expected <<\EOF
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 95e33b5210..496f4ec172 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -42,9 +42,9 @@ test_expect_success \
test_expect_success 'rebase against master' '
git rebase master'
-test_expect_failure \
+test_expect_success \
'the rebase operation should not have destroyed author information' \
- 'git log | grep "Author:" | grep "<>"'
+ '! git log | grep "Author:" | grep "<>"'
test_expect_success 'rebase after merge master' '
git reset --hard topic &&
diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh
index 0779aaa9ab..7b7d07269a 100755
--- a/t/t3402-rebase-merge.sh
+++ b/t/t3402-rebase-merge.sh
@@ -48,9 +48,14 @@ test_expect_success 'reference merge' '
git merge -s recursive "reference merge" HEAD master
'
+PRE_REBASE=$(git rev-parse test-rebase)
test_expect_success rebase '
git checkout test-rebase &&
- git rebase --merge master
+ GIT_TRACE=1 git rebase --merge master
+'
+
+test_expect_success 'test-rebase@{1} is pre rebase' '
+ test $PRE_REBASE = $(git rev-parse test-rebase@{1})
'
test_expect_success 'merge and rebase should match' '
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index eab053c3e0..0a26099658 100755
--- a/t/t3403-rebase-skip.sh
+++ b/t/t3403-rebase-skip.sh
@@ -31,26 +31,41 @@ test_expect_success setup '
git branch skip-merge skip-reference
'
-test_expect_failure 'rebase with git am -3 (default)' '
- git rebase master
+test_expect_success 'rebase with git am -3 (default)' '
+ ! git rebase master
'
test_expect_success 'rebase --skip with am -3' '
- git reset --hard HEAD &&
git rebase --skip
'
+
+test_expect_success 'rebase moves back to skip-reference' '
+ test refs/heads/skip-reference = $(git symbolic-ref HEAD) &&
+ git branch post-rebase &&
+ git reset --hard pre-rebase &&
+ ! git rebase master &&
+ echo "hello" > hello &&
+ git add hello &&
+ git rebase --continue &&
+ test refs/heads/skip-reference = $(git symbolic-ref HEAD) &&
+ git reset --hard post-rebase
+'
+
test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge'
-test_expect_failure 'rebase with --merge' 'git rebase --merge master'
+test_expect_success 'rebase with --merge' '! git rebase --merge master'
test_expect_success 'rebase --skip with --merge' '
- git reset --hard HEAD &&
git rebase --skip
'
test_expect_success 'merge and reference trees equal' \
'test -z "`git diff-tree skip-merge skip-reference`"'
+test_expect_success 'moved back to branch correctly' '
+ test refs/heads/skip-merge = $(git symbolic-ref HEAD)
+'
+
test_debug 'gitk --all & sleep 1'
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 11139048fe..62e65d704b 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -61,8 +61,8 @@ test_expect_success 'setup' '
git tag I
'
-cat > fake-editor.sh <<\EOF
-#!/bin/sh
+echo "#!$SHELL" >fake-editor
+cat >> fake-editor.sh <<\EOF
case "$1" in
*/COMMIT_EDITMSG)
test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
@@ -149,7 +149,8 @@ test_expect_success 'stop on conflicting pick' '
diff -u expect .git/.dotest-merge/patch &&
diff -u expect2 file1 &&
test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
- test 0 = $(grep -v "^#" < .git/.dotest-merge/todo | wc -l)
+ test 0 = $(grep -ve "^#" -e "^$" < .git/.dotest-merge/git-rebase-todo |
+ wc -l)
'
test_expect_success 'abort' '
@@ -183,6 +184,12 @@ test_expect_success 'retain authorship when squashing' '
git show HEAD | grep "^Author: Twerp Snog"
'
+test_expect_success '-p handles "no changes" gracefully' '
+ HEAD=$(git rev-parse HEAD) &&
+ git rebase -i -p HEAD^ &&
+ test $HEAD = $(git rev-parse HEAD)
+'
+
test_expect_success 'preserve merges with -p' '
git checkout -b to-be-preserved master^ &&
: > unrelated-file &&
@@ -317,4 +324,42 @@ test_expect_success 'rebase a detached HEAD' '
test $grandparent = $(git rev-parse HEAD~2)
'
+test_expect_success 'rebase a commit violating pre-commit' '
+
+ mkdir -p .git/hooks &&
+ PRE_COMMIT=.git/hooks/pre-commit &&
+ echo "#!/bin/sh" > $PRE_COMMIT &&
+ echo "test -z \"\$(git diff --cached --check)\"" >> $PRE_COMMIT &&
+ chmod a+x $PRE_COMMIT &&
+ echo "monde! " >> file1 &&
+ test_tick &&
+ ! git commit -m doesnt-verify file1 &&
+ git commit -m doesnt-verify --no-verify file1 &&
+ test_tick &&
+ FAKE_LINES=2 git rebase -i HEAD~2
+
+'
+
+test_expect_success 'rebase with a file named HEAD in worktree' '
+
+ rm -fr .git/hooks &&
+ git reset --hard &&
+ git checkout -b branch3 A &&
+
+ (
+ GIT_AUTHOR_NAME="Squashed Away" &&
+ export GIT_AUTHOR_NAME &&
+ >HEAD &&
+ git add HEAD &&
+ git commit -m "Add head" &&
+ >BODY &&
+ git add BODY &&
+ git commit -m "Add body"
+ ) &&
+
+ FAKE_LINES="1 squash 2" git rebase -i to-be-rebased &&
+ test "$(git show -s --pretty=format:%an)" = "Squashed Away"
+
+'
+
test_done
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 552af1c4d2..2dbe04fb20 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -44,7 +44,7 @@ test_expect_success setup '
test_expect_success 'cherry-pick after renaming branch' '
git checkout rename2 &&
- EDITOR=: VISUAL=: git cherry-pick added &&
+ git cherry-pick added &&
test -f opos &&
grep "Add extra line at the end" opos
@@ -53,7 +53,7 @@ test_expect_success 'cherry-pick after renaming branch' '
test_expect_success 'revert after renaming branch' '
git checkout rename1 &&
- EDITOR=: VISUAL=: git revert added &&
+ git revert added &&
test -f spoo &&
! grep "Add extra line at the end" spoo
diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh
new file mode 100755
index 0000000000..7c92e261fc
--- /dev/null
+++ b/t/t3502-cherry-pick-merge.sh
@@ -0,0 +1,123 @@
+#!/bin/sh
+
+test_description='cherry picking and reverting a merge
+
+ b---c
+ / /
+ initial---a
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ >A &&
+ >B &&
+ git add A B &&
+ git commit -m "Initial" &&
+ git tag initial &&
+ git branch side &&
+ echo new line >A &&
+ git commit -m "add line to A" A &&
+ git tag a &&
+ git checkout side &&
+ echo new line >B &&
+ git commit -m "add line to B" B &&
+ git tag b &&
+ git checkout master &&
+ git merge side &&
+ git tag c
+
+'
+
+test_expect_success 'cherry-pick a non-merge with -m should fail' '
+
+ git reset --hard &&
+ git checkout a^0 &&
+ ! git cherry-pick -m 1 b &&
+ git diff --exit-code a --
+
+'
+
+test_expect_success 'cherry pick a merge without -m should fail' '
+
+ git reset --hard &&
+ git checkout a^0 &&
+ ! git cherry-pick c &&
+ git diff --exit-code a --
+
+'
+
+test_expect_success 'cherry pick a merge (1)' '
+
+ git reset --hard &&
+ git checkout a^0 &&
+ git cherry-pick -m 1 c &&
+ git diff --exit-code c
+
+'
+
+test_expect_success 'cherry pick a merge (2)' '
+
+ git reset --hard &&
+ git checkout b^0 &&
+ git cherry-pick -m 2 c &&
+ git diff --exit-code c
+
+'
+
+test_expect_success 'cherry pick a merge relative to nonexistent parent should fail' '
+
+ git reset --hard &&
+ git checkout b^0 &&
+ ! git cherry-pick -m 3 c
+
+'
+
+test_expect_success 'revert a non-merge with -m should fail' '
+
+ git reset --hard &&
+ git checkout c^0 &&
+ ! git revert -m 1 b &&
+ git diff --exit-code c
+
+'
+
+test_expect_success 'revert a merge without -m should fail' '
+
+ git reset --hard &&
+ git checkout c^0 &&
+ ! git revert c &&
+ git diff --exit-code c
+
+'
+
+test_expect_success 'revert a merge (1)' '
+
+ git reset --hard &&
+ git checkout c^0 &&
+ git revert -m 1 c &&
+ git diff --exit-code a --
+
+'
+
+test_expect_success 'revert a merge (2)' '
+
+ git reset --hard &&
+ git checkout c^0 &&
+ git revert -m 2 c &&
+ git diff --exit-code b --
+
+'
+
+test_expect_success 'revert a merge relative to nonexistent parent should fail' '
+
+ git reset --hard &&
+ git checkout c^0 &&
+ ! git revert -m 3 c &&
+ git diff --exit-code c
+
+'
+
+test_done
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index 5c001aa489..f542f0af41 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -59,15 +59,16 @@ test_expect_success \
echo "other content" > foo
git rm --cached foo'
-test_expect_failure \
- 'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' \
- 'echo content > foo
+test_expect_success \
+ 'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' '
+ echo content > foo
git add foo
git commit -m foo
echo "other content" > foo
git add foo
echo "yet another content" > foo
- git rm --cached foo'
+ ! git rm --cached foo
+'
test_expect_success \
'Test that git rm --cached -f foo works in case where --cached only did not' \
@@ -106,9 +107,9 @@ embedded'"
if test "$test_failed_remove" = y; then
chmod a-w .
-test_expect_failure \
+test_expect_success \
'Test that "git rm -f" fails if its rm fails' \
- 'git rm -f baz'
+ '! git rm -f baz'
chmod 775 .
else
test_expect_success 'skipping removal failure (perhaps running as root?)' :
@@ -127,7 +128,7 @@ test_expect_success '"rm" command printed' '
git add test-file &&
git commit -m "add file for rm test" &&
git rm test-file > rm-output &&
- test `egrep "^rm " rm-output | wc -l` = 1 &&
+ test `grep "^rm " rm-output | wc -l` = 1 &&
rm -f test-file rm-output &&
git commit -m "remove file from rm test"
'
@@ -212,8 +213,8 @@ test_expect_success 'Recursive with -r -f' '
! test -d frotz
'
-test_expect_failure 'Remove nonexistent file returns nonzero exit status' '
- git rm nonexistent
+test_expect_success 'Remove nonexistent file returns nonzero exit status' '
+ ! git rm nonexistent
'
test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index a328bf57eb..287e058e37 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -104,9 +104,33 @@ test_expect_success 'add ignored ones with -f' '
git ls-files --error-unmatch d.ig/d.if d.ig/d.ig
'
+test_expect_success 'add ignored ones with -f' '
+ rm -f .git/index &&
+ git add -f d.?? &&
+ git ls-files --error-unmatch d.ig/d.if d.ig/d.ig
+'
+
+test_expect_success '.gitignore with subdirectory' '
+
+ rm -f .git/index &&
+ mkdir -p sub/dir &&
+ echo "!dir/a.*" >sub/.gitignore &&
+ >sub/a.ig &&
+ >sub/dir/a.ig &&
+ git add sub/dir &&
+ git ls-files --error-unmatch sub/dir/a.ig &&
+ rm -f .git/index &&
+ (
+ cd sub/dir &&
+ git add .
+ ) &&
+ git ls-files --error-unmatch sub/dir/a.ig
+'
+
mkdir 1 1/2 1/3
touch 1/2/a 1/3/b 1/2/c
test_expect_success 'check correct prefix detection' '
+ rm -f .git/index &&
git add 1/2/a 1/3/b 1/2/c
'
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
new file mode 100755
index 0000000000..c8dc1ac241
--- /dev/null
+++ b/t/t3701-add-interactive.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='add -i basic tests'
+. ./test-lib.sh
+
+test_expect_success 'setup (initial)' '
+ echo content >file &&
+ git add file &&
+ echo more >>file &&
+ echo lines >>file
+'
+test_expect_success 'status works (initial)' '
+ git add -i </dev/null >output &&
+ grep "+1/-0 *+2/-0 file" output
+'
+cat >expected <<EOF
+new file mode 100644
+index 0000000..d95f3ad
+--- /dev/null
++++ b/file
+@@ -0,0 +1 @@
++content
+EOF
+test_expect_success 'diff works (initial)' '
+ (echo d; echo 1) | git add -i >output &&
+ sed -ne "/new file/,/content/p" <output >diff &&
+ diff -u expected diff
+'
+test_expect_success 'revert works (initial)' '
+ git add file &&
+ (echo r; echo 1) | git add -i &&
+ git ls-files >output &&
+ ! grep . output
+'
+
+test_expect_success 'setup (commit)' '
+ echo baseline >file &&
+ git add file &&
+ git commit -m commit &&
+ echo content >>file &&
+ git add file &&
+ echo more >>file &&
+ echo lines >>file
+'
+test_expect_success 'status works (commit)' '
+ git add -i </dev/null >output &&
+ grep "+1/-0 *+2/-0 file" output
+'
+cat >expected <<EOF
+index 180b47c..b6f2c08 100644
+--- a/file
++++ b/file
+@@ -1 +1,2 @@
+ baseline
++content
+EOF
+test_expect_success 'diff works (commit)' '
+ (echo d; echo 1) | git add -i >output &&
+ sed -ne "/^index/,/content/p" <output >diff &&
+ diff -u expected diff
+'
+test_expect_success '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_done
diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh
index 261f199a0c..f2803206f1 100755
--- a/t/t3800-mktag.sh
+++ b/t/t3800-mktag.sh
@@ -12,10 +12,11 @@ test_description='git-mktag: tag object verify test'
# given in the expect.pat file.
check_verify_failure () {
- test_expect_success \
- "$1" \
- 'git-mktag <tag.sig 2>message ||
- egrep -q -f expect.pat message'
+ expect="$2"
+ test_expect_success "$1" '
+ ( ! git-mktag <tag.sig 2>message ) &&
+ grep -q "$expect" message
+ '
}
###########################################################
@@ -33,11 +34,8 @@ cat >tag.sig <<EOF
too short for a tag
EOF
-cat >expect.pat <<EOF
-^error: .*size wrong.*$
-EOF
-
-check_verify_failure 'Tag object length check'
+check_verify_failure 'Tag object length check' \
+ '^error: .*size wrong.*$'
############################################################
# 2. object line label check
@@ -48,11 +46,7 @@ type tag
tag mytag
EOF
-cat >expect.pat <<EOF
-^error: char0: .*"object "$
-EOF
-
-check_verify_failure '"object" line label check'
+check_verify_failure '"object" line label check' '^error: char0: .*"object "$'
############################################################
# 3. object line SHA1 check
@@ -63,11 +57,7 @@ type tag
tag mytag
EOF
-cat >expect.pat <<EOF
-^error: char7: .*SHA1 hash$
-EOF
-
-check_verify_failure '"object" line SHA1 check'
+check_verify_failure '"object" line SHA1 check' '^error: char7: .*SHA1 hash$'
############################################################
# 4. type line label check
@@ -78,11 +68,7 @@ xxxx tag
tag mytag
EOF
-cat >expect.pat <<EOF
-^error: char47: .*"[\]ntype "$
-EOF
-
-check_verify_failure '"type" line label check'
+check_verify_failure '"type" line label check' '^error: char47: .*"\\ntype "$'
############################################################
# 5. type line eol check
@@ -90,11 +76,7 @@ check_verify_failure '"type" line label check'
echo "object 779e9b33986b1c2670fff52c5067603117b3e895" >tag.sig
printf "type tagsssssssssssssssssssssssssssssss" >>tag.sig
-cat >expect.pat <<EOF
-^error: char48: .*"[\]n"$
-EOF
-
-check_verify_failure '"type" line eol check'
+check_verify_failure '"type" line eol check' '^error: char48: .*"\\n"$'
############################################################
# 6. tag line label check #1
@@ -105,11 +87,8 @@ type tag
xxx mytag
EOF
-cat >expect.pat <<EOF
-^error: char57: no "tag " found$
-EOF
-
-check_verify_failure '"tag" line label check #1'
+check_verify_failure '"tag" line label check #1' \
+ '^error: char57: no "tag " found$'
############################################################
# 7. tag line label check #2
@@ -120,11 +99,8 @@ type taggggggggggggggggggggggggggggggg
tag
EOF
-cat >expect.pat <<EOF
-^error: char87: no "tag " found$
-EOF
-
-check_verify_failure '"tag" line label check #2'
+check_verify_failure '"tag" line label check #2' \
+ '^error: char87: no "tag " found$'
############################################################
# 8. type line type-name length check
@@ -135,11 +111,8 @@ type taggggggggggggggggggggggggggggggg
tag mytag
EOF
-cat >expect.pat <<EOF
-^error: char53: type too long$
-EOF
-
-check_verify_failure '"type" line type-name length check'
+check_verify_failure '"type" line type-name length check' \
+ '^error: char53: type too long$'
############################################################
# 9. verify object (SHA1/type) check
@@ -150,11 +123,8 @@ type tagggg
tag mytag
EOF
-cat >expect.pat <<EOF
-^error: char7: could not verify object.*$
-EOF
-
-check_verify_failure 'verify object (SHA1/type) check'
+check_verify_failure 'verify object (SHA1/type) check' \
+ '^error: char7: could not verify object.*$'
############################################################
# 10. verify tag-name check
@@ -165,11 +135,8 @@ type commit
tag my tag
EOF
-cat >expect.pat <<EOF
-^error: char67: could not verify tag name$
-EOF
-
-check_verify_failure 'verify tag-name check'
+check_verify_failure 'verify tag-name check' \
+ '^error: char67: could not verify tag name$'
############################################################
# 11. tagger line label check #1
@@ -180,11 +147,8 @@ type commit
tag mytag
EOF
-cat >expect.pat <<EOF
-^error: char70: could not find "tagger"$
-EOF
-
-check_verify_failure '"tagger" line label check #1'
+check_verify_failure '"tagger" line label check #1' \
+ '^error: char70: could not find "tagger"$'
############################################################
# 12. tagger line label check #2
@@ -196,11 +160,8 @@ tag mytag
tagger
EOF
-cat >expect.pat <<EOF
-^error: char70: could not find "tagger"$
-EOF
-
-check_verify_failure '"tagger" line label check #2'
+check_verify_failure '"tagger" line label check #2' \
+ '^error: char70: could not find "tagger"$'
############################################################
# 13. create valid tag
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 28e9e372f3..235f372832 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -154,7 +154,7 @@ test_expect_success 'cherry-pick(U/U)' '
git reset --hard master &&
git cherry-pick side^ &&
git cherry-pick side &&
- EDITOR=: VISUAL=: git revert HEAD &&
+ git revert HEAD &&
check_encoding 3
'
@@ -169,7 +169,7 @@ test_expect_success 'cherry-pick(L/L)' '
git reset --hard master &&
git cherry-pick side^ &&
git cherry-pick side &&
- EDITOR=: VISUAL=: git revert HEAD &&
+ git revert HEAD &&
check_encoding 3 8859
'
@@ -184,7 +184,7 @@ test_expect_success 'cherry-pick(U/L)' '
git reset --hard master &&
git cherry-pick side^ &&
git cherry-pick side &&
- EDITOR=: VISUAL=: git revert HEAD &&
+ git revert HEAD &&
check_encoding 3
'
@@ -200,7 +200,7 @@ test_expect_success 'cherry-pick(L/U)' '
git reset --hard master &&
git cherry-pick side^ &&
git cherry-pick side &&
- EDITOR=: VISUAL=: git revert HEAD &&
+ git revert HEAD &&
check_encoding 3 8859
'
diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh
index 245fb3babd..73da45f18c 100755
--- a/t/t3902-quoted.sh
+++ b/t/t3902-quoted.sh
@@ -20,6 +20,13 @@ LF='
'
DQ='"'
+echo foo > "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
+}
+
for_each_name () {
for name in \
Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \
diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh
index 7d92ae3e99..c44b27aeb2 100755
--- a/t/t4000-diff-format.sh
+++ b/t/t4000-diff-format.sh
@@ -16,7 +16,7 @@ cat path0 >path1
chmod +x path1
test_expect_success \
- 'update-cache --add two files with and without +x.' \
+ 'update-index --add two files with and without +x.' \
'git update-index --add path0 path1'
mv path0 path0-
diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
index 063e79257a..a32692417d 100755
--- a/t/t4001-diff-rename.sh
+++ b/t/t4001-diff-rename.sh
@@ -27,7 +27,7 @@ Line 15
'
test_expect_success \
- 'update-cache --add a file.' \
+ 'update-index --add a file.' \
'git update-index --add path0'
test_expect_success \
@@ -71,10 +71,10 @@ test_expect_success 'favour same basenames over different ones' '
git rm path1 &&
mkdir subdir &&
git mv another-path subdir/path1 &&
- git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+ git status | grep "renamed: .*path1 -> subdir/path1"'
test_expect_success 'favour same basenames even with minor differences' '
git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
- git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+ git status | grep "renamed: .*path1 -> subdir/path1"'
test_done
diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh
index 5836e3a899..26c2e4aa65 100755
--- a/t/t4008-diff-break-rewrite.sh
+++ b/t/t4008-diff-break-rewrite.sh
@@ -119,14 +119,14 @@ test_expect_success \
'compare_diff_raw expected current'
test_expect_success \
- 'run diff with -B' \
+ 'run diff with -B -M' \
'git diff-index -B -M "$tree" >current'
-# This should not mistake file0 as the copy source of new file1
-# due to type differences.
+# file0 changed from regular to symlink. file1 is very close to the preimage of file0.
+# because we break file0, file1 can become a rename of it.
cat >expected <<\EOF
:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R file0 file1
EOF
test_expect_success \
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 79fdff3f3a..83c54b747f 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -117,4 +117,222 @@ EOF
git diff -b > out
test_expect_success 'another test, with -b' 'git diff expect out'
+test_expect_success 'check mixed spaces and tabs in indent' '
+
+ # This is indented with SP HT SP.
+ echo " foo();" > x &&
+ git diff --check | grep "space before tab in indent"
+
+'
+
+test_expect_success 'check mixed tabs and spaces in indent' '
+
+ # This is indented with HT SP HT.
+ echo " foo();" > x &&
+ git diff --check | grep "space before tab in indent"
+
+'
+
+test_expect_success 'check with no whitespace errors' '
+
+ git commit -m "snapshot" &&
+ echo "foo();" > x &&
+ git diff --check
+
+'
+
+test_expect_success 'check with trailing whitespace' '
+
+ echo "foo(); " > x &&
+ ! git diff --check
+
+'
+
+test_expect_success 'check with space before tab in indent' '
+
+ # indent has space followed by hard tab
+ echo " foo();" > x &&
+ ! git diff --check
+
+'
+
+test_expect_success '--check and --exit-code are not exclusive' '
+
+ git checkout x &&
+ git diff --check --exit-code
+
+'
+
+test_expect_success '--check and --quiet are not exclusive' '
+
+ git diff --check --quiet
+
+'
+
+test_expect_success 'check staged with no whitespace errors' '
+
+ echo "foo();" > x &&
+ git add x &&
+ git diff --cached --check
+
+'
+
+test_expect_success 'check staged with trailing whitespace' '
+
+ echo "foo(); " > x &&
+ git add x &&
+ ! git diff --cached --check
+
+'
+
+test_expect_success 'check staged with space before tab in indent' '
+
+ # indent has space followed by hard tab
+ echo " foo();" > x &&
+ git add x &&
+ ! git diff --cached --check
+
+'
+
+test_expect_success 'check with no whitespace errors (diff-index)' '
+
+ echo "foo();" > x &&
+ git add x &&
+ git diff-index --check HEAD
+
+'
+
+test_expect_success 'check with trailing whitespace (diff-index)' '
+
+ echo "foo(); " > x &&
+ git add x &&
+ ! git diff-index --check HEAD
+
+'
+
+test_expect_success 'check with space before tab in indent (diff-index)' '
+
+ # indent has space followed by hard tab
+ echo " foo();" > x &&
+ git add x &&
+ ! git diff-index --check HEAD
+
+'
+
+test_expect_success 'check staged with no whitespace errors (diff-index)' '
+
+ echo "foo();" > x &&
+ git add x &&
+ git diff-index --cached --check HEAD
+
+'
+
+test_expect_success 'check staged with trailing whitespace (diff-index)' '
+
+ echo "foo(); " > x &&
+ git add x &&
+ ! git diff-index --cached --check HEAD
+
+'
+
+test_expect_success 'check staged with space before tab in indent (diff-index)' '
+
+ # indent has space followed by hard tab
+ echo " foo();" > x &&
+ git add x &&
+ ! git diff-index --cached --check HEAD
+
+'
+
+test_expect_success 'check with no whitespace errors (diff-tree)' '
+
+ echo "foo();" > x &&
+ git commit -m "new commit" x &&
+ git diff-tree --check HEAD^ HEAD
+
+'
+
+test_expect_success 'check with trailing whitespace (diff-tree)' '
+
+ echo "foo(); " > x &&
+ git commit -m "another commit" x &&
+ ! git diff-tree --check HEAD^ HEAD
+
+'
+
+test_expect_success 'check with space before tab in indent (diff-tree)' '
+
+ # indent has space followed by hard tab
+ echo " foo();" > x &&
+ git commit -m "yet another" x &&
+ ! git diff-tree --check HEAD^ HEAD
+
+'
+
+test_expect_success 'check trailing whitespace (trailing-space: off)' '
+
+ git config core.whitespace "-trailing-space" &&
+ echo "foo (); " > x &&
+ git diff --check
+
+'
+
+test_expect_success 'check trailing whitespace (trailing-space: on)' '
+
+ git config core.whitespace "trailing-space" &&
+ echo "foo (); " > x &&
+ ! git diff --check
+
+'
+
+test_expect_success 'check space before tab in indent (space-before-tab: off)' '
+
+ # indent contains space followed by HT
+ git config core.whitespace "-space-before-tab" &&
+ echo " foo ();" > x &&
+ git diff --check
+
+'
+
+test_expect_success 'check space before tab in indent (space-before-tab: on)' '
+
+ # indent contains space followed by HT
+ git config core.whitespace "space-before-tab" &&
+ echo " foo (); " > x &&
+ ! git diff --check
+
+'
+
+test_expect_success 'check spaces as indentation (indent-with-non-tab: off)' '
+
+ git config core.whitespace "-indent-with-non-tab"
+ echo " foo ();" > x &&
+ git diff --check
+
+'
+
+test_expect_success 'check spaces as indentation (indent-with-non-tab: on)' '
+
+ git config core.whitespace "indent-with-non-tab" &&
+ echo " foo ();" > x &&
+ ! 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" &&
+ echo " foo ();" > x &&
+ ! git diff --check
+
+'
+
+test_expect_success 'line numbers in --check output are correct' '
+
+ echo "" > x &&
+ echo "foo(); " >> x &&
+ git diff --check | grep "x:2:"
+
+'
+
test_done
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
index 68731908be..dc0b7126cc 100755
--- a/t/t4017-diff-retval.sh
+++ b/t/t4017-diff-retval.sh
@@ -76,4 +76,33 @@ test_expect_success 'git diff-index --cached HEAD' '
}
'
+test_expect_success '--check --exit-code returns 0 for no difference' '
+
+ git diff --check --exit-code
+
+'
+
+test_expect_success '--check --exit-code returns 1 for a clean difference' '
+
+ echo "good" > a &&
+ git diff --check --exit-code
+ test $? = 1
+
+'
+
+test_expect_success '--check --exit-code returns 3 for a dirty difference' '
+
+ echo "bad " >> a &&
+ git diff --check --exit-code
+ test $? = 3
+
+'
+
+test_expect_success '--check with --no-pager returns 2 for dirty difference' '
+
+ git --no-pager diff --check
+ test $? = 2
+
+'
+
test_done
diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh
index f9db81d3ab..f9db81d3ab 100644..100755
--- a/t/t4018-diff-funcname.sh
+++ b/t/t4018-diff-funcname.sh
diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh
new file mode 100755
index 0000000000..67e080bdbe
--- /dev/null
+++ b/t/t4019-diff-wserror.sh
@@ -0,0 +1,123 @@
+#!/bin/sh
+
+test_description='diff whitespace error detection'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ git config diff.color.whitespace "blue reverse" &&
+ >F &&
+ git add F &&
+ echo " Eight SP indent" >>F &&
+ echo " HT and SP indent" >>F &&
+ echo "With trailing SP " >>F &&
+ echo "No problem" >>F
+
+'
+
+blue_grep='7;34m' ;# ESC [ 7 ; 3 4 m
+
+test_expect_success default '
+
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight normal >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With error >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'without -trail' '
+
+ git config core.whitespace -trail
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight normal >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'without -trail (attribute)' '
+
+ git config --unset core.whitespace
+ echo "F whitespace=-trail" >.gitattributes
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight normal >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'without -space' '
+
+ rm -f .gitattributes
+ git config core.whitespace -space
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With error >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'without -space (attribute)' '
+
+ git config --unset core.whitespace
+ echo "F whitespace=-space" >.gitattributes
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With error >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'with indent-non-tab only' '
+
+ rm -f .gitattributes
+ git config core.whitespace indent,-trailing,-space
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight error >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'with indent-non-tab only (attribute)' '
+
+ git config --unset core.whitespace
+ echo "F whitespace=indent,-trailing,-space" >.gitattributes
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight error >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_done
diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh
index ed3bd5b3fe..888293361d 100755
--- a/t/t4020-diff-external.sh
+++ b/t/t4020-diff-external.sh
@@ -99,7 +99,7 @@ test_expect_success 'no diff with -diff' '
git diff | grep Binary
'
-echo NULZbetweenZwords | tr Z '\0' > file
+echo NULZbetweenZwords | tr Z '\000' > file
test_expect_success 'force diff with "diff"' '
echo >.gitattributes "file diff" &&
diff --git a/t/t4021-format-patch-numbered.sh b/t/t4021-format-patch-numbered.sh
new file mode 100755
index 0000000000..43d64bbd82
--- /dev/null
+++ b/t/t4021-format-patch-numbered.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Brian C Gernhardt
+#
+
+test_description='Format-patch numbering options'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ echo A > file &&
+ git add file &&
+ git commit -m First &&
+
+ echo B >> file &&
+ git commit -a -m Second &&
+
+ echo C >> file &&
+ git commit -a -m Third
+
+'
+
+# Each of these gets used multiple times.
+
+test_num_no_numbered() {
+ cnt=$(grep "^Subject: \[PATCH\]" $1 | wc -l) &&
+ test $cnt = $2
+}
+
+test_single_no_numbered() {
+ test_num_no_numbered $1 1
+}
+
+test_no_numbered() {
+ test_num_no_numbered $1 2
+}
+
+test_single_numbered() {
+ grep "^Subject: \[PATCH 1/1\]" $1
+}
+
+test_numbered() {
+ grep "^Subject: \[PATCH 1/2\]" $1 &&
+ grep "^Subject: \[PATCH 2/2\]" $1
+}
+
+test_expect_success 'Default: no numbered' '
+
+ git format-patch --stdout HEAD~2 >patch0 &&
+ test_no_numbered patch0
+
+'
+
+test_expect_success 'Use --numbered' '
+
+ git format-patch --numbered --stdout HEAD~2 >patch1 &&
+ test_numbered patch1
+
+'
+
+test_expect_success 'format.numbered = true' '
+
+ git config format.numbered true &&
+ git format-patch --stdout HEAD~2 >patch2 &&
+ test_numbered patch2
+
+'
+
+test_expect_success 'format.numbered && single patch' '
+
+ git format-patch --stdout HEAD^ > patch3 &&
+ test_single_numbered patch3
+
+'
+
+test_expect_success 'format.numbered && --no-numbered' '
+
+ git format-patch --no-numbered --stdout HEAD~2 >patch4 &&
+ test_no_numbered patch4
+
+'
+
+test_expect_success 'format.numbered = auto' '
+
+ git config format.numbered auto
+ git format-patch --stdout HEAD~2 > patch5 &&
+ test_numbered patch5
+
+'
+
+test_expect_success 'format.numbered = auto && single patch' '
+
+ git format-patch --stdout HEAD^ > patch6 &&
+ test_single_no_numbered patch6
+
+'
+
+test_expect_success 'format.numbered = auto && --no-numbered' '
+
+ git format-patch --no-numbered --stdout HEAD~2 > patch7 &&
+ test_no_numbered patch7
+
+'
+
+test_done
diff --git a/t/t4021-format-patch-signer-mime.sh b/t/t4021-format-patch-signer-mime.sh
new file mode 100755
index 0000000000..67a70fadab
--- /dev/null
+++ b/t/t4021-format-patch-signer-mime.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='format-patch -s should force MIME encoding as needed'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ >F &&
+ git add F &&
+ git commit -m initial &&
+ echo new line >F &&
+
+ test_tick &&
+ git commit -m "This adds some lines to F" F
+
+'
+
+test_expect_success 'format normally' '
+
+ git format-patch --stdout -1 >output &&
+ ! grep Content-Type output
+
+'
+
+test_expect_success 'format with signoff without funny signer name' '
+
+ git format-patch -s --stdout -1 >output &&
+ ! grep Content-Type output
+
+'
+
+test_expect_success 'format with non ASCII signer name' '
+
+ GIT_COMMITTER_NAME="$B$O$^$N(B $B$U$K$*$&(B" \
+ git format-patch -s --stdout -1 >output &&
+ grep Content-Type output
+
+'
+
+test_done
+
diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh
new file mode 100755
index 0000000000..6de4acbd44
--- /dev/null
+++ b/t/t4022-diff-rewrite.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+test_description='rewrite diff'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ cat ../../COPYING >test &&
+ git add test &&
+ tr 'a-zA-Z' 'n-za-mN-ZA-M' <../../COPYING >test
+
+'
+
+test_expect_success 'detect rewrite' '
+
+ actual=$(git diff-files -B --summary test) &&
+ expr "$actual" : " rewrite test ([0-9]*%)$" || {
+ echo "Eh? <<$actual>>"
+ false
+ }
+
+'
+
+test_done
+
diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh
new file mode 100755
index 0000000000..255604effd
--- /dev/null
+++ b/t/t4023-diff-rename-typechange.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='typechange rename detection'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ rm -f foo bar &&
+ cat ../../COPYING >foo &&
+ ln -s linklink bar &&
+ git add foo bar &&
+ git commit -a -m Initial &&
+ git tag one &&
+
+ rm -f foo bar &&
+ cat ../../COPYING >bar &&
+ ln -s linklink foo &&
+ git add foo bar &&
+ git commit -a -m Second &&
+ git tag two &&
+
+ rm -f foo bar &&
+ cat ../../COPYING >foo &&
+ git add foo &&
+ git commit -a -m Third &&
+ git tag three &&
+
+ mv foo bar &&
+ ln -s linklink foo &&
+ git add foo bar &&
+ git commit -a -m Fourth &&
+ git tag four &&
+
+ # This is purely for sanity check
+
+ rm -f foo bar &&
+ cat ../../COPYING >foo &&
+ cat ../../Makefile >bar &&
+ git add foo bar &&
+ git commit -a -m Fifth &&
+ git tag five &&
+
+ rm -f foo bar &&
+ cat ../../Makefile >foo &&
+ cat ../../COPYING >bar &&
+ git add foo bar &&
+ git commit -a -m Sixth &&
+ git tag six
+
+'
+
+test_expect_success 'cross renames to be detected for regular files' '
+
+ git diff-tree five six -r --name-status -B -M | sort >actual &&
+ {
+ echo "R100 foo bar"
+ echo "R100 bar foo"
+ } | sort >expect &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'cross renames to be detected for typechange' '
+
+ git diff-tree one two -r --name-status -B -M | sort >actual &&
+ {
+ echo "R100 foo bar"
+ echo "R100 bar foo"
+ } | sort >expect &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'moves and renames' '
+
+ git diff-tree three four -r --name-status -B -M | sort >actual &&
+ {
+ echo "R100 foo bar"
+ echo "T100 foo"
+ } | sort >expect &&
+ diff -u expect actual
+
+'
+
+test_done
diff --git a/t/t4024-diff-optimize-common.sh b/t/t4024-diff-optimize-common.sh
new file mode 100755
index 0000000000..3c66102f7a
--- /dev/null
+++ b/t/t4024-diff-optimize-common.sh
@@ -0,0 +1,157 @@
+#!/bin/sh
+
+test_description='common tail optimization'
+
+. ./test-lib.sh
+
+z=zzzzzzzz ;# 8
+z="$z$z$z$z$z$z$z$z" ;# 64
+z="$z$z$z$z$z$z$z$z" ;# 512
+z="$z$z$z$z" ;# 2048
+z2047=$(expr "$z" : '.\(.*\)') ; #2047
+
+x=zzzzzzzzzz ;# 10
+y="$x$x$x$x$x$x$x$x$x$x" ;# 100
+z="$y$y$y$y$y$y$y$y$y$y" ;# 1000
+z1000=$z
+z100=$y
+z10=$x
+
+zs() {
+ count="$1"
+ while test "$count" -ge 1000
+ do
+ count=$(($count - 1000))
+ printf "%s" $z1000
+ done
+ while test "$count" -ge 100
+ do
+ count=$(($count - 100))
+ printf "%s" $z100
+ done
+ while test "$count" -ge 10
+ do
+ count=$(($count - 10))
+ printf "%s" $z10
+ done
+ while test "$count" -ge 1
+ do
+ count=$(($count - 1))
+ printf "z"
+ done
+}
+
+zc () {
+ sed -e "/^index/d" \
+ -e "s/$z1000/Q/g" \
+ -e "s/QQQQQQQQQ/Z9000/g" \
+ -e "s/QQQQQQQQ/Z8000/g" \
+ -e "s/QQQQQQQ/Z7000/g" \
+ -e "s/QQQQQQ/Z6000/g" \
+ -e "s/QQQQQ/Z5000/g" \
+ -e "s/QQQQ/Z4000/g" \
+ -e "s/QQQ/Z3000/g" \
+ -e "s/QQ/Z2000/g" \
+ -e "s/Q/Z1000/g" \
+ -e "s/$z100/Q/g" \
+ -e "s/QQQQQQQQQ/Z900/g" \
+ -e "s/QQQQQQQQ/Z800/g" \
+ -e "s/QQQQQQQ/Z700/g" \
+ -e "s/QQQQQQ/Z600/g" \
+ -e "s/QQQQQ/Z500/g" \
+ -e "s/QQQQ/Z400/g" \
+ -e "s/QQQ/Z300/g" \
+ -e "s/QQ/Z200/g" \
+ -e "s/Q/Z100/g" \
+ -e "s/000Z//g" \
+ -e "s/$z10/Q/g" \
+ -e "s/QQQQQQQQQ/Z90/g" \
+ -e "s/QQQQQQQQ/Z80/g" \
+ -e "s/QQQQQQQ/Z70/g" \
+ -e "s/QQQQQQ/Z60/g" \
+ -e "s/QQQQQ/Z50/g" \
+ -e "s/QQQQ/Z40/g" \
+ -e "s/QQQ/Z30/g" \
+ -e "s/QQ/Z20/g" \
+ -e "s/Q/Z10/g" \
+ -e "s/00Z//g" \
+ -e "s/z/Q/g" \
+ -e "s/QQQQQQQQQ/Z9/g" \
+ -e "s/QQQQQQQQ/Z8/g" \
+ -e "s/QQQQQQQ/Z7/g" \
+ -e "s/QQQQQQ/Z6/g" \
+ -e "s/QQQQQ/Z5/g" \
+ -e "s/QQQQ/Z4/g" \
+ -e "s/QQQ/Z3/g" \
+ -e "s/QQ/Z2/g" \
+ -e "s/Q/Z1/g" \
+ -e "s/0Z//g" \
+ ;
+}
+
+expect_pattern () {
+ cnt="$1"
+ cat <<EOF
+diff --git a/file-a$cnt b/file-a$cnt
+--- a/file-a$cnt
++++ b/file-a$cnt
+@@ -1 +1 @@
+-Z${cnt}a
++Z${cnt}A
+diff --git a/file-b$cnt b/file-b$cnt
+--- a/file-b$cnt
++++ b/file-b$cnt
+@@ -1 +1 @@
+-b
++B
+diff --git a/file-c$cnt b/file-c$cnt
+--- a/file-c$cnt
++++ b/file-c$cnt
+@@ -1 +1 @@
+-cZ$cnt
+\ No newline at end of file
++CZ$cnt
+\ No newline at end of file
+diff --git a/file-d$cnt b/file-d$cnt
+--- a/file-d$cnt
++++ b/file-d$cnt
+@@ -1 +1 @@
+-d
++D
+EOF
+}
+
+sample='1023 1024 1025 2047 4095'
+
+test_expect_success setup '
+
+ for n in $sample
+ do
+ ( zs $n ; echo a ) >file-a$n &&
+ ( echo b; zs $n; echo ) >file-b$n &&
+ ( printf c; zs $n ) >file-c$n &&
+ ( echo d; zs $n ) >file-d$n &&
+
+ git add file-a$n file-b$n file-c$n file-d$n &&
+
+ ( zs $n ; echo A ) >file-a$n &&
+ ( echo B; zs $n; echo ) >file-b$n &&
+ ( printf C; zs $n ) >file-c$n &&
+ ( echo D; zs $n ) >file-d$n &&
+
+ expect_pattern $n || break
+
+ done >expect
+'
+
+test_expect_success 'diff -U0' '
+
+ for n in $sample
+ do
+ git diff -U0 file-?$n
+ done | zc >actual &&
+ diff -u expect actual
+
+'
+
+test_done
diff --git a/t/t4025-hunk-header.sh b/t/t4025-hunk-header.sh
new file mode 100755
index 0000000000..9ba06b74ce
--- /dev/null
+++ b/t/t4025-hunk-header.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+test_description='diff hunk header truncation'
+
+. ./test-lib.sh
+
+N='日本語'
+N1='æ—¥'
+N2='日本'
+NS="$N$N$N$N$N$N$N$N$N$N$N$N$N"
+
+test_expect_success setup '
+
+ (
+ echo "A $NS"
+ for c in B C D E F G H I J K
+ do
+ echo " $c"
+ done
+ echo "L $NS"
+ for c in M N O P Q R S T U V
+ do
+ echo " $c"
+ done
+ ) >file &&
+ git add file &&
+
+ sed -e "/^ [EP]/s/$/ modified/" <file >file+ &&
+ mv file+ file
+
+'
+
+test_expect_success 'hunk header truncation with an overly long line' '
+
+ git diff | sed -n -e "s/^.*@@//p" >actual &&
+ (
+ echo " A $N$N$N$N$N$N$N$N$N2"
+ echo " L $N$N$N$N$N$N$N$N$N1"
+ ) >expected &&
+ diff -u actual expected
+
+'
+
+test_done
diff --git a/t/t4026-color.sh b/t/t4026-color.sh
new file mode 100755
index 0000000000..b61e5169f4
--- /dev/null
+++ b/t/t4026-color.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Timo Hirvonen
+#
+
+test_description='Test diff/status color escape codes'
+. ./test-lib.sh
+
+color()
+{
+ git config diff.color.new "$1" &&
+ test "`git config --get-color diff.color.new`" = "$2"
+}
+
+invalid_color()
+{
+ git config diff.color.new "$1" &&
+ test -z "`git config --get-color diff.color.new 2>/dev/null`"
+}
+
+test_expect_success 'reset' '
+ color "reset" "[m"
+'
+
+test_expect_success 'attribute before color name' '
+ color "bold red" "[1;31m"
+'
+
+test_expect_success 'color name before attribute' '
+ color "red bold" "[1;31m"
+'
+
+test_expect_success 'attr fg bg' '
+ color "ul blue red" "[4;34;41m"
+'
+
+test_expect_success 'fg attr bg' '
+ color "blue ul red" "[4;34;41m"
+'
+
+test_expect_success 'fg bg attr' '
+ color "blue red ul" "[4;34;41m"
+'
+
+test_expect_success '256 colors' '
+ color "254 bold 255" "[1;38;5;254;48;5;255m"
+'
+
+test_expect_success 'color too small' '
+ invalid_color "-2"
+'
+
+test_expect_success 'color too big' '
+ invalid_color "256"
+'
+
+test_expect_success 'extra character after color number' '
+ invalid_color "3X"
+'
+
+test_expect_success 'extra character after color name' '
+ invalid_color "redX"
+'
+
+test_expect_success 'extra character after attribute' '
+ invalid_color "dimX"
+'
+
+test_done
diff --git a/t/t4100/t-apply-1.patch b/t/t4100/t-apply-1.patch
index de587517f4..90ab54f0f5 100644
--- a/t/t4100/t-apply-1.patch
+++ b/t/t4100/t-apply-1.patch
@@ -90,7 +90,7 @@ diff --git a/Documentation/git.txt b/Documentation/git.txt
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
-@@ -30,7 +30,7 @@ PROG= git-update-cache git-diff-files
+@@ -30,7 +30,7 @@ PROG= git-update-index git-diff-files
git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
git-check-files git-ls-tree git-merge-base git-merge-cache \
git-unpack-file git-export git-diff-cache git-convert-cache \
diff --git a/t/t4100/t-apply-2.patch b/t/t4100/t-apply-2.patch
index cfdc80885b..f5c7d601fc 100644
--- a/t/t4100/t-apply-2.patch
+++ b/t/t4100/t-apply-2.patch
@@ -9,7 +9,7 @@ diff --git a/Makefile b/Makefile
- git-deltafy-script
+ git-deltafy-script git-fetch-script
- PROG= git-update-cache git-diff-files git-init-db git-write-tree \
+ PROG= git-update-index git-diff-files git-init-db git-write-tree \
git-read-tree git-commit-tree git-cat-file git-fsck-cache \
diff --git a/git-pull-script b/git-fetch-script
similarity index 87%
diff --git a/t/t4100/t-apply-5.patch b/t/t4100/t-apply-5.patch
index de11623d1b..5f6ddc1059 100644
--- a/t/t4100/t-apply-5.patch
+++ b/t/t4100/t-apply-5.patch
@@ -200,7 +200,7 @@ diff a/Documentation/git.txt b/Documentation/git.txt
diff a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
-@@ -30,7 +30,7 @@ PROG= git-update-cache git-diff-files
+@@ -30,7 +30,7 @@ PROG= git-update-index git-diff-files
git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
git-check-files git-ls-tree git-merge-base git-merge-cache \
git-unpack-file git-export git-diff-cache git-convert-cache \
diff --git a/t/t4100/t-apply-6.patch b/t/t4100/t-apply-6.patch
index d9753637fc..a72729a712 100644
--- a/t/t4100/t-apply-6.patch
+++ b/t/t4100/t-apply-6.patch
@@ -8,7 +8,7 @@ diff a/Makefile b/Makefile
- git-deltafy-script
+ git-deltafy-script git-fetch-script
- PROG= git-update-cache git-diff-files git-init-db git-write-tree \
+ PROG= git-update-index git-diff-files git-init-db git-write-tree \
git-read-tree git-commit-tree git-cat-file git-fsck-cache \
diff a/git-fetch-script b/git-fetch-script
--- /dev/null
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
index 011126f336..7c25634fc2 100755
--- a/t/t4103-apply-binary.sh
+++ b/t/t4103-apply-binary.sh
@@ -24,10 +24,10 @@ git update-index --add --remove file1 file2 file4
git-commit -m 'Initial Version' 2>/dev/null
git-checkout -b binary
-tr 'x' '\0' <file1 >file3
+tr 'x' '\000' <file1 >file3
cat file3 >file4
git add file2
-tr '\0' 'v' <file3 >file1
+tr '\000' 'v' <file3 >file1
rm -f file2
git update-index --add --remove file1 file2 file3 file4
git-commit -m 'Second Version'
@@ -46,21 +46,25 @@ test_expect_success 'stat binary diff (copy) -- should not fail.' \
'git-checkout master
git apply --stat --summary C.diff'
-test_expect_failure 'check binary diff -- should fail.' \
- 'git-checkout master
- git apply --check B.diff'
+test_expect_success 'check binary diff -- should fail.' \
+ 'git-checkout master &&
+ ! git apply --check B.diff'
-test_expect_failure 'check binary diff (copy) -- should fail.' \
- 'git-checkout master
- git apply --check C.diff'
+test_expect_success 'check binary diff (copy) -- should fail.' \
+ 'git-checkout master &&
+ ! git apply --check C.diff'
-test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \
- 'git-checkout master
- git apply --check --allow-binary-replacement B.diff'
+test_expect_success \
+ 'check incomplete binary diff with replacement -- should fail.' '
+ git-checkout master &&
+ ! git apply --check --allow-binary-replacement B.diff
+'
-test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \
- 'git-checkout master
- git apply --check --allow-binary-replacement C.diff'
+test_expect_success \
+ 'check incomplete binary diff with replacement (copy) -- should fail.' '
+ git-checkout master &&
+ ! git apply --check --allow-binary-replacement C.diff
+'
test_expect_success 'check binary diff with replacement.' \
'git-checkout master
@@ -73,42 +77,42 @@ test_expect_success 'check binary diff with replacement (copy).' \
# Now we start applying them.
do_reset () {
- rm -f file?
- git-reset --hard
+ rm -f file? &&
+ git-reset --hard &&
git-checkout -f master
}
-test_expect_failure 'apply binary diff -- should fail.' \
- 'do_reset
- git apply B.diff'
+test_expect_success 'apply binary diff -- should fail.' \
+ 'do_reset &&
+ ! git apply B.diff'
-test_expect_failure 'apply binary diff -- should fail.' \
- 'do_reset
- git apply --index B.diff'
+test_expect_success 'apply binary diff -- should fail.' \
+ 'do_reset &&
+ ! git apply --index B.diff'
-test_expect_failure 'apply binary diff (copy) -- should fail.' \
- 'do_reset
- git apply C.diff'
+test_expect_success 'apply binary diff (copy) -- should fail.' \
+ 'do_reset &&
+ ! git apply C.diff'
-test_expect_failure 'apply binary diff (copy) -- should fail.' \
- 'do_reset
- git apply --index C.diff'
+test_expect_success 'apply binary diff (copy) -- should fail.' \
+ 'do_reset &&
+ ! git apply --index C.diff'
test_expect_success 'apply binary diff without replacement.' \
- 'do_reset
+ 'do_reset &&
git apply BF.diff'
test_expect_success 'apply binary diff without replacement (copy).' \
- 'do_reset
+ 'do_reset &&
git apply CF.diff'
test_expect_success 'apply binary diff.' \
- 'do_reset
+ 'do_reset &&
git apply --allow-binary-replacement --index BF.diff &&
test -z "$(git diff --name-status binary)"'
test_expect_success 'apply binary diff (copy).' \
- 'do_reset
+ 'do_reset &&
git apply --allow-binary-replacement --index CF.diff &&
test -z "$(git diff --name-status binary)"'
diff --git a/t/t4113-apply-ending.sh b/t/t4113-apply-ending.sh
index 1c6bec044a..d741039882 100755
--- a/t/t4113-apply-ending.sh
+++ b/t/t4113-apply-ending.sh
@@ -29,8 +29,8 @@ test_expect_success setup \
# test
-test_expect_failure 'apply at the end' \
- 'git apply --index test-patch'
+test_expect_success 'apply at the end' \
+ '! git apply --index test-patch'
cat >test-patch <<\EOF
diff a/file b/file
@@ -47,7 +47,7 @@ b
c'
git update-index file
-test_expect_failure 'apply at the beginning' \
- 'git apply --index test-patch'
+test_expect_success 'apply at the beginning' \
+ '! git apply --index test-patch'
test_done
diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh
index 9ae2b3a8ef..b1d35ab04d 100755
--- a/t/t4116-apply-reverse.sh
+++ b/t/t4116-apply-reverse.sh
@@ -12,14 +12,14 @@ test_description='git apply in reverse
test_expect_success setup '
for i in a b c d e f g h i j k l m n; do echo $i; done >file1 &&
- tr "[ijk]" '\''[\0\1\2]'\'' <file1 >file2 &&
+ tr "ijk" '\''\000\001\002'\'' <file1 >file2 &&
git add file1 file2 &&
git commit -m initial &&
git tag initial &&
for i in a b c g h i J K L m o n p q; do echo $i; done >file1 &&
- tr "[mon]" '\''[\0\1\2]'\'' <file1 >file2 &&
+ tr "mon" '\''\000\001\002'\'' <file1 >file2 &&
git commit -a -m second &&
git tag second &&
diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh
index 65571e0549..b540f7295a 100755
--- a/t/t4119-apply-config.sh
+++ b/t/t4119-apply-config.sh
@@ -24,7 +24,7 @@ cat >gpatch.file <<\EOF &&
+++ file1+ 2007-02-21 01:07:44.000000000 -0800
@@ -1 +1 @@
-A
-+B
++B
EOF
sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file &&
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh
new file mode 100755
index 0000000000..85f3da2b98
--- /dev/null
+++ b/t/t4124-apply-ws-rule.sh
@@ -0,0 +1,151 @@
+#!/bin/sh
+
+test_description='core.whitespace rules and git-apply'
+
+. ./test-lib.sh
+
+prepare_test_file () {
+
+ # A line that has character X is touched iff RULE is in effect:
+ # X RULE
+ # ! trailing-space
+ # @ space-before-tab
+ # # indent-with-non-tab
+ sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF
+ 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
+}
+
+apply_patch () {
+ >target &&
+ sed -e "s|\([ab]\)/file|\1/target|" <patch |
+ git apply "$@"
+}
+
+test_fix () {
+
+ # fix should not barf
+ apply_patch --whitespace=fix || return 1
+
+ # find touched lines
+ diff file target | sed -n -e "s/^> //p" >fixed
+
+ # the changed lines are all expeced to change
+ fixed_cnt=$(wc -l <fixed)
+ case "$1" in
+ '') expect_cnt=$fixed_cnt ;;
+ ?*) expect_cnt=$(grep "[$1]" <fixed | wc -l) ;;
+ esac
+ test $fixed_cnt -eq $expect_cnt || return 1
+
+ # and we are not missing anything
+ case "$1" in
+ '') expect_cnt=0 ;;
+ ?*) expect_cnt=$(grep "[$1]" <file | wc -l) ;;
+ esac
+ test $fixed_cnt -eq $expect_cnt || return 1
+
+ # Get the patch actually applied
+ git diff-files -p target >fixed-patch
+ test -s fixed-patch && return 0
+
+ # Make sure it is complaint-free
+ >target
+ git apply --whitespace=error-all <fixed-patch
+
+}
+
+test_expect_success setup '
+
+ >file &&
+ git add file &&
+ prepare_test_file >file &&
+ git diff-files -p >patch &&
+ >target &&
+ git add target
+
+'
+
+test_expect_success 'whitespace=nowarn, default rule' '
+
+ apply_patch --whitespace=nowarn &&
+ diff file target
+
+'
+
+test_expect_success 'whitespace=warn, default rule' '
+
+ apply_patch --whitespace=warn &&
+ diff file target
+
+'
+
+test_expect_success 'whitespace=error-all, default rule' '
+
+ apply_patch --whitespace=error-all && return 1
+ test -s target && return 1
+ : happy
+
+'
+
+test_expect_success 'whitespace=error-all, no rule' '
+
+ git config core.whitespace -trailing,-space-before,-indent &&
+ apply_patch --whitespace=error-all &&
+ diff file target
+
+'
+
+test_expect_success 'whitespace=error-all, no rule (attribute)' '
+
+ git config --unset core.whitespace &&
+ echo "target -whitespace" >.gitattributes &&
+ apply_patch --whitespace=error-all &&
+ diff file target
+
+'
+
+for t in - ''
+do
+ case "$t" in '') tt='!' ;; *) tt= ;; esac
+ for s in - ''
+ do
+ case "$s" in '') ts='@' ;; *) ts= ;; esac
+ for i in - ''
+ do
+ case "$i" in '') ti='#' ;; *) ti= ;; esac
+ rule=${t}trailing,${s}space,${i}indent
+
+ rm -f .gitattributes
+ test_expect_success "rule=$rule" '
+ git config core.whitespace "$rule" &&
+ test_fix "$tt$ts$ti"
+ '
+
+ test_expect_success "rule=$rule (attributes)" '
+ git config --unset core.whitespace &&
+ echo "target whitespace=$rule" >.gitattributes &&
+ test_fix "$tt$ts$ti"
+ '
+
+ done
+ done
+done
+
+test_done
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index cfcdb69dc8..eeff3c9c07 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -129,7 +129,7 @@ test_expect_success 'rerere kicked in' "! grep ======= a1"
test_expect_success 'rerere prefers first change' 'git diff a1 expect'
rm $rr/postimage
-echo "$sha1 a1" | tr '\012' '\0' > .git/rr-cache/MERGE_RR
+echo "$sha1 a1" | tr '\012' '\000' > .git/rr-cache/MERGE_RR
test_expect_success 'rerere clear' 'git rerere clear'
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index 10a5fa9a3a..6d12efb74d 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -22,7 +22,7 @@ echo 3 > a1
git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\360\235\204\236')" a1
# now fsck up the utf8
-git repo-config i18n.commitencoding non-utf-8
+git config i18n.commitencoding non-utf-8
echo 4 > a1
git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\370\235\204\236')" a1
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
new file mode 100755
index 0000000000..b53645417b
--- /dev/null
+++ b/t/t4202-log.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+test_description='git log'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ echo one >one &&
+ git add one &&
+ test_tick &&
+ git commit -m initial &&
+
+ echo ichi >one &&
+ git add one &&
+ test_tick &&
+ git commit -m second &&
+
+ mkdir a &&
+ echo ni >a/two &&
+ git add a/two &&
+ test_tick &&
+ git commit -m third &&
+
+ echo san >a/three &&
+ git add a/three &&
+ test_tick &&
+ git commit -m fourth &&
+
+ git rm a/three &&
+ test_tick &&
+ git commit -m fifth
+
+'
+
+test_expect_success 'diff-filter=A' '
+
+ actual=$(git log --pretty="format:%s" --diff-filter=A HEAD) &&
+ expect=$(echo fourth ; echo third ; echo initial) &&
+ test "$actual" = "$expect" || {
+ echo Oops
+ echo "Actual: $actual"
+ false
+ }
+
+'
+
+test_expect_success 'diff-filter=M' '
+
+ actual=$(git log --pretty="format:%s" --diff-filter=M HEAD) &&
+ expect=$(echo second) &&
+ test "$actual" = "$expect" || {
+ echo Oops
+ echo "Actual: $actual"
+ false
+ }
+
+'
+
+test_expect_success 'diff-filter=D' '
+
+ actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) &&
+ expect=$(echo fifth) &&
+ test "$actual" = "$expect" || {
+ echo Oops
+ echo "Actual: $actual"
+ false
+ }
+
+'
+
+
+
+test_done \ No newline at end of file
diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh
index 9b1a74542a..d6c55c1157 100755
--- a/t/t5100-mailinfo.sh
+++ b/t/t5100-mailinfo.sh
@@ -11,7 +11,7 @@ test_expect_success 'split sample box' \
'git mailsplit -o. ../t5100/sample.mbox >last &&
last=`cat last` &&
echo total is $last &&
- test `cat last` = 8'
+ test `cat last` = 9'
for mail in `echo 00*`
do
diff --git a/t/t5100/info0009 b/t/t5100/info0009
new file mode 100644
index 0000000000..2a66321c80
--- /dev/null
+++ b/t/t5100/info0009
@@ -0,0 +1,5 @@
+Author: F U Bar
+Email: f.u.bar@example.com
+Subject: updates
+Date: Mon, 17 Sep 2001 00:00:00 +0900
+
diff --git a/t/t5100/msg0009 b/t/t5100/msg0009
new file mode 100644
index 0000000000..9ffe131489
--- /dev/null
+++ b/t/t5100/msg0009
@@ -0,0 +1,2 @@
+This is to fix diff-format documentation.
+
diff --git a/t/t5100/patch0009 b/t/t5100/patch0009
new file mode 100644
index 0000000000..65615c34af
--- /dev/null
+++ b/t/t5100/patch0009
@@ -0,0 +1,13 @@
+diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
+index b426a14..97756ec 100644
+--- a/Documentation/diff-format.txt
++++ b/Documentation/diff-format.txt
+@@ -81,7 +81,7 @@ The "diff" formatting options can be customized via the
+ environment variable 'GIT_DIFF_OPTS'. For example, if you
+ prefer context diff:
+
+- GIT_DIFF_OPTS=-c git-diff-index -p $(cat .git/HEAD)
++ GIT_DIFF_OPTS=-c git-diff-index -p HEAD
+
+
+ 2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox
index b80c981c16..0476b96c33 100644
--- a/t/t5100/sample.mbox
+++ b/t/t5100/sample.mbox
@@ -1,3 +1,6 @@
+
+
+
From nobody Mon Sep 17 00:00:00 2001
From: A U Thor <a.u.thor@example.com>
Date: Fri, 9 Jun 2006 00:44:16 -0700
@@ -404,3 +407,26 @@ Subject: [PATCH] another patch
Hey you forgot the patch!
+From nobody Mon Sep 17 00:00:00 2001
+From: A U Thor <a.u.thor@example.com>
+Date: Mon, 17 Sep 2001 00:00:00 +0900
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: Quoted-Printable
+
+=0A=0AFrom: F U Bar <f.u.bar@example.com>
+Subject: [PATCH] updates=0A=0AThis is to fix diff-format documentation.
+
+diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
+index b426a14..97756ec 100644
+--- a/Documentation/diff-format.txt
++++ b/Documentation/diff-format.txt
+@@ -81,7 +81,7 @@ The "diff" formatting options can be customized via the
+ environment variable 'GIT_DIFF_OPTS'. For example, if you
+ prefer context diff:
+=20
+- GIT_DIFF_OPTS=3D-c git-diff-index -p $(cat .git/HEAD)
++ GIT_DIFF_OPTS=3D-c git-diff-index -p HEAD
+=20
+=20
+ 2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index ba7579c251..cd3c149800 100755
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
@@ -15,7 +15,7 @@ test_expect_success \
'rm -f .git/index*
for i in a b c
do
- dd if=/dev/zero bs=4k count=1 | tr "\\0" $i >$i &&
+ dd if=/dev/zero bs=4k count=1 | tr "\\000" $i >$i &&
git update-index --add $i || return 1
done &&
cat c >d && echo foo >>d && git update-index --add d &&
@@ -187,49 +187,51 @@ test_expect_success \
test-3-${packname_3}.idx'
test_expect_success \
- 'corrupt a pack and see if verify catches' \
+ 'verify-pack catches mismatched .idx and .pack files' \
'cat test-1-${packname_1}.idx >test-3.idx &&
cat test-2-${packname_2}.pack >test-3.pack &&
if git verify-pack test-3.idx
then false
else :;
- fi &&
+ fi'
- : PACK_SIGNATURE &&
- cat test-1-${packname_1}.pack >test-3.pack &&
+test_expect_success \
+ 'verify-pack catches a corrupted pack signature' \
+ 'cat test-1-${packname_1}.pack >test-3.pack &&
dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
if git verify-pack test-3.idx
then false
else :;
- fi &&
+ fi'
- : PACK_VERSION &&
- cat test-1-${packname_1}.pack >test-3.pack &&
+test_expect_success \
+ 'verify-pack catches a corrupted pack version' \
+ 'cat test-1-${packname_1}.pack >test-3.pack &&
dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
if git verify-pack test-3.idx
then false
else :;
- fi &&
+ fi'
- : TYPE/SIZE byte of the first packed object data &&
- cat test-1-${packname_1}.pack >test-3.pack &&
+test_expect_success \
+ 'verify-pack catches a corrupted type/size of the 1st packed object data' \
+ 'cat test-1-${packname_1}.pack >test-3.pack &&
dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
if git verify-pack test-3.idx
then false
else :;
- fi &&
+ fi'
- : sum of the index file itself &&
- l=`wc -c <test-3.idx` &&
+test_expect_success \
+ 'verify-pack catches a corrupted sum of the index file itself' \
+ 'l=`wc -c <test-3.idx` &&
l=`expr $l - 20` &&
cat test-1-${packname_1}.pack >test-3.pack &&
dd if=/dev/zero of=test-3.idx count=20 bs=1 conv=notrunc seek=$l &&
if git verify-pack test-3.pack
then false
else :;
- fi &&
-
- :'
+ fi'
test_expect_success \
'build pack index for an existing pack' \
@@ -262,8 +264,14 @@ test_expect_success \
cp -f .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \
.git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'
-test_expect_failure \
+test_expect_success \
'make sure index-pack detects the SHA1 collision' \
- 'git-index-pack -o bad.idx test-3.pack'
+ '! git-index-pack -o bad.idx test-3.pack'
+
+test_expect_success \
+ 'honor pack.packSizeLimit' \
+ 'git config pack.packSizeLimit 200 &&
+ packname_4=$(git pack-objects test-4 <obj-list) &&
+ test 3 = $(ls test-4-*.pack | wc -l)'
test_done
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh
index 4f58c4c3f9..67b9a7b84a 100755
--- a/t/t5302-pack-index.sh
+++ b/t/t5302-pack-index.sh
@@ -42,9 +42,9 @@ test_expect_success \
'both packs should be identical' \
'cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"'
-test_expect_failure \
+test_expect_success \
'index v1 and index v2 should be different' \
- 'cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"'
+ '! cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"'
test_expect_success \
'index-pack with index version 1' \
@@ -61,17 +61,33 @@ test_expect_success \
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) &&
- git verify-pack -v "test-3-${pack3}.pack"'
+ 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)'
+
+have_64bits=
+if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) ||
+ ! echo "$msg" | grep "pack too large .* off_t"
+then
+ have_64bits=t
+else
+ say "skipping tests concerning 64-bit offsets"
+fi
+
+test "$have_64bits" &&
+test_expect_success \
+ 'index v2: verify a pack with some 64-bit offsets' \
+ 'git verify-pack -v "test-3-${pack3}.pack"'
-test_expect_failure \
+test "$have_64bits" &&
+test_expect_success \
'64-bit offsets: should be different from previous index v2 results' \
- 'cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"'
+ '! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"'
+test "$have_64bits" &&
test_expect_success \
'index v2: force some 64-bit offsets with index-pack' \
'git-index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"'
+test "$have_64bits" &&
test_expect_success \
'64-bit offsets: index-pack result should match pack-objects one' \
'cmp "test-3-${pack3}.idx" "3.idx"'
@@ -96,31 +112,31 @@ test_expect_success \
bs=1 count=20 conv=notrunc &&
git cat-file blob "$delta_sha1" > blob_2 )'
-test_expect_failure \
+test_expect_success \
'[index v1] 3) corrupted delta happily returned wrong data' \
- 'cmp blob_1 blob_2'
+ '! cmp blob_1 blob_2'
-test_expect_failure \
+test_expect_success \
'[index v1] 4) confirm that the pack is actually corrupted' \
- 'git fsck --full $commit'
+ '! git fsck --full $commit'
test_expect_success \
'[index v1] 5) pack-objects happily reuses corrupted data' \
'pack4=$(git pack-objects test-4 <obj-list) &&
test -f "test-4-${pack1}.pack"'
-test_expect_failure \
+test_expect_success \
'[index v1] 6) newly created pack is BAD !' \
- 'git verify-pack -v "test-4-${pack1}.pack"'
+ '! git verify-pack -v "test-4-${pack1}.pack"'
test_expect_success \
'[index v2] 1) stream pack to repository' \
'rm -f .git/objects/pack/* &&
- git-index-pack --index-version=2,0x40000 --stdin < "test-1-${pack1}.pack" &&
+ git-index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" &&
git prune-packed &&
git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
- cmp "test-3-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"'
+ cmp "test-2-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"'
test_expect_success \
'[index v2] 2) create a stealth corruption in a delta base reference' \
@@ -134,16 +150,16 @@ test_expect_success \
bs=1 count=20 conv=notrunc &&
git cat-file blob "$delta_sha1" > blob_4 )'
-test_expect_failure \
+test_expect_success \
'[index v2] 3) corrupted delta happily returned wrong data' \
- 'cmp blob_3 blob_4'
+ '! cmp blob_3 blob_4'
-test_expect_failure \
+test_expect_success \
'[index v2] 4) confirm that the pack is actually corrupted' \
- 'git fsck --full $commit'
+ '! git fsck --full $commit'
-test_expect_failure \
+test_expect_success \
'[index v2] 5) pack-objects refuses to reuse corrupted data' \
- 'git pack-objects test-5 <obj-list'
+ '! git pack-objects test-5 <obj-list'
test_done
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
new file mode 100644
index 0000000000..6560af756e
--- /dev/null
+++ b/t/t5304-prune.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Johannes E. Schindelin
+#
+
+test_description='prune'
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ : > file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ git gc
+
+'
+
+test_expect_success 'prune stale packs' '
+
+ orig_pack=$(echo .git/objects/pack/*.pack) &&
+ : > .git/objects/tmp_1.pack &&
+ : > .git/objects/tmp_2.pack &&
+ test-chmtime -86501 .git/objects/tmp_1.pack &&
+ git prune --expire 1.day &&
+ test -f $orig_pack &&
+ test -f .git/objects/tmp_2.pack &&
+ ! test -f .git/objects/tmp_1.pack
+
+'
+
+test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index 57c6397be1..2d0c07fd6a 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -123,4 +123,52 @@ test_expect_success \
git-branch -a >branches && ! grep -q origin/master branches
'
+rewound_push_setup() {
+ rm -rf parent child &&
+ mkdir parent && cd parent &&
+ git-init && echo one >file && git-add file && git-commit -m one &&
+ echo two >file && git-commit -a -m two &&
+ cd .. &&
+ git-clone parent child && cd child && git-reset --hard HEAD^
+}
+
+rewound_push_succeeded() {
+ cmp ../parent/.git/refs/heads/master .git/refs/heads/master
+}
+
+rewound_push_failed() {
+ if rewound_push_succeeded
+ then
+ false
+ else
+ true
+ fi
+}
+
+test_expect_success \
+ 'pushing explicit refspecs respects forcing' '
+ rewound_push_setup &&
+ if git-send-pack ../parent/.git refs/heads/master:refs/heads/master
+ then
+ false
+ else
+ true
+ fi && rewound_push_failed &&
+ git-send-pack ../parent/.git +refs/heads/master:refs/heads/master &&
+ rewound_push_succeeded
+'
+
+test_expect_success \
+ 'pushing wildcard refspecs respects forcing' '
+ rewound_push_setup &&
+ if git-send-pack ../parent/.git refs/heads/*:refs/heads/*
+ then
+ false
+ else
+ true
+ fi && rewound_push_failed &&
+ git-send-pack ../parent/.git +refs/heads/*:refs/heads/* &&
+ rewound_push_succeeded
+'
+
test_done
diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh
index c5dd30d0a4..9a12024241 100755
--- a/t/t5401-update-hooks.sh
+++ b/t/t5401-update-hooks.sh
@@ -25,7 +25,7 @@ test_expect_success setup '
cat >victim/.git/hooks/pre-receive <<'EOF'
#!/bin/sh
-printf "$@" >>$GIT_DIR/pre-receive.args
+printf %s "$@" >>$GIT_DIR/pre-receive.args
cat - >$GIT_DIR/pre-receive.stdin
echo STDOUT pre-receive
echo STDERR pre-receive >&2
@@ -35,7 +35,7 @@ chmod u+x victim/.git/hooks/pre-receive
cat >victim/.git/hooks/update <<'EOF'
#!/bin/sh
echo "$@" >>$GIT_DIR/update.args
-read x; printf "$x" >$GIT_DIR/update.stdin
+read x; printf %s "$x" >$GIT_DIR/update.stdin
echo STDOUT update $1
echo STDERR update $1 >&2
test "$1" = refs/heads/master || exit
@@ -44,7 +44,7 @@ chmod u+x victim/.git/hooks/update
cat >victim/.git/hooks/post-receive <<'EOF'
#!/bin/sh
-printf "$@" >>$GIT_DIR/post-receive.args
+printf %s "$@" >>$GIT_DIR/post-receive.args
cat - >$GIT_DIR/post-receive.stdin
echo STDOUT post-receive
echo STDERR post-receive >&2
@@ -54,14 +54,14 @@ chmod u+x victim/.git/hooks/post-receive
cat >victim/.git/hooks/post-update <<'EOF'
#!/bin/sh
echo "$@" >>$GIT_DIR/post-update.args
-read x; printf "$x" >$GIT_DIR/post-update.stdin
+read x; printf %s "$x" >$GIT_DIR/post-update.stdin
echo STDOUT post-update
echo STDERR post-update >&2
EOF
chmod u+x victim/.git/hooks/post-update
-test_expect_failure push '
- git-send-pack --force ./victim/.git master tofail >send.out 2>send.err
+test_expect_success push '
+ ! git-send-pack --force ./victim/.git master tofail >send.out 2>send.err
'
test_expect_success 'updated as expected' '
@@ -112,8 +112,8 @@ test_expect_success 'all *-receive hook args are empty' '
! test -s victim/.git/post-receive.args
'
-test_expect_failure 'send-pack produced no output' '
- test -s send.out
+test_expect_success 'send-pack produced no output' '
+ ! test -s send.out
'
cat <<EOF >expect
@@ -129,7 +129,7 @@ STDOUT post-update
STDERR post-update
EOF
test_expect_success 'send-pack stderr contains hook messages' '
- egrep ^STD send.err >actual &&
+ grep ^STD send.err >actual &&
git diff - actual <expect
'
diff --git a/t/t5402-post-merge-hook.sh b/t/t5402-post-merge-hook.sh
index 1c4b0b32ab..1394047a8d 100755
--- a/t/t5402-post-merge-hook.sh
+++ b/t/t5402-post-merge-hook.sh
@@ -30,9 +30,9 @@ EOF
chmod u+x clone${clone}/.git/hooks/post-merge
done
-test_expect_failure 'post-merge does not run for up-to-date ' '
+test_expect_success 'post-merge does not run for up-to-date ' '
GIT_DIR=clone1/.git git merge $commit0 &&
- test -e clone1/.git/post-merge.args
+ ! test -f clone1/.git/post-merge.args
'
test_expect_success 'post-merge runs as expected ' '
diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh
new file mode 100755
index 0000000000..1493a92c06
--- /dev/null
+++ b/t/t5404-tracking-branches.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+test_description='tracking branch update checks for git push'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo 1 >file &&
+ git add file &&
+ git commit -m 1 &&
+ git branch b1 &&
+ git branch b2 &&
+ git clone . aa &&
+ git checkout b1 &&
+ echo b1 >>file &&
+ git commit -a -m b1 &&
+ git checkout b2 &&
+ echo b2 >>file &&
+ git commit -a -m b2
+'
+
+test_expect_success 'prepare pushable branches' '
+ cd aa &&
+ b1=$(git rev-parse origin/b1) &&
+ b2=$(git rev-parse origin/b2) &&
+ git checkout -b b1 origin/b1 &&
+ echo aa-b1 >>file &&
+ git commit -a -m aa-b1 &&
+ git checkout -b b2 origin/b2 &&
+ echo aa-b2 >>file &&
+ git commit -a -m aa-b2 &&
+ git checkout master &&
+ echo aa-master >>file &&
+ git commit -a -m aa-master
+'
+
+test_expect_success 'mixed-success push returns error' '! git push'
+
+test_expect_success 'check tracking branches updated correctly after push' '
+ test "$(git rev-parse origin/master)" = "$(git rev-parse master)"
+'
+
+test_expect_success 'check tracking branches not updated for failed refs' '
+ test "$(git rev-parse origin/b1)" = "$b1" &&
+ test "$(git rev-parse origin/b2)" = "$b2"
+'
+
+test_expect_success 'deleted branches have their tracking branches removed' '
+ git push origin :b1 &&
+ test "$(git rev-parse origin/b1)" = "origin/b1"
+'
+
+test_done
diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh
new file mode 100755
index 0000000000..86abc62271
--- /dev/null
+++ b/t/t5405-send-pack-rewind.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='forced push to replace commit we do not have'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ >file1 && git add file1 && test_tick &&
+ git commit -m Initial &&
+
+ mkdir another && (
+ cd another &&
+ git init &&
+ git fetch .. master:master
+ ) &&
+
+ >file2 && git add file2 && test_tick &&
+ git commit -m Second
+
+'
+
+test_expect_success 'non forced push should die not segfault' '
+
+ (
+ cd another &&
+ git push .. master:master
+ test $? = 1
+ )
+
+'
+
+test_expect_success 'forced push should succeed' '
+
+ (
+ cd another &&
+ git push .. +master:master
+ )
+
+'
+
+test_done
diff --git a/t/t5406-remote-rejects.sh b/t/t5406-remote-rejects.sh
new file mode 100755
index 0000000000..46b2cb4e46
--- /dev/null
+++ b/t/t5406-remote-rejects.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='remote push rejects are reported by client'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ mkdir .git/hooks &&
+ (echo "#!/bin/sh" ; echo "exit 1") >.git/hooks/update &&
+ chmod +x .git/hooks/update &&
+ echo 1 >file &&
+ git add file &&
+ git commit -m 1 &&
+ git clone . child &&
+ cd child &&
+ echo 2 >file &&
+ git commit -a -m 2
+'
+
+test_expect_success 'push reports error' '! git push 2>stderr'
+
+test_expect_success 'individual ref reports error' 'grep rejected stderr'
+
+test_done
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 7b6798d8b5..788b4a5aae 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -176,7 +176,7 @@ test_expect_success "deepening fetch in shallow repo" \
test_expect_success "clone shallow object count" \
"test \"count: 18\" = \"$(grep count count.shallow)\""
-test_expect_failure "pull in shallow repo with missing merge base" \
- "(cd shallow; git pull --depth 4 .. A)"
+test_expect_success "pull in shallow repo with missing merge base" \
+ "(cd shallow && ! git pull --depth 4 .. A)"
test_done
diff --git a/t/t5502-quickfetch.sh b/t/t5502-quickfetch.sh
index b4760f2dc0..16eadd6b68 100755
--- a/t/t5502-quickfetch.sh
+++ b/t/t5502-quickfetch.sh
@@ -86,4 +86,37 @@ test_expect_success 'quickfetch should not leave a corrupted repository' '
'
+test_expect_success 'quickfetch should not copy from alternate' '
+
+ (
+ mkdir quickclone &&
+ cd quickclone &&
+ git init-db &&
+ (cd ../.git/objects && pwd) >.git/objects/info/alternates &&
+ git remote add origin .. &&
+ git fetch -k -k
+ ) &&
+ obj_cnt=$( (
+ cd quickclone &&
+ git count-objects | sed -e "s/ *objects,.*//"
+ ) ) &&
+ pck_cnt=$( (
+ cd quickclone &&
+ git count-objects -v | sed -n -e "/packs:/{
+ s/packs://
+ p
+ q
+ }"
+ ) ) &&
+ origin_master=$( (
+ cd quickclone &&
+ git rev-parse origin/master
+ ) ) &&
+ echo "loose objects: $obj_cnt, packfiles: $pck_cnt" &&
+ test $obj_cnt -eq 0 &&
+ test $pck_cnt -eq 0 &&
+ test z$origin_master = z$(git rev-parse master)
+
+'
+
test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 439430f569..9b948c14e6 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -67,6 +67,18 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
diff expected actual'
+test_expect_success 'fetch tags when there is no tags' '
+
+ cd "$D" &&
+
+ mkdir notags &&
+ cd notags &&
+ git init &&
+
+ git fetch -t ..
+
+'
+
test_expect_success 'fetch following tags' '
cd "$D" &&
@@ -83,6 +95,31 @@ test_expect_success 'fetch following tags' '
'
+test_expect_success 'fetch must not resolve short tag name' '
+
+ cd "$D" &&
+
+ mkdir five &&
+ cd five &&
+ git init &&
+
+ ! git fetch .. anno:five
+
+'
+
+test_expect_success 'fetch must not resolve short remote name' '
+
+ cd "$D" &&
+ git-update-ref refs/remotes/six/HEAD HEAD
+
+ mkdir six &&
+ cd six &&
+ git init &&
+
+ ! git fetch .. six:six
+
+'
+
test_expect_success 'create bundle 1' '
cd "$D" &&
echo >file updated again by origin &&
@@ -102,10 +139,10 @@ test_expect_success 'create bundle 2' '
git bundle create bundle2 master~2..master
'
-test_expect_failure 'unbundle 1' '
+test_expect_success 'unbundle 1' '
cd "$D/bundle" &&
git checkout -b some-branch &&
- git fetch "$D/bundle1" master:master
+ ! git fetch "$D/bundle1" master:master
'
test_expect_success 'bundle 1 has only 3 files ' '
@@ -153,4 +190,109 @@ test_expect_success 'bundle should be able to create a full history' '
'
+test "$TEST_RSYNC" && {
+test_expect_success 'fetch via rsync' '
+ git pack-refs &&
+ mkdir rsynced &&
+ cd rsynced &&
+ git init &&
+ git fetch rsync://127.0.0.1$(pwd)/../.git master:refs/heads/master &&
+ git gc --prune &&
+ test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
+ git fsck --full
+'
+
+test_expect_success 'push via rsync' '
+ mkdir ../rsynced2 &&
+ (cd ../rsynced2 &&
+ git init) &&
+ git push rsync://127.0.0.1$(pwd)/../rsynced2/.git master &&
+ cd ../rsynced2 &&
+ git gc --prune &&
+ test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
+ git fsck --full
+'
+
+test_expect_success 'push via rsync' '
+ cd .. &&
+ mkdir rsynced3 &&
+ (cd rsynced3 &&
+ git init) &&
+ git push --all rsync://127.0.0.1$(pwd)/rsynced3/.git &&
+ cd rsynced3 &&
+ test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
+ git fsck --full
+'
+}
+
+test_expect_success 'fetch with a non-applying branch.<name>.merge' '
+ git config branch.master.remote yeti &&
+ git config branch.master.merge refs/heads/bigfoot &&
+ git config remote.blub.url one &&
+ git config remote.blub.fetch "refs/heads/*:refs/remotes/one/*" &&
+ git fetch blub
+'
+
+# the strange name is: a\!'b
+test_expect_success 'quoting of a strangely named repo' '
+ ! git fetch "a\\!'\''b" > result 2>&1 &&
+ cat result &&
+ grep "fatal: '\''a\\\\!'\''b'\''" result
+'
+
+test_expect_success 'bundle should record HEAD correctly' '
+
+ cd "$D" &&
+ git bundle create bundle5 HEAD master &&
+ git bundle list-heads bundle5 >actual &&
+ for h in HEAD refs/heads/master
+ do
+ echo "$(git rev-parse --verify $h) $h"
+ done >expect &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'explicit fetch should not update tracking' '
+
+ cd "$D" &&
+ git branch -f side &&
+ (
+ cd three &&
+ o=$(git rev-parse --verify refs/remotes/origin/master) &&
+ git fetch origin master &&
+ n=$(git rev-parse --verify refs/remotes/origin/master) &&
+ test "$o" = "$n" &&
+ ! git rev-parse --verify refs/remotes/origin/side
+ )
+'
+
+test_expect_success 'explicit pull should not update tracking' '
+
+ cd "$D" &&
+ git branch -f side &&
+ (
+ cd three &&
+ o=$(git rev-parse --verify refs/remotes/origin/master) &&
+ git pull origin master &&
+ n=$(git rev-parse --verify refs/remotes/origin/master) &&
+ test "$o" = "$n" &&
+ ! git rev-parse --verify refs/remotes/origin/side
+ )
+'
+
+test_expect_success 'configured fetch updates tracking' '
+
+ cd "$D" &&
+ git branch -f side &&
+ (
+ cd three &&
+ o=$(git rev-parse --verify refs/remotes/origin/master) &&
+ git fetch origin &&
+ n=$(git rev-parse --verify refs/remotes/origin/master) &&
+ test "$o" != "$n" &&
+ git rev-parse --verify refs/remotes/origin/side
+ )
+'
+
test_done
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
new file mode 100755
index 0000000000..6ec5f7c48b
--- /dev/null
+++ b/t/t5512-ls-remote.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git ls-remote'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ >file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ git tag mark &&
+ git show-ref --tags -d | sed -e "s/ / /" >expected.tag &&
+ (
+ echo "$(git rev-parse HEAD) HEAD"
+ git show-ref -d | sed -e "s/ / /"
+ ) >expected.all &&
+
+ git remote add self $(pwd)/.git
+
+'
+
+test_expect_success 'ls-remote --tags .git' '
+
+ git ls-remote --tags .git >actual &&
+ diff -u expected.tag actual
+
+'
+
+test_expect_success 'ls-remote .git' '
+
+ git ls-remote .git >actual &&
+ diff -u expected.all actual
+
+'
+
+test_expect_success 'ls-remote --tags self' '
+
+ git ls-remote --tags self >actual &&
+ diff -u expected.tag actual
+
+'
+
+test_expect_success 'ls-remote self' '
+
+ git ls-remote self >actual &&
+ diff -u expected.all actual
+
+'
+
+test_done
diff --git a/t/t5515-fetch-merge-logic.sh b/t/t5515-fetch-merge-logic.sh
index 6c9cc67508..31c1081617 100755
--- a/t/t5515-fetch-merge-logic.sh
+++ b/t/t5515-fetch-merge-logic.sh
@@ -84,8 +84,7 @@ test_expect_success setup '
git config branch.br-$remote-merge.merge refs/heads/three &&
git config branch.br-$remote-octopus.remote $remote &&
git config branch.br-$remote-octopus.merge refs/heads/one &&
- git config --add branch.br-$remote-octopus.merge two &&
- git config --add branch.br-$remote-octopus.merge remotes/rem/three
+ git config --add branch.br-$remote-octopus.merge two
done
'
diff --git a/t/t5515/fetch.br-branches-default-merge b/t/t5515/fetch.br-branches-default-merge
index ea65f31bde..ca2cc1d1b4 100644
--- a/t/t5515/fetch.br-branches-default-merge
+++ b/t/t5515/fetch.br-branches-default-merge
@@ -1,5 +1,6 @@
# br-branches-default-merge
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-branches-default-merge_branches-default b/t/t5515/fetch.br-branches-default-merge_branches-default
index 7b5fa949e6..7d947cd80f 100644
--- a/t/t5515/fetch.br-branches-default-merge_branches-default
+++ b/t/t5515/fetch.br-branches-default-merge_branches-default
@@ -1,5 +1,6 @@
# br-branches-default-merge branches-default
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-branches-default-octopus b/t/t5515/fetch.br-branches-default-octopus
index 128397d737..ec39c54b7e 100644
--- a/t/t5515/fetch.br-branches-default-octopus
+++ b/t/t5515/fetch.br-branches-default-octopus
@@ -1,5 +1,7 @@
# br-branches-default-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-branches-default-octopus_branches-default b/t/t5515/fetch.br-branches-default-octopus_branches-default
index 4b37cd481a..6bf42e24b6 100644
--- a/t/t5515/fetch.br-branches-default-octopus_branches-default
+++ b/t/t5515/fetch.br-branches-default-octopus_branches-default
@@ -1,5 +1,7 @@
# br-branches-default-octopus branches-default
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-branches-one-merge b/t/t5515/fetch.br-branches-one-merge
index 3a4e77ead5..b4b3b35ce0 100644
--- a/t/t5515/fetch.br-branches-one-merge
+++ b/t/t5515/fetch.br-branches-one-merge
@@ -1,5 +1,6 @@
# br-branches-one-merge
-8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-branches-one-merge_branches-one b/t/t5515/fetch.br-branches-one-merge_branches-one
index 00e04b435e..2ecef384eb 100644
--- a/t/t5515/fetch.br-branches-one-merge_branches-one
+++ b/t/t5515/fetch.br-branches-one-merge_branches-one
@@ -1,5 +1,6 @@
# br-branches-one-merge branches-one
-8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-branches-one-octopus b/t/t5515/fetch.br-branches-one-octopus
index 53fe808a3b..96e3029416 100644
--- a/t/t5515/fetch.br-branches-one-octopus
+++ b/t/t5515/fetch.br-branches-one-octopus
@@ -1,5 +1,6 @@
# br-branches-one-octopus
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-branches-one-octopus_branches-one b/t/t5515/fetch.br-branches-one-octopus_branches-one
index 41b18ff78a..55e0bad621 100644
--- a/t/t5515/fetch.br-branches-one-octopus_branches-one
+++ b/t/t5515/fetch.br-branches-one-octopus_branches-one
@@ -1,5 +1,6 @@
# br-branches-one-octopus branches-one
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-config-glob-octopus b/t/t5515/fetch.br-config-glob-octopus
index 9ee213ea45..938e532db2 100644
--- a/t/t5515/fetch.br-config-glob-octopus
+++ b/t/t5515/fetch.br-config-glob-octopus
@@ -2,7 +2,7 @@
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-config-glob-octopus_config-glob b/t/t5515/fetch.br-config-glob-octopus_config-glob
index 44bd0ec59f..c9225bf6ff 100644
--- a/t/t5515/fetch.br-config-glob-octopus_config-glob
+++ b/t/t5515/fetch.br-config-glob-octopus_config-glob
@@ -2,7 +2,7 @@
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-remote-glob-octopus b/t/t5515/fetch.br-remote-glob-octopus
index c1554f8f2d..b08e046195 100644
--- a/t/t5515/fetch.br-remote-glob-octopus
+++ b/t/t5515/fetch.br-remote-glob-octopus
@@ -2,7 +2,7 @@
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5515/fetch.br-remote-glob-octopus_remote-glob b/t/t5515/fetch.br-remote-glob-octopus_remote-glob
index e6134345b8..d4d547c847 100644
--- a/t/t5515/fetch.br-remote-glob-octopus_remote-glob
+++ b/t/t5515/fetch.br-remote-glob-octopus_remote-glob
@@ -2,7 +2,7 @@
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 4fbd5b1f47..9d2dc33cbd 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -145,11 +145,21 @@ test_expect_success 'push with no ambiguity (1)' '
test_expect_success 'push with no ambiguity (2)' '
mk_test remotes/origin/master &&
- git push testrepo master:master &&
+ git push testrepo master:origin/master &&
check_push_result $the_commit remotes/origin/master
'
+test_expect_success 'push with colon-less refspec, no ambiguity' '
+
+ mk_test heads/master heads/t/master &&
+ git branch -f t/master master &&
+ git push testrepo master &&
+ check_push_result $the_commit heads/master &&
+ check_push_result $the_first_commit heads/t/master
+
+'
+
test_expect_success 'push with weak ambiguity (1)' '
mk_test heads/master remotes/origin/master &&
@@ -244,14 +254,70 @@ test_expect_success 'push with colon-less refspec (4)' '
'
+test_expect_success 'push with HEAD' '
+
+ mk_test heads/master &&
+ git checkout master &&
+ git push testrepo HEAD &&
+ check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'push with HEAD nonexisting at remote' '
+
+ mk_test heads/master &&
+ git checkout -b local master &&
+ git push testrepo HEAD &&
+ check_push_result $the_commit heads/local
+'
+
test_expect_success 'push with dry-run' '
mk_test heads/master &&
- cd testrepo &&
- old_commit=$(git show-ref -s --verify refs/heads/master) &&
- cd .. &&
+ (cd testrepo &&
+ old_commit=$(git show-ref -s --verify refs/heads/master)) &&
git push --dry-run testrepo &&
check_push_result $old_commit heads/master
'
+test_expect_success 'push updates local refs' '
+
+ rm -rf parent child &&
+ mkdir parent &&
+ (cd parent && git init &&
+ echo one >foo && git add foo && git commit -m one) &&
+ git clone parent child &&
+ (cd child &&
+ echo two >foo && git commit -a -m two &&
+ git push &&
+ test $(git rev-parse master) = $(git rev-parse remotes/origin/master))
+
+'
+
+test_expect_success 'push does not update local refs on failure' '
+
+ rm -rf parent child &&
+ mkdir parent &&
+ (cd parent && git init &&
+ echo one >foo && git add foo && git commit -m one &&
+ echo exit 1 >.git/hooks/pre-receive &&
+ chmod +x .git/hooks/pre-receive) &&
+ git clone parent child &&
+ (cd child &&
+ echo two >foo && git commit -a -m two &&
+ ! git push &&
+ test $(git rev-parse master) != \
+ $(git rev-parse remotes/origin/master))
+
+'
+
+test_expect_success 'allow deleting an invalid remote ref' '
+
+ pwd &&
+ rm -f testrepo/.git/objects/??/* &&
+ git push testrepo :refs/heads/master &&
+ (cd testrepo && ! git rev-parse --verify refs/heads/master)
+
+'
+
test_done
diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh
new file mode 100755
index 0000000000..ed3fec192a
--- /dev/null
+++ b/t/t5517-push-mirror.sh
@@ -0,0 +1,228 @@
+#!/bin/sh
+
+test_description='pushing to a mirror repository'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+invert () {
+ if "$@"; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+mk_repo_pair () {
+ rm -rf master mirror &&
+ mkdir mirror &&
+ (
+ cd mirror &&
+ git init
+ ) &&
+ mkdir master &&
+ (
+ cd master &&
+ git init &&
+ git config remote.up.url ../mirror
+ )
+}
+
+
+# BRANCH tests
+test_expect_success 'push mirror creates new branches' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror updates existing branches' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git push --mirror up &&
+ echo two >foo && git add foo && git commit -m two &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror force updates existing branches' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git push --mirror up &&
+ echo two >foo && git add foo && git commit -m two &&
+ git push --mirror up &&
+ git reset --hard HEAD^
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror removes branches' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git branch remove master &&
+ git push --mirror up &&
+ git branch -D remove
+ git push --mirror up
+ ) &&
+ (
+ cd mirror &&
+ invert git show-ref -s --verify refs/heads/remove
+ )
+
+'
+
+test_expect_success 'push mirror adds, updates and removes branches together' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git branch remove master &&
+ git push --mirror up &&
+ git branch -D remove &&
+ git branch add master &&
+ echo two >foo && git add foo && git commit -m two &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/heads/master) &&
+ master_add=$(cd master && git show-ref -s --verify refs/heads/add) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) &&
+ mirror_add=$(cd mirror && git show-ref -s --verify refs/heads/add) &&
+ test "$master_master" = "$mirror_master" &&
+ test "$master_add" = "$mirror_add" &&
+ (
+ cd mirror &&
+ invert git show-ref -s --verify refs/heads/remove
+ )
+
+'
+
+
+# TAG tests
+test_expect_success 'push mirror creates new tags' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tmaster master &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror updates existing tags' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tmaster master &&
+ git push --mirror up &&
+ echo two >foo && git add foo && git commit -m two &&
+ git tag -f tmaster master &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror force updates existing tags' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tmaster master &&
+ git push --mirror up &&
+ echo two >foo && git add foo && git commit -m two &&
+ git tag -f tmaster master &&
+ git push --mirror up &&
+ git reset --hard HEAD^
+ git tag -f tmaster master &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+ test "$master_master" = "$mirror_master"
+
+'
+
+test_expect_success 'push mirror removes tags' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tremove master &&
+ git push --mirror up &&
+ git tag -d tremove
+ git push --mirror up
+ ) &&
+ (
+ cd mirror &&
+ invert git show-ref -s --verify refs/tags/tremove
+ )
+
+'
+
+test_expect_success 'push mirror adds, updates and removes tags together' '
+
+ mk_repo_pair &&
+ (
+ cd master &&
+ echo one >foo && git add foo && git commit -m one &&
+ git tag -f tmaster master &&
+ git tag -f tremove master &&
+ git push --mirror up &&
+ git tag -d tremove &&
+ git tag tadd master &&
+ echo two >foo && git add foo && git commit -m two &&
+ git tag -f tmaster master &&
+ git push --mirror up
+ ) &&
+ master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) &&
+ master_add=$(cd master && git show-ref -s --verify refs/tags/tadd) &&
+ mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) &&
+ mirror_add=$(cd mirror && git show-ref -s --verify refs/tags/tadd) &&
+ test "$master_master" = "$mirror_master" &&
+ test "$master_add" = "$mirror_add" &&
+ (
+ cd mirror &&
+ invert git show-ref -s --verify refs/tags/tremove
+ )
+
+'
+
+test_done
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 93eaf2c154..9484129ca5 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -53,4 +53,43 @@ test_expect_success 'the default remote . should not break explicit pull' '
test `cat file` = modified
'
+test_expect_success '--rebase' '
+ git branch to-rebase &&
+ echo modified again > file &&
+ git commit -m file file &&
+ git checkout to-rebase &&
+ echo new > file2 &&
+ git add file2 &&
+ git commit -m "new file" &&
+ git tag before-rebase &&
+ git pull --rebase . copy &&
+ test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
+ test new = $(git show HEAD:file2)
+'
+
+test_expect_success 'branch.to-rebase.rebase' '
+ git reset --hard before-rebase &&
+ git config branch.to-rebase.rebase 1 &&
+ git pull . copy &&
+ git config branch.to-rebase.rebase 0 &&
+ test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
+ test new = $(git show HEAD:file2)
+'
+
+test_expect_success '--rebase with rebased upstream' '
+
+ git remote add -f me . &&
+ git checkout copy &&
+ git reset --hard HEAD^ &&
+ echo conflicting modification > file &&
+ git commit -m conflict file &&
+ git checkout to-rebase &&
+ echo file > file2 &&
+ git commit -m to-rebase file2 &&
+ git pull --rebase me copy &&
+ test "conflicting modification" = "$(cat file)" &&
+ test file = $(cat file2)
+
+'
+
test_done
diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh
new file mode 100755
index 0000000000..8b05091069
--- /dev/null
+++ b/t/t5530-upload-pack-error.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='errors in upload-pack'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+corrupt_repo () {
+ object_sha1=$(git rev-parse "$1") &&
+ ob=$(expr "$object_sha1" : "\(..\)") &&
+ ject=$(expr "$object_sha1" : "..\(..*\)") &&
+ rm -f ".git/objects/$ob/$ject"
+}
+
+test_expect_success 'setup and corrupt repository' '
+
+ echo file >file &&
+ git add file &&
+ git rev-parse :file &&
+ git commit -a -m original &&
+ test_tick &&
+ echo changed >file &&
+ git commit -a -m changed &&
+ corrupt_repo HEAD:file
+
+'
+
+test_expect_success 'fsck fails' '
+ ! git fsck
+'
+
+test_expect_success 'upload-pack fails due to error in pack-objects' '
+
+ ! echo "0032want $(git rev-parse HEAD)
+00000009done
+0000" | git-upload-pack . > /dev/null 2> output.err &&
+ grep "pack-objects died" output.err
+'
+
+test_expect_success 'corrupt repo differently' '
+
+ git hash-object -w file &&
+ corrupt_repo HEAD^^{tree}
+
+'
+
+test_expect_success 'fsck fails' '
+ ! git fsck
+'
+test_expect_success 'upload-pack fails due to error in rev-list' '
+
+ ! echo "0032want $(git rev-parse HEAD)
+00000009done
+0000" | git-upload-pack . > /dev/null 2> output.err &&
+ grep "waitpid (async) failed" output.err
+'
+
+test_expect_success 'create empty repository' '
+
+ mkdir foo &&
+ cd foo &&
+ git init
+
+'
+
+test_expect_success 'fetch fails' '
+
+ ! git fetch .. master
+
+'
+
+test_done
diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh
index 1776b377f3..acf34cec8f 100755
--- a/t/t5600-clone-fail-cleanup.sh
+++ b/t/t5600-clone-fail-cleanup.sh
@@ -11,13 +11,13 @@ remove the directory before attempting a clone again.'
. ./test-lib.sh
-test_expect_failure \
+test_expect_success \
'clone of non-existent source should fail' \
- 'git-clone foo bar'
+ '! git-clone foo bar'
-test_expect_failure \
+test_expect_success \
'failed clone should not leave a directory' \
- 'cd bar'
+ '! test -d bar'
# Need a repo to clone
test_create_repo foo
@@ -27,9 +27,9 @@ test_create_repo foo
# source repository given to git-clone should be relative to the
# current path not to the target dir
-test_expect_failure \
+test_expect_success \
'clone of non-existent (relative to $PWD) source should fail' \
- 'git-clone ../foo baz'
+ '! git-clone ../foo baz'
test_expect_success \
'clone should work now that source exists' \
diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh
index 4e93aaab02..b6a54867b4 100755
--- a/t/t5700-clone-reference.sh
+++ b/t/t5700-clone-reference.sh
@@ -38,7 +38,7 @@ cd "$base_dir"
test_expect_success 'pulling from reference' \
'cd C &&
-git pull ../B'
+git pull ../B master'
cd "$base_dir"
@@ -61,7 +61,7 @@ test_expect_success 'existence of info/alternates' \
cd "$base_dir"
test_expect_success 'pulling from reference' \
-'cd D && git pull ../B'
+'cd D && git pull ../B master'
cd "$base_dir"
diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
index 822ac8c28e..59a165a6d4 100755
--- a/t/t5701-clone-local.sh
+++ b/t/t5701-clone-local.sh
@@ -63,4 +63,12 @@ test_expect_success 'Even without -l, local will make a hardlink' '
test 0 = $copied
'
+test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
+ cd "$D" &&
+ echo "ref: refs/heads/nonexistent" > a.git/HEAD &&
+ git clone a d &&
+ cd d &&
+ git fetch &&
+ test ! -e .git/refs/remotes/origin/HEAD'
+
test_done
diff --git a/t/t5702-clone-options.sh b/t/t5702-clone-options.sh
new file mode 100755
index 0000000000..328e4d9a33
--- /dev/null
+++ b/t/t5702-clone-options.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+test_description='basic clone options'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+ mkdir parent &&
+ (cd parent && git init &&
+ echo one >file && git add file &&
+ git commit -m one)
+
+'
+
+test_expect_success 'clone -o' '
+
+ git clone -o foo parent clone-o &&
+ (cd clone-o && git rev-parse --verify refs/remotes/foo/master)
+
+'
+
+test_done
diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh
index 699df6ebd8..910ccb4fff 100755
--- a/t/t5710-info-alternate.sh
+++ b/t/t5710-info-alternate.sh
@@ -53,14 +53,18 @@ git prune'
cd "$base_dir"
-test_expect_failure 'creating too deep nesting' \
+test_expect_success 'creating too deep nesting' \
'git clone -l -s C D &&
git clone -l -s D E &&
git clone -l -s E F &&
git clone -l -s F G &&
-git clone -l -s G H &&
-cd H &&
-test_valid_repo'
+git clone -l -s G H'
+
+test_expect_success 'invalidity of deepest repository' \
+'cd H && {
+ test_valid_repo
+ test $? -ne 0
+}'
cd "$base_dir"
@@ -83,10 +87,10 @@ test_valid_repo"
cd "$base_dir"
-test_expect_failure 'that info/alternates is necessary' \
+test_expect_success 'that info/alternates is necessary' \
'cd C &&
-rm .git/objects/info/alternates &&
-test_valid_repo'
+rm -f .git/objects/info/alternates &&
+! (test_valid_repo)'
cd "$base_dir"
@@ -97,9 +101,11 @@ test_valid_repo'
cd "$base_dir"
-test_expect_failure 'that relative alternate is only possible for current dir' \
-'cd D &&
-test_valid_repo'
+test_expect_success \
+ 'that relative alternate is only possible for current dir' '
+ cd D &&
+ ! (test_valid_repo)
+'
cd "$base_dir"
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
index 1e4541afea..0dc915ea67 100755
--- a/t/t6006-rev-list-format.sh
+++ b/t/t6006-rev-list-format.sh
@@ -139,4 +139,14 @@ commit 131a310eb913d107dd3c09a65d1651175898735d
commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
EOF
+test_expect_success 'empty email' '
+ test_tick &&
+ C=$(GIT_AUTHOR_EMAIL= git commit-tree HEAD^{tree} </dev/null) &&
+ A=$(git show --pretty=format:%an,%ae,%ad%n -s $C) &&
+ test "$A" = "A U Thor,,Thu Apr 7 15:14:13 2005 -0700" || {
+ echo "Eh? $A" >failure
+ false
+ }
+'
+
test_done
diff --git a/t/t6008-rev-list-submodule.sh b/t/t6008-rev-list-submodule.sh
new file mode 100755
index 0000000000..88e96fb91b
--- /dev/null
+++ b/t/t6008-rev-list-submodule.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git rev-list involving submodules that this repo has'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ : > file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ echo 1 > file &&
+ test_tick &&
+ git commit -m second file &&
+ echo 2 > file &&
+ test_tick &&
+ git commit -m third file &&
+
+ rm .git/index &&
+
+ : > super-file &&
+ git add super-file &&
+ git submodule add . sub &&
+ git symbolic-ref HEAD refs/heads/super &&
+ test_tick &&
+ git commit -m super-initial &&
+ echo 1 > super-file &&
+ test_tick &&
+ git commit -m super-first super-file &&
+ echo 2 > super-file &&
+ test_tick &&
+ git commit -m super-second super-file
+'
+
+test_expect_success "Ilari's test" '
+ git rev-list --objects super master ^super^
+'
+
+test_done
diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh
new file mode 100755
index 0000000000..be3d238d99
--- /dev/null
+++ b/t/t6009-rev-list-parent.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='properly cull all ancestors'
+
+. ./test-lib.sh
+
+commit () {
+ test_tick &&
+ echo $1 >file &&
+ git commit -a -m $1 &&
+ git tag $1
+}
+
+test_expect_success setup '
+
+ touch file &&
+ git add file &&
+
+ commit one &&
+
+ test_tick=$(($test_tick - 2400))
+
+ commit two &&
+ commit three &&
+ commit four &&
+
+ git log --pretty=oneline --abbrev-commit
+'
+
+test_expect_failure 'one is ancestor of others and should not be shown' '
+
+ git rev-list one --not four >result &&
+ >expect &&
+ diff -u expect result
+
+'
+
+test_done
diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh
index ae3b6f2831..86419964b4 100755
--- a/t/t6023-merge-file.sh
+++ b/t/t6023-merge-file.sh
@@ -66,8 +66,8 @@ test_expect_success "merge result added missing LF" \
"git diff test.txt test2.txt"
cp test.txt backup.txt
-test_expect_failure "merge with conflicts" \
- "git merge-file test.txt orig.txt new3.txt"
+test_expect_success "merge with conflicts" \
+ "! git merge-file test.txt orig.txt new3.txt"
cat > expect.txt << EOF
<<<<<<< test.txt
@@ -89,8 +89,8 @@ EOF
test_expect_success "expected conflict markers" "git diff test.txt expect.txt"
cp backup.txt test.txt
-test_expect_failure "merge with conflicts, using -L" \
- "git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
+test_expect_success "merge with conflicts, using -L" \
+ "! git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
cat > expect.txt << EOF
<<<<<<< 1
@@ -113,8 +113,8 @@ test_expect_success "expected conflict markers, with -L" \
"git diff test.txt expect.txt"
sed "s/ tu / TU /" < new1.txt > new5.txt
-test_expect_failure "conflict in removed tail" \
- "git merge-file -p orig.txt new1.txt new5.txt > out"
+test_expect_success "conflict in removed tail" \
+ "! git merge-file -p orig.txt new1.txt new5.txt > out"
cat > expect << EOF
Dominus regit me,
diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh
index c154f03cf5..149ea8543a 100755
--- a/t/t6024-recursive-merge.sh
+++ b/t/t6024-recursive-merge.sh
@@ -60,7 +60,7 @@ git update-index a1 &&
GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
'
-test_expect_failure "combined merge conflicts" "git merge -m final G"
+test_expect_success "combined merge conflicts" "! git merge -m final G"
cat > expect << EOF
<<<<<<< HEAD:a1
diff --git a/t/t6025-merge-symlinks.sh b/t/t6025-merge-symlinks.sh
index 950c2e9b63..6004deb432 100755
--- a/t/t6025-merge-symlinks.sh
+++ b/t/t6025-merge-symlinks.sh
@@ -30,30 +30,29 @@ echo plain-file > symlink &&
git add symlink &&
git-commit -m b-file'
-test_expect_failure \
+test_expect_success \
'merge master into b-symlink, which has a different symbolic link' '
-! git-checkout b-symlink ||
-git-merge master'
+git-checkout b-symlink &&
+! git-merge master'
test_expect_success \
'the merge result must be a file' '
test -f symlink'
-test_expect_failure \
+test_expect_success \
'merge master into b-file, which has a file instead of a symbolic link' '
-! (git-reset --hard &&
-git-checkout b-file) ||
-git-merge master'
+git-reset --hard && git-checkout b-file &&
+! git-merge master'
test_expect_success \
'the merge result must be a file' '
test -f symlink'
-test_expect_failure \
+test_expect_success \
'merge b-file, which has a file instead of a symbolic link, into master' '
-! (git-reset --hard &&
-git-checkout master) ||
-git-merge b-file'
+git-reset --hard &&
+git-checkout master &&
+! git-merge b-file'
test_expect_success \
'the merge result must be a file' '
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 03cdba5808..ec71123f4b 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -71,6 +71,100 @@ test_expect_success 'bisect start with one bad and good' '
git bisect next
'
+test_expect_success 'bisect reset: back in the master branch' '
+ git bisect reset &&
+ echo "* master" > branch.expect &&
+ git branch > branch.output &&
+ cmp branch.expect branch.output
+'
+
+test_expect_success 'bisect reset: back in another branch' '
+ git checkout -b other &&
+ git bisect start &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH3 &&
+ git bisect reset &&
+ echo " master" > branch.expect &&
+ echo "* other" >> branch.expect &&
+ git branch > branch.output &&
+ cmp branch.expect branch.output
+'
+
+test_expect_success 'bisect reset when not bisecting' '
+ git bisect reset &&
+ git branch > branch.output &&
+ cmp branch.expect branch.output
+'
+
+test_expect_success 'bisect reset removes packed refs' '
+ git bisect reset &&
+ git bisect start &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH3 &&
+ git pack-refs --all --prune &&
+ git bisect next &&
+ git bisect reset &&
+ test -z "$(git for-each-ref "refs/bisect/*")" &&
+ test -z "$(git for-each-ref "refs/heads/bisect")"
+'
+
+# $HASH1 is good, $HASH4 is bad, we skip $HASH3
+# but $HASH2 is bad,
+# so we should find $HASH2 as the first bad commit
+test_expect_success 'bisect skip: successfull result' '
+ git bisect reset &&
+ git bisect start $HASH4 $HASH1 &&
+ git bisect skip &&
+ git bisect bad > my_bisect_log.txt &&
+ grep "$HASH2 is first bad commit" my_bisect_log.txt &&
+ git bisect reset
+'
+
+# $HASH1 is good, $HASH4 is bad, we skip $HASH3 and $HASH2
+# so we should not be able to tell the first bad commit
+# among $HASH2, $HASH3 and $HASH4
+test_expect_success 'bisect skip: cannot tell between 3 commits' '
+ git bisect start $HASH4 $HASH1 &&
+ git bisect skip || return 1
+
+ if git bisect skip > my_bisect_log.txt
+ then
+ echo Oops, should have failed.
+ false
+ else
+ test $? -eq 2 &&
+ grep "first bad commit could be any of" my_bisect_log.txt &&
+ ! grep $HASH1 my_bisect_log.txt &&
+ grep $HASH2 my_bisect_log.txt &&
+ grep $HASH3 my_bisect_log.txt &&
+ grep $HASH4 my_bisect_log.txt &&
+ git bisect reset
+ fi
+'
+
+# $HASH1 is good, $HASH4 is bad, we skip $HASH3
+# but $HASH2 is good,
+# so we should not be able to tell the first bad commit
+# among $HASH3 and $HASH4
+test_expect_success 'bisect skip: cannot tell between 2 commits' '
+ git bisect start $HASH4 $HASH1 &&
+ git bisect skip || return 1
+
+ if git bisect good > my_bisect_log.txt
+ then
+ echo Oops, should have failed.
+ false
+ else
+ test $? -eq 2 &&
+ grep "first bad commit could be any of" my_bisect_log.txt &&
+ ! grep $HASH1 my_bisect_log.txt &&
+ ! grep $HASH2 my_bisect_log.txt &&
+ grep $HASH3 my_bisect_log.txt &&
+ grep $HASH4 my_bisect_log.txt &&
+ git bisect reset
+ fi
+'
+
# We want to automatically find the commit that
# introduced "Another" into hello.
test_expect_success \
@@ -99,6 +193,79 @@ test_expect_success \
grep "$HASH4 is first bad commit" my_bisect_log.txt &&
git bisect reset'
+# $HASH1 is good, $HASH5 is bad, we skip $HASH3
+# but $HASH4 is good,
+# so we should find $HASH5 as the first bad commit
+HASH5=
+test_expect_success 'bisect skip: add line and then a new test' '
+ add_line_into_file "5: Another new line." hello &&
+ HASH5=$(git rev-parse --verify HEAD) &&
+ git bisect start $HASH5 $HASH1 &&
+ git bisect skip &&
+ git bisect good > my_bisect_log.txt &&
+ grep "$HASH5 is first bad commit" my_bisect_log.txt &&
+ git bisect log > log_to_replay.txt &&
+ git bisect reset
+'
+
+test_expect_success 'bisect skip and bisect replay' '
+ git bisect replay log_to_replay.txt > my_bisect_log.txt &&
+ grep "$HASH5 is first bad commit" my_bisect_log.txt &&
+ git bisect reset
+'
+
+HASH6=
+test_expect_success 'bisect run & skip: cannot tell between 2' '
+ add_line_into_file "6: Yet a line." hello &&
+ HASH6=$(git rev-parse --verify HEAD) &&
+ echo "#"\!"/bin/sh" > test_script.sh &&
+ echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh &&
+ echo "grep line hello > /dev/null" >> test_script.sh &&
+ echo "test \$? -ne 0" >> test_script.sh &&
+ chmod +x test_script.sh &&
+ git bisect start $HASH6 $HASH1 &&
+ if git bisect run ./test_script.sh > my_bisect_log.txt
+ then
+ echo Oops, should have failed.
+ false
+ else
+ test $? -eq 2 &&
+ grep "first bad commit could be any of" my_bisect_log.txt &&
+ ! grep $HASH3 my_bisect_log.txt &&
+ ! grep $HASH6 my_bisect_log.txt &&
+ grep $HASH4 my_bisect_log.txt &&
+ grep $HASH5 my_bisect_log.txt
+ fi
+'
+
+HASH7=
+test_expect_success 'bisect run & skip: find first bad' '
+ git bisect reset &&
+ add_line_into_file "7: Should be the last line." hello &&
+ HASH7=$(git rev-parse --verify HEAD) &&
+ echo "#"\!"/bin/sh" > test_script.sh &&
+ echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh &&
+ echo "tail -1 hello | grep day > /dev/null && exit 125" >> test_script.sh &&
+ echo "grep Yet hello > /dev/null" >> test_script.sh &&
+ echo "test \$? -ne 0" >> test_script.sh &&
+ chmod +x test_script.sh &&
+ git bisect start $HASH7 $HASH1 &&
+ git bisect run ./test_script.sh > my_bisect_log.txt &&
+ grep "$HASH6 is first bad commit" my_bisect_log.txt
+'
+
+test_expect_success 'bisect starting with a detached HEAD' '
+
+ git bisect reset &&
+ git checkout master^ &&
+ HEAD=$(git rev-parse --verify HEAD) &&
+ git bisect start &&
+ test $HEAD = $(cat .git/head-name) &&
+ git bisect reset &&
+ test $HEAD = $(git rev-parse --verify HEAD)
+
+'
+
#
#
test_done
diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh
index 0724864e56..2328b69947 100755
--- a/t/t6101-rev-parse-parents.sh
+++ b/t/t6101-rev-parse-parents.sh
@@ -26,7 +26,7 @@ test_expect_success 'final^1^1^1 = final^^^' "test $(git rev-parse final^1^1^1)
test_expect_success 'final^1^2' "test $(git rev-parse start2) = $(git rev-parse final^1^2)"
test_expect_success 'final^1^2 != final^1^1' "test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)"
test_expect_success 'final^1^3 not valid' "if git rev-parse --verify final^1^3; then false; else :; fi"
-test_expect_failure '--verify start2^1' 'git rev-parse --verify start2^1'
+test_expect_success '--verify start2^1' '! git rev-parse --verify start2^1'
test_expect_success '--verify start2^0' 'git rev-parse --verify start2^0'
test_expect_success 'repack for next test' 'git repack -a -d'
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index d0809eb651..f46ec93c83 100644..100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -43,8 +43,8 @@ test_expect_success 'Check atom names are valid' '
test -z "$bad"
'
-test_expect_failure 'Check invalid atoms names are errors' '
- git-for-each-ref --format="%(INVALID)" refs/heads
+test_expect_success 'Check invalid atoms names are errors' '
+ ! git-for-each-ref --format="%(INVALID)" refs/heads
'
test_expect_success 'Check format specifiers are ignored in naming date atoms' '
@@ -63,8 +63,8 @@ test_expect_success 'Check valid format specifiers for date fields' '
git-for-each-ref --format="%(authordate:rfc2822)" refs/heads
'
-test_expect_failure 'Check invalid format specifiers are errors' '
- git-for-each-ref --format="%(authordate:INVALID)" refs/heads
+test_expect_success 'Check invalid format specifiers are errors' '
+ ! git-for-each-ref --format="%(authordate:INVALID)" refs/heads
'
cat >expected <<\EOF
@@ -148,4 +148,65 @@ test_expect_success 'Check format "rfc2822" date fields output' '
git diff expected actual
'
+cat >expected <<\EOF
+refs/heads/master
+refs/tags/testtag
+EOF
+
+test_expect_success 'Verify ascending sort' '
+ git-for-each-ref --format="%(refname)" --sort=refname >actual &&
+ git diff expected actual
+'
+
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/heads/master
+EOF
+
+test_expect_success 'Verify descending sort' '
+ git-for-each-ref --format="%(refname)" --sort=-refname >actual &&
+ git diff expected actual
+'
+
+cat >expected <<\EOF
+'refs/heads/master'
+'refs/tags/testtag'
+EOF
+
+test_expect_success 'Quoting style: shell' '
+ git for-each-ref --shell --format="%(refname)" >actual &&
+ git diff expected actual
+'
+
+test_expect_success 'Quoting style: perl' '
+ git for-each-ref --perl --format="%(refname)" >actual &&
+ git diff expected actual
+'
+
+test_expect_success 'Quoting style: python' '
+ git for-each-ref --python --format="%(refname)" >actual &&
+ git diff expected actual
+'
+
+cat >expected <<\EOF
+"refs/heads/master"
+"refs/tags/testtag"
+EOF
+
+test_expect_success 'Quoting style: tcl' '
+ git for-each-ref --tcl --format="%(refname)" >actual &&
+ git diff expected actual
+'
+
+for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
+ test_expect_success "more than one quoting style: $i" "
+ git for-each-ref $i 2>&1 | (read line &&
+ case \$line in
+ \"error: more than one quoting style\"*) : happy;;
+ *) false
+ esac)
+ "
+done
+
test_done
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 8b43fb5a27..fa382c58da 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -22,7 +22,7 @@ test_expect_success \
test_expect_success \
'checking the commit' \
'git diff-tree -r -M --name-status HEAD^ HEAD | \
- grep -E "^R100.+path0/COPYING.+path1/COPYING"'
+ grep "^R100..*path0/COPYING..*path1/COPYING"'
test_expect_success \
'moving the file back into subdirectory' \
@@ -36,7 +36,7 @@ test_expect_success \
test_expect_success \
'checking the commit' \
'git diff-tree -r -M --name-status HEAD^ HEAD | \
- grep -E "^R100.+path1/COPYING.+path0/COPYING"'
+ grep "^R100..*path1/COPYING..*path0/COPYING"'
test_expect_success \
'adding another file' \
@@ -55,9 +55,9 @@ test_expect_success \
test_expect_success \
'checking the commit' \
'git diff-tree -r -M --name-status HEAD^ HEAD | \
- grep -E "^R100.+path0/COPYING.+path2/COPYING" &&
+ grep "^R100..*path0/COPYING..*path2/COPYING" &&
git diff-tree -r -M --name-status HEAD^ HEAD | \
- grep -E "^R100.+path0/README.+path2/README"'
+ grep "^R100..*path0/README..*path2/README"'
test_expect_success \
'succeed when source is a prefix of destination' \
@@ -74,13 +74,13 @@ test_expect_success \
test_expect_success \
'checking the commit' \
'git diff-tree -r -M --name-status HEAD^ HEAD | \
- grep -E "^R100.+path2/COPYING.+path1/path2/COPYING" &&
+ grep "^R100..*path2/COPYING..*path1/path2/COPYING" &&
git diff-tree -r -M --name-status HEAD^ HEAD | \
- grep -E "^R100.+path2/README.+path1/path2/README"'
+ grep "^R100..*path2/README..*path1/path2/README"'
-test_expect_failure \
+test_expect_success \
'do not move directory over existing directory' \
- 'mkdir path0 && mkdir path0/path2 && git mv path2 path0'
+ 'mkdir path0 && mkdir path0/path2 && ! git mv path2 path0'
test_expect_success \
'move into "."' \
@@ -118,4 +118,42 @@ test_expect_success "Sergey Vlasov's test case" '
git mv ab a
'
+test_expect_success 'absolute pathname' '(
+
+ rm -fr mine &&
+ mkdir mine &&
+ cd mine &&
+ test_create_repo one &&
+ cd one &&
+ mkdir sub &&
+ >sub/file &&
+ git add sub/file &&
+
+ git mv sub "$(pwd)/in" &&
+ ! test -d sub &&
+ test -d in &&
+ git ls-files --error-unmatch in/file
+
+
+)'
+
+test_expect_success 'absolute pathname outside should fail' '(
+
+ rm -fr mine &&
+ mkdir mine &&
+ cd mine &&
+ out=$(pwd) &&
+ test_create_repo one &&
+ cd one &&
+ mkdir sub &&
+ >sub/file &&
+ git add sub/file &&
+
+ ! git mv sub "$out/out" &&
+ test -d sub &&
+ ! test -d ../in &&
+ git ls-files --error-unmatch sub/file
+
+)'
+
test_done
diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh
index 68b2b92879..c8b4f65f38 100755
--- a/t/t7002-grep.sh
+++ b/t/t7002-grep.sh
@@ -107,8 +107,8 @@ do
diff expected actual
'
- test_expect_failure "grep -c $L (no /dev/null)" '
- git grep -c test $H | grep -q "/dev/null"
+ test_expect_success "grep -c $L (no /dev/null)" '
+ ! git grep -c test $H | grep -q /dev/null
'
done
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 2089351f7d..868babc4b2 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -114,7 +114,7 @@ test_expect_success 'use index-filter to move into a subdirectory' '
test_expect_success 'stops when msg filter fails' '
old=$(git rev-parse HEAD) &&
- ! git-filter-branch -f --msg-filter false &&
+ ! git-filter-branch -f --msg-filter false HEAD &&
test $old = $(git rev-parse HEAD) &&
rm -rf .git-rewrite
'
@@ -165,4 +165,18 @@ test_expect_success '"map" works in commit filter' '
git rev-parse --verify master
'
+test_expect_success 'Name needing quotes' '
+
+ git checkout -b rerere A &&
+ mkdir foo &&
+ name="れれれ" &&
+ >foo/$name &&
+ git add foo &&
+ git commit -m "Adding a file" &&
+ git filter-branch --tree-filter "rm -fr foo" &&
+ ! git ls-files --error-unmatch "foo/$name" &&
+ test $(git rev-parse --verify rerere) != $(git rev-parse --verify A)
+
+'
+
test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 0d07bc39c7..75cd33bde8 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -26,8 +26,8 @@ test_expect_success 'listing all tags in an empty tree should output nothing' '
test `git-tag | wc -l` -eq 0
'
-test_expect_failure 'looking for a tag in an empty tree should fail' \
- 'tag_exists mytag'
+test_expect_success 'looking for a tag in an empty tree should fail' \
+ '! (tag_exists mytag)'
test_expect_success 'creating a tag in an empty tree should fail' '
! git-tag mynotag &&
@@ -83,9 +83,9 @@ test_expect_success \
# special cases for creating tags:
-test_expect_failure \
+test_expect_success \
'trying to create a tag with the name of one existing should fail' \
- 'git tag mytag'
+ '! git tag mytag'
test_expect_success \
'trying to create a tag with a non-valid name should fail' '
@@ -146,8 +146,8 @@ test_expect_success \
! tag_exists myhead
'
-test_expect_failure 'trying to delete an already deleted tag should fail' \
- 'git-tag -d mytag'
+test_expect_success 'trying to delete an already deleted tag should fail' \
+ '! git-tag -d mytag'
# listing various tags with pattern matching:
@@ -265,16 +265,16 @@ test_expect_success \
test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD)
'
-test_expect_failure 'trying to verify an unknown tag should fail' \
- 'git-tag -v unknown-tag'
+test_expect_success 'trying to verify an unknown tag should fail' \
+ '! git-tag -v unknown-tag'
-test_expect_failure \
+test_expect_success \
'trying to verify a non-annotated and non-signed tag should fail' \
- 'git-tag -v non-annotated-tag'
+ '! git-tag -v non-annotated-tag'
-test_expect_failure \
+test_expect_success \
'trying to verify many non-annotated or unknown tags, should fail' \
- 'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
+ '! git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
# creating annotated tags:
@@ -339,20 +339,14 @@ test_expect_success \
'
test_expect_success \
- 'trying to create tags giving many -m or -F options should fail' '
+ 'trying to create tags giving both -m or -F options should fail' '
echo "message file 1" >msgfile1 &&
echo "message file 2" >msgfile2 &&
! tag_exists msgtag &&
- ! git-tag -m "message 1" -m "message 2" msgtag &&
- ! tag_exists msgtag &&
- ! git-tag -F msgfile1 -F msgfile2 msgtag &&
- ! tag_exists msgtag &&
! git-tag -m "message 1" -F msgfile1 msgtag &&
! tag_exists msgtag &&
! git-tag -F msgfile1 -m "message 1" msgtag &&
! tag_exists msgtag &&
- ! git-tag -F msgfile1 -m "message 1" -F msgfile2 msgtag &&
- ! tag_exists msgtag &&
! git-tag -m "message 1" -F msgfile1 -m "message 2" msgtag &&
! tag_exists msgtag
'
@@ -497,25 +491,21 @@ test_expect_success \
echo "tag-one-line" >expect &&
git-tag -l | grep "^tag-one-line" >actual &&
git diff expect actual &&
- git-tag -n 0 -l | grep "^tag-one-line" >actual &&
+ git-tag -n0 -l | grep "^tag-one-line" >actual &&
git diff expect actual &&
- git-tag -n 0 -l tag-one-line >actual &&
+ git-tag -n0 -l tag-one-line >actual &&
git diff expect actual &&
echo "tag-one-line A msg" >expect &&
- git-tag -n xxx -l | grep "^tag-one-line" >actual &&
- git diff expect actual &&
- git-tag -n "" -l | grep "^tag-one-line" >actual &&
- git diff expect actual &&
- git-tag -n 1 -l | grep "^tag-one-line" >actual &&
+ git-tag -n1 -l | grep "^tag-one-line" >actual &&
git diff expect actual &&
git-tag -n -l | grep "^tag-one-line" >actual &&
git diff expect actual &&
- git-tag -n 1 -l tag-one-line >actual &&
+ git-tag -n1 -l tag-one-line >actual &&
git diff expect actual &&
- git-tag -n 2 -l tag-one-line >actual &&
+ git-tag -n2 -l tag-one-line >actual &&
git diff expect actual &&
- git-tag -n 999 -l tag-one-line >actual &&
+ git-tag -n999 -l tag-one-line >actual &&
git diff expect actual
'
@@ -526,21 +516,21 @@ test_expect_success \
echo "tag-zero-lines" >expect &&
git-tag -l | grep "^tag-zero-lines" >actual &&
git diff expect actual &&
- git-tag -n 0 -l | grep "^tag-zero-lines" >actual &&
+ git-tag -n0 -l | grep "^tag-zero-lines" >actual &&
git diff expect actual &&
- git-tag -n 0 -l tag-zero-lines >actual &&
+ git-tag -n0 -l tag-zero-lines >actual &&
git diff expect actual &&
echo "tag-zero-lines " >expect &&
- git-tag -n 1 -l | grep "^tag-zero-lines" >actual &&
+ git-tag -n1 -l | grep "^tag-zero-lines" >actual &&
git diff expect actual &&
git-tag -n -l | grep "^tag-zero-lines" >actual &&
git diff expect actual &&
- git-tag -n 1 -l tag-zero-lines >actual &&
+ git-tag -n1 -l tag-zero-lines >actual &&
git diff expect actual &&
- git-tag -n 2 -l tag-zero-lines >actual &&
+ git-tag -n2 -l tag-zero-lines >actual &&
git diff expect actual &&
- git-tag -n 999 -l tag-zero-lines >actual &&
+ git-tag -n999 -l tag-zero-lines >actual &&
git diff expect actual
'
@@ -554,37 +544,37 @@ test_expect_success \
echo "tag-lines" >expect &&
git-tag -l | grep "^tag-lines" >actual &&
git diff expect actual &&
- git-tag -n 0 -l | grep "^tag-lines" >actual &&
+ git-tag -n0 -l | grep "^tag-lines" >actual &&
git diff expect actual &&
- git-tag -n 0 -l tag-lines >actual &&
+ git-tag -n0 -l tag-lines >actual &&
git diff expect actual &&
echo "tag-lines tag line one" >expect &&
- git-tag -n 1 -l | grep "^tag-lines" >actual &&
+ git-tag -n1 -l | grep "^tag-lines" >actual &&
git diff expect actual &&
git-tag -n -l | grep "^tag-lines" >actual &&
git diff expect actual &&
- git-tag -n 1 -l tag-lines >actual &&
+ git-tag -n1 -l tag-lines >actual &&
git diff expect actual &&
echo " tag line two" >>expect &&
- git-tag -n 2 -l | grep "^ *tag.line" >actual &&
+ git-tag -n2 -l | grep "^ *tag.line" >actual &&
git diff expect actual &&
- git-tag -n 2 -l tag-lines >actual &&
+ git-tag -n2 -l tag-lines >actual &&
git diff expect actual &&
echo " tag line three" >>expect &&
- git-tag -n 3 -l | grep "^ *tag.line" >actual &&
+ git-tag -n3 -l | grep "^ *tag.line" >actual &&
git diff expect actual &&
- git-tag -n 3 -l tag-lines >actual &&
+ git-tag -n3 -l tag-lines >actual &&
git diff expect actual &&
- git-tag -n 4 -l | grep "^ *tag.line" >actual &&
+ git-tag -n4 -l | grep "^ *tag.line" >actual &&
git diff expect actual &&
- git-tag -n 4 -l tag-lines >actual &&
+ git-tag -n4 -l tag-lines >actual &&
git diff expect actual &&
- git-tag -n 99 -l | grep "^ *tag.line" >actual &&
+ git-tag -n99 -l | grep "^ *tag.line" >actual &&
git diff expect actual &&
- git-tag -n 99 -l tag-lines >actual &&
+ git-tag -n99 -l tag-lines >actual &&
git diff expect actual
'
@@ -646,6 +636,46 @@ test_expect_success 'creating a signed tag with -m message should succeed' '
git diff expect actual
'
+get_tag_header u-signed-tag $commit commit $time >expect
+echo 'Another message' >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success 'sign with a given key id' '
+
+ git tag -u committer@example.com -m "Another message" u-signed-tag &&
+ get_tag_msg u-signed-tag >actual &&
+ git diff expect actual
+
+'
+
+test_expect_success 'sign with an unknown id (1)' '
+
+ ! git tag -u author@example.com -m "Another message" o-signed-tag
+
+'
+
+test_expect_success 'sign with an unknown id (2)' '
+
+ ! git tag -u DEADBEEF -m "Another message" o-signed-tag
+
+'
+
+cat >fakeeditor <<'EOF'
+#!/bin/sh
+test -n "$1" && exec >"$1"
+echo A signed tag message
+echo from a fake editor.
+EOF
+chmod +x fakeeditor
+
+get_tag_header implied-sign $commit commit $time >expect
+./fakeeditor >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success '-u implies signed tag' '
+ GIT_EDITOR=./fakeeditor git-tag -u CDDE430D implied-sign &&
+ get_tag_msg implied-sign >actual &&
+ git diff expect actual
+'
+
cat >sigmsgfile <<EOF
Another signed tag
message in a file.
@@ -673,6 +703,15 @@ test_expect_success 'creating a signed tag with -F - should succeed' '
git diff expect actual
'
+get_tag_header implied-annotate $commit commit $time >expect
+./fakeeditor >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success '-s implies annotated tag' '
+ GIT_EDITOR=./fakeeditor git-tag -s implied-annotate &&
+ get_tag_msg implied-annotate >actual &&
+ git diff expect actual
+'
+
test_expect_success \
'trying to create a signed tag with non-existing -F file should fail' '
! test -f nonexistingfile &&
@@ -863,25 +902,21 @@ test_expect_success \
echo "stag-one-line" >expect &&
git-tag -l | grep "^stag-one-line" >actual &&
git diff expect actual &&
- git-tag -n 0 -l | grep "^stag-one-line" >actual &&
+ git-tag -n0 -l | grep "^stag-one-line" >actual &&
git diff expect actual &&
- git-tag -n 0 -l stag-one-line >actual &&
+ git-tag -n0 -l stag-one-line >actual &&
git diff expect actual &&
echo "stag-one-line A message line signed" >expect &&
- git-tag -n xxx -l | grep "^stag-one-line" >actual &&
- git diff expect actual &&
- git-tag -n "" -l | grep "^stag-one-line" >actual &&
- git diff expect actual &&
- git-tag -n 1 -l | grep "^stag-one-line" >actual &&
+ git-tag -n1 -l | grep "^stag-one-line" >actual &&
git diff expect actual &&
git-tag -n -l | grep "^stag-one-line" >actual &&
git diff expect actual &&
- git-tag -n 1 -l stag-one-line >actual &&
+ git-tag -n1 -l stag-one-line >actual &&
git diff expect actual &&
- git-tag -n 2 -l stag-one-line >actual &&
+ git-tag -n2 -l stag-one-line >actual &&
git diff expect actual &&
- git-tag -n 999 -l stag-one-line >actual &&
+ git-tag -n999 -l stag-one-line >actual &&
git diff expect actual
'
@@ -892,21 +927,21 @@ test_expect_success \
echo "stag-zero-lines" >expect &&
git-tag -l | grep "^stag-zero-lines" >actual &&
git diff expect actual &&
- git-tag -n 0 -l | grep "^stag-zero-lines" >actual &&
+ git-tag -n0 -l | grep "^stag-zero-lines" >actual &&
git diff expect actual &&
- git-tag -n 0 -l stag-zero-lines >actual &&
+ git-tag -n0 -l stag-zero-lines >actual &&
git diff expect actual &&
echo "stag-zero-lines " >expect &&
- git-tag -n 1 -l | grep "^stag-zero-lines" >actual &&
+ git-tag -n1 -l | grep "^stag-zero-lines" >actual &&
git diff expect actual &&
git-tag -n -l | grep "^stag-zero-lines" >actual &&
git diff expect actual &&
- git-tag -n 1 -l stag-zero-lines >actual &&
+ git-tag -n1 -l stag-zero-lines >actual &&
git diff expect actual &&
- git-tag -n 2 -l stag-zero-lines >actual &&
+ git-tag -n2 -l stag-zero-lines >actual &&
git diff expect actual &&
- git-tag -n 999 -l stag-zero-lines >actual &&
+ git-tag -n999 -l stag-zero-lines >actual &&
git diff expect actual
'
@@ -920,37 +955,37 @@ test_expect_success \
echo "stag-lines" >expect &&
git-tag -l | grep "^stag-lines" >actual &&
git diff expect actual &&
- git-tag -n 0 -l | grep "^stag-lines" >actual &&
+ git-tag -n0 -l | grep "^stag-lines" >actual &&
git diff expect actual &&
- git-tag -n 0 -l stag-lines >actual &&
+ git-tag -n0 -l stag-lines >actual &&
git diff expect actual &&
echo "stag-lines stag line one" >expect &&
- git-tag -n 1 -l | grep "^stag-lines" >actual &&
+ git-tag -n1 -l | grep "^stag-lines" >actual &&
git diff expect actual &&
git-tag -n -l | grep "^stag-lines" >actual &&
git diff expect actual &&
- git-tag -n 1 -l stag-lines >actual &&
+ git-tag -n1 -l stag-lines >actual &&
git diff expect actual &&
echo " stag line two" >>expect &&
- git-tag -n 2 -l | grep "^ *stag.line" >actual &&
+ git-tag -n2 -l | grep "^ *stag.line" >actual &&
git diff expect actual &&
- git-tag -n 2 -l stag-lines >actual &&
+ git-tag -n2 -l stag-lines >actual &&
git diff expect actual &&
echo " stag line three" >>expect &&
- git-tag -n 3 -l | grep "^ *stag.line" >actual &&
+ git-tag -n3 -l | grep "^ *stag.line" >actual &&
git diff expect actual &&
- git-tag -n 3 -l stag-lines >actual &&
+ git-tag -n3 -l stag-lines >actual &&
git diff expect actual &&
- git-tag -n 4 -l | grep "^ *stag.line" >actual &&
+ git-tag -n4 -l | grep "^ *stag.line" >actual &&
git diff expect actual &&
- git-tag -n 4 -l stag-lines >actual &&
+ git-tag -n4 -l stag-lines >actual &&
git diff expect actual &&
- git-tag -n 99 -l | grep "^ *stag.line" >actual &&
+ git-tag -n99 -l | grep "^ *stag.line" >actual &&
git diff expect actual &&
- git-tag -n 99 -l stag-lines >actual &&
+ git-tag -n99 -l stag-lines >actual &&
git diff expect actual
'
@@ -992,16 +1027,42 @@ test_expect_success \
# try to sign with bad user.signingkey
git config user.signingkey BobTheMouse
-test_expect_failure \
+test_expect_success \
'git-tag -s fails if gpg is misconfigured' \
- 'git tag -s -m tail tag-gpg-failure'
+ '! git tag -s -m tail tag-gpg-failure'
git config --unset user.signingkey
# try to verify without gpg:
rm -rf gpghome
-test_expect_failure \
+test_expect_success \
'verify signed tag fails when public key is not present' \
- 'git-tag -v signed-tag'
+ '! git-tag -v signed-tag'
+
+test_expect_success \
+ 'git-tag -a fails if tag annotation is empty' '
+ ! (GIT_EDITOR=cat git tag -a initial-comment)
+'
+
+test_expect_success \
+ 'message in editor has initial comment' '
+ GIT_EDITOR=cat git tag -a initial-comment > actual
+ # check the first line --- should be empty
+ first=$(sed -e 1q <actual) &&
+ test -z "$first" &&
+ # remove commented lines from the remainder -- should be empty
+ rest=$(sed -e 1d -e '/^#/d' <actual) &&
+ test -z "$rest"
+'
+
+get_tag_header reuse $commit commit $time >expect
+echo "An annotation to be reused" >> expect
+test_expect_success \
+ 'overwriting an annoted tag should use its previous body' '
+ git tag -a -m "An annotation to be reused" reuse &&
+ GIT_EDITOR=true git tag -f -a reuse &&
+ get_tag_msg reuse >actual &&
+ git diff expect actual
+'
test_done
diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh
index 28643b0da4..c1cec55306 100755
--- a/t/t7005-editor.sh
+++ b/t/t7005-editor.sh
@@ -4,6 +4,8 @@ test_description='GIT_EDITOR, core.editor, and stuff'
. ./test-lib.sh
+OLD_TERM="$TERM"
+
for i in GIT_EDITOR core_editor EDITOR VISUAL vi
do
cat >e-$i.sh <<-EOF
@@ -13,7 +15,6 @@ do
done
unset vi
mv e-vi.sh vi
-PATH=".:$PATH"
unset EDITOR VISUAL GIT_EDITOR
test_expect_success setup '
@@ -36,7 +37,7 @@ test_expect_success 'dumb should error out when falling back on vi' '
if git commit --amend
then
echo "Oops?"
- exit 1
+ false
else
: happy
fi
@@ -59,7 +60,7 @@ do
;;
esac
test_expect_success "Using $i" '
- git commit --amend &&
+ git --exec-path=. commit --amend &&
git show -s --pretty=oneline |
sed -e "s/^[0-9a-f]* //" >actual &&
diff actual expect
@@ -81,11 +82,13 @@ do
;;
esac
test_expect_success "Using $i (override)" '
- git commit --amend &&
+ git --exec-path=. commit --amend &&
git show -s --pretty=oneline |
sed -e "s/^[0-9a-f]* //" >actual &&
diff actual expect
'
done
+TERM="$OLD_TERM"
+
test_done
diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh
new file mode 100755
index 0000000000..e809e0e2c9
--- /dev/null
+++ b/t/t7010-setup.sh
@@ -0,0 +1,164 @@
+#!/bin/sh
+
+test_description='setup taking and sanitizing funny paths'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ mkdir -p a/b/c a/e &&
+ D=$(pwd) &&
+ >a/b/c/d &&
+ >a/e/f
+
+'
+
+test_expect_success 'git add (absolute)' '
+
+ git add "$D/a/b/c/d" &&
+ git ls-files >current &&
+ echo a/b/c/d >expect &&
+ diff -u expect current
+
+'
+
+
+test_expect_success 'git add (funny relative)' '
+
+ rm -f .git/index &&
+ (
+ cd a/b &&
+ git add "../e/./f"
+ ) &&
+ git ls-files >current &&
+ echo a/e/f >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git rm (absolute)' '
+
+ rm -f .git/index &&
+ git add a &&
+ git rm -f --cached "$D/a/b/c/d" &&
+ git ls-files >current &&
+ echo a/e/f >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git rm (funny relative)' '
+
+ rm -f .git/index &&
+ git add a &&
+ (
+ cd a/b &&
+ git rm -f --cached "../e/./f"
+ ) &&
+ git ls-files >current &&
+ echo a/b/c/d >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (absolute)' '
+
+ rm -f .git/index &&
+ git add a &&
+ git ls-files "$D/a/e/../b" >current &&
+ echo a/b/c/d >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #1)' '
+
+ rm -f .git/index &&
+ git add a &&
+ (
+ cd a/b &&
+ git ls-files "../b/c"
+ ) >current &&
+ echo c/d >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #2)' '
+
+ rm -f .git/index &&
+ git add a &&
+ (
+ cd a/b &&
+ git ls-files --full-name "../e/f"
+ ) >current &&
+ echo a/e/f >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #3)' '
+
+ rm -f .git/index &&
+ git add a &&
+ (
+ cd a/b &&
+ if git ls-files "../e/f"
+ then
+ echo Gaah, should have failed
+ exit 1
+ else
+ : happy
+ fi
+ )
+
+'
+
+test_expect_success 'commit using absolute path names' '
+ git commit -m "foo" &&
+ echo aa >>a/b/c/d &&
+ git commit -m "aa" "$(pwd)/a/b/c/d"
+'
+
+test_expect_success 'log using absolute path names' '
+ echo bb >>a/b/c/d &&
+ git commit -m "bb" $(pwd)/a/b/c/d &&
+
+ git log a/b/c/d >f1.txt &&
+ git log "$(pwd)/a/b/c/d" >f2.txt &&
+ diff -u f1.txt f2.txt
+'
+
+test_expect_success 'blame using absolute path names' '
+ git blame a/b/c/d >f1.txt &&
+ git blame "$(pwd)/a/b/c/d" >f2.txt &&
+ diff -u f1.txt f2.txt
+'
+
+test_expect_success 'setup deeper work tree' '
+ test_create_repo tester
+'
+
+test_expect_success 'add a directory outside the work tree' '(
+ cd tester &&
+ d1="$(cd .. ; pwd)" &&
+ git add "$d1"
+)'
+
+test_expect_success 'add a file outside the work tree, nasty case 1' '(
+ cd tester &&
+ f="$(pwd)x" &&
+ echo "$f" &&
+ touch "$f" &&
+ git add "$f"
+)'
+
+test_expect_success 'add a file outside the work tree, nasty case 2' '(
+ cd tester &&
+ f="$(pwd | sed "s/.$//")x" &&
+ echo "$f" &&
+ touch "$f" &&
+ git add "$f"
+)'
+
+test_done
diff --git a/t/t7101-reset.sh b/t/t7101-reset.sh
index 66d40430b2..0d9874bfd7 100755
--- a/t/t7101-reset.sh
+++ b/t/t7101-reset.sh
@@ -36,28 +36,28 @@ test_expect_success \
'test -d path0 &&
test -f path0/COPYING'
-test_expect_failure \
+test_expect_success \
'checking lack of path1/path2/COPYING' \
- 'test -f path1/path2/COPYING'
+ '! test -f path1/path2/COPYING'
-test_expect_failure \
+test_expect_success \
'checking lack of path1/COPYING' \
- 'test -f path1/COPYING'
+ '! test -f path1/COPYING'
-test_expect_failure \
+test_expect_success \
'checking lack of COPYING' \
- 'test -f COPYING'
+ '! test -f COPYING'
-test_expect_failure \
+test_expect_success \
'checking checking lack of path1/COPYING-TOO' \
- 'test -f path0/COPYING-TOO'
+ '! test -f path0/COPYING-TOO'
-test_expect_failure \
+test_expect_success \
'checking lack of path1/path2' \
- 'test -d path1/path2'
+ '! test -d path1/path2'
-test_expect_failure \
+test_expect_success \
'checking lack of path1' \
- 'test -d path1'
+ '! test -d path1'
test_done
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index f64b1cbf75..e5c9f30c73 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -59,6 +59,15 @@ test_expect_success 'giving a non existing revision should fail' '
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
'
+test_expect_success 'reset --soft with unmerged index should fail' '
+ touch .git/MERGE_HEAD &&
+ echo "100644 44c5b5884550c17758737edcced463447b91d42b 1 un" |
+ git update-index --index-info &&
+ ! git reset --soft HEAD &&
+ rm .git/MERGE_HEAD &&
+ git rm --cached -- un
+'
+
test_expect_success \
'giving paths with options different than --mixed should fail' '
! git reset --soft -- first &&
@@ -402,4 +411,21 @@ test_expect_success 'test resetting the index at give paths' '
'
+test_expect_success 'resetting an unmodified path is a no-op' '
+ git reset --hard &&
+ git reset -- file1 &&
+ git diff-files --exit-code &&
+ git diff-index --cached --exit-code HEAD
+'
+
+cat > expect << EOF
+file2: needs update
+EOF
+
+test_expect_success '--mixed refreshes the index' '
+ echo 123 >> file2 &&
+ git reset --mixed HEAD > output &&
+ git diff --exit-code expect output
+'
+
test_done
diff --git a/t/t7103-reset-bare.sh b/t/t7103-reset-bare.sh
new file mode 100755
index 0000000000..b25a77f910
--- /dev/null
+++ b/t/t7103-reset-bare.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+test_description='git-reset in a bare repository'
+. ./test-lib.sh
+
+test_expect_success 'setup non-bare' '
+ echo one >file &&
+ git add file &&
+ git commit -m one &&
+ echo two >file &&
+ git commit -a -m two
+'
+
+test_expect_success 'setup bare' '
+ git clone --bare . bare.git &&
+ cd bare.git
+'
+
+test_expect_success 'hard reset is not allowed' '
+ ! git reset --hard HEAD^
+'
+
+test_expect_success 'soft reset is allowed' '
+ git reset --soft HEAD^ &&
+ test "`git show --pretty=format:%s | head -n 1`" = "one"
+'
+
+test_done
diff --git a/t/t7104-reset.sh b/t/t7104-reset.sh
new file mode 100755
index 0000000000..f136ee7bb5
--- /dev/null
+++ b/t/t7104-reset.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='reset --hard unmerged'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ mkdir before later &&
+ >before/1 &&
+ >before/2 &&
+ >hello &&
+ >later/3 &&
+ git add before hello later &&
+ git commit -m world &&
+
+ H=$(git rev-parse :hello) &&
+ git rm --cached hello &&
+ echo "100644 $H 2 hello" | git update-index --index-info &&
+
+ rm -f hello &&
+ mkdir -p hello &&
+ >hello/world &&
+ test "$(git ls-files -o)" = hello/world
+
+'
+
+test_expect_success 'reset --hard should restore unmerged ones' '
+
+ git reset --hard &&
+ git ls-files --error-unmatch before/1 before/2 hello later/3 &&
+ test -f hello
+
+'
+
+test_expect_success 'reset --hard did not corrupt index nor cached-tree' '
+
+ T=$(git write-tree) &&
+ rm -f .git/index &&
+ git add before hello later &&
+ U=$(git write-tree) &&
+ test "$T" = "$U"
+
+'
+
+test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index ed2e9ee3c6..dbf1ace29e 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -20,6 +20,8 @@ Test switching across them.
. ./test-lib.sh
+test_tick
+
fill () {
for i
do
@@ -30,9 +32,10 @@ fill () {
test_expect_success setup '
+ fill x y z > same &&
fill 1 2 3 4 5 6 7 8 >one &&
fill a b c d e >two &&
- git add one two &&
+ git add same one two &&
git commit -m "Initial A one, A two" &&
git checkout -b renamer &&
@@ -74,16 +77,44 @@ test_expect_success "checkout with dirty tree without -m" '
'
+test_expect_success "checkout with unrelated dirty tree without -m" '
+
+ git checkout -f master &&
+ fill 0 1 2 3 4 5 6 7 8 >same &&
+ cp same kept
+ git checkout side >messages &&
+ git diff same kept
+ (cat > messages.expect <<EOF
+M same
+EOF
+) &&
+ touch messages.expect &&
+ git diff messages.expect messages
+'
+
test_expect_success "checkout -m with dirty tree" '
git checkout -f master &&
- git clean &&
+ git clean -f &&
fill 0 1 2 3 4 5 6 7 8 >one &&
- git checkout -m side &&
+ git checkout -m side > messages &&
test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&
+ (cat >expect.messages <<EOF
+Merging side with local
+Merging:
+ab76817 Side M one, D two, A three
+virtual local
+found 1 common ancestor(s):
+7329388 Initial A one, A two
+Auto-merged one
+M one
+EOF
+) &&
+ git diff expect.messages messages &&
+
fill "M one" "A three" "D two" >expect.master &&
git diff --name-status master >current.master &&
diff expect.master current.master &&
@@ -99,7 +130,7 @@ test_expect_success "checkout -m with dirty tree" '
test_expect_success "checkout -m with dirty tree, renamed" '
- git checkout -f master && git clean &&
+ git checkout -f master && git clean -f &&
fill 1 2 3 4 5 7 8 >one &&
if git checkout renamer
@@ -121,7 +152,7 @@ test_expect_success "checkout -m with dirty tree, renamed" '
test_expect_success 'checkout -m with merge conflict' '
- git checkout -f master && git clean &&
+ git checkout -f master && git clean -f &&
fill 1 T 3 4 5 6 S 8 >one &&
if git checkout renamer
@@ -144,8 +175,17 @@ test_expect_success 'checkout -m with merge conflict' '
test_expect_success 'checkout to detach HEAD' '
- git checkout -f renamer && git clean &&
- git checkout renamer^ &&
+ git checkout -f renamer && git clean -f &&
+ git checkout renamer^ 2>messages &&
+ (cat >messages.expect <<EOF
+Note: moving to "renamer^" which isn'"'"'t a local branch
+If you want to create a new branch from this checkout, you may do so
+(now or later) by using -b with the checkout command again. Example:
+ git checkout -b <new_branch_name>
+HEAD is now at 7329388... Initial A one, A two
+EOF
+) &&
+ git diff messages.expect messages &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
test "z$H" = "z$M" &&
@@ -160,7 +200,7 @@ test_expect_success 'checkout to detach HEAD' '
test_expect_success 'checkout to detach HEAD with branchname^' '
- git checkout -f master && git clean &&
+ git checkout -f master && git clean -f &&
git checkout renamer^ &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
@@ -174,9 +214,25 @@ test_expect_success 'checkout to detach HEAD with branchname^' '
fi
'
+test_expect_success 'checkout to detach HEAD with :/message' '
+
+ git checkout -f master && git clean -f &&
+ git checkout ":/Initial" &&
+ H=$(git rev-parse --verify HEAD) &&
+ M=$(git show-ref -s --verify refs/heads/master) &&
+ test "z$H" = "z$M" &&
+ if git symbolic-ref HEAD >/dev/null 2>&1
+ then
+ echo "OOPS, HEAD is still symbolic???"
+ false
+ else
+ : happy
+ fi
+'
+
test_expect_success 'checkout to detach HEAD with HEAD^0' '
- git checkout -f master && git clean &&
+ git checkout -f master && git clean -f &&
git checkout HEAD^0 &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
index eb0847afe9..38403643a6 100755
--- a/t/t7300-clean.sh
+++ b/t/t7300-clean.sh
@@ -7,6 +7,8 @@ test_description='git-clean basic tests'
. ./test-lib.sh
+git config clean.requireForce no
+
test_expect_success 'setup' '
mkdir -p src &&
@@ -37,6 +39,107 @@ test_expect_success 'git-clean' '
'
+test_expect_success 'git-clean src/' '
+
+ mkdir -p build docs &&
+ touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+ git-clean src/ &&
+ test -f Makefile &&
+ test -f README &&
+ test -f src/part1.c &&
+ test -f src/part2.c &&
+ test -f a.out &&
+ test ! -f src/part3.c &&
+ test -f docs/manual.txt &&
+ test -f obj.o &&
+ test -f build/lib.so
+
+'
+
+test_expect_success 'git-clean src/ src/' '
+
+ mkdir -p build docs &&
+ touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+ git-clean src/ src/ &&
+ test -f Makefile &&
+ test -f README &&
+ test -f src/part1.c &&
+ test -f src/part2.c &&
+ test -f a.out &&
+ test ! -f src/part3.c &&
+ test -f docs/manual.txt &&
+ test -f obj.o &&
+ test -f build/lib.so
+
+'
+
+test_expect_success 'git-clean with prefix' '
+
+ mkdir -p build docs &&
+ touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+ (cd src/ && git-clean) &&
+ test -f Makefile &&
+ test -f README &&
+ test -f src/part1.c &&
+ test -f src/part2.c &&
+ test -f a.out &&
+ test ! -f src/part3.c &&
+ test -f docs/manual.txt &&
+ test -f obj.o &&
+ test -f build/lib.so
+
+'
+test_expect_success 'git-clean -d with prefix and path' '
+
+ mkdir -p build docs src/feature &&
+ touch a.out src/part3.c src/feature/file.c docs/manual.txt obj.o build/lib.so &&
+ (cd src/ && git-clean -d feature/) &&
+ test -f Makefile &&
+ test -f README &&
+ test -f src/part1.c &&
+ test -f src/part2.c &&
+ test -f a.out &&
+ test -f src/part3.c &&
+ test ! -f src/feature/file.c &&
+ test -f docs/manual.txt &&
+ test -f obj.o &&
+ test -f build/lib.so
+
+'
+
+test_expect_success '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
+ git-clean &&
+ test -f Makefile &&
+ test -f README &&
+ test -f src/part1.c &&
+ test -f src/part2.c &&
+ test ! -f a.out &&
+ test ! -f src/part3.c &&
+ test ! -f src/part4.c &&
+ test -f docs/manual.txt &&
+ test -f obj.o &&
+ test -f build/lib.so
+
+'
+
+test_expect_success 'git-clean with wildcard' '
+
+ touch a.clean b.clean other.c &&
+ git-clean "*.clean" &&
+ test -f Makefile &&
+ test -f README &&
+ test -f src/part1.c &&
+ test -f src/part2.c &&
+ test ! -f a.clean &&
+ test ! -f b.clean &&
+ test -f other.c
+
+'
+
test_expect_success 'git-clean -n' '
mkdir -p build docs &&
@@ -71,6 +174,24 @@ test_expect_success 'git-clean -d' '
'
+test_expect_success 'git-clean -d src/ examples/' '
+
+ mkdir -p build docs examples &&
+ touch a.out src/part3.c docs/manual.txt obj.o build/lib.so examples/1.c &&
+ git-clean -d src/ examples/ &&
+ test -f Makefile &&
+ test -f README &&
+ test -f src/part1.c &&
+ test -f src/part2.c &&
+ test -f a.out &&
+ test ! -f src/part3.c &&
+ test ! -f examples/1.c &&
+ test -f docs/manual.txt &&
+ test -f obj.o &&
+ test -f build/lib.so
+
+'
+
test_expect_success 'git-clean -x' '
mkdir -p build docs &&
@@ -139,6 +260,13 @@ test_expect_success 'git-clean -d -X' '
'
+test_expect_success 'clean.requireForce defaults to true' '
+
+ git config --unset clean.requireForce &&
+ ! git-clean
+
+'
+
test_expect_success 'clean.requireForce' '
git config clean.requireForce true &&
@@ -177,4 +305,25 @@ test_expect_success 'clean.requireForce and -f' '
'
+test_expect_success 'core.excludesfile' '
+
+ echo excludes >excludes &&
+ echo included >included &&
+ git config core.excludesfile excludes &&
+ output=$(git clean -n excludes included 2>&1) &&
+ expr "$output" : ".*included" >/dev/null &&
+ ! expr "$output" : ".*excludes" >/dev/null
+
+'
+
+test_expect_success 'removal failure' '
+
+ mkdir foo &&
+ touch foo/bar &&
+ chmod 0 foo &&
+ ! git clean -f -d
+
+'
+chmod 755 foo
+
test_done
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index 4fe3a41f07..2ef85a869d 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -13,11 +13,11 @@ subcommands of git-submodule.
#
# Test setup:
-# -create a repository in directory lib
+# -create a repository in directory init
# -add a couple of files
-# -add directory lib to 'superproject', this creates a DIRLINK entry
+# -add directory init to 'superproject', this creates a DIRLINK entry
# -add a couple of regular files to enable testing of submodule filtering
-# -mv lib subrepo
+# -mv init subrepo
# -add an entry to .gitmodules for submodule 'example'
#
test_expect_success 'Prepare submodule testing' '
@@ -25,8 +25,8 @@ test_expect_success 'Prepare submodule testing' '
git-add t &&
git-commit -m "initial commit" &&
git branch initial HEAD &&
- mkdir lib &&
- cd lib &&
+ mkdir init &&
+ cd init &&
git init &&
echo a >a &&
git add a &&
@@ -41,10 +41,10 @@ test_expect_success 'Prepare submodule testing' '
cd .. &&
echo a >a &&
echo z >z &&
- git add a lib z &&
+ git add a init z &&
git-commit -m "super commit 1" &&
- mv lib .subrepo &&
- GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/lib.git
+ mv init .subrepo &&
+ GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/init.git
'
test_expect_success 'status should fail for unmapped paths' '
@@ -52,7 +52,7 @@ test_expect_success 'status should fail for unmapped paths' '
then
echo "[OOPS] submodule status succeeded"
false
- elif ! GIT_CONFIG=.gitmodules git config submodule.example.path lib
+ elif ! GIT_CONFIG=.gitmodules git config submodule.example.path init
then
echo "[OOPS] git config failed to update .gitmodules"
false
@@ -71,7 +71,7 @@ test_expect_success 'status should initially be "missing"' '
test_expect_success 'init should register submodule url in .git/config' '
git-submodule init &&
url=$(git config submodule.example.url) &&
- if test "$url" != "git://example.com/lib.git"
+ if test "$url" != "git://example.com/init.git"
then
echo "[OOPS] init succeeded but submodule url is wrong"
false
@@ -83,41 +83,41 @@ test_expect_success 'init should register submodule url in .git/config' '
'
test_expect_success 'update should fail when path is used by a file' '
- echo "hello" >lib &&
+ echo "hello" >init &&
if git-submodule update
then
echo "[OOPS] update should have failed"
false
- elif test "$(cat lib)" != "hello"
+ elif test "$(cat init)" != "hello"
then
- echo "[OOPS] update failed but lib file was molested"
+ echo "[OOPS] update failed but init file was molested"
false
else
- rm lib
+ rm init
fi
'
test_expect_success 'update should fail when path is used by a nonempty directory' '
- mkdir lib &&
- echo "hello" >lib/a &&
+ mkdir init &&
+ echo "hello" >init/a &&
if git-submodule update
then
echo "[OOPS] update should have failed"
false
- elif test "$(cat lib/a)" != "hello"
+ elif test "$(cat init/a)" != "hello"
then
- echo "[OOPS] update failed but lib/a was molested"
+ echo "[OOPS] update failed but init/a was molested"
false
else
- rm lib/a
+ rm init/a
fi
'
test_expect_success 'update should work when path is an empty dir' '
- rm -rf lib &&
- mkdir lib &&
+ rm -rf init &&
+ mkdir init &&
git-submodule update &&
- head=$(cd lib && git rev-parse HEAD) &&
+ head=$(cd init && git rev-parse HEAD) &&
if test -z "$head"
then
echo "[OOPS] Failed to obtain submodule head"
@@ -134,7 +134,7 @@ test_expect_success 'status should be "up-to-date" after update' '
'
test_expect_success 'status should be "modified" after submodule commit' '
- cd lib &&
+ cd init &&
echo b >b &&
git add b &&
git-commit -m "submodule commit 2" &&
@@ -157,8 +157,8 @@ test_expect_success 'git diff should report the SHA1 of the new submodule commit
'
test_expect_success 'update should checkout rev1' '
- git-submodule update &&
- head=$(cd lib && git rev-parse HEAD) &&
+ git-submodule update init &&
+ head=$(cd init && git rev-parse HEAD) &&
if test -z "$head"
then
echo "[OOPS] submodule git rev-parse returned nothing"
@@ -182,13 +182,13 @@ test_expect_success 'checkout superproject with subproject already present' '
test_expect_success 'apply submodule diff' '
git branch second &&
(
- cd lib &&
+ cd init &&
echo s >s &&
git add s &&
git commit -m "change subproject"
) &&
- git update-index --add lib &&
- git-commit -m "change lib" &&
+ git update-index --add init &&
+ git-commit -m "change init" &&
git-format-patch -1 --stdout >P.diff &&
git checkout second &&
git apply --index P.diff &&
diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh
index abbf54ba63..baed6ce96b 100755
--- a/t/t7500-commit.sh
+++ b/t/t7500-commit.sh
@@ -93,4 +93,49 @@ test_expect_success 'commit message from file should override template' '
commit_msg_is "standard input msg"
'
+test_expect_success 'using alternate GIT_INDEX_FILE (1)' '
+
+ cp .git/index saved-index &&
+ (
+ echo some new content >file &&
+ GIT_INDEX_FILE=.git/another_index &&
+ export GIT_INDEX_FILE &&
+ git add file &&
+ git commit -m "commit using another index" &&
+ git diff-index --exit-code HEAD &&
+ git diff-files --exit-code
+ ) &&
+ cmp .git/index saved-index >/dev/null
+
+'
+
+test_expect_success 'using alternate GIT_INDEX_FILE (2)' '
+
+ cp .git/index saved-index &&
+ (
+ rm -f .git/no-such-index &&
+ GIT_INDEX_FILE=.git/no-such-index &&
+ export GIT_INDEX_FILE &&
+ git commit -m "commit using nonexistent index" &&
+ test -z "$(git ls-files)" &&
+ test -z "$(git ls-tree HEAD)"
+
+ ) &&
+ cmp .git/index saved-index >/dev/null
+'
+
+cat > expect << EOF
+zort
+
+Signed-off-by: C O Mitter <committer@example.com>
+EOF
+
+test_expect_success '--signoff' '
+ echo "yet another content *narf*" >> foo &&
+ echo "zort" |
+ GIT_EDITOR=../t7500/add-content git commit -s -F - foo &&
+ git cat-file commit HEAD | sed "1,/^$/d" > output &&
+ diff expect output
+'
+
test_done
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index b151b51a34..361886c3d6 100644..100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -4,7 +4,7 @@
#
# FIXME: Test the various index usages, -i and -o, test reflog,
-# signoff, hooks
+# signoff
test_description='git-commit'
. ./test-lib.sh
@@ -17,39 +17,49 @@ test_expect_success \
git-add file && \
git-status | grep 'Initial commit'"
-test_expect_failure \
+test_expect_success \
"fail initial amend" \
- "git-commit --amend"
+ "! git-commit --amend"
test_expect_success \
"initial commit" \
"git-commit -m initial"
-test_expect_failure \
+test_expect_success \
"invalid options 1" \
- "git-commit -m foo -m bar -F file"
+ "! git-commit -m foo -m bar -F file"
-test_expect_failure \
+test_expect_success \
"invalid options 2" \
- "git-commit -C HEAD -m illegal"
+ "! git-commit -C HEAD -m illegal"
+
+test_expect_success \
+ "using paths with -a" \
+ "echo King of the bongo >file &&
+ ! git-commit -m foo -a file"
+
+test_expect_success \
+ "using paths with --interactive" \
+ "echo bong-o-bong >file &&
+ ! echo 7 | git-commit -m foo --interactive file"
-test_expect_failure \
+test_expect_success \
"using invalid commit with -C" \
- "git-commit -C bogus"
+ "! git-commit -C bogus"
-test_expect_failure \
+test_expect_success \
"testing nothing to commit" \
- "git-commit -m initial"
+ "! git-commit -m initial"
test_expect_success \
"next commit" \
"echo 'bongo bongo bongo' >file \
git-commit -m next -a"
-test_expect_failure \
+test_expect_success \
"commit message from non-existing file" \
"echo 'more bongo: bongo bongo bongo bongo' >file && \
- git-commit -F gah -a"
+ ! git-commit -F gah -a"
# Empty except stray tabs and spaces on a few lines.
sed -e 's/@$//' >msg <<EOF
@@ -58,9 +68,9 @@ sed -e 's/@$//' >msg <<EOF
@
Signed-off-by: hula
EOF
-test_expect_failure \
+test_expect_success \
"empty commit message" \
- "git-commit -F msg -a"
+ "! git-commit -F msg -a"
test_expect_success \
"commit message from file" \
@@ -69,7 +79,8 @@ test_expect_success \
cat >editor <<\EOF
#!/bin/sh
-sed -i -e "s/a file/an amend commit/g" $1
+sed -e "s/a file/an amend commit/g" < $1 > $1-
+mv $1- $1
EOF
chmod 755 editor
@@ -77,10 +88,10 @@ test_expect_success \
"amend commit" \
"VISUAL=./editor git-commit --amend"
-test_expect_failure \
+test_expect_success \
"passing -m and -F" \
"echo 'enough with the bongos' >file && \
- git-commit -F msg -m amending ."
+ ! git-commit -F msg -m amending ."
test_expect_success \
"using message from other commit" \
@@ -88,7 +99,8 @@ test_expect_success \
cat >editor <<\EOF
#!/bin/sh
-sed -i -e "s/amend/older/g" $1
+sed -e "s/amend/older/g" < $1 > $1-
+mv $1- $1
EOF
chmod 755 editor
@@ -163,4 +175,174 @@ test_expect_success 'partial commit that involves removal (3)' '
'
+author="The Real Author <someguy@his.email.org>"
+test_expect_success 'amend commit to fix author' '
+
+ oldtick=$GIT_AUTHOR_DATE &&
+ test_tick &&
+ git reset --hard &&
+ git cat-file -p HEAD |
+ sed -e "s/author.*/author $author $oldtick/" \
+ -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+ expected &&
+ git commit --amend --author="$author" &&
+ git cat-file -p HEAD > current &&
+ diff expected current
+
+'
+
+test_expect_success 'sign off (1)' '
+
+ echo 1 >positive &&
+ git add positive &&
+ git commit -s -m "thank you" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo thank you
+ echo
+ git var GIT_COMMITTER_IDENT |
+ sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+ ) >expected &&
+ diff -u expected actual
+
+'
+
+test_expect_success 'sign off (2)' '
+
+ echo 2 >positive &&
+ git add positive &&
+ existing="Signed-off-by: Watch This <watchthis@example.com>" &&
+ git commit -s -m "thank you
+
+$existing" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo thank you
+ echo
+ echo $existing
+ git var GIT_COMMITTER_IDENT |
+ sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+ ) >expected &&
+ diff -u expected actual
+
+'
+
+test_expect_success 'multiple -m' '
+
+ >negative &&
+ git add negative &&
+ git commit -m "one" -m "two" -m "three" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo one
+ echo
+ echo two
+ echo
+ echo three
+ ) >expected &&
+ diff -u expected actual
+
+'
+
+author="The Real Author <someguy@his.email.org>"
+test_expect_success 'amend commit to fix author' '
+
+ oldtick=$GIT_AUTHOR_DATE &&
+ test_tick &&
+ git reset --hard &&
+ git cat-file -p HEAD |
+ sed -e "s/author.*/author $author $oldtick/" \
+ -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+ expected &&
+ git commit --amend --author="$author" &&
+ git cat-file -p HEAD > current &&
+ diff expected current
+
+'
+
+test_expect_success 'git commit <file> with dirty index' '
+ echo tacocat > elif &&
+ echo tehlulz > chz &&
+ git add chz &&
+ git commit elif -m "tacocat is a palindrome" &&
+ git show --stat | grep elif &&
+ git diff --cached | grep chz
+'
+
+test_expect_success 'same tree (single parent)' '
+
+ git reset --hard
+
+ if git commit -m empty
+ then
+ echo oops -- should have complained
+ false
+ else
+ : happy
+ fi
+
+'
+
+test_expect_success 'same tree (single parent) --allow-empty' '
+
+ git commit --allow-empty -m "forced empty" &&
+ git cat-file commit HEAD | grep forced
+
+'
+
+test_expect_success 'same tree (merge and amend merge)' '
+
+ git checkout -b side HEAD^ &&
+ echo zero >zero &&
+ git add zero &&
+ git commit -m "add zero" &&
+ git checkout master &&
+
+ git merge -s ours side -m "empty ok" &&
+ git diff HEAD^ HEAD >actual &&
+ : >expected &&
+ diff -u expected actual &&
+
+ git commit --amend -m "empty really ok" &&
+ git diff HEAD^ HEAD >actual &&
+ : >expected &&
+ diff -u expected actual
+
+'
+
+test_expect_success 'amend using the message from another commit' '
+
+ git reset --hard &&
+ test_tick &&
+ git commit --allow-empty -m "old commit" &&
+ old=$(git rev-parse --verify HEAD) &&
+ test_tick &&
+ git commit --allow-empty -m "new commit" &&
+ new=$(git rev-parse --verify HEAD) &&
+ test_tick &&
+ git commit --allow-empty --amend -C "$old" &&
+ git show --pretty="format:%ad %s" "$old" >expected &&
+ git show --pretty="format:%ad %s" HEAD >actual &&
+ diff -u expected actual
+
+'
+
+test_expect_success 'amend using the message from a commit named with tag' '
+
+ git reset --hard &&
+ test_tick &&
+ git commit --allow-empty -m "old commit" &&
+ old=$(git rev-parse --verify HEAD) &&
+ git tag -a -m "tag on old" tagged-old HEAD &&
+ test_tick &&
+ git commit --allow-empty -m "new commit" &&
+ new=$(git rev-parse --verify HEAD) &&
+ test_tick &&
+ git commit --allow-empty --amend -C tagged-old &&
+ git show --pretty="format:%ad %s" "$old" >expected &&
+ git show --pretty="format:%ad %s" HEAD >actual &&
+ diff -u expected actual
+
+'
+
test_done
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
new file mode 100755
index 0000000000..b780fddc08
--- /dev/null
+++ b/t/t7502-commit.sh
@@ -0,0 +1,186 @@
+#!/bin/sh
+
+test_description='git commit porcelain-ish'
+
+. ./test-lib.sh
+
+test_expect_success 'the basics' '
+
+ echo doing partial >"commit is" &&
+ mkdir not &&
+ echo very much encouraged but we should >not/forbid &&
+ git add "commit is" not &&
+ echo update added "commit is" file >"commit is" &&
+ echo also update another >not/forbid &&
+ test_tick &&
+ git commit -a -m "initial with -a" &&
+
+ git cat-file blob HEAD:"commit is" >current.1 &&
+ git cat-file blob HEAD:not/forbid >current.2 &&
+
+ cmp current.1 "commit is" &&
+ cmp current.2 not/forbid
+
+'
+
+test_expect_success 'partial' '
+
+ echo another >"commit is" &&
+ echo another >not/forbid &&
+ test_tick &&
+ git commit -m "partial commit to handle a file" "commit is" &&
+
+ changed=$(git diff-tree --name-only HEAD^ HEAD) &&
+ test "$changed" = "commit is"
+
+'
+
+test_expect_success 'partial modification in a subdirecotry' '
+
+ test_tick &&
+ git commit -m "partial commit to subdirectory" not &&
+
+ changed=$(git diff-tree -r --name-only HEAD^ HEAD) &&
+ test "$changed" = "not/forbid"
+
+'
+
+test_expect_success 'partial removal' '
+
+ git rm not/forbid &&
+ git commit -m "partial commit to remove not/forbid" not &&
+
+ changed=$(git diff-tree -r --name-only HEAD^ HEAD) &&
+ test "$changed" = "not/forbid" &&
+ remain=$(git ls-tree -r --name-only HEAD) &&
+ test "$remain" = "commit is"
+
+'
+
+test_expect_success 'sign off' '
+
+ >positive &&
+ git add positive &&
+ git commit -s -m "thank you" &&
+ actual=$(git cat-file commit HEAD | sed -ne "s/Signed-off-by: //p") &&
+ expected=$(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") &&
+ test "z$actual" = "z$expected"
+
+'
+
+test_expect_success 'multiple -m' '
+
+ >negative &&
+ git add negative &&
+ git commit -m "one" -m "two" -m "three" &&
+ actual=$(git cat-file commit HEAD | sed -e "1,/^\$/d") &&
+ expected=$(echo one; echo; echo two; echo; echo three) &&
+ test "z$actual" = "z$expected"
+
+'
+
+test_expect_success 'verbose' '
+
+ echo minus >negative &&
+ git add negative &&
+ git status -v | sed -ne "/^diff --git /p" >actual &&
+ echo "diff --git a/negative b/negative" >expect &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (verbatim,-t)' '
+
+ echo >>negative &&
+ { echo;echo "# text";echo; } >expect &&
+ git commit --cleanup=verbatim -t expect -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d" |head -n 3 >actual &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (verbatim,-F)' '
+
+ echo >>negative &&
+ git commit --cleanup=verbatim -F expect -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (verbatim,-m)' '
+
+ echo >>negative &&
+ git commit --cleanup=verbatim -m "$(cat expect)" -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (whitespace,-F)' '
+
+ echo >>negative &&
+ { echo;echo "# text";echo; } >text &&
+ echo "# text" >expect &&
+ git commit --cleanup=whitespace -F text -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'cleanup commit messages (strip,-F)' '
+
+ echo >>negative &&
+ { echo;echo "# text";echo sample;echo; } >text &&
+ echo sample >expect &&
+ git commit --cleanup=strip -F text -a &&
+ git cat-file -p HEAD |sed -e "1,/^\$/d">actual &&
+ diff -u expect actual
+
+'
+
+echo "sample
+
+# Please enter the commit message for your changes.
+# (Comment lines starting with '#' will not be included)" >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 &&
+ diff -u expect actual
+
+'
+
+pwd=`pwd`
+cat >> .git/FAKE_EDITOR << EOF
+#! /bin/sh
+echo editor started > "$pwd/.git/result"
+exit 0
+EOF
+chmod +x .git/FAKE_EDITOR
+
+test_expect_success 'do not fire editor in the presence of conflicts' '
+
+ git clean
+ echo f>g
+ git add g
+ git commit -myes
+ git branch second
+ echo master>g
+ echo g>h
+ git add g h
+ git commit -mmaster
+ git checkout second
+ echo second>g
+ git add g
+ git commit -msecond
+ git cherry-pick -n master
+ echo "editor not started" > .git/result
+ GIT_EDITOR=`pwd`/.git/FAKE_EDITOR git commit && exit 1 # should fail
+ test "`cat .git/result`" = "editor not started"
+'
+
+test_done
diff --git a/t/t7502-status.sh b/t/t7502-status.sh
new file mode 100755
index 0000000000..e00607490b
--- /dev/null
+++ b/t/t7502-status.sh
@@ -0,0 +1,152 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git-status'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ : > tracked &&
+ : > modified &&
+ mkdir dir1 &&
+ : > dir1/tracked &&
+ : > dir1/modified &&
+ mkdir dir2 &&
+ : > dir1/tracked &&
+ : > dir1/modified &&
+ git add . &&
+
+ git status >output &&
+
+ test_tick &&
+ git commit -m initial &&
+ : > untracked &&
+ : > dir1/untracked &&
+ : > dir2/untracked &&
+ echo 1 > dir1/modified &&
+ echo 2 > dir2/modified &&
+ echo 3 > dir2/added &&
+ git add dir2/added
+'
+
+test_expect_success 'status (1)' '
+
+ grep -e "use \"git rm --cached <file>\.\.\.\" to unstage" output
+
+'
+
+cat > expect << \EOF
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# new file: dir2/added
+#
+# Changed but not updated:
+# (use "git add <file>..." to update what will be committed)
+#
+# modified: dir1/modified
+#
+# Untracked files:
+# (use "git add <file>..." to include in what will be committed)
+#
+# dir1/untracked
+# dir2/modified
+# dir2/untracked
+# expect
+# output
+# untracked
+EOF
+
+test_expect_success 'status (2)' '
+
+ git status > output &&
+ git diff expect output
+
+'
+
+cat > expect << \EOF
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# new file: ../dir2/added
+#
+# Changed but not updated:
+# (use "git add <file>..." to update what will be committed)
+#
+# modified: modified
+#
+# Untracked files:
+# (use "git add <file>..." to include in what will be committed)
+#
+# untracked
+# ../dir2/modified
+# ../dir2/untracked
+# ../expect
+# ../output
+# ../untracked
+EOF
+
+test_expect_success 'status with relative paths' '
+
+ (cd dir1 && git status) > output &&
+ git diff expect output
+
+'
+
+cat > expect << \EOF
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# new file: dir2/added
+#
+# Changed but not updated:
+# (use "git add <file>..." to update what will be committed)
+#
+# modified: dir1/modified
+#
+# Untracked files:
+# (use "git add <file>..." to include in what will be committed)
+#
+# dir1/untracked
+# dir2/modified
+# dir2/untracked
+# expect
+# output
+# untracked
+EOF
+
+test_expect_success 'status without relative paths' '
+
+ git config status.relativePaths false
+ (cd dir1 && git status) > output &&
+ git diff expect output
+
+'
+
+cat <<EOF >expect
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# modified: dir1/modified
+#
+# Untracked files:
+# (use "git add <file>..." to include in what will be committed)
+#
+# dir1/untracked
+# dir2/
+# expect
+# output
+# untracked
+EOF
+test_expect_success 'status of partial commit excluding new file in index' '
+ git status dir1/modified >output &&
+ diff -u expect output
+'
+
+test_done
diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh
new file mode 100755
index 0000000000..2dd5a5e302
--- /dev/null
+++ b/t/t7503-pre-commit-hook.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+
+test_description='pre-commit hook'
+
+. ./test-lib.sh
+
+test_expect_success 'with no hook' '
+
+ echo "foo" > file &&
+ git add file &&
+ git commit -m "first"
+
+'
+
+test_expect_success '--no-verify with no hook' '
+
+ echo "bar" > file &&
+ git add file &&
+ git commit --no-verify -m "bar"
+
+'
+
+# now install hook that always succeeds
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+HOOK="$HOOKDIR/pre-commit"
+mkdir -p "$HOOKDIR"
+cat > "$HOOK" <<EOF
+#!/bin/sh
+exit 0
+EOF
+chmod +x "$HOOK"
+
+test_expect_success 'with succeeding hook' '
+
+ echo "more" >> file &&
+ git add file &&
+ git commit -m "more"
+
+'
+
+test_expect_success '--no-verify with succeeding hook' '
+
+ echo "even more" >> file &&
+ git add file &&
+ git commit --no-verify -m "even more"
+
+'
+
+# now a hook that fails
+cat > "$HOOK" <<EOF
+#!/bin/sh
+exit 1
+EOF
+
+test_expect_success 'with failing hook' '
+
+ echo "another" >> file &&
+ git add file &&
+ ! git commit -m "another"
+
+'
+
+test_expect_success '--no-verify with failing hook' '
+
+ echo "stuff" >> file &&
+ git add file &&
+ git commit --no-verify -m "stuff"
+
+'
+
+chmod -x "$HOOK"
+test_expect_success 'with non-executable hook' '
+
+ echo "content" >> file &&
+ git add file &&
+ git commit -m "content"
+
+'
+
+test_expect_success '--no-verify with non-executable hook' '
+
+ echo "more content" >> file &&
+ git add file &&
+ git commit --no-verify -m "more content"
+
+'
+
+test_done
diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh
new file mode 100755
index 0000000000..eff36aaee3
--- /dev/null
+++ b/t/t7504-commit-msg-hook.sh
@@ -0,0 +1,220 @@
+#!/bin/sh
+
+test_description='commit-msg hook'
+
+. ./test-lib.sh
+
+test_expect_success 'with no hook' '
+
+ echo "foo" > file &&
+ git add file &&
+ git commit -m "first"
+
+'
+
+# set up fake editor for interactive editing
+cat > fake-editor <<'EOF'
+#!/bin/sh
+cp FAKE_MSG "$1"
+exit 0
+EOF
+chmod +x fake-editor
+FAKE_EDITOR="$(pwd)/fake-editor"
+export FAKE_EDITOR
+
+test_expect_success 'with no hook (editor)' '
+
+ echo "more foo" >> file &&
+ git add file &&
+ echo "more foo" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit
+
+'
+
+test_expect_success '--no-verify with no hook' '
+
+ echo "bar" > file &&
+ git add file &&
+ git commit --no-verify -m "bar"
+
+'
+
+test_expect_success '--no-verify with no hook (editor)' '
+
+ echo "more bar" > file &&
+ git add file &&
+ echo "more bar" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify
+
+'
+
+# now install hook that always succeeds
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+HOOK="$HOOKDIR/commit-msg"
+mkdir -p "$HOOKDIR"
+cat > "$HOOK" <<EOF
+#!/bin/sh
+exit 0
+EOF
+chmod +x "$HOOK"
+
+test_expect_success 'with succeeding hook' '
+
+ echo "more" >> file &&
+ git add file &&
+ git commit -m "more"
+
+'
+
+test_expect_success 'with succeeding hook (editor)' '
+
+ echo "more more" >> file &&
+ git add file &&
+ echo "more more" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit
+
+'
+
+test_expect_success '--no-verify with succeeding hook' '
+
+ echo "even more" >> file &&
+ git add file &&
+ git commit --no-verify -m "even more"
+
+'
+
+test_expect_success '--no-verify with succeeding hook (editor)' '
+
+ echo "even more more" >> file &&
+ git add file &&
+ echo "even more more" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify
+
+'
+
+# now a hook that fails
+cat > "$HOOK" <<EOF
+#!/bin/sh
+exit 1
+EOF
+
+test_expect_success 'with failing hook' '
+
+ echo "another" >> file &&
+ git add file &&
+ ! git commit -m "another"
+
+'
+
+test_expect_success 'with failing hook (editor)' '
+
+ echo "more another" >> file &&
+ git add file &&
+ echo "more another" > FAKE_MSG &&
+ ! (GIT_EDITOR="$FAKE_EDITOR" git commit)
+
+'
+
+test_expect_success '--no-verify with failing hook' '
+
+ echo "stuff" >> file &&
+ git add file &&
+ git commit --no-verify -m "stuff"
+
+'
+
+test_expect_success '--no-verify with failing hook (editor)' '
+
+ echo "more stuff" >> file &&
+ git add file &&
+ echo "more stuff" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify
+
+'
+
+chmod -x "$HOOK"
+test_expect_success 'with non-executable hook' '
+
+ echo "content" >> file &&
+ git add file &&
+ git commit -m "content"
+
+'
+
+test_expect_success 'with non-executable hook (editor)' '
+
+ echo "content again" >> file &&
+ git add file &&
+ echo "content again" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit -m "content again"
+
+'
+
+test_expect_success '--no-verify with non-executable hook' '
+
+ echo "more content" >> file &&
+ git add file &&
+ git commit --no-verify -m "more content"
+
+'
+
+test_expect_success '--no-verify with non-executable hook (editor)' '
+
+ echo "even more content" >> file &&
+ git add file &&
+ echo "even more content" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify
+
+'
+
+# now a hook that edits the commit message
+cat > "$HOOK" <<'EOF'
+#!/bin/sh
+echo "new message" > "$1"
+exit 0
+EOF
+chmod +x "$HOOK"
+
+commit_msg_is () {
+ test "`git log --pretty=format:%s%b -1`" = "$1"
+}
+
+test_expect_success 'hook edits commit message' '
+
+ echo "additional" >> file &&
+ git add file &&
+ git commit -m "additional" &&
+ commit_msg_is "new message"
+
+'
+
+test_expect_success 'hook edits commit message (editor)' '
+
+ echo "additional content" >> file &&
+ git add file &&
+ echo "additional content" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit &&
+ commit_msg_is "new message"
+
+'
+
+test_expect_success "hook doesn't edit commit message" '
+
+ echo "plus" >> file &&
+ git add file &&
+ git commit --no-verify -m "plus" &&
+ commit_msg_is "plus"
+
+'
+
+test_expect_success "hook doesn't edit commit message (editor)" '
+
+ echo "more plus" >> file &&
+ git add file &&
+ echo "more plus" > FAKE_MSG &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify &&
+ commit_msg_is "more plus"
+
+'
+
+test_done
diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh
new file mode 100755
index 0000000000..7ddec99a64
--- /dev/null
+++ b/t/t7505-prepare-commit-msg-hook.sh
@@ -0,0 +1,155 @@
+#!/bin/sh
+
+test_description='prepare-commit-msg hook'
+
+. ./test-lib.sh
+
+test_expect_success 'with no hook' '
+
+ echo "foo" > file &&
+ git add file &&
+ git commit -m "first"
+
+'
+
+# set up fake editor for interactive editing
+cat > fake-editor <<'EOF'
+#!/bin/sh
+exit 0
+EOF
+chmod +x fake-editor
+FAKE_EDITOR="$(pwd)/fake-editor"
+export FAKE_EDITOR
+
+# now install hook that always succeeds and adds a message
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+HOOK="$HOOKDIR/prepare-commit-msg"
+mkdir -p "$HOOKDIR"
+cat > "$HOOK" <<'EOF'
+#!/bin/sh
+if test "$2" = commit; then
+ source=$(git-rev-parse "$3")
+else
+ source=${2-default}
+fi
+if test "$GIT_EDITOR" = :; then
+ sed -e "1s/.*/$source (no editor)/" "$1" > msg.tmp
+else
+ sed -e "1s/.*/$source/" "$1" > msg.tmp
+fi
+mv msg.tmp "$1"
+exit 0
+EOF
+chmod +x "$HOOK"
+
+echo dummy template > "$(git rev-parse --git-dir)/template"
+
+test_expect_success 'with hook (-m)' '
+
+ echo "more" >> file &&
+ git add file &&
+ git commit -m "more" &&
+ test "`git log -1 --pretty=format:%s`" = "message (no editor)"
+
+'
+
+test_expect_success 'with hook (-m editor)' '
+
+ echo "more" >> file &&
+ git add file &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit -e -m "more more" &&
+ test "`git log -1 --pretty=format:%s`" = message
+
+'
+
+test_expect_success 'with hook (-t)' '
+
+ echo "more" >> file &&
+ git add file &&
+ git commit -t "$(git rev-parse --git-dir)/template" &&
+ test "`git log -1 --pretty=format:%s`" = template
+
+'
+
+test_expect_success 'with hook (-F)' '
+
+ echo "more" >> file &&
+ git add file &&
+ (echo more | git commit -F -) &&
+ test "`git log -1 --pretty=format:%s`" = "message (no editor)"
+
+'
+
+test_expect_success 'with hook (-F editor)' '
+
+ echo "more" >> file &&
+ git add file &&
+ (echo more more | GIT_EDITOR="$FAKE_EDITOR" git commit -e -F -) &&
+ test "`git log -1 --pretty=format:%s`" = message
+
+'
+
+test_expect_success 'with hook (-C)' '
+
+ head=`git rev-parse HEAD` &&
+ echo "more" >> file &&
+ git add file &&
+ git commit -C $head &&
+ test "`git log -1 --pretty=format:%s`" = "$head (no editor)"
+
+'
+
+test_expect_success 'with hook (editor)' '
+
+ echo "more more" >> file &&
+ git add file &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit &&
+ test "`git log -1 --pretty=format:%s`" = default
+
+'
+
+test_expect_success 'with hook (--amend)' '
+
+ head=`git rev-parse HEAD` &&
+ echo "more" >> file &&
+ git add file &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit --amend &&
+ test "`git log -1 --pretty=format:%s`" = "$head"
+
+'
+
+test_expect_success 'with hook (-c)' '
+
+ head=`git rev-parse HEAD` &&
+ echo "more" >> file &&
+ git add file &&
+ GIT_EDITOR="$FAKE_EDITOR" git commit -c $head &&
+ test "`git log -1 --pretty=format:%s`" = "$head"
+
+'
+
+cat > "$HOOK" <<'EOF'
+#!/bin/sh
+exit 1
+EOF
+
+test_expect_success 'with failing hook' '
+
+ head=`git rev-parse HEAD` &&
+ echo "more" >> file &&
+ git add file &&
+ ! GIT_EDITOR="$FAKE_EDITOR" git commit -c $head
+
+'
+
+test_expect_success 'with failing hook (--no-verify)' '
+
+ head=`git rev-parse HEAD` &&
+ echo "more" >> file &&
+ git add file &&
+ ! GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify -c $head
+
+'
+
+
+test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 6424c6e2c0..50c51c82fa 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -371,7 +371,7 @@ test_expect_success 'override config option -n' '
git merge --summary c2 >diffstat.txt &&
verify_merge file result.1-5 msg.1-5 &&
verify_parents $c1 $c2 &&
- if ! grep -e "^ file | \+2 +-$" diffstat.txt
+ if ! grep -e "^ file | *2 +-$" diffstat.txt
then
echo "[OOPS] diffstat was not generated"
fi
@@ -386,7 +386,7 @@ test_expect_success 'override config option --summary' '
git merge -n c2 >diffstat.txt &&
verify_merge file result.1-5 msg.1-5 &&
verify_parents $c1 $c2 &&
- if grep -e "^ file | \+2 +-$" diffstat.txt
+ if grep -e "^ file | *2 +-$" diffstat.txt
then
echo "[OOPS] diffstat was generated"
false
diff --git a/t/t8004-blame.sh b/t/t8004-blame.sh
new file mode 100755
index 0000000000..ba19ac127e
--- /dev/null
+++ b/t/t8004-blame.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Based on a test case submitted by Björn Steinbrink.
+
+test_description='git blame on conflicted files'
+. ./test-lib.sh
+
+test_expect_success 'setup first case' '
+ # Create the old file
+ echo "Old line" > file1 &&
+ git add file1 &&
+ git commit --author "Old Line <ol@localhost>" -m file1.a &&
+
+ # Branch
+ git checkout -b foo &&
+
+ # Do an ugly move and change
+ git rm file1 &&
+ echo "New line ..." > file2 &&
+ echo "... and more" >> file2 &&
+ git add file2 &&
+ git commit --author "U Gly <ug@localhost>" -m ugly &&
+
+ # Back to master and change something
+ git checkout master &&
+ echo "
+
+bla" >> file1 &&
+ git commit --author "Old Line <ol@localhost>" -a -m file1.b &&
+
+ # Back to foo and merge master
+ git checkout foo &&
+ if git merge master; then
+ echo needed conflict here
+ exit 1
+ else
+ echo merge failed - resolving automatically
+ fi &&
+ echo "New line ...
+... and more
+
+bla
+Even more" > file2 &&
+ git rm file1 &&
+ git commit --author "M Result <mr@localhost>" -a -m merged &&
+
+ # Back to master and change file1 again
+ git checkout master &&
+ sed s/bla/foo/ <file1 >X &&
+ rm file1 &&
+ mv X file1 &&
+ git commit --author "No Bla <nb@localhost>" -a -m replace &&
+
+ # Try to merge into foo again
+ git checkout foo &&
+ if git merge master; then
+ echo needed conflict here
+ exit 1
+ else
+ echo merge failed - test is setup
+ fi
+'
+
+test_expect_success \
+ 'blame runs on unconflicted file while other file has conflicts' '
+ git blame file2
+'
+
+test_expect_success 'blame runs on conflicted file in stages 1,3' '
+ git blame file1
+'
+
+test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 83f9470202..2efaed441d 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -8,7 +8,7 @@ test_expect_success \
'prepare reference tree' \
'echo "1A quick brown fox jumps over the" >file &&
echo "lazy dog" >>file &&
- git add file
+ git add file &&
GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
test_expect_success \
@@ -20,9 +20,9 @@ test_expect_success \
echo " echo \"!\$a!\""
echo "done >commandline"
echo "cat > msgtxt"
- ) >fake.sendmail
- chmod +x ./fake.sendmail
- git add fake.sendmail
+ ) >fake.sendmail &&
+ chmod +x ./fake.sendmail &&
+ git add fake.sendmail &&
GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
test_expect_success 'Extract patches' '
@@ -41,4 +41,92 @@ test_expect_success \
'Verify commandline' \
'diff commandline expected'
+cat >expected-show-all-headers <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<bcc@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: cc@example.com, A <author@example.com>
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+In-Reply-To: <unique-message-id@example.com>
+References: <unique-message-id@example.com>
+
+Result: OK
+EOF
+
+test_expect_success 'Show all headers' '
+ git send-email \
+ --dry-run \
+ --from="Example <from@example.com>" \
+ --to=to@example.com \
+ --cc=cc@example.com \
+ --bcc=bcc@example.com \
+ --in-reply-to="<unique-message-id@example.com>" \
+ --smtp-server relay.example.com \
+ $patches |
+ sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \
+ -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
+ -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
+ >actual-show-all-headers &&
+ diff -u expected-show-all-headers actual-show-all-headers
+'
+
+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' '
+ rm -f commandline &&
+ cp $patches longline.patch &&
+ echo $z512$z512 >>longline.patch &&
+ ! git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches longline.patch \
+ 2>errors &&
+ grep longline.patch errors
+'
+
+test_expect_success 'no patch was sent' '
+ ! test -e commandline
+'
+
+test_expect_success 'allow long lines with --no-validate' '
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --no-validate \
+ $patches longline.patch \
+ 2>errors
+'
+
+test_expect_success 'Invalid In-Reply-To' '
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --in-reply-to=" " \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches
+ 2>errors
+ ! grep "^In-Reply-To: < *>" msgtxt
+'
+
+test_expect_success 'Valid In-Reply-To when prompting' '
+ (echo "From Example <from@example.com>"
+ echo "To Example <to@example.com>"
+ echo ""
+ ) | env GIT_SEND_EMAIL_NOTTY=1 git send-email \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches 2>errors &&
+ ! grep "^In-Reply-To: < *>" msgtxt
+'
+
test_done
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh
index 614cf50d19..4e24ab3a7d 100755
--- a/t/t9100-git-svn-basic.sh
+++ b/t/t9100-git-svn-basic.sh
@@ -56,19 +56,19 @@ test_expect_success "$name" "
name='detect node change from file to directory #1'
-test_expect_failure "$name" "
+test_expect_success "$name" "
mkdir dir/new_file &&
mv dir/file dir/new_file/file &&
mv dir/new_file dir/file &&
git update-index --remove dir/file &&
git update-index --add dir/file/file &&
- git commit -m '$name' &&
- git-svn set-tree --find-copies-harder --rmdir \
+ git commit -m '$name' &&
+ ! git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch" || true
name='detect node change from directory to file #1'
-test_expect_failure "$name" "
+test_expect_success "$name" "
rm -rf dir '$GIT_DIR'/index &&
git checkout -f -b mybranch2 remotes/git-svn &&
mv bar/zzz zzz &&
@@ -77,12 +77,12 @@ test_expect_failure "$name" "
git update-index --remove -- bar/zzz &&
git update-index --add -- bar &&
git commit -m '$name' &&
- git-svn set-tree --find-copies-harder --rmdir \
+ ! git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch2" || true
name='detect node change from file to directory #2'
-test_expect_failure "$name" "
+test_expect_success "$name" "
rm -f '$GIT_DIR'/index &&
git checkout -f -b mybranch3 remotes/git-svn &&
rm bar/zzz &&
@@ -91,12 +91,12 @@ test_expect_failure "$name" "
echo yyy > bar/zzz/yyy &&
git update-index --add bar/zzz/yyy &&
git commit -m '$name' &&
- git-svn set-tree --find-copies-harder --rmdir \
+ ! git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch3" || true
name='detect node change from directory to file #2'
-test_expect_failure "$name" "
+test_expect_success "$name" "
rm -f '$GIT_DIR'/index &&
git checkout -f -b mybranch4 remotes/git-svn &&
rm -rf dir &&
@@ -105,7 +105,7 @@ test_expect_failure "$name" "
echo asdf > dir &&
git update-index --add -- dir &&
git commit -m '$name' &&
- git-svn set-tree --find-copies-harder --rmdir \
+ ! git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch4" || true
@@ -213,18 +213,18 @@ EOF
test_expect_success "$name" "git diff a expected"
-test_expect_failure 'exit if remote refs are ambigious' "
+test_expect_success 'exit if remote refs are ambigious' "
git config --add svn-remote.svn.fetch \
bar:refs/remotes/git-svn &&
- git-svn migrate
- "
+ ! git-svn migrate
+"
-test_expect_failure 'exit if init-ing a would clobber a URL' "
+test_expect_success 'exit if init-ing a would clobber a URL' "
svnadmin create ${PWD}/svnrepo2 &&
svn mkdir -m 'mkdir bar' ${svnrepo}2/bar &&
git config --unset svn-remote.svn.fetch \
'^bar:refs/remotes/git-svn$' &&
- git-svn init ${svnrepo}2/bar
+ ! git-svn init ${svnrepo}2/bar
"
test_expect_success \
diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh
index 3c83127a0e..d7a704754e 100755
--- a/t/t9101-git-svn-props.sh
+++ b/t/t9101-git-svn-props.sh
@@ -48,7 +48,7 @@ EOF
printf "\r\n" > empty_crlf
a_empty_crlf=`git-hash-object -w empty_crlf`
- svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
+ svn import --no-auto-props -m 'import for git-svn' . "$svnrepo" >/dev/null
cd ..
rm -rf import
diff --git a/t/t9103-git-svn-tracked-directory-removed.sh b/t/t9103-git-svn-tracked-directory-removed.sh
new file mode 100755
index 0000000000..0f0b0fd2c6
--- /dev/null
+++ b/t/t9103-git-svn-tracked-directory-removed.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+test_description='git-svn tracking removed top-level path'
+. ./lib-git-svn.sh
+
+test_expect_success 'make history for tracking' '
+ mkdir import &&
+ mkdir import/trunk &&
+ echo hello >> import/trunk/README &&
+ svn import -m initial import $svnrepo &&
+ rm -rf import &&
+ svn co $svnrepo/trunk trunk &&
+ echo bye bye >> trunk/README &&
+ svn rm -m "gone" $svnrepo/trunk &&
+ rm -rf trunk &&
+ mkdir trunk &&
+ echo "new" > trunk/FOLLOWME &&
+ svn import -m "new trunk" trunk $svnrepo/trunk
+'
+
+test_expect_success 'clone repo with git' '
+ git svn clone -s $svnrepo x &&
+ test -f x/FOLLOWME &&
+ test ! -f x/README
+'
+
+test_expect_success 'make sure r2 still has old file' '
+ cd x &&
+ test -n "$(git svn find-rev r1)" &&
+ git reset --hard $(git svn find-rev r1) &&
+ test -f README &&
+ test ! -f FOLLOWME &&
+ test x$(git svn find-rev r2) = x
+'
+
+test_done
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
index 79b7968eaf..f74ab1269e 100755
--- a/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -24,11 +24,11 @@ test_expect_success 'commit change from svn side' "
rm -rf t.svn
"
-test_expect_failure 'commit conflicting change from git' "
+test_expect_success 'commit conflicting change from git' "
echo second line from git >> file &&
git commit -a -m 'second line from git' &&
- git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo
- " || true
+ ! git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo
+"
test_expect_success 'commit complementing change from git' "
git reset --hard HEAD~1 &&
@@ -39,7 +39,7 @@ test_expect_success 'commit complementing change from git' "
git-svn commit-diff -r2 HEAD~1 HEAD $svnrepo
"
-test_expect_failure 'dcommit fails to commit because of conflict' "
+test_expect_success 'dcommit fails to commit because of conflict' "
git-svn init $svnrepo &&
git-svn fetch &&
git reset --hard refs/remotes/git-svn &&
@@ -52,8 +52,8 @@ test_expect_failure 'dcommit fails to commit because of conflict' "
rm -rf t.svn &&
echo 'fourth line from git' >> file &&
git commit -a -m 'fourth line from git' &&
- git-svn dcommit
- " || true
+ ! git-svn dcommit
+ "
test_expect_success 'dcommit does the svn equivalent of an index merge' "
git reset --hard refs/remotes/git-svn &&
@@ -76,15 +76,15 @@ test_expect_success 'commit another change from svn side' "
rm -rf t.svn
"
-test_expect_failure 'multiple dcommit from git-svn will not clobber svn' "
+test_expect_success 'multiple dcommit from git-svn will not clobber svn' "
git reset --hard refs/remotes/git-svn &&
echo new file >> new-file &&
git update-index --add new-file &&
git commit -a -m 'new file' &&
echo clobber > file &&
git commit -a -m 'clobber' &&
- git svn dcommit
- " || true
+ ! git svn dcommit
+ "
test_expect_success 'check that rebase really failed' 'test -d .dotest'
diff --git a/t/t9106-git-svn-dcommit-clobber-series.sh b/t/t9106-git-svn-dcommit-clobber-series.sh
new file mode 100755
index 0000000000..ca8a00ed0a
--- /dev/null
+++ b/t/t9106-git-svn-dcommit-clobber-series.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+test_description='git-svn dcommit clobber series'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' "
+ mkdir import &&
+ cd import &&
+ awk 'BEGIN { for (i = 1; i < 64; i++) { print i } }' > file
+ svn import -m 'initial' . $svnrepo &&
+ cd .. &&
+ git svn init $svnrepo &&
+ git svn fetch &&
+ test -e file
+ "
+
+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 co $svnrepo tmp &&
+ cd tmp &&
+ perl -i -p -e 's/^58\$/5588/' file &&
+ perl -i -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 commit -m '58 => 5588, 61 => 6611' &&
+ cd ..
+ "
+
+test_expect_success 'some unrelated changes to git' "
+ echo hi > life &&
+ git update-index --add life &&
+ git commit -m hi-life &&
+ echo bye >> life &&
+ git commit -m bye-life life
+ "
+
+test_expect_success 'change file but in unrelated area' "
+ test x\"\`sed -n -e 4p < file\`\" = x4 &&
+ test x\"\`sed -n -e 7p < file\`\" = x7 &&
+ perl -i -p -e 's/^4\$/4444/' file &&
+ perl -i -p -e 's/^7\$/7777/' file &&
+ test x\"\`sed -n -e 4p < file\`\" = x4444 &&
+ test x\"\`sed -n -e 7p < file\`\" = x7777 &&
+ git commit -m '4 => 4444, 7 => 7777' file &&
+ git svn dcommit &&
+ svn up tmp &&
+ cd tmp &&
+ test x\"\`sed -n -e 4p < file\`\" = x4444 &&
+ test x\"\`sed -n -e 7p < file\`\" = x7777 &&
+ test x\"\`sed -n -e 58p < file\`\" = x5588 &&
+ test x\"\`sed -n -e 61p < file\`\" = x6611
+ "
+
+test_expect_success 'attempt to dcommit with a dirty index' '
+ echo foo >>file &&
+ git add file &&
+ ! git svn dcommit
+'
+
+test_done
diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh
index 67fdf7023f..0a41d52c7a 100755
--- a/t/t9107-git-svn-migrate.sh
+++ b/t/t9107-git-svn-migrate.sh
@@ -97,15 +97,19 @@ test_expect_success 'migrate --minimize on old inited layout' "
grep '^:refs/remotes/git-svn' fetch.out
"
-test_expect_success ".rev_db auto-converted to .rev_db.UUID" "
+test_expect_success ".rev_db auto-converted to .rev_map.UUID" "
git-svn fetch -i trunk &&
- expect=$GIT_DIR/svn/trunk/.rev_db.* &&
+ test -z \"\$(ls $GIT_DIR/svn/trunk/.rev_db.* 2>/dev/null)\" &&
+ expect=\"\$(ls $GIT_DIR/svn/trunk/.rev_map.*)\" &&
test -n \"\$expect\" &&
- mv \$expect $GIT_DIR/svn/trunk/.rev_db &&
+ rev_db=\$(echo \$expect | sed -e 's,_map,_db,') &&
+ convert_to_rev_db \$expect \$rev_db &&
+ rm -f \$expect &&
+ test -f \$rev_db &&
git-svn fetch -i trunk &&
- test -L $GIT_DIR/svn/trunk/.rev_db &&
- test -f \$expect &&
- cmp \$expect $GIT_DIR/svn/trunk/.rev_db
+ test -z \"\$(ls $GIT_DIR/svn/trunk/.rev_db.* 2>/dev/null)\" &&
+ test ! -e $GIT_DIR/svn/trunk/.rev_db &&
+ test -f \$expect
"
test_done
diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh
index d6ca955081..225060b88b 100755
--- a/t/t9114-git-svn-dcommit-merge.sh
+++ b/t/t9114-git-svn-dcommit-merge.sh
@@ -86,4 +86,9 @@ test_expect_success 'verify post-merge ancestry' "
git cat-file commit refs/heads/svn^ | grep '^friend$'
"
+test_expect_success 'verify merge commit message' "
+ git rev-list --pretty=raw -1 refs/heads/svn | \
+ grep \" Merge branch 'merge' into svn\"
+ "
+
test_done
diff --git a/t/t9116-git-svn-log.sh b/t/t9116-git-svn-log.sh
index 0d4e6b3f04..902ed4145d 100755
--- a/t/t9116-git-svn-log.sh
+++ b/t/t9116-git-svn-log.sh
@@ -30,6 +30,12 @@ test_expect_success 'setup repository and import' "
git reset --hard trunk &&
echo aye >> README &&
git commit -a -m aye &&
+ git svn dcommit &&
+ git reset --hard b &&
+ echo spy >> README &&
+ git commit -a -m spy &&
+ echo try >> README &&
+ git commit -a -m try &&
git svn dcommit
"
@@ -45,4 +51,78 @@ test_expect_success 'run log against a from trunk' "
git svn log -r3 a | grep ^r3
"
+printf 'r1 \nr2 \nr4 \n' > expected-range-r1-r2-r4
+
+test_expect_success 'test ascending revision range' "
+ git reset --hard trunk &&
+ git svn log -r 1:4 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r1-r2-r4 -
+ "
+
+printf 'r4 \nr2 \nr1 \n' > expected-range-r4-r2-r1
+
+test_expect_success 'test descending revision range' "
+ git reset --hard trunk &&
+ git svn log -r 4:1 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4-r2-r1 -
+ "
+
+printf 'r1 \nr2 \n' > expected-range-r1-r2
+
+test_expect_success 'test ascending revision range with unreachable revision' "
+ git reset --hard trunk &&
+ git svn log -r 1:3 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r1-r2 -
+ "
+
+printf 'r2 \nr1 \n' > expected-range-r2-r1
+
+test_expect_success 'test descending revision range with unreachable revision' "
+ git reset --hard trunk &&
+ git svn log -r 3:1 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r2-r1 -
+ "
+
+printf 'r2 \n' > expected-range-r2
+
+test_expect_success 'test ascending revision range with unreachable upper boundary revision and 1 commit' "
+ git reset --hard trunk &&
+ git svn log -r 2:3 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r2 -
+ "
+
+test_expect_success 'test descending revision range with unreachable upper boundary revision and 1 commit' "
+ git reset --hard trunk &&
+ git svn log -r 3:2 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r2 -
+ "
+
+printf 'r4 \n' > expected-range-r4
+
+test_expect_success 'test ascending revision range with unreachable lower boundary revision and 1 commit' "
+ git reset --hard trunk &&
+ git svn log -r 3:4 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4 -
+ "
+
+test_expect_success 'test descending revision range with unreachable lower boundary revision and 1 commit' "
+ git reset --hard trunk &&
+ git svn log -r 4:3 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4 -
+ "
+
+printf -- '------------------------------------------------------------------------\n' > expected-separator
+
+test_expect_success 'test ascending revision range with unreachable boundary revisions and no commits' "
+ git reset --hard trunk &&
+ git svn log -r 5:6 | diff -u expected-separator -
+ "
+
+test_expect_success 'test descending revision range with unreachable boundary revisions and no commits' "
+ git reset --hard trunk &&
+ git svn log -r 6:5 | diff -u expected-separator -
+ "
+
+test_expect_success 'test ascending revision range with unreachable boundary revisions and 1 commit' "
+ git reset --hard trunk &&
+ git svn log -r 3:5 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4 -
+ "
+
+test_expect_success 'test descending revision range with unreachable boundary revisions and 1 commit' "
+ git reset --hard trunk &&
+ git svn log -r 5:3 | grep '^r[0-9]' | cut -d'|' -f1 | diff -u expected-range-r4 -
+ "
+
test_done
diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh
new file mode 100755
index 0000000000..d482b407f2
--- /dev/null
+++ b/t/t9117-git-svn-init-clone.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+test_description='git-svn init/clone tests'
+
+. ./lib-git-svn.sh
+
+# setup, run inside tmp so we don't have any conflicts with $svnrepo
+set -e
+rm -r .git
+mkdir tmp
+cd tmp
+
+test_expect_success 'setup svnrepo' "
+ mkdir project project/trunk project/branches project/tags &&
+ echo foo > project/trunk/foo &&
+ svn import -m '$test_description' project $svnrepo/project &&
+ rm -rf project
+ "
+
+test_expect_success 'basic clone' "
+ test ! -d trunk &&
+ git svn clone $svnrepo/project/trunk &&
+ test -d trunk/.git/svn &&
+ test -e trunk/foo &&
+ rm -rf trunk
+ "
+
+test_expect_success 'clone to target directory' "
+ test ! -d target &&
+ git svn clone $svnrepo/project/trunk target &&
+ test -d target/.git/svn &&
+ test -e target/foo &&
+ rm -rf target
+ "
+
+test_expect_success 'clone with --stdlayout' "
+ test ! -d project &&
+ git svn clone -s $svnrepo/project &&
+ test -d project/.git/svn &&
+ test -e project/foo &&
+ rm -rf project
+ "
+
+test_expect_success 'clone to target directory with --stdlayout' "
+ test ! -d target &&
+ git svn clone -s $svnrepo/project target &&
+ test -d target/.git/svn &&
+ test -e target/foo &&
+ rm -rf target
+ "
+
+test_done
diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh
new file mode 100755
index 0000000000..640bb066f3
--- /dev/null
+++ b/t/t9118-git-svn-funky-branch-names.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+test_description='git-svn funky branch names'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svnrepo' "
+ mkdir project project/trunk project/branches project/tags &&
+ echo foo > project/trunk/foo &&
+ svn import -m '$test_description' project \"$svnrepo/pr ject\" &&
+ rm -rf project &&
+ svn cp -m 'fun' \"$svnrepo/pr ject/trunk\" \
+ \"$svnrepo/pr ject/branches/fun plugin\" &&
+ svn cp -m 'more fun!' \"$svnrepo/pr ject/branches/fun plugin\" \
+ \"$svnrepo/pr ject/branches/more fun plugin!\" &&
+ start_httpd
+ "
+
+test_expect_success 'test clone with funky branch names' "
+ git svn clone -s \"$svnrepo/pr ject\" project &&
+ cd project &&
+ git rev-parse 'refs/remotes/fun%20plugin' &&
+ git rev-parse 'refs/remotes/more%20fun%20plugin!' &&
+ cd ..
+ "
+
+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 ..
+ "
+
+stop_httpd
+
+test_done
diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh
new file mode 100755
index 0000000000..cc61911593
--- /dev/null
+++ b/t/t9119-git-svn-info.sh
@@ -0,0 +1,370 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 David D. Kilzer
+
+test_description='git-svn info'
+
+. ./lib-git-svn.sh
+say 'skipping svn-info test (has a race undiagnosed yet)'
+test_done
+
+ptouch() {
+ perl -w -e '
+ use strict;
+ die "ptouch requires exactly 2 arguments" if @ARGV != 2;
+ die "$ARGV[0] does not exist" if ! -e $ARGV[0];
+ my @s = stat $ARGV[0];
+ utime $s[8], $s[9], $ARGV[1];
+ ' "$1" "$2"
+}
+
+test_expect_success 'setup repository and import' "
+ mkdir info &&
+ cd info &&
+ echo FIRST > A &&
+ echo one > file &&
+ ln -s file symlink-file &&
+ mkdir directory &&
+ touch directory/.placeholder &&
+ ln -s directory symlink-directory &&
+ svn import -m 'initial' . $svnrepo &&
+ cd .. &&
+ mkdir gitwc &&
+ cd gitwc &&
+ git-svn init $svnrepo &&
+ git-svn fetch &&
+ cd .. &&
+ svn co $svnrepo svnwc &&
+ ptouch svnwc/file gitwc/file &&
+ ptouch svnwc/directory gitwc/directory &&
+ ptouch svnwc/symlink-file gitwc/symlink-file &&
+ ptouch svnwc/symlink-directory gitwc/symlink-directory
+ "
+
+test_expect_success 'info' "
+ (cd svnwc; svn info) > expected.info &&
+ (cd gitwc; git-svn info) > actual.info &&
+ git-diff expected.info actual.info
+ "
+
+test_expect_success 'info --url' '
+ test $(cd gitwc; git-svn info --url) = $svnrepo
+ '
+
+test_expect_success 'info .' "
+ (cd svnwc; svn info .) > expected.info-dot &&
+ (cd gitwc; git-svn info .) > actual.info-dot &&
+ git-diff expected.info-dot actual.info-dot
+ "
+
+test_expect_success 'info --url .' '
+ test $(cd gitwc; git-svn info --url .) = $svnrepo
+ '
+
+test_expect_success 'info file' "
+ (cd svnwc; svn info file) > expected.info-file &&
+ (cd gitwc; git-svn info file) > actual.info-file &&
+ git-diff expected.info-file actual.info-file
+ "
+
+test_expect_success 'info --url file' '
+ test $(cd gitwc; git-svn info --url file) = "$svnrepo/file"
+ '
+
+test_expect_success 'info directory' "
+ (cd svnwc; svn info directory) > expected.info-directory &&
+ (cd gitwc; git-svn info directory) > actual.info-directory &&
+ git-diff expected.info-directory actual.info-directory
+ "
+
+test_expect_success 'info --url directory' '
+ test $(cd gitwc; git-svn info --url directory) = "$svnrepo/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 &&
+ git-diff expected.info-symlink-file actual.info-symlink-file
+ "
+
+test_expect_success 'info --url symlink-file' '
+ test $(cd gitwc; git-svn info --url symlink-file) \
+ = "$svnrepo/symlink-file"
+ '
+
+test_expect_success 'info symlink-directory' "
+ (cd svnwc; svn info symlink-directory) \
+ > expected.info-symlink-directory &&
+ (cd gitwc; git-svn info symlink-directory) \
+ > actual.info-symlink-directory &&
+ git-diff expected.info-symlink-directory actual.info-symlink-directory
+ "
+
+test_expect_success 'info --url symlink-directory' '
+ test $(cd gitwc; git-svn info --url symlink-directory) \
+ = "$svnrepo/symlink-directory"
+ '
+
+test_expect_success 'info added-file' "
+ echo two > gitwc/added-file &&
+ cd gitwc &&
+ git add added-file &&
+ cd .. &&
+ cp gitwc/added-file svnwc/added-file &&
+ ptouch gitwc/added-file svnwc/added-file &&
+ cd svnwc &&
+ svn add added-file > /dev/null &&
+ cd .. &&
+ (cd svnwc; svn info added-file) > expected.info-added-file &&
+ (cd gitwc; git-svn info added-file) > actual.info-added-file &&
+ git-diff expected.info-added-file actual.info-added-file
+ "
+
+test_expect_success 'info --url added-file' '
+ test $(cd gitwc; git-svn info --url added-file) \
+ = "$svnrepo/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 add added-directory > /dev/null &&
+ cd .. &&
+ cd gitwc &&
+ git add added-directory &&
+ cd .. &&
+ (cd svnwc; svn info added-directory) \
+ > expected.info-added-directory &&
+ (cd gitwc; git-svn info added-directory) \
+ > actual.info-added-directory &&
+ git-diff expected.info-added-directory actual.info-added-directory
+ "
+
+test_expect_success 'info --url added-directory' '
+ test $(cd gitwc; git-svn info --url added-directory) \
+ = "$svnrepo/added-directory"
+ '
+
+test_expect_success 'info added-symlink-file' "
+ cd gitwc &&
+ ln -s added-file added-symlink-file &&
+ git add added-symlink-file &&
+ cd .. &&
+ cd svnwc &&
+ ln -s added-file added-symlink-file &&
+ svn add added-symlink-file > /dev/null &&
+ cd .. &&
+ ptouch gitwc/added-symlink-file svnwc/added-symlink-file &&
+ (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 &&
+ git-diff expected.info-added-symlink-file \
+ actual.info-added-symlink-file
+ "
+
+test_expect_success 'info --url added-symlink-file' '
+ test $(cd gitwc; git-svn info --url added-symlink-file) \
+ = "$svnrepo/added-symlink-file"
+ '
+
+test_expect_success 'info added-symlink-directory' "
+ cd gitwc &&
+ ln -s added-directory added-symlink-directory &&
+ git add added-symlink-directory &&
+ cd .. &&
+ cd svnwc &&
+ ln -s added-directory added-symlink-directory &&
+ svn add added-symlink-directory > /dev/null &&
+ cd .. &&
+ ptouch gitwc/added-symlink-directory svnwc/added-symlink-directory &&
+ (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 &&
+ git-diff expected.info-added-symlink-directory \
+ actual.info-added-symlink-directory
+ "
+
+test_expect_success 'info --url added-symlink-directory' '
+ test $(cd gitwc; git-svn info --url added-symlink-directory) \
+ = "$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 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 &&
+ git-diff expected.info-deleted-file actual.info-deleted-file
+ "
+
+test_expect_success 'info --url file (deleted)' '
+ test $(cd gitwc; git-svn info --url file) \
+ = "$svnrepo/file"
+ '
+
+test_expect_success 'info deleted-directory' "
+ cd gitwc &&
+ git rm -r -f directory > /dev/null &&
+ cd .. &&
+ cd svnwc &&
+ svn 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 &&
+ git-diff expected.info-deleted-directory actual.info-deleted-directory
+ "
+
+test_expect_success 'info --url directory (deleted)' '
+ test $(cd gitwc; git-svn info --url directory) \
+ = "$svnrepo/directory"
+ '
+
+test_expect_success 'info deleted-symlink-file' "
+ cd gitwc &&
+ git rm -f symlink-file > /dev/null &&
+ cd .. &&
+ cd svnwc &&
+ svn 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 &&
+ git-diff expected.info-deleted-symlink-file \
+ actual.info-deleted-symlink-file
+ "
+
+test_expect_success 'info --url symlink-file (deleted)' '
+ test $(cd gitwc; git-svn info --url symlink-file) \
+ = "$svnrepo/symlink-file"
+ '
+
+test_expect_success 'info deleted-symlink-directory' "
+ cd gitwc &&
+ git rm -f symlink-directory > /dev/null &&
+ cd .. &&
+ cd svnwc &&
+ svn 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 &&
+ git-diff expected.info-deleted-symlink-directory \
+ actual.info-deleted-symlink-directory
+ "
+
+test_expect_success 'info --url symlink-directory (deleted)' '
+ test $(cd gitwc; git-svn info --url symlink-directory) \
+ = "$svnrepo/symlink-directory"
+ '
+
+# NOTE: git does not have the concept of replaced objects,
+# so we can't test for files in that state.
+
+test_expect_success 'info unknown-file' "
+ echo two > gitwc/unknown-file &&
+ cp gitwc/unknown-file svnwc/unknown-file &&
+ ptouch gitwc/unknown-file svnwc/unknown-file &&
+ (cd svnwc; svn info unknown-file) 2> expected.info-unknown-file &&
+ (cd gitwc; git-svn info unknown-file) 2> actual.info-unknown-file &&
+ git-diff expected.info-unknown-file actual.info-unknown-file
+ "
+
+test_expect_success 'info --url unknown-file' '
+ test -z $(cd gitwc; git-svn info --url unknown-file \
+ 2> ../actual.info--url-unknown-file) &&
+ git-diff expected.info-unknown-file actual.info--url-unknown-file
+ '
+
+test_expect_success 'info unknown-directory' "
+ mkdir gitwc/unknown-directory svnwc/unknown-directory &&
+ ptouch gitwc/unknown-directory svnwc/unknown-directory &&
+ touch gitwc/unknown-directory/.placeholder &&
+ (cd svnwc; svn info unknown-directory) \
+ 2> expected.info-unknown-directory &&
+ (cd gitwc; git-svn info unknown-directory) \
+ 2> actual.info-unknown-directory &&
+ git-diff expected.info-unknown-directory actual.info-unknown-directory
+ "
+
+test_expect_success 'info --url unknown-directory' '
+ test -z $(cd gitwc; git-svn info --url unknown-directory \
+ 2> ../actual.info--url-unknown-directory) &&
+ git-diff expected.info-unknown-directory \
+ actual.info--url-unknown-directory
+ '
+
+test_expect_success 'info unknown-symlink-file' "
+ cd gitwc &&
+ ln -s unknown-file unknown-symlink-file &&
+ cd .. &&
+ cd svnwc &&
+ ln -s unknown-file unknown-symlink-file &&
+ cd .. &&
+ ptouch gitwc/unknown-symlink-file svnwc/unknown-symlink-file &&
+ (cd svnwc; svn info unknown-symlink-file) \
+ 2> expected.info-unknown-symlink-file &&
+ (cd gitwc; git-svn info unknown-symlink-file) \
+ 2> actual.info-unknown-symlink-file &&
+ git-diff expected.info-unknown-symlink-file \
+ actual.info-unknown-symlink-file
+ "
+
+test_expect_success 'info --url unknown-symlink-file' '
+ test -z $(cd gitwc; git-svn info --url unknown-symlink-file \
+ 2> ../actual.info--url-unknown-symlink-file) &&
+ git-diff expected.info-unknown-symlink-file \
+ actual.info--url-unknown-symlink-file
+ '
+
+test_expect_success 'info unknown-symlink-directory' "
+ cd gitwc &&
+ ln -s unknown-directory unknown-symlink-directory &&
+ cd .. &&
+ cd svnwc &&
+ ln -s unknown-directory unknown-symlink-directory &&
+ cd .. &&
+ ptouch gitwc/unknown-symlink-directory \
+ svnwc/unknown-symlink-directory &&
+ (cd svnwc; svn info unknown-symlink-directory) \
+ 2> expected.info-unknown-symlink-directory &&
+ (cd gitwc; git-svn info unknown-symlink-directory) \
+ 2> actual.info-unknown-symlink-directory &&
+ git-diff expected.info-unknown-symlink-directory \
+ actual.info-unknown-symlink-directory
+ "
+
+test_expect_success 'info --url unknown-symlink-directory' '
+ test -z $(cd gitwc; git-svn info --url unknown-symlink-directory \
+ 2> ../actual.info--url-unknown-symlink-directory) &&
+ git-diff expected.info-unknown-symlink-directory \
+ actual.info--url-unknown-symlink-directory
+ '
+
+test_done
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index 910c584f24..58c59ed5ae 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -2,7 +2,7 @@
#
# Copyright (c) Robin Rosenberg
#
-test_description='CVS export comit. '
+test_description='Test export of commits to CVS'
. ./test-lib.sh
@@ -225,7 +225,7 @@ test_expect_success \
! git-cvsexportcommit -c $id
)'
-case "$(git repo-config --bool core.filemode)" in
+case "$(git config --bool core.filemode)" in
false)
;;
*)
@@ -246,4 +246,55 @@ test_expect_success \
;;
esac
+test_expect_success '-w option should work with relative GIT_DIR' '
+ mkdir W &&
+ echo foobar >W/file1.txt &&
+ echo bazzle >W/file2.txt &&
+ git add W/file1.txt &&
+ git add W/file2.txt &&
+ git commit -m "More updates" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$GIT_DIR" &&
+ GIT_DIR=. git cvsexportcommit -w "$CVSWORK" -c $id &&
+ check_entries "$CVSWORK/W" "file1.txt/1.1/|file2.txt/1.1/" &&
+ diff -u "$CVSWORK/W/file1.txt" ../W/file1.txt &&
+ diff -u "$CVSWORK/W/file2.txt" ../W/file2.txt
+ )
+'
+
+test_expect_success 'check files before directories' '
+
+ echo Notes > release-notes &&
+ git add release-notes &&
+ git commit -m "Add release notes" release-notes &&
+ id=$(git rev-parse HEAD) &&
+ git cvsexportcommit -w "$CVSWORK" -c $id &&
+
+ echo new > DS &&
+ echo new > E/DS &&
+ echo modified > release-notes &&
+ git add DS E/DS release-notes &&
+ git commit -m "Add two files with the same basename" &&
+ id=$(git rev-parse HEAD) &&
+ git cvsexportcommit -w "$CVSWORK" -c $id &&
+ check_entries "$CVSWORK/E" "DS/1.1/|newfile5.txt/1.1/" &&
+ check_entries "$CVSWORK" "DS/1.1/|release-notes/1.2/" &&
+ diff -u "$CVSWORK/DS" DS &&
+ diff -u "$CVSWORK/E/DS" E/DS &&
+ diff -u "$CVSWORK/release-notes" release-notes
+
+'
+
+test_expect_success 'commit a file with leading spaces in the name' '
+
+ echo space > " space" &&
+ git add " space" &&
+ git commit -m "Add a file with a leading space" &&
+ id=$(git rev-parse HEAD) &&
+ git cvsexportcommit -w "$CVSWORK" -c $id &&
+ check_entries "$CVSWORK" " space/1.1/|DS/1.1/|release-notes/1.2/" &&
+ diff -u "$CVSWORK/ space" " space"
+
+'
+
test_done
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 0595041af5..cceedbb2b7 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -165,9 +165,9 @@ from refs/heads/master
M 755 0000000000000000000000000000000000000001 zero1
INPUT_END
-test_expect_failure \
- 'B: fail on invalid blob sha1' \
- 'git-fast-import <input'
+test_expect_success 'B: fail on invalid blob sha1' '
+ ! git-fast-import <input
+'
rm -f .git/objects/pack_* .git/objects/index_*
cat >input <<INPUT_END
@@ -180,9 +180,9 @@ COMMIT
from refs/heads/master
INPUT_END
-test_expect_failure \
- 'B: fail on invalid branch name ".badbranchname"' \
- 'git-fast-import <input'
+test_expect_success 'B: fail on invalid branch name ".badbranchname"' '
+ ! git-fast-import <input
+'
rm -f .git/objects/pack_* .git/objects/index_*
cat >input <<INPUT_END
@@ -195,9 +195,9 @@ COMMIT
from refs/heads/master
INPUT_END
-test_expect_failure \
- 'B: fail on invalid branch name "bad[branch]name"' \
- 'git-fast-import <input'
+test_expect_success 'B: fail on invalid branch name "bad[branch]name"' '
+ ! git-fast-import <input
+'
rm -f .git/objects/pack_* .git/objects/index_*
cat >input <<INPUT_END
@@ -339,9 +339,9 @@ COMMIT
from refs/heads/branch^0
INPUT_END
-test_expect_failure \
- 'E: rfc2822 date, --date-format=raw' \
- 'git-fast-import --date-format=raw <input'
+test_expect_success 'E: rfc2822 date, --date-format=raw' '
+ ! git-fast-import --date-format=raw <input
+'
test_expect_success \
'E: rfc2822 date, --date-format=rfc2822' \
'git-fast-import --date-format=rfc2822 <input'
diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh
new file mode 100755
index 0000000000..f09bfb1117
--- /dev/null
+++ b/t/t9301-fast-export.sh
@@ -0,0 +1,123 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git-fast-export'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+ echo Wohlauf > file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ echo die Luft > file &&
+ echo geht frisch > file2 &&
+ git add file file2 &&
+ test_tick &&
+ git commit -m second &&
+ echo und > file2 &&
+ test_tick &&
+ git commit -m third file2 &&
+ test_tick &&
+ git tag rein &&
+ git checkout -b wer HEAD^ &&
+ echo lange > file2
+ test_tick &&
+ git commit -m sitzt file2 &&
+ test_tick &&
+ git tag -a -m valentin muss &&
+ git merge -s ours master
+
+'
+
+test_expect_success 'fast-export | fast-import' '
+
+ MASTER=$(git rev-parse --verify master) &&
+ REIN=$(git rev-parse --verify rein) &&
+ WER=$(git rev-parse --verify wer) &&
+ MUSS=$(git rev-parse --verify muss) &&
+ mkdir new &&
+ git --git-dir=new/.git init &&
+ git fast-export --all |
+ (cd new &&
+ git fast-import &&
+ test $MASTER = $(git rev-parse --verify refs/heads/master) &&
+ test $REIN = $(git rev-parse --verify refs/tags/rein) &&
+ test $WER = $(git rev-parse --verify refs/heads/wer) &&
+ test $MUSS = $(git rev-parse --verify refs/tags/muss))
+
+'
+
+test_expect_success 'fast-export master~2..master' '
+
+ git fast-export master~2..master |
+ sed "s/master/partial/" |
+ (cd new &&
+ git fast-import &&
+ test $MASTER != $(git rev-parse --verify refs/heads/partial) &&
+ git diff master..partial &&
+ git diff master^..partial^ &&
+ ! git rev-parse partial~2)
+
+'
+
+test_expect_success 'iso-8859-1' '
+
+ git config i18n.commitencoding ISO-8859-1 &&
+ # use author and committer name in ISO-8859-1 to match it.
+ . ../t3901-8859-1.txt &&
+ test_tick &&
+ echo rosten >file &&
+ git commit -s -m den file &&
+ git fast-export wer^..wer |
+ sed "s/wer/i18n/" |
+ (cd new &&
+ git fast-import &&
+ git cat-file commit i18n | grep "Ãéí óú")
+
+'
+
+cat > signed-tag-import << EOF
+tag sign-your-name
+from $(git rev-parse HEAD)
+tagger C O Mitter <committer@example.com> 1112911993 -0700
+data 210
+A message for a sign
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+fakedsignaturefakedsignaturefakedsignaturefakedsignaturfakedsign
+aturefakedsignaturefake=
+=/59v
+-----END PGP SIGNATURE-----
+EOF
+
+test_expect_success 'set up faked signed tag' '
+
+ cat signed-tag-import | git fast-import
+
+'
+
+test_expect_success 'signed-tags=abort' '
+
+ ! git fast-export --signed-tags=abort sign-your-name
+
+'
+
+test_expect_success 'signed-tags=verbatim' '
+
+ git fast-export --signed-tags=verbatim sign-your-name > output &&
+ grep PGP output
+
+'
+
+test_expect_success 'signed-tags=strip' '
+
+ git fast-export --signed-tags=strip sign-your-name > output &&
+ ! grep PGP output
+
+'
+
+test_done
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index 641303e0a1..0a20971ebb 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -33,19 +33,28 @@ CVS_SERVER=git-cvsserver
export CVSROOT CVS_SERVER
rm -rf "$CVSWORK" "$SERVERDIR"
-echo >empty &&
+test_expect_success 'setup' '
+ echo >empty &&
git add empty &&
git commit -q -m "First Commit" &&
+ mkdir secondroot &&
+ ( cd secondroot &&
+ git init &&
+ touch secondrootfile &&
+ git add secondrootfile &&
+ git commit -m "second root") &&
+ git pull secondroot master &&
git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
- GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
- exit 1
+ GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log"
+'
# note that cvs doesn't accept absolute pathnames
# 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))" = "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 | tail -n 1))" = "secondrootfile/1.1/"'
#------------------------
# PSERVER AUTHENTICATION
@@ -147,15 +156,19 @@ test_expect_success 'req_Root (strict paths)' \
'cat request-anonymous | git-cvsserver --strict-paths pserver $SERVERDIR >log 2>&1 &&
tail -n1 log | grep -q "^I LOVE YOU$"'
-test_expect_failure 'req_Root failure (strict-paths)' \
- 'cat request-anonymous | git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1'
+test_expect_success 'req_Root failure (strict-paths)' '
+ ! cat request-anonymous |
+ git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1
+'
test_expect_success 'req_Root (w/o strict-paths)' \
'cat request-anonymous | git-cvsserver pserver $WORKDIR/ >log 2>&1 &&
tail -n1 log | grep -q "^I LOVE YOU$"'
-test_expect_failure 'req_Root failure (w/o strict-paths)' \
- 'cat request-anonymous | git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1'
+test_expect_success 'req_Root failure (w/o strict-paths)' '
+ ! cat request-anonymous |
+ git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1
+'
cat >request-base <<EOF
BEGIN AUTH REQUEST
@@ -170,8 +183,10 @@ test_expect_success 'req_Root (base-path)' \
'cat request-base | git-cvsserver --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&
tail -n1 log | grep -q "^I LOVE YOU$"'
-test_expect_failure 'req_Root failure (base-path)' \
- 'cat request-anonymous | git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1'
+test_expect_success 'req_Root failure (base-path)' '
+ ! cat request-anonymous |
+ git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1
+'
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1
@@ -179,9 +194,8 @@ test_expect_success 'req_Root (export-all)' \
'cat request-anonymous | git-cvsserver --export-all pserver $WORKDIR >log 2>&1 &&
tail -n1 log | grep -q "^I LOVE YOU$"'
-test_expect_failure 'req_Root failure (export-all w/o whitelist)' \
- 'cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 ||
- false'
+test_expect_success 'req_Root failure (export-all w/o whitelist)' \
+ '! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)'
test_expect_success 'req_Root (everything together)' \
'cat request-base | git-cvsserver --export-all --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&
@@ -281,15 +295,16 @@ test_expect_success 'cvs update (update existing file)' \
cd "$WORKDIR"
#TODO: cvsserver doesn't support update w/o -d
-test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" \
- 'mkdir test &&
+test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" '
+ mkdir test &&
echo >test/empty &&
git add test &&
git commit -q -m "Single Subdirectory" &&
git push gitcvs.git >/dev/null &&
cd cvswork &&
GIT_CONFIG="$git_config" cvs -Q update &&
- test ! -d test'
+ test ! -d test
+'
cd "$WORKDIR"
test_expect_success 'cvs update (subdirectories)' \
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
index f7bad5bb2f..796cd7dba0 100755
--- a/t/t9500-gitweb-standalone-no-errors.sh
+++ b/t/t9500-gitweb-standalone-no-errors.sh
@@ -31,7 +31,6 @@ our \$projects_list = "";
our \$export_ok = "";
our \$strict_export = "";
-CGI::Carp::set_programname("gitweb/gitweb.cgi");
EOF
cat >.git/description <<EOF
@@ -558,4 +557,52 @@ test_expect_success \
'gitweb_run "p=.git;a=tree;opt=--no-merges"'
test_debug 'cat gitweb.log'
+# ----------------------------------------------------------------------
+# testing config_to_multi / cloneurl
+
+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
+http://example.com/git/trash.git
+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
+
+cat >>gitweb_config.perl <<EOF
+
+\$feature{'blame'}{'override'} = 1;
+\$feature{'snapshot'}{'override'} = 1;
+EOF
+
+test_expect_success \
+ 'config override: tree view, features disabled in repo config' \
+ 'git config gitweb.blame no &&
+ git config gitweb.snapshot none &&
+ gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'config override: tree view, features enabled in repo config' \
+ 'git config gitweb.blame yes &&
+ git config gitweb.snapshot "zip,tgz, tbz2" &&
+ gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
test_done
diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh
new file mode 100755
index 0000000000..7706430d81
--- /dev/null
+++ b/t/t9600-cvsimport.sh
@@ -0,0 +1,150 @@
+#!/bin/sh
+
+test_description='git-cvsimport basic tests'
+. ./test-lib.sh
+
+if ! type cvs >/dev/null 2>&1
+then
+ say 'skipping cvsimport tests, cvs not found'
+ test_done
+ exit
+fi
+
+cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
+case "$cvsps_version" in
+2.1)
+ ;;
+'')
+ say 'skipping cvsimport tests, cvsps not found'
+ test_done
+ exit
+ ;;
+*)
+ say 'skipping cvsimport tests, cvsps too old'
+ test_done
+ exit
+ ;;
+esac
+
+CVSROOT=$(pwd)/cvsroot
+export CVSROOT
+# for clean cvsps cache
+HOME=$(pwd)
+export HOME
+
+test_expect_success 'setup cvsroot' 'cvs init'
+
+test_expect_success 'setup a cvs module' '
+
+ mkdir $CVSROOT/module &&
+ cvs co -d module-cvs module &&
+ cd module-cvs &&
+ cat <<EOF >o_fortuna &&
+O Fortuna
+velut luna
+statu variabilis,
+
+semper crescis
+aut decrescis;
+vita detestabilis
+
+nunc obdurat
+et tunc curat
+ludo mentis aciem,
+
+egestatem,
+potestatem
+dissolvit ut glaciem.
+EOF
+ cvs add o_fortuna &&
+ cat <<EOF >message &&
+add "O Fortuna" lyrics
+
+These public domain lyrics make an excellent sample text.
+EOF
+ cvs commit -F message &&
+ cd ..
+'
+
+test_expect_success 'import a trivial module' '
+
+ git cvsimport -a -z 0 -C module-git module &&
+ git diff module-cvs/o_fortuna module-git/o_fortuna
+
+'
+
+test_expect_success 'pack refs' 'cd module-git && git gc && cd ..'
+
+test_expect_success 'update cvs module' '
+
+ cd module-cvs &&
+ cat <<EOF >o_fortuna &&
+O Fortune,
+like the moon
+you are changeable,
+
+ever waxing
+and waning;
+hateful life
+
+first oppresses
+and then soothes
+as fancy takes it;
+
+poverty
+and power
+it melts them like ice.
+EOF
+ cat <<EOF >message &&
+translate to English
+
+My Latin is terrible.
+EOF
+ cvs commit -F message &&
+ cd ..
+'
+
+test_expect_success 'update git module' '
+
+ cd module-git &&
+ git cvsimport -a -z 0 module &&
+ git merge origin &&
+ cd .. &&
+ git diff module-cvs/o_fortuna module-git/o_fortuna
+
+'
+
+test_expect_success 'update cvs module' '
+
+ cd module-cvs &&
+ echo 1 >tick &&
+ cvs add tick &&
+ cvs commit -m 1
+ cd ..
+
+'
+
+test_expect_success 'cvsimport.module config works' '
+
+ cd module-git &&
+ git config cvsimport.module module &&
+ git cvsimport -a -z0 &&
+ git merge origin &&
+ cd .. &&
+ git diff module-cvs/tick module-git/tick
+
+'
+
+test_expect_success 'import from a CVS working tree' '
+
+ cvs co -d import-from-wt module &&
+ cd import-from-wt &&
+ git cvsimport -a -z0 &&
+ echo 1 >expect &&
+ git log -1 --pretty=format:%s%n >actual &&
+ git diff actual expect &&
+ cd ..
+
+'
+
+test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index cc1253ccab..83889c4f46 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -59,18 +59,12 @@ esac
# '
# . ./test-lib.sh
-error () {
- echo "* error: $*"
- trap - exit
- exit 1
-}
-
-say () {
- echo "* $*"
-}
-
-test "${test_description}" != "" ||
-error "Test script did not set test_description."
+[ "x$TERM" != "xdumb" ] &&
+ [ -t 1 ] &&
+ tput bold >/dev/null 2>&1 &&
+ tput setaf 1 >/dev/null 2>&1 &&
+ tput sgr0 >/dev/null 2>&1 &&
+ color=t
while test "$#" -ne 0
do
@@ -80,10 +74,13 @@ do
-i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
immediate=t; shift ;;
-h|--h|--he|--hel|--help)
- echo "$test_description"
- exit 0 ;;
+ help=t; shift ;;
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
verbose=t; shift ;;
+ -q|--q|--qu|--qui|--quie|--quiet)
+ quiet=t; shift ;;
+ --no-color)
+ color=; shift ;;
--no-python)
# noop now...
shift ;;
@@ -92,6 +89,46 @@ do
esac
done
+if test -n "$color"; then
+ say_color () {
+ case "$1" in
+ error) tput bold; tput setaf 1;; # bold red
+ skip) tput bold; tput setaf 2;; # bold green
+ pass) tput setaf 2;; # green
+ info) tput setaf 3;; # brown
+ *) test -n "$quiet" && return;;
+ esac
+ shift
+ echo "* $*"
+ tput sgr0
+ }
+else
+ say_color() {
+ test -z "$1" && test -n "$quiet" && return
+ shift
+ echo "* $*"
+ }
+fi
+
+error () {
+ say_color error "error: $*"
+ trap - exit
+ exit 1
+}
+
+say () {
+ say_color info "$*"
+}
+
+test "${test_description}" != "" ||
+error "Test script did not set test_description."
+
+if test "$help" = "t"
+then
+ echo "$test_description"
+ exit 0
+fi
+
exec 5>&1
if test "$verbose" = "t"
then
@@ -102,6 +139,8 @@ fi
test_failure=0
test_count=0
+test_fixed=0
+test_broken=0
trap 'echo >&5 "FATAL: Unexpected exit with code $?"; exit 1' exit
@@ -122,18 +161,29 @@ test_tick () {
test_ok_ () {
test_count=$(expr "$test_count" + 1)
- say " ok $test_count: $@"
+ say_color "" " ok $test_count: $@"
}
test_failure_ () {
test_count=$(expr "$test_count" + 1)
test_failure=$(expr "$test_failure" + 1);
- say "FAIL $test_count: $1"
+ say_color error "FAIL $test_count: $1"
shift
echo "$@" | sed -e 's/^/ /'
test "$immediate" = "" || { trap - exit; exit 1; }
}
+test_known_broken_ok_ () {
+ test_count=$(expr "$test_count" + 1)
+ test_fixed=$(($test_fixed+1))
+ say_color "" " FIXED $test_count: $@"
+}
+
+test_known_broken_failure_ () {
+ test_count=$(expr "$test_count" + 1)
+ test_broken=$(($test_broken+1))
+ say_color skip " still broken $test_count: $@"
+}
test_debug () {
test "$debug" = "" || eval "$1"
@@ -158,9 +208,9 @@ test_skip () {
done
case "$to_skip" in
t)
- say >&3 "skipping test: $@"
+ say_color skip >&3 "skipping test: $@"
test_count=$(expr "$test_count" + 1)
- say "skip $test_count: $1"
+ say_color skip "skip $test_count: $1"
: true
;;
*)
@@ -174,13 +224,13 @@ test_expect_failure () {
error "bug in the test script: not 2 parameters to test-expect-failure"
if ! test_skip "$@"
then
- say >&3 "expecting failure: $2"
+ say >&3 "checking known breakage: $2"
test_run_ "$2"
- if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ]
+ if [ "$?" = 0 -a "$eval_ret" = 0 ]
then
- test_ok_ "$1"
+ test_known_broken_ok_ "$1"
else
- test_failure_ "$@"
+ test_known_broken_failure_ "$1"
fi
fi
echo >&3 ""
@@ -220,7 +270,7 @@ test_expect_code () {
echo >&3 ""
}
-# Most tests can use the created repository, but some amy need to create more.
+# Most tests can use the created repository, but some may need to create more.
# Usage: test_create_repo <directory>
test_create_repo () {
test "$#" = 1 ||
@@ -237,6 +287,18 @@ test_create_repo () {
test_done () {
trap - exit
+
+ if test "$test_fixed" != 0
+ then
+ 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)"
+ msg="remaining $(($test_count-$test_broken)) test(s)"
+ else
+ msg="$test_count test(s)"
+ fi
case "$test_failure" in
0)
# We could:
@@ -247,11 +309,11 @@ test_done () {
# The Makefile provided will clean this test area so
# we will leave things as they are.
- say "passed all $test_count test(s)"
+ say_color pass "passed all $msg"
exit 0 ;;
*)
- say "failed $test_failure among $test_count test(s)"
+ say_color error "failed $test_failure among $msg"
exit 1 ;;
esac
@@ -262,8 +324,11 @@ test_done () {
PATH=$(pwd)/..:$PATH
GIT_EXEC_PATH=$(pwd)/..
GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
-GIT_CONFIG=.git/config
-export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG
+unset GIT_CONFIG
+unset GIT_CONFIG_LOCAL
+GIT_CONFIG_NOSYSTEM=1
+GIT_CONFIG_NOGLOBAL=1
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
export GITPERLLIB
@@ -296,8 +361,8 @@ do
done
case "$to_skip" in
t)
- say >&3 "skipping test $this_test altogether"
- say "skip all tests in $this_test"
+ say_color skip >&3 "skipping test $this_test altogether"
+ say_color skip "skip all tests in $this_test"
test_done
esac
done
diff --git a/tag.c b/tag.c
index f62bcdd994..990134fe7a 100644
--- a/tag.c
+++ b/tag.c
@@ -9,7 +9,10 @@ const char *tag_type = "tag";
struct object *deref_tag(struct object *o, const char *warn, int warnlen)
{
while (o && o->type == OBJ_TAG)
- o = parse_object(((struct tag *)o)->tagged->sha1);
+ if (((struct tag *)o)->tagged)
+ o = parse_object(((struct tag *)o)->tagged->sha1);
+ else
+ o = NULL;
if (!o && warn) {
if (!warnlen)
warnlen = strlen(warn);
@@ -39,6 +42,7 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
unsigned char sha1[20];
const char *type_line, *tag_line, *sig_line;
char type[20];
+ const char *start = data;
if (item->object.parsed)
return 0;
@@ -53,11 +57,11 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
if (memcmp("\ntype ", type_line-1, 6))
return -1;
- tag_line = strchr(type_line, '\n');
+ tag_line = memchr(type_line, '\n', size - (type_line - start));
if (!tag_line || memcmp("tag ", ++tag_line, 4))
return -1;
- sig_line = strchr(tag_line, '\n');
+ sig_line = memchr(tag_line, '\n', size - (tag_line - start));
if (!sig_line)
return -1;
sig_line++;
diff --git a/templates/Makefile b/templates/Makefile
index 6f4dbd362f..ebd3a62fd8 100644
--- a/templates/Makefile
+++ b/templates/Makefile
@@ -46,6 +46,6 @@ clean:
$(RM) -r blt boilerplates.made
install: all
- $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(template_dir_SQ)'
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(template_dir_SQ)'
(cd blt && $(TAR) cf - .) | \
(cd '$(DESTDIR_SQ)$(template_dir_SQ)' && $(TAR) xf -)
diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg
index c5cdb9d7ee..4ef86eb244 100644
--- a/templates/hooks--commit-msg
+++ b/templates/hooks--commit-msg
@@ -9,6 +9,9 @@
# To enable this hook, make this file executable.
# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
diff --git a/templates/hooks--pre-commit b/templates/hooks--pre-commit
index 18b87309f6..b25dce6bbf 100644
--- a/templates/hooks--pre-commit
+++ b/templates/hooks--pre-commit
@@ -13,7 +13,7 @@
if git-rev-parse --verify HEAD 2>/dev/null
then
- git-diff-index -p -M --cached HEAD
+ git-diff-index -p -M --cached HEAD --
else
# NEEDSWORK: we should produce a diff with an empty tree here
# if we want to do the same verification for the initial import.
@@ -58,10 +58,10 @@ perl -e '
if (/\s$/) {
bad_line("trailing whitespace", $_);
}
- if (/^\s* /) {
+ if (/^\s* \t/) {
bad_line("indent SP followed by a TAB", $_);
}
- if (/^(?:[<>=]){7}/) {
+ if (/^([<>])\1{6} |^={7}$/) {
bad_line("unresolved merge conflict", $_);
}
}
diff --git a/templates/hooks--prepare-commit-msg b/templates/hooks--prepare-commit-msg
new file mode 100644
index 0000000000..ff0f42a1d9
--- /dev/null
+++ b/templates/hooks--prepare-commit-msg
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by git-commit with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source. The hook's purpose is to edit the commit
+# message file. If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, make this file executable.
+
+# This hook includes three examples. The first comments out the
+# "Conflicts:" part of a merge commit.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output. It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited. This is rarely a good idea.
+
+case "$2 $3" in
+ merge)
+ sed -i '/^Conflicts:/,/#/!b;s/^/# &/;s/^# #/#/' "$1" ;;
+
+# ""|template)
+# perl -i -pe '
+# print "\n" . `git diff --cached --name-status -r`
+# if /^#/ && $first++ == 0' "$1" ;;
+
+ *) ;;
+esac
+
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
diff --git a/templates/hooks--update b/templates/hooks--update
index d8c76264be..4b69268fd0 100644
--- a/templates/hooks--update
+++ b/templates/hooks--update
@@ -10,6 +10,12 @@
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
+# hooks.allowdeletetag
+# This boolean sets whether deleting tags will be allowed in the
+# repository. By default they won't be.
+# hooks.allowdeletebranch
+# This boolean sets whether deleting branches will be allowed in the
+# repository. By default they won't be.
#
# --- Command line
@@ -31,19 +37,21 @@ if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
fi
# --- Config
-allowunannotated=$(git-repo-config --bool hooks.allowunannotated)
+allowunannotated=$(git config --bool hooks.allowunannotated)
+allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
+allowdeletetag=$(git config --bool hooks.allowdeletetag)
# check for no description
-projectdesc=$(sed -e '1p' "$GIT_DIR/description")
-if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb" ]; then
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb." ]; then
echo "*** Project description file hasn't been set" >&2
exit 1
fi
# --- Check types
-# if $newrev is 0000...0000, it's a commit to delete a branch
+# if $newrev is 0000...0000, it's a commit to delete a ref.
if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then
- newrev_type=commit
+ newrev_type=delete
else
newrev_type=$(git-cat-file -t $newrev)
fi
@@ -58,15 +66,36 @@ case "$refname","$newrev_type" in
exit 1
fi
;;
+ refs/tags/*,delete)
+ # delete tag
+ if [ "$allowdeletetag" != "true" ]; then
+ echo "*** Deleting a tag is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
refs/tags/*,tag)
# annotated tag
;;
refs/heads/*,commit)
# branch
;;
+ refs/heads/*,delete)
+ # delete branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
refs/remotes/*,commit)
# tracking branch
;;
+ refs/remotes/*,delete)
+ # delete tracking branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
diff --git a/test-parse-options.c b/test-parse-options.c
new file mode 100644
index 0000000000..eed8a02c65
--- /dev/null
+++ b/test-parse-options.c
@@ -0,0 +1,37 @@
+#include "cache.h"
+#include "parse-options.h"
+
+static int boolean = 0;
+static int integer = 0;
+static char *string = NULL;
+
+int main(int argc, const char **argv)
+{
+ const char *usage[] = {
+ "test-parse-options <options>",
+ NULL
+ };
+ struct option options[] = {
+ OPT_BOOLEAN('b', "boolean", &boolean, "get a boolean"),
+ OPT_INTEGER('i', "integer", &integer, "get a integer"),
+ OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
+ OPT_GROUP("string options"),
+ OPT_STRING('s', "string", &string, "string", "get a string"),
+ OPT_STRING(0, "string2", &string, "str", "get another string"),
+ OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"),
+ OPT_STRING('o', NULL, &string, "str", "get another string"),
+ OPT_END(),
+ };
+ int i;
+
+ argc = parse_options(argc, argv, options, usage, 0);
+
+ printf("boolean: %d\n", boolean);
+ printf("integer: %d\n", integer);
+ printf("string: %s\n", string ? string : "(not set)");
+
+ for (i = 0; i < argc; i++)
+ printf("arg %02d: %s\n", i, argv[i]);
+
+ return 0;
+}
diff --git a/test-sha1.sh b/test-sha1.sh
index 640856af5a..bf526c8f5e 100755
--- a/test-sha1.sh
+++ b/test-sha1.sh
@@ -10,7 +10,7 @@ do
{
test -z "$pfx" || echo "$pfx"
dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |
- tr '[\0]' '[g]'
+ tr '\000' 'g'
} | ./test-sha1 $cnt
`
if test "$expect" = "$actual"
@@ -55,7 +55,7 @@ do
{
test -z "$pfx" || echo "$pfx"
dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |
- tr '[\0]' '[g]'
+ tr '\000' 'g'
} | sha1sum |
sed -e 's/ .*//'
`
diff --git a/trace.c b/trace.c
index 69fa05e644..4713f9165c 100644
--- a/trace.c
+++ b/trace.c
@@ -37,7 +37,7 @@ static int get_trace_fd(int *need_close)
return STDERR_FILENO;
if (strlen(trace) == 1 && isdigit(*trace))
return atoi(trace);
- if (*trace == '/') {
+ if (is_absolute_path(trace)) {
int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666);
if (fd == -1) {
fprintf(stderr,
@@ -72,7 +72,7 @@ void trace_printf(const char *fmt, ...)
if (!fd)
return;
- strbuf_init(&buf, 0);
+ strbuf_init(&buf, 64);
va_start(ap, fmt);
len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
va_end(ap);
@@ -93,7 +93,7 @@ void trace_printf(const char *fmt, ...)
close(fd);
}
-void trace_argv_printf(const char **argv, int count, const char *fmt, ...)
+void trace_argv_printf(const char **argv, const char *fmt, ...)
{
struct strbuf buf;
va_list ap;
@@ -103,7 +103,7 @@ void trace_argv_printf(const char **argv, int count, const char *fmt, ...)
if (!fd)
return;
- strbuf_init(&buf, 0);
+ strbuf_init(&buf, 64);
va_start(ap, fmt);
len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
va_end(ap);
@@ -117,7 +117,7 @@ void trace_argv_printf(const char **argv, int count, const char *fmt, ...)
}
strbuf_setlen(&buf, len);
- sq_quote_argv(&buf, argv, count, 0);
+ sq_quote_argv(&buf, argv, 0);
strbuf_addch(&buf, '\n');
write_or_whine_pipe(fd, buf.buf, buf.len, err_msg);
strbuf_release(&buf);
diff --git a/transport.c b/transport.c
new file mode 100644
index 0000000000..397983d115
--- /dev/null
+++ b/transport.c
@@ -0,0 +1,818 @@
+#include "cache.h"
+#include "transport.h"
+#include "run-command.h"
+#ifndef NO_CURL
+#include "http.h"
+#endif
+#include "pkt-line.h"
+#include "fetch-pack.h"
+#include "send-pack.h"
+#include "walker.h"
+#include "bundle.h"
+#include "dir.h"
+#include "refs.h"
+
+/* rsync support */
+
+/*
+ * We copy packed-refs and refs/ into a temporary file, then read the
+ * loose refs recursively (sorting whenever possible), and then inserting
+ * those packed refs that are not yet in the list (not validating, but
+ * assuming that the file is sorted).
+ *
+ * Appears refactoring this from refs.c is too cumbersome.
+ */
+
+static int str_cmp(const void *a, const void *b)
+{
+ const char *s1 = a;
+ const char *s2 = b;
+
+ return strcmp(s1, s2);
+}
+
+/* path->buf + name_offset is expected to point to "refs/" */
+
+static int read_loose_refs(struct strbuf *path, int name_offset,
+ struct ref **tail)
+{
+ DIR *dir = opendir(path->buf);
+ struct dirent *de;
+ struct {
+ char **entries;
+ int nr, alloc;
+ } list;
+ int i, pathlen;
+
+ if (!dir)
+ return -1;
+
+ memset (&list, 0, sizeof(list));
+
+ while ((de = readdir(dir))) {
+ if (de->d_name[0] == '.' && (de->d_name[1] == '\0' ||
+ (de->d_name[1] == '.' &&
+ de->d_name[2] == '\0')))
+ continue;
+ ALLOC_GROW(list.entries, list.nr + 1, list.alloc);
+ list.entries[list.nr++] = xstrdup(de->d_name);
+ }
+ closedir(dir);
+
+ /* sort the list */
+
+ qsort(list.entries, list.nr, sizeof(char *), str_cmp);
+
+ pathlen = path->len;
+ strbuf_addch(path, '/');
+
+ for (i = 0; i < list.nr; i++, strbuf_setlen(path, pathlen + 1)) {
+ strbuf_addstr(path, list.entries[i]);
+ if (read_loose_refs(path, name_offset, tail)) {
+ int fd = open(path->buf, O_RDONLY);
+ char buffer[40];
+ struct ref *next;
+
+ if (fd < 0)
+ continue;
+ next = alloc_ref(path->len - name_offset + 1);
+ if (read_in_full(fd, buffer, 40) != 40 ||
+ get_sha1_hex(buffer, next->old_sha1)) {
+ close(fd);
+ free(next);
+ continue;
+ }
+ close(fd);
+ strcpy(next->name, path->buf + name_offset);
+ (*tail)->next = next;
+ *tail = next;
+ }
+ }
+ strbuf_setlen(path, pathlen);
+
+ for (i = 0; i < list.nr; i++)
+ free(list.entries[i]);
+ free(list.entries);
+
+ return 0;
+}
+
+/* insert the packed refs for which no loose refs were found */
+
+static void insert_packed_refs(const char *packed_refs, struct ref **list)
+{
+ FILE *f = fopen(packed_refs, "r");
+ static char buffer[PATH_MAX];
+
+ if (!f)
+ return;
+
+ for (;;) {
+ int cmp = cmp, len;
+
+ if (!fgets(buffer, sizeof(buffer), f)) {
+ fclose(f);
+ return;
+ }
+
+ if (hexval(buffer[0]) > 0xf)
+ continue;
+ len = strlen(buffer);
+ if (len && buffer[len - 1] == '\n')
+ buffer[--len] = '\0';
+ if (len < 41)
+ continue;
+ while ((*list)->next &&
+ (cmp = strcmp(buffer + 41,
+ (*list)->next->name)) > 0)
+ list = &(*list)->next;
+ if (!(*list)->next || cmp < 0) {
+ struct ref *next = alloc_ref(len - 40);
+ buffer[40] = '\0';
+ if (get_sha1_hex(buffer, next->old_sha1)) {
+ warning ("invalid SHA-1: %s", buffer);
+ free(next);
+ continue;
+ }
+ strcpy(next->name, buffer + 41);
+ next->next = (*list)->next;
+ (*list)->next = next;
+ list = &(*list)->next;
+ }
+ }
+}
+
+static struct ref *get_refs_via_rsync(struct transport *transport)
+{
+ struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
+ struct ref dummy, *tail = &dummy;
+ struct child_process rsync;
+ const char *args[5];
+ int temp_dir_len;
+
+ /* copy the refs to the temporary directory */
+
+ strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX"));
+ if (!mkdtemp(temp_dir.buf))
+ die ("Could not make temporary directory");
+ temp_dir_len = temp_dir.len;
+
+ strbuf_addstr(&buf, transport->url);
+ strbuf_addstr(&buf, "/refs");
+
+ memset(&rsync, 0, sizeof(rsync));
+ rsync.argv = args;
+ rsync.stdout_to_stderr = 1;
+ args[0] = "rsync";
+ args[1] = (transport->verbose > 0) ? "-rv" : "-r";
+ args[2] = buf.buf;
+ args[3] = temp_dir.buf;
+ args[4] = NULL;
+
+ if (run_command(&rsync))
+ die ("Could not run rsync to get refs");
+
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, transport->url);
+ strbuf_addstr(&buf, "/packed-refs");
+
+ args[2] = buf.buf;
+
+ if (run_command(&rsync))
+ die ("Could not run rsync to get refs");
+
+ /* read the copied refs */
+
+ strbuf_addstr(&temp_dir, "/refs");
+ read_loose_refs(&temp_dir, temp_dir_len + 1, &tail);
+ strbuf_setlen(&temp_dir, temp_dir_len);
+
+ tail = &dummy;
+ strbuf_addstr(&temp_dir, "/packed-refs");
+ insert_packed_refs(temp_dir.buf, &tail);
+ strbuf_setlen(&temp_dir, temp_dir_len);
+
+ if (remove_dir_recursively(&temp_dir, 0))
+ warning ("Error removing temporary directory %s.",
+ temp_dir.buf);
+
+ strbuf_release(&buf);
+ strbuf_release(&temp_dir);
+
+ return dummy.next;
+}
+
+static int fetch_objs_via_rsync(struct transport *transport,
+ int nr_objs, struct ref **to_fetch)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct child_process rsync;
+ const char *args[8];
+ int result;
+
+ strbuf_addstr(&buf, transport->url);
+ strbuf_addstr(&buf, "/objects/");
+
+ memset(&rsync, 0, sizeof(rsync));
+ rsync.argv = args;
+ rsync.stdout_to_stderr = 1;
+ args[0] = "rsync";
+ args[1] = (transport->verbose > 0) ? "-rv" : "-r";
+ args[2] = "--ignore-existing";
+ args[3] = "--exclude";
+ args[4] = "info";
+ args[5] = buf.buf;
+ args[6] = get_object_directory();
+ args[7] = NULL;
+
+ /* NEEDSWORK: handle one level of alternates */
+ result = run_command(&rsync);
+
+ strbuf_release(&buf);
+
+ return result;
+}
+
+static int write_one_ref(const char *name, const unsigned char *sha1,
+ int flags, void *data)
+{
+ struct strbuf *buf = data;
+ int len = buf->len;
+ FILE *f;
+
+ /* when called via for_each_ref(), flags is non-zero */
+ if (flags && prefixcmp(name, "refs/heads/") &&
+ prefixcmp(name, "refs/tags/"))
+ return 0;
+
+ strbuf_addstr(buf, name);
+ if (safe_create_leading_directories(buf->buf) ||
+ !(f = fopen(buf->buf, "w")) ||
+ fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 ||
+ fclose(f))
+ return error("problems writing temporary file %s", buf->buf);
+ strbuf_setlen(buf, len);
+ return 0;
+}
+
+static int write_refs_to_temp_dir(struct strbuf *temp_dir,
+ int refspec_nr, const char **refspec)
+{
+ int i;
+
+ for (i = 0; i < refspec_nr; i++) {
+ unsigned char sha1[20];
+ char *ref;
+
+ if (dwim_ref(refspec[i], strlen(refspec[i]), sha1, &ref) != 1)
+ return error("Could not get ref %s", refspec[i]);
+
+ if (write_one_ref(ref, sha1, 0, temp_dir)) {
+ free(ref);
+ return -1;
+ }
+ free(ref);
+ }
+ return 0;
+}
+
+static int rsync_transport_push(struct transport *transport,
+ int refspec_nr, const char **refspec, int flags)
+{
+ struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
+ int result = 0, i;
+ struct child_process rsync;
+ const char *args[10];
+
+ if (flags & TRANSPORT_PUSH_MIRROR)
+ return error("rsync transport does not support mirror mode");
+
+ /* first push the objects */
+
+ strbuf_addstr(&buf, transport->url);
+ strbuf_addch(&buf, '/');
+
+ memset(&rsync, 0, sizeof(rsync));
+ rsync.argv = args;
+ rsync.stdout_to_stderr = 1;
+ i = 0;
+ args[i++] = "rsync";
+ args[i++] = "-a";
+ if (flags & TRANSPORT_PUSH_DRY_RUN)
+ args[i++] = "--dry-run";
+ if (transport->verbose > 0)
+ args[i++] = "-v";
+ args[i++] = "--ignore-existing";
+ args[i++] = "--exclude";
+ args[i++] = "info";
+ args[i++] = get_object_directory();
+ args[i++] = buf.buf;
+ args[i++] = NULL;
+
+ if (run_command(&rsync))
+ return error("Could not push objects to %s", transport->url);
+
+ /* copy the refs to the temporary directory; they could be packed. */
+
+ strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX"));
+ if (!mkdtemp(temp_dir.buf))
+ die ("Could not make temporary directory");
+ strbuf_addch(&temp_dir, '/');
+
+ if (flags & TRANSPORT_PUSH_ALL) {
+ if (for_each_ref(write_one_ref, &temp_dir))
+ return -1;
+ } else if (write_refs_to_temp_dir(&temp_dir, refspec_nr, refspec))
+ return -1;
+
+ i = 2;
+ if (flags & TRANSPORT_PUSH_DRY_RUN)
+ args[i++] = "--dry-run";
+ if (!(flags & TRANSPORT_PUSH_FORCE))
+ args[i++] = "--ignore-existing";
+ args[i++] = temp_dir.buf;
+ args[i++] = transport->url;
+ args[i++] = NULL;
+ if (run_command(&rsync))
+ result = error("Could not push to %s", transport->url);
+
+ if (remove_dir_recursively(&temp_dir, 0))
+ warning ("Could not remove temporary directory %s.",
+ temp_dir.buf);
+
+ strbuf_release(&buf);
+ strbuf_release(&temp_dir);
+
+ return result;
+}
+
+/* Generic functions for using commit walkers */
+
+#ifndef NO_CURL /* http fetch is the only user */
+static int fetch_objs_via_walker(struct transport *transport,
+ int nr_objs, struct ref **to_fetch)
+{
+ char *dest = xstrdup(transport->url);
+ struct walker *walker = transport->data;
+ char **objs = xmalloc(nr_objs * sizeof(*objs));
+ int i;
+
+ walker->get_all = 1;
+ walker->get_tree = 1;
+ walker->get_history = 1;
+ walker->get_verbosely = transport->verbose >= 0;
+ walker->get_recover = 0;
+
+ for (i = 0; i < nr_objs; i++)
+ objs[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
+
+ if (walker_fetch(walker, nr_objs, objs, NULL, NULL))
+ die("Fetch failed.");
+
+ for (i = 0; i < nr_objs; i++)
+ free(objs[i]);
+ free(objs);
+ free(dest);
+ return 0;
+}
+#endif /* NO_CURL */
+
+static int disconnect_walker(struct transport *transport)
+{
+ struct walker *walker = transport->data;
+ if (walker)
+ walker_free(walker);
+ return 0;
+}
+
+#ifndef NO_CURL
+static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
+{
+ const char **argv;
+ int argc;
+ int err;
+
+ if (flags & TRANSPORT_PUSH_MIRROR)
+ return error("http transport does not support mirror mode");
+
+ argv = xmalloc((refspec_nr + 12) * sizeof(char *));
+ argv[0] = "http-push";
+ argc = 1;
+ if (flags & TRANSPORT_PUSH_ALL)
+ argv[argc++] = "--all";
+ if (flags & TRANSPORT_PUSH_FORCE)
+ argv[argc++] = "--force";
+ if (flags & TRANSPORT_PUSH_DRY_RUN)
+ argv[argc++] = "--dry-run";
+ if (flags & TRANSPORT_PUSH_VERBOSE)
+ argv[argc++] = "--verbose";
+ argv[argc++] = transport->url;
+ while (refspec_nr--)
+ argv[argc++] = *refspec++;
+ argv[argc] = NULL;
+ err = run_command_v_opt(argv, RUN_GIT_CMD);
+ switch (err) {
+ case -ERR_RUN_COMMAND_FORK:
+ error("unable to fork for %s", argv[0]);
+ case -ERR_RUN_COMMAND_EXEC:
+ error("unable to exec %s", argv[0]);
+ break;
+ case -ERR_RUN_COMMAND_WAITPID:
+ case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+ case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+ case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+ error("%s died with strange error", argv[0]);
+ }
+ return !!err;
+}
+
+static struct ref *get_refs_via_curl(struct transport *transport)
+{
+ struct strbuf buffer = STRBUF_INIT;
+ char *data, *start, *mid;
+ char *ref_name;
+ char *refs_url;
+ int i = 0;
+
+ struct active_request_slot *slot;
+ struct slot_results results;
+
+ struct ref *refs = NULL;
+ struct ref *ref = NULL;
+ struct ref *last_ref = NULL;
+
+ if (!transport->data)
+ transport->data = get_http_walker(transport->url);
+
+ refs_url = xmalloc(strlen(transport->url) + 11);
+ sprintf(refs_url, "%s/info/refs", transport->url);
+
+ slot = get_active_slot();
+ slot->results = &results;
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, refs_url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+ if (transport->remote->http_proxy)
+ curl_easy_setopt(slot->curl, CURLOPT_PROXY,
+ transport->remote->http_proxy);
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (results.curl_result != CURLE_OK) {
+ strbuf_release(&buffer);
+ if (missing_target(&results)) {
+ return NULL;
+ } else {
+ error("%s", curl_errorstr);
+ return NULL;
+ }
+ }
+ } else {
+ strbuf_release(&buffer);
+ error("Unable to start request");
+ return NULL;
+ }
+
+ data = buffer.buf;
+ start = NULL;
+ mid = data;
+ while (i < buffer.len) {
+ if (!start)
+ start = &data[i];
+ if (data[i] == '\t')
+ mid = &data[i];
+ if (data[i] == '\n') {
+ data[i] = 0;
+ ref_name = mid + 1;
+ ref = xmalloc(sizeof(struct ref) +
+ strlen(ref_name) + 1);
+ memset(ref, 0, sizeof(struct ref));
+ strcpy(ref->name, ref_name);
+ get_sha1_hex(start, ref->old_sha1);
+ if (!refs)
+ refs = ref;
+ if (last_ref)
+ last_ref->next = ref;
+ last_ref = ref;
+ start = NULL;
+ }
+ i++;
+ }
+
+ strbuf_release(&buffer);
+
+ return refs;
+}
+
+static int fetch_objs_via_curl(struct transport *transport,
+ int nr_objs, struct ref **to_fetch)
+{
+ if (!transport->data)
+ transport->data = get_http_walker(transport->url);
+ return fetch_objs_via_walker(transport, nr_objs, to_fetch);
+}
+
+#endif
+
+struct bundle_transport_data {
+ int fd;
+ struct bundle_header header;
+};
+
+static struct ref *get_refs_from_bundle(struct transport *transport)
+{
+ struct bundle_transport_data *data = transport->data;
+ struct ref *result = NULL;
+ int i;
+
+ if (data->fd > 0)
+ close(data->fd);
+ data->fd = read_bundle_header(transport->url, &data->header);
+ if (data->fd < 0)
+ die ("Could not read bundle '%s'.", transport->url);
+ for (i = 0; i < data->header.references.nr; i++) {
+ struct ref_list_entry *e = data->header.references.list + i;
+ struct ref *ref = alloc_ref(strlen(e->name) + 1);
+ hashcpy(ref->old_sha1, e->sha1);
+ strcpy(ref->name, e->name);
+ ref->next = result;
+ result = ref;
+ }
+ return result;
+}
+
+static int fetch_refs_from_bundle(struct transport *transport,
+ int nr_heads, struct ref **to_fetch)
+{
+ struct bundle_transport_data *data = transport->data;
+ return unbundle(&data->header, data->fd);
+}
+
+static int close_bundle(struct transport *transport)
+{
+ struct bundle_transport_data *data = transport->data;
+ if (data->fd > 0)
+ close(data->fd);
+ free(data);
+ return 0;
+}
+
+struct git_transport_data {
+ unsigned thin : 1;
+ unsigned keep : 1;
+ int depth;
+ struct child_process *conn;
+ int fd[2];
+ const char *uploadpack;
+ const char *receivepack;
+};
+
+static int set_git_option(struct transport *connection,
+ const char *name, const char *value)
+{
+ struct git_transport_data *data = connection->data;
+ if (!strcmp(name, TRANS_OPT_UPLOADPACK)) {
+ data->uploadpack = value;
+ return 0;
+ } else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
+ data->receivepack = value;
+ return 0;
+ } else if (!strcmp(name, TRANS_OPT_THIN)) {
+ data->thin = !!value;
+ return 0;
+ } else if (!strcmp(name, TRANS_OPT_KEEP)) {
+ data->keep = !!value;
+ return 0;
+ } else if (!strcmp(name, TRANS_OPT_DEPTH)) {
+ if (!value)
+ data->depth = 0;
+ else
+ data->depth = atoi(value);
+ return 0;
+ }
+ return 1;
+}
+
+static int connect_setup(struct transport *transport)
+{
+ struct git_transport_data *data = transport->data;
+ data->conn = git_connect(data->fd, transport->url, data->uploadpack, 0);
+ return 0;
+}
+
+static struct ref *get_refs_via_connect(struct transport *transport)
+{
+ struct git_transport_data *data = transport->data;
+ struct ref *refs;
+
+ connect_setup(transport);
+ get_remote_heads(data->fd[0], &refs, 0, NULL, 0);
+
+ return refs;
+}
+
+static int fetch_refs_via_pack(struct transport *transport,
+ int nr_heads, struct ref **to_fetch)
+{
+ struct git_transport_data *data = transport->data;
+ char **heads = xmalloc(nr_heads * sizeof(*heads));
+ char **origh = xmalloc(nr_heads * sizeof(*origh));
+ const struct ref *refs;
+ char *dest = xstrdup(transport->url);
+ struct fetch_pack_args args;
+ int i;
+
+ memset(&args, 0, sizeof(args));
+ args.uploadpack = data->uploadpack;
+ args.keep_pack = data->keep;
+ args.lock_pack = 1;
+ args.use_thin_pack = data->thin;
+ args.verbose = transport->verbose > 0;
+ args.depth = data->depth;
+
+ for (i = 0; i < nr_heads; i++)
+ origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
+
+ refs = transport_get_remote_refs(transport);
+ if (!data->conn) {
+ struct ref *refs_tmp;
+ connect_setup(transport);
+ get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0);
+ free_refs(refs_tmp);
+ }
+
+ refs = fetch_pack(&args, data->fd, data->conn, transport->remote_refs,
+ dest, nr_heads, heads, &transport->pack_lockfile);
+ close(data->fd[0]);
+ close(data->fd[1]);
+ if (finish_connect(data->conn))
+ refs = NULL;
+ data->conn = NULL;
+
+ for (i = 0; i < nr_heads; i++)
+ free(origh[i]);
+ free(origh);
+ free(heads);
+ free(dest);
+ return (refs ? 0 : -1);
+}
+
+static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
+{
+ struct git_transport_data *data = transport->data;
+ struct send_pack_args args;
+
+ args.receivepack = data->receivepack;
+ args.send_all = !!(flags & TRANSPORT_PUSH_ALL);
+ args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
+ args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
+ args.use_thin_pack = data->thin;
+ args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
+ args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
+
+ return send_pack(&args, transport->url, transport->remote, refspec_nr, refspec);
+}
+
+static int disconnect_git(struct transport *transport)
+{
+ struct git_transport_data *data = transport->data;
+ if (data->conn) {
+ packet_flush(data->fd[1]);
+ close(data->fd[0]);
+ close(data->fd[1]);
+ finish_connect(data->conn);
+ }
+
+ free(data);
+ return 0;
+}
+
+static int is_local(const char *url)
+{
+ const char *colon = strchr(url, ':');
+ const char *slash = strchr(url, '/');
+ return !colon || (slash && slash < colon);
+}
+
+static int is_file(const char *url)
+{
+ struct stat buf;
+ if (stat(url, &buf))
+ return 0;
+ return S_ISREG(buf.st_mode);
+}
+
+struct transport *transport_get(struct remote *remote, const char *url)
+{
+ struct transport *ret = xcalloc(1, sizeof(*ret));
+
+ ret->remote = remote;
+ ret->url = url;
+
+ if (!prefixcmp(url, "rsync://")) {
+ ret->get_refs_list = get_refs_via_rsync;
+ ret->fetch = fetch_objs_via_rsync;
+ ret->push = rsync_transport_push;
+
+ } else if (!prefixcmp(url, "http://")
+ || !prefixcmp(url, "https://")
+ || !prefixcmp(url, "ftp://")) {
+#ifdef NO_CURL
+ error("git was compiled without libcurl support.");
+#else
+ ret->get_refs_list = get_refs_via_curl;
+ ret->fetch = fetch_objs_via_curl;
+ ret->push = curl_transport_push;
+#endif
+ ret->disconnect = disconnect_walker;
+
+ } else if (is_local(url) && is_file(url)) {
+ struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
+ ret->data = data;
+ ret->get_refs_list = get_refs_from_bundle;
+ ret->fetch = fetch_refs_from_bundle;
+ ret->disconnect = close_bundle;
+
+ } else {
+ struct git_transport_data *data = xcalloc(1, sizeof(*data));
+ ret->data = data;
+ ret->set_option = set_git_option;
+ ret->get_refs_list = get_refs_via_connect;
+ ret->fetch = fetch_refs_via_pack;
+ ret->push = git_transport_push;
+ ret->disconnect = disconnect_git;
+
+ data->thin = 1;
+ data->conn = NULL;
+ data->uploadpack = "git-upload-pack";
+ if (remote && remote->uploadpack)
+ data->uploadpack = remote->uploadpack;
+ data->receivepack = "git-receive-pack";
+ if (remote && remote->receivepack)
+ data->receivepack = remote->receivepack;
+ }
+
+ return ret;
+}
+
+int transport_set_option(struct transport *transport,
+ const char *name, const char *value)
+{
+ if (transport->set_option)
+ return transport->set_option(transport, name, value);
+ return 1;
+}
+
+int transport_push(struct transport *transport,
+ int refspec_nr, const char **refspec, int flags)
+{
+ if (!transport->push)
+ return 1;
+ return transport->push(transport, refspec_nr, refspec, flags);
+}
+
+const struct ref *transport_get_remote_refs(struct transport *transport)
+{
+ if (!transport->remote_refs)
+ transport->remote_refs = transport->get_refs_list(transport);
+ return transport->remote_refs;
+}
+
+int transport_fetch_refs(struct transport *transport, struct ref *refs)
+{
+ int rc;
+ int nr_heads = 0, nr_alloc = 0;
+ struct ref **heads = NULL;
+ struct ref *rm;
+
+ for (rm = refs; rm; rm = rm->next) {
+ if (rm->peer_ref &&
+ !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
+ continue;
+ ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
+ heads[nr_heads++] = rm;
+ }
+
+ rc = transport->fetch(transport, nr_heads, heads);
+ free(heads);
+ return rc;
+}
+
+void transport_unlock_pack(struct transport *transport)
+{
+ if (transport->pack_lockfile) {
+ unlink(transport->pack_lockfile);
+ free(transport->pack_lockfile);
+ transport->pack_lockfile = NULL;
+ }
+}
+
+int transport_disconnect(struct transport *transport)
+{
+ int ret = 0;
+ if (transport->disconnect)
+ ret = transport->disconnect(transport);
+ free(transport);
+ return ret;
+}
diff --git a/transport.h b/transport.h
new file mode 100644
index 0000000000..6fb4526cda
--- /dev/null
+++ b/transport.h
@@ -0,0 +1,72 @@
+#ifndef TRANSPORT_H
+#define TRANSPORT_H
+
+#include "cache.h"
+#include "remote.h"
+
+struct transport {
+ struct remote *remote;
+ const char *url;
+ void *data;
+ const struct ref *remote_refs;
+
+ /**
+ * Returns 0 if successful, positive if the option is not
+ * recognized or is inapplicable, and negative if the option
+ * is applicable but the value is invalid.
+ **/
+ int (*set_option)(struct transport *connection, const char *name,
+ const char *value);
+
+ struct ref *(*get_refs_list)(struct transport *transport);
+ int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+ int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
+
+ int (*disconnect)(struct transport *connection);
+ char *pack_lockfile;
+ signed verbose : 2;
+};
+
+#define TRANSPORT_PUSH_ALL 1
+#define TRANSPORT_PUSH_FORCE 2
+#define TRANSPORT_PUSH_DRY_RUN 4
+#define TRANSPORT_PUSH_MIRROR 8
+#define TRANSPORT_PUSH_VERBOSE 16
+
+/* Returns a transport suitable for the url */
+struct transport *transport_get(struct remote *, const char *);
+
+/* Transport options which apply to git:// and scp-style URLs */
+
+/* The program to use on the remote side to send a pack */
+#define TRANS_OPT_UPLOADPACK "uploadpack"
+
+/* The program to use on the remote side to receive a pack */
+#define TRANS_OPT_RECEIVEPACK "receivepack"
+
+/* Transfer the data as a thin pack if not null */
+#define TRANS_OPT_THIN "thin"
+
+/* Keep the pack that was transferred if not null */
+#define TRANS_OPT_KEEP "keep"
+
+/* Limit the depth of the fetch if not null */
+#define TRANS_OPT_DEPTH "depth"
+
+/**
+ * Returns 0 if the option was used, non-zero otherwise. Prints a
+ * message to stderr if the option is not used.
+ **/
+int transport_set_option(struct transport *transport, const char *name,
+ const char *value);
+
+int transport_push(struct transport *connection,
+ int refspec_nr, const char **refspec, int flags);
+
+const struct ref *transport_get_remote_refs(struct transport *transport);
+
+int transport_fetch_refs(struct transport *transport, struct ref *refs);
+void transport_unlock_pack(struct transport *transport);
+int transport_disconnect(struct transport *transport);
+
+#endif
diff --git a/tree-diff.c b/tree-diff.c
index 26bdbdd2bf..e1e2e6c6ce 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -39,7 +39,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
show_entry(opt, "+", t2, base, baselen);
return 1;
}
- if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2)
+ if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2)
return 0;
/*
@@ -52,10 +52,10 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
return 0;
}
- if (opt->recursive && S_ISDIR(mode1)) {
+ if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
int retval;
char *newbase = malloc_base(base, baselen, path1, pathlen1);
- if (opt->tree_in_recursive)
+ if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
opt->change(opt, mode1, mode2,
sha1, sha2, base, path1);
retval = diff_tree_sha1(sha1, sha2, newbase, opt);
@@ -206,7 +206,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
const char *path;
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
- if (opt->recursive && S_ISDIR(mode)) {
+ if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
enum object_type type;
int pathlen = tree_entry_len(path, sha1);
char *newbase = malloc_base(base, baselen, path, pathlen);
@@ -257,7 +257,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
int baselen = strlen(base);
for (;;) {
- if (opt->quiet && opt->has_changes)
+ if (DIFF_OPT_TST(opt, QUIET) && DIFF_OPT_TST(opt, HAS_CHANGES))
break;
if (opt->nr_paths) {
skip_uninteresting(t1, base, baselen, opt);
@@ -315,16 +315,18 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
q->nr = 0;
diff_setup(&diff_opts);
- diff_opts.recursive = 1;
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = opt->paths[0];
+ diff_opts.break_opt = opt->break_opt;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
if (diff_setup_done(&diff_opts) < 0)
die("unable to set up diff options to follow renames");
diff_tree(t1, t2, base, &diff_opts);
diffcore_std(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
/* Go through the new set of filepairing, and see if we find a more interesting one */
for (i = 0; i < q->nr; i++) {
@@ -341,6 +343,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
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);
break;
@@ -379,7 +382,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 (opt->follow_renames && diff_might_be_rename()) {
+ if (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);
diff --git a/tree-walk.c b/tree-walk.c
index 8d4b67317f..142205ddc3 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -7,6 +7,9 @@ static const char *get_mode(const char *str, unsigned int *modep)
unsigned char c;
unsigned int mode = 0;
+ if (*str == ' ')
+ return NULL;
+
while ((c = *str++) != ' ') {
if (c < '0' || c > '7')
return NULL;
@@ -16,13 +19,16 @@ static const char *get_mode(const char *str, unsigned int *modep)
return str;
}
-static void decode_tree_entry(struct tree_desc *desc, const void *buf, unsigned long size)
+static void decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size)
{
const char *path;
unsigned int mode, len;
+ if (size < 24 || buf[size - 21])
+ die("corrupt tree file");
+
path = get_mode(buf, &mode);
- if (!path)
+ if (!path || !*path)
die("corrupt tree file");
len = strlen(path) + 1;
diff --git a/tree.c b/tree.c
index 8c0819fa72..87708ef420 100644
--- a/tree.c
+++ b/tree.c
@@ -142,8 +142,8 @@ static int cmp_cache_name_compare(const void *a_, const void *b_)
ce1 = *((const struct cache_entry **)a_);
ce2 = *((const struct cache_entry **)b_);
- return cache_name_compare(ce1->name, ntohs(ce1->ce_flags),
- ce2->name, ntohs(ce2->ce_flags));
+ return cache_name_compare(ce1->name, ce1->ce_flags,
+ ce2->name, ce2->ce_flags);
}
int read_tree(struct tree *tree, int stage, const char **match)
diff --git a/unpack-trees.c b/unpack-trees.c
index ccfeb6e245..ec558f9005 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -71,12 +71,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
int remove;
int baselen = strlen(base);
int src_size = len + 1;
- int i_stk = i_stk;
int retval = 0;
- if (o->dir)
- i_stk = push_exclude_per_directory(o->dir, base, strlen(base));
-
do {
int i;
const char *first;
@@ -255,8 +251,6 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
} while (1);
leave_directory:
- if (o->dir)
- pop_exclude_per_directory(o->dir, i_stk);
return retval;
}
@@ -295,20 +289,19 @@ static struct checkout state;
static void check_updates(struct cache_entry **src, int nr,
struct unpack_trees_options *o)
{
- unsigned short mask = htons(CE_UPDATE);
unsigned cnt = 0, total = 0;
- struct progress progress;
+ struct progress *progress = NULL;
char last_symlink[PATH_MAX];
if (o->update && o->verbose_update) {
for (total = cnt = 0; cnt < nr; cnt++) {
struct cache_entry *ce = src[cnt];
- if (!ce->ce_mode || ce->ce_flags & mask)
+ if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
total++;
}
- start_progress_delay(&progress, "Checking %u files out...",
- "", total, 50, 2);
+ progress = start_progress_delay("Checking out files",
+ total, 50, 2);
cnt = 0;
}
@@ -316,24 +309,22 @@ static void check_updates(struct cache_entry **src, int nr,
while (nr--) {
struct cache_entry *ce = *src++;
- if (total)
- if (!ce->ce_mode || ce->ce_flags & mask)
- display_progress(&progress, ++cnt);
- if (!ce->ce_mode) {
+ if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
+ display_progress(progress, ++cnt);
+ if (ce->ce_flags & CE_REMOVE) {
if (o->update)
unlink_entry(ce->name, last_symlink);
continue;
}
- if (ce->ce_flags & mask) {
- ce->ce_flags &= ~mask;
+ if (ce->ce_flags & CE_UPDATE) {
+ ce->ce_flags &= ~CE_UPDATE;
if (o->update) {
checkout_entry(ce, &state, NULL);
*last_symlink = '\0';
}
}
}
- if (total)
- stop_progress(&progress);;
+ stop_progress(&progress);
}
int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
@@ -406,7 +397,7 @@ static void verify_uptodate(struct cache_entry *ce,
return;
if (!lstat(ce->name, &st)) {
- unsigned changed = ce_match_stat(ce, &st, 1);
+ unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
if (!changed)
return;
/*
@@ -416,7 +407,7 @@ static void verify_uptodate(struct cache_entry *ce,
* submodules that are marked to be automatically
* checked out.
*/
- if (S_ISGITLINK(ntohl(ce->ce_mode)))
+ if (S_ISGITLINK(ce->ce_mode))
return;
errno = 0;
}
@@ -458,7 +449,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
int cnt = 0;
unsigned char sha1[20];
- if (S_ISGITLINK(ntohl(ce->ce_mode)) &&
+ if (S_ISGITLINK(ce->ce_mode) &&
resolve_gitlink_ref(ce->name, "HEAD", sha1) == 0) {
/* If we are not going to update the submodule, then
* we don't care.
@@ -489,7 +480,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
*/
if (!ce_stage(ce)) {
verify_uptodate(ce, o);
- ce->ce_mode = 0;
+ ce->ce_flags |= CE_REMOVE;
}
cnt++;
}
@@ -530,8 +521,9 @@ static void verify_absent(struct cache_entry *ce, const char *action,
if (!lstat(ce->name, &st)) {
int cnt;
+ int dtype = ce_to_dtype(ce);
- if (o->dir && excluded(o->dir, ce->name))
+ if (o->dir && excluded(o->dir, ce->name, &dtype))
/*
* ce->name is explicitly excluded, so it is Ok to
* overwrite it.
@@ -576,7 +568,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
cnt = cache_name_pos(ce->name, strlen(ce->name));
if (0 <= cnt) {
struct cache_entry *ce = active_cache[cnt];
- if (!ce_stage(ce) && !ce->ce_mode)
+ if (ce->ce_flags & CE_REMOVE)
return;
}
@@ -588,7 +580,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
struct unpack_trees_options *o)
{
- merge->ce_flags |= htons(CE_UPDATE);
+ merge->ce_flags |= CE_UPDATE;
if (old) {
/*
* See if we can re-use the old CE directly?
@@ -598,7 +590,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
* a match.
*/
if (same(old, merge)) {
- *merge = *old;
+ memcpy(merge, old, offsetof(struct cache_entry, name));
} else {
verify_uptodate(old, o);
invalidate_ce_path(old);
@@ -609,7 +601,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
invalidate_ce_path(merge);
}
- merge->ce_flags &= ~htons(CE_STAGEMASK);
+ merge->ce_flags &= ~CE_STAGEMASK;
add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
return 1;
}
@@ -621,7 +613,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
verify_uptodate(old, o);
else
verify_absent(ce, "removed", o);
- ce->ce_mode = 0;
+ ce->ce_flags |= CE_REMOVE;
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
invalidate_ce_path(ce);
return 1;
@@ -642,7 +634,7 @@ static void show_stage_entry(FILE *o,
else
fprintf(o, "%s%06o %s %d\t%s\n",
label,
- ntohl(ce->ce_mode),
+ ce->ce_mode,
sha1_to_hex(ce->sha1),
ce_stage(ce),
ce->name);
@@ -927,8 +919,8 @@ int oneway_merge(struct cache_entry **src,
if (o->reset) {
struct stat st;
if (lstat(old->name, &st) ||
- ce_match_stat(old, &st, 1))
- old->ce_flags |= htons(CE_UPDATE);
+ ce_match_stat(old, &st, CE_MATCH_IGNORE_VALID))
+ old->ce_flags |= CE_UPDATE;
}
return keep_entry(old, o);
}
diff --git a/unpack-trees.h b/unpack-trees.h
index 5517faafad..197a0044aa 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -25,6 +25,7 @@ struct unpack_trees_options {
int merge_size;
struct cache_entry *df_conflict_entry;
+ void *unpack_data;
};
extern int unpack_trees(unsigned n, struct tree_desc *t,
diff --git a/upload-pack.c b/upload-pack.c
index fe96ef15c4..b26d05331d 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -9,6 +9,7 @@
#include "diff.h"
#include "revision.h"
#include "list-objects.h"
+#include "run-command.h"
static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
@@ -96,117 +97,88 @@ static void show_edge(struct commit *commit)
fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1));
}
+static int do_rev_list(int fd, void *create_full_pack)
+{
+ int i;
+ struct rev_info revs;
+
+ pack_pipe = fdopen(fd, "w");
+ if (create_full_pack)
+ use_thin_pack = 0; /* no point doing it */
+ init_revisions(&revs, NULL);
+ revs.tag_objects = 1;
+ revs.tree_objects = 1;
+ revs.blob_objects = 1;
+ 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);
+ }
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ mark_edges_uninteresting(revs.commits, &revs, show_edge);
+ traverse_commit_list(&revs, show_commit, show_object);
+ return 0;
+}
+
static void create_pack_file(void)
{
- /* Pipes between rev-list to pack-objects, pack-objects to us
- * and pack-objects error stream for progress bar.
- */
- int lp_pipe[2], pu_pipe[2], pe_pipe[2];
- pid_t pid_rev_list, pid_pack_objects;
+ struct async rev_list;
+ struct child_process pack_objects;
int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr);
char data[8193], progress[128];
char abort_msg[] = "aborting due to possible repository "
"corruption on the remote side.";
int buffered = -1;
-
- if (pipe(lp_pipe) < 0)
- die("git-upload-pack: unable to create pipe");
- pid_rev_list = fork();
- if (pid_rev_list < 0)
+ ssize_t sz;
+ const char *argv[10];
+ int arg = 0;
+
+ rev_list.proc = do_rev_list;
+ /* .data is just a boolean: any non-NULL value will do */
+ rev_list.data = create_full_pack ? &rev_list : NULL;
+ if (start_async(&rev_list))
die("git-upload-pack: unable to fork git-rev-list");
- if (!pid_rev_list) {
- int i;
- struct rev_info revs;
-
- close(lp_pipe[0]);
- pack_pipe = fdopen(lp_pipe[1], "w");
-
- if (create_full_pack)
- use_thin_pack = 0; /* no point doing it */
- init_revisions(&revs, NULL);
- revs.tag_objects = 1;
- revs.tree_objects = 1;
- revs.blob_objects = 1;
- 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);
- }
- prepare_revision_walk(&revs);
- mark_edges_uninteresting(revs.commits, &revs, show_edge);
- traverse_commit_list(&revs, show_commit, show_object);
- exit(0);
- }
-
- if (pipe(pu_pipe) < 0)
- die("git-upload-pack: unable to create pipe");
- if (pipe(pe_pipe) < 0)
- die("git-upload-pack: unable to create pipe");
- pid_pack_objects = fork();
- if (pid_pack_objects < 0) {
- /* daemon sets things up to ignore TERM */
- kill(pid_rev_list, SIGKILL);
+ argv[arg++] = "pack-objects";
+ argv[arg++] = "--stdout";
+ if (!no_progress)
+ argv[arg++] = "--progress";
+ if (use_ofs_delta)
+ argv[arg++] = "--delta-base-offset";
+ argv[arg++] = NULL;
+
+ memset(&pack_objects, 0, sizeof(pack_objects));
+ pack_objects.in = rev_list.out; /* start_command closes it */
+ pack_objects.out = -1;
+ pack_objects.err = -1;
+ pack_objects.git_cmd = 1;
+ pack_objects.argv = argv;
+
+ if (start_command(&pack_objects))
die("git-upload-pack: unable to fork git-pack-objects");
- }
- if (!pid_pack_objects) {
- const char *argv[10];
- int i = 0;
-
- dup2(lp_pipe[0], 0);
- dup2(pu_pipe[1], 1);
- dup2(pe_pipe[1], 2);
-
- close(lp_pipe[0]);
- close(lp_pipe[1]);
- close(pu_pipe[0]);
- close(pu_pipe[1]);
- close(pe_pipe[0]);
- close(pe_pipe[1]);
-
- argv[i++] = "pack-objects";
- argv[i++] = "--stdout";
- if (!no_progress)
- argv[i++] = "--progress";
- if (use_ofs_delta)
- argv[i++] = "--delta-base-offset";
- argv[i++] = NULL;
-
- execv_git_cmd(argv);
- kill(pid_rev_list, SIGKILL);
- die("git-upload-pack: unable to exec git-pack-objects");
- }
-
- close(lp_pipe[0]);
- close(lp_pipe[1]);
- /* We read from pe_pipe[0] to capture stderr output for
- * progress bar, and pu_pipe[0] to capture the pack data.
+ /* We read from pack_objects.err to capture stderr output for
+ * progress bar, and pack_objects.out to capture the pack data.
*/
- close(pe_pipe[1]);
- close(pu_pipe[1]);
while (1) {
- const char *who;
struct pollfd pfd[2];
- pid_t pid;
- int status;
- ssize_t sz;
int pe, pu, pollsize;
reset_timeout();
@@ -214,136 +186,104 @@ static void create_pack_file(void)
pollsize = 0;
pe = pu = -1;
- if (0 <= pu_pipe[0]) {
- pfd[pollsize].fd = pu_pipe[0];
+ if (0 <= pack_objects.out) {
+ pfd[pollsize].fd = pack_objects.out;
pfd[pollsize].events = POLLIN;
pu = pollsize;
pollsize++;
}
- if (0 <= pe_pipe[0]) {
- pfd[pollsize].fd = pe_pipe[0];
+ if (0 <= pack_objects.err) {
+ pfd[pollsize].fd = pack_objects.err;
pfd[pollsize].events = POLLIN;
pe = pollsize;
pollsize++;
}
- if (pollsize) {
- if (poll(pfd, pollsize, -1) < 0) {
- if (errno != EINTR) {
- error("poll failed, resuming: %s",
- strerror(errno));
- sleep(1);
- }
- continue;
- }
- if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
- /* Data ready; we keep the last byte
- * to ourselves in case we detect
- * broken rev-list, so that we can
- * leave the stream corrupted. This
- * is unfortunate -- unpack-objects
- * would happily accept a valid pack
- * data with trailing garbage, so
- * appending garbage after we pass all
- * the pack data is not good enough to
- * signal breakage to downstream.
- */
- char *cp = data;
- ssize_t outsz = 0;
- if (0 <= buffered) {
- *cp++ = buffered;
- outsz++;
- }
- sz = xread(pu_pipe[0], cp,
- sizeof(data) - outsz);
- if (0 < sz)
- ;
- else if (sz == 0) {
- close(pu_pipe[0]);
- pu_pipe[0] = -1;
- }
- else
- goto fail;
- sz += outsz;
- if (1 < sz) {
- buffered = data[sz-1] & 0xFF;
- sz--;
- }
- else
- buffered = -1;
- sz = send_client_data(1, data, sz);
- if (sz < 0)
- goto fail;
- }
- if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
- /* Status ready; we ship that in the side-band
- * or dump to the standard error.
- */
- sz = xread(pe_pipe[0], progress,
- sizeof(progress));
- if (0 < sz)
- send_client_data(2, progress, sz);
- else if (sz == 0) {
- close(pe_pipe[0]);
- pe_pipe[0] = -1;
- }
- else
- goto fail;
+ if (!pollsize)
+ break;
+
+ if (poll(pfd, pollsize, -1) < 0) {
+ if (errno != EINTR) {
+ error("poll failed, resuming: %s",
+ strerror(errno));
+ sleep(1);
}
+ continue;
}
-
- /* See if the children are still there */
- if (pid_rev_list || pid_pack_objects) {
- pid = waitpid(-1, &status, WNOHANG);
- if (!pid)
- continue;
- who = ((pid == pid_rev_list) ? "git-rev-list" :
- (pid == pid_pack_objects) ? "git-pack-objects" :
- NULL);
- if (!who) {
- if (pid < 0) {
- error("git-upload-pack: %s",
- strerror(errno));
- goto fail;
- }
- error("git-upload-pack: we weren't "
- "waiting for %d", pid);
- continue;
+ if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
+ /* Data ready; we keep the last byte to ourselves
+ * in case we detect broken rev-list, so that we
+ * can leave the stream corrupted. This is
+ * unfortunate -- unpack-objects would happily
+ * accept a valid packdata with trailing garbage,
+ * so appending garbage after we pass all the
+ * pack data is not good enough to signal
+ * breakage to downstream.
+ */
+ char *cp = data;
+ ssize_t outsz = 0;
+ if (0 <= buffered) {
+ *cp++ = buffered;
+ outsz++;
}
- if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) {
- error("git-upload-pack: %s died with error.",
- who);
+ sz = xread(pack_objects.out, cp,
+ sizeof(data) - outsz);
+ if (0 < sz)
+ ;
+ else if (sz == 0) {
+ close(pack_objects.out);
+ pack_objects.out = -1;
+ }
+ else
goto fail;
+ sz += outsz;
+ if (1 < sz) {
+ buffered = data[sz-1] & 0xFF;
+ sz--;
}
- if (pid == pid_rev_list)
- pid_rev_list = 0;
- if (pid == pid_pack_objects)
- pid_pack_objects = 0;
- if (pid_rev_list || pid_pack_objects)
- continue;
- }
-
- /* both died happily */
- if (pollsize)
- continue;
-
- /* flush the data */
- if (0 <= buffered) {
- data[0] = buffered;
- sz = send_client_data(1, data, 1);
+ else
+ buffered = -1;
+ sz = send_client_data(1, data, sz);
if (sz < 0)
goto fail;
- fprintf(stderr, "flushed.\n");
}
- if (use_sideband)
- packet_flush(1);
- return;
+ if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
+ /* Status ready; we ship that in the side-band
+ * or dump to the standard error.
+ */
+ sz = xread(pack_objects.err, progress,
+ sizeof(progress));
+ if (0 < sz)
+ send_client_data(2, progress, sz);
+ else if (sz == 0) {
+ close(pack_objects.err);
+ pack_objects.err = -1;
+ }
+ else
+ goto fail;
+ }
+ }
+
+ if (finish_command(&pack_objects)) {
+ error("git-upload-pack: git-pack-objects died with error.");
+ goto fail;
}
+ if (finish_async(&rev_list))
+ goto fail; /* error was already reported */
+
+ /* flush the data */
+ if (0 <= buffered) {
+ data[0] = buffered;
+ sz = send_client_data(1, data, 1);
+ if (sz < 0)
+ goto fail;
+ fprintf(stderr, "flushed.\n");
+ }
+ if (use_sideband)
+ packet_flush(1);
+ return;
+
fail:
- if (pid_pack_objects)
- kill(pid_pack_objects, SIGKILL);
- if (pid_rev_list)
- kill(pid_rev_list, SIGKILL);
send_client_data(3, abort_msg, sizeof(abort_msg));
die("git-upload-pack: %s", abort_msg);
}
@@ -594,7 +534,8 @@ static void receive_needs(void)
/* make sure the real parents are parsed */
unregister_shallow(object->sha1);
object->parsed = 0;
- parse_commit((struct commit *)object);
+ if (parse_commit((struct commit *)object))
+ die("invalid commit");
parents = ((struct commit *)object)->parents;
while (parents) {
add_object_array(&parents->item->object,
@@ -636,7 +577,8 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
}
if (o->type == OBJ_TAG) {
o = deref_tag(o, refname, 0);
- packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+ if (o)
+ packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
}
return 0;
}
@@ -681,6 +623,9 @@ int main(int argc, char **argv)
if (i != argc-1)
usage(upload_pack_usage);
+
+ setup_path(NULL);
+
dir = argv[i];
if (!enter_repo(dir, strict))
diff --git a/usage.c b/usage.c
index f5e652cc76..a5fc4ec5fa 100644
--- a/usage.c
+++ b/usage.c
@@ -7,9 +7,9 @@
static void report(const char *prefix, const char *err, va_list params)
{
- fputs(prefix, stderr);
- vfprintf(stderr, err, params);
- fputs("\n", stderr);
+ char msg[256];
+ vsnprintf(msg, sizeof(msg), err, params);
+ fprintf(stderr, "%s%s\n", prefix, msg);
}
static NORETURN void usage_builtin(const char *err)
diff --git a/utf8.c b/utf8.c
index 4efef6faf7..dc3735364f 100644
--- a/utf8.c
+++ b/utf8.c
@@ -3,15 +3,14 @@
/* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */
-typedef unsigned int ucs_char_t; /* assuming 32bit int */
-
struct interval {
int first;
int last;
};
/* auxiliary function for binary search in interval table */
-static int bisearch(ucs_char_t ucs, const struct interval *table, int max) {
+static int bisearch(ucs_char_t ucs, const struct interval *table, int max)
+{
int min = 0;
int mid;
@@ -152,61 +151,98 @@ static int git_wcwidth(ucs_char_t ch)
}
/*
- * This function returns the number of columns occupied by the character
- * pointed to by the variable start. The pointer is updated to point at
- * the next character. If it was not valid UTF-8, the pointer is set to NULL.
+ * Pick one ucs character starting from the location *start points at,
+ * and return it, while updating the *start pointer to point at the
+ * end of that character. When remainder_p is not NULL, the location
+ * holds the number of bytes remaining in the string that we are allowed
+ * to pick from. Otherwise we are allowed to pick up to the NUL that
+ * would eventually appear in the string. *remainder_p is also reduced
+ * by the number of bytes we have consumed.
+ *
+ * If the string was not a valid UTF-8, *start pointer is set to NULL
+ * and the return value is undefined.
*/
-int utf8_width(const char **start)
+ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p)
{
unsigned char *s = (unsigned char *)*start;
ucs_char_t ch;
+ size_t remainder, incr;
+
+ /*
+ * A caller that assumes NUL terminated text can choose
+ * not to bother with the remainder length. We will
+ * stop at the first NUL.
+ */
+ remainder = (remainder_p ? *remainder_p : 999);
- if (*s < 0x80) {
+ if (remainder < 1) {
+ goto invalid;
+ } else if (*s < 0x80) {
/* 0xxxxxxx */
ch = *s;
- *start += 1;
+ incr = 1;
} else if ((s[0] & 0xe0) == 0xc0) {
/* 110XXXXx 10xxxxxx */
- if ((s[1] & 0xc0) != 0x80 ||
- /* overlong? */
- (s[0] & 0xfe) == 0xc0)
+ if (remainder < 2 ||
+ (s[1] & 0xc0) != 0x80 ||
+ (s[0] & 0xfe) == 0xc0)
goto invalid;
ch = ((s[0] & 0x1f) << 6) | (s[1] & 0x3f);
- *start += 2;
+ incr = 2;
} else if ((s[0] & 0xf0) == 0xe0) {
/* 1110XXXX 10Xxxxxx 10xxxxxx */
- if ((s[1] & 0xc0) != 0x80 ||
- (s[2] & 0xc0) != 0x80 ||
- /* overlong? */
- (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) ||
- /* surrogate? */
- (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) ||
- /* U+FFFE or U+FFFF? */
- (s[0] == 0xef && s[1] == 0xbf &&
- (s[2] & 0xfe) == 0xbe))
+ if (remainder < 3 ||
+ (s[1] & 0xc0) != 0x80 ||
+ (s[2] & 0xc0) != 0x80 ||
+ /* overlong? */
+ (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) ||
+ /* surrogate? */
+ (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) ||
+ /* U+FFFE or U+FFFF? */
+ (s[0] == 0xef && s[1] == 0xbf &&
+ (s[2] & 0xfe) == 0xbe))
goto invalid;
ch = ((s[0] & 0x0f) << 12) |
((s[1] & 0x3f) << 6) | (s[2] & 0x3f);
- *start += 3;
+ incr = 3;
} else if ((s[0] & 0xf8) == 0xf0) {
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
- if ((s[1] & 0xc0) != 0x80 ||
- (s[2] & 0xc0) != 0x80 ||
- (s[3] & 0xc0) != 0x80 ||
- /* overlong? */
- (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) ||
- /* > U+10FFFF? */
- (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4)
+ if (remainder < 4 ||
+ (s[1] & 0xc0) != 0x80 ||
+ (s[2] & 0xc0) != 0x80 ||
+ (s[3] & 0xc0) != 0x80 ||
+ /* overlong? */
+ (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) ||
+ /* > U+10FFFF? */
+ (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4)
goto invalid;
ch = ((s[0] & 0x07) << 18) | ((s[1] & 0x3f) << 12) |
((s[2] & 0x3f) << 6) | (s[3] & 0x3f);
- *start += 4;
+ incr = 4;
} else {
invalid:
*start = NULL;
return 0;
}
+ *start += incr;
+ if (remainder_p)
+ *remainder_p = remainder - incr;
+ return ch;
+}
+
+/*
+ * This function returns the number of columns occupied by the character
+ * pointed to by the variable start. The pointer is updated to point at
+ * the next character. When remainder_p is not NULL, it points at the
+ * location that stores the number of remaining bytes we can use to pick
+ * a character (see pick_one_utf8_char() above).
+ */
+int utf8_width(const char **start, size_t *remainder_p)
+{
+ ucs_char_t ch = pick_one_utf8_char(start, remainder_p);
+ if (!*start)
+ return 0;
return git_wcwidth(ch);
}
@@ -217,7 +253,7 @@ int is_utf8(const char *text)
text++;
continue;
}
- utf8_width(&text);
+ utf8_width(&text, NULL);
if (!text)
return 0;
}
@@ -277,13 +313,12 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width)
continue;
}
if (assume_utf8)
- w += utf8_width(&text);
+ w += utf8_width(&text, NULL);
else {
w++;
text++;
}
}
- return w;
}
int is_encoding_utf8(const char *name)
diff --git a/utf8.h b/utf8.h
index 15db6f1f27..98cce1b038 100644
--- a/utf8.h
+++ b/utf8.h
@@ -1,7 +1,10 @@
#ifndef GIT_UTF8_H
#define GIT_UTF8_H
-int utf8_width(const char **start);
+typedef unsigned int ucs_char_t; /* assuming 32bit int */
+
+ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p);
+int utf8_width(const char **start, size_t *remainder_p);
int is_utf8(const char *text);
int is_encoding_utf8(const char *name);
diff --git a/var.c b/var.c
index 4127031910..0de0efa2aa 100644
--- a/var.c
+++ b/var.c
@@ -21,7 +21,7 @@ static void list_vars(void)
{
struct git_var *ptr;
for(ptr = git_vars; ptr->read; ptr++) {
- printf("%s=%s\n", ptr->name, ptr->read(0));
+ printf("%s=%s\n", ptr->name, ptr->read(IDENT_WARN_ON_NO_NAME));
}
}
@@ -32,7 +32,7 @@ static const char *read_var(const char *var)
val = NULL;
for(ptr = git_vars; ptr->read; ptr++) {
if (strcmp(var, ptr->name) == 0) {
- val = ptr->read(1);
+ val = ptr->read(IDENT_ERROR_ON_NO_NAME);
break;
}
}
diff --git a/fetch.c b/walker.c
index b1c1f07b2a..adc3e80ce1 100644
--- a/fetch.c
+++ b/walker.c
@@ -1,5 +1,5 @@
#include "cache.h"
-#include "fetch.h"
+#include "walker.h"
#include "commit.h"
#include "tree.h"
#include "tree-walk.h"
@@ -7,16 +7,11 @@
#include "blob.h"
#include "refs.h"
-int get_tree = 0;
-int get_history = 0;
-int get_all = 0;
-int get_verbosely = 0;
-int get_recover = 0;
static unsigned char current_commit_sha1[20];
-void pull_say(const char *fmt, const char *hex)
+void walker_say(struct walker *walker, const char *fmt, const char *hex)
{
- if (get_verbosely)
+ if (walker->get_verbosely)
fprintf(stderr, fmt, hex);
}
@@ -31,9 +26,9 @@ static void report_missing(const struct object *obj)
sha1_to_hex(current_commit_sha1));
}
-static int process(struct object *obj);
+static int process(struct walker *walker, struct object *obj);
-static int process_tree(struct tree *tree)
+static int process_tree(struct walker *walker, struct tree *tree)
{
struct tree_desc desc;
struct name_entry entry;
@@ -58,7 +53,7 @@ static int process_tree(struct tree *tree)
if (blob)
obj = &blob->object;
}
- if (!obj || process(obj))
+ if (!obj || process(walker, obj))
return -1;
}
free(tree->buffer);
@@ -73,7 +68,7 @@ static int process_tree(struct tree *tree)
static struct commit_list *complete = NULL;
-static int process_commit(struct commit *commit)
+static int process_commit(struct walker *walker, struct commit *commit)
{
if (parse_commit(commit))
return -1;
@@ -87,43 +82,43 @@ static int process_commit(struct commit *commit)
hashcpy(current_commit_sha1, commit->object.sha1);
- pull_say("walk %s\n", sha1_to_hex(commit->object.sha1));
+ walker_say(walker, "walk %s\n", sha1_to_hex(commit->object.sha1));
- if (get_tree) {
- if (process(&commit->tree->object))
+ if (walker->get_tree) {
+ if (process(walker, &commit->tree->object))
return -1;
- if (!get_all)
- get_tree = 0;
+ if (!walker->get_all)
+ walker->get_tree = 0;
}
- if (get_history) {
+ if (walker->get_history) {
struct commit_list *parents = commit->parents;
for (; parents; parents = parents->next) {
- if (process(&parents->item->object))
+ if (process(walker, &parents->item->object))
return -1;
}
}
return 0;
}
-static int process_tag(struct tag *tag)
+static int process_tag(struct walker *walker, struct tag *tag)
{
if (parse_tag(tag))
return -1;
- return process(tag->tagged);
+ return process(walker, tag->tagged);
}
static struct object_list *process_queue = NULL;
static struct object_list **process_queue_end = &process_queue;
-static int process_object(struct object *obj)
+static int process_object(struct walker *walker, struct object *obj)
{
if (obj->type == OBJ_COMMIT) {
- if (process_commit((struct commit *)obj))
+ if (process_commit(walker, (struct commit *)obj))
return -1;
return 0;
}
if (obj->type == OBJ_TREE) {
- if (process_tree((struct tree *)obj))
+ if (process_tree(walker, (struct tree *)obj))
return -1;
return 0;
}
@@ -131,7 +126,7 @@ static int process_object(struct object *obj)
return 0;
}
if (obj->type == OBJ_TAG) {
- if (process_tag((struct tag *)obj))
+ if (process_tag(walker, (struct tag *)obj))
return -1;
return 0;
}
@@ -140,7 +135,7 @@ static int process_object(struct object *obj)
typename(obj->type), sha1_to_hex(obj->sha1));
}
-static int process(struct object *obj)
+static int process(struct walker *walker, struct object *obj)
{
if (obj->flags & SEEN)
return 0;
@@ -153,7 +148,7 @@ static int process(struct object *obj)
else {
if (obj->flags & COMPLETE)
return 0;
- prefetch(obj->sha1);
+ walker->prefetch(walker, obj->sha1);
}
object_list_insert(obj, process_queue_end);
@@ -161,7 +156,7 @@ static int process(struct object *obj)
return 0;
}
-static int loop(void)
+static int loop(struct walker *walker)
{
struct object_list *elem;
@@ -177,25 +172,25 @@ static int loop(void)
* the queue because we needed to fetch it first.
*/
if (! (obj->flags & TO_SCAN)) {
- if (fetch(obj->sha1)) {
+ if (walker->fetch(walker, obj->sha1)) {
report_missing(obj);
return -1;
}
}
if (!obj->type)
parse_object(obj->sha1);
- if (process_object(obj))
+ if (process_object(walker, obj))
return -1;
}
return 0;
}
-static int interpret_target(char *target, unsigned char *sha1)
+static int interpret_target(struct walker *walker, char *target, unsigned char *sha1)
{
if (!get_sha1_hex(target, sha1))
return 0;
if (!check_ref_format(target)) {
- if (!fetch_ref(target, sha1)) {
+ if (!walker->fetch_ref(walker, target, sha1)) {
return 0;
}
}
@@ -212,7 +207,7 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag,
return 0;
}
-int pull_targets_stdin(char ***target, const char ***write_ref)
+int walker_targets_stdin(char ***target, const char ***write_ref)
{
int targets = 0, targets_alloc = 0;
struct strbuf buf;
@@ -242,7 +237,7 @@ int pull_targets_stdin(char ***target, const char ***write_ref)
return targets;
}
-void pull_targets_free(int targets, char **target, const char **write_ref)
+void walker_targets_free(int targets, char **target, const char **write_ref)
{
while (targets--) {
free(target[targets]);
@@ -251,8 +246,8 @@ void pull_targets_free(int targets, char **target, const char **write_ref)
}
}
-int pull(int targets, char **target, const char **write_ref,
- const char *write_ref_log_details)
+int walker_fetch(struct walker *walker, int targets, char **target,
+ const char **write_ref, const char *write_ref_log_details)
{
struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));
unsigned char *sha1 = xmalloc(targets * 20);
@@ -274,19 +269,19 @@ int pull(int targets, char **target, const char **write_ref,
}
}
- if (!get_recover)
+ if (!walker->get_recover)
for_each_ref(mark_complete, NULL);
for (i = 0; i < targets; i++) {
- if (interpret_target(target[i], &sha1[20 * i])) {
- error("Could not interpret %s as something to pull", target[i]);
+ if (interpret_target(walker, target[i], &sha1[20 * i])) {
+ error("Could not interpret response from server '%s' as something to pull", target[i]);
goto unlock_and_fail;
}
- if (process(lookup_unknown_object(&sha1[20 * i])))
+ if (process(walker, lookup_unknown_object(&sha1[20 * i])))
goto unlock_and_fail;
}
- if (loop())
+ if (loop(walker))
goto unlock_and_fail;
if (write_ref_log_details) {
@@ -307,10 +302,16 @@ int pull(int targets, char **target, const char **write_ref,
return 0;
-
unlock_and_fail:
for (i = 0; i < targets; i++)
if (lock[i])
unlock_ref(lock[i]);
+
return -1;
}
+
+void walker_free(struct walker *walker)
+{
+ walker->cleanup(walker);
+ free(walker);
+}
diff --git a/walker.h b/walker.h
new file mode 100644
index 0000000000..ea2c363f4e
--- /dev/null
+++ b/walker.h
@@ -0,0 +1,37 @@
+#ifndef WALKER_H
+#define WALKER_H
+
+struct walker {
+ void *data;
+ int (*fetch_ref)(struct walker *, char *ref, unsigned char *sha1);
+ void (*prefetch)(struct walker *, unsigned char *sha1);
+ int (*fetch)(struct walker *, unsigned char *sha1);
+ void (*cleanup)(struct walker *);
+ int get_tree;
+ int get_history;
+ int get_all;
+ int get_verbosely;
+ int get_recover;
+
+ int corrupt_object_found;
+};
+
+/* Report what we got under get_verbosely */
+void walker_say(struct walker *walker, const char *, const char *);
+
+/* Load pull targets from stdin */
+int walker_targets_stdin(char ***target, const char ***write_ref);
+
+/* Free up loaded targets */
+void walker_targets_free(int targets, char **target, const char **write_ref);
+
+/* If write_ref is set, the ref filename to write the target value to. */
+/* If write_ref_log_details is set, additional text will appear in the ref log. */
+int walker_fetch(struct walker *impl, int targets, char **target,
+ const char **write_ref, const char *write_ref_log_details);
+
+void walker_free(struct walker *walker);
+
+struct walker *get_http_walker(const char *url);
+
+#endif /* WALKER_H */
diff --git a/ws.c b/ws.c
new file mode 100644
index 0000000000..d09b9df89a
--- /dev/null
+++ b/ws.c
@@ -0,0 +1,203 @@
+/*
+ * Whitespace rules
+ *
+ * Copyright (c) 2007 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "attr.h"
+
+static struct whitespace_rule {
+ const char *rule_name;
+ unsigned rule_bits;
+} whitespace_rule_names[] = {
+ { "trailing-space", WS_TRAILING_SPACE },
+ { "space-before-tab", WS_SPACE_BEFORE_TAB },
+ { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB },
+};
+
+unsigned parse_whitespace_rule(const char *string)
+{
+ unsigned rule = WS_DEFAULT_RULE;
+
+ while (string) {
+ int i;
+ size_t len;
+ const char *ep;
+ int negated = 0;
+
+ string = string + strspn(string, ", \t\n\r");
+ ep = strchr(string, ',');
+ if (!ep)
+ len = strlen(string);
+ else
+ len = ep - string;
+
+ if (*string == '-') {
+ negated = 1;
+ string++;
+ len--;
+ }
+ if (!len)
+ break;
+ for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) {
+ if (strncmp(whitespace_rule_names[i].rule_name,
+ string, len))
+ continue;
+ if (negated)
+ rule &= ~whitespace_rule_names[i].rule_bits;
+ else
+ rule |= whitespace_rule_names[i].rule_bits;
+ break;
+ }
+ string = ep;
+ }
+ return rule;
+}
+
+static void setup_whitespace_attr_check(struct git_attr_check *check)
+{
+ static struct git_attr *attr_whitespace;
+
+ if (!attr_whitespace)
+ attr_whitespace = git_attr("whitespace", 10);
+ check[0].attr = attr_whitespace;
+}
+
+unsigned whitespace_rule(const char *pathname)
+{
+ struct git_attr_check attr_whitespace_rule;
+
+ setup_whitespace_attr_check(&attr_whitespace_rule);
+ if (!git_checkattr(pathname, 1, &attr_whitespace_rule)) {
+ const char *value;
+
+ value = attr_whitespace_rule.value;
+ if (ATTR_TRUE(value)) {
+ /* true (whitespace) */
+ unsigned all_rule = 0;
+ int i;
+ for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
+ all_rule |= whitespace_rule_names[i].rule_bits;
+ return all_rule;
+ } else if (ATTR_FALSE(value)) {
+ /* false (-whitespace) */
+ return 0;
+ } else if (ATTR_UNSET(value)) {
+ /* reset to default (!whitespace) */
+ return whitespace_rule_cfg;
+ } else {
+ /* string */
+ return parse_whitespace_rule(value);
+ }
+ } else {
+ return whitespace_rule_cfg;
+ }
+}
+
+/* The returned string should be freed by the caller. */
+char *whitespace_error_string(unsigned ws)
+{
+ struct strbuf err;
+ strbuf_init(&err, 0);
+ if (ws & WS_TRAILING_SPACE)
+ strbuf_addstr(&err, "trailing whitespace");
+ if (ws & WS_SPACE_BEFORE_TAB) {
+ if (err.len)
+ strbuf_addstr(&err, ", ");
+ strbuf_addstr(&err, "space before tab in indent");
+ }
+ if (ws & WS_INDENT_WITH_NON_TAB) {
+ if (err.len)
+ strbuf_addstr(&err, ", ");
+ strbuf_addstr(&err, "indent with spaces");
+ }
+ return strbuf_detach(&err, NULL);
+}
+
+/* If stream is non-NULL, emits the line after checking. */
+unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
+ FILE *stream, const char *set,
+ const char *reset, const char *ws)
+{
+ unsigned result = 0;
+ int written = 0;
+ int trailing_whitespace = -1;
+ int trailing_newline = 0;
+ int i;
+
+ /* Logic is simpler if we temporarily ignore the trailing newline. */
+ if (len > 0 && line[len - 1] == '\n') {
+ trailing_newline = 1;
+ len--;
+ }
+
+ /* Check for trailing whitespace. */
+ if (ws_rule & WS_TRAILING_SPACE) {
+ for (i = len - 1; i >= 0; i--) {
+ if (isspace(line[i])) {
+ trailing_whitespace = i;
+ result |= WS_TRAILING_SPACE;
+ }
+ else
+ break;
+ }
+ }
+
+ /* Check for space before tab in initial indent. */
+ for (i = 0; i < len; i++) {
+ if (line[i] == ' ')
+ continue;
+ if (line[i] != '\t')
+ break;
+ if ((ws_rule & WS_SPACE_BEFORE_TAB) && written < i) {
+ result |= WS_SPACE_BEFORE_TAB;
+ if (stream) {
+ fputs(ws, stream);
+ fwrite(line + written, i - written, 1, stream);
+ fputs(reset, stream);
+ }
+ } else if (stream)
+ fwrite(line + written, i - written, 1, stream);
+ if (stream)
+ fwrite(line + i, 1, 1, stream);
+ written = i + 1;
+ }
+
+ /* Check for indent using non-tab. */
+ if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= 8) {
+ result |= WS_INDENT_WITH_NON_TAB;
+ if (stream) {
+ fputs(ws, stream);
+ fwrite(line + written, i - written, 1, stream);
+ fputs(reset, stream);
+ }
+ written = i;
+ }
+
+ if (stream) {
+ /* 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) {
+ fputs(set, stream);
+ fwrite(line + written,
+ trailing_whitespace - written, 1, stream);
+ fputs(reset, stream);
+ }
+
+ /* Highlight errors in trailing whitespace. */
+ if (trailing_whitespace != len) {
+ fputs(ws, stream);
+ fwrite(line + trailing_whitespace,
+ len - trailing_whitespace, 1, stream);
+ fputs(reset, stream);
+ }
+ if (trailing_newline)
+ fputc('\n', stream);
+ }
+ return result;
+}
diff --git a/wt-status.c b/wt-status.c
index 10ce6eedc7..32d780af1e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -8,7 +8,8 @@
#include "revision.h"
#include "diffcore.h"
-int wt_status_use_color = 0;
+int wt_status_relative_paths = 1;
+int wt_status_use_color = -1;
static char wt_status_colors[][COLOR_MAXLEN] = {
"", /* WT_STATUS_HEADER: normal */
"\033[32m", /* WT_STATUS_UPDATED: green */
@@ -22,7 +23,6 @@ static const char use_add_rm_msg[] =
"use \"git add/rm <file>...\" to update what will be committed";
static const char use_add_to_include_msg[] =
"use \"git add <file>...\" to include in what will be committed";
-static const char *excludes_file;
static int parse_status_slot(const char *var, int offset)
{
@@ -40,7 +40,7 @@ static int parse_status_slot(const char *var, int offset)
static const char* color(int slot)
{
- return wt_status_use_color ? wt_status_colors[slot] : "";
+ return wt_status_use_color > 0 ? wt_status_colors[slot] : "";
}
void wt_status_prepare(struct wt_status *s)
@@ -52,101 +52,126 @@ void wt_status_prepare(struct wt_status *s)
head = resolve_ref("HEAD", sha1, 0, NULL);
s->branch = head ? xstrdup(head) : NULL;
s->reference = "HEAD";
+ s->fp = stdout;
+ s->index_file = get_index_file();
}
-static void wt_status_print_cached_header(const char *reference)
+static void wt_status_print_cached_header(struct wt_status *s)
{
const char *c = color(WT_STATUS_HEADER);
- color_printf_ln(c, "# Changes to be committed:");
- if (reference) {
- color_printf_ln(c, "# (use \"git reset %s <file>...\" to unstage)", reference);
+ color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+ if (!s->is_initial) {
+ color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
} else {
- color_printf_ln(c, "# (use \"git rm --cached <file>...\" to unstage)");
+ color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)");
}
- color_printf_ln(c, "#");
+ color_fprintf_ln(s->fp, c, "#");
}
-static void wt_status_print_header(const char *main, const char *sub)
+static void wt_status_print_header(struct wt_status *s,
+ const char *main, const char *sub)
{
const char *c = color(WT_STATUS_HEADER);
- color_printf_ln(c, "# %s:", main);
- color_printf_ln(c, "# (%s)", sub);
- color_printf_ln(c, "#");
+ color_fprintf_ln(s->fp, c, "# %s:", main);
+ color_fprintf_ln(s->fp, c, "# (%s)", sub);
+ color_fprintf_ln(s->fp, c, "#");
}
-static void wt_status_print_trailer(void)
+static void wt_status_print_trailer(struct wt_status *s)
{
- color_printf_ln(color(WT_STATUS_HEADER), "#");
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
}
-static const char *quote_crlf(const char *in, char *buf, size_t sz)
+static char *quote_path(const char *in, int len,
+ struct strbuf *out, const char *prefix)
{
- const char *scan;
- char *out;
- const char *ret = in;
+ if (len < 0)
+ len = strlen(in);
+
+ strbuf_grow(out, len);
+ strbuf_setlen(out, 0);
+ if (prefix) {
+ int off = 0;
+ while (prefix[off] && off < len && prefix[off] == in[off])
+ if (prefix[off] == '/') {
+ prefix += off + 1;
+ in += off + 1;
+ len -= off + 1;
+ off = 0;
+ } else
+ off++;
+
+ for (; *prefix; prefix++)
+ if (*prefix == '/')
+ strbuf_addstr(out, "../");
+ }
- for (scan = in, out = buf; *scan; scan++) {
- int ch = *scan;
- int quoted;
+ for ( ; len > 0; in++, len--) {
+ int ch = *in;
switch (ch) {
case '\n':
- quoted = 'n';
+ strbuf_addstr(out, "\\n");
break;
case '\r':
- quoted = 'r';
+ strbuf_addstr(out, "\\r");
break;
default:
- *out++ = ch;
+ strbuf_addch(out, ch);
continue;
}
- *out++ = '\\';
- *out++ = quoted;
- ret = buf;
}
- *out = '\0';
- return ret;
+
+ if (!out->len)
+ strbuf_addstr(out, "./");
+
+ return out->buf;
}
-static void wt_status_print_filepair(int t, struct diff_filepair *p)
+static void wt_status_print_filepair(struct wt_status *s,
+ int t, struct diff_filepair *p)
{
const char *c = color(t);
const char *one, *two;
- char onebuf[PATH_MAX], twobuf[PATH_MAX];
+ struct strbuf onebuf, twobuf;
- one = quote_crlf(p->one->path, onebuf, sizeof(onebuf));
- two = quote_crlf(p->two->path, twobuf, sizeof(twobuf));
+ strbuf_init(&onebuf, 0);
+ strbuf_init(&twobuf, 0);
+ one = quote_path(p->one->path, -1, &onebuf, s->prefix);
+ two = quote_path(p->two->path, -1, &twobuf, s->prefix);
- color_printf(color(WT_STATUS_HEADER), "#\t");
+ color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
switch (p->status) {
case DIFF_STATUS_ADDED:
- color_printf(c, "new file: %s", one);
+ color_fprintf(s->fp, c, "new file: %s", one);
break;
case DIFF_STATUS_COPIED:
- color_printf(c, "copied: %s -> %s", one, two);
+ color_fprintf(s->fp, c, "copied: %s -> %s", one, two);
break;
case DIFF_STATUS_DELETED:
- color_printf(c, "deleted: %s", one);
+ color_fprintf(s->fp, c, "deleted: %s", one);
break;
case DIFF_STATUS_MODIFIED:
- color_printf(c, "modified: %s", one);
+ color_fprintf(s->fp, c, "modified: %s", one);
break;
case DIFF_STATUS_RENAMED:
- color_printf(c, "renamed: %s -> %s", one, two);
+ color_fprintf(s->fp, c, "renamed: %s -> %s", one, two);
break;
case DIFF_STATUS_TYPE_CHANGED:
- color_printf(c, "typechange: %s", one);
+ color_fprintf(s->fp, c, "typechange: %s", one);
break;
case DIFF_STATUS_UNKNOWN:
- color_printf(c, "unknown: %s", one);
+ color_fprintf(s->fp, c, "unknown: %s", one);
break;
case DIFF_STATUS_UNMERGED:
- color_printf(c, "unmerged: %s", one);
+ color_fprintf(s->fp, c, "unmerged: %s", one);
break;
default:
die("bug: unhandled diff status %c", p->status);
}
- printf("\n");
+ fprintf(s->fp, "\n");
+ strbuf_release(&onebuf);
+ strbuf_release(&twobuf);
}
static void wt_status_print_updated_cb(struct diff_queue_struct *q,
@@ -160,14 +185,14 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q,
if (q->queue[i]->status == 'U')
continue;
if (!shown_header) {
- wt_status_print_cached_header(s->reference);
+ wt_status_print_cached_header(s);
s->commitable = 1;
shown_header = 1;
}
- wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]);
+ wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]);
}
if (shown_header)
- wt_status_print_trailer();
+ wt_status_print_trailer(s);
}
static void wt_status_print_changed_cb(struct diff_queue_struct *q,
@@ -184,38 +209,33 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
msg = use_add_rm_msg;
break;
}
- wt_status_print_header("Changed but not updated", msg);
+ wt_status_print_header(s, "Changed but not updated", msg);
}
for (i = 0; i < q->nr; i++)
- wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
+ wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
if (q->nr)
- wt_status_print_trailer();
-}
-
-static void wt_read_cache(struct wt_status *s)
-{
- discard_cache();
- read_cache();
+ wt_status_print_trailer(s);
}
static void wt_status_print_initial(struct wt_status *s)
{
int i;
- char buf[PATH_MAX];
+ struct strbuf buf;
- wt_read_cache(s);
+ strbuf_init(&buf, 0);
if (active_nr) {
s->commitable = 1;
- wt_status_print_cached_header(NULL);
+ wt_status_print_cached_header(s);
}
for (i = 0; i < active_nr; i++) {
- color_printf(color(WT_STATUS_HEADER), "#\t");
- color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s",
- quote_crlf(active_cache[i]->name,
- buf, sizeof(buf)));
+ color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+ color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s",
+ quote_path(active_cache[i]->name, -1,
+ &buf, s->prefix));
}
if (active_nr)
- wt_status_print_trailer();
+ wt_status_print_trailer(s);
+ strbuf_release(&buf);
}
static void wt_status_print_updated(struct wt_status *s)
@@ -228,7 +248,7 @@ static void wt_status_print_updated(struct wt_status *s)
rev.diffopt.format_callback_data = s;
rev.diffopt.detect_rename = 1;
rev.diffopt.rename_limit = 100;
- wt_read_cache(s);
+ rev.diffopt.break_opt = 0;
run_diff_index(&rev, 1);
}
@@ -240,29 +260,24 @@ static void wt_status_print_changed(struct wt_status *s)
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = wt_status_print_changed_cb;
rev.diffopt.format_callback_data = s;
- wt_read_cache(s);
run_diff_files(&rev, 0);
}
static void wt_status_print_untracked(struct wt_status *s)
{
struct dir_struct dir;
- const char *x;
int i;
int shown_header = 0;
+ struct strbuf buf;
+ strbuf_init(&buf, 0);
memset(&dir, 0, sizeof(dir));
- dir.exclude_per_dir = ".gitignore";
if (!s->untracked) {
dir.show_other_directories = 1;
dir.hide_empty_directories = 1;
}
- x = git_path("info/exclude");
- if (file_exists(x))
- add_excludes_from_file(&dir, x);
- if (excludes_file && file_exists(excludes_file))
- add_excludes_from_file(&dir, excludes_file);
+ setup_standard_excludes(&dir);
read_directory(&dir, ".", "", 0, NULL);
for(i = 0; i < dir.nr; i++) {
@@ -282,25 +297,42 @@ static void wt_status_print_untracked(struct wt_status *s)
}
if (!shown_header) {
s->workdir_untracked = 1;
- wt_status_print_header("Untracked files",
+ wt_status_print_header(s, "Untracked files",
use_add_to_include_msg);
shown_header = 1;
}
- color_printf(color(WT_STATUS_HEADER), "#\t");
- color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s",
- ent->len, ent->name);
+ color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+ color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s",
+ quote_path(ent->name, ent->len,
+ &buf, s->prefix));
}
+ strbuf_release(&buf);
}
static void wt_status_print_verbose(struct wt_status *s)
{
struct rev_info rev;
+ int saved_stdout;
+
+ fflush(s->fp);
+
+ /* Sigh, the entire diff machinery is hardcoded to output to
+ * stdout. Do the dup-dance...*/
+ saved_stdout = dup(STDOUT_FILENO);
+ if (saved_stdout < 0 ||dup2(fileno(s->fp), STDOUT_FILENO) < 0)
+ die("couldn't redirect stdout\n");
+
init_revisions(&rev, NULL);
setup_revisions(0, NULL, &rev, s->reference);
rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
rev.diffopt.detect_rename = 1;
- wt_read_cache(s);
run_diff_index(&rev, 1);
+
+ fflush(stdout);
+
+ if (dup2(saved_stdout, STDOUT_FILENO) < 0)
+ die("couldn't restore stdout\n");
+ close(saved_stdout);
}
void wt_status_print(struct wt_status *s)
@@ -317,14 +349,14 @@ void wt_status_print(struct wt_status *s)
branch_name = "";
on_what = "Not currently on any branch.";
}
- color_printf_ln(color(WT_STATUS_HEADER),
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),
"# %s%s", on_what, branch_name);
}
if (s->is_initial) {
- color_printf_ln(color(WT_STATUS_HEADER), "#");
- color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit");
- color_printf_ln(color(WT_STATUS_HEADER), "#");
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit");
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
wt_status_print_initial(s);
}
else {
@@ -338,7 +370,9 @@ void wt_status_print(struct wt_status *s)
wt_status_print_verbose(s);
if (!s->commitable) {
if (s->amend)
- printf("# No changes\n");
+ fprintf(s->fp, "# No changes\n");
+ else if (s->nowarn)
+ ; /* nothing */
else if (s->workdir_dirty)
printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
else if (s->workdir_untracked)
@@ -353,18 +387,19 @@ void wt_status_print(struct wt_status *s)
int git_status_config(const char *k, const char *v)
{
if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
- wt_status_use_color = git_config_colorbool(k, v);
+ wt_status_use_color = git_config_colorbool(k, v, -1);
return 0;
}
if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
int slot = parse_status_slot(k, 13);
+ if (!v)
+ return config_error_nonbool(k);
color_parse(v, k, wt_status_colors[slot]);
+ return 0;
}
- if (!strcmp(k, "core.excludesfile")) {
- if (!v)
- die("core.excludesfile without value");
- excludes_file = xstrdup(v);
+ if (!strcmp(k, "status.relativepaths")) {
+ wt_status_relative_paths = git_config_bool(k, v);
return 0;
}
- return git_default_config(k, v);
+ return git_color_default_config(k, v);
}
diff --git a/wt-status.h b/wt-status.h
index cfea4ae688..02afaa60ee 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -1,6 +1,8 @@
#ifndef STATUS_H
#define STATUS_H
+#include <stdio.h>
+
enum color_wt_status {
WT_STATUS_HEADER,
WT_STATUS_UPDATED,
@@ -15,13 +17,19 @@ struct wt_status {
int verbose;
int amend;
int untracked;
+ int nowarn;
/* These are computed during processing of the individual sections */
int commitable;
int workdir_dirty;
int workdir_untracked;
+ const char *index_file;
+ FILE *fp;
+ const char *prefix;
};
int git_status_config(const char *var, const char *value);
+int wt_status_use_color;
+int wt_status_relative_paths;
void wt_status_prepare(struct wt_status *s);
void wt_status_print(struct wt_status *s);
diff --git a/xdiff-interface.c b/xdiff-interface.c
index be866d12d3..4b8e5cca80 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -103,6 +103,44 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
return 0;
}
+/*
+ * Trim down common substring at the end of the buffers,
+ * but leave at least ctx lines at the end.
+ */
+static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx)
+{
+ const int blk = 1024;
+ long trimmed = 0, recovered = 0;
+ char *ap = a->ptr + a->size;
+ char *bp = b->ptr + b->size;
+ long smaller = (a->size < b->size) ? a->size : b->size;
+
+ if (ctx)
+ return;
+
+ while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
+ trimmed += blk;
+ ap -= blk;
+ bp -= blk;
+ }
+
+ while (recovered < trimmed)
+ if (ap[recovered++] == '\n')
+ break;
+ a->size -= trimmed - recovered;
+ b->size -= trimmed - recovered;
+}
+
+int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *xecb)
+{
+ mmfile_t a = *mf1;
+ mmfile_t b = *mf2;
+
+ trim_common_tail(&a, &b, xecfg->ctxlen);
+
+ return xdl_diff(&a, &b, xpp, xecfg, xecb);
+}
+
int read_mmfile(mmfile_t *ptr, const char *filename)
{
struct stat st;
diff --git a/xdiff-interface.h b/xdiff-interface.h
index fb742dbb6b..f7f791d96b 100644
--- a/xdiff-interface.h
+++ b/xdiff-interface.h
@@ -13,6 +13,7 @@ struct xdiff_emit_state {
unsigned long remainder_size;
};
+int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb);
int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf);
int parse_hunk_header(char *line, int len,
int *ob, int *on,
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index 5cb7171a8f..1bad8462fb 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -257,8 +257,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
return ec;
}
}
-
- return -1;
}
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 2ade97b257..d7974d1a3e 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -232,8 +232,6 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
return i1 >= s1 && i2 >= s2;
} else
return s1 == s2 && !memcmp(l1, l2, s1);
-
- return 0;
}
static unsigned long xdl_hash_record_with_whitespace(char const **data,