summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore12
-rw-r--r--.mailmap8
-rw-r--r--Documentation/CodingGuidelines19
-rw-r--r--Documentation/Makefile63
-rw-r--r--Documentation/RelNotes/1.7.10.1.txt78
-rw-r--r--Documentation/RelNotes/1.7.10.2.txt85
-rw-r--r--Documentation/RelNotes/1.7.10.3.txt43
-rw-r--r--Documentation/RelNotes/1.7.10.4.txt29
-rw-r--r--Documentation/RelNotes/1.7.10.5.txt12
-rw-r--r--Documentation/RelNotes/1.7.10.txt219
-rw-r--r--Documentation/RelNotes/1.7.11.1.txt9
-rw-r--r--Documentation/RelNotes/1.7.11.txt139
-rw-r--r--Documentation/RelNotes/1.7.12.txt123
-rw-r--r--Documentation/RelNotes/1.7.7.1.txt60
-rw-r--r--Documentation/RelNotes/1.7.7.2.txt44
-rw-r--r--Documentation/RelNotes/1.7.7.3.txt19
-rw-r--r--Documentation/RelNotes/1.7.7.4.txt14
-rw-r--r--Documentation/RelNotes/1.7.7.5.txt14
-rw-r--r--Documentation/RelNotes/1.7.7.6.txt20
-rw-r--r--Documentation/RelNotes/1.7.7.7.txt13
-rw-r--r--Documentation/RelNotes/1.7.7.txt134
-rw-r--r--Documentation/RelNotes/1.7.8.1.txt38
-rw-r--r--Documentation/RelNotes/1.7.8.2.txt71
-rw-r--r--Documentation/RelNotes/1.7.8.3.txt16
-rw-r--r--Documentation/RelNotes/1.7.8.4.txt23
-rw-r--r--Documentation/RelNotes/1.7.8.5.txt19
-rw-r--r--Documentation/RelNotes/1.7.8.6.txt22
-rw-r--r--Documentation/RelNotes/1.7.8.txt161
-rw-r--r--Documentation/RelNotes/1.7.9.1.txt63
-rw-r--r--Documentation/RelNotes/1.7.9.2.txt69
-rw-r--r--Documentation/RelNotes/1.7.9.3.txt51
-rw-r--r--Documentation/RelNotes/1.7.9.4.txt24
-rw-r--r--Documentation/RelNotes/1.7.9.5.txt23
-rw-r--r--Documentation/RelNotes/1.7.9.6.txt12
-rw-r--r--Documentation/RelNotes/1.7.9.7.txt13
-rw-r--r--Documentation/RelNotes/1.7.9.txt112
-rw-r--r--Documentation/asciidoc.conf4
-rw-r--r--Documentation/blame-options.txt1
-rw-r--r--Documentation/config.txt301
-rw-r--r--Documentation/diff-config.txt4
-rw-r--r--Documentation/diff-generate-patch.txt2
-rw-r--r--Documentation/diff-options.txt48
-rw-r--r--Documentation/everyday.txt4
-rw-r--r--Documentation/git-am.txt8
-rw-r--r--Documentation/git-archive.txt43
-rw-r--r--Documentation/git-bisect.txt34
-rw-r--r--Documentation/git-blame.txt2
-rw-r--r--Documentation/git-branch.txt59
-rw-r--r--Documentation/git-bundle.txt8
-rw-r--r--Documentation/git-check-attr.txt26
-rw-r--r--Documentation/git-check-ref-format.txt57
-rw-r--r--Documentation/git-checkout.txt8
-rw-r--r--Documentation/git-cherry-pick.txt53
-rw-r--r--Documentation/git-clean.txt10
-rw-r--r--Documentation/git-clone.txt46
-rw-r--r--Documentation/git-column.txt53
-rw-r--r--Documentation/git-commit-tree.txt33
-rw-r--r--Documentation/git-commit.txt25
-rw-r--r--Documentation/git-config.txt47
-rw-r--r--Documentation/git-credential-cache--daemon.txt26
-rw-r--r--Documentation/git-credential-cache.txt77
-rw-r--r--Documentation/git-credential-store.txt75
-rw-r--r--Documentation/git-credential.txt144
-rw-r--r--Documentation/git-cvsserver.txt2
-rw-r--r--Documentation/git-daemon.txt20
-rw-r--r--Documentation/git-difftool.txt16
-rw-r--r--Documentation/git-fast-export.txt6
-rw-r--r--Documentation/git-fast-import.txt87
-rw-r--r--Documentation/git-fetch-pack.txt10
-rw-r--r--Documentation/git-filter-branch.txt34
-rw-r--r--Documentation/git-fmt-merge-msg.txt5
-rw-r--r--Documentation/git-for-each-ref.txt7
-rw-r--r--Documentation/git-format-patch.txt13
-rw-r--r--Documentation/git-fsck.txt40
-rw-r--r--Documentation/git-gc.txt2
-rw-r--r--Documentation/git-grep.txt67
-rw-r--r--Documentation/git-gui.txt16
-rw-r--r--Documentation/git-http-backend.txt8
-rw-r--r--Documentation/git-http-fetch.txt3
-rw-r--r--Documentation/git-index-pack.txt10
-rw-r--r--Documentation/git-instaweb.txt12
-rw-r--r--Documentation/git-log.txt16
-rw-r--r--Documentation/git-mailinfo.txt25
-rw-r--r--Documentation/git-merge-file.txt4
-rw-r--r--Documentation/git-merge.txt2
-rw-r--r--Documentation/git-mv.txt8
-rw-r--r--Documentation/git-notes.txt9
-rw-r--r--Documentation/git-p4.txt531
-rw-r--r--Documentation/git-pack-refs.txt2
-rw-r--r--Documentation/git-pull.txt4
-rw-r--r--Documentation/git-push.txt52
-rw-r--r--Documentation/git-read-tree.txt2
-rw-r--r--Documentation/git-rebase.txt66
-rw-r--r--Documentation/git-receive-pack.txt2
-rw-r--r--Documentation/git-reflog.txt6
-rw-r--r--Documentation/git-remote-fd.txt8
-rw-r--r--Documentation/git-remote-helpers.txt186
-rw-r--r--Documentation/git-remote-testgit.txt30
-rw-r--r--Documentation/git-remote.txt8
-rw-r--r--Documentation/git-repack.txt2
-rw-r--r--Documentation/git-rerere.txt25
-rw-r--r--Documentation/git-reset.txt10
-rw-r--r--Documentation/git-rev-parse.txt14
-rw-r--r--Documentation/git-revert.txt13
-rw-r--r--Documentation/git-rm.txt7
-rw-r--r--Documentation/git-send-email.txt4
-rw-r--r--Documentation/git-sh-i18n--envsubst.txt2
-rw-r--r--Documentation/git-sh-setup.txt10
-rw-r--r--Documentation/git-shortlog.txt2
-rw-r--r--Documentation/git-show-ref.txt6
-rw-r--r--Documentation/git-show.txt10
-rw-r--r--Documentation/git-stash.txt28
-rw-r--r--Documentation/git-status.txt13
-rw-r--r--Documentation/git-stripspace.txt69
-rw-r--r--Documentation/git-submodule.txt58
-rw-r--r--Documentation/git-svn.txt75
-rw-r--r--Documentation/git-symbolic-ref.txt16
-rw-r--r--Documentation/git-tag.txt38
-rw-r--r--Documentation/git-tar-tree.txt10
-rw-r--r--Documentation/git-update-index.txt6
-rw-r--r--Documentation/git-upload-pack.txt4
-rw-r--r--Documentation/git-var.txt21
-rw-r--r--Documentation/git-whatchanged.txt4
-rw-r--r--Documentation/git.txt79
-rw-r--r--Documentation/gitattributes.txt46
-rw-r--r--Documentation/gitcli.txt12
-rw-r--r--Documentation/gitcore-tutorial.txt34
-rw-r--r--Documentation/gitcredentials.txt183
-rw-r--r--Documentation/gitdiffcore.txt10
-rw-r--r--Documentation/githooks.txt8
-rw-r--r--Documentation/gitignore.txt4
-rw-r--r--Documentation/gitmodules.txt9
-rw-r--r--Documentation/gitnamespaces.txt82
-rw-r--r--Documentation/gitrepository-layout.txt58
-rw-r--r--Documentation/gittutorial-2.txt4
-rw-r--r--Documentation/gitweb.conf.txt896
-rw-r--r--Documentation/gitweb.txt704
-rw-r--r--Documentation/gitworkflows.txt4
-rw-r--r--Documentation/howto/maintain-git.txt13
-rw-r--r--Documentation/howto/using-merge-subtree.txt2
-rw-r--r--Documentation/howto/using-signed-tag-in-pull-request.txt217
-rwxr-xr-xDocumentation/install-doc-quick.sh44
-rw-r--r--Documentation/merge-options.txt38
-rw-r--r--Documentation/pretty-formats.txt10
-rw-r--r--Documentation/pull-fetch-param.txt2
-rw-r--r--Documentation/rev-list-options.txt19
-rw-r--r--Documentation/revisions.txt2
-rw-r--r--Documentation/sequencer.txt12
-rw-r--r--Documentation/technical/api-argv-array.txt51
-rw-r--r--Documentation/technical/api-config.txt140
-rw-r--r--Documentation/technical/api-credentials.txt268
-rw-r--r--Documentation/technical/api-gitattributes.txt61
-rw-r--r--Documentation/technical/api-parse-options.txt58
-rw-r--r--Documentation/technical/api-revision-walking.txt5
-rw-r--r--Documentation/technical/api-sha1-array.txt79
-rw-r--r--Documentation/technical/api-strbuf.txt18
-rw-r--r--Documentation/technical/api-string-list.txt14
-rw-r--r--Documentation/technical/index-format.txt13
-rw-r--r--Documentation/technical/pack-protocol.txt11
-rw-r--r--Documentation/technical/protocol-common.txt2
-rw-r--r--Documentation/user-manual.txt23
-rwxr-xr-xGIT-VERSION-GEN4
-rw-r--r--INSTALL78
-rw-r--r--Makefile502
-rw-r--r--README10
l---------RelNotes2
-rw-r--r--abspath.c32
-rw-r--r--advice.c58
-rw-r--r--advice.h7
-rw-r--r--archive-tar.c348
-rw-r--r--archive-zip.c214
-rw-r--r--archive.c140
-rw-r--r--archive.h31
-rw-r--r--argv-array.c61
-rw-r--r--argv-array.h21
-rw-r--r--attr.c249
-rw-r--r--attr.h20
-rw-r--r--bisect.c109
-rw-r--r--bisect.h7
-rw-r--r--branch.c118
-rw-r--r--branch.h26
-rw-r--r--builtin.h17
-rw-r--r--builtin/add.c12
-rw-r--r--builtin/apply.c506
-rw-r--r--builtin/archive.c53
-rw-r--r--builtin/bisect--helper.c7
-rw-r--r--builtin/blame.c104
-rw-r--r--builtin/branch.c264
-rw-r--r--builtin/bundle.c2
-rw-r--r--builtin/cat-file.c33
-rw-r--r--builtin/check-attr.c132
-rw-r--r--builtin/check-ref-format.c61
-rw-r--r--builtin/checkout.c240
-rw-r--r--builtin/clean.c5
-rw-r--r--builtin/clone.c411
-rw-r--r--builtin/column.c59
-rw-r--r--builtin/commit-tree.c95
-rw-r--r--builtin/commit.c449
-rw-r--r--builtin/config.c171
-rw-r--r--builtin/credential.c31
-rw-r--r--builtin/diff.c45
-rw-r--r--builtin/fast-export.c19
-rw-r--r--builtin/fetch-pack.c295
-rw-r--r--builtin/fetch.c307
-rw-r--r--builtin/fmt-merge-msg.c428
-rw-r--r--builtin/for-each-ref.c100
-rw-r--r--builtin/fsck.c100
-rw-r--r--builtin/gc.c89
-rw-r--r--builtin/grep.c307
-rw-r--r--builtin/help.c59
-rw-r--r--builtin/index-pack.c1006
-rw-r--r--builtin/init-db.c2
-rw-r--r--builtin/log.c173
-rw-r--r--builtin/ls-files.c61
-rw-r--r--builtin/ls-remote.c3
-rw-r--r--builtin/mailinfo.c11
-rw-r--r--builtin/merge-file.c4
-rw-r--r--builtin/merge.c576
-rw-r--r--builtin/mktree.c1
-rw-r--r--builtin/mv.c12
-rw-r--r--builtin/name-rev.c4
-rw-r--r--builtin/notes.c13
-rw-r--r--builtin/pack-objects.c1043
-rw-r--r--builtin/patch-id.c10
-rw-r--r--builtin/prune-packed.c4
-rw-r--r--builtin/prune.c15
-rw-r--r--builtin/push.c166
-rw-r--r--builtin/receive-pack.c297
-rw-r--r--builtin/reflog.c8
-rw-r--r--builtin/remote.c270
-rw-r--r--builtin/replace.c6
-rw-r--r--builtin/reset.c9
-rw-r--r--builtin/rev-list.c62
-rw-r--r--builtin/rev-parse.c14
-rw-r--r--builtin/revert.c687
-rw-r--r--builtin/send-pack.c45
-rw-r--r--builtin/show-branch.c15
-rw-r--r--builtin/show-ref.c4
-rw-r--r--builtin/stripspace.c2
-rw-r--r--builtin/symbolic-ref.c11
-rw-r--r--builtin/tag.c314
-rw-r--r--builtin/unpack-objects.c2
-rw-r--r--builtin/update-index.c20
-rw-r--r--builtin/update-server-info.c1
-rw-r--r--builtin/upload-archive.c45
-rw-r--r--builtin/var.c4
-rw-r--r--builtin/verify-pack.c132
-rw-r--r--builtin/verify-tag.c47
-rw-r--r--bulk-checkin.c275
-rw-r--r--bulk-checkin.h16
-rw-r--r--bundle.c186
-rw-r--r--bundle.h4
-rw-r--r--cache-tree.c38
-rw-r--r--cache-tree.h6
-rw-r--r--cache.h280
-rw-r--r--color.c46
-rw-r--r--color.h22
-rw-r--r--column.c434
-rw-r--r--column.h45
-rw-r--r--combine-diff.c117
-rw-r--r--command-list.txt3
-rw-r--r--commit.c408
-rw-r--r--commit.h55
-rw-r--r--compat/cygwin.c3
-rw-r--r--compat/inet_ntop.c8
-rw-r--r--compat/inet_pton.c6
-rw-r--r--compat/mingw.c64
-rw-r--r--compat/mingw.h25
-rw-r--r--compat/msvc.h1
-rw-r--r--compat/nedmalloc/Readme.txt2
-rw-r--r--compat/obstack.c413
-rw-r--r--compat/obstack.h506
-rw-r--r--compat/qsort.c2
-rw-r--r--compat/setenv.c10
-rw-r--r--compat/snprintf.c9
-rw-r--r--compat/strtoimax.c10
-rw-r--r--compat/terminal.c81
-rw-r--r--compat/terminal.h6
-rw-r--r--compat/vcbuild/include/arpa/inet.h1
-rw-r--r--compat/vcbuild/include/grp.h1
-rw-r--r--compat/vcbuild/include/inttypes.h1
-rw-r--r--compat/vcbuild/include/netdb.h1
-rw-r--r--compat/vcbuild/include/netinet/in.h1
-rw-r--r--compat/vcbuild/include/netinet/tcp.h1
-rw-r--r--compat/vcbuild/include/pwd.h1
-rw-r--r--compat/vcbuild/include/sys/ioctl.h1
-rw-r--r--compat/vcbuild/include/sys/select.h1
-rw-r--r--compat/vcbuild/include/sys/socket.h1
-rw-r--r--compat/vcbuild/include/sys/wait.h1
-rw-r--r--compat/vcbuild/include/termios.h1
-rw-r--r--compat/win32/poll.c (renamed from compat/win32/sys/poll.c)19
-rw-r--r--compat/win32/poll.h (renamed from compat/win32/sys/poll.h)0
-rw-r--r--compat/win32/pthread.h5
-rw-r--r--compat/win32/syslog.c30
-rw-r--r--compat/win32mmap.c2
-rw-r--r--config.c368
-rw-r--r--config.mak.in5
-rw-r--r--configure.ac254
-rw-r--r--connect.c141
-rw-r--r--connected.c62
-rw-r--r--connected.h20
-rw-r--r--[-rwxr-xr-x]contrib/completion/git-completion.bash708
-rw-r--r--contrib/completion/git-prompt.sh289
-rw-r--r--contrib/credential/osxkeychain/.gitignore1
-rw-r--r--contrib/credential/osxkeychain/Makefile17
-rw-r--r--contrib/credential/osxkeychain/git-credential-osxkeychain.c173
-rw-r--r--contrib/diff-highlight/README152
-rwxr-xr-xcontrib/diff-highlight/diff-highlight173
-rw-r--r--contrib/diffall/README31
-rwxr-xr-xcontrib/diffall/git-diffall257
-rw-r--r--contrib/emacs/git-blame.el75
-rw-r--r--contrib/examples/builtin-fetch--tool.c4
-rw-r--r--contrib/fast-import/git-p4.README12
-rw-r--r--contrib/fast-import/git-p4.bat1
-rw-r--r--contrib/fast-import/git-p4.txt251
-rw-r--r--contrib/git-jump/README92
-rwxr-xr-xcontrib/git-jump/git-jump69
-rwxr-xr-xcontrib/hooks/post-receive-email20
-rwxr-xr-xcontrib/mw-to-git/git-remote-mediawiki904
-rw-r--r--contrib/mw-to-git/git-remote-mediawiki.txt7
-rw-r--r--contrib/persistent-https/LICENSE202
-rw-r--r--contrib/persistent-https/Makefile38
-rw-r--r--contrib/persistent-https/README62
-rw-r--r--contrib/persistent-https/client.go189
-rw-r--r--contrib/persistent-https/main.go82
-rw-r--r--contrib/persistent-https/proxy.go190
-rw-r--r--contrib/persistent-https/socket.go97
-rwxr-xr-xcontrib/rerere-train.sh2
-rw-r--r--contrib/subtree/.gitignore5
-rw-r--r--contrib/subtree/COPYING339
-rw-r--r--contrib/subtree/INSTALL28
-rw-r--r--contrib/subtree/Makefile52
-rw-r--r--contrib/subtree/README8
-rwxr-xr-xcontrib/subtree/git-subtree.sh712
-rw-r--r--contrib/subtree/git-subtree.txt366
-rw-r--r--contrib/subtree/t/Makefile69
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh508
-rw-r--r--contrib/subtree/todo50
-rw-r--r--contrib/svn-fe/svn-fe.txt10
-rw-r--r--convert.c537
-rw-r--r--convert.h77
-rw-r--r--credential-cache--daemon.c269
-rw-r--r--credential-cache.c123
-rw-r--r--credential-store.c157
-rw-r--r--credential.c365
-rw-r--r--credential.h34
-rw-r--r--csum-file.c66
-rw-r--r--csum-file.h11
-rw-r--r--ctype.c38
-rw-r--r--daemon.c74
-rw-r--r--date.c125
-rw-r--r--diff-lib.c76
-rw-r--r--diff-no-index.c78
-rw-r--r--diff.c548
-rw-r--r--diff.h21
-rw-r--r--diffcore-pickaxe.c199
-rw-r--r--diffcore-rename.c6
-rw-r--r--diffcore.h2
-rw-r--r--dir.c427
-rw-r--r--dir.h22
-rw-r--r--entry.c73
-rw-r--r--environment.c51
-rw-r--r--exec_cmd.c2
-rw-r--r--fast-import.c307
-rw-r--r--fetch-pack.h1
-rw-r--r--fmt-merge-msg.h7
-rw-r--r--fsck.c12
-rwxr-xr-xgenerate-cmdlist.sh4
-rw-r--r--gettext.c117
-rw-r--r--gettext.h25
-rwxr-xr-xgit-add--interactive.perl27
-rwxr-xr-xgit-am.sh169
-rwxr-xr-xgit-bisect.sh408
-rw-r--r--git-compat-util.h45
-rwxr-xr-xgit-cvsexportcommit.perl7
-rwxr-xr-xgit-cvsserver.perl4
-rwxr-xr-xgit-difftool--helper.sh46
-rwxr-xr-xgit-difftool.perl392
-rwxr-xr-xgit-filter-branch.sh16
-rw-r--r--git-gui/.gitattributes1
-rwxr-xr-xgit-gui/GIT-VERSION-GEN2
-rwxr-xr-xgit-gui/git-gui.sh59
-rw-r--r--git-gui/lib/blame.tcl58
-rw-r--r--git-gui/lib/browser.tcl8
-rw-r--r--git-gui/lib/choose_rev.tcl5
-rw-r--r--git-gui/lib/class.tcl1
-rw-r--r--git-gui/lib/commit.tcl17
-rw-r--r--git-gui/lib/diff.tcl5
-rw-r--r--git-gui/lib/index.tcl21
-rw-r--r--git-gui/lib/line.tcl81
-rw-r--r--git-gui/lib/option.tcl20
-rw-r--r--git-gui/lib/search.tcl127
-rw-r--r--git-gui/lib/sshkey.tcl2
-rw-r--r--git-gui/lib/themed.tcl99
-rw-r--r--git-gui/lib/tools.tcl10
-rw-r--r--git-gui/lib/transport.tcl1
-rw-r--r--git-gui/po/README39
-rw-r--r--git-gui/po/sv.po2
-rwxr-xr-xgit-instaweb.sh84
-rwxr-xr-xgit-merge-one-file.sh2
-rw-r--r--git-mergetool--lib.sh403
-rwxr-xr-xgit-mergetool.sh12
-rwxr-xr-xgit-p4.py (renamed from contrib/fast-import/git-p4)1398
-rw-r--r--git-parse-remote.sh43
-rwxr-xr-xgit-pull.sh49
-rw-r--r--git-rebase--am.sh19
-rw-r--r--git-rebase--interactive.sh143
-rw-r--r--git-rebase--merge.sh11
-rwxr-xr-xgit-rebase.sh37
-rwxr-xr-xgit-relink.perl2
-rw-r--r--git-remote-testgit.py82
-rwxr-xr-xgit-repack.sh10
-rwxr-xr-xgit-request-pull.sh128
-rwxr-xr-xgit-send-email.perl33
-rw-r--r--git-sh-i18n.sh81
-rw-r--r--git-sh-setup.sh61
-rwxr-xr-xgit-stash.sh153
-rwxr-xr-xgit-submodule.sh359
-rwxr-xr-xgit-svn.perl2080
-rwxr-xr-xgit-web--browse.sh10
-rw-r--r--git.c43
-rw-r--r--git.spec.in9
-rw-r--r--git_remote_helpers/git/exporter.py15
-rw-r--r--git_remote_helpers/git/git.py2
-rw-r--r--git_remote_helpers/git/importer.py32
-rw-r--r--git_remote_helpers/git/non_local.py20
-rw-r--r--git_remote_helpers/git/repo.py7
-rw-r--r--git_remote_helpers/util.py81
-rwxr-xr-xgitk-git/gitk523
-rw-r--r--gitweb/INSTALL111
-rw-r--r--gitweb/Makefile8
-rw-r--r--gitweb/README434
-rwxr-xr-xgitweb/gitweb.perl1587
-rw-r--r--gitweb/static/gitweb.css45
-rw-r--r--gitweb/static/js/blame_incremental.js79
-rw-r--r--gitweb/static/js/lib/cookies.js2
-rw-r--r--gpg-interface.c140
-rw-r--r--gpg-interface.h10
-rw-r--r--graph.c5
-rw-r--r--grep.c475
-rw-r--r--grep.h63
-rw-r--r--help.c116
-rw-r--r--help.h4
-rw-r--r--hex.c10
-rw-r--r--http-backend.c24
-rw-r--r--http-fetch.c8
-rw-r--r--http-push.c30
-rw-r--r--http.c237
-rw-r--r--http.h7
-rw-r--r--ident.c331
-rw-r--r--imap-send.c40
-rw-r--r--kwset.c771
-rw-r--r--kwset.h63
-rw-r--r--list-objects.c37
-rw-r--r--list-objects.h5
-rw-r--r--ll-merge.c6
-rw-r--r--log-tree.c223
-rw-r--r--log-tree.h4
-rw-r--r--mailmap.c18
-rw-r--r--merge-recursive.c1113
-rw-r--r--merge-recursive.h1
-rw-r--r--mergesort.c73
-rw-r--r--mergesort.h17
-rw-r--r--mergetools/araxis20
-rw-r--r--mergetools/bc325
-rw-r--r--mergetools/defaults46
-rw-r--r--mergetools/deltawalker21
-rw-r--r--mergetools/diffuse17
-rw-r--r--mergetools/ecmerge16
-rw-r--r--mergetools/emerge23
-rw-r--r--mergetools/kdiff324
-rw-r--r--mergetools/kompare7
-rw-r--r--mergetools/meld32
-rw-r--r--mergetools/opendiff16
-rw-r--r--mergetools/p4merge10
-rw-r--r--mergetools/tkdiff12
-rw-r--r--mergetools/tortoisemerge17
-rw-r--r--mergetools/vim44
-rw-r--r--mergetools/xxdiff25
-rw-r--r--name-hash.c17
-rw-r--r--notes-cache.c5
-rw-r--r--notes-merge.c180
-rw-r--r--notes-merge.h2
-rw-r--r--object.c37
-rw-r--r--object.h2
-rw-r--r--pack-check.c28
-rw-r--r--pack-refs.c5
-rw-r--r--pack-write.c139
-rw-r--r--pack.h36
-rw-r--r--pager.c59
-rw-r--r--parse-options-cb.c130
-rw-r--r--parse-options.c166
-rw-r--r--parse-options.h51
-rw-r--r--path.c80
-rw-r--r--perl/Git.pm80
-rw-r--r--perl/Git/I18N.pm98
-rw-r--r--perl/Git/SVN/Editor.pm536
-rw-r--r--perl/Git/SVN/Fetcher.pm603
-rw-r--r--perl/Git/SVN/Memoize/YAML.pm93
-rw-r--r--perl/Git/SVN/Prompt.pm202
-rw-r--r--perl/Git/SVN/Ra.pm658
-rw-r--r--perl/Makefile45
-rw-r--r--perl/Makefile.PL23
-rw-r--r--pkt-line.c32
-rw-r--r--pkt-line.h1
-rw-r--r--po/.gitignore2
-rw-r--r--po/README292
-rw-r--r--po/TEAMS47
-rw-r--r--po/da.po3503
-rw-r--r--po/de.po5647
-rw-r--r--po/git.pot5213
-rw-r--r--po/is.po93
-rw-r--r--po/it.po5367
-rw-r--r--po/nl.po3493
-rw-r--r--po/pt_PT.po4980
-rw-r--r--po/sv.po5615
-rw-r--r--po/vi.po5570
-rw-r--r--po/zh_CN.po5471
-rw-r--r--pretty.c193
-rw-r--r--prompt.c72
-rw-r--r--prompt.h10
-rw-r--r--quote.c23
-rw-r--r--quote.h13
-rw-r--r--reachable.c55
-rw-r--r--reachable.h3
-rw-r--r--read-cache.c394
-rw-r--r--reflog-walk.c50
-rw-r--r--reflog-walk.h5
-rw-r--r--refs.c2008
-rw-r--r--refs.h82
-rw-r--r--remote-curl.c62
-rw-r--r--remote.c347
-rw-r--r--remote.h13
-rw-r--r--rerere.c12
-rw-r--r--revision.c252
-rw-r--r--revision.h28
-rw-r--r--run-command.c161
-rw-r--r--run-command.h2
-rw-r--r--sequencer.c1006
-rw-r--r--sequencer.h51
-rw-r--r--setup.c109
-rw-r--r--sh-i18n--envsubst.c1
-rw-r--r--sha1_file.c448
-rw-r--r--sha1_name.c117
-rw-r--r--shell.c2
-rw-r--r--show-index.c4
-rw-r--r--strbuf.c110
-rw-r--r--strbuf.h21
-rw-r--r--streaming.c546
-rw-r--r--streaming.h17
-rw-r--r--string-list.c9
-rw-r--r--string-list.h1
-rw-r--r--submodule.c206
-rw-r--r--submodule.h7
-rw-r--r--symlinks.c28
-rw-r--r--t/Makefile17
-rw-r--r--t/README111
-rw-r--r--t/gitweb-lib.sh3
-rwxr-xr-xt/harness21
-rw-r--r--t/lib-bash.sh18
-rwxr-xr-xt/lib-credential.sh289
-rw-r--r--t/lib-diff-alternative.sh165
-rw-r--r--t/lib-gettext.sh55
-rw-r--r--t/lib-git-daemon.sh69
-rw-r--r--t/lib-git-p4.sh73
-rwxr-xr-xt/lib-gpg.sh34
-rw-r--r--t/lib-gpg/pubring.gpg (renamed from t/t7004/pubring.gpg)bin1164 -> 1164 bytes
-rw-r--r--t/lib-gpg/random_seed (renamed from t/t7004/random_seed)bin600 -> 600 bytes
-rw-r--r--t/lib-gpg/secring.gpg (renamed from t/t7004/secring.gpg)bin1237 -> 1237 bytes
-rw-r--r--t/lib-gpg/trustdb.gpg (renamed from t/t7004/trustdb.gpg)bin1280 -> 1280 bytes
-rw-r--r--t/lib-httpd.sh12
-rw-r--r--t/lib-httpd/apache.conf10
-rw-r--r--t/perf/.gitignore2
-rw-r--r--t/perf/Makefile15
-rw-r--r--t/perf/README146
-rwxr-xr-xt/perf/aggregate.perl166
-rwxr-xr-xt/perf/min_time.perl21
-rwxr-xr-xt/perf/p0000-perf-lib-sanity.sh55
-rwxr-xr-xt/perf/p0001-rev-list.sh17
-rwxr-xr-xt/perf/p4000-diff-algorithms.sh29
-rwxr-xr-xt/perf/p5302-pack-index.sh40
-rwxr-xr-xt/perf/p7810-grep.sh23
-rw-r--r--t/perf/perf-lib.sh202
-rwxr-xr-xt/perf/run82
-rwxr-xr-xt/t0000-basic.sh563
-rwxr-xr-xt/t0003-attributes.sh172
-rwxr-xr-xt/t0021-conversion.sh88
-rwxr-xr-xt/t0023-crlf-am.sh2
-rwxr-xr-xt/t0040-parse-options.sh89
-rwxr-xr-xt/t0061-run-command.sh13
-rwxr-xr-xt/t0062-revision-walking.sh33
-rwxr-xr-xt/t0080-vcs-svn.sh117
-rwxr-xr-xt/t0090-cache-tree.sh93
-rwxr-xr-xt/t0200-gettext-basic.sh108
-rw-r--r--t/t0200/test.c23
-rw-r--r--t/t0200/test.perl14
-rw-r--r--t/t0200/test.sh14
-rwxr-xr-xt/t0201-gettext-fallbacks.sh20
-rwxr-xr-xt/t0202-gettext-perl.sh27
-rw-r--r--t/t0202/test.pl110
-rwxr-xr-xt/t0203-gettext-setlocale-sanity.sh26
-rwxr-xr-xt/t0204-gettext-reencode-sanity.sh87
-rwxr-xr-xt/t0205-gettext-poison.sh36
-rwxr-xr-xt/t0300-credentials.sh292
-rwxr-xr-xt/t0301-credential-cache.sh23
-rwxr-xr-xt/t0302-credential-store.sh9
-rwxr-xr-xt/t0303-credential-external.sh60
-rwxr-xr-xt/t1007-hash-object.sh2
-rwxr-xr-xt/t1010-mktree.sh4
-rwxr-xr-xt/t1011-read-tree-sparse-checkout.sh16
-rwxr-xr-xt/t1013-loose-object-format.sh66
-rw-r--r--t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6bin0 -> 117 bytes
-rw-r--r--t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435bin0 -> 17 bytes
-rw-r--r--t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbdbin0 -> 18 bytes
-rw-r--r--t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99bin0 -> 19 bytes
-rw-r--r--t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752ebin0 -> 10 bytes
-rw-r--r--t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696bin0 -> 181 bytes
-rw-r--r--t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fdbin0 -> 26 bytes
-rw-r--r--t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb2
-rw-r--r--t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09bin0 -> 139 bytes
-rw-r--r--t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8bin0 -> 54 bytes
-rw-r--r--t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730bin0 -> 13 bytes
-rw-r--r--t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fabin0 -> 156 bytes
-rw-r--r--t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64fbin0 -> 252 bytes
-rw-r--r--t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832bin0 -> 11 bytes
-rw-r--r--t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8bin0 -> 34 bytes
-rw-r--r--t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 9 bytes
-rw-r--r--t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a1
-rwxr-xr-xt/t1020-subdirectory.sh20
-rwxr-xr-xt/t1050-large.sh159
-rwxr-xr-xt/t1051-large-conversion.sh86
-rwxr-xr-xt/t1200-tutorial.sh6
-rwxr-xr-xt/t1300-repo-config.sh290
-rwxr-xr-xt/t1304-default-acl.sh18
-rwxr-xr-xt/t1305-config-include.sh142
-rwxr-xr-xt/t1306-xdg-files.sh158
-rwxr-xr-xt/t1402-check-ref-format.sh144
-rwxr-xr-xt/t1410-reflog.sh26
-rwxr-xr-xt/t1411-reflog-show.sh61
-rwxr-xr-xt/t1412-reflog-loop.sh2
-rwxr-xr-xt/t1450-fsck.sh68
-rwxr-xr-xt/t1501-worktree.sh8
-rwxr-xr-xt/t1506-rev-parse-diagnosis.sh11
-rwxr-xr-xt/t1507-rev-parse-upstream.sh81
-rwxr-xr-xt/t1510-repo-setup.sh4
-rwxr-xr-xt/t1511-rev-parse-caret.sh2
-rwxr-xr-xt/t2004-checkout-cache-temp.sh20
-rwxr-xr-xt/t2015-checkout-unborn.sh11
-rwxr-xr-xt/t2018-checkout-branch.sh18
-rwxr-xr-xt/t2020-checkout-detach.sh26
-rwxr-xr-xt/t2022-checkout-paths.sh42
-rwxr-xr-xt/t2023-checkout-m.sh49
-rwxr-xr-xt/t2030-unresolve-info.sh2
-rwxr-xr-xt/t2203-add-intent.sh8
-rwxr-xr-xt/t3000-ls-files-others.sh19
-rwxr-xr-xt/t3030-merge-recursive.sh79
-rwxr-xr-xt/t3040-subprojects-basic.sh144
-rwxr-xr-xt/t3200-branch.sh236
-rwxr-xr-xt/t3203-branch-output.sh28
-rwxr-xr-xt/t3300-funny-names.sh356
-rwxr-xr-xt/t3310-notes-merge-manual-resolve.sh37
-rwxr-xr-xt/t3400-rebase.sh33
-rwxr-xr-xt/t3401-rebase-partial.sh62
-rwxr-xr-xt/t3404-rebase-interactive.sh164
-rwxr-xr-xt/t3406-rebase-message.sh5
-rwxr-xr-xt/t3411-rebase-preserve-around-merges.sh1
-rwxr-xr-xt/t3415-rebase-autosquash.sh16
-rwxr-xr-xt/t3418-rebase-continue.sh4
-rwxr-xr-xt/t3419-rebase-patch-id.sh2
-rwxr-xr-xt/t3502-cherry-pick-merge.sh2
-rwxr-xr-xt/t3503-cherry-pick-root.sh27
-rwxr-xr-xt/t3505-cherry-pick-empty.sh55
-rwxr-xr-xt/t3507-cherry-pick-conflict.sh83
-rwxr-xr-xt/t3508-cherry-pick-many-commits.sh42
-rwxr-xr-xt/t3510-cherry-pick-sequence.sh520
-rwxr-xr-xt/t3700-add.sh15
-rwxr-xr-xt/t3701-add-interactive.sh26
-rwxr-xr-xt/t3900-i18n-commit.sh12
-rwxr-xr-xt/t3902-quoted.sh2
-rwxr-xr-xt/t3903-stash.sh54
-rwxr-xr-xt/t3904-stash-patch.sh47
-rwxr-xr-xt/t3905-stash-include-untracked.sh188
-rwxr-xr-xt/t4006-diff-mode.sh65
-rwxr-xr-xt/t4010-diff-pathspec.sh8
-rwxr-xr-xt/t4011-diff-symlink.sh195
-rwxr-xr-xt/t4012-diff-binary.sh60
-rwxr-xr-xt/t4013-diff-various.sh7
-rw-r--r--t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master6
-rw-r--r--t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side8
-rw-r--r--t/t4013/diff.diff-tree_--cc_--patch-with-stat_master6
-rw-r--r--t/t4013/diff.diff-tree_--cc_--stat_--summary_master6
-rw-r--r--t/t4013/diff.diff-tree_--cc_--stat_--summary_side8
-rw-r--r--t/t4013/diff.diff-tree_--cc_--stat_master6
-rw-r--r--t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial8
-rw-r--r--t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side8
-rw-r--r--t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial8
-rw-r--r--t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial8
-rw-r--r--t/t4013/diff.diff-tree_--pretty_--root_--stat_initial8
-rw-r--r--t/t4013/diff.diff-tree_--root_--patch-with-stat_initial8
-rw-r--r--t/t4013/diff.diff-tree_-c_--stat_--summary_master6
-rw-r--r--t/t4013/diff.diff-tree_-c_--stat_--summary_side8
-rw-r--r--t/t4013/diff.diff-tree_-c_--stat_master6
-rw-r--r--t/t4013/diff.diff_--patch-with-stat_-r_initial..side8
-rw-r--r--t/t4013/diff.diff_--patch-with-stat_initial..side8
-rw-r--r--t/t4013/diff.diff_--stat_initial..side8
-rw-r--r--t/t4013/diff.diff_-r_--stat_initial..side8
-rw-r--r--t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side8
-rw-r--r--t/t4013/diff.format-patch_--attach_--stdout_initial..master20
-rw-r--r--t/t4013/diff.format-patch_--attach_--stdout_initial..master^12
-rw-r--r--t/t4013/diff.format-patch_--attach_--stdout_initial..side8
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master20
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master20
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_initial..master20
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_initial..master^12
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_initial..master^^6
-rw-r--r--t/t4013/diff.format-patch_--inline_--stdout_initial..side8
-rw-r--r--t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^20
-rw-r--r--t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master20
-rw-r--r--t/t4013/diff.format-patch_--stdout_--numbered_initial..master20
-rw-r--r--t/t4013/diff.format-patch_--stdout_initial..master20
-rw-r--r--t/t4013/diff.format-patch_--stdout_initial..master^12
-rw-r--r--t/t4013/diff.format-patch_--stdout_initial..side8
-rw-r--r--t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_12
-rw-r--r--t/t4013/diff.log_--patch-with-stat_master20
-rw-r--r--t/t4013/diff.log_--patch-with-stat_master_--_dir_12
-rw-r--r--t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master34
-rw-r--r--t/t4013/diff.log_--root_--patch-with-stat_--summary_master28
-rw-r--r--t/t4013/diff.log_--root_--patch-with-stat_master28
-rw-r--r--t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master34
-rw-r--r--t/t4013/diff.show_--patch-with-stat_--summary_side8
-rw-r--r--t/t4013/diff.show_--patch-with-stat_side8
-rw-r--r--t/t4013/diff.show_--stat_--summary_side8
-rw-r--r--t/t4013/diff.show_--stat_side8
-rw-r--r--t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_12
-rw-r--r--t/t4013/diff.whatchanged_--patch-with-stat_master20
-rw-r--r--t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_12
-rw-r--r--t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master34
-rw-r--r--t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master28
-rw-r--r--t/t4013/diff.whatchanged_--root_--patch-with-stat_master28
-rw-r--r--t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master34
-rwxr-xr-xt/t4014-format-patch.sh50
-rwxr-xr-xt/t4015-diff-whitespace.sh14
-rwxr-xr-xt/t4016-diff-quote.sh35
-rwxr-xr-xt/t4018-diff-funcname.sh2
-rwxr-xr-xt/t4020-diff-external.sh2
-rwxr-xr-xt/t4029-diff-trailing-space.sh2
-rwxr-xr-xt/t4030-diff-textconv.sh12
-rwxr-xr-xt/t4031-diff-rewrite-binary.sh12
-rwxr-xr-xt/t4033-diff-patience.sh162
-rwxr-xr-xt/t4034-diff-words.sh51
-rw-r--r--t/t4034/matlab/expect14
-rw-r--r--t/t4034/matlab/post9
-rw-r--r--t/t4034/matlab/pre9
-rwxr-xr-xt/t4035-diff-quiet.sh99
-rwxr-xr-xt/t4041-diff-submodule-option.sh34
-rwxr-xr-xt/t4043-diff-rename-binary.sh8
-rwxr-xr-xt/t4045-diff-relative.sh20
-rwxr-xr-xt/t4047-diff-dirstat.sh69
-rwxr-xr-xt/t4049-diff-stat-count.sh25
-rwxr-xr-xt/t4050-diff-histogram.sh12
-rwxr-xr-xt/t4051-diff-function-context.sh92
-rwxr-xr-xt/t4052-stat-output.sh336
-rwxr-xr-xt/t4053-diff-no-index.sh32
-rwxr-xr-xt/t4100-apply-stat.sh4
-rw-r--r--t/t4100/t-apply-8.expect2
-rw-r--r--t/t4100/t-apply-9.expect2
-rwxr-xr-xt/t4103-apply-binary.sh4
-rwxr-xr-xt/t4116-apply-reverse.sh4
-rwxr-xr-xt/t4131-apply-fake-ancestor.sh2
-rwxr-xr-xt/t4136-apply-check.sh19
-rwxr-xr-xt/t4150-am.sh61
-rwxr-xr-xt/t4151-am-abort.sh5
-rwxr-xr-xt/t4200-rerere.sh8
-rwxr-xr-xt/t4202-log.sh294
-rwxr-xr-xt/t4205-log-pretty-formats.sh28
-rwxr-xr-xt/t4209-log-pickaxe.sh119
-rwxr-xr-xt/t4253-am-keep-cr-dos.sh4
-rwxr-xr-xt/t4254-am-corrupt.sh43
-rwxr-xr-xt/t5000-tar-tree.sh188
-rwxr-xr-xt/t5001-archive-attr.sh9
-rwxr-xr-xt/t5100-mailinfo.sh2
-rw-r--r--t/t5100/patch00012
-rw-r--r--t/t5100/patch00022
-rw-r--r--t/t5100/patch00032
-rw-r--r--t/t5100/patch00054
-rw-r--r--t/t5100/patch00062
-rw-r--r--t/t5100/patch00102
-rw-r--r--t/t5100/patch00112
-rw-r--r--t/t5100/patch00142
-rw-r--r--t/t5100/patch0014--scissors2
-rw-r--r--t/t5100/sample.mbox18
-rwxr-xr-xt/t5150-request-pull.sh21
-rwxr-xr-xt/t5300-pack-object.sh17
-rwxr-xr-xt/t5302-pack-index.sh27
-rwxr-xr-xt/t5303-pack-corruption-resilience.sh4
-rwxr-xr-xt/t5403-post-checkout-hook.sh46
-rwxr-xr-xt/t5500-fetch-pack.sh157
-rwxr-xr-xt/t5501-fetch-push-alternates.sh2
-rwxr-xr-xt/t5504-fetch-receive-strict.sh118
-rwxr-xr-xt/t5509-fetch-push-namespaces.sh85
-rwxr-xr-xt/t5510-fetch.sh144
-rwxr-xr-xt/t5512-ls-remote.sh41
-rw-r--r--t/t5515/fetch.br-branches-default6
-rw-r--r--t/t5515/fetch.br-branches-default-merge8
-rw-r--r--t/t5515/fetch.br-branches-default-merge_branches-default8
-rw-r--r--t/t5515/fetch.br-branches-default-octopus8
-rw-r--r--t/t5515/fetch.br-branches-default-octopus_branches-default8
-rw-r--r--t/t5515/fetch.br-branches-default_branches-default6
-rw-r--r--t/t5515/fetch.br-branches-one6
-rw-r--r--t/t5515/fetch.br-branches-one-merge8
-rw-r--r--t/t5515/fetch.br-branches-one-merge_branches-one8
-rw-r--r--t/t5515/fetch.br-branches-one-octopus6
-rw-r--r--t/t5515/fetch.br-branches-one-octopus_branches-one6
-rw-r--r--t/t5515/fetch.br-branches-one_branches-one6
-rw-r--r--t/t5515/fetch.br-config-explicit6
-rw-r--r--t/t5515/fetch.br-config-explicit-merge8
-rw-r--r--t/t5515/fetch.br-config-explicit-merge_config-explicit8
-rw-r--r--t/t5515/fetch.br-config-explicit-octopus8
-rw-r--r--t/t5515/fetch.br-config-explicit-octopus_config-explicit8
-rw-r--r--t/t5515/fetch.br-config-explicit_config-explicit6
-rw-r--r--t/t5515/fetch.br-config-glob6
-rw-r--r--t/t5515/fetch.br-config-glob-merge8
-rw-r--r--t/t5515/fetch.br-config-glob-merge_config-glob8
-rw-r--r--t/t5515/fetch.br-config-glob-octopus10
-rw-r--r--t/t5515/fetch.br-config-glob-octopus_config-glob10
-rw-r--r--t/t5515/fetch.br-config-glob_config-glob6
-rw-r--r--t/t5515/fetch.br-remote-explicit6
-rw-r--r--t/t5515/fetch.br-remote-explicit-merge8
-rw-r--r--t/t5515/fetch.br-remote-explicit-merge_remote-explicit8
-rw-r--r--t/t5515/fetch.br-remote-explicit-octopus8
-rw-r--r--t/t5515/fetch.br-remote-explicit-octopus_remote-explicit8
-rw-r--r--t/t5515/fetch.br-remote-explicit_remote-explicit6
-rw-r--r--t/t5515/fetch.br-remote-glob6
-rw-r--r--t/t5515/fetch.br-remote-glob-merge8
-rw-r--r--t/t5515/fetch.br-remote-glob-merge_remote-glob8
-rw-r--r--t/t5515/fetch.br-remote-glob-octopus10
-rw-r--r--t/t5515/fetch.br-remote-glob-octopus_remote-glob10
-rw-r--r--t/t5515/fetch.br-remote-glob_remote-glob6
-rw-r--r--t/t5515/fetch.br-unconfig6
-rw-r--r--t/t5515/fetch.br-unconfig_--tags_.._.git6
-rw-r--r--t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file6
-rw-r--r--t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file6
-rw-r--r--t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three6
-rw-r--r--t/t5515/fetch.br-unconfig_branches-default6
-rw-r--r--t/t5515/fetch.br-unconfig_branches-one6
-rw-r--r--t/t5515/fetch.br-unconfig_config-explicit6
-rw-r--r--t/t5515/fetch.br-unconfig_config-glob6
-rw-r--r--t/t5515/fetch.br-unconfig_remote-explicit6
-rw-r--r--t/t5515/fetch.br-unconfig_remote-glob6
-rw-r--r--t/t5515/fetch.master6
-rw-r--r--t/t5515/fetch.master_--tags_.._.git6
-rw-r--r--t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file6
-rw-r--r--t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file6
-rw-r--r--t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three6
-rw-r--r--t/t5515/fetch.master_branches-default6
-rw-r--r--t/t5515/fetch.master_branches-one6
-rw-r--r--t/t5515/fetch.master_config-explicit6
-rw-r--r--t/t5515/fetch.master_config-glob6
-rw-r--r--t/t5515/fetch.master_remote-explicit6
-rw-r--r--t/t5515/fetch.master_remote-glob6
-rwxr-xr-xt/t5516-fetch-push.sh213
-rwxr-xr-xt/t5520-pull.sh23
-rwxr-xr-xt/t5523-push-upstream.sh10
-rwxr-xr-xt/t5527-fetch-odd-refs.sh29
-rwxr-xr-xt/t5528-push-default.sh118
-rwxr-xr-xt/t5531-deep-submodule-push.sh181
-rwxr-xr-xt/t5532-fetch-proxy.sh2
-rwxr-xr-xt/t5540-http-push.sh42
-rwxr-xr-xt/t5541-http-push.sh114
-rwxr-xr-xt/t5550-http-fetch.sh115
-rwxr-xr-xt/t5551-http-fetch.sh31
-rwxr-xr-xt/t5560-http-backend-noserver.sh6
-rwxr-xr-xt/t5570-git-daemon.sh146
-rwxr-xr-xt/t5601-clone.sh54
-rwxr-xr-xt/t5700-clone-reference.sh45
-rwxr-xr-xt/t5701-clone-local.sh83
-rwxr-xr-xt/t5704-bundle.sh47
-rwxr-xr-xt/t5706-clone-branch.sh8
-rwxr-xr-xt/t5707-clone-detached.sh76
-rwxr-xr-xt/t5708-clone-config.sh40
-rwxr-xr-xt/t5710-info-alternate.sh2
-rwxr-xr-xt/t5800-remote-helpers.sh106
-rwxr-xr-xt/t5900-repo-selection.sh100
-rwxr-xr-xt/t6006-rev-list-format.sh26
-rwxr-xr-xt/t6011-rev-list-with-bad-commit.sh2
-rwxr-xr-xt/t6012-rev-list-simplify.sh1
-rwxr-xr-xt/t6013-rev-list-reverse-parents.sh4
-rwxr-xr-xt/t6019-rev-list-ancestry-path.sh38
-rwxr-xr-xt/t6020-merge-df.sh26
-rwxr-xr-xt/t6022-merge-rename.sh307
-rwxr-xr-xt/t6028-merge-up-to-date.sh17
-rwxr-xr-xt/t6030-bisect-porcelain.sh177
-rwxr-xr-xt/t6032-merge-large-rename.sh4
-rwxr-xr-xt/t6036-recursive-corner-cases.sh598
-rwxr-xr-xt/t6040-tracking-info.sh26
-rwxr-xr-xt/t6042-merge-rename-corner-cases.sh578
-rwxr-xr-xt/t6200-fmt-merge-msg.sh27
-rwxr-xr-xt/t6300-for-each-ref.sh101
-rwxr-xr-xt/t7004-tag.sh123
-rwxr-xr-xt/t7006-pager.sh169
-rwxr-xr-xt/t7008-grep-binary.sh28
-rwxr-xr-xt/t7060-wtstatus.sh96
-rwxr-xr-xt/t7201-co.sh4
-rwxr-xr-xt/t7300-clean.sh27
-rwxr-xr-xt/t7400-submodule-basic.sh181
-rwxr-xr-xt/t7401-submodule-summary.sh12
-rwxr-xr-xt/t7403-submodule-sync.sh95
-rwxr-xr-xt/t7405-submodule-merge.sh51
-rwxr-xr-xt/t7406-submodule-update.sh340
-rwxr-xr-xt/t7407-submodule-foreach.sh107
-rwxr-xr-xt/t7408-submodule-reference.sh8
-rwxr-xr-xt/t7501-commit.sh333
-rwxr-xr-xt/t7502-commit.sh2
-rwxr-xr-xt/t7503-pre-commit-hook.sh51
-rwxr-xr-xt/t7508-status.sh46
-rwxr-xr-xt/t7510-signed-commit.sh80
-rwxr-xr-xt/t7511-status-index.sh50
-rwxr-xr-xt/t7512-status-help.sh649
-rwxr-xr-xt/t7600-merge.sh60
-rwxr-xr-xt/t7602-merge-octopus-many.sh36
-rwxr-xr-xt/t7603-merge-reduce-heads.sh50
-rwxr-xr-xt/t7604-merge-custom-message.sh2
-rwxr-xr-xt/t7607-merge-overwrite.sh9
-rwxr-xr-xt/t7608-merge-messages.sh4
-rwxr-xr-xt/t7610-mergetool.sh28
-rwxr-xr-xt/t7701-repack-unpack-unreachable.sh14
-rwxr-xr-xt/t7800-difftool.sh114
-rwxr-xr-xt/t7810-grep.sh207
-rwxr-xr-xt/t8006-blame-textconv.sh13
-rwxr-xr-xt/t9001-send-email.sh29
-rwxr-xr-xt/t9002-column.sh180
-rwxr-xr-xt/t9010-svn-fe.sh365
-rwxr-xr-xt/t9011-svn-da.sh248
-rwxr-xr-xt/t9100-git-svn-basic.sh45
-rwxr-xr-xt/t9129-git-svn-i18n-commitencoding.sh2
-rwxr-xr-xt/t9130-git-svn-authors-file.sh4
-rwxr-xr-xt/t9137-git-svn-dcommit-clobber-series.sh8
-rwxr-xr-xt/t9158-git-svn-mergeinfo.sh13
-rwxr-xr-xt/t9160-git-svn-preserve-empty-dirs.sh153
-rwxr-xr-xt/t9161-git-svn-mergeinfo-push.sh104
-rw-r--r--t/t9161/branches.dump374
-rwxr-xr-xt/t9162-git-svn-dcommit-interactive.sh64
-rwxr-xr-xt/t9200-git-cvsexportcommit.sh8
-rwxr-xr-xt/t9300-fast-import.sh753
-rwxr-xr-xt/t9301-fast-import-notes.sh63
-rwxr-xr-xt/t9350-fast-export.sh6
-rwxr-xr-xt/t9400-git-cvsserver-server.sh8
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh129
-rwxr-xr-xt/t9501-gitweb-standalone-http-status.sh77
-rwxr-xr-xt/t9700-perl-git.sh6
-rwxr-xr-xt/t9700/test.pl4
-rwxr-xr-xt/t9800-git-p4-basic.sh576
-rwxr-xr-xt/t9800-git-p4.sh264
-rwxr-xr-xt/t9801-git-p4-branch.sh417
-rwxr-xr-xt/t9802-git-p4-filetype.sh139
-rwxr-xr-xt/t9803-git-p4-shell-metachars.sh112
-rwxr-xr-xt/t9804-git-p4-label.sh115
-rwxr-xr-xt/t9805-git-p4-skip-submit-edit.sh104
-rwxr-xr-xt/t9806-git-p4-options.sh170
-rwxr-xr-xt/t9807-git-p4-submit.sh189
-rwxr-xr-xt/t9808-git-p4-chdir.sh49
-rwxr-xr-xt/t9809-git-p4-client-view.sh854
-rwxr-xr-xt/t9810-git-p4-rcs.sh388
-rwxr-xr-xt/t9811-git-p4-label-import.sh222
-rwxr-xr-xt/t9901-git-web--browse.sh63
-rwxr-xr-xt/t9902-completion.sh231
-rwxr-xr-xt/t9903-bash-prompt.sh456
-rw-r--r--t/test-lib-functions.sh565
-rw-r--r--t/test-lib.sh566
-rwxr-xr-xt/test-terminal.perl4
-rw-r--r--tag.c17
-rw-r--r--tag.h1
-rwxr-xr-xtemplates/hooks--post-commit.sample8
-rwxr-xr-xtemplates/hooks--post-receive.sample15
-rw-r--r--test-ctype.c80
-rw-r--r--test-date.c7
-rw-r--r--test-dump-cache-tree.c2
-rw-r--r--test-mergesort.c52
-rw-r--r--test-obj-pool.c116
-rw-r--r--test-parse-options.c19
-rw-r--r--test-path-utils.c22
-rw-r--r--test-revision-walking.c66
-rw-r--r--test-scrap-cache-tree.c17
-rw-r--r--test-string-pool.c31
-rw-r--r--test-subprocess.c6
-rw-r--r--test-svn-fe.c50
-rw-r--r--test-treap.c70
-rw-r--r--transport-helper.c274
-rw-r--r--transport.c125
-rw-r--r--transport.h5
-rw-r--r--tree-diff.c23
-rw-r--r--tree-walk.c127
-rw-r--r--tree-walk.h19
-rw-r--r--tree.c11
-rw-r--r--unix-socket.c122
-rw-r--r--unix-socket.h7
-rw-r--r--unpack-trees.c71
-rw-r--r--unpack-trees.h2
-rw-r--r--upload-pack.c69
-rw-r--r--url.c58
-rw-r--r--url.h1
-rw-r--r--userdiff.c26
-rw-r--r--varint.c29
-rw-r--r--varint.h9
-rw-r--r--vcs-svn/LICENSE3
-rw-r--r--vcs-svn/fast_export.c256
-rw-r--r--vcs-svn/fast_export.h26
-rw-r--r--vcs-svn/line_buffer.c6
-rw-r--r--vcs-svn/line_buffer.h2
-rw-r--r--vcs-svn/obj_pool.h61
-rw-r--r--vcs-svn/repo_tree.c330
-rw-r--r--vcs-svn/repo_tree.h12
-rw-r--r--vcs-svn/sliding_window.c79
-rw-r--r--vcs-svn/sliding_window.h18
-rw-r--r--vcs-svn/string_pool.c102
-rw-r--r--vcs-svn/string_pool.h11
-rw-r--r--vcs-svn/string_pool.txt43
-rw-r--r--vcs-svn/svndiff.c308
-rw-r--r--vcs-svn/svndiff.h10
-rw-r--r--vcs-svn/svndump.c138
-rw-r--r--vcs-svn/trp.h237
-rw-r--r--vcs-svn/trp.txt109
-rw-r--r--version.c17
-rw-r--r--version.h8
-rw-r--r--walker.c2
-rw-r--r--wrap-for-bin.sh3
-rw-r--r--wrapper.c39
-rw-r--r--ws.c2
-rw-r--r--wt-status.c318
-rw-r--r--wt-status.h18
-rw-r--r--xdiff-interface.c44
-rw-r--r--xdiff-interface.h4
-rw-r--r--xdiff/xdiff.h17
-rw-r--r--xdiff/xdiffi.c22
-rw-r--r--xdiff/xdiffi.h2
-rw-r--r--xdiff/xemit.c97
-rw-r--r--xdiff/xhistogram.c363
-rw-r--r--xdiff/xpatience.c29
-rw-r--r--xdiff/xprepare.c222
-rw-r--r--xdiff/xutils.c200
-rw-r--r--xdiff/xutils.h7
-rw-r--r--zlib.c9
1042 files changed, 113976 insertions, 19809 deletions
diff --git a/.gitignore b/.gitignore
index 8572c8c..c188d0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,10 +26,15 @@
/git-cherry-pick
/git-clean
/git-clone
+/git-column
/git-commit
/git-commit-tree
/git-config
/git-count-objects
+/git-credential
+/git-credential-cache
+/git-credential-cache--daemon
+/git-credential-store
/git-cvsexportcommit
/git-cvsimport
/git-cvsserver
@@ -89,6 +94,7 @@
/git-name-rev
/git-mv
/git-notes
+/git-p4
/git-pack-redundant
/git-pack-objects
/git-pack-refs
@@ -171,21 +177,21 @@
/test-date
/test-delta
/test-dump-cache-tree
+/test-scrap-cache-tree
/test-genrandom
/test-index-version
/test-line-buffer
/test-match-trees
+/test-mergesort
/test-mktemp
-/test-obj-pool
/test-parse-options
/test-path-utils
+/test-revision-walking
/test-run-command
/test-sha1
/test-sigchain
-/test-string-pool
/test-subprocess
/test-svn-fe
-/test-treap
/common-cmds.h
*.tar.gz
*.dsc
diff --git a/.mailmap b/.mailmap
index 19c8726..6303782 100644
--- a/.mailmap
+++ b/.mailmap
@@ -29,7 +29,13 @@ Joachim Berdal Haga <cjhaga@fys.uio.no>
Jon Loeliger <jdl@freescale.com>
Jon Seymour <jon@blackcubes.dyndns.org>
Jonathan Nieder <jrnieder@uchicago.edu>
-Junio C Hamano <junio@twinsun.com>
+Junio C Hamano <gitster@pobox.com> <gitster@pobox.com>
+Junio C Hamano <gitster@pobox.com> <junio@pobox.com>
+Junio C Hamano <gitster@pobox.com> <junio@twinsun.com>
+Junio C Hamano <gitster@pobox.com> <junkio@twinsun.com>
+Junio C Hamano <gitster@pobox.com> <junio@hera.kernel.org>
+Junio C Hamano <gitster@pobox.com> <junio@kernel.org>
+Junio C Hamano <gitster@pobox.com> <junkio@cox.net>
Karl Hasselström <kha@treskal.com>
Kent Engstrom <kent@lysator.liu.se>
Lars Doelle <lars.doelle@on-line ! de>
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index fe1c1e5..4557711 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -35,10 +35,22 @@ For shell scripts specifically (not exhaustive):
- Case arms are indented at the same depth as case and esac lines.
+ - Redirection operators should be written with space before, but no
+ space after them. In other words, write 'echo test >"$file"'
+ instead of 'echo test> $file' or 'echo test > $file'. Note that
+ even though it is not required by POSIX to double-quote the
+ redirection target in a variable (as shown above), our code does so
+ because some versions of bash issue a warning without the quotes.
+
- 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.
+ - If you want to find out if a command is available on the user's
+ $PATH, you should use 'type <command>', instead of 'which <command>'.
+ The output of 'which' is not machine parseable and its exit code
+ is not reliable across platforms.
+
- We use POSIX compliant parameter substitutions and avoid bashisms;
namely:
@@ -81,6 +93,10 @@ For shell scripts specifically (not exhaustive):
are ERE elements not BRE (note that \? and \+ are not even part
of BRE -- making them accessible from BRE is a GNU extension).
+ - Use Git's gettext wrappers in git-sh-i18n to make the user
+ interface translatable. See "Marking strings for translation" in
+ po/README.
+
For C programs:
- We use tabs to indent, and interpret tabs as taking up to
@@ -144,6 +160,9 @@ For C programs:
- When we pass <string, length> pair to functions, we should try to
pass them in that order.
+ - Use Git's gettext wrappers to make the user interface
+ translatable. See "Marking strings for translation" in po/README.
+
Writing Documentation:
Every user-visible change should be reflected in the documentation.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 18c71d7..063fa69 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -1,12 +1,13 @@
MAN1_TXT= \
$(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
$(wildcard git-*.txt)) \
- gitk.txt git.txt
+ gitk.txt gitweb.txt git.txt
MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
- gitrepository-layout.txt
+ gitrepository-layout.txt gitweb.conf.txt
MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
- gitdiffcore.txt gitrevisions.txt gitworkflows.txt
+ gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt
+MAN7_TXT += gitcredentials.txt
MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
@@ -19,7 +20,10 @@ ARTICLES += everyday
ARTICLES += git-tools
ARTICLES += git-bisect-lk2009
# with their own formatting rules.
-SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual
+SP_ARTICLES = user-manual
+SP_ARTICLES += howto/revert-branch-rebase
+SP_ARTICLES += howto/using-merge-subtree
+SP_ARTICLES += howto/using-signed-tag-in-pull-request
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
@@ -46,8 +50,8 @@ MANPAGE_XSL = manpage-normal.xsl
XMLTO_EXTRA =
INSTALL?=install
RM ?= rm -f
-DOC_REF = origin/man
-HTML_REF = origin/html
+MAN_REPO = ../../git-manpages
+HTML_REPO = ../../git-htmldocs
infodir?=$(prefix)/share/info
MAKEINFO=makeinfo
@@ -62,12 +66,6 @@ endif
-include ../config.mak
#
-# For asciidoc ...
-# -7.1.2, set ASCIIDOC7
-# 8.0-, no extra settings are needed
-#
-
-#
# For docbook-xsl ...
# -1.68.1, no extra settings are needed?
# 1.69.0, set ASCIIDOC_ROFF?
@@ -77,9 +75,6 @@ endif
# 1.73.0-, no extra settings are needed
#
-ifndef ASCIIDOC7
-ASCIIDOC_EXTRA += -a asciidoc7compatible -a no-inline-literal
-endif
ifdef DOCBOOK_XSL_172
ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
MANPAGE_XSL = manpage-1.72.xsl
@@ -120,14 +115,15 @@ SHELL_PATH ?= $(SHELL)
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
-#
-# Please note that there is a minor bug in asciidoc.
-# The version after 6.0.3 _will_ include the patch found here:
-# http://marc.theaimsgroup.com/?l=git&m=111558757202243&w=2
-#
-# Until that version is released you may have to apply the patch
-# yourself - yes, all 6 characters of it!
-#
+ifdef DEFAULT_PAGER
+DEFAULT_PAGER_SQ = $(subst ','\'',$(DEFAULT_PAGER))
+ASCIIDOC_EXTRA += -a 'git-default-pager=$(DEFAULT_PAGER_SQ)'
+endif
+
+ifdef DEFAULT_EDITOR
+DEFAULT_EDITOR_SQ = $(subst ','\'',$(DEFAULT_EDITOR))
+ASCIIDOC_EXTRA += -a 'git-default-editor=$(DEFAULT_EDITOR_SQ)'
+endif
QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
QUIET_SUBDIR1 =
@@ -266,6 +262,7 @@ technical/api-index.txt: technical/api-index-skel.txt \
technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
$(QUIET_GEN)cd technical && '$(SHELL_PATH_SQ)' ./api-index.sh
+technical/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../
$(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt
$(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \
$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt
@@ -319,6 +316,7 @@ $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
WEBDOC_DEST = /pub/software/scm/git/docs
+howto/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../
$(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
sed -e '1,/^$$/d' $< | $(ASCIIDOC) $(ASCIIDOC_EXTRA) -b xhtml11 - >$@+ && \
@@ -327,12 +325,23 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
install-webdoc : html
'$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)
+# You must have a clone of git-htmldocs and git-manpages repositories
+# next to the git repository itself for the following to work.
+
quick-install: quick-install-man
-quick-install-man:
- '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
+require-manrepo::
+ @if test ! -d $(MAN_REPO); \
+ then echo "git-manpages repository must exist at $(MAN_REPO)"; exit 1; fi
+
+quick-install-man: require-manrepo
+ '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir)
+
+require-htmlrepo::
+ @if test ! -d $(HTML_REPO); \
+ then echo "git-htmldocs repository must exist at $(HTML_REPO)"; exit 1; fi
-quick-install-html:
- '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
+quick-install-html: require-htmlrepo
+ '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir)
.PHONY: FORCE
diff --git a/Documentation/RelNotes/1.7.10.1.txt b/Documentation/RelNotes/1.7.10.1.txt
new file mode 100644
index 0000000..806a965
--- /dev/null
+++ b/Documentation/RelNotes/1.7.10.1.txt
@@ -0,0 +1,78 @@
+Git v1.7.10.1 Release Notes
+===========================
+
+Additions since v1.7.10
+-----------------------
+
+Localization message files for Danish and German have been added.
+
+
+Fixes since v1.7.10
+-------------------
+
+ * "git add -p" is not designed to deal with unmerged paths but did
+ not exclude them and tried to apply funny patches only to fail.
+
+ * "git blame" started missing quite a few changes from the origin
+ since we stopped using the diff minimalization by default in v1.7.2
+ era.
+
+ * When PATH contains an unreadable directory, alias expansion code
+ did not kick in, and failed with an error that said "git-subcmd"
+ was not found.
+
+ * "git clean -d -f" (not "-d -f -f") is supposed to protect nested
+ working trees of independent git repositories that exist in the
+ current project working tree from getting removed, but the
+ protection applied only to such working trees that are at the
+ top-level of the current project by mistake.
+
+ * "git commit --author=$name" did not tell the name that was being
+ recorded in the resulting commit to hooks, even though it does do
+ so when the end user overrode the authorship via the
+ "GIT_AUTHOR_NAME" environment variable.
+
+ * When "git commit --template F" errors out because the user did not
+ touch the message, it claimed that it aborts due to "empty
+ message", which was utterly wrong.
+
+ * The regexp configured with diff.wordregex was incorrectly reused
+ across files.
+
+ * An age-old corner case bug in combine diff (only triggered with -U0
+ and the hunk at the beginning of the file needs to be shown) has
+ been fixed.
+
+ * Rename detection logic used to match two empty files as renames
+ during merge-recursive, leading to unnatural mismerges.
+
+ * The parser in "fast-import" did not diagnose ":9" style references
+ that is not followed by required SP/LF as an error.
+
+ * When "git fetch" encounters repositories with too many references,
+ the command line of "fetch-pack" that is run by a helper
+ e.g. remote-curl, may fail to hold all of them. Now such an
+ internal invocation can feed the references through the standard
+ input of "fetch-pack".
+
+ * "git fetch" that recurses into submodules on demand did not check
+ if it needs to go into submodules when non branches (most notably,
+ tags) are fetched.
+
+ * "log -p --graph" used with "--stat" had a few formatting error.
+
+ * Running "notes merge --commit" failed to perform correctly when run
+ from any directory inside $GIT_DIR/. When "notes merge" stops with
+ conflicts, $GIT_DIR/NOTES_MERGE_WORKTREE is the place a user edits
+ to resolve it.
+
+ * The 'push to upstream' implementation was broken in some corner
+ cases. "git push $there" without refspec, when the current branch
+ is set to push to a remote different from $there, used to push to
+ $there using the upstream information to a remote unreleated to
+ $there.
+
+ * Giving "--continue" to a conflicted "rebase -i" session skipped a
+ commit that only results in changes to submodules.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.10.2.txt b/Documentation/RelNotes/1.7.10.2.txt
new file mode 100644
index 0000000..7a7e9d6
--- /dev/null
+++ b/Documentation/RelNotes/1.7.10.2.txt
@@ -0,0 +1,85 @@
+Git v1.7.10.2 Release Notes
+===========================
+
+Fixes since v1.7.10.1
+---------------------
+
+ * The test scaffolding for git-daemon was flaky.
+
+ * The test scaffolding for fast-import was flaky.
+
+ * The filesystem boundary was not correctly reported when .git directory
+ discovery stopped at a mount point.
+
+ * HTTP transport that requires authentication did not work correctly when
+ multiple connections are used simultaneously.
+
+ * Minor memory leak during unpack_trees (hence "merge" and "checkout"
+ to check out another branch) has been plugged.
+
+ * In the older days, the header "Conflicts:" in "cherry-pick" and "merge"
+ was separated by a blank line from the list of paths that follow for
+ readability, but when "merge" was rewritten in C, we lost it by
+ mistake. Remove the newline from "cherry-pick" to make them match
+ again.
+
+ * The command line parser choked "git cherry-pick $name" when $name can
+ be both revision name and a pathname, even though $name can never be a
+ path in the context of the command.
+
+ * The "include.path" facility in the configuration mechanism added in
+ 1.7.10 forgot to interpret "~/path" and "~user/path" as it should.
+
+ * "git config --rename-section" to rename an existing section into a
+ bogus one did not check the new name.
+
+ * The "diff --no-index" codepath used limited-length buffers, risking
+ pathnames getting truncated. Update it to use the strbuf API.
+
+ * The report from "git fetch" said "new branch" even for a non branch
+ ref.
+
+ * The http-backend (the server side of the smart http transfer) used
+ to overwrite GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL with the
+ value obtained from REMOTE_USER unconditionally, making it
+ impossible for the server side site-specific customization to use
+ different identity sources to affect the names logged. It now uses
+ REMOTE_USER only as a fallback value.
+
+ * "log --graph" was not very friendly with "--stat" option and its
+ output had line breaks at wrong places.
+
+ * Octopus merge strategy did not reduce heads that are recorded in the
+ final commit correctly.
+
+ * "git push" over smart-http lost progress output a few releases ago;
+ this release resurrects it.
+
+ * The error and advice messages given by "git push" when it fails due
+ to non-ff were not very helpful to new users; it has been broken
+ into three cases, and each is given a separate advice message.
+
+ * The insn sheet given by "rebase -i" did not make it clear that the
+ insn lines can be re-ordered to affect the order of the commits in
+ the resulting history.
+
+ * "git repack" used to write out unreachable objects as loose objects
+ when repacking, even if such loose objects will immediately pruned
+ due to its age.
+
+ * A contrib script "rerere-train" did not work out of the box unless
+ user futzed with her $PATH.
+
+ * "git rev-parse --show-prefix" used to emit nothing when run at the
+ top-level of the working tree, but now it gives a blank line.
+
+ * The i18n of error message "git stash save" was not properly done.
+
+ * "git submodule" used a sed script that some platforms mishandled.
+
+ * When using a Perl script on a system where "perl" found on user's
+ $PATH could be ancient or otherwise broken, we allow builders to
+ specify the path to a good copy of Perl with $PERL_PATH. The
+ gitweb test forgot to use that Perl when running its test.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.10.3.txt b/Documentation/RelNotes/1.7.10.3.txt
new file mode 100644
index 0000000..703fbf1
--- /dev/null
+++ b/Documentation/RelNotes/1.7.10.3.txt
@@ -0,0 +1,43 @@
+Git v1.7.10.3 Release Notes
+===========================
+
+Fixes since v1.7.10.2
+---------------------
+
+ * The message file for German translation has been updated a bit.
+
+ * Running "git checkout" on an unborn branch used to corrupt HEAD.
+
+ * When checking out another commit from an already detached state, we
+ used to report all commits that are not reachable from any of the
+ refs as lossage, but some of them might be reachable from the new
+ HEAD, and there is no need to warn about them.
+
+ * Some time ago, "git clone" lost the progress output for its
+ "checkout" phase; when run without any "--quiet" option, it should
+ give progress to the lengthy operation.
+
+ * The directory path used in "git diff --no-index", when it recurses
+ down, was broken with a recent update after v1.7.10.1 release.
+
+ * "log -z --pretty=tformat:..." did not terminate each record with
+ NUL. The fix is not entirely correct when the output also asks for
+ --patch and/or --stat, though.
+
+ * The DWIM behaviour for "log --pretty=format:%gd -g" was somewhat
+ broken and gave undue precedence to configured log.date, causing
+ "git stash list" to show "stash@{time stamp string}".
+
+ * "git status --porcelain" ignored "--branch" option by mistake. The
+ output for "git status --branch -z" was also incorrect and did not
+ terminate the record for the current branch name with NUL as asked.
+
+ * When a submodule repository uses alternate object store mechanism,
+ some commands that were started from the superproject did not
+ notice it and failed with "No such object" errors. The subcommands
+ of "git submodule" command that recursed into the submodule in a
+ separate process were OK; only the ones that cheated and peeked
+ directly into the submodule's repository from the primary process
+ were affected.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.10.4.txt b/Documentation/RelNotes/1.7.10.4.txt
new file mode 100644
index 0000000..326670d
--- /dev/null
+++ b/Documentation/RelNotes/1.7.10.4.txt
@@ -0,0 +1,29 @@
+Git v1.7.10.4 Release Notes
+===========================
+
+Fixes since v1.7.10.3
+---------------------
+
+ * The message file for Swedish translation has been updated a bit.
+
+ * A name taken from mailmap was copied into an internal buffer
+ incorrectly and could overun the buffer if it is too long.
+
+ * A malformed commit object that has a header line chomped in the
+ middle could kill git with a NULL pointer dereference.
+
+ * An author/committer name that is a single character was mishandled
+ as an invalid name by mistake.
+
+ * The progress indicator for a large "git checkout" was sent to
+ stderr even if it is not a terminal.
+
+ * "git grep -e '$pattern'", unlike the case where the patterns are
+ read from a file, did not treat individual lines in the given
+ pattern argument as separate regular expressions as it should.
+
+ * When "git rebase" was given a bad commit to replay the history on,
+ its error message did not correctly give the command line argument
+ it had trouble parsing.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.10.5.txt b/Documentation/RelNotes/1.7.10.5.txt
new file mode 100644
index 0000000..4db1770
--- /dev/null
+++ b/Documentation/RelNotes/1.7.10.5.txt
@@ -0,0 +1,12 @@
+Git v1.7.10.5 Release Notes
+===========================
+
+Fixes since v1.7.10.4
+---------------------
+
+ * "git fast-export" did not give a readable error message when the
+ same mark erroneously appeared twice in the --import-marks input.
+
+ * "git rebase -p" used to pay attention to rebase.autosquash which
+ was wrong. "git rebase -p -i" should, but "git rebase -p" by
+ itself should not.
diff --git a/Documentation/RelNotes/1.7.10.txt b/Documentation/RelNotes/1.7.10.txt
new file mode 100644
index 0000000..58100bf
--- /dev/null
+++ b/Documentation/RelNotes/1.7.10.txt
@@ -0,0 +1,219 @@
+Git v1.7.10 Release Notes
+=========================
+
+Compatibility Notes
+-------------------
+
+ * From this release on, the "git merge" command in an interactive
+ session will start an editor when it automatically resolves the
+ merge for the user to explain the resulting commit, just like the
+ "git commit" command does when it wasn't given a commit message.
+
+ If you have a script that runs "git merge" and keeps its standard
+ input and output attached to the user's terminal, and if you do not
+ want the user to explain the resulting merge commits, you can
+ export GIT_MERGE_AUTOEDIT environment variable set to "no", like
+ this:
+
+ #!/bin/sh
+ GIT_MERGE_AUTOEDIT=no
+ export GIT_MERGE_AUTOEDIT
+
+ to disable this behavior (if you want your users to explain their
+ merge commits, you do not have to do anything). Alternatively, you
+ can give the "--no-edit" option to individual invocations of the
+ "git merge" command if you know everybody who uses your script has
+ Git v1.7.8 or newer.
+
+ * The "--binary/-b" options to "git am" have been a no-op for quite a
+ while and were deprecated in mid 2008 (v1.6.0). When you give these
+ options to "git am", it will now warn and ask you not to use them.
+
+ * When you do not tell which branches and tags to push to the "git
+ push" command in any way, the command used "matching refs" rule to
+ update remote branches and tags with branches and tags with the
+ same name you locally have. In future versions of Git, this will
+ change to push out only your current branch according to either the
+ "upstream" or the "current" rule. Although "upstream" may be more
+ powerful once the user understands Git better, the semantics
+ "current" gives is simpler and easier to understand for beginners
+ and may be a safer and better default option. We haven't decided
+ yet which one to switch to.
+
+
+Updates since v1.7.9
+--------------------
+
+UI, Workflows & Features
+
+ * various "gitk" updates.
+ - show the path to the top level directory in the window title
+ - update preference edit dialog
+ - display file list correctly when directories are given on command line
+ - make "git-describe" output in the log message into a clickable link
+ - avoid matching the UNIX timestamp part when searching all fields
+ - give preference to symbolic font names like sans & monospace
+ - allow comparing two commits using a mark
+ - "gitk" honors log.showroot configuration.
+
+ * Teams for localizing the messages from the Porcelain layer of
+ commands are starting to form, thanks to Jiang Xin who volunteered
+ to be the localization coordinator. Translated messages for
+ simplified Chinese, Swedish and Portuguese are available.
+
+ * The configuration mechanism learned an "include" facility; an
+ assignment to the include.path pseudo-variable causes the named
+ file to be included in-place when Git looks up configuration
+ variables.
+
+ * A content filter (clean/smudge) used to be just a way to make the
+ recorded contents "more useful", and allowed to fail; a filter can
+ now optionally be marked as "required".
+
+ * Options whose names begin with "--no-" (e.g. the "--no-verify"
+ option of the "git commit" command) can be negated by omitting
+ "no-" from its name, e.g. "git commit --verify".
+
+ * "git am" learned to pass "-b" option to underlying "git mailinfo", so
+ that a bracketed string other than "PATCH" at the beginning can be kept.
+
+ * "git clone" learned "--single-branch" option to limit cloning to a
+ single branch (surprise!); tags that do not point into the history
+ of the branch are not fetched.
+
+ * "git clone" learned to detach the HEAD in the resulting repository
+ when the user specifies a tag with "--branch" (e.g., "--branch=v1.0").
+ Clone also learned to print the usual "detached HEAD" advice in such
+ a case, similar to "git checkout v1.0".
+
+ * When showing a patch while ignoring whitespace changes, the context
+ lines are taken from the postimage, in order to make it easier to
+ view the output.
+
+ * "git diff --stat" learned to adjust the width of the output on
+ wider terminals, and give more columns to pathnames as needed.
+
+ * "diff-highlight" filter (in contrib/) was updated to produce more
+ aesthetically pleasing output.
+
+ * "fsck" learned "--no-dangling" option to omit dangling object
+ information.
+
+ * "git log -G" and "git log -S" learned to pay attention to the "-i"
+ option. With "-i", "log -G" ignores the case when finding patch
+ hunks that introduce or remove a string that matches the given
+ pattern. Similarly with "-i", "log -S" ignores the case when
+ finding the commit the given block of text appears or disappears
+ from the file.
+
+ * "git merge" in an interactive session learned to spawn the editor
+ by default to let the user edit the auto-generated merge message,
+ to encourage people to explain their merges better. Legacy scripts
+ can export GIT_MERGE_AUTOEDIT=no to retain the historical behavior.
+ Both "git merge" and "git pull" can be given --no-edit from the
+ command line to accept the auto-generated merge message.
+
+ * The advice message given when the user didn't give enough clue on
+ what to merge to "git pull" and "git merge" has been updated to
+ be more concise and easier to understand.
+
+ * "git push" learned the "--prune" option, similar to "git fetch".
+
+ * The whole directory that houses a top-level superproject managed by
+ "git submodule" can be moved to another place.
+
+ * "git symbolic-ref" learned the "--short" option to abbreviate the
+ refname it shows unambiguously.
+
+ * "git tag --list" can be given "--points-at <object>" to limit its
+ output to those that point at the given object.
+
+ * "gitweb" allows intermediate entries in the directory hierarchy
+ that leads to a project to be clicked, which in turn shows the
+ list of projects inside that directory.
+
+ * "gitweb" learned to read various pieces of information for the
+ repositories lazily, instead of reading everything that could be
+ needed (including the ones that are not necessary for a specific
+ task).
+
+ * Project search in "gitweb" shows the substring that matched in the
+ project name and description highlighted.
+
+ * HTTP transport learned to authenticate with a proxy if needed.
+
+ * A new script "diffall" is added to contrib/; it drives an
+ external tool to perform a directory diff of two Git revisions
+ in one go, unlike "difftool" that compares one file at a time.
+
+Foreign Interface
+
+ * Improved handling of views, labels and branches in "git-p4" (in contrib).
+
+ * "git-p4" (in contrib) suffered from unnecessary merge conflicts when
+ p4 expanded the embedded $RCS$-like keywords; it can be now told to
+ unexpand them.
+
+ * Some "git-svn" updates.
+
+ * "vcs-svn"/"svn-fe" learned to read dumps with svn-deltas and
+ support incremental imports.
+
+ * "git difftool/mergetool" learned to drive DeltaWalker.
+
+Performance
+
+ * Unnecessary calls to parse_object() "git upload-pack" makes in
+ response to "git fetch", have been eliminated, to help performance
+ in repositories with excessive number of refs.
+
+Internal Implementation (please report possible regressions)
+
+ * Recursive call chains in "git index-pack" to deal with long delta
+ chains have been flattened, to reduce the stack footprint.
+
+ * Use of add_extra_ref() API is now gone, to make it possible to
+ cleanly restructure the overall refs API.
+
+ * The command line parser of "git pack-objects" now uses parse-options
+ API.
+
+ * The test suite supports the new "test_pause" helper function.
+
+ * Parallel to the test suite, there is a beginning of performance
+ benchmarking framework.
+
+ * t/Makefile is adjusted to prevent newer versions of GNU make from
+ running tests in seemingly random order.
+
+ * The code to check if a path points at a file beyond a symbolic link
+ has been restructured to be thread-safe.
+
+ * When pruning directories that has become empty during "git prune"
+ and "git prune-packed", call closedir() that iterates over a
+ directory before rmdir() it.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.9
+------------------
+
+Unless otherwise noted, all the fixes since v1.7.9 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
+
+ * Build with NO_PERL_MAKEMAKER was broken and Git::I18N did not work
+ with versions of Perl older than 5.8.3.
+ (merge 5eb660e ab/perl-i18n later to maint).
+
+ * "git tag -s" honored "gpg.program" configuration variable since
+ 1.7.9, but "git tag -v" and "git verify-tag" didn't.
+ (merge a2c2506 az/verify-tag-use-gpg-config later to maint).
+
+ * "configure" script learned to take "--with-sane-tool-path" from the
+ command line to record SANE_TOOL_PATH (used to avoid broken platform
+ tools in /usr/bin) in config.mak.autogen. This may be useful for
+ people on Solaris who have saner tools outside /usr/xpg[46]/bin.
+
+ * zsh port of bash completion script needed another workaround.
diff --git a/Documentation/RelNotes/1.7.11.1.txt b/Documentation/RelNotes/1.7.11.1.txt
new file mode 100644
index 0000000..577ecca
--- /dev/null
+++ b/Documentation/RelNotes/1.7.11.1.txt
@@ -0,0 +1,9 @@
+Git v1.7.11.1 Release Notes
+===========================
+
+Fixes since v1.7.11
+-------------------
+
+ * The cross links in the HTML version of manual pages were broken.
+
+Also contains minor typofixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.11.txt b/Documentation/RelNotes/1.7.11.txt
new file mode 100644
index 0000000..15b954c
--- /dev/null
+++ b/Documentation/RelNotes/1.7.11.txt
@@ -0,0 +1,139 @@
+Git v1.7.11 Release Notes
+=========================
+
+Updates since v1.7.10
+---------------------
+
+UI, Workflows & Features
+
+ * A new mode for push, "simple", which is a cross between "current"
+ and "upstream", has been introduced. "git push" without any refspec
+ will push the current branch out to the same name at the remote
+ repository only when it is set to track the branch with the same
+ name over there. The plan is to make this mode the new default
+ value when push.default is not configured.
+
+ * A couple of commands learned the "--column" option to produce
+ columnar output.
+
+ * A third-party tool "git subtree" is distributed in contrib/
+
+ * A remote helper that acts as a proxy and caches ssl session for the
+ https:// transport is added to the contrib/ area.
+
+ * Error messages given when @{u} is used for a branch without its
+ upstream configured have been clarified.
+
+ * Even with the "-q"uiet option, "checkout" used to report setting up
+ tracking. Also "branch" learned the "-q"uiet option to squelch
+ informational message.
+
+ * Your build platform may support hardlinks but you may prefer not to
+ use them, e.g. when installing to DESTDIR to make a tarball and
+ untarring on a filesystem that has poor support for hardlinks.
+ There is a Makefile option NO_INSTALL_HARDLINKS for you.
+
+ * The smart-http backend used to always override GIT_COMMITTER_*
+ variables with REMOTE_USER and REMOTE_ADDR, but these variables are
+ now preserved when set.
+
+ * "git am" learned the "--include" option, which is an opposite of
+ existing the "--exclude" option.
+
+ * When "git am -3" needs to fall back to an application of the patch
+ to a synthesized preimage followed by a 3-way merge, the paths that
+ needed such treatment are now reported to the end user, so that the
+ result in them can be eyeballed with extra care.
+
+ * The output from "diff/log --stat" used to always allocate 4 columns
+ to show the number of modified lines, but not anymore.
+
+ * "git difftool" learned the "--dir-diff" option to spawn external
+ diff tools that can compare two directory hierarchies at a time
+ after populating two temporary directories, instead of running an
+ instance of the external tool once per a file pair.
+
+ * The "fmt-merge-msg" command learned to list the primary contributors
+ involved in the side topic you are merging in a comment in the merge
+ commit template.
+
+ * "git rebase" learned to optionally keep commits that do not
+ introduce any change in the original history.
+
+ * "git push --recurse-submodules" learned to optionally look into the
+ histories of submodules bound to the superproject and push them
+ out.
+
+ * A 'snapshot' request to "gitweb" honors If-Modified-Since: header,
+ based on the commit date.
+
+ * "gitweb" learned to highlight the patch it outputs even more.
+
+Foreign Interface
+
+ * "git svn" used to die with unwanted SIGPIPE when talking with an HTTP
+ server that uses keep-alive.
+
+ * "git svn" learned to use platform specific authentication
+ providers, e.g. gnome-keyring, kwallet, etc.
+
+ * "git p4" has been moved out of the contrib/ area and has seen more
+ work on importing labels as tags from (and exporting tags as labels
+ to) p4.
+
+Performance and Internal Implementation (please report possible regressions)
+
+ * Bash completion script (in contrib/) have been cleaned up to make
+ future work on it simpler.
+
+ * An experimental "version 4" format of the index file has been
+ introduced to reduce on-disk footprint and I/O overhead.
+
+ * "git archive" learned to produce its output without reading the
+ blob object it writes out in memory in its entirety.
+
+ * "git index-pack" that runs when fetching or pushing objects to
+ complete the packfile on the receiving end learned to use multiple
+ threads to do its job when available.
+
+ * The code to compute hash values for lines used by the internal diff
+ engine was optimized on little-endian machines, using the same
+ trick the kernel folks came up with.
+
+ * "git apply" had some memory leaks plugged.
+
+ * Setting up a revision traversal with many starting points was
+ inefficient as these were placed in a date-order priority queue
+ one-by-one. Now they are collected in the queue unordered first,
+ and sorted immediately before getting used.
+
+ * More lower-level commands learned to use the streaming API to read
+ from the object store without keeping everything in core.
+
+ * The weighting parameters to suggestion command name typo have been
+ tweaked, so that "git tags" will suggest "tag?" and not "stage?".
+
+ * Because "sh" on the user's PATH may be utterly broken on some
+ systems, run-command API now uses SHELL_PATH, not /bin/sh, when
+ spawning an external command (not applicable to Windows port).
+
+ * The API to iterate over the refs/ hierarchy has been tweaked to
+ allow walking only a subset of it more efficiently.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.10
+-------------------
+
+Unless otherwise noted, all the fixes since v1.7.10 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
+
+ * "git submodule init" used to report "registered for path ..."
+ even for submodules that were registered earlier.
+ (cherry-pick c1c259e jl/submodule-report-new-path-once later to maint).
+
+ * "git diff --stat" used to fully count a binary file with modified
+ execution bits whose contents is unmodified, which was not quite
+ right.
diff --git a/Documentation/RelNotes/1.7.12.txt b/Documentation/RelNotes/1.7.12.txt
new file mode 100644
index 0000000..34f301d
--- /dev/null
+++ b/Documentation/RelNotes/1.7.12.txt
@@ -0,0 +1,123 @@
+Git v1.7.12 Release Notes
+=========================
+
+Updates since v1.7.11
+---------------------
+
+UI, Workflows & Features
+
+ * "git help" used to always default to "man" format even on platforms
+ where "man" viewer is not widely available.
+
+ * "git clone --local $path" started its life as an experiment to
+ optionally use link/copy when cloning a repository on the disk, but
+ we didn't deprecate it after we made the option a no-op to always
+ use the optimization. The command learned "--no-local" option to
+ turn this off, as a more explicit alternative over use of file://
+ URL.
+
+ * "git fetch" and friends used to say "remote side hung up
+ unexpectedly" when they failed to get response they expect from the
+ other side, but one common reason why they don't get expected
+ response is that the remote repository does not exist or cannot be
+ read. The error message in this case was updated to give better
+ hints to the user.
+
+ * git native protocol agents learned to show software version over
+ the wire, so that the server log can be examined to see the vintage
+ distribution of clients.
+
+ * "git rebase -i" learned "-x <cmd>" to insert "exec <cmd>" after
+ each commit in the resulting history.
+
+ * "git status" gives finer classification to various states of paths
+ in conflicted state and offer advice messages in its output.
+
+ * "git submodule" learned to deal with nested submodule structure
+ where a module is contained within a module whose origin is
+ specified as a relative URL to its superproject's origin.
+
+ * A rather heavy-ish "git completion" script has been split to create
+ a separate "git prompting" script, to help lazy-autoloading of the
+ completion part while making prompting part always available.
+
+
+Foreign Interface
+
+
+Performance, Internal Implementation, etc. (please report possible regressions)
+
+ * Some tests showed false failures caused by a bug in ecryptofs.
+
+ * We no longer use AsciiDoc7 syntax in our documentation and favor a
+ more modern style.
+
+ * "git index-pack" and "git pack-objects" use streaming API to read
+ from the object store to avoid having to hold a large blob object
+ in-core while they are doing their thing.
+
+ * Code to match paths with exclude patterns learned to avoid calling
+ fnmatch() by comparing fixed leading substring literally when
+ possible.
+
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.11
+-------------------
+
+Unless otherwise noted, all the fixes since v1.7.11 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
+
+ * "git clone --single-branch" to clone a single branch did not limit
+ the cloning to the specified branch.
+ (merge 0ec4b16 nd/clone-single-fix later to maint).
+
+ * "git diff --no-index" did not correctly handle relative paths and
+ did not correctly give exit codes when run under "--quiet" option.
+ (merge 304970d th/diff-no-index-fixes later to maint).
+
+ * When "git log" gets "--simplify-merges/by-decoration" together with
+ "--first-parent", the combination of these options makes the
+ simplification logic to use in-core commit objects that haven't
+ been examined for relevance, either producing incorrect result or
+ taking too long to produce any output. Teach the simplification
+ logic to ignore commits that the first-parent traversal logic
+ ignored when both are in effect to work around the issue.
+ (merge 6e513ba jc/rev-list-simplify-merges-first-parent later to maint).
+
+ * "git add" allows adding a regular file to the path where a
+ submodule used to exist, but "git update-index" does not allow an
+ equivalent operation to Porcelain writers.
+ (merge 242f55f hv/submodule-update-nuke-submodules later to maint).
+
+ * "git diff --no-index" did not work with pagers correctly.
+ (merge af63b54 jk/diff-no-index-pager later to maint).
+
+ * "git diff COPYING HEAD:COPYING" gave a nonsense error message that
+ claimed that the treeish HEAD did not have COPYING in it.
+ (merge 023e37c mm/verify-filename-fix later to maint).
+
+ * The documentation for "git cherry-pick A B..C" was misleading.
+ (merge b98878e cn/cherry-pick-range-docs later to maint).
+
+ * "git archive" incorrectly computed the header checksum; the symptom
+ was observed only when using pathnames with hi-bit set.
+ (merge a5a46eb jc/ustar-checksum-is-unsigned later to maint).
+
+ * Running "git bundle verify" on a bundle that records a complete
+ history said "it requires these 0 commits".
+ (merge 8c3710f jc/bundle-complete-notice later to maint).
+
+ * "git ls-files --exclude=t -i" did not consider anything under t/ as
+ excluded, as it did not pay attention to exclusion of leading paths
+ while walking the index. Other two users of excluded() are also
+ updated.
+ (merge 0d316f0 jc/ls-files-i-dir later to maint).
+
+ * "git request-pull $url dev" when the tip of "dev" branch was tagged
+ with "ext4-for-linus" used the contents from the tag in the output
+ but still asked the "dev" branch to be pulled, not the tag.
+ (merge 682853e jc/request-pull-match-tagname later to maint).
diff --git a/Documentation/RelNotes/1.7.7.1.txt b/Documentation/RelNotes/1.7.7.1.txt
new file mode 100644
index 0000000..ac9b838
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.1.txt
@@ -0,0 +1,60 @@
+Git v1.7.7.1 Release Notes
+==========================
+
+Fixes since v1.7.7
+------------------
+
+ * On some BSD systems, adding +s bit on directories is detrimental
+ (it is not necessary on BSD to begin with). "git init --shared"
+ has been updated to take this into account without extra makefile
+ settings on platforms the Makefile knows about.
+
+ * After incorrectly written third-party tools store a tag object in
+ HEAD, git diagnosed it as a repository corruption and refused to
+ proceed in order to avoid spreading the damage. We now gracefully
+ recover from such a situation by pretending as if the commit that
+ is pointed at by the tag were in HEAD.
+
+ * "git apply --whitespace=error" did not bother to report the exact
+ line number in the patch that introduced new blank lines at the end
+ of the file.
+
+ * "git apply --index" did not check corrupted patch.
+
+ * "git checkout $tree $directory/" resurrected paths locally removed or
+ modified only in the working tree in $directory/ that did not appear
+ in $directory of the given $tree. They should have been kept intact.
+
+ * "git diff $tree $path" used to apply the pathspec at the output stage,
+ reading the whole tree, wasting resources.
+
+ * The code to check for updated submodules during a "git fetch" of the
+ superproject had an unnecessary quadratic loop.
+
+ * "git fetch" from a large bundle did not enable the progress output.
+
+ * When "git fsck --lost-and-found" found that an empty blob object in the
+ object store is unreachable, it incorrectly reported an error after
+ writing the lost blob out successfully.
+
+ * "git filter-branch" did not refresh the index before checking that the
+ working tree was clean.
+
+ * "git grep $tree" when run with multiple threads had an unsafe access to
+ the object database that should have been protected with mutex.
+
+ * The "--ancestry-path" option to "git log" and friends misbehaved in a
+ history with complex criss-cross merges and showed an uninteresting
+ side history as well.
+
+ * Test t1304 assumed LOGNAME is always set, which may not be true on
+ some systems.
+
+ * Tests with --valgrind failed to find "mergetool" scriptlets.
+
+ * "git patch-id" miscomputed the patch-id in a patch that has a line longer
+ than 1kB.
+
+ * When an "exec" insn failed after modifying the index and/or the working
+ tree during "rebase -i", we now check and warn that the changes need to
+ be cleaned up.
diff --git a/Documentation/RelNotes/1.7.7.2.txt b/Documentation/RelNotes/1.7.7.2.txt
new file mode 100644
index 0000000..e6bbef2
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.2.txt
@@ -0,0 +1,44 @@
+Git v1.7.7.2 Release Notes
+==========================
+
+Fixes since v1.7.7.1
+--------------------
+
+ * We used to drop error messages from libcurl on certain kinds of
+ errors.
+
+ * Error report from smart HTTP transport, when the connection was
+ broken in the middle of a transfer, showed a useless message on
+ a corrupt packet.
+
+ * "git fetch --prune" was unsafe when used with refspecs from the
+ command line.
+
+ * The attribute mechanism did not use case insensitive match when
+ core.ignorecase was set.
+
+ * "git bisect" did not notice when it failed to update the working tree
+ to the next commit to be tested.
+
+ * "git config --bool --get-regexp" failed to separate the variable name
+ and its value "true" when the variable is defined without "= true".
+
+ * "git remote rename $a $b" were not careful to match the remote name
+ against $a (i.e. source side of the remote nickname).
+
+ * "git mergetool" did not use its arguments as pathspec, but as a path to
+ the file that may not even have any conflict.
+
+ * "git diff --[num]stat" used to use the number of lines of context
+ different from the default, potentially giving different results from
+ "git diff | diffstat" and confusing the users.
+
+ * "git pull" and "git rebase" did not work well even when GIT_WORK_TREE is
+ set correctly with GIT_DIR if the current directory is outside the working
+ tree.
+
+ * "git send-email" did not honor the configured hostname when restarting
+ the HELO/EHLO exchange after switching TLS on.
+
+ * "gitweb" used to produce a non-working link while showing the contents
+ of a blob, when JavaScript actions are enabled.
diff --git a/Documentation/RelNotes/1.7.7.3.txt b/Documentation/RelNotes/1.7.7.3.txt
new file mode 100644
index 0000000..09301f0
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.3.txt
@@ -0,0 +1,19 @@
+Git v1.7.7.3 Release Notes
+==========================
+
+Fixes since v1.7.7.2
+--------------------
+
+ * Adjust the "quick-install-doc" procedures as preformatted
+ html/manpage are no longer in the source repository.
+
+ * The logic to optimize the locality of the data in a pack introduced in
+ 1.7.7 was grossly inefficient.
+
+ * The logic to filter out forked projects in the project list in
+ "gitweb" was broken for some time.
+
+ * "git branch -m/-M" advertised to update RENAME_REF ref in the
+ commit log message that introduced the feature but not anywhere in
+ the documentation, and never did update such a ref anyway. This
+ undocumented misfeature that did not exist has been excised.
diff --git a/Documentation/RelNotes/1.7.7.4.txt b/Documentation/RelNotes/1.7.7.4.txt
new file mode 100644
index 0000000..e523448
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.4.txt
@@ -0,0 +1,14 @@
+Git v1.7.7.4 Release Notes
+==========================
+
+Fixes since v1.7.7.3
+--------------------
+
+ * A few header dependencies were missing from the Makefile.
+
+ * Some newer parts of the code used C99 __VA_ARGS__ while we still
+ try to cater to older compilers.
+
+ * "git name-rev --all" tried to name all _objects_, naturally failing to
+ describe many blobs and trees, instead of showing only commits as
+ advertised in its documentation.
diff --git a/Documentation/RelNotes/1.7.7.5.txt b/Documentation/RelNotes/1.7.7.5.txt
new file mode 100644
index 0000000..7b09319
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.5.txt
@@ -0,0 +1,14 @@
+Git v1.7.7.5 Release Notes
+==========================
+
+Fixes since v1.7.7.4
+--------------------
+
+ * After fetching from a remote that has very long refname, the reporting
+ output could have corrupted by overrunning a static buffer.
+
+ * "git checkout" and "git merge" treated in-tree .gitignore and exclude
+ file in $GIT_DIR/info/ directory inconsistently when deciding which
+ untracked files are ignored and expendable.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.7.6.txt b/Documentation/RelNotes/1.7.7.6.txt
new file mode 100644
index 0000000..8df606d
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.6.txt
@@ -0,0 +1,20 @@
+Git v1.7.7.6 Release Notes
+==========================
+
+Fixes since v1.7.7.5
+--------------------
+
+ * The code to look up attributes for paths reused entries from a wrong
+ directory when two paths in question are in adjacent directories and
+ the name of the one directory is a prefix of the other.
+
+ * A wildcard that matches deeper hierarchy given to the "diff-index" command,
+ e.g. "git diff-index HEAD -- '*.txt'", incorrectly reported additions of
+ matching files even when there is no change.
+
+ * When producing a "thin pack" (primarily used in bundles and smart
+ HTTP transfers) out of a fully packed repository, we unnecessarily
+ avoided sending recent objects as a delta against objects we know
+ the other side has.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.7.7.txt b/Documentation/RelNotes/1.7.7.7.txt
new file mode 100644
index 0000000..e79118d
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.7.txt
@@ -0,0 +1,13 @@
+Git v1.7.7.7 Release Notes
+==========================
+
+Fixes since v1.7.7.6
+--------------------
+
+ * An error message from 'git bundle' had an unmatched single quote pair in it.
+
+ * 'git diff --histogram' option was not described.
+
+ * 'git imap-send' carried an unused dead code.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.7.txt b/Documentation/RelNotes/1.7.7.txt
new file mode 100644
index 0000000..7655ccc
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.txt
@@ -0,0 +1,134 @@
+Git v1.7.7 Release Notes
+========================
+
+Updates since v1.7.6
+--------------------
+
+ * The scripting part of the codebase is getting prepared for i18n/l10n.
+
+ * Interix, Cygwin and Minix ports got updated.
+
+ * Various updates to git-p4 (in contrib/), fast-import, and git-svn.
+
+ * Gitweb learned to read from /etc/gitweb-common.conf when it exists,
+ before reading from gitweb_config.perl or from /etc/gitweb.conf
+ (this last one is read only when per-repository gitweb_config.perl
+ does not exist).
+
+ * Various codepaths that invoked zlib deflate/inflate assumed that these
+ functions can compress or uncompress more than 4GB data in one call on
+ platforms with 64-bit long, which has been corrected.
+
+ * Git now recognizes loose objects written by other implementations that
+ use a non-standard window size for zlib deflation (e.g. Agit running on
+ Android with 4kb window). We used to reject anything that was not
+ deflated with 32kb window.
+
+ * Interaction between the use of pager and coloring of the output has
+ been improved, especially when a command that is not built-in was
+ involved.
+
+ * "git am" learned to pass the "--exclude=<path>" option through to underlying
+ "git apply".
+
+ * You can now feed many empty lines before feeding an mbox file to
+ "git am".
+
+ * "git archive" can be told to pass the output to gzip compression and
+ produce "archive.tar.gz".
+
+ * "git bisect" can be used in a bare repository (provided that the test
+ you perform per each iteration does not need a working tree, of
+ course).
+
+ * The length of abbreviated object names in "git branch -v" output
+ now honors the core.abbrev configuration variable.
+
+ * "git check-attr" can take relative paths from the command line.
+
+ * "git check-attr" learned an "--all" option to list the attributes for a
+ given path.
+
+ * "git checkout" (both the code to update the files upon checking out a
+ different branch and the code to checkout a specific set of files) learned
+ to stream the data from object store when possible, without having to
+ read the entire contents of a file into memory first. An earlier round
+ of this code that is not in any released version had a large leak but
+ now it has been plugged.
+
+ * "git clone" can now take a "--config key=value" option to set the
+ repository configuration options that affect the initial checkout.
+
+ * "git commit <paths>..." now lets you feed relative pathspecs that
+ refer to outside your current subdirectory.
+
+ * "git diff --stat" learned a --stat-count option to limit the output of
+ a diffstat report.
+
+ * "git diff" learned a "--histogram" option to use a different diff
+ generation machinery stolen from jgit, which might give better
+ performance.
+
+ * "git diff" had a weird worst case behaviour that can be triggered
+ when comparing files with potentially many places that could match.
+
+ * "git fetch", "git push" and friends no longer show connection
+ errors for addresses that couldn't be connected to when at least one
+ address succeeds (this is arguably a regression but a deliberate
+ one).
+
+ * "git grep" learned "--break" and "--heading" options, to let users mimic
+ the output format of "ack".
+
+ * "git grep" learned a "-W" option that shows wider context using the same
+ logic used by "git diff" to determine the hunk header.
+
+ * Invoking the low-level "git http-fetch" without "-a" option (which
+ git itself never did---normal users should not have to worry about
+ this) is now deprecated.
+
+ * The "--decorate" option to "git log" and its family learned to
+ highlight grafted and replaced commits.
+
+ * "git rebase master topci" no longer spews usage hints after giving
+ the "fatal: no such branch: topci" error message.
+
+ * The recursive merge strategy implementation got a fairly large
+ fix for many corner cases that may rarely happen in real world
+ projects (it has been verified that none of the 16000+ merges in
+ the Linux kernel history back to v2.6.12 is affected with the
+ corner case bugs this update fixes).
+
+ * "git stash" learned an "--include-untracked option".
+
+ * "git submodule update" used to stop at the first error updating a
+ submodule; it now goes on to update other submodules that can be
+ updated, and reports the ones with errors at the end.
+
+ * "git push" can be told with the "--recurse-submodules=check" option to
+ refuse pushing of the supermodule, if any of its submodules'
+ commits hasn't been pushed out to their remotes.
+
+ * "git upload-pack" and "git receive-pack" learned to pretend that only a
+ subset of the refs exist in a repository. This may help a site to
+ put many tiny repositories into one repository (this would not be
+ useful for larger repositories as repacking would be problematic).
+
+ * "git verify-pack" has been rewritten to use the "index-pack" machinery
+ that is more efficient in reading objects in packfiles.
+
+ * test scripts for gitweb tried to run even when CGI-related perl modules
+ are not installed; they now exit early when the latter are unavailable.
+
+Also contains various documentation updates and minor miscellaneous
+changes.
+
+
+Fixes since v1.7.6
+------------------
+
+Unless otherwise noted, all fixes in the 1.7.6.X maintenance track are
+included in this release.
+
+ * "git branch -m" and "git checkout -b" incorrectly allowed the tip
+ of the branch that is currently checked out updated.
diff --git a/Documentation/RelNotes/1.7.8.1.txt b/Documentation/RelNotes/1.7.8.1.txt
new file mode 100644
index 0000000..33dc948
--- /dev/null
+++ b/Documentation/RelNotes/1.7.8.1.txt
@@ -0,0 +1,38 @@
+Git v1.7.8.1 Release Notes
+==========================
+
+Fixes since v1.7.8
+------------------
+
+ * In some codepaths (notably, checkout and merge), the ignore patterns
+ recorded in $GIT_DIR/info/exclude were not honored. They now are.
+
+ * "git apply --check" did not error out when given an empty input
+ without any patch.
+
+ * "git archive" mistakenly allowed remote clients to ask for commits
+ that are not at the tip of any ref.
+
+ * "git checkout" and "git merge" treated in-tree .gitignore and exclude
+ file in $GIT_DIR/info/ directory inconsistently when deciding which
+ untracked files are ignored and expendable.
+
+ * LF-to-CRLF streaming filter used when checking out a large-ish blob
+ fell into an infinite loop with a rare input.
+
+ * The function header pattern for files with "diff=cpp" attribute did
+ not consider "type *funcname(type param1,..." as the beginning of a
+ function.
+
+ * The error message from "git diff" and "git status" when they fail
+ to inspect changes in submodules did not report which submodule they
+ had trouble with.
+
+ * After fetching from a remote that has very long refname, the reporting
+ output could have corrupted by overrunning a static buffer.
+
+ * "git pack-objects" avoids creating cyclic dependencies among deltas
+ when seeing a broken packfile that records the same object in both
+ the deflated form and as a delta.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.2.txt b/Documentation/RelNotes/1.7.8.2.txt
new file mode 100644
index 0000000..e74f4ef
--- /dev/null
+++ b/Documentation/RelNotes/1.7.8.2.txt
@@ -0,0 +1,71 @@
+Git v1.7.8.2 Release Notes
+==========================
+
+Fixes since v1.7.8.1
+--------------------
+
+ * Porcelain commands like "git reset" did not distinguish deletions
+ and type-changes from ordinary modification, and reported them with
+ the same 'M' moniker. They now use 'D' (for deletion) and 'T' (for
+ type-change) to match "git status -s" and "git diff --name-status".
+
+ * The configuration file parser used for sizes (e.g. bigFileThreshold)
+ did not correctly interpret 'g' suffix.
+
+ * The replacement implemention for snprintf used on platforms with
+ native snprintf that is broken did not use va_copy correctly.
+
+ * LF-to-CRLF streaming filter replaced all LF with CRLF, which might
+ be techinically correct but not friendly to people who are trying
+ to recover from earlier mistakes of using CRLF in the repository
+ data in the first place. It now refrains from doing so for LF that
+ follows a CR.
+
+ * git native connection going over TCP (not over SSH) did not set
+ SO_KEEPALIVE option which failed to receive link layer errors.
+
+ * "git branch -m <current branch> HEAD" is an obvious no-op but was not
+ allowed.
+
+ * "git checkout -m" did not recreate the conflicted state in a "both
+ sides added, without any common ancestor version" conflict
+ situation.
+
+ * "git cherry-pick $commit" (not a range) created an unnecessary
+ sequencer state and interfered with valid workflow to use the
+ command during a session to cherry-pick multiple commits.
+
+ * You could make "git commit" segfault by giving the "--no-message"
+ option.
+
+ * "fast-import" did not correctly update an existing notes tree,
+ possibly corrupting the fan-out.
+
+ * "git fetch-pack" accepted unqualified refs that do not begin with
+ refs/ by mistake and compensated it by matching the refspec with
+ tail-match, which was doubly wrong. This broke fetching from a
+ repository with a funny named ref "refs/foo/refs/heads/master" and a
+ 'master' branch with "git fetch-pack refs/heads/master", as the
+ command incorrectly considered the former a "match".
+
+ * "git log --follow" did not honor the rename threshold score given
+ with the -M option (e.g. "-M50%").
+
+ * "git mv" gave suboptimal error/warning messages when it overwrites
+ target files. It also did not pay attention to "-v" option.
+
+ * Authenticated "git push" over dumb HTTP were broken with a recent
+ change and failed without asking for password when username is
+ given.
+
+ * "git push" to an empty repository over HTTP were broken with a
+ recent change to the ref handling.
+
+ * "git push -v" forgot how to be verbose by mistake. It now properly
+ becomes verbose when asked to.
+
+ * When a "reword" action in "git rebase -i" failed to run "commit --amend",
+ we did not give the control back to the user to resolve the situation, and
+ instead kept the original commit log message.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.3.txt b/Documentation/RelNotes/1.7.8.3.txt
new file mode 100644
index 0000000..a92714c
--- /dev/null
+++ b/Documentation/RelNotes/1.7.8.3.txt
@@ -0,0 +1,16 @@
+Git v1.7.8.3 Release Notes
+==========================
+
+Fixes since v1.7.8.2
+--------------------
+
+ * Attempt to fetch from an empty file pretending it to be a bundle did
+ not error out correctly.
+
+ * gitweb did not correctly fall back to configured $fallback_encoding
+ that is not 'latin1'.
+
+ * "git clone --depth $n" did not catch a non-number given as $n as an
+ error.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.4.txt b/Documentation/RelNotes/1.7.8.4.txt
new file mode 100644
index 0000000..9bebdbf
--- /dev/null
+++ b/Documentation/RelNotes/1.7.8.4.txt
@@ -0,0 +1,23 @@
+Git v1.7.8.4 Release Notes
+==========================
+
+Fixes since v1.7.8.3
+--------------------
+
+ * The code to look up attributes for paths reused entries from a wrong
+ directory when two paths in question are in adjacent directories and
+ the name of the one directory is a prefix of the other.
+
+ * A wildcard that matches deeper hierarchy given to the "diff-index" command,
+ e.g. "git diff-index HEAD -- '*.txt'", incorrectly reported additions of
+ matching files even when there is no change.
+
+ * When producing a "thin pack" (primarily used in bundles and smart
+ HTTP transfers) out of a fully packed repository, we unnecessarily
+ avoided sending recent objects as a delta against objects we know
+ the other side has.
+
+ * "git send-email" did not properly treat sendemail.multiedit as a
+ boolean (e.g. setting it to "false" did not turn it off).
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.5.txt b/Documentation/RelNotes/1.7.8.5.txt
new file mode 100644
index 0000000..011fd2a
--- /dev/null
+++ b/Documentation/RelNotes/1.7.8.5.txt
@@ -0,0 +1,19 @@
+Git v1.7.8.5 Release Notes
+==========================
+
+Fixes since v1.7.8.4
+--------------------
+
+ * Dependency on our thread-utils.h header file was missing for
+ objects that depend on it in the Makefile.
+
+ * "git am" when fed an empty file did not correctly finish reading it
+ when it attempts to guess the input format.
+
+ * "git grep -P" (when PCRE is enabled in the build) did not match the
+ beginning and the end of the line correctly with ^ and $.
+
+ * "git rebase -m" tried to run "git notes copy" needlessly when
+ nothing was rewritten.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.6.txt b/Documentation/RelNotes/1.7.8.6.txt
new file mode 100644
index 0000000..d9bf2b7
--- /dev/null
+++ b/Documentation/RelNotes/1.7.8.6.txt
@@ -0,0 +1,22 @@
+Git v1.7.8.6 Release Notes
+==========================
+
+Fixes since v1.7.8.5
+--------------------
+
+ * An error message from 'git bundle' had an unmatched single quote pair in it.
+
+ * 'git diff --histogram' option was not described.
+
+ * Documentation for 'git rev-list' had minor formatting errors.
+
+ * 'git imap-send' carried an unused dead code.
+
+ * The way 'git fetch' implemented its connectivity check over
+ received objects was overly pessimistic, and wasted a lot of
+ cycles.
+
+ * Various minor backports of fixes from the 'master' and the 'maint'
+ branch.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.txt b/Documentation/RelNotes/1.7.8.txt
new file mode 100644
index 0000000..b4d90bb
--- /dev/null
+++ b/Documentation/RelNotes/1.7.8.txt
@@ -0,0 +1,161 @@
+Git v1.7.8 Release Notes
+========================
+
+Updates since v1.7.7
+--------------------
+
+ * Some git-svn, git-gui, git-p4 (in contrib) and msysgit updates.
+
+ * Updates to bash completion scripts.
+
+ * The build procedure has been taught to take advantage of computed
+ dependency automatically when the complier supports it.
+
+ * The date parser now accepts timezone designators that lack minutes
+ part and also has a colon between "hh:mm".
+
+ * The contents of the /etc/mailname file, if exists, is used as the
+ default value of the hostname part of the committer/author e-mail.
+
+ * "git am" learned how to read from patches generated by Hg.
+
+ * "git archive" talking with a remote repository can report errors
+ from the remote side in a more informative way.
+
+ * "git branch" learned an explicit --list option to ask for branches
+ listed, optionally with a glob matching pattern to limit its output.
+
+ * "git check-attr" learned "--cached" option to look at .gitattributes
+ files from the index, not from the working tree.
+
+ * Variants of "git cherry-pick" and "git revert" that take multiple
+ commits learned to "--continue" and "--abort".
+
+ * "git daemon" gives more human readble error messages to clients
+ using ERR packets when appropriate.
+
+ * Errors at the network layer is logged by "git daemon".
+
+ * "git diff" learned "--minimal" option to spend extra cycles to come
+ up with a minimal patch output.
+
+ * "git diff" learned "--function-context" option to show the whole
+ function as context that was affected by a change.
+
+ * "git difftool" can be told to skip launching the tool for a path by
+ answering 'n' to its prompt.
+
+ * "git fetch" learned to honor transfer.fsckobjects configuration to
+ validate the objects that were received from the other end, just like
+ "git receive-pack" (the receiving end of "git push") does.
+
+ * "git fetch" makes sure that the set of objects it received from the
+ other end actually completes the history before updating the refs.
+ "git receive-pack" (the receiving end of "git push") learned to do the
+ same.
+
+ * "git fetch" learned that fetching/cloning from a regular file on the
+ filesystem is not necessarily a request to unpack a bundle file; the
+ file could be ".git" with "gitdir: <path>" in it.
+
+ * "git for-each-ref" learned "%(contents:subject)", "%(contents:body)"
+ and "%(contents:signature)". The last one is useful for signed tags.
+
+ * "git grep" used to incorrectly pay attention to .gitignore files
+ scattered in the directory it was working in even when "--no-index"
+ option was used. It no longer does this. The "--exclude-standard"
+ option needs to be given to explicitly activate the ignore
+ mechanism.
+
+ * "git grep" learned "--untracked" option, where given patterns are
+ searched in untracked (but not ignored) files as well as tracked
+ files in the working tree, so that matches in new but not yet
+ added files do not get missed.
+
+ * The recursive merge backend no longer looks for meaningless
+ existing merges in submodules unless in the outermost merge.
+
+ * "git log" and friends learned "--children" option.
+
+ * "git ls-remote" learned to respond to "-h"(elp) requests.
+
+ * "mediawiki" remote helper can interact with (surprise!) MediaWiki
+ with "git fetch" & "git push".
+
+ * "git merge" learned the "--edit" option to allow users to edit the
+ merge commit log message.
+
+ * "git rebase -i" can be told to use special purpose editor suitable
+ only for its insn sheet via sequence.editor configuration variable.
+
+ * "git send-email" learned to respond to "-h"(elp) requests.
+
+ * "git send-email" allows the value given to sendemail.aliasfile to begin
+ with "~/" to refer to the $HOME directory.
+
+ * "git send-email" forces use of Authen::SASL::Perl to work around
+ issues between Authen::SASL::Cyrus and AUTH PLAIN/LOGIN.
+
+ * "git stash" learned "--include-untracked" option to stash away
+ untracked/ignored cruft from the working tree.
+
+ * "git submodule clone" does not leak an error message to the UI
+ level unnecessarily anymore.
+
+ * "git submodule update" learned to honor "none" as the value for
+ submodule.<name>.update to specify that the named submodule should
+ not be checked out by default.
+
+ * When populating a new submodule directory with "git submodule init",
+ the $GIT_DIR metainformation directory for submodules is created inside
+ $GIT_DIR/modules/<name>/ directory of the superproject and referenced
+ via the gitfile mechanism. This is to make it possible to switch
+ between commits in the superproject that has and does not have the
+ submodule in the tree without re-cloning.
+
+ * "gitweb" leaked unescaped control characters from syntax hiliter
+ outputs.
+
+ * "gitweb" can be told to give custom string at the end of the HTML
+ HEAD element.
+
+ * "gitweb" now has its own manual pages.
+
+
+Also contains other documentation updates and minor code cleanups.
+
+
+Fixes since v1.7.7
+------------------
+
+Unless otherwise noted, all fixes in the 1.7.7.X maintenance track are
+included in this release.
+
+ * HTTP transport did not use pushurl correctly, and also did not tell
+ what host it is trying to authenticate with when asking for
+ credentials.
+ (merge deba493 jk/http-auth later to maint).
+
+ * "git blame" was aborted if started from an uncommitted content and
+ the path had the textconv filter in effect.
+ (merge 8518088 ss/blame-textconv-fake-working-tree later to maint).
+
+ * Adding many refs to the local repository in one go (e.g. "git fetch"
+ that fetches many tags) and looking up a ref by name in a repository
+ with too many refs were unnecessarily slow.
+ (merge 17d68a54d jp/get-ref-dir-unsorted later to maint).
+
+ * Report from "git commit" on untracked files was confused under
+ core.ignorecase option.
+ (merge 395c7356 jk/name-hash-dirent later to maint).
+
+ * "git merge" did not understand ":/<pattern>" as a way to name a commit.
+
+ " "git push" on the receiving end used to call post-receive and post-update
+ hooks for attempted removal of non-existing refs.
+ (merge 160b81ed ph/push-to-delete-nothing later to maint).
+
+ * Help text for "git remote set-url" and "git remote set-branches"
+ were misspelled.
+ (merge c49904e fc/remote-seturl-usage-fix later to maint).
+ (merge 656cdf0 jc/remote-setbranches-usage-fix later to maint).
diff --git a/Documentation/RelNotes/1.7.9.1.txt b/Documentation/RelNotes/1.7.9.1.txt
new file mode 100644
index 0000000..6957183
--- /dev/null
+++ b/Documentation/RelNotes/1.7.9.1.txt
@@ -0,0 +1,63 @@
+Git v1.7.9.1 Release Notes
+==========================
+
+Fixes since v1.7.9
+------------------
+
+ * The makefile allowed environment variable X seep into it result in
+ command names suffixed with unnecessary strings.
+
+ * The set of included header files in compat/inet-{ntop,pton}
+ wrappers was updated for Windows some time ago, but in a way that
+ broke Solaris build.
+
+ * rpmbuild noticed an unpackaged but installed *.mo file and failed.
+
+ * Subprocesses spawned from various git programs were often left running
+ to completion even when the top-level process was killed.
+
+ * "git add -e" learned not to show a diff for an otherwise unmodified
+ submodule that only has uncommitted local changes in the patch
+ prepared by for the user to edit.
+
+ * Typo in "git branch --edit-description my-tpoic" was not diagnosed.
+
+ * Using "git grep -l/-L" together with options -W or --break may not
+ make much sense as the output is to only count the number of hits
+ and there is no place for file breaks, but the latter options made
+ "-l/-L" to miscount the hits.
+
+ * "git log --first-parent $pathspec" did not stay on the first parent
+ chain and veered into side branch from which the whole change to the
+ specified paths came.
+
+ * "git merge --no-edit $tag" failed to honor the --no-edit option.
+
+ * "git merge --ff-only $tag" failed because it cannot record the
+ required mergetag without creating a merge, but this is so common
+ operation for branch that is used _only_ to follow the upstream, so
+ it was changed to allow fast-forwarding without recording the mergetag.
+
+ * "git mergetool" now gives an empty file as the common base version
+ to the backend when dealing with the "both sides added, differently"
+ case.
+
+ * "git push -q" was not sufficiently quiet.
+
+ * When "git push" fails to update any refs, the client side did not
+ report an error correctly to the end user.
+
+ * "rebase" and "commit --amend" failed to work on commits with ancient
+ timestamps near year 1970.
+
+ * When asking for a tag to be pulled, "request-pull" did not show the
+ name of the tag prefixed with "tags/", which would have helped older
+ clients.
+
+ * "git submodule add $path" forgot to recompute the name to be stored
+ in .gitmodules when the submodule at $path was once added to the
+ superproject and already initialized.
+
+ * Many small corner case bugs on "git tag -n" was corrected.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.2.txt b/Documentation/RelNotes/1.7.9.2.txt
new file mode 100644
index 0000000..e500da7
--- /dev/null
+++ b/Documentation/RelNotes/1.7.9.2.txt
@@ -0,0 +1,69 @@
+Git v1.7.9.2 Release Notes
+==========================
+
+Fixes since v1.7.9.1
+--------------------
+
+ * Bash completion script (in contrib/) did not like a pattern that
+ begins with a dash to be passed to __git_ps1 helper function.
+
+ * Adaptation of the bash completion script (in contrib/) for zsh
+ incorrectly listed all subcommands when "git <TAB><TAB>" was given
+ to ask for list of porcelain subcommands.
+
+ * The build procedure for profile-directed optimized binary was not
+ working very well.
+
+ * Some systems need to explicitly link -lcharset to get locale_charset().
+
+ * t5541 ignored user-supplied port number used for HTTP server testing.
+
+ * The error message emitted when we see an empty loose object was
+ not phrased correctly.
+
+ * The code to ask for password did not fall back to the terminal
+ input when GIT_ASKPASS is set but does not work (e.g. lack of X
+ with GUI askpass helper).
+
+ * We failed to give the true terminal width to any subcommand when
+ they are invoked with the pager, i.e. "git -p cmd".
+
+ * map_user() was not rewriting its output correctly, which resulted
+ in the user visible symptom that "git blame -e" sometimes showed
+ excess '>' at the end of email addresses.
+
+ * "git checkout -b" did not allow switching out of an unborn branch.
+
+ * When you have both .../foo and .../foo.git, "git clone .../foo" did not
+ favor the former but the latter.
+
+ * "git commit" refused to create a commit when entries added with
+ "add -N" remained in the index, without telling Git what their content
+ in the next commit should be. We should have created the commit without
+ these paths.
+
+ * "git diff --stat" said "files", "insertions", and "deletions" even
+ when it is showing one "file", one "insertion" or one "deletion".
+
+ * The output from "git diff --stat" for two paths that have the same
+ amount of changes showed graph bars of different length due to the
+ way we handled rounding errors.
+
+ * "git grep" did not pay attention to -diff (hence -binary) attribute.
+
+ * The transport programs (fetch, push, clone)ignored --no-progress
+ and showed progress when sending their output to a terminal.
+
+ * Sometimes error status detected by a check in an earlier phase of
+ "git receive-pack" (the other end of "git push") was lost by later
+ checks, resulting in false indication of success.
+
+ * "git rev-list --verify" sometimes skipped verification depending on
+ the phase of the moon, which dates back to 1.7.8.x series.
+
+ * Search box in "gitweb" did not accept non-ASCII characters correctly.
+
+ * Search interface of "gitweb" did not show multiple matches in the same file
+ correctly.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.3.txt b/Documentation/RelNotes/1.7.9.3.txt
new file mode 100644
index 0000000..91c6501
--- /dev/null
+++ b/Documentation/RelNotes/1.7.9.3.txt
@@ -0,0 +1,51 @@
+Git v1.7.9.3 Release Notes
+==========================
+
+Fixes since v1.7.9.2
+--------------------
+
+ * "git p4" (in contrib/) submit the changes to a wrong place when the
+ "--use-client-spec" option is set.
+
+ * The config.mak.autogen generated by optional autoconf support tried
+ to link the binary with -lintl even when libintl.h is missing from
+ the system.
+
+ * When the filter driver exits before reading the content before the
+ main git process writes the contents to be filtered to the pipe to
+ it, the latter could be killed with SIGPIPE instead of ignoring
+ such an event as an error.
+
+ * "git add --refresh <pathspec>" used to warn about unmerged paths
+ outside the given pathspec.
+
+ * The bulk check-in codepath in "git add" streamed contents that
+ needs smudge/clean filters without running them, instead of punting
+ and delegating to the codepath to run filters after slurping
+ everything to core.
+
+ * "git branch --with $that" assumed incorrectly that the user will never
+ ask the question with nonsense value in $that.
+
+ * "git bundle create" produced a corrupt bundle file upon seeing
+ commits with excessively long subject line.
+
+ * When a remote helper exits before reading the blank line from the
+ main git process to signal the end of commands, the latter could be
+ killed with SIGPIPE. Instead we should ignore such event as a
+ non-error.
+
+ * The commit log template given with "git merge --edit" did not have
+ a short instructive text like what "git commit" gives.
+
+ * "git rev-list --verify-objects -q" omitted the extra verification
+ it needs to do over "git rev-list --objects -q" by mistake.
+
+ * "gitweb" used to drop warnings in the log file when "heads" view is
+ accessed in a repository whose HEAD does not point at a valid
+ branch.
+
+ * An invalid regular expression pattern given by an end user made
+ "gitweb" to return garbled response.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.4.txt b/Documentation/RelNotes/1.7.9.4.txt
new file mode 100644
index 0000000..e5217a1
--- /dev/null
+++ b/Documentation/RelNotes/1.7.9.4.txt
@@ -0,0 +1,24 @@
+Git v1.7.9.4 Release Notes
+==========================
+
+Fixes since v1.7.9.3
+--------------------
+
+ * The code to synthesize the fake ancestor tree used by 3-way merge
+ fallback in "git am" was not prepared to read a patch created with
+ a non-standard -p<num> value.
+
+ * "git bundle" did not record boundary commits correctly when there
+ are many of them.
+
+ * "git diff-index" and its friends at the plumbing level showed the
+ "diff --git" header and nothing else for a path whose cached stat
+ info is dirty without actual difference when asked to produce a
+ patch. This was a longstanding bug that we could have fixed long
+ time ago.
+
+ * "gitweb" did use quotemeta() to prepare search string when asked to
+ do a fixed-string project search, but did not use it by mistake and
+ used the user-supplied string instead.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.5.txt b/Documentation/RelNotes/1.7.9.5.txt
new file mode 100644
index 0000000..95cc2bb
--- /dev/null
+++ b/Documentation/RelNotes/1.7.9.5.txt
@@ -0,0 +1,23 @@
+Git v1.7.9.5 Release Notes
+==========================
+
+Fixes since v1.7.9.4
+--------------------
+
+ * When "git config" diagnoses an error in a configuration file and
+ shows the line number for the offending line, it miscounted if the
+ error was at the end of line.
+
+ * "git fast-import" accepted "ls" command with an empty path by
+ mistake.
+
+ * Various new-ish output decoration modes of "git grep" were not
+ documented in the manual's synopsis section.
+
+ * The "remaining" subcommand to "git rerere" was not documented.
+
+ * "gitweb" used to drop warnings in the log file when "heads" view is
+ accessed in a repository whose HEAD does not point at a valid
+ branch.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.6.txt b/Documentation/RelNotes/1.7.9.6.txt
new file mode 100644
index 0000000..74bf882
--- /dev/null
+++ b/Documentation/RelNotes/1.7.9.6.txt
@@ -0,0 +1,12 @@
+Git v1.7.9.6 Release Notes
+==========================
+
+Fixes since v1.7.9.5
+--------------------
+
+ * "git merge $tag" to merge an annotated tag always opens the editor
+ during an interactive edit session. v1.7.10 series introduced an
+ environment variable GIT_MERGE_AUTOEDIT to help older scripts decline
+ this behaviour, but the maintenance track should also support it.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.7.txt b/Documentation/RelNotes/1.7.9.7.txt
new file mode 100644
index 0000000..59667d0
--- /dev/null
+++ b/Documentation/RelNotes/1.7.9.7.txt
@@ -0,0 +1,13 @@
+Git v1.7.9.7 Release Notes
+==========================
+
+Fixes since v1.7.9.6
+--------------------
+
+ * An error message from 'git bundle' had an unmatched single quote pair in it.
+
+ * The way 'git fetch' implemented its connectivity check over
+ received objects was overly pessimistic, and wasted a lot of
+ cycles.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.9.txt b/Documentation/RelNotes/1.7.9.txt
new file mode 100644
index 0000000..95320aa
--- /dev/null
+++ b/Documentation/RelNotes/1.7.9.txt
@@ -0,0 +1,112 @@
+Git v1.7.9 Release Notes
+========================
+
+Updates since v1.7.8
+--------------------
+
+ * gitk updates accumulated since early 2011.
+
+ * git-gui updated to 0.16.0.
+
+ * git-p4 (in contrib/) updates.
+
+ * Git uses gettext to translate its most common interface messages
+ into the user's language if translations are available and the
+ locale is appropriately set. Distributors can drop new PO files
+ in po/ to add new translations.
+
+ * The code to handle username/password for HTTP transactions used in
+ "git push" & "git fetch" learned to talk "credential API" to
+ external programs to cache or store them, to allow integration with
+ platform native keychain mechanisms.
+
+ * The input prompts in the terminal use our own getpass() replacement
+ when possible. HTTP transactions used to ask for the username without
+ echoing back what was typed, but with this change you will see it as
+ you type.
+
+ * The internals of "revert/cherry-pick" have been tweaked to prepare
+ building more generic "sequencer" on top of the implementation that
+ drives them.
+
+ * "git rev-parse FETCH_HEAD" after "git fetch" without specifying
+ what to fetch from the command line will now show the commit that
+ would be merged if the command were "git pull".
+
+ * "git add" learned to stream large files directly into a packfile
+ instead of writing them into individual loose object files.
+
+ * "git checkout -B <current branch> <elsewhere>" is a more intuitive
+ way to spell "git reset --keep <elsewhere>".
+
+ * "git checkout" and "git merge" learned "--no-overwrite-ignore" option
+ to tell Git that untracked and ignored files are not expendable.
+
+ * "git commit --amend" learned "--no-edit" option to say that the
+ user is amending the tree being recorded, without updating the
+ commit log message.
+
+ * "git commit" and "git reset" re-learned the optimization to prime
+ the cache-tree information in the index, which makes it faster to
+ write a tree object out after the index entries are updated.
+
+ * "git commit" detects and rejects an attempt to stuff NUL byte in
+ the commit log message.
+
+ * "git commit" learned "-S" to GPG-sign the commit; this can be shown
+ with the "--show-signature" option to "git log".
+
+ * fsck and prune are relatively lengthy operations that still go
+ silent while making the end-user wait. They learned to give progress
+ output like other slow operations.
+
+ * The set of built-in function-header patterns for various languages
+ knows MATLAB.
+
+ * "git log --format='<format>'" learned new %g[nNeE] specifiers to
+ show information from the reflog entries when walking the reflog
+ (i.e. with "-g").
+
+ * "git pull" can be used to fetch and merge an annotated/signed tag,
+ instead of the tip of a topic branch. The GPG signature from the
+ signed tag is recorded in the resulting merge commit for later
+ auditing.
+
+ * "git log" learned "--show-signature" option to show the signed tag
+ that was merged that is embedded in the merge commit. It also can
+ show the signature made on the commit with "git commit -S".
+
+ * "git branch --edit-description" can be used to add descriptive text
+ to explain what a topic branch is about.
+
+ * "git fmt-merge-msg" learned to take the branch description into
+ account when preparing a merge summary that "git merge" records
+ when merging a local branch.
+
+ * "git request-pull" has been updated to convey more information
+ useful for integrators to decide if a topic is worth merging and
+ what is pulled is indeed what the requestor asked to pull,
+ including:
+
+ - the tip of the branch being requested to be merged;
+ - the branch description describing what the topic is about;
+ - the contents of the annotated tag, when requesting to pull a tag.
+
+ * "git pull" learned to notice 'pull.rebase' configuration variable,
+ which serves as a global fallback for setting 'branch.<name>.rebase'
+ configuration variable per branch.
+
+ * "git tag" learned "--cleanup" option to control how the whitespaces
+ and empty lines in tag message are cleaned up.
+
+ * "gitweb" learned to show side-by-side diff.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.8
+------------------
+
+Unless otherwise noted, all the fixes since v1.7.8 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
index aea8627..a26d245 100644
--- a/Documentation/asciidoc.conf
+++ b/Documentation/asciidoc.conf
@@ -90,6 +90,8 @@ endif::backend-docbook[]
endif::doctype-manpage[]
ifdef::backend-xhtml11[]
+[attributes]
+git-relative-html-prefix=
[linkgit-inlinemacro]
-<a href="{target}.html">{target}{0?({0})}</a>
+<a href="{git-relative-html-prefix}{target}.html">{target}{0?({0})}</a>
endif::backend-xhtml11[]
diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index e76195a..d4a51da 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -117,5 +117,4 @@ commit. And the default value is 40. If there are more than one
take effect.
-h::
---help::
Show help message.
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 27b57d2..c6ff15e 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -12,8 +12,9 @@ The configuration variables are used by both the git plumbing
and the porcelains. The variables are divided into sections, wherein
the fully qualified variable name of the variable itself is the last
dot-separated segment and the section name is everything before the last
-dot. The variable names are case-insensitive and only alphanumeric
-characters are allowed. Some variables may appear multiple times.
+dot. The variable names are case-insensitive, allow only alphanumeric
+characters and `-`, and must start with an alphabetic character. Some
+variables may appear multiple times.
Syntax
~~~~~~
@@ -45,17 +46,19 @@ lines. Variables may belong directly to a section or to a given subsection.
You can have `[section]` if you have `[section "subsection"]`, but you
don't need to.
-There is also a case insensitive alternative `[section.subsection]` syntax.
-In this syntax, subsection names follow the same restrictions as for section
-names.
+There is also a deprecated `[section.subsection]` syntax. With this
+syntax, the subsection name is converted to lower-case and is also
+compared case sensitively. These subsection names follow the same
+restrictions as section names.
All the other lines (and the remainder of the line after the section
header) are recognized as setting variables, in the form
'name = value'. If there is no equal sign on the line, the entire line
is taken as 'name' and the variable is recognized as boolean "true".
-The variable names are case-insensitive and only alphanumeric
-characters and `-` are allowed. There can be more than one value
-for a given variable; we say then that variable is multivalued.
+The variable names are case-insensitive, allow only alphanumeric characters
+and `-`, and must start with an alphabetic character. There can be more
+than one value for a given variable; we say then that the variable is
+multivalued.
Leading and trailing whitespace in a variable value is discarded.
Internal whitespace within a variable value is retained verbatim.
@@ -83,6 +86,19 @@ customary UNIX fashion.
Some variables may require a special value format.
+Includes
+~~~~~~~~
+
+You can include one config file from another by setting the special
+`include.path` variable to the name of the file to be included. The
+included file is expanded immediately, as if its contents had been
+found at the location of the include directive. If the value of the
+`include.path` variable is a relative path, the path is considered to be
+relative to the configuration file in which the include directive was
+found. The value of `include.path` is subject to tilde expansion: `~/`
+is expanded to the value of `$HOME`, and `~user/` to the specified
+user's home directory. See below for examples.
+
Example
~~~~~~~
@@ -105,6 +121,11 @@ Example
gitProxy="ssh" for "kernel.org"
gitProxy=default-proxy ; for the rest
+ [include]
+ path = /path/to/foo.inc ; include by absolute path
+ path = foo ; expand "foo" relative to the current file
+ path = ~/foo ; expand "foo" in your $HOME directory
+
Variables
~~~~~~~~~
@@ -114,40 +135,53 @@ in the appropriate manual page. You will find a description of non-core
porcelain configuration variables in the respective porcelain documentation.
advice.*::
- When set to 'true', display the given optional help message.
- When set to 'false', do not display. The configuration variables
- are:
+ These variables control various optional help messages designed to
+ aid new users. All 'advice.*' variables default to 'true', and you
+ can tell Git that you do not need help by setting these to 'false':
+
--
pushNonFastForward::
- Advice shown when linkgit:git-push[1] refuses
- non-fast-forward refs. Default: true.
+ Set this variable to 'false' if you want to disable
+ 'pushNonFFCurrent', 'pushNonFFDefault', and
+ 'pushNonFFMatching' simultaneously.
+ pushNonFFCurrent::
+ Advice shown when linkgit:git-push[1] fails due to a
+ non-fast-forward update to the current branch.
+ pushNonFFDefault::
+ Advice to set 'push.default' to 'upstream' or 'current'
+ when you ran linkgit:git-push[1] and pushed 'matching
+ refs' by default (i.e. you did not provide an explicit
+ refspec, and no 'push.default' configuration was set)
+ and it resulted in a non-fast-forward error.
+ pushNonFFMatching::
+ Advice shown when you ran linkgit:git-push[1] and pushed
+ 'matching refs' explicitly (i.e. you used ':', or
+ specified a refspec that isn't your current branch) and
+ it resulted in a non-fast-forward error.
statusHints::
- Directions on how to stage/unstage/add shown in the
- output of linkgit:git-status[1] and the template shown
- when writing commit messages. Default: true.
+ Show directions on how to proceed from the current
+ state in the output of linkgit:git-status[1] and in
+ the template shown when writing commit messages in
+ linkgit:git-commit[1].
commitBeforeMerge::
Advice shown when linkgit:git-merge[1] refuses to
merge to avoid overwriting local changes.
- Default: true.
resolveConflict::
Advices shown by various commands when conflicts
prevent the operation from being performed.
- Default: true.
implicitIdentity::
Advice on how to set your identity configuration when
your information is guessed from the system username and
- domain name. Default: true.
-
+ domain name.
detachedHead::
- Advice shown when you used linkgit::git-checkout[1] to
+ Advice shown when you used linkgit:git-checkout[1] to
move to the detach HEAD state, to instruct how to create
- a local branch after the fact. Default: true.
+ a local branch after the fact.
--
core.fileMode::
If false, the executable bit differences between the index and
- the working copy are ignored; useful on broken filesystems like FAT.
+ the working tree are ignored; useful on broken filesystems like FAT.
See linkgit:git-update-index[1].
+
The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
@@ -179,7 +213,7 @@ is created.
core.trustctime::
If false, the ctime differences between the index and the
- working copy are ignored; useful when the inode change time
+ working tree are ignored; useful when the inode change time
is regularly modified by something outside Git (file system
crawlers and some backup systems).
See linkgit:git-update-index[1]. True by default.
@@ -292,7 +326,7 @@ core.ignoreStat::
If true, commands which modify both the working tree and the index
will mark the updated paths with the "assume unchanged" bit in the
index. These marked files are then assumed to stay unchanged in the
- working copy, until you mark them otherwise manually - Git will not
+ working tree, 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 linkgit:git-update-index[1].
@@ -448,9 +482,11 @@ Common unit suffixes of 'k', 'm', or 'g' are supported.
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. "{tilde}/" is expanded
- to the value of `$HOME` and "{tilde}user/" to the specified user's
- home directory. See linkgit:gitignore[5].
+ of files which are not meant to be tracked. "`~/`" is expanded
+ to the value of `$HOME` and "`~user/`" to the specified user's
+ home directory. Its default value is $XDG_CONFIG_HOME/git/ignore.
+ If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/ignore
+ is used instead. See linkgit:gitignore[5].
core.askpass::
Some commands (e.g. svn and http interfaces) that interactively
@@ -465,7 +501,9 @@ core.attributesfile::
In addition to '.gitattributes' (per-directory) and
'.git/info/attributes', git looks into this file for attributes
(see linkgit:gitattributes[5]). Path expansions are made the same
- way as for `core.excludesfile`.
+ way as for `core.excludesfile`. Its default value is
+ $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME is either not
+ set or empty, $HOME/.config/git/attributes is used instead.
core.editor::
Commands such as `commit` and `tag` that lets you edit
@@ -473,6 +511,12 @@ core.editor::
variable when it is set, and the environment variable
`GIT_EDITOR` is not set. See linkgit:git-var[1].
+sequence.editor::
+ Text editor used by `git rebase -i` for editing the rebase insn file.
+ The value is meant to be interpreted by the shell when it is used.
+ It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable.
+ When not configured the default commit message editor is used instead.
+
core.pager::
The command that git will use to paginate output. Can
be overridden with the `GIT_PAGER` environment
@@ -670,10 +714,12 @@ branch.<name>.mergeoptions::
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 when
- "git pull" is run.
- *NOTE*: this is a possibly dangerous operation; do *not* use
- it unless you understand the implications (see linkgit:git-rebase[1]
- for details).
+ "git pull" is run. See "pull.rebase" for doing this in a non
+ branch-specific manner.
++
+*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>.cmd::
Specify the command to invoke the specified browser. The
@@ -815,6 +861,44 @@ color.ui::
`never` if you prefer git commands not to use color unless enabled
explicitly with some other configuration or the `--color` option.
+column.ui::
+ Specify whether supported commands should output in columns.
+ This variable consists of a list of tokens separated by spaces
+ or commas:
++
+--
+`always`;;
+ always show in columns
+`never`;;
+ never show in columns
+`auto`;;
+ show in columns if the output is to the terminal
+`column`;;
+ fill columns before rows (default)
+`row`;;
+ fill rows before columns
+`plain`;;
+ show in one column
+`dense`;;
+ make unequal size columns to utilize more space
+`nodense`;;
+ make equal size columns
+--
++
+This option defaults to 'never'.
+
+column.branch::
+ Specify whether to output branch listing in `git branch` in columns.
+ See `column.ui` for details.
+
+column.status::
+ Specify whether to output untracked files in `git status` in columns.
+ See `column.ui` for details.
+
+column.tag::
+ Specify whether to output tag listing in `git tag` in columns.
+ See `column.ui` for details.
+
commit.status::
A boolean to enable/disable inclusion of status information in the
commit message template when using an editor to prepare the commit
@@ -822,9 +906,32 @@ commit.status::
commit.template::
Specify a file to use as the template for new commit messages.
- "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
+ "`~/`" is expanded to the value of `$HOME` and "`~user/`" to the
specified user's home directory.
+credential.helper::
+ Specify an external helper to be called when a username or
+ password credential is needed; the helper may consult external
+ storage to avoid prompting the user for the credentials. See
+ linkgit:gitcredentials[7] for details.
+
+credential.useHttpPath::
+ When acquiring credentials, consider the "path" component of an http
+ or https URL to be important. Defaults to false. See
+ linkgit:gitcredentials[7] for more information.
+
+credential.username::
+ If no username is set for a network authentication, use this username
+ by default. See credential.<context>.* below, and
+ linkgit:gitcredentials[7].
+
+credential.<url>.*::
+ Any of the credential.* options above can be applied selectively to
+ some credentials. For example "credential.https://example.com.username"
+ would set the default username only for https connections to
+ example.com. See linkgit:gitcredentials[7] for details on how URLs are
+ matched.
+
include::diff-config.txt[]
difftool.<tool>.path::
@@ -857,6 +964,13 @@ fetch.recurseSubmodules::
when its superproject retrieves a commit that updates the submodule's
reference.
+fetch.fsckObjects::
+ If it is set to true, git-fetch-pack will check all fetched
+ objects. It will abort in the case of a malformed object or a
+ broken link. The result of an abort are only dangling objects.
+ Defaults to false. If not set, the value of `transfer.fsckObjects`
+ is used instead.
+
fetch.unpackLimit::
If the number of objects fetched over the git native
transfer is below this
@@ -917,7 +1031,7 @@ format.thread::
a boolean value, or `shallow` or `deep`. `shallow` threading
makes every mail a reply to the head of the series,
where the head is chosen from the cover letter, the
- `\--in-reply-to`, and the first patch mail, in this order.
+ `--in-reply-to`, and the first patch mail, in this order.
`deep` threading makes every mail a reply to the previous one.
A true boolean value is the same as `shallow`, and a false
value disables threading.
@@ -1064,12 +1178,40 @@ All gitcvs variables except for 'gitcvs.usecrlfattr' and
is one of "ext" and "pserver") to make them apply only for the given
access method.
+gitweb.category::
+gitweb.description::
+gitweb.owner::
+gitweb.url::
+ See linkgit:gitweb[1] for description.
+
+gitweb.avatar::
+gitweb.blame::
+gitweb.grep::
+gitweb.highlight::
+gitweb.patches::
+gitweb.pickaxe::
+gitweb.remote_heads::
+gitweb.showsizes::
+gitweb.snapshot::
+ See linkgit:gitweb.conf[5] for description.
+
grep.lineNumber::
If set to true, enable '-n' option by default.
grep.extendedRegexp::
If set to true, enable '--extended-regexp' option by default.
+gpg.program::
+ Use this custom program instead of "gpg" found on $PATH when
+ making or verifying a PGP signature. The program must support the
+ same command line interface as GPG, namely, to verify a detached
+ signature, "gpg --verify $file - <$signature" is run, and the
+ program is expected to signal a good signature by exiting with
+ code 0, and to generate an ascii-armored detached signature, the
+ standard input of "gpg -bsau $key" is fed with the contents to be
+ signed, and the program is expected to send the result to its
+ standard output.
+
gui.commitmsgwidth::
Defines how wide the commit message window is in the
linkgit:git-gui[1]. "75" is the default.
@@ -1194,9 +1336,18 @@ help.autocorrect::
This is the default.
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
+ Override the HTTP proxy, normally configured using the 'http_proxy',
+ 'https_proxy', and 'all_proxy' environment variables (see
+ `curl(1)`). This can be overridden on a per-remote basis; see
+ remote.<name>.proxy
+
+http.cookiefile::
+ File containing previously stored cookie lines which should be used
+ in the git http session, if they match the server. The file format
+ of the file to read cookies from should be plain HTTP headers or
+ the Netscape/Mozilla cookie file format (see linkgit:curl[1]).
+ NOTE that the file specified with http.cookiefile is only used as
+ input. No cookies will be stored in the file.
http.sslVerify::
Whether to verify the SSL certificate when fetching or pushing
@@ -1311,7 +1462,7 @@ instaweb.port::
interactive.singlekey::
In interactive commands, allow the user to provide one-letter
input with a single key (i.e., without hitting enter).
- Currently this is used by the `\--patch` mode of
+ Currently this is used by the `--patch` mode of
linkgit:git-add[1], linkgit:git-checkout[1], linkgit:git-commit[1],
linkgit:git-reset[1], and linkgit:git-stash[1]. Note that this
setting is silently ignored if portable keystroke input
@@ -1319,13 +1470,13 @@ interactive.singlekey::
log.abbrevCommit::
If true, makes linkgit:git-log[1], linkgit:git-show[1], and
- linkgit:git-whatchanged[1] assume `\--abbrev-commit`. You may
- override this option with `\--no-abbrev-commit`.
+ linkgit:git-whatchanged[1] assume `--abbrev-commit`. You may
+ override this option with `--no-abbrev-commit`.
log.date::
Set the default date-time mode for the 'log' command.
Setting a value for log.date is similar to using 'git log''s
- `\--date` option. Possible values are `relative`, `local`,
+ `--date` option. Possible values are `relative`, `local`,
`default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1]
for details.
@@ -1515,18 +1666,18 @@ pack.indexVersion::
and this config option ignored whenever the corresponding pack is
larger than 2 GB.
+
-If you have an old git that does not understand the version 2 `{asterisk}.idx` file,
+If you have an old git that does not understand the version 2 `*.idx` file,
cloning or fetching over a non native protocol (e.g. "http" and "rsync")
-that will copy both `{asterisk}.pack` file and corresponding `{asterisk}.idx` file from the
+that will copy both `*.pack` file and corresponding `*.idx` file from the
other side may give you a repository that cannot be accessed with your
-older version of git. If the `{asterisk}.pack` file is smaller than 2 GB, however,
+older version of git. If the `*.pack` file is smaller than 2 GB, however,
you can use linkgit:git-index-pack[1] on the *.pack file to regenerate
-the `{asterisk}.idx` file.
+the `*.idx` file.
pack.packSizeLimit::
The maximum size of a pack. This setting only affects
packing to a file when repacking, i.e. the git:// protocol
- is unaffected. It can be overridden by the `\--max-pack-size`
+ is unaffected. It can be overridden by the `--max-pack-size`
option of linkgit:git-repack[1]. The minimum size allowed is
limited to 1 MiB. The default is unlimited.
Common unit suffixes of 'k', 'm', or 'g' are
@@ -1536,8 +1687,8 @@ pager.<cmd>::
If the value is boolean, turns on or off pagination of the
output of a particular git subcommand when writing to a tty.
Otherwise, turns on pagination for the subcommand using the
- pager specified by the value of `pager.<cmd>`. If `\--paginate`
- or `\--no-pager` is specified on the command line, it takes
+ pager specified by the value of `pager.<cmd>`. If `--paginate`
+ or `--no-pager` is specified on the command line, it takes
precedence over this option. To disable pagination for all
commands, set `core.pager` or `GIT_PAGER` to `cat`.
@@ -1545,12 +1696,22 @@ pretty.<name>::
Alias for a --pretty= format string, as specified in
linkgit:git-log[1]. Any aliases defined here can be used just
as the built-in pretty formats could. For example,
- running `git config pretty.changelog "format:{asterisk} %H %s"`
+ running `git config pretty.changelog "format:* %H %s"`
would cause the invocation `git log --pretty=changelog`
- to be equivalent to running `git log "--pretty=format:{asterisk} %H %s"`.
+ to be equivalent to running `git log "--pretty=format:* %H %s"`.
Note that an alias with the same name as a built-in format
will be silently ignored.
+pull.rebase::
+ When true, rebase branches on top of the fetched branch, instead
+ of merging the default branch from the default remote when "git
+ pull" is run. See "branch.<name>.rebase" for setting this on a
+ per-branch basis.
++
+*NOTE*: this is a possibly dangerous operation; do *not* use
+it unless you understand the implications (see linkgit:git-rebase[1]
+for details).
+
pull.octopus::
The default merge strategy to use when pulling multiple branches
at once.
@@ -1564,13 +1725,33 @@ push.default::
no refspec is implied by any of the options given on the command
line. Possible values are:
+
+--
* `nothing` - do not push anything.
-* `matching` - push all matching branches.
- All branches having the same name in both ends are considered to be
- matching. This is the default.
+* `matching` - push all branches having the same name in both ends.
+ This is for those who prepare all the branches into a publishable
+ shape and then push them out with a single command. It is not
+ appropriate for pushing into a repository shared by multiple users,
+ since locally stalled branches will attempt a non-fast forward push
+ if other users updated the branch.
+ +
+ This is currently the default, but Git 2.0 will change the default
+ to `simple`.
* `upstream` - push the current branch to its upstream branch.
-* `tracking` - deprecated synonym for `upstream`.
+ With this, `git push` will update the same remote ref as the one which
+ is merged by `git pull`, making `push` and `pull` symmetrical.
+ See "branch.<name>.merge" for how to configure the upstream branch.
+* `simple` - like `upstream`, but refuses to push if the upstream
+ branch's name is different from the local one. This is the safest
+ option and is well-suited for beginners. It will become the default
+ in Git 2.0.
* `current` - push the current branch to a branch of the same name.
+--
++
+The `simple`, `current` and `upstream` modes are for those who want to
+push out a single branch after finishing work, even when the other
+branches are not yet ready to be pushed out. If you are working with
+other people to push into the same shared repository, you would want
+to use one of these.
rebase.stat::
Whether to show a diffstat of what changed upstream since the last
@@ -1588,7 +1769,8 @@ receive.fsckObjects::
If it is set to true, git-receive-pack will check all received
objects. It will abort in the case of a malformed object or a
broken link. The result of an abort are only dangling objects.
- Defaults to false.
+ Defaults to false. If not set, the value of `transfer.fsckObjects`
+ is used instead.
receive.unpackLimit::
If the number of objects received in a push is below this
@@ -1649,7 +1831,7 @@ remote.<name>.push::
remote.<name>.mirror::
If true, pushing to this remote will automatically behave
- as if the `\--mirror` option was given on the command line.
+ as if the `--mirror` option was given on the command line.
remote.<name>.skipDefaultUpdate::
If true, this remote will be skipped by default when updating
@@ -1824,6 +2006,11 @@ tar.umask::
archiving user's umask will be used instead. See umask(2) and
linkgit:git-archive[1].
+transfer.fsckObjects::
+ When `fetch.fsckObjects` or `receive.fsckObjects` are
+ not set, the value of this variable is used instead.
+ Defaults to false.
+
transfer.unpackLimit::
When `fetch.unpackLimit` or `receive.unpackLimit` are
not set, the value of this variable is used instead.
diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt
index 1aed79e..67a90a8 100644
--- a/Documentation/diff-config.txt
+++ b/Documentation/diff-config.txt
@@ -52,6 +52,10 @@ directories with less than 10% of the total amount of changed files,
and accumulating child directory counts in the parent directories:
`files,10,cumulative`.
+diff.statGraphWidth::
+ Limit the width of the graph part in --stat output. If set, applies
+ to all commands generating --stat output except format-patch.
+
diff.external::
If this config variable is set, diff generation is not
performed using the internal diff machinery, but using the
diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt
index c57460c..55f499a 100644
--- a/Documentation/diff-generate-patch.txt
+++ b/Documentation/diff-generate-patch.txt
@@ -175,7 +175,7 @@ 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 eight other lines are the same
-from file1 but do not appear in file2 (hence prefixed with `{plus}`).
+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
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 659de6f..cf4b216 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -45,17 +45,36 @@ ifndef::git-format-patch[]
Synonym for `-p --raw`.
endif::git-format-patch[]
+--minimal::
+ Spend extra time to make sure the smallest possible
+ diff is produced.
+
--patience::
Generate a diff using the "patience diff" algorithm.
---stat[=<width>[,<name-width>]]::
- Generate a diffstat. You can override the default
- output width for 80-column terminal by `--stat=<width>`.
- The width of the filename part can be controlled by
- giving another width to it separated by a comma.
+--histogram::
+ Generate a diff using the "histogram diff" algorithm.
+
+--stat[=<width>[,<name-width>[,<count>]]]::
+ Generate a diffstat. By default, as much space as necessary
+ will be used for the filename part, and the rest for the graph
+ part. Maximum width defaults to terminal width, or 80 columns
+ if not connected to a terminal, and can be overridden by
+ `<width>`. The width of the filename part can be limited by
+ giving another width `<name-width>` after a comma. The width
+ of the graph part can be limited by using
+ `--stat-graph-width=<width>` (affects all commands generating
+ a stat graph) or by setting `diff.statGraphWidth=<width>`
+ (does not affect `git format-patch`).
+ By giving a third parameter `<count>`, you can limit the
+ output to the first `<count>` lines, followed by `...` if
+ there are more.
++
+These parameters can also be set individually with `--stat-width=<width>`,
+`--stat-name-width=<name-width>` and `--stat-count=<count>`.
--numstat::
- Similar to `\--stat`, but shows number of added and
+ Similar to `--stat`, but shows number of added and
deleted lines in decimal notation and pathname without
abbreviation, to make it more machine friendly. For
binary files, outputs two `-` instead of saying
@@ -146,11 +165,12 @@ any of those replacements occurred.
of the `--diff-filter` option on what the status letters mean.
--submodule[=<format>]::
- Chose the output format for submodule differences. <format> can be one of
- 'short' and 'log'. 'short' just shows pairs of commit names, this format
- is used when this option is not given. 'log' is the default value for this
- option and lists the commits in that commit range like the 'summary'
- option of linkgit:git-submodule[1] does.
+ Specify how differences in submodules are shown. When `--submodule`
+ or `--submodule=log` is given, the 'log' format is used. This format lists
+ the commits in the range like linkgit:git-submodule[1] `summary` does.
+ Omitting the `--submodule` option or specifying `--submodule=short`,
+ uses the 'short' format. This format just shows the names of the commits
+ at the beginning and end of the range.
--color[=<when>]::
Show colored diff.
@@ -398,7 +418,12 @@ endif::git-format-patch[]
Show the context between diff hunks, up to the specified number
of lines, thereby fusing hunks that are close to each other.
+-W::
+--function-context::
+ Show whole surrounding functions of changes.
+
ifndef::git-format-patch[]
+ifndef::git-log[]
--exit-code::
Make the program exit with codes similar to diff(1).
That is, it exits with 1 if there were differences and
@@ -406,6 +431,7 @@ ifndef::git-format-patch[]
--quiet::
Disable all output of the program. Implies `--exit-code`.
+endif::git-log[]
endif::git-format-patch[]
--ext-diff::
diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt
index ae413e5..048337b 100644
--- a/Documentation/everyday.txt
+++ b/Documentation/everyday.txt
@@ -98,8 +98,8 @@ you originally wrote.
<9> switch to the master branch.
<10> merge a topic branch into your master branch.
<11> review commit logs; other forms to limit output can be
-combined and include `\--max-count=10` (show 10 commits),
-`\--until=2005-12-10`, etc.
+combined and include `--max-count=10` (show 10 commits),
+`--until=2005-12-10`, etc.
<12> view only the changes that touch what's in `curses/`
directory, since `v2.43` tag.
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 6b1b5af..19d57a8 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -13,7 +13,8 @@ SYNOPSIS
[--3way] [--interactive] [--committer-date-is-author-date]
[--ignore-date] [--ignore-space-change | --ignore-whitespace]
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
- [--reject] [-q | --quiet] [--scissors | --no-scissors]
+ [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
+ [--scissors | --no-scissors]
[(<mbox> | <Maildir>)...]
'git am' (--continue | --skip | --abort)
@@ -39,6 +40,9 @@ OPTIONS
--keep::
Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+--keep-non-patch::
+ Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+
--keep-cr::
--no-keep-cr::
With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
@@ -87,6 +91,8 @@ default. You can use `--no-utf8` to override this.
-C<n>::
-p<n>::
--directory=<dir>::
+--exclude=<path>::
+--include=<path>::
--reject::
These flags are passed to the 'git apply' (see linkgit:git-apply[1])
program that applies
diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
index 9c750e2..59d73e5 100644
--- a/Documentation/git-archive.txt
+++ b/Documentation/git-archive.txt
@@ -101,6 +101,25 @@ tar.umask::
details. If `--remote` is used then only the configuration of
the remote repository takes effect.
+tar.<format>.command::
+ This variable specifies a shell command through which the tar
+ output generated by `git archive` should be piped. The command
+ is executed using the shell with the generated tar file on its
+ standard input, and should produce the final output on its
+ standard output. Any compression-level options will be passed
+ to the command (e.g., "-9"). An output file with the same
+ extension as `<format>` will be use this format if no other
+ format is given.
++
+The "tar.gz" and "tgz" formats are defined automatically and default to
+`gzip -cn`. You may override them with custom commands.
+
+tar.<format>.remote::
+ If true, enable `<format>` for use by remote clients via
+ linkgit:git-upload-archive[1]. Defaults to false for
+ user-defined formats, but true for the "tar.gz" and "tgz"
+ formats.
+
ATTRIBUTES
----------
@@ -123,32 +142,46 @@ while archiving any tree in your `$GIT_DIR/info/attributes` file.
EXAMPLES
--------
-git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
+`git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)`::
Create a tar archive that contains the contents of the
latest commit on the current branch, and extract it in the
`/var/tmp/junk` directory.
-git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
+`git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz`::
Create a compressed tarball for v1.4.0 release.
-git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz::
+`git archive --format=tar.gz --prefix=git-1.4.0/ v1.4.0 >git-1.4.0.tar.gz`::
+
+ Same as above, but using the builtin tar.gz handling.
+
+`git archive --prefix=git-1.4.0/ -o git-1.4.0.tar.gz v1.4.0`::
+
+ Same as above, but the format is inferred from the output file.
+
+`git archive --format=tar --prefix=git-1.4.0/ v1.4.0^{tree} | gzip >git-1.4.0.tar.gz`::
Create a compressed tarball for v1.4.0 release, but without a
global extended pax header.
-git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip::
+`git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip`::
Put everything in the current head's Documentation/ directory
into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
-git archive -o latest.zip HEAD::
+`git archive -o latest.zip HEAD`::
Create a Zip archive that contains the contents of the latest
commit on the current branch. Note that the output format is
inferred by the extension of the output file.
+`git config tar.tar.xz.command "xz -c"`::
+
+ Configure a "tar.xz" format for making LZMA-compressed tarfiles.
+ You can use it specifying `--format=tar.xz`, or by creating an
+ output file like `-o foo.tar.xz`.
+
SEE ALSO
--------
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index ab60a18..e4f46bc 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -17,7 +17,7 @@ The command takes various subcommands, and different options depending
on the subcommand:
git bisect help
- git bisect start [<bad> [<good>...]] [--] [<paths>...]
+ git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
git bisect bad [<rev>]
git bisect good [<rev>...]
git bisect skip [(<rev>|<range>)...]
@@ -263,6 +263,19 @@ rewind the tree to the pristine state. Finally the script should exit
with the status of the real test to let the "git bisect run" command loop
determine the eventual outcome of the bisect session.
+OPTIONS
+-------
+--no-checkout::
++
+Do not checkout the new working tree at each iteration of the bisection
+process. Instead just update a special reference named 'BISECT_HEAD' to make
+it point to the commit that should be tested.
++
+This option may be useful when the test you would perform in each step
+does not require a checked out tree.
++
+If the repository is bare, `--no-checkout` is assumed.
+
EXAMPLES
--------
@@ -343,6 +356,25 @@ $ git bisect run sh -c "make || exit 125; ~/check_test_case.sh"
This shows that you can do without a run script if you write the test
on a single line.
+* Locate a good region of the object graph in a damaged repository
++
+------------
+$ git bisect start HEAD <known-good-commit> [ <boundary-commit> ... ] --no-checkout
+$ git bisect run sh -c '
+ GOOD=$(git for-each-ref "--format=%(objectname)" refs/bisect/good-*) &&
+ git rev-list --objects BISECT_HEAD --not $GOOD >tmp.$$ &&
+ git pack-objects --stdout >/dev/null <tmp.$$
+ rc=$?
+ rm -f tmp.$$
+ test $rc = 0'
+
+------------
++
+In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit that
+has at least one parent whose reachable graph is fully traversable in the sense
+required by 'git pack objects'.
+
+
SEE ALSO
--------
link:git-bisect-lk2009.html[Fighting regressions with git bisect],
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index 9516914..7ee9236 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -160,7 +160,7 @@ introduced the file with:
git log --diff-filter=A --pretty=short -- foo
and then annotate the change between the commit and its
-parents, using `commit{caret}!` notation:
+parents, using `commit^!` notation:
git blame -C -C -f $commit^! -- foo
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index c50f189..47235be 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -9,18 +9,24 @@ SYNOPSIS
--------
[verse]
'git branch' [--color[=<when>] | --no-color] [-r | -a]
- [-v [--abbrev=<length> | --no-abbrev]]
- [(--merged | --no-merged | --contains) [<commit>]]
+ [--list] [-v [--abbrev=<length> | --no-abbrev]]
+ [--column[=<options>] | --no-column]
+ [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
'git branch' (-d | -D) [-r] <branchname>...
+'git branch' --edit-description [<branchname>]
DESCRIPTION
-----------
With no arguments, existing branches are listed and the current branch will
be highlighted with an asterisk. Option `-r` causes the remote-tracking
-branches to be listed, and option `-a` shows both.
+branches to be listed, and option `-a` shows both. This list mode is also
+activated by the `--list` option (see below).
+<pattern> restricts the output to matching branches, the pattern is a shell
+wildcard (i.e., matched using fnmatch(3)).
+Multiple patterns may be given; if any of them matches, the branch is shown.
With `--contains`, shows only the branches that contain the named commit
(in other words, the branches whose tip commits are descendants of the
@@ -44,7 +50,7 @@ the remote-tracking branch. This behavior may be changed via the global
overridden by using the `--track` and `--no-track` options, and
changed later using `git branch --set-upstream`.
-With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
+With a `-m` or `-M` option, <oldbranch> will be renamed to <newbranch>.
If <oldbranch> had a corresponding reflog, it is renamed to match
<newbranch>, and a reflog entry is created to remember the branch
renaming. If <newbranch> exists, -M must be used to force the rename
@@ -54,7 +60,7 @@ 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. Note, that it
+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 the remote repository or if 'git fetch' was configured not to fetch
them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a
@@ -64,6 +70,7 @@ way to clean up all obsolete remote-tracking branches.
OPTIONS
-------
-d::
+--delete::
Delete a branch. The branch must be fully merged in its
upstream branch, or in `HEAD` if no upstream was set with
`--track` or `--set-upstream`.
@@ -72,6 +79,7 @@ OPTIONS
Delete a branch irrespective of its merged status.
-l::
+--create-reflog::
Create the branch's reflog. This activates recording of
all changes made to the branch ref, enabling use of date
based sha1 expressions such as "<branchname>@\{yesterday}".
@@ -84,6 +92,7 @@ OPTIONS
already. Without `-f` 'git branch' refuses to change an existing branch.
-m::
+--move::
Move/rename a branch and the corresponding reflog.
-M::
@@ -99,21 +108,42 @@ OPTIONS
default to color output.
Same as `--color=never`.
+--column[=<options>]::
+--no-column::
+ Display branch listing in columns. See configuration variable
+ column.branch for option syntax.`--column` and `--no-column`
+ without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable in non-verbose mode.
+
-r::
+--remotes::
List or delete (if used with -d) the remote-tracking branches.
-a::
+--all::
List both remote-tracking branches and local branches.
+--list::
+ Activate the list mode. `git branch <pattern>` would try to create a branch,
+ use `git branch --list <pattern>` to list matching branches.
+
-v::
--verbose::
- Show sha1 and commit subject line for each head, along with
+ When in list mode,
+ show sha1 and commit subject line for each head, along with
relationship to upstream branch (if any). If given twice, print
the name of the upstream branch, as well.
+-q::
+--quiet::
+ Be more quiet when creating or deleting a branch, suppressing
+ non-error messages.
+
--abbrev=<length>::
Alter the sha1's minimum display length in the output listing.
- The default value is 7.
+ The default value is 7 and can be overridden by the `core.abbrev`
+ config option.
--no-abbrev::
Display the full sha1s in the output listing rather than abbreviating them.
@@ -138,13 +168,18 @@ start-point is either a local or remote-tracking branch.
branch.autosetupmerge configuration variable is true.
--set-upstream::
- If specified branch does not exist yet or if '--force' has been
- given, acts exactly like '--track'. Otherwise sets up configuration
- like '--track' would when creating the branch, except that where
+ If specified branch does not exist yet or if `--force` has been
+ given, acts exactly like `--track`. Otherwise sets up configuration
+ like `--track` would when creating the branch, except that where
branch points to is not changed.
---contains <commit>::
- Only list branches which contain the specified commit.
+--edit-description::
+ Open an editor and edit the text to explain what the branch is
+ for, to be used by various other commands (e.g. `request-pull`).
+
+--contains [<commit>]::
+ Only list branches which contain the specified commit (HEAD
+ if not specified).
--merged [<commit>]::
Only list branches whose tips are reachable from the
diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt
index 92b01ec2..16a6b0a 100644
--- a/Documentation/git-bundle.txt
+++ b/Documentation/git-bundle.txt
@@ -61,7 +61,7 @@ unbundle <file>::
A list of arguments, acceptable to 'git rev-parse' and
'git rev-list' (and containing a named ref, see SPECIFYING REFERENCES
below), that specifies the specific objects and references
- to transport. For example, `master{tilde}10..master` causes the
+ to transport. For example, `master~10..master` causes the
current master reference to be packaged along with all objects
added since its 10th ancestor commit. There is no explicit
limit to the number of references and objects that may be
@@ -80,12 +80,12 @@ SPECIFYING REFERENCES
'git bundle' will only package references that are shown by
'git show-ref': this includes heads, tags, and remote heads. References
-such as `master{tilde}1` cannot be packaged, but are perfectly suitable for
+such as `master~1` cannot be packaged, but are perfectly suitable for
defining the basis. More than one reference may be packaged, and more
than one basis can be specified. The objects packaged are those not
contained in the union of the given bases. Each basis can be
-specified explicitly (e.g. `^master{tilde}10`), or implicitly (e.g.
-`master{tilde}10..master`, `--since=10.days.ago master`).
+specified explicitly (e.g. `^master~10`), or implicitly (e.g.
+`master~10..master`, `--since=10.days.ago master`).
It is very important that the basis used be held by the destination.
It is okay to err on the side of caution, causing the bundle file
diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt
index 30eca6c..5abdbaa 100644
--- a/Documentation/git-check-attr.txt
+++ b/Documentation/git-check-attr.txt
@@ -9,8 +9,8 @@ git-check-attr - Display gitattributes information
SYNOPSIS
--------
[verse]
-'git check-attr' attr... [--] pathname...
-'git check-attr' --stdin [-z] attr... < <list-of-paths>
+'git check-attr' [-a | --all | attr...] [--] pathname...
+'git check-attr' --stdin [-z] [-a | --all | attr...] < <list-of-paths>
DESCRIPTION
-----------
@@ -19,6 +19,14 @@ For every pathname, this command will list if each attribute is 'unspecified',
OPTIONS
-------
+-a, --all::
+ List all attributes that are associated with the specified
+ paths. If this option is used, then 'unspecified' attributes
+ will not be included in the output.
+
+--cached::
+ Consider `.gitattributes` in the index only, ignoring the working tree.
+
--stdin::
Read file names from stdin instead of from the command-line.
@@ -28,8 +36,11 @@ OPTIONS
\--::
Interpret all preceding arguments as attributes and all following
- arguments as path names. If not supplied, only the first argument will
- be treated as an attribute.
+ arguments as path names.
+
+If none of `--stdin`, `--all`, or `--` is used, the first argument
+will be treated as an attribute and the rest of the arguments as
+pathnames.
OUTPUT
------
@@ -69,6 +80,13 @@ org/example/MyClass.java: diff: java
org/example/MyClass.java: myAttr: set
---------------
+* Listing all attributes for a file:
+---------------
+$ git check-attr --all -- org/example/MyClass.java
+org/example/MyClass.java: diff: java
+org/example/MyClass.java: myAttr: set
+---------------
+
* Listing an attribute for multiple files:
---------------
$ git check-attr myAttr -- org/example/MyClass.java org/example/NoMyAttr.java
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index c9fdf84..98009d1 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -8,8 +8,9 @@ git-check-ref-format - Ensures that a reference name is well formed
SYNOPSIS
--------
[verse]
-'git check-ref-format' <refname>
-'git check-ref-format' --print <refname>
+'git check-ref-format' [--normalize]
+ [--[no-]allow-onelevel] [--refspec-pattern]
+ <refname>
'git check-ref-format' --branch <branchname-shorthand>
DESCRIPTION
@@ -28,22 +29,28 @@ git imposes the following rules on how references are named:
. They can include slash `/` for hierarchical (directory)
grouping, but no slash-separated component can begin with a
- dot `.`.
+ dot `.` or end with the sequence `.lock`.
. They must contain at least one `/`. This enforces the presence of a
category like `heads/`, `tags/` etc. but the actual names are not
- restricted.
+ restricted. If the `--allow-onelevel` option is used, this rule
+ is waived.
. They cannot have two consecutive dots `..` anywhere.
. They cannot have ASCII control characters (i.e. bytes whose
values are lower than \040, or \177 `DEL`), space, tilde `~`,
- caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
- or open bracket `[` anywhere.
+ caret `^`, or colon `:` anywhere.
-. They cannot end with a slash `/` nor a dot `.`.
+. They cannot have question-mark `?`, asterisk `*`, or open
+ bracket `[` anywhere. See the `--refspec-pattern` option below for
+ an exception to this rule.
-. They cannot end with the sequence `.lock`.
+. They cannot begin or end with a slash `/` or contain multiple
+ consecutive slashes (see the `--normalize` option below for an
+ exception to this rule)
+
+. They cannot end with a dot `.`.
. They cannot contain a sequence `@{`.
@@ -55,10 +62,10 @@ unquoted (by mistake), and also avoids ambiguities in certain
reference name expressions (see linkgit:gitrevisions[7]):
. A double-dot `..` is often used as in `ref1..ref2`, and in some
- contexts this notation means `{caret}ref1 ref2` (i.e. not in
+ contexts this notation means `^ref1 ref2` (i.e. not in
`ref1` and in `ref2`).
-. A tilde `~` and caret `{caret}` are used to introduce the postfix
+. A tilde `~` and caret `^` are used to introduce the postfix
'nth parent' and 'peel onion' operation.
. A colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
@@ -68,16 +75,36 @@ reference name expressions (see linkgit:gitrevisions[7]):
. at-open-brace `@{` is used as a notation to access a reflog entry.
-With the `--print` option, if 'refname' is acceptable, it prints the
-canonicalized name of a hypothetical reference with that name. That is,
-it prints 'refname' with any extra `/` characters removed.
-
With the `--branch` option, it expands the ``previous branch syntax''
`@{-n}`. For example, `@{-1}` is a way to refer the last branch you
were on. This option should be used by porcelains to accept this
syntax anywhere a branch name is expected, so they can act as if you
typed the branch name.
+OPTIONS
+-------
+--allow-onelevel::
+--no-allow-onelevel::
+ Controls whether one-level refnames are accepted (i.e.,
+ refnames that do not contain multiple `/`-separated
+ components). The default is `--no-allow-onelevel`.
+
+--refspec-pattern::
+ Interpret <refname> as a reference name pattern for a refspec
+ (as used with remote repositories). If this option is
+ enabled, <refname> is allowed to contain a single `*`
+ in place of a one full pathname component (e.g.,
+ `foo/*/bar` but not `foo/bar*`).
+
+--normalize::
+ Normalize 'refname' by removing any leading slash (`/`)
+ characters and collapsing runs of adjacent slashes between
+ name components into a single slash. Iff the normalized
+ refname is valid then print it to standard output and exit
+ with a status of 0. (`--print` is a deprecated way to spell
+ `--normalize`.)
+
+
EXAMPLES
--------
@@ -90,7 +117,7 @@ $ git check-ref-format --branch @{-1}
* Determine the reference name to use for a new branch:
+
------------
-$ ref=$(git check-ref-format --print "refs/heads/$newbranch") ||
+$ ref=$(git check-ref-format --normalize "refs/heads/$newbranch") ||
die "we do not like '$newbranch' as a branch name."
------------
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index c0a96e6..63a2516 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -184,7 +184,7 @@ the conflicted merge in the specified paths.
+
This means that you can use `git checkout -p` to selectively discard
edits from your current working tree. See the ``Interactive Mode''
-section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
<branch>::
Branch to checkout; if it refers to a branch (i.e., a name that,
@@ -193,11 +193,11 @@ section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
commit, your HEAD becomes "detached" and you are no longer on
any branch (see below for details).
+
-As a special case, the `"@\{-N\}"` syntax for the N-th last branch
+As a special case, the `"@{-N}"` syntax for the N-th last branch
checks out the branch (instead of detaching). You may also specify
-`-` which is synonymous with `"@\{-1\}"`.
+`-` which is synonymous with `"@{-1}"`.
+
-As a further special case, you may use `"A\...B"` as a shortcut for the
+As a further special case, you may use `"A...B"` as a shortcut for the
merge base of `A` and `B` if there is exactly one merge base. You can
leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 6c9c2cb..0e170a5 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -9,6 +9,9 @@ SYNOPSIS
--------
[verse]
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
+'git cherry-pick' --continue
+'git cherry-pick' --quit
+'git cherry-pick' --abort
DESCRIPTION
-----------
@@ -44,7 +47,9 @@ OPTIONS
linkgit:gitrevisions[7].
Sets of commits can be passed but no traversal is done by
default, as if the '--no-walk' option was specified, see
- linkgit:git-rev-list[1].
+ linkgit:git-rev-list[1]. Note that specifying a range will
+ feed all <commit>... arguments to a single revision walk
+ (see a later example that uses 'maint master..next').
-e::
--edit::
@@ -100,6 +105,25 @@ effect to your index in a row.
cherry-pick'ed commit, then a fast forward to this commit will
be performed.
+--allow-empty::
+ By default, cherry-picking an empty commit will fail,
+ indicating that an explicit invocation of `git commit
+ --allow-empty` is required. This option overrides that
+ behavior, allowing empty commits to be preserved automatically
+ in a cherry-pick. Note that when "--ff" is in effect, empty
+ commits that meet the "fast-forward" requirement will be kept
+ even without this option. Note also, that use of this option only
+ keeps commits that were initially empty (i.e. the commit recorded the
+ same tree as its parent). Commits which are made empty due to a
+ previous commit are dropped. To force the inclusion of those commits
+ use `--keep-redundant-commits`.
+
+--keep-redundant-commits::
+ If a commit being cherry picked duplicates a commit already in the
+ current history, it will become empty. By default these
+ redundant commits are ignored. This option overrides that behavior and
+ creates an empty commit object. Implies `--allow-empty`.
+
--strategy=<strategy>::
Use the given merge strategy. Should only be used once.
See the MERGE STRATEGIES section in linkgit:git-merge[1]
@@ -110,33 +134,46 @@ effect to your index in a row.
Pass the merge strategy-specific option through to the
merge strategy. See linkgit:git-merge[1] for details.
+SEQUENCER SUBCOMMANDS
+---------------------
+include::sequencer.txt[]
+
EXAMPLES
--------
-git cherry-pick master::
+`git cherry-pick master`::
Apply the change introduced by the commit at the tip of the
master branch and create a new commit with this change.
-git cherry-pick ..master::
-git cherry-pick ^HEAD master::
+`git cherry-pick ..master`::
+`git cherry-pick ^HEAD master`::
Apply the changes introduced by all commits that are ancestors
of master but not of HEAD to produce new commits.
-git cherry-pick master{tilde}4 master{tilde}2::
+`git cherry-pick maint next ^master`::
+`git cherry-pick maint master..next`::
+
+ Apply the changes introduced by all commits that are
+ ancestors of maint or next, but not master or any of its
+ ancestors. Note that the latter does not mean `maint` and
+ everything between `master` and `next`; specifically,
+ `maint` will not be used if it is included in `master`.
+
+`git cherry-pick master~4 master~2`::
Apply the changes introduced by the fifth and third last
commits pointed to by master and create 2 new commits with
these changes.
-git cherry-pick -n master~1 next::
+`git cherry-pick -n master~1 next`::
Apply to the working tree and the index the changes introduced
by the second last commit pointed to by master and by the last
commit pointed to by next, but do not create any commit with
these changes.
-git cherry-pick --ff ..next::
+`git cherry-pick --ff ..next`::
If history is linear and HEAD is an ancestor of next, update
the working tree and advance the HEAD pointer to match next.
@@ -144,7 +181,7 @@ git cherry-pick --ff ..next::
are in next but not HEAD to the current branch, creating a new
commit for each new change.
-git rev-list --reverse master \-- README | git cherry-pick -n --stdin::
+`git rev-list --reverse master -- README | git cherry-pick -n --stdin`::
Apply the changes introduced by all commits on the master
branch that touched README to the working tree and index,
diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
index 974e04e..79fb984 100644
--- a/Documentation/git-clean.txt
+++ b/Documentation/git-clean.txt
@@ -47,12 +47,14 @@ OPTIONS
-e <pattern>::
--exclude=<pattern>::
- Specify special exceptions to not be cleaned. Each <pattern> is
- the same form as in $GIT_DIR/info/excludes and this option can be
- given multiple times.
+ In addition to those found in .gitignore (per directory) and
+ $GIT_DIR/info/exclude, also consider these patterns to be in the
+ set of the ignore rules in effect.
-x::
- Don't use the ignore rules. This allows removing all untracked
+ Don't use the standard ignore rules read from .gitignore (per
+ directory) and $GIT_DIR/info/exclude, but do still use the ignore
+ rules given with `-e` options. This allows removing all untracked
files, including build products. This can be used (possibly in
conjunction with 'git reset') to create a pristine
working directory to test a clean build.
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index b093e45..c1ddd4c 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -13,7 +13,8 @@ SYNOPSIS
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
[--separate-git-dir <git dir>]
- [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
+ [--depth <depth>] [--[no-]single-branch]
+ [--recursive|--recurse-submodules] [--] <repository>
[<directory>]
DESCRIPTION
@@ -45,13 +46,18 @@ OPTIONS
mechanism and clones the repository by making a copy of
HEAD and everything under objects and refs directories.
The files under `.git/objects/` directory are hardlinked
- to save space when possible. This is now the default when
- the source repository is specified with `/path/to/repo`
- syntax, so it essentially is a no-op option. To force
- copying instead of hardlinking (which may be desirable
- if you are trying to make a back-up of your repository),
- but still avoid the usual "git aware" transport
- mechanism, `--no-hardlinks` can be used.
+ to save space when possible.
++
+If the repository is specified as a local path (e.g., `/path/to/repo`),
+this is the default, and --local is essentially a no-op. If the
+repository is specified as a URL, then this flag is ignored (and we
+never use the local optimizations). Specifying `--no-local` will
+override the default when `/path/to/repo` is given, using the regular
+git transport instead.
++
+To force copying instead of hardlinking (which may be desirable if you
+are trying to make a back-up of your repository), but still avoid the
+usual "git aware" transport mechanism, `--no-hardlinks` can be used.
--no-hardlinks::
Optimize the cloning process from a repository on a
@@ -146,8 +152,9 @@ objects from the source repository into a pack in the cloned repository.
-b <name>::
Instead of pointing the newly created HEAD to the branch pointed
to by the cloned repository's HEAD, point to `<name>` branch
- instead. In a non-bare repository, this is the branch that will
- be checked out.
+ instead. `--branch` can also take tags and treat them like
+ detached HEAD. In a non-bare repository, this is the branch
+ that will be checked out.
--upload-pack <upload-pack>::
-u <upload-pack>::
@@ -159,6 +166,17 @@ objects from the source repository into a pack in the cloned repository.
Specify the directory from which templates will be used;
(See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
+--config <key>=<value>::
+-c <key>=<value>::
+ Set a configuration variable in the newly-created repository;
+ this takes effect immediately after the repository is
+ initialized, but before the remote history is fetched or any
+ files checked out. The key is in the same format as expected by
+ linkgit:git-config[1] (e.g., `core.eol=true`). If multiple
+ values are given for the same key, each value will be written to
+ the config file. This makes it safe, for example, to add
+ additional fetch refspecs to the origin remote.
+
--depth <depth>::
Create a 'shallow' clone with a history truncated to the
specified number of revisions. A shallow repository has a
@@ -168,6 +186,14 @@ objects from the source repository into a pack in the cloned repository.
with a long history, and would want to send in fixes
as patches.
+--single-branch::
+ Clone only the history leading to the tip of a single branch,
+ either specified by the `--branch` option or the primary
+ branch remote's `HEAD` points at. When creating a shallow
+ clone with the `--depth` option, this is the default, unless
+ `--no-single-branch` is given to fetch the histories near the
+ tips of all branches.
+
--recursive::
--recurse-submodules::
After the clone is created, initialize all submodules within,
diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt
new file mode 100644
index 0000000..5d6f1cc
--- /dev/null
+++ b/Documentation/git-column.txt
@@ -0,0 +1,53 @@
+git-column(1)
+=============
+
+NAME
+----
+git-column - Display data in columns
+
+SYNOPSIS
+--------
+[verse]
+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]
+ [--indent=<string>] [--nl=<string>] [--padding=<n>]
+
+DESCRIPTION
+-----------
+This command formats its input into multiple columns.
+
+OPTIONS
+-------
+--command=<name>::
+ Look up layout mode using configuration variable column.<name> and
+ column.ui.
+
+--mode=<mode>::
+ Specify layout mode. See configuration variable column.ui for option
+ syntax.
+
+--raw-mode=<n>::
+ Same as --mode but take mode encoded as a number. This is mainly used
+ by other commands that have already parsed layout mode.
+
+--width=<width>::
+ Specify the terminal width. By default 'git column' will detect the
+ terminal width, or fall back to 80 if it is unable to do so.
+
+--indent=<string>::
+ String to be printed at the beginning of each line.
+
+--nl=<N>::
+ String to be printed at the end of each line,
+ including newline character.
+
+--padding=<N>::
+ The number of spaces between columns. One space by default.
+
+
+Author
+------
+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt
index 0fdb82e..ff73286 100644
--- a/Documentation/git-commit-tree.txt
+++ b/Documentation/git-commit-tree.txt
@@ -9,7 +9,8 @@ git-commit-tree - Create a new commit object
SYNOPSIS
--------
[verse]
-'git commit-tree' <tree> [(-p <parent commit>)...] < changelog
+'git commit-tree' <tree> [(-p <parent>)...] < changelog
+'git commit-tree' <tree> [(-p <parent>)...] [(-m <message>)...] [(-F <file>)...]
DESCRIPTION
-----------
@@ -17,7 +18,8 @@ This is usually not what an end user wants to run directly. See
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.
+emits the new commit object id on stdout. The log message is read
+from the standard input, unless `-m` or `-F` options are given.
A commit object may have any number of parents. With exactly one
parent, it is an ordinary commit. Having more than one parent makes
@@ -39,9 +41,17 @@ OPTIONS
<tree>::
An existing tree object
--p <parent commit>::
+-p <parent>::
Each '-p' indicates the id of a parent commit object.
+-m <message>::
+ A paragraph in the commit log message. This can be given more than
+ once and each <message> becomes its own paragraph.
+
+-F <file>::
+ Read the commit log message from the given file. Use `-` to read
+ from the standard input.
+
Commit Information
------------------
@@ -68,7 +78,9 @@ if set:
In case (some of) these environment variables are not set, the information
is taken from the configuration items user.name and user.email, or, if not
-present, system user name and fully qualified hostname.
+present, system user name and the hostname used for outgoing mail (taken
+from `/etc/mailname` and falling back to the fully qualified hostname when
+that file does not exist).
A commit comment is read from stdin. If a changelog
entry is not provided via "<" redirection, 'git commit-tree' will just wait
@@ -76,20 +88,15 @@ for one to be entered and terminated with ^D.
include::date-formats.txt[]
-Diagnostics
------------
-You don't exist. Go away!::
- The passwd(5) gecos field couldn't be read
-Your parents must have hated you!::
- The passwd(5) gecos field is longer than a giant static buffer.
-Your sysadmin must hate you!::
- The passwd(5) name field is longer than a giant static buffer.
-
Discussion
----------
include::i18n.txt[]
+FILES
+-----
+/etc/mailname
+
SEE ALSO
--------
linkgit:git-write-tree[1]
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 5cc84a1..f400835 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -42,7 +42,7 @@ The content to be added can be specified in several ways:
5. by using the --interactive or --patch switches with the 'commit' command
to decide one by one which files or hunks should be part of the commit,
- before finalizing the operation. See the ``Interactive Mode`` section of
+ before finalizing the operation. See the ``Interactive Mode'' section of
linkgit:git-add[1] to learn how to operate these modes.
The `--dry-run` option can be used to obtain a
@@ -101,12 +101,16 @@ OPTIONS
When doing a dry-run, give the output in the short-format. See
linkgit:git-status[1] for details. Implies `--dry-run`.
+--branch::
+ Show the branch and tracking info even in short-format.
+
--porcelain::
When doing a dry-run, give the output in a porcelain-ready
format. See linkgit:git-status[1] for details. Implies
`--dry-run`.
-z::
+--null::
When showing `short` or `porcelain` status output, terminate
entries in the status output with NUL, instead of LF. If no
format is given, implies the `--porcelain` output format.
@@ -132,11 +136,14 @@ OPTIONS
-t <file>::
--template=<file>::
- Use the contents of the given file as the initial version
- of the commit message. The editor is invoked and you can
- make subsequent changes. If a message is specified using
- the `-m` or `-F` options, this option has no effect. This
- overrides the `commit.template` configuration variable.
+ When editing the commit message, start the editor with the
+ contents in the given file. The `commit.template` configuration
+ variable is often used to give this option implicitly to the
+ command. This mechanism can be used by projects that want to
+ guide participants with some hints on what to write in the message
+ in what order. If the user exits the editor without editing the
+ message, the commit is aborted. This has no effect when a message
+ is given by other means, e.g. with the `-m` or `-F` options.
-s::
--signoff::
@@ -186,6 +193,10 @@ OPTIONS
current tip -- if it was a merge, it will have the parents of
the current tip as parents -- so the current top commit is
discarded.
+
+--no-post-rewrite::
+ Bypass the post-rewrite hook.
+
+
--
It is a rough equivalent for:
@@ -284,7 +295,7 @@ When recording your own work, the contents of modified files in
your working tree are temporarily stored to a staging area
called the "index" with 'git add'. A file can be
reverted back, only in the index but not in the working tree,
-to that of the last commit with `git reset HEAD \-- <file>`,
+to that of the last commit with `git reset HEAD -- <file>`,
which effectively reverts 'git add' and prevents the changes to
this file from participating in the next commit. After building
the state to be committed incrementally with these commands,
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index e7ecf5d..2d6ef32 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -44,11 +44,15 @@ a "true" or "false" string for bool), or '--path', which does some
path expansion (see '--path' below). If no type specifier is passed, no
checks or transformations are performed on the value.
-The file-option can be one of '--system', '--global' or '--file'
-which specify where the values will be read from or written to.
-The default is to assume the config file of the current repository,
-.git/config unless defined otherwise with GIT_DIR and GIT_CONFIG
-(see <<FILES>>).
+When reading, the values are read from the system, global and
+repository local configuration files by default, and options
+'--system', '--global', '--local' and '--file <filename>' can be
+used to tell the command to read from only that location (see <<FILES>>).
+
+When writing, the new value is written to the repository local
+configuration file by default, and options '--system', '--global',
+'--file <filename>' can be used to tell the command to write to
+that location (you can say '--local' but that is the default).
This command will fail (with exit code ret) if:
@@ -85,15 +89,19 @@ OPTIONS
is not exactly one.
--get-regexp::
- Like --get-all, but interprets the name as a regular expression.
- Also outputs the key names.
+ Like --get-all, but interprets the name as a regular expression and
+ writes out the key names. Regular expression matching is currently
+ case-sensitive and done against a canonicalized version of the key
+ in which section and variable names are lowercased, but subsection
+ names are not.
--global::
For writing options: write to global ~/.gitconfig file rather than
- the repository .git/config.
+ the repository .git/config, write to $XDG_CONFIG_HOME/git/config file
+ if this file exists and the ~/.gitconfig file doesn't.
+
-For reading options: read only from global ~/.gitconfig rather than
-from all available files.
+For reading options: read only from global ~/.gitconfig and from
+$XDG_CONFIG_HOME/git/config rather than from all available files.
+
See also <<FILES>>.
@@ -178,22 +186,33 @@ See also <<FILES>>.
Opens an editor to modify the specified config file; either
'--system', '--global', or repository (default).
+--includes::
+--no-includes::
+ Respect `include.*` directives in config files when looking up
+ values. Defaults to on.
+
[[FILES]]
FILES
-----
-If not set explicitly with '--file', there are three files where
+If not set explicitly with '--file', there are four files where
'git config' will search for configuration options:
$GIT_DIR/config::
- Repository specific configuration file. (The filename is
- of course relative to the repository root, not the working
- directory.)
+ Repository specific configuration file.
~/.gitconfig::
User-specific configuration file. Also called "global"
configuration file.
+$XDG_CONFIG_HOME/git/config::
+ Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
+ or empty, $HOME/.config/git/config will be used. Any single-valued
+ variable set in this file will be overwritten by whatever is in
+ ~/.gitconfig. It is a good idea not to create this file if
+ you sometimes use older versions of Git, as support for this
+ file was added fairly recently.
+
$(prefix)/etc/gitconfig::
System-wide configuration file.
diff --git a/Documentation/git-credential-cache--daemon.txt b/Documentation/git-credential-cache--daemon.txt
new file mode 100644
index 0000000..11edc5a
--- /dev/null
+++ b/Documentation/git-credential-cache--daemon.txt
@@ -0,0 +1,26 @@
+git-credential-cache--daemon(1)
+===============================
+
+NAME
+----
+git-credential-cache--daemon - temporarily store user credentials in memory
+
+SYNOPSIS
+--------
+[verse]
+git credential-cache--daemon <socket>
+
+DESCRIPTION
+-----------
+
+NOTE: You probably don't want to invoke this command yourself; it is
+started automatically when you use linkgit:git-credential-cache[1].
+
+This command listens on the Unix domain socket specified by `<socket>`
+for `git-credential-cache` clients. Clients may store and retrieve
+credentials. Each credential is held for a timeout specified by the
+client; once no credentials are held, the daemon exits.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-credential-cache.txt b/Documentation/git-credential-cache.txt
new file mode 100644
index 0000000..f3d09c5
--- /dev/null
+++ b/Documentation/git-credential-cache.txt
@@ -0,0 +1,77 @@
+git-credential-cache(1)
+=======================
+
+NAME
+----
+git-credential-cache - helper to temporarily store passwords in memory
+
+SYNOPSIS
+--------
+-----------------------------
+git config credential.helper 'cache [options]'
+-----------------------------
+
+DESCRIPTION
+-----------
+
+This command caches credentials in memory for use by future git
+programs. The stored credentials never touch the disk, and are forgotten
+after a configurable timeout. The cache is accessible over a Unix
+domain socket, restricted to the current user by filesystem permissions.
+
+You probably don't want to invoke this command directly; it is meant to
+be used as a credential helper by other parts of git. See
+linkgit:gitcredentials[7] or `EXAMPLES` below.
+
+OPTIONS
+-------
+
+--timeout <seconds>::
+
+ Number of seconds to cache credentials (default: 900).
+
+--socket <path>::
+
+ Use `<path>` to contact a running cache daemon (or start a new
+ cache daemon if one is not started). Defaults to
+ `~/.git-credential-cache/socket`. If your home directory is on a
+ network-mounted filesystem, you may need to change this to a
+ local filesystem.
+
+CONTROLLING THE DAEMON
+----------------------
+
+If you would like the daemon to exit early, forgetting all cached
+credentials before their timeout, you can issue an `exit` action:
+
+--------------------------------------
+git credential-cache exit
+--------------------------------------
+
+EXAMPLES
+--------
+
+The point of this helper is to reduce the number of times you must type
+your username or password. For example:
+
+------------------------------------
+$ git config credential.helper cache
+$ git push http://example.com/repo.git
+Username: <type your username>
+Password: <type your password>
+
+[work for 5 more minutes]
+$ git push http://example.com/repo.git
+[your credentials are used automatically]
+------------------------------------
+
+You can provide options via the credential.helper configuration
+variable (this example drops the cache time to 5 minutes):
+
+-------------------------------------------------------
+$ git config credential.helper 'cache --timeout=300'
+-------------------------------------------------------
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt
new file mode 100644
index 0000000..3109346
--- /dev/null
+++ b/Documentation/git-credential-store.txt
@@ -0,0 +1,75 @@
+git-credential-store(1)
+=======================
+
+NAME
+----
+git-credential-store - helper to store credentials on disk
+
+SYNOPSIS
+--------
+-------------------
+git config credential.helper 'store [options]'
+-------------------
+
+DESCRIPTION
+-----------
+
+NOTE: Using this helper will store your passwords unencrypted on disk,
+protected only by filesystem permissions. If this is not an acceptable
+security tradeoff, try linkgit:git-credential-cache[1], or find a helper
+that integrates with secure storage provided by your operating system.
+
+This command stores credentials indefinitely on disk for use by future
+git programs.
+
+You probably don't want to invoke this command directly; it is meant to
+be used as a credential helper by other parts of git. See
+linkgit:gitcredentials[7] or `EXAMPLES` below.
+
+OPTIONS
+-------
+
+--store=<path>::
+
+ Use `<path>` to store credentials. The file will have its
+ filesystem permissions set to prevent other users on the system
+ from reading it, but will not be encrypted or otherwise
+ protected. Defaults to `~/.git-credentials`.
+
+EXAMPLES
+--------
+
+The point of this helper is to reduce the number of times you must type
+your username or password. For example:
+
+------------------------------------------
+$ git config credential.helper store
+$ git push http://example.com/repo.git
+Username: <type your username>
+Password: <type your password>
+
+[several days later]
+$ git push http://example.com/repo.git
+[your credentials are used automatically]
+------------------------------------------
+
+STORAGE FORMAT
+--------------
+
+The `.git-credentials` file is stored in plaintext. Each credential is
+stored on its own line as a URL like:
+
+------------------------------
+https://user:pass@example.com
+------------------------------
+
+When git needs authentication for a particular URL context,
+credential-store will consider that context a pattern to match against
+each entry in the credentials file. If the protocol, hostname, and
+username (if we already have one) match, then the password is returned
+to git. See the discussion of configuration in linkgit:gitcredentials[7]
+for more information.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt
new file mode 100644
index 0000000..a81684e
--- /dev/null
+++ b/Documentation/git-credential.txt
@@ -0,0 +1,144 @@
+git-credential(1)
+=================
+
+NAME
+----
+git-credential - retrieve and store user credentials
+
+SYNOPSIS
+--------
+------------------
+git credential <fill|approve|reject>
+------------------
+
+DESCRIPTION
+-----------
+
+Git has an internal interface for storing and retrieving credentials
+from system-specific helpers, as well as prompting the user for
+usernames and passwords. The git-credential command exposes this
+interface to scripts which may want to retrieve, store, or prompt for
+credentials in the same manner as git. The design of this scriptable
+interface models the internal C API; see
+link:technical/api-credentials.txt[the git credential API] for more
+background on the concepts.
+
+git-credential takes an "action" option on the command-line (one of
+`fill`, `approve`, or `reject`) and reads a credential description
+on stdin (see <<IOFMT,INPUT/OUTPUT FORMAT>>).
+
+If the action is `fill`, git-credential will attempt to add "username"
+and "password" attributes to the description by reading config files,
+by contacting any configured credential helpers, or by prompting the
+user. The username and password attributes of the credential
+description are then printed to stdout together with the attributes
+already provided.
+
+If the action is `approve`, git-credential will send the description
+to any configured credential helpers, which may store the credential
+for later use.
+
+If the action is `reject`, git-credential will send the description to
+any configured credential helpers, which may erase any stored
+credential matching the description.
+
+If the action is `approve` or `reject`, no output should be emitted.
+
+TYPICAL USE OF GIT CREDENTIAL
+-----------------------------
+
+An application using git-credential will typically use `git
+credential` following these steps:
+
+ 1. Generate a credential description based on the context.
++
+For example, if we want a password for
+`https://example.com/foo.git`, we might generate the following
+credential description (don't forget the blank line at the end; it
+tells `git credential` that the application finished feeding all the
+infomation it has):
+
+ protocol=https
+ host=example.com
+ path=foo.git
+
+ 2. Ask git-credential to give us a username and password for this
+ description. This is done by running `git credential fill`,
+ feeding the description from step (1) to its standard input. The complete
+ credential description (including the credential per se, i.e. the
+ login and password) will be produced on standard output, like:
+
+ protocol=https
+ host=example.com
+ username=bob
+ password=secr3t
++
+In most cases, this means the attributes given in the input will be
+repeated in the output, but git may also modify the credential
+description, for example by removing the `path` attribute when the
+protocol is HTTP(s) and `credential.useHttpPath` is false.
++
+If the `git credential` knew about the password, this step may
+not have involved the user actually typing this password (the
+user may have typed a password to unlock the keychain instead,
+or no user interaction was done if the keychain was already
+unlocked) before it returned `password=secr3t`.
+
+ 3. Use the credential (e.g., access the URL with the username and
+ password from step (2)), and see if it's accepted.
+
+ 4. Report on the success or failure of the password. If the
+ credential allowed the operation to complete successfully, then
+ it can be marked with an "approve" action to tell `git
+ credential` to reuse it in its next invocation. If the credential
+ was rejected during the operation, use the "reject" action so
+ that `git credential` will ask for a new password in its next
+ invocation. In either case, `git credential` should be fed with
+ the credential description obtained from step (2) (which also
+ contain the ones provided in step (1)).
+
+[[IOFMT]]
+INPUT/OUTPUT FORMAT
+-------------------
+
+`git credential` reads and/or writes (depending on the action used)
+credential information in its standard input/output. These information
+can correspond either to keys for which `git credential` will obtain
+the login/password information (e.g. host, protocol, path), or to the
+actual credential data to be obtained (login/password).
+
+The credential is split into a set of named attributes.
+Attributes are provided to the helper, one per line. Each attribute is
+specified by a key-value pair, separated by an `=` (equals) sign,
+followed by a newline. The key may contain any bytes except `=`,
+newline, or NUL. The value may contain any bytes except newline or NUL.
+In both cases, all bytes are treated as-is (i.e., there is no quoting,
+and one cannot transmit a value with newline or NUL in it). The list of
+attributes is terminated by a blank line or end-of-file.
+Git will send the following attributes (but may not send all of
+them for a given credential; for example, a `host` attribute makes no
+sense when dealing with a non-network protocol):
+
+`protocol`::
+
+ The protocol over which the credential will be used (e.g.,
+ `https`).
+
+`host`::
+
+ The remote hostname for a network credential.
+
+`path`::
+
+ The path with which the credential will be used. E.g., for
+ accessing a remote https repository, this will be the
+ repository's path on the server.
+
+`username`::
+
+ The credential's username, if we already have one (e.g., from a
+ URL, from the user, or from a previously run helper).
+
+`password`::
+
+ The credential's password, if we are asking it to be stored.
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index 827bc98..88d814a 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -252,7 +252,7 @@ Configuring database backend
'git-cvsserver' uses the Perl DBI module. Please also read
its documentation if changing these variables, especially
-about `DBI\->connect()`.
+about `DBI->connect()`.
gitcvs.dbname::
Database name. The exact meaning depends on the
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index ebd13be..31b28fc 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -93,14 +93,14 @@ OPTIONS
Listen on an alternative port. Incompatible with '--inetd' option.
--init-timeout=<n>::
- Timeout between the moment the connection is established and the
- client request is received (typically a rather low value, since
+ Timeout (in seconds) between the moment the connection is established
+ and the client request is received (typically a rather low value, since
that should be basically immediate).
--timeout=<n>::
- Timeout for specific client sub-requests. This includes the time
- it takes for the server to process the sub-request and the time spent
- waiting for the next client's request.
+ Timeout (in seconds) for specific client sub-requests. This includes
+ the time it takes for the server to process the sub-request and the
+ time spent waiting for the next client's request.
--max-connections=<n>::
Maximum number of concurrent clients, defaults to 32. Set it to
@@ -161,6 +161,16 @@ the facility of inet daemon to achieve the same before spawning
repository configuration. By default, all the services
are overridable.
+--informative-errors::
+--no-informative-errors::
+ When informative errors are turned on, git-daemon will report
+ more verbose errors to the client, differentiating conditions
+ like "no such repository" from "repository not exported". This
+ is more convenient for clients, but may leak information about
+ the existence of unexported repositories. When informative
+ errors are not enabled, all errors report "access denied" to the
+ client. The default is --no-informative-errors.
+
<directory>::
A directory to add to the whitelist of allowed directories. Unless
--strict-paths is specified this will also include subdirectories
diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt
index a03515f..31fc2e3 100644
--- a/Documentation/git-difftool.txt
+++ b/Documentation/git-difftool.txt
@@ -19,6 +19,12 @@ linkgit:git-diff[1].
OPTIONS
-------
+-d::
+--dir-diff::
+ Copy the modified files to a temporary location and perform
+ a directory diff on them. This mode never prompts before
+ launching the diff tool.
+
-y::
--no-prompt::
Do not prompt before launching a diff tool.
@@ -30,10 +36,9 @@ OPTIONS
-t <tool>::
--tool=<tool>::
- Use the diff tool specified by <tool>.
- Valid merge tools are:
- araxis, bc3, diffuse, emerge, ecmerge, gvimdiff, kdiff3,
- kompare, meld, opendiff, p4merge, tkdiff, vimdiff and xxdiff.
+ Use the diff tool specified by <tool>. Valid values include
+ emerge, kompare, meld, and vimdiff. Run `git difftool --tool-help`
+ for the list of valid <tool> settings.
+
If a diff tool is not specified, 'git difftool'
will use the configuration variable `diff.tool`. If the
@@ -61,6 +66,9 @@ of the diff post-image. `$MERGED` is the name of the file which is
being compared. `$BASE` is provided for compatibility
with custom merge tool commands and has the same value as `$MERGED`.
+--tool-help::
+ Print a list of diff tools that may be used with `--tool`.
+
-x <command>::
--extcmd=<command>::
Specify a custom command for viewing diffs.
diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt
index a29ac02..d6487e1 100644
--- a/Documentation/git-fast-export.txt
+++ b/Documentation/git-fast-export.txt
@@ -83,6 +83,10 @@ marks the same across runs.
allow that. So fake a tagger to be able to fast-import the
output.
+--use-done-feature::
+ Start the stream with a 'feature done' stanza, and terminate
+ it with a 'done' command.
+
--no-data::
Skip output of blob objects and instead refer to blobs via
their original SHA-1 hash. This is useful when rewriting the
@@ -100,7 +104,7 @@ marks the same across runs.
[<git-rev-list-args>...]::
A list of arguments, acceptable to 'git rev-parse' and
'git rev-list', that specifies the specific objects and references
- to export. For example, `master{tilde}10..master` causes the
+ to export. For example, `master~10..master` causes the
current master reference to be exported along with all objects
added since its 10th ancestor commit.
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index 95e480e..2620d28 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -98,9 +98,16 @@ OPTIONS
options.
--cat-blob-fd=<fd>::
- Specify the file descriptor that will be written to
- when the `cat-blob` command is encountered in the stream.
- The default behaviour is to write to `stdout`.
+ Write responses to `cat-blob` and `ls` queries to the
+ file descriptor <fd> instead of `stdout`. Allows `progress`
+ output intended for the end-user to be separated from other
+ output.
+
+--done::
+ Require a `done` command at the end of the stream.
+ This option might be useful for detecting errors that
+ cause the frontend to terminate before it has started to
+ write a stream.
--export-pack-edges=<file>::
After creating a packfile, print a line of data to
@@ -331,6 +338,11 @@ and control the current import process. More detailed discussion
standard output. This command is optional and is not needed
to perform an import.
+`done`::
+ Marks the end of the stream. This command is optional
+ unless the `done` feature was requested using the
+ `--done` command line option or `feature done` command.
+
`cat-blob`::
Causes fast-import to print a blob in 'cat-file --batch'
format to the file descriptor set with `--cat-blob-fd` or
@@ -414,8 +426,8 @@ Here `<name>` is the person's display name (for example
(``cm@example.com''). `LT` and `GT` are the literal less-than (\x3c)
and greater-than (\x3e) symbols. These are required to delimit
the email address from the other fields in the line. Note that
-`<name>` is free-form and may contain any sequence of bytes, except
-`LT` and `LF`. It is typically UTF-8 encoded.
+`<name>` and `<email>` are free-form and may contain any sequence
+of bytes, except `LT`, `GT` and `LF`. `<name>` is typically UTF-8 encoded.
The time of the change is specified by `<when>` using the date format
that was selected by the \--date-format=<fmt> command line option.
@@ -467,9 +479,9 @@ current branch value should be written as:
----
from refs/heads/branch^0
----
-The `{caret}0` suffix is necessary as fast-import does not permit a branch to
+The `^0` suffix is necessary as fast-import does not permit a branch to
start from itself, and the branch is created in memory before the
-`from` command is even read from the input. Adding `{caret}0` will force
+`from` command is even read from the input. Adding `^0` will force
fast-import to resolve the commit through Git's revision parsing library,
rather than its internal branch table, thereby loading in the
existing value of the branch.
@@ -931,6 +943,9 @@ This command can be used anywhere in the stream that comments are
accepted. In particular, the `cat-blob` command can be used in the
middle of a commit but not in the middle of a `data` command.
+See ``Responses To Commands'' below for details about how to read
+this output safely.
+
`ls`
~~~~
Prints information about the object at a path to a file descriptor
@@ -964,7 +979,7 @@ Reading from a named tree::
See `filemodify` above for a detailed description of `<path>`.
-Output uses the same format as `git ls-tree <tree> {litdd} <path>`:
+Output uses the same format as `git ls-tree <tree> -- <path>`:
====
<mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF
@@ -980,6 +995,9 @@ instead report
missing SP <path> LF
====
+See ``Responses To Commands'' below for details about how to read
+this output safely.
+
`feature`
~~~~~~~~~
Require that fast-import supports the specified feature, or abort if
@@ -1001,10 +1019,14 @@ force::
(see OPTIONS, above).
import-marks::
+import-marks-if-exists::
Like --import-marks except in two respects: first, only one
- "feature import-marks" command is allowed per stream;
- second, an --import-marks= command-line option overrides
- any "feature import-marks" command in the stream.
+ "feature import-marks" or "feature import-marks-if-exists"
+ command is allowed per stream; second, an --import-marks=
+ or --import-marks-if-exists command-line option overrides
+ any of these "feature" commands in the stream; third,
+ "feature import-marks-if-exists" like a corresponding
+ command-line option silently skips a nonexistent file.
cat-blob::
ls::
@@ -1021,6 +1043,11 @@ notes::
Versions of fast-import not supporting notes will exit
with a message indicating so.
+done::
+ Error out if the stream ends without a 'done' command.
+ Without this feature, errors causing the frontend to end
+ abruptly at a convenient point in the stream can go
+ undetected.
`option`
~~~~~~~~
@@ -1050,6 +1077,44 @@ not be passed as option:
* cat-blob-fd
* force
+`done`
+~~~~~~
+If the `done` feature is not in use, treated as if EOF was read.
+This can be used to tell fast-import to finish early.
+
+If the `--done` command line option or `feature done` command is
+in use, the `done` command is mandatory and marks the end of the
+stream.
+
+Responses To Commands
+---------------------
+New objects written by fast-import are not available immediately.
+Most fast-import commands have no visible effect until the next
+checkpoint (or completion). The frontend can send commands to
+fill fast-import's input pipe without worrying about how quickly
+they will take effect, which improves performance by simplifying
+scheduling.
+
+For some frontends, though, it is useful to be able to read back
+data from the current repository as it is being updated (for
+example when the source material describes objects in terms of
+patches to be applied to previously imported objects). This can
+be accomplished by connecting the frontend and fast-import via
+bidirectional pipes:
+
+====
+ mkfifo fast-import-output
+ frontend <fast-import-output |
+ git fast-import >fast-import-output
+====
+
+A frontend set up this way can use `progress`, `ls`, and `cat-blob`
+commands to read information from the import in progress.
+
+To avoid deadlock, such frontends must completely consume any
+pending output from `progress`, `ls`, and `cat-blob` before
+performing writes to fast-import that might block.
+
Crash Reports
-------------
If fast-import is supplied invalid input it will terminate with a
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
index ed1bdaa..474fa30 100644
--- a/Documentation/git-fetch-pack.txt
+++ b/Documentation/git-fetch-pack.txt
@@ -32,6 +32,16 @@ OPTIONS
--all::
Fetch all remote refs.
+--stdin::
+ Take the list of refs from stdin, one per line. If there
+ are refs specified on the command line in addition to this
+ option, then the refs from stdin are processed after those
+ on the command line.
++
+If '--stateless-rpc' is specified together with this option then
+the list of refs must be in packet format (pkt-line). Each ref must
+be in a separate packet, and the list must end with a flush packet.
+
-q::
--quiet::
Pass '-q' flag to 'git unpack-objects'; this makes the
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 0f2f117..81f5823 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -96,8 +96,8 @@ 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. Frequently used with `git rm \--cached
- \--ignore-unmatch ...`, see EXAMPLES below. For hairy
+ faster. Frequently used with `git rm --cached
+ --ignore-unmatch ...`, see EXAMPLES below. For hairy
cases, see linkgit:git-update-index[1].
--parent-filter <command>::
@@ -222,11 +222,11 @@ However, if the file is absent from the tree of some commit,
a simple `rm filename` will fail for that tree and commit.
Thus you may instead want to use `rm -f filename` as the script.
-Using `\--index-filter` with 'git rm' yields a significantly faster
+Using `--index-filter` with 'git rm' yields a significantly faster
version. Like with using `rm filename`, `git rm --cached filename`
will fail if the file is absent from the tree of a commit. If you
want to "completely forget" a file, it does not matter when it entered
-history, so we also add `\--ignore-unmatch`:
+history, so we also add `--ignore-unmatch`:
--------------------------------------------------------------------------
git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
@@ -242,8 +242,8 @@ git filter-branch --subdirectory-filter foodir -- --all
-------------------------------------------------------
Thus you can, e.g., turn a library subdirectory into a repository of
-its own. Note the `\--` that separates 'filter-branch' options from
-revision options, and the `\--all` to rewrite all branches and tags.
+its own. Note the `--` that separates 'filter-branch' options from
+revision options, and the `--all` to rewrite all branches and tags.
To set a commit (which typically is at the tip of another
history) to be the parent of the current initial commit, in
@@ -371,23 +371,23 @@ Checklist for Shrinking a Repository
------------------------------------
git-filter-branch is often used to get rid of a subset of files,
-usually with some combination of `\--index-filter` and
-`\--subdirectory-filter`. People expect the resulting repository to
+usually with some combination of `--index-filter` and
+`--subdirectory-filter`. People expect the resulting repository to
be smaller than the original, but you need a few more steps to
actually make it smaller, because git tries hard not to lose your
objects until you tell it to. First make sure that:
* You really removed all variants of a filename, if a blob was moved
- over its lifetime. `git log \--name-only \--follow \--all \--
- filename` can help you find renames.
+ over its lifetime. `git log --name-only --follow --all -- filename`
+ can help you find renames.
-* You really filtered all refs: use `\--tag-name-filter cat \--
- \--all` when calling git-filter-branch.
+* You really filtered all refs: use `--tag-name-filter cat -- --all`
+ when calling git-filter-branch.
Then there are two ways to get a smaller repository. A safer way is
to clone, that keeps your original intact.
-* Clone it with `git clone +++file:///path/to/repo+++`. The clone
+* Clone it with `git clone file:///path/to/repo`. The clone
will not have the removed objects. See linkgit:git-clone[1]. (Note
that cloning with a plain path just hardlinks everything!)
@@ -397,14 +397,14 @@ approach, so *make a backup* or go back to cloning it. You have been
warned.
* Remove the original refs backed up by git-filter-branch: say `git
- for-each-ref \--format="%(refname)" refs/original/ | xargs -n 1 git
+ for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git
update-ref -d`.
-* Expire all reflogs with `git reflog expire \--expire=now \--all`.
+* Expire all reflogs with `git reflog expire --expire=now --all`.
-* Garbage collect all unreferenced objects with `git gc \--prune=now`
+* Garbage collect all unreferenced objects with `git gc --prune=now`
(or if your git-gc is not new enough to support arguments to
- `\--prune`, use `git repack -ad; git prune` instead).
+ `--prune`, use `git repack -ad; git prune` instead).
GIT
---
diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt
index 32aff95..3a0f55e 100644
--- a/Documentation/git-fmt-merge-msg.txt
+++ b/Documentation/git-fmt-merge-msg.txt
@@ -53,6 +53,11 @@ OPTIONS
CONFIGURATION
-------------
+merge.branchdesc::
+ In addition to branch names, populate the log message with
+ the branch description text associated with them. Defaults
+ to false.
+
merge.log::
In addition to branch names, populate the log message with at
most the specified number of one-line descriptions from the
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 152e695..c872b88 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -101,9 +101,10 @@ Fields that have name-email-date tuple as its value (`author`,
`committer`, and `tagger`) can be suffixed with `name`, `email`,
and `date` to extract the named component.
-The first line of the message in a commit and tag object is
-`subject`, the remaining lines are `body`. The whole message
-is `contents`.
+The complete message in a commit and tag object is `contents`.
+Its first line is `contents:subject`, the remaining lines
+are `contents:body` and the optional GPG signature
+is `contents:signature`.
For sorting purposes, fields with numeric values sort in numeric
order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index d13c9b2..04c7346 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -45,7 +45,7 @@ There are two ways to specify which commits to operate on.
The first rule takes precedence in the case of a single <commit>. To
apply the second rule, i.e., format everything since the beginning of
history up until <commit>, use the '\--root' option: `git format-patch
-\--root <commit>`. If you want to format only <commit> itself, you
+--root <commit>`. If you want to format only <commit> itself, you
can do this with `git format-patch -1 <commit>`.
By default, each output file is numbered sequentially from 1, and uses the
@@ -134,7 +134,7 @@ include::diff-options.txt[]
The optional <style> argument can be either `shallow` or `deep`.
'shallow' threading makes every mail a reply to the head of the
series, where the head is chosen from the cover letter, the
-`\--in-reply-to`, and the first patch mail, in this order. 'deep'
+`--in-reply-to`, and the first patch mail, in this order. 'deep'
threading makes every mail a reply to the previous one.
+
The default is `--no-thread`, unless the 'format.thread' configuration
@@ -166,15 +166,22 @@ will want to ensure that threading is disabled for `git send-email`.
--to=<email>::
Add a `To:` header to the email headers. This is in addition
to any configured headers, and may be used multiple times.
+ The negated form `--no-to` discards all `To:` headers added so
+ far (from config or command line).
--cc=<email>::
Add a `Cc:` header to the email headers. This is in addition
to any configured headers, and may be used multiple times.
+ The negated form `--no-cc` discards all `Cc:` headers added so
+ far (from config or command line).
--add-header=<header>::
Add an arbitrary header to the email headers. This is in addition
to any configured headers, and may be used multiple times.
- For example, `--add-header="Organization: git-foo"`
+ For example, `--add-header="Organization: git-foo"`.
+ The negated form `--no-add-header` discards *all* (`To:`,
+ `Cc:`, and custom) headers added so far from config or command
+ line.
--cover-letter::
In addition to the patches, generate a cover letter file
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index a2a508d..bbb25da 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -10,7 +10,8 @@ SYNOPSIS
--------
[verse]
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
- [--[no-]full] [--strict] [--verbose] [--lost-found] [<object>*]
+ [--[no-]full] [--strict] [--verbose] [--lost-found]
+ [--[no-]dangling] [--[no-]progress] [<object>*]
DESCRIPTION
-----------
@@ -29,6 +30,11 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
Print out objects that exist but that aren't reachable from any
of the reference nodes.
+--dangling::
+--no-dangling::
+ Print objects that exist but that are never 'directly' used (default).
+ `--no-dangling` can be used to omit this information from the output.
+
--root::
Report root nodes.
@@ -72,30 +78,28 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
a blob, the contents are written into the file, rather than
its object name.
-It tests SHA1 and general object sanity, and it does full tracking of
-the resulting reachability and everything else. It prints out any
-corruption it finds (missing or bad objects), and if you use the
-'--unreachable' flag it will also print out objects that exist but
-that aren't reachable from any of the specified head nodes.
-
-So for example
+--progress::
+--no-progress::
+ Progress status is reported on the standard error stream by
+ default when it is attached to a terminal, unless
+ --no-progress or --verbose is specified. --progress forces
+ progress status even if the standard error stream is not
+ directed to a terminal.
- git fsck --unreachable HEAD \
- $(git for-each-ref --format="%(objectname)" refs/heads)
+DISCUSSION
+----------
-will do quite a _lot_ of verification on the tree. There are a few
-extra validity tests to be added (make sure that tree objects are
-sorted properly etc), but on the whole if 'git fsck' is happy, you
-do have a valid tree.
+git-fsck tests SHA1 and general object sanity, and it does full tracking
+of the resulting reachability and everything else. It prints out any
+corruption it finds (missing or bad objects), and if you use the
+'--unreachable' flag it will also print out objects that exist but that
+aren't reachable from any of the specified head nodes (or the default
+set, as mentioned above).
Any corrupt objects you will have to find in backups or other archives
(i.e., you can just remove them and do an 'rsync' with some other site in
the hopes that somebody else has the object you have corrupted).
-Of course, "valid tree" doesn't mean that it wasn't generated by some
-evil person, and the end result might be crap. git is a revision
-tracking system, not a quality assurance system ;)
-
Extracted Diagnostics
---------------------
diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt
index 815afcb..b370b02 100644
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
@@ -84,7 +84,7 @@ The optional configuration variable 'gc.reflogExpireUnreachable'
can be set to indicate how long historical reflog entries which
are not part of the current branch should remain available in
this repository. These types of entries are generally created as
-a result of using `git commit \--amend` or `git rebase` and are the
+a result of using `git commit --amend` or `git rebase` and are the
commits prior to the amend or rebase occurring. Since these changes
are not part of the current project most users will want to expire
them sooner. This option defaults to '30 days'.
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index e150c77..3bec036 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -20,16 +20,20 @@ SYNOPSIS
[-c | --count] [--all-match] [-q | --quiet]
[--max-depth <depth>]
[--color[=<when>] | --no-color]
+ [--break] [--heading] [-p | --show-function]
[-A <post-context>] [-B <pre-context>] [-C <context>]
+ [-W | --function-context]
[-f <file>] [-e] <pattern>
[--and|--or|--not|(|)|-e <pattern>...]
- [--cached | --no-index | <tree>...]
+ [ [--exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
[--] [<pathspec>...]
DESCRIPTION
-----------
Look for specified patterns in the tracked files in the work tree, blobs
-registered in the index file, or blobs in given tree objects.
+registered in the index file, or blobs in given tree objects. Patterns
+are lists of one or more search expressions separated by newline
+characters. An empty string as search expression matches all lines.
CONFIGURATION
@@ -49,7 +53,20 @@ OPTIONS
blobs registered in the index file.
--no-index::
- Search files in the current directory, not just those tracked by git.
+ Search files in the current directory that is not managed by git.
+
+--untracked::
+ In addition to searching in the tracked files in the working
+ tree, search also in untracked files.
+
+--no-exclude-standard::
+ Also search in ignored files by not honoring the `.gitignore`
+ mechanism. Only useful with `--untracked`.
+
+--exclude-standard::
+ Do not pay attention to ignored files specified via the `.gitignore`
+ mechanism. Only useful when searching files in the current directory
+ with `--no-index`.
-a::
--text::
@@ -66,6 +83,9 @@ OPTIONS
--max-depth <depth>::
For each <pathspec> given on command line, descend at most <depth>
levels of directories. A negative value means no limit.
+ This option is ignored if <pathspec> contains active wildcards.
+ In other words if "a*" matches a directory named "a*",
+ "*" is matched literally so --max-depth is still effective.
-w::
--word-regexp::
@@ -148,14 +168,12 @@ OPTIONS
gives the default to color output.
Same as `--color=never`.
--[ABC] <context>::
- Show `context` trailing (`A` -- after), or leading (`B`
- -- before), or both (`C` -- context) lines, and place a
- line containing `--` between contiguous groups of
- matches.
+--break::
+ Print an empty line between matches from different files.
--<num>::
- A shortcut for specifying `-C<num>`.
+--heading::
+ Show the filename above the matches in that file instead of
+ at the start of each shown line.
-p::
--show-function::
@@ -165,6 +183,29 @@ OPTIONS
patch hunk headers (see 'Defining a custom hunk-header' in
linkgit:gitattributes[5]).
+-<num>::
+-C <num>::
+--context <num>::
+ Show <num> leading and trailing lines, and place a line
+ containing `--` between contiguous groups of matches.
+
+-A <num>::
+--after-context <num>::
+ Show <num> trailing lines, and place a line containing
+ `--` between contiguous groups of matches.
+
+-B <num>::
+--before-context <num>::
+ Show <num> leading lines, and place a line containing
+ `--` between contiguous groups of matches.
+
+-W::
+--function-context::
+ Show the surrounding text from the previous line containing a
+ function name up to the one before the next function name,
+ effectively showing the whole function in which the match was
+ found.
+
-f <file>::
Read patterns from <file>, one per line.
@@ -208,15 +249,15 @@ OPTIONS
Examples
--------
-git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}::
+`git grep 'time_t' -- '*.[ch]'`::
Looks for `time_t` in all tracked .c and .h files in the working
directory and its subdirectories.
-git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \)::
+`git grep -e '#define' --and \( -e MAX_PATH -e PATH_MAX \)`::
Looks for a line that has `#define` and either `MAX_PATH` or
`PATH_MAX`.
-git grep --all-match -e NODE -e Unexpected::
+`git grep --all-match -e NODE -e Unexpected`::
Looks for a line that has `NODE` or `Unexpected` in
files that have lines that match both.
diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt
index 18f713b..0041994 100644
--- a/Documentation/git-gui.txt
+++ b/Documentation/git-gui.txt
@@ -50,7 +50,7 @@ version::
Examples
--------
-git gui blame Makefile::
+`git gui blame Makefile`::
Show the contents of the file 'Makefile' in the current
working directory, and provide annotations for both the
@@ -59,41 +59,41 @@ git gui blame Makefile::
uncommitted changes (if any) are explicitly attributed to
'Not Yet Committed'.
-git gui blame v0.99.8 Makefile::
+`git gui blame v0.99.8 Makefile`::
Show the contents of 'Makefile' in revision 'v0.99.8'
and provide annotations for each line. Unlike the above
example the file is read from the object database and not
the working directory.
-git gui blame --line=100 Makefile::
+`git gui blame --line=100 Makefile`::
Loads annotations as described above and automatically
scrolls the view to center on line '100'.
-git gui citool::
+`git gui citool`::
Make one commit and return to the shell when it is complete.
This command returns a non-zero exit code if the window was
closed in any way other than by making a commit.
-git gui citool --amend::
+`git gui citool --amend`::
Automatically enter the 'Amend Last Commit' mode of
the interface.
-git gui citool --nocommit::
+`git gui citool --nocommit`::
Behave as normal citool, but instead of making a commit
simply terminate with a zero exit code. It still checks
that the index does not contain any unmerged entries, so
you can use it as a GUI version of linkgit:git-mergetool[1]
-git citool::
+`git citool`::
Same as `git gui citool` (above).
-git gui browser maint::
+`git gui browser maint`::
Show a browser for the tree of the 'maint' branch. Files
selected in the browser can be viewed with the internal
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index 277d9e1..f4e0741 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -119,6 +119,14 @@ ScriptAliasMatch \
ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
----------------------------------------------------------------
++
+To serve multiple repositories from different linkgit:gitnamespaces[7] in a
+single repository:
++
+----------------------------------------------------------------
+SetEnvIf Request_URI "^/git/([^/]*)" GIT_NAMESPACE=$1
+ScriptAliasMatch ^/git/[^/]*(.*) /usr/libexec/git-core/git-http-backend/storage.git$1
+----------------------------------------------------------------
Accelerated static Apache 2.x::
Similar to the above, but Apache can be used to return static
diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt
index 4d42073..070cd1e 100644
--- a/Documentation/git-http-fetch.txt
+++ b/Documentation/git-http-fetch.txt
@@ -15,6 +15,9 @@ DESCRIPTION
-----------
Downloads a remote git repository via HTTP.
+*NOTE*: use of this command without -a is deprecated. The -a
+behaviour will become the default in a future release.
+
OPTIONS
-------
commit-id::
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
index 909687f..39e6d0d 100644
--- a/Documentation/git-index-pack.txt
+++ b/Documentation/git-index-pack.txt
@@ -74,6 +74,16 @@ OPTIONS
--strict::
Die, if the pack contains broken objects or links.
+--threads=<n>::
+ Specifies the number of threads to spawn when resolving
+ deltas. This requires that index-pack 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.
+ Specifying 0 will cause git to auto-detect the number of CPU's
+ and use maximum 3 threads.
+
Note
----
diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt
index 08f85ba..f3eef51 100644
--- a/Documentation/git-instaweb.txt
+++ b/Documentation/git-instaweb.txt
@@ -51,8 +51,8 @@ OPTIONS
start::
--start::
- Start the httpd instance and exit. This does not generate
- any of the configuration files for spawning a new instance.
+ Start the httpd instance and exit. Regenerate configuration files
+ as necessary for spawning a new instance.
stop::
--stop::
@@ -62,8 +62,8 @@ stop::
restart::
--restart::
- Restart the httpd instance and exit. This does not generate
- any of the configuration files for spawning a new instance.
+ Restart the httpd instance and exit. Regenerate configuration files
+ as necessary for spawning a new instance.
CONFIGURATION
-------------
@@ -84,6 +84,10 @@ If the configuration variable 'instaweb.browser' is not set,
'web.browser' will be used instead if it is defined. See
linkgit:git-web{litdd}browse[1] for more information about this.
+SEE ALSO
+--------
+linkgit:gitweb[1]
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 771a356..1f90620 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -91,45 +91,45 @@ include::diff-generate-patch.txt[]
Examples
--------
-git log --no-merges::
+`git log --no-merges`::
Show the whole commit history, but skip any merges
-git log v2.6.12.. include/scsi drivers/scsi::
+`git log v2.6.12.. include/scsi drivers/scsi`::
Show all commits since version 'v2.6.12' that changed any file
in the include/scsi or drivers/scsi subdirectories
-git log --since="2 weeks ago" \-- gitk::
+`git log --since="2 weeks ago" -- gitk`::
Show the changes during the last two weeks to the file 'gitk'.
The "--" is necessary to avoid confusion with the *branch* named
'gitk'
-git log --name-status release..test::
+`git log --name-status release..test`::
Show the commits that are in the "test" branch but not yet
in the "release" branch, along with the list of paths
each commit modifies.
-git log --follow builtin-rev-list.c::
+`git log --follow builtin-rev-list.c`::
Shows the commits that changed builtin-rev-list.c, including
those commits that occurred before the file was given its
present name.
-git log --branches --not --remotes=origin::
+`git log --branches --not --remotes=origin`::
Shows all commits that are in any of local branches but not in
any of remote-tracking branches for 'origin' (what you have that
origin doesn't).
-git log master --not --remotes=*/master::
+`git log master --not --remotes=*/master`::
Shows all commits that are in local master but not in any remote
repository master branches.
-git log -p -m --first-parent::
+`git log -p -m --first-parent`::
Shows the history including change diffs, but only from the
"main branch" perspective, skipping commits that come from merged
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index 51dc325..97e7a8e 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -25,13 +25,24 @@ command directly. See linkgit:git-am[1] instead.
OPTIONS
-------
-k::
- Usually the program 'cleans up' the Subject: header line
- to extract the title line for the commit log message,
- among which (1) remove 'Re:' or 're:', (2) leading
- whitespaces, (3) '[' up to ']', typically '[PATCH]', and
- then prepends "[PATCH] ". This flag forbids this
- munging, and is most useful when used to read back
- 'git format-patch -k' output.
+ Usually the program removes email cruft from the Subject:
+ header line to extract the title line for the commit log
+ message. This option prevents this munging, and is most
+ useful when used to read back 'git format-patch -k' output.
++
+Specifically, the following are removed until none of them remain:
++
+--
+* Leading and trailing whitespace.
+
+* Leading `Re:`, `re:`, and `:`.
+
+* Leading bracketed strings (between `[` and `]`, usually
+ `[PATCH]`).
+--
++
+Finally, runs of whitespace are normalized to a single ASCII space
+character.
-b::
When -k is not in effect, all leading strings bracketed with '['
diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt
index 635c669..d7db2a3 100644
--- a/Documentation/git-merge-file.txt
+++ b/Documentation/git-merge-file.txt
@@ -76,12 +76,12 @@ OPTIONS
EXAMPLES
--------
-git merge-file README.my README README.upstream::
+`git merge-file README.my README README.upstream`::
combines the changes of README.my and README.upstream since README,
tries to merge them and writes the result into README.my.
-git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345::
+`git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345`::
merges tmp/a123 and tmp/c345 with the base tmp/b234, but uses labels
`a` and `c` instead of `tmp/a123` and `tmp/c345`.
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index e2e6aba..3ceefb8 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -9,7 +9,7 @@ git-merge - Join two or more development histories together
SYNOPSIS
--------
[verse]
-'git merge' [-n] [--stat] [--no-commit] [--squash]
+'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
[-s <strategy>] [-X <strategy-option>]
[--[no-]rerere-autoupdate] [-m <msg>] [<commit>...]
'git merge' <msg> HEAD <commit>...
diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt
index b8db373..e3c8448 100644
--- a/Documentation/git-mv.txt
+++ b/Documentation/git-mv.txt
@@ -15,8 +15,8 @@ DESCRIPTION
-----------
This script is used to move or rename a file, directory or symlink.
- git mv [-f] [-n] <source> <destination>
- git mv [-f] [-n] [-k] <source> ... <destination directory>
+ git mv [-v] [-f] [-n] [-k] <source> <destination>
+ git mv [-v] [-f] [-n] [-k] <source> ... <destination directory>
In the first form, it renames <source>, which must exist and be either
a file, symlink or directory, to <destination>.
@@ -40,6 +40,10 @@ OPTIONS
--dry-run::
Do nothing; only show what would happen
+-v::
+--verbose::
+ Report the names of files as they are moved.
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 6a187f2..b95aafa 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -70,7 +70,7 @@ copy::
second object). This subcommand is equivalent to:
`git notes add [-f] -C $(git notes list <from-object>) <to-object>`
+
-In `\--stdin` mode, take lines in the format
+In `--stdin` mode, take lines in the format
+
----------
<from-object> SP <to-object> [ SP <rest> ] LF
@@ -142,8 +142,9 @@ OPTIONS
-C <object>::
--reuse-message=<object>::
- Take the note message from the given blob object (for
- example, another note).
+ Take the given blob object (for example, another note) as the
+ note message. (Use `git notes copy <object>` instead to
+ copy notes between objects.)
-c <object>::
--reedit-message=<object>::
@@ -285,6 +286,8 @@ $ blob=$(git hash-object -w a.out)
$ git notes --ref=built add -C "$blob" HEAD
------------
+(You cannot simply use `git notes --ref=built add -F a.out HEAD`
+because that is not binary-safe.)
Of course, it doesn't make much sense to display non-text-format notes
with 'git log', so if you use such notes, you'll probably need to write
some special-purpose tools to do something useful with them.
diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
new file mode 100644
index 0000000..fe1f49b
--- /dev/null
+++ b/Documentation/git-p4.txt
@@ -0,0 +1,531 @@
+git-p4(1)
+=========
+
+NAME
+----
+git-p4 - Import from and submit to Perforce repositories
+
+
+SYNOPSIS
+--------
+[verse]
+'git p4 clone' [<sync options>] [<clone options>] <p4 depot path>...
+'git p4 sync' [<sync options>] [<p4 depot path>...]
+'git p4 rebase'
+'git p4 submit' [<submit options>] [<master branch name>]
+
+
+DESCRIPTION
+-----------
+This command provides a way to interact with p4 repositories
+using git.
+
+Create a new git repository from an existing p4 repository using
+'git p4 clone', giving it one or more p4 depot paths. Incorporate
+new commits from p4 changes with 'git p4 sync'. The 'sync' command
+is also used to include new branches from other p4 depot paths.
+Submit git changes back to p4 using 'git p4 submit'. The command
+'git p4 rebase' does a sync plus rebases the current branch onto
+the updated p4 remote branch.
+
+
+EXAMPLE
+-------
+* Clone a repository:
++
+------------
+$ git p4 clone //depot/path/project
+------------
+
+* Do some work in the newly created git repository:
++
+------------
+$ cd project
+$ vi foo.h
+$ git commit -a -m "edited foo.h"
+------------
+
+* Update the git repository with recent changes from p4, rebasing your
+ work on top:
++
+------------
+$ git p4 rebase
+------------
+
+* Submit your commits back to p4:
++
+------------
+$ git p4 submit
+------------
+
+
+COMMANDS
+--------
+
+Clone
+~~~~~
+Generally, 'git p4 clone' is used to create a new git directory
+from an existing p4 repository:
+------------
+$ git p4 clone //depot/path/project
+------------
+This:
+
+1. Creates an empty git repository in a subdirectory called 'project'.
++
+2. Imports the full contents of the head revision from the given p4
+depot path into a single commit in the git branch 'refs/remotes/p4/master'.
++
+3. Creates a local branch, 'master' from this remote and checks it out.
+
+To reproduce the entire p4 history in git, use the '@all' modifier on
+the depot path:
+------------
+$ git p4 clone //depot/path/project@all
+------------
+
+
+Sync
+~~~~
+As development continues in the p4 repository, those changes can
+be included in the git repository using:
+------------
+$ git p4 sync
+------------
+This command finds new changes in p4 and imports them as git commits.
+
+P4 repositories can be added to an existing git repository using
+'git p4 sync' too:
+------------
+$ mkdir repo-git
+$ cd repo-git
+$ git init
+$ git p4 sync //path/in/your/perforce/depot
+------------
+This imports the specified depot into
+'refs/remotes/p4/master' in an existing git repository. The
+'--branch' option can be used to specify a different branch to
+be used for the p4 content.
+
+If a git repository includes branches 'refs/remotes/origin/p4', these
+will be fetched and consulted first during a 'git p4 sync'. Since
+importing directly from p4 is considerably slower than pulling changes
+from a git remote, this can be useful in a multi-developer environment.
+
+
+Rebase
+~~~~~~
+A common working pattern is to fetch the latest changes from the p4 depot
+and merge them with local uncommitted changes. Often, the p4 repository
+is the ultimate location for all code, thus a rebase workflow makes
+sense. This command does 'git p4 sync' followed by 'git rebase' to move
+local commits on top of updated p4 changes.
+------------
+$ git p4 rebase
+------------
+
+
+Submit
+~~~~~~
+Submitting changes from a git repository back to the p4 repository
+requires a separate p4 client workspace. This should be specified
+using the 'P4CLIENT' environment variable or the git configuration
+variable 'git-p4.client'. The p4 client must exist, but the client root
+will be created and populated if it does not already exist.
+
+To submit all changes that are in the current git branch but not in
+the 'p4/master' branch, use:
+------------
+$ git p4 submit
+------------
+
+To specify a branch other than the current one, use:
+------------
+$ git p4 submit topicbranch
+------------
+
+The upstream reference is generally 'refs/remotes/p4/master', but can
+be overridden using the '--origin=' command-line option.
+
+The p4 changes will be created as the user invoking 'git p4 submit'. The
+'--preserve-user' option will cause ownership to be modified
+according to the author of the git commit. This option requires admin
+privileges in p4, which can be granted using 'p4 protect'.
+
+
+OPTIONS
+-------
+
+General options
+~~~~~~~~~~~~~~~
+All commands except clone accept these options.
+
+--git-dir <dir>::
+ Set the 'GIT_DIR' environment variable. See linkgit:git[1].
+
+--verbose::
+ Provide more progress information.
+
+Sync options
+~~~~~~~~~~~~
+These options can be used in the initial 'clone' as well as in
+subsequent 'sync' operations.
+
+--branch <branch>::
+ Import changes into given branch. If the branch starts with
+ 'refs/', it will be used as is, otherwise the path 'refs/heads/'
+ will be prepended. The default branch is 'master'. If used
+ with an initial clone, no HEAD will be checked out.
++
+This example imports a new remote "p4/proj2" into an existing
+git repository:
++
+----
+ $ git init
+ $ git p4 sync --branch=refs/remotes/p4/proj2 //depot/proj2
+----
+
+--detect-branches::
+ Use the branch detection algorithm to find new paths in p4. It is
+ documented below in "BRANCH DETECTION".
+
+--changesfile <file>::
+ Import exactly the p4 change numbers listed in 'file', one per
+ line. Normally, 'git p4' inspects the current p4 repository
+ state and detects the changes it should import.
+
+--silent::
+ Do not print any progress information.
+
+--detect-labels::
+ Query p4 for labels associated with the depot paths, and add
+ them as tags in git. Limited usefulness as only imports labels
+ associated with new changelists. Deprecated.
+
+--import-labels::
+ Import labels from p4 into git.
+
+--import-local::
+ By default, p4 branches are stored in 'refs/remotes/p4/',
+ where they will be treated as remote-tracking branches by
+ linkgit:git-branch[1] and other commands. This option instead
+ puts p4 branches in 'refs/heads/p4/'. Note that future
+ sync operations must specify '--import-local' as well so that
+ they can find the p4 branches in refs/heads.
+
+--max-changes <n>::
+ Limit the number of imported changes to 'n'. Useful to
+ limit the amount of history when using the '@all' p4 revision
+ specifier.
+
+--keep-path::
+ The mapping of file names from the p4 depot path to git, by
+ default, involves removing the entire depot path. With this
+ option, the full p4 depot path is retained in git. For example,
+ path '//depot/main/foo/bar.c', when imported from
+ '//depot/main/', becomes 'foo/bar.c'. With '--keep-path', the
+ git path is instead 'depot/main/foo/bar.c'.
+
+--use-client-spec::
+ Use a client spec to find the list of interesting files in p4.
+ See the "CLIENT SPEC" section below.
+
+Clone options
+~~~~~~~~~~~~~
+These options can be used in an initial 'clone', along with the 'sync'
+options described above.
+
+--destination <directory>::
+ Where to create the git repository. If not provided, the last
+ component in the p4 depot path is used to create a new
+ directory.
+
+--bare::
+ Perform a bare clone. See linkgit:git-clone[1].
+
+-/ <path>::
+ Exclude selected depot paths when cloning.
+
+Submit options
+~~~~~~~~~~~~~~
+These options can be used to modify 'git p4 submit' behavior.
+
+--origin <commit>::
+ Upstream location from which commits are identified to submit to
+ p4. By default, this is the most recent p4 commit reachable
+ from 'HEAD'.
+
+-M[<n>]::
+ Detect renames. See linkgit:git-diff[1]. Renames will be
+ represented in p4 using explicit 'move' operations. There
+ is no corresponding option to detect copies, but there are
+ variables for both moves and copies.
+
+--preserve-user::
+ Re-author p4 changes before submitting to p4. This option
+ requires p4 admin privileges.
+
+--export-labels::
+ Export tags from git as p4 labels. Tags found in git are applied
+ to the perforce working directory.
+
+Rebase options
+~~~~~~~~~~~~~~
+These options can be used to modify 'git p4 rebase' behavior.
+
+--import-labels::
+ Import p4 labels.
+
+DEPOT PATH SYNTAX
+-----------------
+The p4 depot path argument to 'git p4 sync' and 'git p4 clone' can
+be one or more space-separated p4 depot paths, with an optional
+p4 revision specifier on the end:
+
+"//depot/my/project"::
+ Import one commit with all files in the '#head' change under that tree.
+
+"//depot/my/project@all"::
+ Import one commit for each change in the history of that depot path.
+
+"//depot/my/project@1,6"::
+ Import only changes 1 through 6.
+
+"//depot/proj1@all //depot/proj2@all"::
+ Import all changes from both named depot paths into a single
+ repository. Only files below these directories are included.
+ There is not a subdirectory in git for each "proj1" and "proj2".
+ You must use the '--destination' option when specifying more
+ than one depot path. The revision specifier must be specified
+ identically on each depot path. If there are files in the
+ depot paths with the same name, the path with the most recently
+ updated version of the file is the one that appears in git.
+
+See 'p4 help revisions' for the full syntax of p4 revision specifiers.
+
+
+CLIENT SPEC
+-----------
+The p4 client specification is maintained with the 'p4 client' command
+and contains among other fields, a View that specifies how the depot
+is mapped into the client repository. The 'clone' and 'sync' commands
+can consult the client spec when given the '--use-client-spec' option or
+when the useClientSpec variable is true. After 'git p4 clone', the
+useClientSpec variable is automatically set in the repository
+configuration file. This allows future 'git p4 submit' commands to
+work properly; the submit command looks only at the variable and does
+not have a command-line option.
+
+The full syntax for a p4 view is documented in 'p4 help views'. 'Git p4'
+knows only a subset of the view syntax. It understands multi-line
+mappings, overlays with '+', exclusions with '-' and double-quotes
+around whitespace. Of the possible wildcards, 'git p4' only handles
+'...', and only when it is at the end of the path. 'Git p4' will complain
+if it encounters an unhandled wildcard.
+
+Bugs in the implementation of overlap mappings exist. If multiple depot
+paths map through overlays to the same location in the repository,
+'git p4' can choose the wrong one. This is hard to solve without
+dedicating a client spec just for 'git p4'.
+
+The name of the client can be given to 'git p4' in multiple ways. The
+variable 'git-p4.client' takes precedence if it exists. Otherwise,
+normal p4 mechanisms of determining the client are used: environment
+variable P4CLIENT, a file referenced by P4CONFIG, or the local host name.
+
+
+BRANCH DETECTION
+----------------
+P4 does not have the same concept of a branch as git. Instead,
+p4 organizes its content as a directory tree, where by convention
+different logical branches are in different locations in the tree.
+The 'p4 branch' command is used to maintain mappings between
+different areas in the tree, and indicate related content. 'git p4'
+can use these mappings to determine branch relationships.
+
+If you have a repository where all the branches of interest exist as
+subdirectories of a single depot path, you can use '--detect-branches'
+when cloning or syncing to have 'git p4' automatically find
+subdirectories in p4, and to generate these as branches in git.
+
+For example, if the P4 repository structure is:
+----
+//depot/main/...
+//depot/branch1/...
+----
+
+And "p4 branch -o branch1" shows a View line that looks like:
+----
+//depot/main/... //depot/branch1/...
+----
+
+Then this 'git p4 clone' command:
+----
+git p4 clone --detect-branches //depot@all
+----
+produces a separate branch in 'refs/remotes/p4/' for //depot/main,
+called 'master', and one for //depot/branch1 called 'depot/branch1'.
+
+However, it is not necessary to create branches in p4 to be able to use
+them like branches. Because it is difficult to infer branch
+relationships automatically, a git configuration setting
+'git-p4.branchList' can be used to explicitly identify branch
+relationships. It is a list of "source:destination" pairs, like a
+simple p4 branch specification, where the "source" and "destination" are
+the path elements in the p4 repository. The example above relied on the
+presence of the p4 branch. Without p4 branches, the same result will
+occur with:
+----
+git config git-p4.branchList main:branch1
+git p4 clone --detect-branches //depot@all
+----
+
+
+PERFORMANCE
+-----------
+The fast-import mechanism used by 'git p4' creates one pack file for
+each invocation of 'git p4 sync'. Normally, git garbage compression
+(linkgit:git-gc[1]) automatically compresses these to fewer pack files,
+but explicit invocation of 'git repack -adf' may improve performance.
+
+
+CONFIGURATION VARIABLES
+-----------------------
+The following config settings can be used to modify 'git p4' behavior.
+They all are in the 'git-p4' section.
+
+General variables
+~~~~~~~~~~~~~~~~~
+git-p4.user::
+ User specified as an option to all p4 commands, with '-u <user>'.
+ The environment variable 'P4USER' can be used instead.
+
+git-p4.password::
+ Password specified as an option to all p4 commands, with
+ '-P <password>'.
+ The environment variable 'P4PASS' can be used instead.
+
+git-p4.port::
+ Port specified as an option to all p4 commands, with
+ '-p <port>'.
+ The environment variable 'P4PORT' can be used instead.
+
+git-p4.host::
+ Host specified as an option to all p4 commands, with
+ '-h <host>'.
+ The environment variable 'P4HOST' can be used instead.
+
+git-p4.client::
+ Client specified as an option to all p4 commands, with
+ '-c <client>', including the client spec.
+
+Clone and sync variables
+~~~~~~~~~~~~~~~~~~~~~~~~
+git-p4.syncFromOrigin::
+ Because importing commits from other git repositories is much faster
+ than importing them from p4, a mechanism exists to find p4 changes
+ first in git remotes. If branches exist under 'refs/remote/origin/p4',
+ those will be fetched and used when syncing from p4. This
+ variable can be set to 'false' to disable this behavior.
+
+git-p4.branchUser::
+ One phase in branch detection involves looking at p4 branches
+ to find new ones to import. By default, all branches are
+ inspected. This option limits the search to just those owned
+ by the single user named in the variable.
+
+git-p4.branchList::
+ List of branches to be imported when branch detection is
+ enabled. Each entry should be a pair of branch names separated
+ by a colon (:). This example declares that both branchA and
+ branchB were created from main:
++
+-------------
+git config git-p4.branchList main:branchA
+git config --add git-p4.branchList main:branchB
+-------------
+
+git-p4.ignoredP4Labels::
+ List of p4 labels to ignore. This is built automatically as
+ unimportable labels are discovered.
+
+git-p4.importLabels::
+ Import p4 labels into git, as per --import-labels.
+
+git-p4.labelImportRegexp::
+ Only p4 labels matching this regular expression will be imported. The
+ default value is '[a-zA-Z0-9_\-.]+$'.
+
+git-p4.useClientSpec::
+ Specify that the p4 client spec should be used to identify p4
+ depot paths of interest. This is equivalent to specifying the
+ option '--use-client-spec'. See the "CLIENT SPEC" section above.
+ This variable is a boolean, not the name of a p4 client.
+
+Submit variables
+~~~~~~~~~~~~~~~~
+git-p4.detectRenames::
+ Detect renames. See linkgit:git-diff[1].
+
+git-p4.detectCopies::
+ Detect copies. See linkgit:git-diff[1].
+
+git-p4.detectCopiesHarder::
+ Detect copies harder. See linkgit:git-diff[1].
+
+git-p4.preserveUser::
+ On submit, re-author changes to reflect the git author,
+ regardless of who invokes 'git p4 submit'.
+
+git-p4.allowMissingP4Users::
+ When 'preserveUser' is true, 'git p4' normally dies if it
+ cannot find an author in the p4 user map. This setting
+ submits the change regardless.
+
+git-p4.skipSubmitEdit::
+ The submit process invokes the editor before each p4 change
+ is submitted. If this setting is true, though, the editing
+ step is skipped.
+
+git-p4.skipSubmitEditCheck::
+ After editing the p4 change message, 'git p4' makes sure that
+ the description really was changed by looking at the file
+ modification time. This option disables that test.
+
+git-p4.allowSubmit::
+ By default, any branch can be used as the source for a 'git p4
+ submit' operation. This configuration variable, if set, permits only
+ the named branches to be used as submit sources. Branch names
+ must be the short names (no "refs/heads/"), and should be
+ separated by commas (","), with no spaces.
+
+git-p4.skipUserNameCheck::
+ If the user running 'git p4 submit' does not exist in the p4
+ user map, 'git p4' exits. This option can be used to force
+ submission regardless.
+
+git-p4.attemptRCSCleanup::
+ If enabled, 'git p4 submit' will attempt to cleanup RCS keywords
+ ($Header$, etc). These would otherwise cause merge conflicts and prevent
+ the submit going ahead. This option should be considered experimental at
+ present.
+
+git-p4.exportLabels::
+ Export git tags to p4 labels, as per --export-labels.
+
+git-p4.labelExportRegexp::
+ Only p4 labels matching this regular expression will be exported. The
+ default value is '[a-zA-Z0-9_\-.]+$'.
+
+IMPLEMENTATION DETAILS
+----------------------
+* Changesets from p4 are imported using git fast-import.
+* Cloning or syncing does not require a p4 client; file contents are
+ collected using 'p4 print'.
+* Submitting requires a p4 client, which is not in the same location
+ as the git repository. Patches are applied, one at a time, to
+ this p4 client and submitted from there.
+* Each commit imported by 'git p4' has a line at the end of the log
+ message indicating the p4 depot location and change number. This
+ line is used by later 'git p4 sync' operations to know which p4
+ changes are new.
diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt
index a3c6677..10afd4e 100644
--- a/Documentation/git-pack-refs.txt
+++ b/Documentation/git-pack-refs.txt
@@ -32,7 +32,7 @@ Subsequent updates to branches always create new files under
A recommended practice to deal with a repository with too many
refs is to pack its refs with `--all --prune` once, and
-occasionally run `git pack-refs \--prune`. Tags are by
+occasionally run `git pack-refs --prune`. Tags are by
definition stationary and are not expected to change. Branch
heads will be packed with the initial `pack-refs --all`, but
only the currently active branch heads will become unpacked,
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index e1da468..defb544 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -108,9 +108,9 @@ include::merge-options.txt[]
fetched, the rebase uses that information to avoid rebasing
non-local changes.
+
-See `branch.<name>.rebase` and `branch.autosetuprebase` in
+See `pull.rebase`, `branch.<name>.rebase` and `branch.autosetuprebase` in
linkgit:git-config[1] if you want to make `git pull` always use
-`{litdd}rebase` instead of merging.
+`--rebase` instead of merging.
+
[NOTE]
This is a potentially _dangerous_ mode of operation.
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 88acfcd..cb97cc1 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
- [--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
+ [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
[<repository> [<refspec>...]]
DESCRIPTION
@@ -34,7 +34,7 @@ OPTIONS[[OPTIONS]]
<refspec>...::
The format of a <refspec> parameter is an optional plus
- `{plus}`, followed by the source ref <src>, followed
+ `+`, followed by the source ref <src>, followed
by a colon `:`, followed by the destination ref <dst>.
It is used to specify with what <src> object the <dst> ref
in the remote repository is to be updated.
@@ -50,7 +50,7 @@ updated.
+
The object referenced by <src> is used to update the <dst> reference
on the remote side, but by default this is only allowed if the
-update can fast-forward <dst>. By having the optional leading `{plus}`,
+update can fast-forward <dst>. By having the optional leading `+`,
you can tell git to update the <dst> ref even when the update is not a
fast-forward. This does *not* attempt to merge <src> into <dst>. See
EXAMPLES below for details.
@@ -60,7 +60,7 @@ EXAMPLES below for details.
Pushing an empty <src> allows you to delete the <dst> ref from
the remote repository.
+
-The special refspec `:` (or `{plus}:` to allow non-fast-forward updates)
+The special refspec `:` (or `+:` to allow non-fast-forward updates)
directs git to push "matching" branches: for every branch that exists on
the local side, the remote side is updated if a branch of the same name
already exists on the remote side. This is the default operation mode
@@ -71,6 +71,14 @@ nor in any Push line of the corresponding remotes file---see below).
Instead of naming each ref to push, specifies that all
refs under `refs/heads/` be pushed.
+--prune::
+ Remove remote branches that don't have a local counterpart. For example
+ a remote branch `tmp` will be removed if a local branch with the same
+ name doesn't exist any more. This also respects refspecs, e.g.
+ `git push --prune remote refs/heads/*:refs/tmp/*` would
+ make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo`
+ doesn't exist.
+
--mirror::
Instead of naming each ref to push, specifies that all
refs under `refs/` (which includes but is not
@@ -162,6 +170,18 @@ useful if you write an alias or script around 'git push'.
is specified. This flag forces progress status even if the
standard error stream is not directed to a terminal.
+--recurse-submodules=check|on-demand::
+ Make sure all submodule commits used by the revisions to be
+ pushed are available on a remote tracking branch. If 'check' is
+ used git will verify that all submodule commits that changed in
+ the revisions to be pushed are available on at least one remote
+ of the submodule. If any commits are missing the push will be
+ aborted and exit with non-zero status. If 'on-demand' is used
+ all submodules that changed in the revisions to be pushed will
+ be pushed. If on-demand was not able to push all necessary
+ revisions it will also be aborted and exit with non-zero status.
+
+
include::urls-remotes.txt[]
OUTPUT
@@ -190,7 +210,7 @@ option is used.
flag::
A single character indicating the status of the ref:
(space);; for a successfully pushed fast-forward;
-`{plus}`;; for a successful forced update;
+`+`;; for a successful forced update;
`-`;; for a successfully deleted ref;
`*`;; for a successfully pushed new ref;
`!`;; for a ref that was rejected or failed to push; and
@@ -200,7 +220,7 @@ 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).
+ `<old>...<new>` for forced non-fast-forward updates).
+
For a failed update, more details are given:
+
@@ -327,12 +347,12 @@ a case where you do mean to lose history.
Examples
--------
-git push::
+`git push`::
Works like `git push <remote>`, where <remote> is the
current branch's remote (or `origin`, if no remote is
configured for the current branch).
-git push origin::
+`git push origin`::
Without additional configuration, works like
`git push origin :`.
+
@@ -344,45 +364,45 @@ use `git config remote.origin.push HEAD`. Any valid <refspec> (like
the ones in the examples below) can be configured as the default for
`git push origin`.
-git push origin :::
+`git push origin :`::
Push "matching" branches to `origin`. See
<refspec> in the <<OPTIONS,OPTIONS>> section above for a
description of "matching" branches.
-git push origin master::
+`git push origin master`::
Find a ref that matches `master` in the source repository
(most likely, it would find `refs/heads/master`), and update
the same ref (e.g. `refs/heads/master`) in `origin` repository
with it. If `master` did not exist remotely, it would be
created.
-git push origin HEAD::
+`git push origin HEAD`::
A handy way to push the current branch to the same name on the
remote.
-git push origin master:satellite/master dev:satellite/dev::
+`git push origin master:satellite/master dev:satellite/dev`::
Use the source ref that matches `master` (e.g. `refs/heads/master`)
to update the ref that matches `satellite/master` (most probably
`refs/remotes/satellite/master`) in the `origin` repository, then
do the same for `dev` and `satellite/dev`.
-git push origin HEAD:master::
+`git push origin HEAD:master`::
Push the current branch to the remote ref matching `master` in the
`origin` repository. This form is convenient to push the current
branch without thinking about its local name.
-git push origin master:refs/heads/experimental::
+`git push origin master:refs/heads/experimental`::
Create the branch `experimental` in the `origin` repository
by copying the current `master` branch. This form is only
needed to create a new branch or tag in the remote repository when
the local name and the remote name are different; otherwise,
the ref name on its own will work.
-git push origin :experimental::
+`git push origin :experimental`::
Find a ref that matches `experimental` in the `origin` repository
(e.g. `refs/heads/experimental`), and delete it.
-git push origin {plus}dev:master::
+`git push origin +dev:master`::
Update the origin repository's master branch with the dev branch,
allowing non-fast-forward updates. *This can leave unreferenced
commits dangling in the origin repository.* Consider the
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index a43e874..c4bde65 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -341,7 +341,7 @@ since you pulled from him:
----------------
$ git fetch git://.... linus
-$ LT=`cat .git/FETCH_HEAD`
+$ LT=`git rev-parse FETCH_HEAD`
----------------
Your work tree is still based on your HEAD ($JC), but you have
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 504945c..2d71e4b 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -8,9 +8,9 @@ git-rebase - Forward-port local commits to the updated upstream head
SYNOPSIS
--------
[verse]
-'git rebase' [-i | --interactive] [options] [--onto <newbase>]
+'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
[<upstream>] [<branch>]
-'git rebase' [-i | --interactive] [options] --onto <newbase>
+'git rebase' [-i | --interactive] [options] [--exec <cmd>] --onto <newbase>
--root [<branch>]
'git rebase' --continue | --skip | --abort
@@ -210,7 +210,7 @@ rebase.autosquash::
OPTIONS
-------
-<newbase>::
+--onto <newbase>::
Starting point at which to create the new commits. If the
--onto option is not specified, the starting point is
<upstream>. May be any valid commit, and not just an
@@ -238,6 +238,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
will be reset to where it was when the rebase operation was
started.
+--keep-empty::
+ Keep the commits that do not change anything from its
+ parents in the result.
+
--skip::
Restart the rebasing process by skipping the current patch.
@@ -267,7 +271,7 @@ which makes little sense.
-X <strategy-option>::
--strategy-option=<strategy-option>::
Pass the <strategy-option> through to the merge strategy.
- This implies `\--merge` and, if no strategy has been
+ This implies `--merge` and, if no strategy has been
specified, `-s recursive`. Note the reversal of 'ours' and
'theirs' as noted in above for the `-m` option.
@@ -340,6 +344,27 @@ This uses the `--interactive` machinery internally, but combining it
with the `--interactive` option explicitly is generally not a good
idea unless you know what you are doing (see BUGS below).
+-x <cmd>::
+--exec <cmd>::
+ Append "exec <cmd>" after each line creating a commit in the
+ final history. <cmd> will be interpreted as one or more shell
+ commands.
++
+This option can only be used with the `--interactive` option
+(see INTERACTIVE MODE below).
++
+You may execute several commands by either using one instance of `--exec`
+with several commands:
++
+ git rebase -i --exec "cmd1 && cmd2 && ..."
++
+or by giving more than one `--exec`:
++
+ git rebase -i --exec "cmd1" --exec "cmd2" --exec ...
++
+If `--autosquash` is used, "exec" lines will not be appended for
+the intermediate commits, and will only appear at the end of each
+squash/fixup series.
--root::
Rebase all commits reachable from <branch>, instead of
@@ -409,10 +434,13 @@ The interactive mode is meant for this type of workflow:
where point 2. consists of several instances of
-a. regular use
+a) regular use
+
1. finish something worthy of a commit
2. commit
-b. independent fixup
+
+b) independent fixup
+
1. realize that something does not work
2. fix that
3. commit it
@@ -514,6 +542,24 @@ in `$SHELL`, or the default shell if `$SHELL` is not set), so you can
use shell features (like "cd", ">", ";" ...). The command is run from
the root of the working tree.
+----------------------------------
+$ git rebase -i --exec "make test"
+----------------------------------
+
+This command lets you check that intermediate commits are compilable.
+The todo list becomes like that:
+
+--------------------
+pick 5928aea one
+exec make test
+pick 04d0fda two
+exec make test
+pick ba46169 three
+exec make test
+pick f4593f9 four
+exec make test
+--------------------
+
SPLITTING COMMITS
-----------------
@@ -608,8 +654,8 @@ Easy case: The changes are literally the same.::
Hard case: The changes are not the same.::
This happens if the 'subsystem' rebase had conflicts, or used
- `\--interactive` to omit, edit, squash, or fixup commits; or
- if the upstream used one of `commit \--amend`, `reset`, or
+ `--interactive` to omit, edit, squash, or fixup commits; or
+ if the upstream used one of `commit --amend`, `reset`, or
`filter-branch`.
@@ -645,7 +691,7 @@ correspond to the ones before the rebase.
NOTE: While an "easy case recovery" sometimes appears to be successful
even in the hard case, it may have unintended consequences. For
example, a commit that was removed via `git rebase
- \--interactive` will be **resurrected**!
+ --interactive` will be **resurrected**!
The idea is to manually tell 'git rebase' "where the old 'subsystem'
ended and your 'topic' began", that is, what the old merge-base
@@ -653,7 +699,7 @@ between them was. You will have to find a way to name the last commit
of the old 'subsystem', for example:
* With the 'subsystem' reflog: after 'git fetch', the old tip of
- 'subsystem' is at `subsystem@\{1}`. Subsequent fetches will
+ 'subsystem' is at `subsystem@{1}`. Subsequent fetches will
increase the number. (See linkgit:git-reflog[1].)
* Relative to the tip of 'topic': knowing that your 'topic' has three
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 459c085..b1f7dc6 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -150,7 +150,7 @@ if the repository is packed and is served via a dumb transport.
SEE ALSO
--------
-linkgit:git-send-pack[1]
+linkgit:git-send-pack[1], linkgit:gitnamespaces[7]
GIT
---
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index 976dc14..7fe2d22 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -39,13 +39,13 @@ 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
+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:gitrevisions[7] 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\}`").
+and specify the _exact_ entry (e.g. "`git reflog delete master@{2}`").
OPTIONS
diff --git a/Documentation/git-remote-fd.txt b/Documentation/git-remote-fd.txt
index 4aecd4d..f095d57 100644
--- a/Documentation/git-remote-fd.txt
+++ b/Documentation/git-remote-fd.txt
@@ -35,19 +35,19 @@ GIT_TRANSLOOP_DEBUG::
EXAMPLES
--------
-git fetch fd::17 master::
+`git fetch fd::17 master`::
Fetch master, using file descriptor #17 to communicate with
git-upload-pack.
-git fetch fd::17/foo master::
+`git fetch fd::17/foo master`::
Same as above.
-git push fd::7,8 master (as URL)::
+`git push fd::7,8 master (as URL)`::
Push master, using file descriptor #7 to read data from
git-receive-pack and file descriptor #8 to write data to
same service.
-git push fd::7,8/bar master::
+`git push fd::7,8/bar master`::
Same as above.
Documentation
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
index 930b403..f5836e4 100644
--- a/Documentation/git-remote-helpers.txt
+++ b/Documentation/git-remote-helpers.txt
@@ -24,22 +24,141 @@ output. Because a remote helper runs as an independent process from
git, there is no need to re-link git to add a new helper, nor any
need to link the helper with the implementation of git.
-Every helper must support the "capabilities" command, which git will
-use to determine what other commands the helper will accept. Other
-commands generally concern facilities like discovering and updating
-remote refs, transporting objects between the object database and
-the remote repository, and updating the local object store.
-
-Helpers supporting the 'fetch' capability can discover refs from the
-remote repository and transfer objects reachable from those refs to
-the local object store. Helpers supporting the 'push' capability can
-transfer local objects to the remote repository and update remote refs.
+Every helper must support the "capabilities" command, which git
+uses to determine what other commands the helper will accept. Those
+other commands can be used to discover and update remote refs,
+transport objects between the object database and the remote repository,
+and update the local object store.
Git comes with a "curl" family of remote helpers, that handle various
transport protocols, such as 'git-remote-http', 'git-remote-https',
'git-remote-ftp' and 'git-remote-ftps'. They implement the capabilities
'fetch', 'option', and 'push'.
+INPUT FORMAT
+------------
+
+Git sends the remote helper a list of commands on standard input, one
+per line. The first command is always the 'capabilities' command, in
+response to which the remote helper must print a list of the
+capabilities it supports (see below) followed by a blank line. The
+response to the capabilities command determines what commands Git uses
+in the remainder of the command stream.
+
+The command stream is terminated by a blank line. In some cases
+(indicated in the documentation of the relevant commands), this blank
+line is followed by a payload in some other protocol (e.g., the pack
+protocol), while in others it indicates the end of input.
+
+Capabilities
+~~~~~~~~~~~~
+
+Each remote helper is expected to support only a subset of commands.
+The operations a helper supports are declared to git in the response
+to the `capabilities` command (see COMMANDS, below).
+
+'option'::
+ For specifying settings like `verbosity` (how much output to
+ write to stderr) and `depth` (how much history is wanted in the
+ case of a shallow clone) that affect how other commands are
+ carried out.
+
+'connect'::
+ For fetching and pushing using git's native packfile protocol
+ that requires a bidirectional, full-duplex connection.
+
+'push'::
+ For listing remote refs and pushing specified objects from the
+ local object store to remote refs.
+
+'fetch'::
+ For listing remote refs and fetching the associated history to
+ the local object store.
+
+'import'::
+ For listing remote refs and fetching the associated history as
+ a fast-import stream.
+
+'refspec' <refspec>::
+ This modifies the 'import' capability, allowing the produced
+ fast-import stream to modify refs in a private namespace
+ instead of writing to refs/heads or refs/remotes directly.
+ It is recommended that all importers providing the 'import'
+ capability use this.
++
+A helper advertising the capability
+`refspec refs/heads/*:refs/svn/origin/branches/*`
+is saying that, when it is asked to `import refs/heads/topic`, the
+stream it outputs will update the `refs/svn/origin/branches/topic`
+ref.
++
+This capability can be advertised multiple times. The first
+applicable refspec takes precedence. The left-hand of refspecs
+advertised with this capability must cover all refs reported by
+the list command. If no 'refspec' capability is advertised,
+there is an implied `refspec *:*`.
+
+Capabilities for Pushing
+~~~~~~~~~~~~~~~~~~~~~~~~
+'connect'::
+ Can attempt to connect to 'git receive-pack' (for pushing),
+ 'git upload-pack', etc for communication using the
+ packfile protocol.
++
+Supported commands: 'connect'.
+
+'push'::
+ Can discover remote refs and push local commits and the
+ history leading up to them to new or existing remote refs.
++
+Supported commands: 'list for-push', 'push'.
+
+If a helper advertises both 'connect' and 'push', git will use
+'connect' if possible and fall back to 'push' if the helper requests
+so when connecting (see the 'connect' command under COMMANDS).
+
+Capabilities for Fetching
+~~~~~~~~~~~~~~~~~~~~~~~~~
+'connect'::
+ Can try to connect to 'git upload-pack' (for fetching),
+ 'git receive-pack', etc for communication using the
+ packfile protocol.
++
+Supported commands: 'connect'.
+
+'fetch'::
+ Can discover remote refs and transfer objects reachable from
+ them to the local object store.
++
+Supported commands: 'list', 'fetch'.
+
+'import'::
+ Can discover remote refs and output objects reachable from
+ them as a stream in fast-import format.
++
+Supported commands: 'list', 'import'.
+
+If a helper advertises 'connect', git will use it if possible and
+fall back to another capability if the helper requests so when
+connecting (see the 'connect' command under COMMANDS).
+When choosing between 'fetch' and 'import', git prefers 'fetch'.
+Other frontends may have some other order of preference.
+
+'refspec' <refspec>::
+ This modifies the 'import' capability.
++
+A helper advertising
+`refspec refs/heads/*:refs/svn/origin/branches/*`
+in its capabilities is saying that, when it handles
+`import refs/heads/topic`, the stream it outputs will update the
+`refs/svn/origin/branches/topic` ref.
++
+This capability can be advertised multiple times. The first
+applicable refspec takes precedence. The left-hand of refspecs
+advertised with this capability must cover all refs reported by
+the list command. If no 'refspec' capability is advertised,
+there is an implied `refspec *:*`.
+
INVOCATION
----------
@@ -48,6 +167,9 @@ arguments. The first argument specifies a remote repository as in git;
it is either the name of a configured remote or a URL. The second
argument specifies a URL; it is usually of the form
'<transport>://<address>', but any arbitrary string is possible.
+The 'GIT_DIR' environment variable is set up for the remote helper
+and can be used to determine where to store additional data or from
+which directory to invoke auxiliary git commands.
When git encounters a URL of the form '<transport>://<address>', where
'<transport>' is a protocol that it cannot handle natively, it
@@ -119,7 +241,22 @@ Supported if the helper has the "fetch" capability.
'push' +<src>:<dst>::
Pushes the given local <src> commit or branch to the
remote branch described by <dst>. A batch sequence of
- one or more push commands is terminated with a blank line.
+ one or more 'push' commands is terminated with a blank line
+ (if there is only one reference to push, a single 'push' command
+ is followed by a blank line). For example, the following would
+ be two batches of 'push', the first asking the remote-helper
+ to push the local ref 'master' to the remote ref 'master' and
+ the local 'HEAD' to the remote 'branch', and the second
+ asking to push ref 'foo' to ref 'bar' (forced update requested
+ by the '+').
++
+------------
+push refs/heads/master:refs/heads/master
+push HEAD:refs/heads/branch
+\n
+push +refs/heads/foo:refs/heads/bar
+\n
+------------
+
Zero or more protocol options may be entered after the last 'push'
command, before the batch's terminating blank line.
@@ -144,6 +281,11 @@ Supported if the helper has the "push" capability.
Especially useful for interoperability with a foreign versioning
system.
+
+Just like 'push', a batch sequence of one or more 'import' is
+terminated with a blank line. For each batch of 'import', the remote
+helper should produce a fast-import stream terminated by a 'done'
+command.
++
Supported if the helper has the "import" capability.
'connect' <service>::
@@ -168,26 +310,6 @@ completing a valid response for the current command.
Additional commands may be supported, as may be determined from
capabilities reported by the helper.
-CAPABILITIES
-------------
-
-'fetch'::
-'option'::
-'push'::
-'import'::
-'connect'::
- This helper supports the corresponding command with the same name.
-
-'refspec' 'spec'::
- When using the import command, expect the source ref to have
- been written to the destination ref. The earliest applicable
- refspec takes precedence. For example
- "refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}" means
- that, after an "import refs/heads/name", the script has written to
- refs/svn/origin/branches/name. If this capability is used at
- all, it must cover all refs reported by the list command; if
- it is not used, it is effectively "{asterisk}:{asterisk}"
-
REF LIST ATTRIBUTES
-------------------
@@ -240,6 +362,8 @@ SEE ALSO
--------
linkgit:git-remote[1]
+linkgit:git-remote-testgit[1]
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-testgit.txt b/Documentation/git-remote-testgit.txt
new file mode 100644
index 0000000..2a67d45
--- /dev/null
+++ b/Documentation/git-remote-testgit.txt
@@ -0,0 +1,30 @@
+git-remote-testgit(1)
+=====================
+
+NAME
+----
+git-remote-testgit - Example remote-helper
+
+
+SYNOPSIS
+--------
+[verse]
+git clone testgit::<source-repo> [<destination>]
+
+DESCRIPTION
+-----------
+
+This command is a simple remote-helper, that is used both as a
+testcase for the remote-helper functionality, and as an example to
+show remote-helper authors one possible implementation.
+
+The best way to learn more is to read the comments and source code in
+'git-remote-testgit.py'.
+
+SEE ALSO
+--------
+linkgit:git-remote-helpers[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 5a8c506..a308f4c 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -14,7 +14,7 @@ SYNOPSIS
'git remote rename' <old> <new>
'git remote rm' <name>
'git remote set-head' <name> (-a | -d | <branch>)
-'git remote set-branches' <name> [--add] <branch>...
+'git remote set-branches' [--add] <name> <branch>...
'git remote set-url' [--push] <name> <newurl> [<oldurl>]
'git remote set-url --add' [--push] <name> <newurl>
'git remote set-url --delete' [--push] <name> <url>
@@ -67,14 +67,14 @@ multiple branches without grabbing all branches.
With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set
up to point at remote's `<master>` branch. See also the set-head command.
+
-When a fetch mirror is created with `\--mirror=fetch`, the refs will not
+When a fetch mirror is created with `--mirror=fetch`, the refs will not
be stored in the 'refs/remotes/' namespace, but rather everything in
'refs/' on the remote will be directly mirrored into 'refs/' in the
local repository. This option only makes sense in bare repositories,
because a fetch would overwrite any local commits.
+
-When a push mirror is created with `\--mirror=push`, then `git push`
-will always behave as if `\--mirror` was passed.
+When a push mirror is created with `--mirror=push`, then `git push`
+will always behave as if `--mirror` was passed.
'rename'::
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index 40af321..4c1aff6 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -34,7 +34,7 @@ OPTIONS
Especially useful when packing a repository that is used
for private development. Use
with '-d'. This will clean up the objects that `git prune`
- leaves behind, but `git fsck --full` shows as
+ leaves behind, but `git fsck --full --dangling` shows as
dangling.
+
Note that users fetching over dumb protocols will have to fetch the
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index a6253ba..a62227f 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -8,7 +8,7 @@ git-rerere - Reuse recorded resolution of conflicted merges
SYNOPSIS
--------
[verse]
-'git rerere' ['clear'|'forget' <pathspec>|'diff'|'status'|'gc']
+'git rerere' ['clear'|'forget' <pathspec>|'diff'|'remaining'|'status'|'gc']
DESCRIPTION
-----------
@@ -37,30 +37,35 @@ its working state.
'clear'::
-This resets the metadata used by rerere if a merge resolution is to be
+Reset the metadata used by rerere if a merge resolution is to be
aborted. Calling 'git am [--skip|--abort]' or 'git rebase [--skip|--abort]'
will automatically invoke this command.
'forget' <pathspec>::
-This resets the conflict resolutions which rerere has recorded for the current
+Reset the conflict resolutions which rerere has recorded for the current
conflict in <pathspec>.
'diff'::
-This displays diffs for the current state of the resolution. It is
+Display diffs for the current state of the resolution. It is
useful for tracking what has changed while the user is resolving
conflicts. Additional arguments are passed directly to the system
'diff' command installed in PATH.
'status'::
-Like 'diff', but this only prints the filenames that will be tracked
-for resolutions.
+Print paths with conflicts whose merge resolution rerere will record.
+
+'remaining'::
+
+Print paths with conflicts that have not been autoresolved by rerere.
+This includes paths whose resolutions cannot be tracked by rerere,
+such as conflicting submodules.
'gc'::
-This prunes records of conflicted merges that
+Prune records of conflicted merges that
occurred a long time ago. By default, unresolved conflicts older
than 15 days and resolved conflicts older than 60
days are pruned. These defaults are controlled via the
@@ -96,15 +101,15 @@ One way to do it is to pull master into the topic branch:
The commits marked with `*` touch the same area in the same
file; you need to resolve the conflicts when creating the commit
-marked with `{plus}`. Then you can test the result to make sure your
+marked with `+`. Then you can test the result to make sure your
work-in-progress still works with what is in the latest master.
After this test merge, there are two ways to continue your work
on the topic. The easiest is to build on top of the test merge
-commit `{plus}`, and when your work in the topic branch is finally
+commit `+`, and when your work in the topic branch is finally
ready, pull the topic branch into master, and/or ask the
upstream to pull from you. By that time, however, the master or
-the upstream might have been advanced since the test merge `{plus}`,
+the upstream might have been advanced since the test merge `+`,
in which case the final commit graph would look like this:
------------
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index b2832fc..117e374 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -9,8 +9,8 @@ SYNOPSIS
--------
[verse]
'git reset' [-q] [<commit>] [--] <paths>...
-'git reset' [--patch|-p] [<commit>] [--] [<paths>...]
-'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
+'git reset' (--patch | -p) [<commit>] [--] [<paths>...]
+'git reset' (--soft | --mixed | --hard | --merge | --keep) [-q] [<commit>]
DESCRIPTION
-----------
@@ -34,16 +34,16 @@ Alternatively, using linkgit:git-checkout[1] and specifying a commit, you
can copy the contents of a path out of a commit to the index and to the
working tree in one go.
-'git reset' --patch|-p [<commit>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<commit>] [--] [<paths>...]::
Interactively select hunks in the difference between the index
and <commit> (defaults to HEAD). The chosen hunks are applied
in reverse to the index.
+
This means that `git reset -p` is the opposite of `git add -p`, i.e.
you can use it to selectively reset hunks. See the ``Interactive Mode''
-section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
-'git reset' [--<mode>] [<commit>]::
+'git reset' --<mode> [<commit>]::
This form resets the current branch head to <commit> and
possibly updates the index (resetting it to the tree of <commit>) and
the working tree depending on <mode>, which
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 42c9676..4cc3e95 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -113,15 +113,14 @@ OPTIONS
+
If a `pattern` is given, only refs matching the given shell glob are
shown. If the pattern does not contain a globbing character (`?`,
-`{asterisk}`, or `[`), it is turned into a prefix match by
-appending `/{asterisk}`.
+`*`, or `[`), it is turned into a prefix match by appending `/*`.
--glob=pattern::
Show all refs matching the shell glob pattern `pattern`. If
the pattern does not start with `refs/`, this is automatically
prepended. If the pattern does not contain a globbing
- character (`?`, `{asterisk}`, or `[`), it is turned into a prefix
- match by appending `/{asterisk}`.
+ character (`?`, `*`, or `[`), it is turned into a prefix
+ match by appending `/*`.
--show-toplevel::
Show the absolute path of the top-level directory.
@@ -138,7 +137,8 @@ appending `/{asterisk}`.
--git-dir::
Show `$GIT_DIR` if defined. Otherwise show the path to
- the .git directory, relative to the current directory.
+ the .git directory. The path shown, when relative, is
+ relative to the current working directory.
+
If `$GIT_DIR` is not defined and the current directory
is not detected to lie in a git repository or work tree
@@ -180,6 +180,10 @@ print a message to stderr and exit with nonzero status.
<args>...::
Flags and parameters to be parsed.
+--resolve-git-dir <path>::
+ Check if <path> is a valid git-dir or a git-file pointing to a valid
+ git-dir. If <path> is a valid git-dir the resolved path to git-dir will
+ be printed.
include::revisions.txt[]
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index 3d0a7d1..70152e8 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -9,6 +9,9 @@ SYNOPSIS
--------
[verse]
'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
+'git revert' --continue
+'git revert' --quit
+'git revert' --abort
DESCRIPTION
-----------
@@ -24,7 +27,7 @@ throw away all uncommitted changes in your working directory, you
should see linkgit:git-reset[1], particularly the '--hard' option. If
you want to extract specific files as they were in another commit, you
should see linkgit:git-checkout[1], specifically the `git checkout
-<commit> \-- <filename>` syntax. Take care with these alternatives as
+<commit> -- <filename>` syntax. Take care with these alternatives as
both will discard uncommitted changes in your working directory.
OPTIONS
@@ -91,14 +94,18 @@ effect to your index in a row.
Pass the merge strategy-specific option through to the
merge strategy. See linkgit:git-merge[1] for details.
+SEQUENCER SUBCOMMANDS
+---------------------
+include::sequencer.txt[]
+
EXAMPLES
--------
-git revert HEAD~3::
+`git revert HEAD~3`::
Revert the changes specified by the fourth last commit in HEAD
and create a new commit with the reverted changes.
-git revert -n master{tilde}5..master{tilde}2::
+`git revert -n master~5..master~2`::
Revert the changes done by commits from the fifth last commit
in master (included) to the third last commit in master
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index da0215d..5d31860 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -79,8 +79,7 @@ a file that you have not told git about does not remove that file.
File globbing matches across directory boundaries. Thus, given
two directories `d` and `d2`, there is a difference between
-using `git rm {apostrophe}d{asterisk}{apostrophe}` and
-`git rm {apostrophe}d/{asterisk}{apostrophe}`, as the former will
+using `git rm 'd*'` and `git rm 'd/*'`, as the former will
also remove all of directory `d2`.
REMOVING FILES THAT HAVE DISAPPEARED FROM THE FILESYSTEM
@@ -137,7 +136,7 @@ git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached
EXAMPLES
--------
-git rm Documentation/\*.txt::
+`git rm Documentation/\*.txt`::
Removes all `*.txt` files from the index that are under the
`Documentation` directory and any of its subdirectories.
+
@@ -145,7 +144,7 @@ Note that the asterisk `*` is quoted from the shell in this
example; this lets git, and not the shell, expand the pathnames
of files and subdirectories under the `Documentation/` directory.
-git rm -f git-*.sh::
+`git rm -f git-*.sh`::
Because this example lets the shell expand the asterisk
(i.e. you are listing the files explicitly), it
does not remove `subdir/git-foo.sh`.
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 327233c..3241170 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -198,6 +198,10 @@ must be used for each option.
if a username is not specified (with '--smtp-user' or 'sendemail.smtpuser'),
then authentication is not attempted.
+--smtp-debug=0|1::
+ Enable (1) or disable (0) debug output. If enabled, SMTP
+ commands and replies will be printed. Useful to debug TLS
+ connection and authentication problems.
Automating
~~~~~~~~~~
diff --git a/Documentation/git-sh-i18n--envsubst.txt b/Documentation/git-sh-i18n--envsubst.txt
index 5c3ec32..2ffaf93 100644
--- a/Documentation/git-sh-i18n--envsubst.txt
+++ b/Documentation/git-sh-i18n--envsubst.txt
@@ -25,7 +25,7 @@ plumbing scripts and/or are writing new ones.
'git sh-i18n{litdd}envsubst' is Git's stripped-down copy of the GNU
`envsubst(1)` program that comes with the GNU gettext package. It's
used internally by linkgit:git-sh-i18n[1] to interpolate the variables
-passed to the the `eval_gettext` function.
+passed to the `eval_gettext` function.
No promises are made about the interface, or that this
program won't disappear without warning in the next version
diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt
index a2f346c..5e5f1c8 100644
--- a/Documentation/git-sh-setup.txt
+++ b/Documentation/git-sh-setup.txt
@@ -68,6 +68,16 @@ require_work_tree_exists::
cd_to_toplevel, which is impossible to do if there is no
working tree.
+require_clean_work_tree <action> [<hint>]::
+ checks that the working tree and index associated with the
+ repository have no uncommitted changes to tracked files.
+ Otherwise it emits an error message of the form `Cannot
+ <action>: <reason>. <hint>`, and dies. Example:
++
+----------------
+require_clean_work_tree rebase "Please commit or stash them."
+----------------
+
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.
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index ff3755b..01d8417 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -47,7 +47,7 @@ OPTIONS
--format[=<format>]::
Instead of the commit subject, use some other information to
describe each commit. '<format>' can be any string accepted
- by the `--format` option of 'git log', such as '{asterisk} [%h] %s'.
+ by the `--format` option of 'git log', such as '* [%h] %s'.
(See the "PRETTY FORMATS" section of linkgit:git-log[1].)
Each pretty-printed commit will be rewrapped before it is shown.
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 3c45895..5dbcd47 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -44,7 +44,7 @@ OPTIONS
-d::
--dereference::
- Dereference tags into object IDs as well. They will be shown with "^{}"
+ Dereference tags into object IDs as well. They will be shown with "{caret}{}"
appended.
-s::
@@ -73,9 +73,9 @@ OPTIONS
--exclude-existing[=<pattern>]::
Make 'git show-ref' act as a filter that reads refs from stdin of the
- form "^(?:<anything>\s)?<refname>(?:{backslash}{caret}\{\})?$"
+ form "`^(?:<anything>\s)?<refname>(?:\^{})?$`"
and performs the following actions on each:
- (1) strip "^{}" at the end of line if any;
+ (1) strip "{caret}{}" at the end of line if any;
(2) ignore if pattern is provided and does not head-match refname;
(3) warn if refname is not a well-formed refname and skip;
(4) ignore if refname is a ref that exists in the local repository;
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
index 1f0e30b..ae4edcc 100644
--- a/Documentation/git-show.txt
+++ b/Documentation/git-show.txt
@@ -48,23 +48,23 @@ include::pretty-formats.txt[]
EXAMPLES
--------
-git show v1.0.0::
+`git show v1.0.0`::
Shows the tag `v1.0.0`, along with the object the tags
points at.
-git show v1.0.0^\{tree\}::
+`git show v1.0.0^{tree}`::
Shows the tree pointed to by the tag `v1.0.0`.
-git show -s --format=%s v1.0.0^\{commit\}::
+`git show -s --format=%s v1.0.0^{commit}`::
Shows the subject of the commit pointed to by the
tag `v1.0.0`.
-git show next~10:Documentation/README::
+`git show next~10:Documentation/README`::
Shows the contents of the file `Documentation/README` as
they were current in the 10th last commit of the branch
`next`.
-git show master:Makefile master:t/Makefile::
+`git show master:Makefile master:t/Makefile`::
Concatenates the contents of said Makefiles in the head
of the branch `master`.
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 15f051f..0aa4e20 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -13,7 +13,8 @@ SYNOPSIS
'git stash' drop [-q|--quiet] [<stash>]
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
'git stash' branch <branchname> [<stash>]
-'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
+'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+ [-u|--include-untracked] [-a|--all] [<message>]]
'git stash' clear
'git stash' create
@@ -35,14 +36,14 @@ you create one.
The latest stash you created is stored in `refs/stash`; older
stashes are found in the reflog of this reference and can be named using
-the usual reflog syntax (e.g. `stash@\{0}` is the most recently
-created stash, `stash@\{1}` is the one before it, `stash@\{2.hours.ago}`
+the usual reflog syntax (e.g. `stash@{0}` is the most recently
+created stash, `stash@{1}` is the one before it, `stash@{2.hours.ago}`
is also possible).
OPTIONS
-------
-save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
+save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
Save your local modifications to a new 'stash', and run `git reset
--hard` to revert them. The <message> part is optional and gives
@@ -54,13 +55,18 @@ save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
If the `--keep-index` option is used, all changes already added to the
index are left intact.
+
+If the `--include-untracked` option is used, all untracked files are also
+stashed and then cleaned up with `git clean`, leaving the working directory
+in a very clean state. If the `--all` option is used instead then the
+ignored files are stashed and cleaned in addition to the untracked files.
++
With `--patch`, you can interactively select hunks from the diff
between HEAD and the working tree to be stashed. The stash entry is
constructed such that its index state is the same as the index state
of your repository, and its worktree contains only the changes you
selected interactively. The selected changes are then rolled back
from your worktree. See the ``Interactive Mode'' section of
-linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+linkgit:git-add[1] to learn how to operate the `--patch` mode.
+
The `--patch` option implies `--keep-index`. You can use
`--no-keep-index` to override this.
@@ -68,7 +74,7 @@ The `--patch` option implies `--keep-index`. You can use
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
+ with its name (e.g. `stash@{0}` is the latest stash, `stash@{1}` is
the one before, etc.), the name of the branch that was current when the
stash was made, and a short description of the commit the stash was
based on.
@@ -87,7 +93,7 @@ show [<stash>]::
stashed state and its original parent. When no `<stash>` is given,
shows the latest one. By default, the command shows the diffstat, but
it will accept any format known to 'git diff' (e.g., `git stash show
- -p stash@\{1}` to view the second most recent stash in patch form).
+ -p stash@{1}` to view the second most recent stash in patch form).
pop [--index] [-q|--quiet] [<stash>]::
@@ -105,8 +111,8 @@ tree's changes, but also the index's ones. However, this can fail, when you
have conflicts (which are stored in the index, where you therefore can no
longer apply the changes as they were originally).
+
-When no `<stash>` is given, `stash@\{0}` is assumed, otherwise `<stash>` must
-be a reference of the form `stash@\{<revision>}`.
+When no `<stash>` is given, `stash@{0}` is assumed, otherwise `<stash>` must
+be a reference of the form `stash@{<revision>}`.
apply [--index] [-q|--quiet] [<stash>]::
@@ -137,9 +143,9 @@ clear::
drop [-q|--quiet] [<stash>]::
Remove a single stashed state from the stash list. When no `<stash>`
- is given, it removes the latest one. i.e. `stash@\{0}`, otherwise
+ is given, it removes the latest one. i.e. `stash@{0}`, otherwise
`<stash>` must a valid stash log reference of the form
- `stash@\{<revision>}`.
+ `stash@{<revision>}`.
create::
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index 3d51717..67e5f53 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -77,6 +77,13 @@ configuration variable documented in linkgit:git-config[1].
Terminate entries with NUL, instead of LF. This implies
the `--porcelain` output format if no other format is given.
+--column[=<options>]::
+--no-column::
+ Display untracked files in columns. See configuration variable
+ column.status for option syntax.`--column` and `--no-column`
+ without options are equivalent to 'always' and 'never'
+ respectively.
+
OUTPUT
------
@@ -98,12 +105,12 @@ In the short-format, the status of each path is shown as
XY PATH1 -> PATH2
-where `PATH1` is the path in the `HEAD`, and the ` \-> PATH2` part is
+where `PATH1` is the path in the `HEAD`, and the " `-> PATH2`" part is
shown only when `PATH1` corresponds to a different path in the
index/worktree (i.e. the file is renamed). The 'XY' is a two-letter
status code.
-The fields (including the `\->`) are separated from each other by a
+The fields (including the `->`) are separated from each other by a
single space. If a filename contains whitespace or other nonprintable
characters, that field will be quoted in the manner of a C string
literal: surrounded by ASCII double quote (34) characters, and with
@@ -177,7 +184,7 @@ order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL
and the terminating newline (but a space still separates the status
field from the first filename). Third, filenames containing special
characters are not specially formatted; no quoting or
-backslash-escaping is performed. Fourth, there is no branch line.
+backslash-escaping is performed.
CONFIGURATION
-------------
diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt
index b78f031..a80d946 100644
--- a/Documentation/git-stripspace.txt
+++ b/Documentation/git-stripspace.txt
@@ -3,26 +3,83 @@ git-stripspace(1)
NAME
----
-git-stripspace - Filter out empty lines
+git-stripspace - Remove unnecessary whitespace
SYNOPSIS
--------
[verse]
-'git stripspace' [-s | --strip-comments] < <stream>
+'git stripspace' [-s | --strip-comments] < input
DESCRIPTION
-----------
-Remove multiple empty lines, and empty lines at beginning and end.
+
+Clean the input in the manner used by 'git' for text such as commit
+messages, notes, tags and branch descriptions.
+
+With no arguments, this will:
+
+- remove trailing whitespace from all lines
+- collapse multiple consecutive empty lines into one empty line
+- remove empty lines from the beginning and end of the input
+- add a missing '\n' to the last line if necessary.
+
+In the case where the input consists entirely of whitespace characters, no
+output will be produced.
+
+*NOTE*: This is intended for cleaning metadata, prefer the `--whitespace=fix`
+mode of linkgit:git-apply[1] for correcting whitespace of patches or files in
+the repository.
OPTIONS
-------
-s::
--strip-comments::
- In addition to empty lines, also strip lines starting with '#'.
+ Skip and remove all lines starting with '#'.
+
+EXAMPLES
+--------
+
+Given the following noisy input with '$' indicating the end of a line:
-<stream>::
- Byte stream to act on.
+--------
+|A brief introduction $
+| $
+|$
+|A new paragraph$
+|# with a commented-out line $
+|explaining lots of stuff.$
+|$
+|# An old paragraph, also commented-out. $
+| $
+|The end.$
+| $
+---------
+
+Use 'git stripspace' with no arguments to obtain:
+
+--------
+|A brief introduction$
+|$
+|A new paragraph$
+|# with a commented-out line$
+|explaining lots of stuff.$
+|$
+|# An old paragraph, also commented-out.$
+|$
+|The end.$
+---------
+
+Use 'git stripspace --strip-comments' to obtain:
+
+--------
+|A brief introduction$
+|$
+|A new paragraph$
+|explaining lots of stuff.$
+|$
+|The end.$
+---------
GIT
---
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index 0ec8574..fbbbcb2 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -15,7 +15,8 @@ SYNOPSIS
'git submodule' [--quiet] init [--] [<path>...]
'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
[--reference <repository>] [--merge] [--recursive] [--] [<path>...]
-'git submodule' [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
+'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
+ [commit] [--] [<path>...]
'git submodule' [--quiet] foreach [--recursive] <command>
'git submodule' [--quiet] sync [--] [<path>...]
@@ -42,9 +43,9 @@ if you choose to go that route.
Submodules are composed from a so-called `gitlink` tree entry
in the main repository that refers to a particular commit object
within the inner repository that is completely separate.
-A record in the `.gitmodules` file at the root of the source
-tree assigns a logical name to the submodule and describes
-the default URL the submodule shall be cloned from.
+A record in the `.gitmodules` (see linkgit:gitmodules[5]) file at the
+root of the source tree assigns a logical name to the submodule and
+describes the default URL the submodule shall be cloned from.
The logical name can be used for overriding this URL within your
local repository configuration (see 'submodule init').
@@ -78,7 +79,12 @@ to exist in the superproject. If <path> is not given, the
<repository> is the URL of the new submodule's origin repository.
This may be either an absolute URL, or (if it begins with ./
or ../), the location relative to the superproject's origin
-repository. If the superproject doesn't have an origin configured
+repository (Please note that to specify a repository 'foo.git'
+which is located right next to a superproject 'bar.git', you'll
+have to use '../foo.git' instead of './foo.git' - as one might expect
+when following the rules for relative URLs - because the evaluation
+of relative URLs in Git is identical to that of relative directories).
+If the superproject doesn't have an origin configured
the superproject is its own authoritative upstream and the current
working directory is used instead.
+
@@ -108,12 +114,19 @@ status::
repository and `U` if the submodule has merge conflicts.
This command is the default command for 'git submodule'.
+
-If '--recursive' is specified, this command will recurse into nested
+If `--recursive` is specified, this command will recurse into nested
submodules, and show their status as well.
++
+If you are only interested in changes of the currently initialized
+submodules with respect to the commit recorded in the index or the HEAD,
+linkgit:git-status[1] and linkgit:git-diff[1] will provide that information
+too (and can also report changes to a submodule's work tree).
init::
Initialize the submodules, i.e. register each submodule name
and url found in .gitmodules into .git/config.
+ It will also copy the value of `submodule.$name.update` into
+ .git/config.
The key used in .git/config is `submodule.$name.url`.
This command does not alter existing information in .git/config.
You can then customize the submodule clone URLs in .git/config
@@ -125,26 +138,30 @@ init::
update::
Update the registered submodules, i.e. clone missing submodules and
checkout the commit specified in the index of the containing repository.
- This will make the submodules HEAD be detached unless '--rebase' or
- '--merge' is specified or the key `submodule.$name.update` is set to
- `rebase` or `merge`.
+ This will make the submodules HEAD be detached unless `--rebase` or
+ `--merge` is specified or the key `submodule.$name.update` is set to
+ `rebase`, `merge` or `none`. `none` can be overridden by specifying
+ `--checkout`.
+
If the submodule is not yet initialized, and you just want to use the
setting as stored in .gitmodules, you can automatically initialize the
-submodule with the --init option.
+submodule with the `--init` option.
+
-If '--recursive' is specified, this command will recurse into the
+If `--recursive` is specified, this command will recurse into the
registered submodules, and update any nested submodules within.
summary::
Show commit summary between the given commit (defaults to HEAD) and
working tree/index. For a submodule in question, a series of commits
in the submodule between the given super project commit and the
- index or working tree (switched by --cached) are shown. If the option
- --files is given, show the series of commits in the submodule between
+ index or working tree (switched by `--cached`) are shown. If the option
+ `--files` is given, show the series of commits in the submodule between
the index of the super project and the working tree of the submodule
- (this option doesn't allow to use the --cached option or to provide an
+ (this option doesn't allow to use the `--cached` option or to provide an
explicit commit).
++
+Using the `--submodule=log` option with linkgit:git-diff[1] will provide that
+information too.
foreach::
Evaluates an arbitrary shell command in each checked out submodule.
@@ -155,9 +172,9 @@ foreach::
superproject, $sha1 is the commit as recorded in the superproject,
and $toplevel is the absolute path to the top-level of the superproject.
Any submodules defined in the superproject but not checked out are
- ignored by this command. Unless given --quiet, foreach prints the name
+ ignored by this command. Unless given `--quiet`, foreach prints the name
of each submodule before evaluating the command.
- If --recursive is given, submodules are traversed recursively (i.e.
+ If `--recursive` is given, submodules are traversed recursively (i.e.
the given shell command is evaluated in nested submodules as well).
A non-zero return from the command in any submodule causes
the processing to terminate. This can be overridden by adding '|| :'
@@ -170,7 +187,7 @@ commit for each submodule.
sync::
Synchronizes submodules' remote URL configuration setting
to the value specified in .gitmodules. It will only affect those
- submodules which already have an url entry in .git/config (that is the
+ submodules which already have a URL entry in .git/config (that is the
case when they are initialized or freshly added). This is useful when
submodule URLs change upstream and you need to update your local
repositories accordingly.
@@ -237,13 +254,18 @@ OPTIONS
If the key `submodule.$name.update` is set to `rebase`, this option is
implicit.
+--init::
+ This option is only valid for the update command.
+ Initialize all submodules for which "git submodule init" has not been
+ called so far before updating.
+
--reference <repository>::
This option is only valid for add and update commands. These
commands sometimes need to clone a remote repository. In this case,
this option will be passed to the linkgit:git-clone[1] command.
+
*NOTE*: Do *not* use this option unless you have read the note
-for linkgit:git-clone[1]'s --reference and --shared options carefully.
+for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully.
--recursive::
This option is only valid for foreach, update and status commands.
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index ed5eca1..cfe8d2b 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -157,6 +157,17 @@ Skip "branches" and "tags" of first level directories;;
affecting the working tree; and the 'rebase' command will be
able to update the working tree with the latest changes.
+--preserve-empty-dirs;;
+ Create a placeholder file in the local Git repository for each
+ empty directory fetched from Subversion. This includes directories
+ that become empty by removing all entries in the Subversion
+ repository (but not the directory itself). The placeholder files
+ are also tracked and removed when no longer necessary.
+
+--placeholder-filename=<filename>;;
+ Set the name of placeholder files created by --preserve-empty-dirs.
+ Default: ".gitignore"
+
'rebase'::
This fetches revisions from the SVN parent of the current HEAD
and rebases the current (uncommitted to SVN) work against it.
@@ -178,18 +189,16 @@ and have no uncommitted changes.
last fetched commit from the upstream SVN.
'dcommit'::
- Commit each diff from a specified head directly to the SVN
+ Commit each diff from the current branch directly to the SVN
repository, and then rebase or reset (depending on whether or
not there is a diff between SVN and head). This will create
a revision in SVN for each commit in git.
- It is recommended that you run 'git svn' fetch and rebase (not
- pull or merge) your commits against the latest changes in the
- SVN repository.
- An optional revision or branch argument may be specified, and
- causes 'git svn' to do all work on that revision/branch
- instead of HEAD.
- This is advantageous over 'set-tree' (below) because it produces
- cleaner, more linear history.
++
+When an optional git branch name (or a git commit object name)
+is specified as an argument, the subcommand works on the specified
+branch, not on the current branch.
++
+Use of 'dcommit' is preferred to 'set-tree' (below).
+
--no-rebase;;
After committing, do not rebase or reset.
@@ -211,8 +220,25 @@ discouraged.
Add the given merge information during the dcommit
(e.g. `--mergeinfo="/branches/foo:1-10"`). All svn server versions can
store this information (as a property), and svn clients starting from
- version 1.5 can make use of it. 'git svn' currently does not use it
- and does not set it automatically.
+ version 1.5 can make use of it. To specify merge information from multiple
+ branches, use a single space character between the branches
+ (`--mergeinfo="/branches/foo:1-10 /branches/bar:3,5-6,8"`)
++
+[verse]
+config key: svn.pushmergeinfo
++
+This option will cause git-svn to attempt to automatically populate the
+svn:mergeinfo property in the SVN repository when possible. Currently, this can
+only be done when dcommitting non-fast-forward merges where all parents but the
+first have already been pushed into SVN.
+
+--interactive;;
+ Ask the user to confirm that a patch set should actually be sent to SVN.
+ For each patch, one may answer "yes" (accept this patch), "no" (discard this
+ patch), "all" (accept all patches), or "quit".
+ +
+ 'git svn dcommit' returns immediately if answer if "no" or "quit", without
+ commiting anything to SVN.
'branch'::
Create a branch in the SVN repository.
@@ -298,7 +324,7 @@ Any other arguments are passed directly to 'git log'
Show what revision and author last modified each line of a file. The
output of this mode is format-compatible with the output of
`svn blame' by default. Like the SVN blame command,
- local uncommitted changes in the working copy are ignored;
+ local uncommitted changes in the working tree are ignored;
the version of the file in the HEAD revision is annotated. Unknown
arguments are passed directly to 'git blame'.
+
@@ -544,6 +570,8 @@ config key: svn.repackflags
--merge::
-s<strategy>::
--strategy=<strategy>::
+-p::
+--preserve-merges::
These are only used with the 'dcommit' and 'rebase' commands.
+
Passed directly to 'git rebase' when using 'dcommit' if a
@@ -772,18 +800,19 @@ have each person clone that repository with 'git clone':
REBASE VS. PULL/MERGE
---------------------
-
-Originally, 'git svn' recommended that the 'remotes/git-svn' branch be
-pulled or merged from. This is because the author favored
+Prefer to use 'git svn rebase' or 'git rebase', rather than
+'git pull' or 'git merge' to synchronize unintegrated commits with a 'git svn'
+branch. Doing so will keep the history of unintegrated commits linear with
+respect to the upstream SVN repository and allow the use of the preferred
+'git svn dcommit' subcommand to push unintegrated commits back into SVN.
+
+Originally, 'git svn' recommended that developers pulled or merged from
+the 'git svn' branch. This was because the author favored
`git svn set-tree B` to commit a single head rather than the
-`git svn set-tree A..B` notation to commit multiple commits.
-
-If you use `git svn set-tree A..B` to commit several diffs and you do
-not have the latest remotes/git-svn merged into my-branch, you should
-use `git svn rebase` to update your work branch instead of `git pull` or
-`git merge`. `pull`/`merge` can cause non-linear history to be flattened
-when committing into SVN, which can lead to merge commits reversing
-previous commits in SVN.
+`git svn set-tree A..B` notation to commit multiple commits. Use of
+'git pull' or 'git merge' with `git svn set-tree A..B` will cause non-linear
+history to be flattened when committing into SVN and this can lead to merge
+commits unexpectedly reversing previous commits in SVN.
MERGE TRACKING
--------------
diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
index 75b1ae5..981d3a8 100644
--- a/Documentation/git-symbolic-ref.txt
+++ b/Documentation/git-symbolic-ref.txt
@@ -8,7 +8,8 @@ git-symbolic-ref - Read and modify symbolic refs
SYNOPSIS
--------
[verse]
-'git symbolic-ref' [-q] [-m <reason>] <name> [<ref>]
+'git symbolic-ref' [-m <reason>] <name> <ref>
+'git symbolic-ref' [-q] [--short] <name>
DESCRIPTION
-----------
@@ -33,6 +34,10 @@ OPTIONS
symbolic ref but a detached HEAD; instead exit with
non-zero status silently.
+--short::
+ When showing the value of <name> as a symbolic ref, try to shorten the
+ value, e.g. from `refs/heads/master` to `master`.
+
-m::
Update the reflog for <name> with <reason>. This is valid only
when creating or updating a symbolic ref.
@@ -43,12 +48,9 @@ In the past, `.git/HEAD` was a symbolic link pointing at
`refs/heads/master`. When we wanted to switch to another branch,
we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we wanted
to find out which branch we are on, we did `readlink .git/HEAD`.
-This was fine, and internally that is what still happens by
-default, but on platforms that do not have working symlinks,
-or that do not have the `readlink(1)` command, this was a bit
-cumbersome. On some platforms, `ln -sf` does not even work as
-advertised (horrors). Therefore symbolic links are now deprecated
-and symbolic refs are used by default.
+But symbolic links are not entirely portable, so they are now
+deprecated and symbolic refs (as described above) are used by
+default.
'git symbolic-ref' will exit with status 0 if the contents of the
symbolic ref were printed correctly, with status 1 if the requested
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index fb1c0ac..e36a7c3 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -12,7 +12,9 @@ SYNOPSIS
'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
<tagname> [<commit> | <object>]
'git tag' -d <tagname>...
-'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>...]
+'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
+ [--column[=<options>] | --no-column] [<pattern>...]
+ [<pattern>...]
'git tag' -v <tagname>...
DESCRIPTION
@@ -38,27 +40,34 @@ created (i.e. a lightweight tag).
A GnuPG signed tag object will be created when `-s` or `-u
<key-id>` is used. When `-u <key-id>` is not used, the
committer identity for the current user is used to find the
-GnuPG key for signing.
+GnuPG key for signing. The configuration variable `gpg.program`
+is used to specify custom GnuPG binary.
+
OPTIONS
-------
-a::
+--annotate::
Make an unsigned, annotated tag object
-s::
- Make a GPG-signed tag, using the default e-mail address's key
+--sign::
+ Make a GPG-signed tag, using the default e-mail address's key.
-u <key-id>::
- Make a GPG-signed tag, using the given key
+--local-user=<key-id>::
+ Make a GPG-signed tag, using the given key.
-f::
--force::
Replace an existing tag with the given name (instead of failing)
-d::
+--delete::
Delete existing tags with the given names.
-v::
+--verify::
Verify the gpg signature of the given tag names.
-n<num>::
@@ -69,16 +78,29 @@ OPTIONS
If the tag is not annotated, the commit message is displayed instead.
-l <pattern>::
+--list <pattern>::
List tags with names that match the given pattern (or all if no
pattern is given). Running "git tag" without arguments also
lists all tags. The pattern is a shell wildcard (i.e., matched
using fnmatch(3)). Multiple patterns may be given; if any of
them matches, the tag is shown.
+--column[=<options>]::
+--no-column::
+ Display tag listing in columns. See configuration variable
+ column.tag for option syntax.`--column` and `--no-column`
+ without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable when listing tags without annotation lines.
+
--contains <commit>::
Only list tags which contain the specified commit.
+--points-at <object>::
+ Only list tags of the given object.
+
-m <msg>::
+--message=<msg>::
Use the given tag message (instead of prompting).
If multiple `-m` options are given, their values are
concatenated as separate paragraphs.
@@ -86,11 +108,19 @@ OPTIONS
is given.
-F <file>::
+--file=<file>::
Take the tag message from the given file. Use '-' to
read the message from the standard input.
Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
is given.
+--cleanup=<mode>::
+ This option sets how the tag message is cleaned up.
+ The '<mode>' can be one of 'verbatim', 'whitespace' and 'strip'. The
+ 'strip' mode is default. The 'verbatim' mode does not change message at
+ all, 'whitespace' removes just leading/trailing whitespace lines and
+ 'strip' removes both whitespace and commentary.
+
<tagname>::
The name of the tag to create, delete, or describe.
The new tag name must pass all checks defined by
diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt
index 95b135d..f7362dc 100644
--- a/Documentation/git-tar-tree.txt
+++ b/Documentation/git-tar-tree.txt
@@ -53,26 +53,26 @@ tar.umask::
EXAMPLES
--------
-git tar-tree HEAD junk | (cd /var/tmp/ && tar xf -)::
+`git tar-tree HEAD junk | (cd /var/tmp/ && tar xf -)`::
Create a tar archive that contains the contents of the
latest commit on the current branch, and extracts it in
`/var/tmp/junk` directory.
-git tar-tree v1.4.0 git-1.4.0 | gzip >git-1.4.0.tar.gz::
+`git tar-tree v1.4.0 git-1.4.0 | gzip >git-1.4.0.tar.gz`::
Create a tarball for v1.4.0 release.
-git tar-tree v1.4.0{caret}\{tree\} git-1.4.0 | gzip >git-1.4.0.tar.gz::
+`git tar-tree v1.4.0^{tree} git-1.4.0 | gzip >git-1.4.0.tar.gz`::
Create a tarball for v1.4.0 release, but without a
global extended pax header.
-git tar-tree --remote=example.com:git.git v1.4.0 >git-1.4.0.tar::
+`git tar-tree --remote=example.com:git.git v1.4.0 >git-1.4.0.tar`::
Get a tarball v1.4.0 from example.com.
-git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar::
+`git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar`::
Put everything in the current head's Documentation/ directory
into 'git-1.4.0-docs.tar', with the prefix 'git-docs/'.
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index a3081f4..9d0b151 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -19,7 +19,7 @@ SYNOPSIS
[--ignore-submodules]
[--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
- [-z] [--stdin]
+ [-z] [--stdin] [--index-version <n>]
[--verbose]
[--] [<file>...]
@@ -143,6 +143,10 @@ you will need to handle the situation manually.
--verbose::
Report what is being added and removed from index.
+--index-version <n>::
+ Write the resulting index out in the named on-disk format version.
+ The current default version is 2.
+
-z::
Only meaningful with `--stdin` or `--index-info`; paths are
separated with NUL character instead of LF.
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
index a58e90c..71f1608 100644
--- a/Documentation/git-upload-pack.txt
+++ b/Documentation/git-upload-pack.txt
@@ -34,6 +34,10 @@ OPTIONS
<directory>::
The repository to sync from.
+SEE ALSO
+--------
+linkgit:gitnamespaces[7]
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
index 5317cc2..67edf58 100644
--- a/Documentation/git-var.txt
+++ b/Documentation/git-var.txt
@@ -43,22 +43,21 @@ GIT_EDITOR::
`$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe"
--nofork`. The order of preference is the `$GIT_EDITOR`
environment variable, then `core.editor` configuration, then
- `$VISUAL`, then `$EDITOR`, and then finally 'vi'.
+ `$VISUAL`, then `$EDITOR`, and then the default chosen at compile
+ time, which is usually 'vi'.
+ifdef::git-default-editor[]
+ The build you are using chose '{git-default-editor}' as the default.
+endif::git-default-editor[]
GIT_PAGER::
Text viewer for use by git commands (e.g., 'less'). The value
is meant to be interpreted by the shell. The order of preference
is the `$GIT_PAGER` environment variable, then `core.pager`
- configuration, then `$PAGER`, and then finally 'less'.
-
-Diagnostics
------------
-You don't exist. Go away!::
- The passwd(5) gecos field couldn't be read
-Your parents must have hated you!::
- The passwd(5) gecos field is longer than a giant static buffer.
-Your sysadmin must hate you!::
- The passwd(5) name field is longer than a giant static buffer.
+ configuration, then `$PAGER`, and then the default chosen at
+ compile time (usually 'less').
+ifdef::git-default-pager[]
+ The build you are using chose '{git-default-pager}' as the default.
+endif::git-default-pager[]
SEE ALSO
--------
diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt
index 99388bd..6c8f510 100644
--- a/Documentation/git-whatchanged.txt
+++ b/Documentation/git-whatchanged.txt
@@ -53,12 +53,12 @@ include::pretty-formats.txt[]
Examples
--------
-git whatchanged -p v2.6.12.. include/scsi drivers/scsi::
+`git whatchanged -p v2.6.12.. include/scsi drivers/scsi`::
Show as patches the commits since version 'v2.6.12' that changed
any file in the include/scsi or drivers/scsi subdirectories
-git whatchanged --since="2 weeks ago" \-- gitk::
+`git whatchanged --since="2 weeks ago" -- gitk`::
Show the changes during the last two weeks to the file 'gitk'.
The "--" is necessary to avoid confusion with the *branch* named
diff --git a/Documentation/git.txt b/Documentation/git.txt
index bcedfc1..d58fad7 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -9,11 +9,11 @@ git - the stupid content tracker
SYNOPSIS
--------
[verse]
-'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
- [-p|--paginate|--no-pager] [--no-replace-objects]
- [--bare] [--git-dir=<path>] [--work-tree=<path>]
- [-c <name>=<value>]
- [--help] <command> [<args>]
+'git' [--version] [--help] [-c <name>=<value>]
+ [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
+ [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
+ [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
+ <command> [<args>]
DESCRIPTION
-----------
@@ -44,9 +44,61 @@ unreleased) version of git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.7.6.5/git.html[documentation for release 1.7.6.5]
+* link:v1.7.11.1/git.html[documentation for release 1.7.11.1]
* release notes for
+ link:RelNotes/1.7.11.1.txt[1.7.11.1],
+ link:RelNotes/1.7.11.txt[1.7.11].
+
+* link:v1.7.10.5/git.html[documentation for release 1.7.10.5]
+
+* release notes for
+ link:RelNotes/1.7.10.5.txt[1.7.10.5],
+ link:RelNotes/1.7.10.4.txt[1.7.10.4],
+ link:RelNotes/1.7.10.3.txt[1.7.10.3],
+ link:RelNotes/1.7.10.2.txt[1.7.10.2],
+ link:RelNotes/1.7.10.1.txt[1.7.10.1],
+ link:RelNotes/1.7.10.txt[1.7.10].
+
+* link:v1.7.9.7/git.html[documentation for release 1.7.9.7]
+
+* release notes for
+ link:RelNotes/1.7.9.7.txt[1.7.9.7],
+ link:RelNotes/1.7.9.6.txt[1.7.9.6],
+ link:RelNotes/1.7.9.5.txt[1.7.9.5],
+ link:RelNotes/1.7.9.4.txt[1.7.9.4],
+ link:RelNotes/1.7.9.3.txt[1.7.9.3],
+ link:RelNotes/1.7.9.2.txt[1.7.9.2],
+ link:RelNotes/1.7.9.1.txt[1.7.9.1],
+ link:RelNotes/1.7.9.txt[1.7.9].
+
+* link:v1.7.8.6/git.html[documentation for release 1.7.8.6]
+
+* release notes for
+ link:RelNotes/1.7.8.6.txt[1.7.8.6],
+ link:RelNotes/1.7.8.5.txt[1.7.8.5],
+ link:RelNotes/1.7.8.4.txt[1.7.8.4],
+ link:RelNotes/1.7.8.3.txt[1.7.8.3],
+ link:RelNotes/1.7.8.2.txt[1.7.8.2],
+ link:RelNotes/1.7.8.1.txt[1.7.8.1],
+ link:RelNotes/1.7.8.txt[1.7.8].
+
+* link:v1.7.7.7/git.html[documentation for release 1.7.7.7]
+
+* release notes for
+ link:RelNotes/1.7.7.7.txt[1.7.7.7],
+ link:RelNotes/1.7.7.6.txt[1.7.7.6],
+ link:RelNotes/1.7.7.5.txt[1.7.7.5],
+ link:RelNotes/1.7.7.4.txt[1.7.7.4],
+ link:RelNotes/1.7.7.3.txt[1.7.7.3],
+ link:RelNotes/1.7.7.2.txt[1.7.7.2],
+ link:RelNotes/1.7.7.1.txt[1.7.7.1],
+ link:RelNotes/1.7.7.txt[1.7.7].
+
+* link:v1.7.6.6/git.html[documentation for release 1.7.6.6]
+
+* release notes for
+ link:RelNotes/1.7.6.6.txt[1.7.6.6],
link:RelNotes/1.7.6.5.txt[1.7.6.5],
link:RelNotes/1.7.6.4.txt[1.7.6.4],
link:RelNotes/1.7.6.3.txt[1.7.6.3],
@@ -335,6 +387,11 @@ help ...`.
variable (see core.worktree in linkgit:git-config[1] for a
more detailed discussion).
+--namespace=<path>::
+ Set the git namespace. See linkgit:gitnamespaces[7] for more
+ details. Equivalent to setting the `GIT_NAMESPACE` environment
+ variable.
+
--bare::
Treat the repository as a bare repository. If GIT_DIR
environment is not set, it is set to the current working
@@ -598,6 +655,10 @@ git so take care if using Cogito etc.
This can also be controlled by the '--work-tree' command line
option and the core.worktree configuration variable.
+'GIT_NAMESPACE'::
+ Set the git namespace; see linkgit:gitnamespaces[7] for details.
+ The '--namespace' command-line option also sets this value.
+
'GIT_CEILING_DIRECTORIES'::
This should be a colon-separated list of absolute paths.
If set, it is a list of directories that git should not chdir
@@ -672,6 +733,12 @@ other
a pager. See also the `core.pager` option in
linkgit:git-config[1].
+'GIT_EDITOR'::
+ This environment variable overrides `$EDITOR` and `$VISUAL`.
+ It is used by several git commands when, on interactive mode,
+ an editor is to be launched. See also linkgit:git-var[1]
+ and the `core.editor` option in linkgit:git-config[1].
+
'GIT_SSH'::
If this environment variable is set then 'git fetch'
and 'git push' will use this command instead
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 2bbe76b..e16f3e1 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -75,6 +75,8 @@ repositories (i.e., attributes of interest to all users) should go into
`.gitattributes` files. Attributes that should affect all repositories
for a single user should be placed in a file specified by the
`core.attributesfile` configuration option (see linkgit:git-config[1]).
+Its default value is $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME
+is either not set or empty, $HOME/.config/git/attributes is used instead.
Attributes for all users on a system should be placed in the
`$(prefix)/etc/gitattributes` file.
@@ -294,16 +296,27 @@ output is used to update the worktree file. Similarly, the
`clean` command is used to convert the contents of worktree file
upon checkin.
-A missing filter driver definition in the config is not an error
-but makes the filter a no-op passthru.
+One use of the content filtering is to massage the content into a shape
+that is more convenient for the platform, filesystem, and the user to use.
+For this mode of operation, 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 should still be usable.
-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 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
-should still be usable.
+Another use of the content filtering is to store the content that cannot
+be directly used in the repository (e.g. a UUID that refers to the true
+content stored outside git, or an encrypted content) and turn it into a
+usable form upon checkout (e.g. download the external content, or decrypt
+the encrypted content).
+
+These two filters behave differently, and by default, a filter is taken as
+the former, massaging the contents into more convenient shape. A missing
+filter driver definition in the config, or a filter driver that exits with
+a non-zero status, is not an error but makes the filter a no-op passthru.
+
+You can declare that a filter turns a content that by itself is unusable
+into a usable content by setting the filter.<driver>.required configuration
+variable to `true`.
For example, in .gitattributes, you would assign the `filter`
attribute for paths.
@@ -335,6 +348,16 @@ input that is already correctly indented. In this case, the lack of a
smudge filter means that the clean filter _must_ accept its own output
without modifying it.
+If a filter _must_ succeed in order to make the stored contents usable,
+you can declare that the filter is `required`, in the configuration:
+
+------------------------
+[filter "crypt"]
+ clean = openssl enc ...
+ smudge = openssl enc -d ...
+ required
+------------------------
+
Sequence "%f" on the filter command line is replaced with the name of
the file the filter is working on. A filter might use this in keyword
substitution. For example:
@@ -500,6 +523,8 @@ patterns are available:
- `java` suitable for source code in the Java language.
+- `matlab` suitable for source code in the MATLAB language.
+
- `objc` suitable for source code in the Objective-C language.
- `pascal` suitable for source code in the Pascal/Delphi language.
@@ -955,6 +980,9 @@ frotz unspecified
----------------------------------------------------------------
+SEE ALSO
+--------
+linkgit:git-check-attr[1].
GIT
---
diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt
index f734f97..ea17f7a 100644
--- a/Documentation/gitcli.txt
+++ b/Documentation/gitcli.txt
@@ -25,22 +25,22 @@ arguments. Here are the rules:
are paths.
* When an argument can be misunderstood as either a revision or a path,
- they can be disambiguated by placing `\--` between them.
- E.g. `git diff \-- HEAD` is, "I have a file called HEAD in my work
+ they can be disambiguated by placing `--` between them.
+ E.g. `git diff -- HEAD` is, "I have a file called HEAD in my work
tree. Please show changes between the version I staged in the index
and what I have in the work tree for that file". not "show difference
between the HEAD commit and the work tree as a whole". You can say
- `git diff HEAD \--` to ask for the latter.
+ `git diff HEAD --` to ask for the latter.
- * Without disambiguating `\--`, git makes a reasonable guess, but errors
+ * Without disambiguating `--`, git makes a reasonable guess, but errors
out and asking you to disambiguate when ambiguous. E.g. if you have a
file called HEAD in your work tree, `git diff HEAD` is ambiguous, and
- you have to say either `git diff HEAD \--` or `git diff \-- HEAD` to
+ you have to say either `git diff HEAD --` or `git diff -- HEAD` to
disambiguate.
When writing a script that is expected to handle random user-input, it is
a good practice to make it explicit which arguments are which by placing
-disambiguating `\--` at appropriate places.
+disambiguating `--` at appropriate places.
Here are the rules regarding the "flags" that you should follow when you are
scripting git:
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index c27d086..9d89336 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -151,8 +151,8 @@ to your working tree, you use the 'git update-index' program. That
program normally just takes a list of filenames you want to update, but
to avoid trivial mistakes, it refuses to add new entries to the index
(or remove existing ones) unless you explicitly tell it that you're
-adding a new entry with the `\--add` flag (or removing an entry with the
-`\--remove`) flag.
+adding a new entry with the `--add` flag (or removing an entry with the
+`--remove`) flag.
So to populate the index with the two files you just created, you can do
@@ -399,10 +399,10 @@ $ git diff HEAD
which ends up doing the above for you.
In other words, 'git diff-index' normally compares a tree against the
-working tree, but when given the `\--cached` flag, it is told to
+working tree, but when given the `--cached` flag, it is told to
instead compare against just the index cache contents, and ignore the
current working tree state entirely. Since we just wrote the index
-file to HEAD, doing `git diff-index \--cached -p HEAD` should thus return
+file to HEAD, doing `git diff-index --cached -p HEAD` should thus return
an empty set of differences, and that's exactly what it does.
[NOTE]
@@ -411,7 +411,7 @@ an empty set of differences, and that's exactly what it does.
comparisons, and saying that it compares a tree against the working
tree is thus not strictly accurate. In particular, the list of
files to compare (the "meta-data") *always* comes from the index file,
-regardless of whether the `\--cached` flag is used or not. The `\--cached`
+regardless of whether the `--cached` flag is used or not. The `--cached`
flag really only determines whether the file *contents* to be compared
come from the working tree or not.
@@ -433,7 +433,7 @@ update the index cache:
$ git update-index hello
------------------------------------------------
-(note how we didn't need the `\--add` flag this time, since git knew
+(note how we didn't need the `--add` flag this time, since git knew
about the file already).
Note what happens to the different 'git diff-{asterisk}' versions here.
@@ -560,7 +560,7 @@ short history.
When using the above two commands, the initial commit will be shown.
If this is a problem because it is huge, you can hide it by setting
the log.showroot configuration variable to false. Having this, you
-can still show it for each command just adding the `\--root` option,
+can still show it for each command just adding the `--root` option,
which is a flag for 'git diff-tree' accepted by both commands.
With that, you should now be having some inkling of what git does, and
@@ -881,7 +881,7 @@ helps you view what's going on:
$ gitk --all
----------------
-will show you graphically both of your branches (that's what the `\--all`
+will show you graphically both of your branches (that's what the `--all`
means: normally it will just show you your current `HEAD`) and their
histories. You can also see exactly how they came to be from a common
source.
@@ -935,7 +935,7 @@ which will very loudly warn you that you're now committing a merge
(which is correct, so never mind), and you can write a small merge
message about your adventures in 'git merge'-land.
-After you're done, start up `gitk \--all` to see graphically what the
+After you're done, start up `gitk --all` to see graphically what the
history looks like. Notice that `mybranch` still exists, and you can
switch to it, and continue to work with it if you want to. The
`mybranch` branch will not contain the merge, but next time you merge it
@@ -958,11 +958,11 @@ $ git show-branch --topo-order --more=1 master mybranch
The first two lines indicate that it is showing the two branches
and the first line of the commit log message from their
top-of-the-tree commits, you are currently on `master` branch
-(notice the asterisk `{asterisk}` character), and the first column for
+(notice the asterisk `*` character), and the first column for
the later output lines is used to show commits contained in the
`master` branch, and the second column for the `mybranch`
branch. Three commits are shown along with their log messages.
-All of them have non blank characters in the first column (`{asterisk}`
+All of them have non blank characters in the first column (`*`
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,
@@ -1002,9 +1002,9 @@ would be different)
----------------
Updating from ae3a2da... to a80b4aa....
Fast-forward (no commit created; -m option ignored)
- example | 1 +
- hello | 1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+ example | 1 +
+ hello | 1 +
+ 2 files changed, 2 insertions(+)
----------------
Because your branch did not contain anything more than what had
@@ -1013,7 +1013,7 @@ not actually do a merge. Instead, it just updated the top of
the tree of your branch to that of the `master` branch. This is
often called 'fast-forward' merge.
-You can run `gitk \--all` again to see how the commit ancestry
+You can run `gitk --all` again to see how the commit ancestry
looks like, or run 'show-branch', which tells you this.
------------------------------------------------
@@ -1257,7 +1257,7 @@ this 'collapsing' tends to trivially merge most of the paths
fairly quickly, leaving only a handful of real changes in non-zero
stages.
-To look at only non-zero stages, use `\--unmerged` flag:
+To look at only non-zero stages, use `--unmerged` flag:
------------
$ git ls-files --unmerged
@@ -1420,7 +1420,7 @@ packed, and stores the packed file in `.git/objects/pack`
directory.
[NOTE]
-You will see two files, `pack-{asterisk}.pack` and `pack-{asterisk}.idx`,
+You will see two files, `pack-*.pack` and `pack-*.idx`,
in `.git/objects/pack` directory. They are closely related to
each other, and if you ever copy them by hand to a different
repository for whatever reason, you should make sure you copy
diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt
new file mode 100644
index 0000000..7dfffc0
--- /dev/null
+++ b/Documentation/gitcredentials.txt
@@ -0,0 +1,183 @@
+gitcredentials(7)
+=================
+
+NAME
+----
+gitcredentials - providing usernames and passwords to git
+
+SYNOPSIS
+--------
+------------------
+git config credential.https://example.com.username myusername
+git config credential.helper "$helper $options"
+------------------
+
+DESCRIPTION
+-----------
+
+Git will sometimes need credentials from the user in order to perform
+operations; for example, it may need to ask for a username and password
+in order to access a remote repository over HTTP. This manual describes
+the mechanisms git uses to request these credentials, as well as some
+features to avoid inputting these credentials repeatedly.
+
+REQUESTING CREDENTIALS
+----------------------
+
+Without any credential helpers defined, git will try the following
+strategies to ask the user for usernames and passwords:
+
+1. If the `GIT_ASKPASS` environment variable is set, the program
+ specified by the variable is invoked. A suitable prompt is provided
+ to the program on the command line, and the user's input is read
+ from its standard output.
+
+2. Otherwise, if the `core.askpass` configuration variable is set, its
+ value is used as above.
+
+3. Otherwise, if the `SSH_ASKPASS` environment variable is set, its
+ value is used as above.
+
+4. Otherwise, the user is prompted on the terminal.
+
+AVOIDING REPETITION
+-------------------
+
+It can be cumbersome to input the same credentials over and over. Git
+provides two methods to reduce this annoyance:
+
+1. Static configuration of usernames for a given authentication context.
+
+2. Credential helpers to cache or store passwords, or to interact with
+ a system password wallet or keychain.
+
+The first is simple and appropriate if you do not have secure storage available
+for a password. It is generally configured by adding this to your config:
+
+---------------------------------------
+[credential "https://example.com"]
+ username = me
+---------------------------------------
+
+Credential helpers, on the other hand, are external programs from which git can
+request both usernames and passwords; they typically interface with secure
+storage provided by the OS or other programs.
+
+To use a helper, you must first select one to use. Git currently
+includes the following helpers:
+
+cache::
+
+ Cache credentials in memory for a short period of time. See
+ linkgit:git-credential-cache[1] for details.
+
+store::
+
+ Store credentials indefinitely on disk. See
+ linkgit:git-credential-store[1] for details.
+
+You may also have third-party helpers installed; search for
+`credential-*` in the output of `git help -a`, and consult the
+documentation of individual helpers. Once you have selected a helper,
+you can tell git to use it by putting its name into the
+credential.helper variable.
+
+1. Find a helper.
++
+-------------------------------------------
+$ git help -a | grep credential-
+credential-foo
+-------------------------------------------
+
+2. Read its description.
++
+-------------------------------------------
+$ git help credential-foo
+-------------------------------------------
+
+3. Tell git to use it.
++
+-------------------------------------------
+$ git config --global credential.helper foo
+-------------------------------------------
+
+If there are multiple instances of the `credential.helper` configuration
+variable, each helper will be tried in turn, and may provide a username,
+password, or nothing. Once git has acquired both a username and a
+password, no more helpers will be tried.
+
+
+CREDENTIAL CONTEXTS
+-------------------
+
+Git considers each credential to have a context defined by a URL. This context
+is used to look up context-specific configuration, and is passed to any
+helpers, which may use it as an index into secure storage.
+
+For instance, imagine we are accessing `https://example.com/foo.git`. When git
+looks into a config file to see if a section matches this context, it will
+consider the two a match if the context is a more-specific subset of the
+pattern in the config file. For example, if you have this in your config file:
+
+--------------------------------------
+[credential "https://example.com"]
+ username = foo
+--------------------------------------
+
+then we will match: both protocols are the same, both hosts are the same, and
+the "pattern" URL does not care about the path component at all. However, this
+context would not match:
+
+--------------------------------------
+[credential "https://kernel.org"]
+ username = foo
+--------------------------------------
+
+because the hostnames differ. Nor would it match `foo.example.com`; git
+compares hostnames exactly, without considering whether two hosts are part of
+the same domain. Likewise, a config entry for `http://example.com` would not
+match: git compares the protocols exactly.
+
+
+CONFIGURATION OPTIONS
+---------------------
+
+Options for a credential context can be configured either in
+`credential.*` (which applies to all credentials), or
+`credential.<url>.*`, where <url> matches the context as described
+above.
+
+The following options are available in either location:
+
+helper::
+
+ The name of an external credential helper, and any associated options.
+ If the helper name is not an absolute path, then the string `git
+ credential-` is prepended. The resulting string is executed by the
+ shell (so, for example, setting this to `foo --option=bar` will execute
+ `git credential-foo --option=bar` via the shell. See the manual of
+ specific helpers for examples of their use.
+
+username::
+
+ A default username, if one is not provided in the URL.
+
+useHttpPath::
+
+ By default, git does not consider the "path" component of an http URL
+ to be worth matching via external helpers. This means that a credential
+ stored for `https://example.com/foo.git` will also be used for
+ `https://example.com/bar.git`. If you do want to distinguish these
+ cases, set this option to `true`.
+
+
+CUSTOM HELPERS
+--------------
+
+You can write your own custom helpers to interface with any system in
+which you keep credentials. See the documentation for git's
+link:technical/api-credentials.html[credentials API] for details.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt
index 370624c..daf1782 100644
--- a/Documentation/gitdiffcore.txt
+++ b/Documentation/gitdiffcore.txt
@@ -168,11 +168,11 @@ a similarity score different from the default of 50% by giving a
number after the "-M" or "-C" option (e.g. "-M8" to tell it to use
8/10 = 80%).
-Note. When the "-C" option is used with `\--find-copies-harder`
+Note. When the "-C" option is used with `--find-copies-harder`
option, 'git diff-{asterisk}' commands feed unmodified filepairs to
diffcore mechanism as well as modified ones. This lets the copy
detector consider unmodified files as copy source candidates at
-the expense of making it slower. Without `\--find-copies-harder`,
+the expense of making it slower. Without `--find-copies-harder`,
'git diff-{asterisk}' commands can detect copies only if the file that was
copied happened to have been modified in the same changeset.
@@ -224,7 +224,7 @@ diffcore-pickaxe: For Detecting Addition/Deletion of Specified String
This transformation is used to find filepairs that represent
changes that touch a specified string, and is controlled by the
--S option and the `\--pickaxe-all` option to the 'git diff-{asterisk}'
+-S option and the `--pickaxe-all` option to the 'git diff-*'
commands.
When diffcore-pickaxe is in use, it checks if there are
@@ -233,9 +233,9 @@ different number of specified string. Such a filepair represents
"the string appeared in this changeset". It also checks for the
opposite case that loses the specified string.
-When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves
+When `--pickaxe-all` is not in effect, diffcore-pickaxe leaves
only such filepairs that touch the specified string in its
-output. When `\--pickaxe-all` is used, diffcore-pickaxe leaves all
+output. When `--pickaxe-all` is used, diffcore-pickaxe leaves all
filepairs intact if there is such a filepair, or makes the
output empty otherwise. The latter behaviour is designed to
make reviewing of the changes in the context of the whole
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 28edefa..b9003fe 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -73,7 +73,7 @@ pre-commit
~~~~~~~~~~
This hook is invoked by 'git commit', and can be bypassed
-with `\--no-verify` option. It takes no parameter, and is
+with `--no-verify` option. It takes no parameter, and is
invoked before obtaining the proposed commit log message and
making a commit. Exiting with non-zero status from this script
causes the 'git commit' to abort.
@@ -99,12 +99,12 @@ 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).
+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
+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.
@@ -115,7 +115,7 @@ commit-msg
~~~~~~~~~~
This hook is invoked by 'git commit', and can be bypassed
-with `\--no-verify` option. It takes a single parameter, the
+with `--no-verify` option. It takes a single parameter, the
name of the file that holds the proposed commit log message.
Exiting with non-zero status causes the 'git commit' to
abort.
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index 2e7328b..c1f692a 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -50,7 +50,9 @@ the repository but are specific to one user's workflow) should go into
the `$GIT_DIR/info/exclude` file. Patterns which a user wants git to
ignore in all situations (e.g., backup or temporary files generated by
the user's editor of choice) generally go into a file specified by
-`core.excludesfile` in the user's `~/.gitconfig`.
+`core.excludesfile` in the user's `~/.gitconfig`. Its default value is
+$XDG_CONFIG_HOME/git/ignore. If $XDG_CONFIG_HOME is either not set or empty,
+$HOME/.config/git/ignore is used instead.
The underlying git plumbing tools, such as
'git ls-files' and 'git read-tree', read
diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt
index 4040941..4effd78 100644
--- a/Documentation/gitmodules.txt
+++ b/Documentation/gitmodules.txt
@@ -28,7 +28,7 @@ submodule.<name>.path::
be unique within the .gitmodules file.
submodule.<name>.url::
- Defines an url from where the submodule repository can be cloned.
+ Defines a URL from which the submodule repository can be cloned.
This may be either an absolute URL ready to be passed to
linkgit:git-clone[1] or (if it begins with ./ or ../) a location
relative to the superproject's origin repository.
@@ -41,8 +41,11 @@ submodule.<name>.update::
the commit specified in the superproject. If 'merge', the commit
specified in the superproject will be merged into the current branch
in the submodule.
+ If 'none', the submodule with name `$name` will not be updated
+ by default.
+
This config option is overridden if 'git submodule update' is given
- the '--merge' or '--rebase' options.
+ the '--merge', '--rebase' or '--checkout' options.
submodule.<name>.fetchRecurseSubmodules::
This option can be used to control recursive fetching of this
@@ -84,7 +87,7 @@ Consider the following .gitmodules file:
This defines two submodules, `libfoo` and `libbar`. These are expected to
be checked out in the paths 'include/foo' and 'include/bar', and for both
-submodules an url is specified which can be used for cloning the submodules.
+submodules a URL is specified which can be used for cloning the submodules.
SEE ALSO
--------
diff --git a/Documentation/gitnamespaces.txt b/Documentation/gitnamespaces.txt
new file mode 100644
index 0000000..c6713cf
--- /dev/null
+++ b/Documentation/gitnamespaces.txt
@@ -0,0 +1,82 @@
+gitnamespaces(7)
+================
+
+NAME
+----
+gitnamespaces - Git namespaces
+
+SYNOPSIS
+--------
+[verse]
+GIT_NAMESPACE=<namespace> 'git upload-pack'
+GIT_NAMESPACE=<namespace> 'git receive-pack'
+
+
+DESCRIPTION
+-----------
+
+Git supports dividing the refs of a single repository into multiple
+namespaces, each of which has its own branches, tags, and HEAD. Git can
+expose each namespace as an independent repository to pull from and push
+to, while sharing the object store, and exposing all the refs to
+operations such as linkgit:git-gc[1].
+
+Storing multiple repositories as namespaces of a single repository
+avoids storing duplicate copies of the same objects, such as when
+storing multiple branches of the same source. The alternates mechanism
+provides similar support for avoiding duplicates, but alternates do not
+prevent duplication between new objects added to the repositories
+without ongoing maintenance, while namespaces do.
+
+To specify a namespace, set the `GIT_NAMESPACE` environment variable to
+the namespace. For each ref namespace, git stores the corresponding
+refs in a directory under `refs/namespaces/`. For example,
+`GIT_NAMESPACE=foo` will store refs under `refs/namespaces/foo/`. You
+can also specify namespaces via the `--namespace` option to
+linkgit:git[1].
+
+Note that namespaces which include a `/` will expand to a hierarchy of
+namespaces; for example, `GIT_NAMESPACE=foo/bar` will store refs under
+`refs/namespaces/foo/refs/namespaces/bar/`. This makes paths in
+`GIT_NAMESPACE` behave hierarchically, so that cloning with
+`GIT_NAMESPACE=foo/bar` produces the same result as cloning with
+`GIT_NAMESPACE=foo` and cloning from that repo with `GIT_NAMESPACE=bar`. It
+also avoids ambiguity with strange namespace paths such as `foo/refs/heads/`,
+which could otherwise generate directory/file conflicts within the `refs`
+directory.
+
+linkgit:git-upload-pack[1] and linkgit:git-receive-pack[1] rewrite the
+names of refs as specified by `GIT_NAMESPACE`. git-upload-pack and
+git-receive-pack will ignore all references outside the specified
+namespace.
+
+The smart HTTP server, linkgit:git-http-backend[1], will pass
+GIT_NAMESPACE through to the backend programs; see
+linkgit:git-http-backend[1] for sample configuration to expose
+repository namespaces as repositories.
+
+For a simple local test, you can use linkgit:git-remote-ext[1]:
+
+----------
+git clone ext::'git --namespace=foo %s /tmp/prefixed.git'
+----------
+
+SECURITY
+--------
+
+Anyone with access to any namespace within a repository can potentially
+access objects from any other namespace stored in the same repository.
+You can't directly say "give me object ABCD" if you don't have a ref to
+it, but you can do some other sneaky things like:
+
+. Claiming to push ABCD, at which point the server will optimize out the
+ need for you to actually send it. Now you have a ref to ABCD and can
+ fetch it (claiming not to have it, of course).
+
+. Requesting other refs, claiming that you have ABCD, at which point the
+ server may generate deltas against ABCD.
+
+None of this causes a problem if you only host public repositories, or
+if everyone who may read one namespace may also read everything in every
+other namespace (for instance, if everyone in an organization has read
+permission to every repository).
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index eb3d040..5c891f1 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -23,32 +23,25 @@ objects::
Object store associated with this repository. Usually
an object store is self sufficient (i.e. all the objects
that are referred to by an object found in it are also
- found in it), but there are couple of ways to violate
- it.
+ found in it), but there are a few ways to violate it.
+
-. You could populate the repository by running a commit walker
-without `-a` option. Depending on which options are given, you
-could have only commit objects without associated blobs and
-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 linkgit:git-clone[1].
-. You can be using `objects/info/alternates` mechanism, or
-`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanism to 'borrow'
+. You could have an incomplete but locally usable repository
+by creating a shallow clone. See linkgit:git-clone[1].
+. You could be using the `objects/info/alternates` or
+`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanisms to 'borrow'
objects from other object stores. A repository with this kind
of incomplete object store is not suitable to be published for
use with dumb transports but otherwise is OK as long as
-`objects/info/alternates` points at the right object stores
-it borrows from.
+`objects/info/alternates` points at the object stores it
+borrows from.
objects/[0-9a-f][0-9a-f]::
- Traditionally, each object is stored in its own file.
- They are split into 256 subdirectories using the first
- two letters from its object name to keep the number of
- directory entries `objects` directory itself needs to
- hold. Objects found here are often called 'unpacked'
- (or 'loose') objects.
+ A newly created object is stored in its own file.
+ The objects are splayed over 256 subdirectories using
+ the first two characters of the sha1 object name to
+ keep the number of directory entries in `objects`
+ itself to a manageable number. Objects found
+ here are often called 'unpacked' (or 'loose') objects.
objects/pack::
Packs (files that store many object in compressed form,
@@ -85,7 +78,7 @@ objects/info/http-alternates::
refs::
References are stored in subdirectories of this
- directory. The 'git prune' command knows to keep
+ directory. The 'git prune' command knows to preserve
objects reachable from refs found in this directory and
its subdirectories.
@@ -119,16 +112,17 @@ 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 linkgit:git-checkout[1] for
-details.
+is often called 'detached HEAD.' See linkgit:git-checkout[1]
+for details.
branches::
A slightly deprecated way to store shorthands to be used
- to specify URL to 'git fetch', 'git pull' and 'git push'
- commands is to store a file in `branches/<name>` and
- give 'name' to these commands in place of 'repository'
- argument.
+ to specify a URL to 'git fetch', 'git pull' and 'git push'.
+ A file can be stored as `branches/<name>` and then
+ 'name' can be given to these commands in place of
+ 'repository' argument. See the REMOTES section in
+ linkgit:git-fetch[1] for details. This mechanism is legacy
+ and not likely to be found in modern repositories.
hooks::
Hooks are customization scripts used by various git
@@ -173,9 +167,11 @@ info/exclude::
at it. See also: linkgit:gitignore[5].
remotes::
- Stores shorthands to be used to give URL and default
- refnames to interact with remote repository to
- 'git fetch', 'git pull' and 'git push' commands.
+ Stores shorthands for URL and default refnames for use
+ when interacting with remote repositories via 'git fetch',
+ 'git pull' and 'git push' commands. See the REMOTES section
+ in linkgit:git-fetch[1] for details. This mechanism is legacy
+ and not likely to be found in modern repositories.
logs::
Records of changes made to refs are stored in this
diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt
index f1e4422..e00a4d2 100644
--- a/Documentation/gittutorial-2.txt
+++ b/Documentation/gittutorial-2.txt
@@ -34,12 +34,12 @@ $ echo 'hello world' > file.txt
$ git add .
$ git commit -a -m "initial commit"
[master (root-commit) 54196cc] initial commit
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
create mode 100644 file.txt
$ echo 'hello world!' >file.txt
$ git commit -a -m "add emphasis"
[master c4d59f3] add emphasis
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
------------------------------------------------
What are the 7 digits of hex that git responded to the commit with?
diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt
new file mode 100644
index 0000000..4947455
--- /dev/null
+++ b/Documentation/gitweb.conf.txt
@@ -0,0 +1,896 @@
+gitweb.conf(5)
+==============
+
+NAME
+----
+gitweb.conf - Gitweb (git web interface) configuration file
+
+SYNOPSIS
+--------
+/etc/gitweb.conf, /etc/gitweb-common.conf, $GITWEBDIR/gitweb_config.perl
+
+DESCRIPTION
+-----------
+
+The gitweb CGI script for viewing Git repositories over the web uses a
+perl script fragment as its configuration file. You can set variables
+using "`our $variable = value`"; text from a "#" character until the
+end of a line is ignored. See *perlsyn*(1) for details.
+
+An example:
+
+ # gitweb configuration file for http://git.example.org
+ #
+ our $projectroot = "/srv/git"; # FHS recommendation
+ our $site_name = 'Example.org >> Repos';
+
+
+The configuration file is used to override the default settings that
+were built into gitweb at the time the 'gitweb.cgi' script was generated.
+
+While one could just alter the configuration settings in the gitweb
+CGI itself, those changes would be lost upon upgrade. Configuration
+settings might also be placed into a file in the same directory as the
+CGI script with the default name 'gitweb_config.perl' -- allowing
+one to have multiple gitweb instances with different configurations by
+the use of symlinks.
+
+Note that some configuration can be controlled on per-repository rather than
+gitweb-wide basis: see "Per-repository gitweb configuration" subsection on
+linkgit:gitweb[1] manpage.
+
+
+DISCUSSION
+----------
+Gitweb reads configuration data from the following sources in the
+following order:
+
+ * built-in values (some set during build stage),
+
+ * common system-wide configuration file (defaults to
+ '/etc/gitweb-common.conf'),
+
+ * either per-instance configuration file (defaults to 'gitweb_config.perl'
+ in the same directory as the installed gitweb), or if it does not exists
+ then fallback system-wide configuration file (defaults to '/etc/gitweb.conf').
+
+Values obtained in later configuration files override values obtained earlier
+in the above sequence.
+
+Locations of the common system-wide configuration file, the fallback
+system-wide configuration file and the per-instance configuration file
+are defined at compile time using build-time Makefile configuration
+variables, respectively `GITWEB_CONFIG_COMMON`, `GITWEB_CONFIG_SYSTEM`
+and `GITWEB_CONFIG`.
+
+You can also override locations of gitweb configuration files during
+runtime by setting the following environment variables:
+`GITWEB_CONFIG_COMMON`, `GITWEB_CONFIG_SYSTEM` and `GITWEB_CONFIG`
+to a non-empty value.
+
+
+The syntax of the configuration files is that of Perl, since these files are
+handled by sourcing them as fragments of Perl code (the language that
+gitweb itself is written in). Variables are typically set using the
+`our` qualifier (as in "`our $variable = <value>;`") to avoid syntax
+errors if a new version of gitweb no longer uses a variable and therefore
+stops declaring it.
+
+You can include other configuration file using read_config_file()
+subroutine. For example, one might want to put gitweb configuration
+related to access control for viewing repositories via Gitolite (one
+of git repository management tools) in a separate file, e.g. in
+'/etc/gitweb-gitolite.conf'. To include it, put
+
+--------------------------------------------------
+read_config_file("/etc/gitweb-gitolite.conf");
+--------------------------------------------------
+
+somewhere in gitweb configuration file used, e.g. in per-installation
+gitweb configuration file. Note that read_config_file() checks itself
+that the file it reads exists, and does nothing if it is not found.
+It also handles errors in included file.
+
+
+The default configuration with no configuration file at all may work
+perfectly well for some installations. Still, a configuration file is
+useful for customizing or tweaking the behavior of gitweb in many ways, and
+some optional features will not be present unless explicitly enabled using
+the configurable `%features` variable (see also "Configuring gitweb
+features" section below).
+
+
+CONFIGURATION VARIABLES
+-----------------------
+Some configuration variables have their default values (embedded in the CGI
+script) set during building gitweb -- if that is the case, this fact is put
+in their description. See gitweb's 'INSTALL' file for instructions on building
+and installing gitweb.
+
+
+Location of repositories
+~~~~~~~~~~~~~~~~~~~~~~~~
+The configuration variables described below control how gitweb finds
+git repositories, and how repositories are displayed and accessed.
+
+See also "Repositories" and later subsections in linkgit:gitweb[1] manpage.
+
+$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 has to be
+ set correctly for gitweb to find repositories.
++
+For example, if `$projectroot` is set to "/srv/git" by putting the following
+in gitweb config file:
++
+----------------------------------------------------------------------------
+our $projectroot = "/srv/git";
+----------------------------------------------------------------------------
++
+then
++
+------------------------------------------------
+http://git.example.com/gitweb.cgi?p=foo/bar.git
+------------------------------------------------
++
+and its path_info based equivalent
++
+------------------------------------------------
+http://git.example.com/gitweb.cgi/foo/bar.git
+------------------------------------------------
++
+will map to the path '/srv/git/foo/bar.git' on the filesystem.
+
+$projects_list::
+ Name of a plain text file listing projects, or a name of directory
+ to be scanned for projects.
++
+Project list files should list one project per line, with each line
+having the following format
++
+-----------------------------------------------------------------------------
+<URI-encoded filesystem path to repository> SP <URI-encoded repository owner>
+-----------------------------------------------------------------------------
++
+The default value of this variable is determined by the `GITWEB_LIST`
+makefile variable at installation time. If this variable is empty, gitweb
+will fall back to scanning the `$projectroot` directory for repositories.
+
+$project_maxdepth::
+ If `$projects_list` variable is unset, gitweb will recursively
+ scan filesystem for git repositories. The `$project_maxdepth`
+ is used to limit traversing depth, relative to `$projectroot`
+ (starting point); it means that directories which are further
+ from `$projectroot` than `$project_maxdepth` will be skipped.
++
+It is purely performance optimization, originally intended for MacOS X,
+where recursive directory traversal is slow. Gitweb follows symbolic
+links, but it detects cycles, ignoring any duplicate files and directories.
++
+The default value of this variable is determined by the build-time
+configuration variable `GITWEB_PROJECT_MAXDEPTH`, which defaults to
+2007.
+
+$export_ok::
+ Show repository only if this file exists (in repository). Only
+ effective if this variable evaluates to true. Can be set when
+ building gitweb by setting `GITWEB_EXPORT_OK`. This path is
+ relative to `GIT_DIR`. git-daemon[1] uses 'git-daemon-export-ok',
+ unless started with `--export-all`. By default this variable is
+ not set, which means that this feature is turned off.
+
+$export_auth_hook::
+ Function used to determine which repositories should be shown.
+ This subroutine should take one parameter, the full path to
+ a project, and if it returns true, that project will be included
+ in the projects list and can be accessed through gitweb as long
+ as it fulfills the other requirements described by $export_ok,
+ $projects_list, and $projects_maxdepth. Example:
++
+----------------------------------------------------------------------------
+our $export_auth_hook = sub { return -e "$_[0]/git-daemon-export-ok"; };
+----------------------------------------------------------------------------
++
+though the above might be done by using `$export_ok` instead
++
+----------------------------------------------------------------------------
+our $export_ok = "git-daemon-export-ok";
+----------------------------------------------------------------------------
++
+If not set (default), it means that this feature is disabled.
++
+See also more involved example in "Controlling access to git repositories"
+subsection on linkgit:gitweb[1] manpage.
+
+$strict_export::
+ Only allow viewing of repositories also shown on the overview page.
+ This for example makes `$gitweb_export_ok` file 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. Can be set during building gitweb via
+ `GITWEB_STRICT_EXPORT`. By default this variable is not set, which
+ means that you can directly access those repositories that are hidden
+ from projects list page (e.g. the are not listed in the $projects_list
+ file).
+
+
+Finding files
+~~~~~~~~~~~~~
+The following configuration variables tell gitweb where to find files.
+The values of these variables are paths on the filesystem.
+
+$GIT::
+ Core 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 installed
+ from a binary package, you should usually set this to "/usr/bin/git".
+ This can just be "git" if your web server has a sensible PATH; from
+ security point of view it is better to use absolute path to git binary.
+ If you have multiple git versions installed it can be used to choose
+ which one to use. Must be (correctly) set for gitweb to be able to
+ work.
+
+$mimetypes_file::
+ File to use for (filename extension based) guessing of MIME types before
+ trying '/etc/mime.types'. *NOTE* that this path, if relative, is taken
+ as relative to the current git repository, not to CGI script. If unset,
+ only '/etc/mime.types' is used (if present on filesystem). If no mimetypes
+ file is found, mimetype guessing based on extension of file is disabled.
+ Unset by default.
+
+$highlight_bin::
+ Path to the highlight executable to use (it must be the one from
+ http://www.andre-simon.de[] due to assumptions about parameters and output).
+ By default set to 'highlight'; set it to full path to highlight
+ executable if it is not installed on your web server's PATH.
+ Note that 'highlight' feature must be set for gitweb to actually
+ use syntax highlighting.
++
+*NOTE*: if you want to add support for new file type (supported by
+"highlight" but not used by gitweb), you need to modify `%highlight_ext`
+or `%highlight_basename`, depending on whether you detect type of file
+based on extension (for example "sh") or on its basename (for example
+"Makefile"). The keys of these hashes are extension and basename,
+respectively, and value for given key is name of syntax to be passed via
+`--syntax <syntax>` to highlighter.
++
+For example if repositories you are hosting use "phtml" extension for
+PHP files, and you want to have correct syntax-highlighting for those
+files, you can add the following to gitweb configuration:
++
+---------------------------------------------------------
+our %highlight_ext;
+$highlight_ext{'phtml'} = 'php';
+---------------------------------------------------------
+
+
+Links and their targets
+~~~~~~~~~~~~~~~~~~~~~~~
+The configuration variables described below configure some of gitweb links:
+their target and their look (text or image), and where to find page
+prerequisites (stylesheet, favicon, images, scripts). Usually they are left
+at their default values, with the possible exception of `@stylesheets`
+variable.
+
+@stylesheets::
+ List of URIs of stylesheets (relative to the base URI of a page). You
+ might specify more than one stylesheet, for example to use "gitweb.css"
+ as base with site specific modifications in a separate stylesheet
+ to make it easier to upgrade gitweb. For example, you can add
+ a `site` stylesheet by putting
++
+----------------------------------------------------------------------------
+push @stylesheets, "gitweb-site.css";
+----------------------------------------------------------------------------
++
+in the gitweb config file. Those values that are relative paths are
+relative to base URI of gitweb.
++
+This list should contain the URI of gitweb's standard stylesheet. The default
+URI of gitweb stylesheet can be set at build time using the `GITWEB_CSS`
+makefile variable. Its default value is 'static/gitweb.css'
+(or 'static/gitweb.min.css' if the `CSSMIN` variable is defined,
+i.e. if CSS minifier is used during build).
++
+*Note*: there is also a legacy `$stylesheet` configuration variable, which was
+used by older gitweb. If `$stylesheet` variable is defined, only CSS stylesheet
+given by this variable is used by gitweb.
+
+$logo::
+ Points to the location where you put 'git-logo.png' on your web
+ server, or to be more the generic URI of logo, 72x27 size). This image
+ is displayed in the top right corner of each gitweb page and used as
+ a logo for the Atom feed. Relative to the base URI of gitweb (as a path).
+ Can be adjusted when building gitweb using `GITWEB_LOGO` variable
+ By default set to 'static/git-logo.png'.
+
+$favicon::
+ Points to the location where you put 'git-favicon.png' on your web
+ server, or to be more the generic URI of favicon, which will be served
+ as "image/png" type. Web browsers that support favicons (website icons)
+ may display them in the browser's URL bar and next to the site name in
+ bookmarks. Relative to the base URI of gitweb. Can be adjusted at
+ build time using `GITWEB_FAVICON` variable.
+ By default set to 'static/git-favicon.png'.
+
+$javascript::
+ Points to the location where you put 'gitweb.js' on your web server,
+ or to be more generic the URI of JavaScript code used by gitweb.
+ Relative to the base URI of gitweb. Can be set at build time using
+ the `GITWEB_JS` build-time configuration variable.
++
+The default value is either 'static/gitweb.js', or 'static/gitweb.min.js' if
+the `JSMIN` build variable was defined, i.e. if JavaScript minifier was used
+at build time. *Note* that this single file is generated from multiple
+individual JavaScript "modules".
+
+$home_link::
+ Target of the home link on the top of all pages (the first part of view
+ "breadcrumbs"). By default it is set to the absolute URI of a current page
+ (to the value of `$my_uri` variable, or to "/" if `$my_uri` is undefined
+ or is an empty string).
+
+$home_link_str::
+ Label for the "home link" at the top of all pages, leading to `$home_link`
+ (usually the main gitweb page, which contains the projects list). It is
+ used as the first component of gitweb's "breadcrumb trail":
+ `<home link> / <project> / <action>`. Can be set at build time using
+ the `GITWEB_HOME_LINK_STR` variable. By default it is set to "projects",
+ as this link leads to the list of projects. Other popular choice it to
+ set it to the name of site.
+
+$logo_url::
+$logo_label::
+ URI and label (title) for the Git logo link (or your site logo,
+ if you chose to use different logo image). By default, these both
+ refer to git homepage, http://git-scm.com[]; in the past, they pointed
+ to git documentation at http://www.kernel.org[].
+
+
+Changing gitweb's look
+~~~~~~~~~~~~~~~~~~~~~~
+You can adjust how pages generated by gitweb look using the variables described
+below. You can change the site name, add common headers and footers for all
+pages, and add a description of this gitweb installation on its main page
+(which is the projects list page), etc.
+
+$site_name::
+ Name of your site or organization, to appear in page titles. Set it
+ to something descriptive for clearer bookmarks etc. If this variable
+ is not set or is, then gitweb uses the value of the `SERVER_NAME`
+ CGI environment variable, setting site name to "$SERVER_NAME Git",
+ or "Untitled Git" if this variable is not set (e.g. if running gitweb
+ as standalone script).
++
+Can be set using the `GITWEB_SITENAME` at build time. Unset by default.
+
+$site_html_head_string::
+ HTML snippet to be included in the <head> section of each page.
+ Can be set using `GITWEB_SITE_HTML_HEAD_STRING` at build time.
+ No default value.
+
+$site_header::
+ Name of a file with HTML to be included at the top of each page.
+ Relative to the directory containing the 'gitweb.cgi' script.
+ Can be set using `GITWEB_SITE_HEADER` at build time. No default
+ value.
+
+$site_footer::
+ Name of a file with HTML to be included at the bottom of each page.
+ Relative to the directory containing the 'gitweb.cgi' script.
+ Can be set using `GITWEB_SITE_FOOTER` at build time. No default
+ value.
+
+$home_text::
+ Name of a HTML file which, if it exists, is included on the
+ gitweb projects overview page ("projects_list" view). Relative to
+ the directory containing the gitweb.cgi script. Default value
+ can be adjusted during build time using `GITWEB_HOMETEXT` variable.
+ By default set to 'indextext.html'.
+
+$projects_list_description_width::
+ The width (in characters) of the "Description" column of the projects list.
+ Longer descriptions will be truncated (trying to cut at word boundary);
+ the full description is available in the 'title' attribute (usually shown on
+ mouseover). The default is 25, which might be too small if you
+ use long project descriptions.
+
+$default_projects_order::
+ Default value of ordering of projects on projects list page, which
+ means the ordering used if you don't explicitly sort projects list
+ (if there is no "o" CGI query parameter in the URL). Valid values
+ are "none" (unsorted), "project" (projects are by project name,
+ i.e. path to repository relative to `$projectroot`), "descr"
+ (project description), "owner", and "age" (by date of most current
+ commit).
++
+Default value is "project". Unknown value means unsorted.
+
+
+Changing gitweb's behavior
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+These configuration variables control _internal_ gitweb behavior.
+
+$default_blob_plain_mimetype::
+ Default mimetype for the blob_plain (raw) view, if mimetype checking
+ doesn't result in some other type; by default "text/plain".
+ Gitweb guesses mimetype of a file to display based on extension
+ of its filename, using `$mimetypes_file` (if set and file exists)
+ and '/etc/mime.types' files (see *mime.types*(5) manpage; only
+ filename extension rules are supported by gitweb).
+
+$default_text_plain_charset::
+ Default charset for text files. If this is not set, the web server
+ configuration will be used. Unset by default.
+
+$fallback_encoding::
+ Gitweb assumes this charset when a line contains non-UTF-8 characters.
+ The fallback decoding is used without error checking, so it can be even
+ "utf-8". The value must be a valid encoding; see the *Encoding::Supported*(3pm)
+ man page for a list. The default is "latin1", aka. "iso-8859-1".
+
+@diff_opts::
+ Rename detection options for git-diff and git-diff-tree. The default is
+ (\'-M'); set it to (\'-C') or (\'-C', \'-C') to also detect copies,
+ or set it to () i.e. empty list if you don't want to have renames
+ detection.
++
+*Note* that rename and especially copy detection can be quite
+CPU-intensive. Note also that non git tools can have problems with
+patches generated with options mentioned above, especially when they
+involve file copies (\'-C') or criss-cross renames (\'-B').
+
+
+Some optional features and policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Most of features are configured via `%feature` hash; however some of extra
+gitweb features can be turned on and configured using variables described
+below. This list beside configuration variables that control how gitweb
+looks does contain variables configuring administrative side of gitweb
+(e.g. cross-site scripting prevention; admittedly this as side effect
+affects how "summary" pages look like, or load limiting).
+
+@git_base_url_list::
+ List of git base URLs. These URLs are used to generate URLs
+ describing from where to fetch a project, which are shown on
+ project summary page. The full fetch URL is "`$git_base_url/$project`",
+ for each element of this list. You can set up multiple base URLs
+ (for example one for `git://` protocol, and one for `http://`
+ protocol).
++
+Note that per repository configuration can be set in '$GIT_DIR/cloneurl'
+file, or as values of multi-value `gitweb.url` configuration variable in
+project config. Per-repository configuration takes precedence over value
+composed from `@git_base_url_list` elements and project name.
++
+You can setup one single value (single entry/item in this list) at build
+time by setting the `GITWEB_BASE_URL` built-time configuration variable.
+By default it is set to (), i.e. an empty list. This means that gitweb
+would not try to create project URL (to fetch) from project name.
+
+$projects_list_group_categories::
+ Whether to enables the grouping of projects by category on the project
+ list page. The category of a project is determined by the
+ `$GIT_DIR/category` file or the `gitweb.category` variable in each
+ repository's configuration. Disabled by default (set to 0).
+
+$project_list_default_category::
+ Default category for projects for which none is specified. If this is
+ set to the empty string, such projects will remain uncategorized and
+ listed at the top, above categorized projects. Used only if project
+ categories are enabled, which means if `$projects_list_group_categories`
+ is true. By default set to "" (empty string).
+
+$prevent_xss::
+ If true, some gitweb features are disabled to prevent content in
+ repositories from launching cross-site scripting (XSS) attacks. Set this
+ to true if you don't trust the content of your repositories.
+ False by default (set to 0).
+
+$maxload::
+ Used to set the maximum load that we will still respond to gitweb queries.
+ If the server load exceeds this value then gitweb will return
+ "503 Service Unavailable" error. The server load is taken to be 0
+ if gitweb cannot determine its value. Currently it works only on Linux,
+ where it uses '/proc/loadavg'; the load there is the number of active
+ tasks on the system -- processes that are actually running -- averaged
+ over the last minute.
++
+Set `$maxload` to undefined value (`undef`) to turn this feature off.
+The default value is 300.
+
+$omit_age_column::
+ If true, omit the column with date of the most current commit on the
+ projects list page. It can save a bit of I/O and a fork per repository.
+
+$omit_owner::
+ If true prevents displaying information about repository owner.
+
+$per_request_config::
+ If this is set to code reference, it will be run once for each request.
+ You can set parts of configuration that change per session this way.
+ For example, one might use the following code in a gitweb configuration
+ file
++
+--------------------------------------------------------------------------------
+our $per_request_config = sub {
+ $ENV{GL_USER} = $cgi->remote_user || "gitweb";
+};
+--------------------------------------------------------------------------------
++
+If `$per_request_config` is not a code reference, it is interpreted as boolean
+value. If it is true gitweb will process config files once per request,
+and if it is false gitweb will process config files only once, each time it
+is executed. True by default (set to 1).
++
+*NOTE*: `$my_url`, `$my_uri`, and `$base_url` are overwritten with their default
+values before every request, so if you want to change them, be sure to set
+this variable to true or a code reference effecting the desired changes.
++
+This variable matters only when using persistent web environments that
+serve multiple requests using single gitweb instance, like mod_perl,
+FastCGI or Plackup.
+
+
+Other variables
+~~~~~~~~~~~~~~~
+Usually you should not need to change (adjust) any of configuration
+variables described below; they should be automatically set by gitweb to
+correct value.
+
+
+$version::
+ Gitweb version, set automatically when creating gitweb.cgi from
+ gitweb.perl. You might want to modify it if you are running modified
+ gitweb, for example
++
+---------------------------------------------------
+our $version .= " with caching";
+---------------------------------------------------
++
+if you run modified version of gitweb with caching support. This variable
+is purely informational, used e.g. in the "generator" meta header in HTML
+header.
+
+$my_url::
+$my_uri::
+ Full URL and absolute URL of the gitweb script;
+ in earlier versions of gitweb you might have need to set those
+ variables, but now there should be no need to do it. See
+ `$per_request_config` if you need to set them still.
+
+$base_url::
+ Base URL for relative URLs in pages generated by gitweb,
+ (e.g. `$logo`, `$favicon`, `@stylesheets` if they are relative URLs),
+ needed and used '<base href="$base_url">' only for URLs with nonempty
+ PATH_INFO. Usually gitweb sets its value correctly,
+ and there is no need to set this variable, e.g. to $my_uri or "/".
+ See `$per_request_config` if you need to override it anyway.
+
+
+CONFIGURING GITWEB FEATURES
+---------------------------
+Many gitweb features can be enabled (or disabled) and configured using the
+`%feature` hash. Names of gitweb features are keys of this hash.
+
+Each `%feature` hash element is a hash reference and has the following
+structure:
+----------------------------------------------------------------------
+"<feature_name>" => {
+ "sub" => <feature-sub (subroutine)>,
+ "override" => <allow-override (boolean)>,
+ "default" => [ <options>... ]
+},
+----------------------------------------------------------------------
+Some features cannot be overridden per project. For those
+features the structure of appropriate `%feature` hash element has a simpler
+form:
+----------------------------------------------------------------------
+"<feature_name>" => {
+ "override" => 0,
+ "default" => [ <options>... ]
+},
+----------------------------------------------------------------------
+As one can see it lacks the \'sub' element.
+
+The meaning of each part of feature configuration is described
+below:
+
+default::
+ List (array reference) of feature parameters (if there are any),
+ used also to toggle (enable or disable) given feature.
++
+Note that it is currently *always* an array reference, even if
+feature doesn't accept any configuration parameters, and \'default'
+is used only to turn it on or off. In such case you turn feature on
+by setting this element to `[1]`, and torn it off by setting it to
+`[0]`. See also the passage about the "blame" feature in the "Examples"
+section.
++
+To disable features that accept parameters (are configurable), you
+need to set this element to empty list i.e. `[]`.
+
+override::
+ If this field has a true value then the given feature is
+ overriddable, which means that it can be configured
+ (or enabled/disabled) on a per-repository basis.
++
+Usually given "<feature>" is configurable via the `gitweb.<feature>`
+config variable in the per-repository git configuration file.
++
+*Note* that no feature is overriddable by default.
+
+sub::
+ Internal detail of implementation. What is important is that
+ if this field is not present then per-repository override for
+ given feature is not supported.
++
+You wouldn't need to ever change it in gitweb config file.
+
+
+Features in `%feature`
+~~~~~~~~~~~~~~~~~~~~~~
+The gitweb features that are configurable via `%feature` hash are listed
+below. This should be a complete list, but ultimately the authoritative
+and complete list is in gitweb.cgi source code, with features described
+in the comments.
+
+blame::
+ Enable the "blame" and "blame_incremental" blob views, showing for
+ each line the last commit that modified it; see linkgit:git-blame[1].
+ This can be very CPU-intensive and is therefore disabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.blame` configuration variable (boolean).
+
+snapshot::
+ Enable and configure the "snapshot" action, which allows user to
+ download a compressed archive of any tree or commit, as produced
+ by linkgit:git-archive[1] and possibly additionally compressed.
+ This can potentially generate high traffic if you have large project.
++
+The value of \'default' is a list of names of snapshot formats,
+defined in `%known_snapshot_formats` hash, that you wish to offer.
+Supported formats include "tgz", "tbz2", "txz" (gzip/bzip2/xz
+compressed tar archive) and "zip"; please consult gitweb sources for
+a definitive list. By default only "tgz" is offered.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.blame` configuration variable, which contains
+a comma separated list of formats or "none" to disable snapshots.
+Unknown values are ignored.
+
+grep::
+ Enable grep search, which lists the files in currently selected
+ tree (directory) containing the given string; see linkgit:git-grep[1].
+ This can be potentially CPU-intensive, of course. Enabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.grep` configuration variable (boolean).
+
+pickaxe::
+ Enable the so called pickaxe search, which will list the commits
+ that introduced or removed a given string in a file. This can be
+ practical and quite faster alternative to "blame" action, but it is
+ still potentially CPU-intensive. Enabled by default.
++
+The pickaxe search is described in linkgit:git-log[1] (the
+description of `-S<string>` option, which refers to pickaxe entry in
+linkgit:gitdiffcore[7] for more details).
++
+This feature can be configured on a per-repository basis by setting
+repository's `gitweb.pickaxe` configuration variable (boolean).
+
+show-sizes::
+ Enable showing size of blobs (ordinary files) in a "tree" view, in a
+ separate column, similar to what `ls -l` does; see description of
+ `-l` option in linkgit:git-ls-tree[1] manpage. This costs a bit of
+ I/O. Enabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.showsizes` configuration variable (boolean).
+
+patches::
+ Enable and configure "patches" view, which displays list of commits in email
+ (plain text) output format; see also linkgit:git-format-patch[1].
+ The value is the maximum number of patches in a patchset generated
+ in "patches" view. Set the 'default' field to a list containing single
+ item of or to an empty list to disable patch view, or to a list
+ containing a single negative number to remove any limit.
+ Default value is 16.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.patches` configuration variable (integer).
+
+avatar::
+ Avatar support. When this feature is enabled, views such as
+ "shortlog" or "commit" will display an avatar associated with
+ the email of each committer and author.
++
+Currently available providers are *"gravatar"* and *"picon"*.
+Only one provider at a time can be selected ('default' is one element list).
+If an unknown provider is specified, the feature is disabled.
+*Note* that some providers might require extra Perl packages to be
+installed; see 'gitweb/INSTALL' for more details.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.avatar` configuration variable.
++
+See also `%avatar_size` with pixel sizes for icons and avatars
+("default" is used for one-line like "log" and "shortlog", "double"
+is used for two-line like "commit", "commitdiff" or "tag"). If the
+default font sizes or lineheights are changed (e.g. via adding extra
+CSS stylesheet in `@stylesheets`), it may be appropriate to change
+these values.
+
+highlight::
+ Server-side syntax highlight support in "blob" view. It requires
+ `$highlight_bin` program to be available (see the description of
+ this variable in the "Configuration variables" section above),
+ and therefore is disabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.highlight` configuration variable (boolean).
+
+remote_heads::
+ Enable displaying remote heads (remote-tracking branches) in the "heads"
+ list. In most cases the list of remote-tracking branches is an
+ unnecessary internal private detail, and this feature is therefore
+ disabled by default. linkgit:git-instaweb[1], which is usually used
+ to browse local repositories, enables and uses this feature.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.remote_heads` configuration variable (boolean).
+
+
+The remaining features cannot be overridden on a per project basis.
+
+search::
+ Enable text search, which will list the commits which match author,
+ committer or commit text to a given string; see the description of
+ `--author`, `--committer` and `--grep` options in linkgit:git-log[1]
+ manpage. Enabled by default.
++
+Project specific override is not supported.
+
+forks::
+ If this feature is enabled, gitweb considers projects in
+ subdirectories of project root (basename) to be forks of existing
+ projects. For each project +$projname.git+, projects in the
+ +$projname/+ directory and its subdirectories will not be
+ shown in the main projects list. Instead, a \'\+' mark is shown
+ next to +$projname+, which links to a "forks" view that lists all
+ the forks (all projects in +$projname/+ subdirectory). Additionally
+ a "forks" view for a project is linked from project summary page.
++
+If the project list is taken from a file (+$projects_list+ points to a
+file), forks are only recognized if they are listed after the main project
+in that file.
++
+Project specific override is not supported.
+
+actions::
+ Insert custom links to the action bar of all project pages. This
+ allows you to link to third-party scripts integrating into gitweb.
++
+The "default" value consists of a list of triplets in the form
+`("<label>", "<link>", "<position>")` where "position" is the label
+after which to insert the link, "link" is a format string where `%n`
+expands to the project name, `%f` to the project path within the
+filesystem (i.e. "$projectroot/$project"), `%h` to the current hash
+(\'h' gitweb parameter) and `%b` to the current hash base
+(\'hb' gitweb parameter); `%%` expands to \'%'.
++
+For example, at the time this page was written, the http://repo.or.cz[]
+git hosting site set it to the following to enable graphical log
+(using the third party tool *git-browser*):
++
+----------------------------------------------------------------------
+$feature{'actions'}{'default'} =
+ [ ('graphiclog', '/git-browser/by-commit.html?r=%n', 'summary')];
+----------------------------------------------------------------------
++
+This adds a link titled "graphiclog" after the "summary" link, leading to
+`git-browser` script, passing `r=<project>` as a query parameter.
++
+Project specific override is not supported.
+
+timed::
+ Enable displaying how much time and how many git commands it took to
+ generate and display each page in the page footer (at the bottom of
+ page). For example the footer might contain: "This page took 6.53325
+ seconds and 13 git commands to generate." Disabled by default.
++
+Project specific override is not supported.
+
+javascript-timezone::
+ Enable and configure the ability to change a common timezone for dates
+ in gitweb output via JavaScript. Dates in gitweb output include
+ authordate and committerdate in "commit", "commitdiff" and "log"
+ views, and taggerdate in "tag" view. Enabled by default.
++
+The value is a list of three values: a default timezone (for if the client
+hasn't selected some other timezone and saved it in a cookie), a name of cookie
+where to store selected timezone, and a CSS class used to mark up
+dates for manipulation. If you want to turn this feature off, set "default"
+to empty list: `[]`.
++
+Typical gitweb config files will only change starting (default) timezone,
+and leave other elements at their default values:
++
+---------------------------------------------------------------------------
+$feature{'javascript-timezone'}{'default'}[0] = "utc";
+---------------------------------------------------------------------------
++
+The example configuration presented here is guaranteed to be backwards
+and forward compatible.
++
+Timezone values can be "local" (for local timezone that browser uses), "utc"
+(what gitweb uses when JavaScript or this feature is disabled), or numerical
+timezones in the form of "+/-HHMM", such as "+0200".
++
+Project specific override is not supported.
+
+
+EXAMPLES
+--------
+
+To enable blame, pickaxe search, and snapshot support (allowing "tar.gz" and
+"zip" snapshots), while allowing individual projects to turn them off, put
+the following in your GITWEB_CONFIG file:
+
+ $feature{'blame'}{'default'} = [1];
+ $feature{'blame'}{'override'} = 1;
+
+ $feature{'pickaxe'}{'default'} = [1];
+ $feature{'pickaxe'}{'override'} = 1;
+
+ $feature{'snapshot'}{'default'} = ['zip', 'tgz'];
+ $feature{'snapshot'}{'override'} = 1;
+
+If you allow overriding for the snapshot feature, you can specify which
+snapshot formats are globally disabled. You can also add any command line
+options you want (such as setting the compression level). For instance, you
+can disable Zip compressed snapshots and set *gzip*(1) to run at level 6 by
+adding the following lines to your gitweb configuration file:
+
+ $known_snapshot_formats{'zip'}{'disabled'} = 1;
+ $known_snapshot_formats{'tgz'}{'compressor'} = ['gzip','-6'];
+
+ENVIRONMENT
+-----------
+The location of per-instance and system-wide configuration files can be
+overridden using the following environment variables:
+
+GITWEB_CONFIG::
+ Sets location of per-instance configuration file.
+GITWEB_CONFIG_SYSTEM::
+ Sets location of fallback system-wide configuration file.
+ This file is read only if per-instance one does not exist.
+GITWEB_CONFIG_COMMON::
+ Sets location of common system-wide configuration file.
+
+
+FILES
+-----
+gitweb_config.perl::
+ This is default name of per-instance configuration file. The
+ format of this file is described above.
+/etc/gitweb.conf::
+ This is default name of fallback system-wide configuration
+ file. This file is used only if per-instance configuration
+ variable is not found.
+/etc/gitweb-common.conf::
+ This is default name of common system-wide configuration
+ file.
+
+
+SEE ALSO
+--------
+linkgit:gitweb[1], linkgit:git-instaweb[1]
+
+'gitweb/README', 'gitweb/INSTALL'
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitweb.txt b/Documentation/gitweb.txt
new file mode 100644
index 0000000..168e8bf
--- /dev/null
+++ b/Documentation/gitweb.txt
@@ -0,0 +1,704 @@
+gitweb(1)
+=========
+
+NAME
+----
+gitweb - Git web interface (web frontend to Git repositories)
+
+SYNOPSIS
+--------
+To get started with gitweb, run linkgit:git-instaweb[1] from a git repository.
+This would configure and start your web server, and run web browser pointing to
+gitweb.
+
+
+DESCRIPTION
+-----------
+Gitweb provides a web interface to git repositories. Its features include:
+
+* Viewing multiple Git repositories with common root.
+* Browsing every revision of the repository.
+* Viewing the contents of files in the repository at any revision.
+* Viewing the revision log of branches, history of files and directories,
+ see what was changed when, by who.
+* Viewing the blame/annotation details of any file (if enabled).
+* Generating RSS and Atom feeds of commits, for any branch.
+ The feeds are auto-discoverable in modern web browsers.
+* Viewing everything that was changed in a revision, and step through
+ revisions one at a time, viewing the history of the repository.
+* Finding commits which commit messages matches given search term.
+
+See http://git.kernel.org/?p=git/git.git;a=tree;f=gitweb[] or
+http://repo.or.cz/w/git.git/tree/HEAD:/gitweb/[] for gitweb source code,
+browsed using gitweb itself.
+
+
+CONFIGURATION
+-------------
+Various aspects of gitweb's behavior can be controlled through the configuration
+file 'gitweb_config.perl' or '/etc/gitweb.conf'. See the linkgit:gitweb.conf[5]
+for details.
+
+Repositories
+~~~~~~~~~~~~
+Gitweb can show information from one or more Git repositories. These
+repositories have to be all on local filesystem, and have to share common
+repository root, i.e. be all under a single parent repository (but see also
+"Advanced web server setup" section, "Webserver configuration with multiple
+projects' root" subsection).
+
+-----------------------------------------------------------------------
+our $projectroot = '/path/to/parent/directory';
+-----------------------------------------------------------------------
+
+The default value for `$projectroot` is '/pub/git'. You can change it during
+building gitweb via `GITWEB_PROJECTROOT` build configuration variable.
+
+By default all git repositories under `$projectroot` are visible and available
+to gitweb. The list of projects is generated by default by scanning the
+`$projectroot` directory for git repositories (for object databases to be
+more exact; gitweb is not interested in a working area, and is best suited
+to showing "bare" repositories).
+
+The name of the repository in gitweb is the path to its `$GIT_DIR` (its object
+database) relative to `$projectroot`. Therefore the repository $repo can be
+found at "$projectroot/$repo".
+
+
+Projects list file format
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Instead of having gitweb find repositories by scanning filesystem
+starting from $projectroot, you can provide a pre-generated list of
+visible projects by setting `$projects_list` to point to a plain text
+file with a list of projects (with some additional info).
+
+This file uses the following format:
+
+* One record (for project / repository) per line; does not support line
+continuation (newline escaping).
+
+* Leading and trailing whitespace are ignored.
+
+* Whitespace separated fields; any run of whitespace can be used as field
+separator (rules for Perl's "`split(" ", $line)`").
+
+* Fields use modified URI encoding, defined in RFC 3986, section 2.1
+(Percent-Encoding), or rather "Query string encoding" (see
+link:http://en.wikipedia.org/wiki/Query_string#URL_encoding[]), the difference
+being that SP (" ") can be encoded as "{plus}" (and therefore "{plus}" has to be
+also percent-encoded).
++
+Reserved characters are: "%" (used for encoding), "{plus}" (can be used to
+encode SPACE), all whitespace characters as defined in Perl, including SP,
+TAB and LF, (used to separate fields in a record).
+
+* Currently recognized fields are:
+<repository path>::
+ path to repository GIT_DIR, relative to `$projectroot`
+<repository owner>::
+ displayed as repository owner, preferably full name, or email,
+ or both
+
+You can generate the projects list index file using the project_index action
+(the 'TXT' link on projects list page) directly from gitweb; see also
+"Generating projects list using gitweb" section below.
+
+Example contents:
+-----------------------------------------------------------------------
+foo.git Joe+R+Hacker+<joe@example.com>
+foo/bar.git O+W+Ner+<owner@example.org>
+-----------------------------------------------------------------------
+
+
+By default this file controls only which projects are *visible* on projects
+list page (note that entries that do not point to correctly recognized git
+repositories won't be displayed by gitweb). Even if a project is not
+visible on projects list page, you can view it nevertheless by hand-crafting
+a gitweb URL. By setting `$strict_export` configuration variable (see
+linkgit:gitweb.conf[5]) to true value you can allow viewing only of
+repositories also shown on the overview page (i.e. only projects explicitly
+listed in projects list file will be accessible).
+
+
+Generating projects list using gitweb
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We assume that GITWEB_CONFIG has its default Makefile value, namely
+'gitweb_config.perl'. Put the following in 'gitweb_make_index.perl' file:
+----------------------------------------------------------------------------
+read_config_file("gitweb_config.perl");
+$projects_list = $projectroot;
+----------------------------------------------------------------------------
+
+Then create the following script to get list of project in the format
+suitable for GITWEB_LIST build configuration variable (or
+`$projects_list` variable in gitweb config):
+
+----------------------------------------------------------------------------
+#!/bin/sh
+
+export GITWEB_CONFIG="gitweb_make_index.perl"
+export GATEWAY_INTERFACE="CGI/1.1"
+export HTTP_ACCEPT="*/*"
+export REQUEST_METHOD="GET"
+export QUERY_STRING="a=project_index"
+
+perl -- /var/www/cgi-bin/gitweb.cgi
+----------------------------------------------------------------------------
+
+Run this script and save its output to a file. This file could then be used
+as projects list file, which means that you can set `$projects_list` to its
+filename.
+
+
+Controlling access to git repositories
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+By default all git repositories under `$projectroot` are visible and
+available to gitweb. You can however configure how gitweb controls access
+to repositories.
+
+* As described in "Projects list file format" section, you can control which
+projects are *visible* by selectively including repositories in projects
+list file, and setting `$projects_list` gitweb configuration variable to
+point to it. With `$strict_export` set, projects list file can be used to
+control which repositories are *available* as well.
+
+* You can configure gitweb to only list and allow viewing of the explicitly
+exported repositories, via `$export_ok` variable in gitweb config file; see
+linkgit:gitweb.conf[5] manpage. If it evaluates to true, gitweb shows
+repositories only if this file named by `$export_ok` exists in its object
+database (if directory has the magic file named `$export_ok`).
++
+For example linkgit:git-daemon[1] by default (unless `--export-all` option
+is used) allows pulling only for those repositories that have
+'git-daemon-export-ok' file. Adding
++
+--------------------------------------------------------------------------
+our $export_ok = "git-daemon-export-ok";
+--------------------------------------------------------------------------
++
+makes gitweb show and allow access only to those repositories that can be
+fetched from via `git://` protocol.
+
+* Finally, it is possible to specify an arbitrary perl subroutine that will
+be called for each repository to determine if it can be exported. The
+subroutine receives an absolute path to the project (repository) as its only
+parameter (i.e. "$projectroot/$project").
++
+For example, if you use mod_perl to run the script, and have dumb
+HTTP protocol authentication configured for your repositories, you
+can use the following hook to allow access only if the user is
+authorized to read the files:
++
+--------------------------------------------------------------------------
+$export_auth_hook = sub {
+ use Apache2::SubRequest ();
+ use Apache2::Const -compile => qw(HTTP_OK);
+ my $path = "$_[0]/HEAD";
+ my $r = Apache2::RequestUtil->request;
+ my $sub = $r->lookup_file($path);
+ return $sub->filename eq $path
+ && $sub->status == Apache2::Const::HTTP_OK;
+};
+--------------------------------------------------------------------------
+
+
+Per-repository gitweb configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+You can 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', see linkgit:git-config[1]).
+
+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 project's
+ homepage), etc. This is recognized only if XSS prevention is off
+ (`$prevent_xss` is false, see linkgit:gitweb.conf[5]); a way to include
+ a README safely when XSS prevention is on may be worked out in the
+ future.
+
+description (or `gitweb.description`)::
+ Short (shortened to `$projects_list_description_width` in the projects
+ list page, which is 25 characters by default; see
+ linkgit:gitweb.conf[5]) 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 repository creation, usually installed in
+'/usr/share/git-core/templates/'. You can use the `gitweb.description` repo
+configuration variable, but the file takes precedence.
+
+category (or `gitweb.category`)::
+ Singe line category of a project, used to group projects if
+ `$projects_list_group_categories` is enabled. By default (file and
+ configuration variable absent), uncategorized projects are put in the
+ `$project_list_default_category` category. You can use the
+ `gitweb.category` repo configuration variable, but the file takes
+ precedence.
++
+The configuration variables `$projects_list_group_categories` and
+`$project_list_default_category` are described in linkgit:gitweb.conf[5]
+
+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 precedence.
++
+This is per-repository enhancement / version of global prefix-based
+`@git_base_url_list` gitweb configuration variable (see
+linkgit:gitweb.conf[5]).
+
+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 (via GECOS field,
+i.e. real name field from *getpwuid*(3)) if `$projects_list` is unset
+(gitweb scans `$projectroot` for repositories); if `$projects_list`
+points to file with list of repositories, then project owner defaults to
+value from this file for given repository.
+
+various `gitweb.*` config variables (in config)::
+ Read description of `%feature` hash for detailed list, and descriptions.
+ See also "Configuring gitweb features" section in linkgit:gitweb.conf[5]
+
+
+ACTIONS, AND URLS
+-----------------
+Gitweb can use path_info (component) based URLs, or it can pass all necessary
+information via query parameters. The typical gitweb URLs are broken down in to
+five components:
+
+-----------------------------------------------------------------------
+.../gitweb.cgi/<repo>/<action>/<revision>:/<path>?<arguments>
+-----------------------------------------------------------------------
+
+repo::
+ The repository the action will be performed on.
++
+All actions except for those that list all available projects,
+in whatever form, require this parameter.
+
+action::
+ The action that will be run. Defaults to 'projects_list' if repo
+ is not set, and to 'summary' otherwise.
+
+revision::
+ Revision shown. Defaults to HEAD.
+
+path::
+ The path within the <repository> that the action is performed on,
+ for those actions that require it.
+
+arguments::
+ Any arguments that control the behaviour of the action.
+
+Some actions require or allow to specify two revisions, and sometimes even two
+pathnames. In most general form such path_info (component) based gitweb URL
+looks like this:
+
+-----------------------------------------------------------------------
+.../gitweb.cgi/<repo>/<action>/<revision_from>:/<path_from>..<revision_to>:/<path_to>?<arguments>
+-----------------------------------------------------------------------
+
+
+Each action is implemented as a subroutine, and must be present in %actions
+hash. Some actions are disabled by default, and must be turned on via feature
+mechanism. For example to enable 'blame' view add the following to gitweb
+configuration file:
+
+-----------------------------------------------------------------------
+$feature{'blame'}{'default'} = [1];
+-----------------------------------------------------------------------
+
+
+Actions:
+~~~~~~~~
+The standard actions are:
+
+project_list::
+ Lists the available Git repositories. This is the default command if no
+ repository is specified in the URL.
+
+summary::
+ Displays summary about given repository. This is the default command if
+ no action is specified in URL, and only repository is specified.
+
+heads::
+remotes::
+ Lists all local or all remote-tracking branches in given repository.
++
+The latter is not available by default, unless configured.
+
+tags::
+ List all tags (lightweight and annotated) in given repository.
+
+blob::
+tree::
+ Shows the files and directories in a given repository path, at given
+ revision. This is default command if no action is specified in the URL,
+ and path is given.
+
+blob_plain::
+ Returns the raw data for the file in given repository, at given path and
+ revision. Links to this action are marked 'raw'.
+
+blobdiff::
+ Shows the difference between two revisions of the same file.
+
+blame::
+blame_incremental::
+ Shows the blame (also called annotation) information for a file. On a
+ per line basis it shows the revision in which that line was last changed
+ and the user that committed the change. The incremental version (which
+ if configured is used automatically when JavaScript is enabled) uses
+ Ajax to incrementally add blame info to the contents of given file.
++
+This action is disabled by default for performance reasons.
+
+commit::
+commitdiff::
+ Shows information about a specific commit in a repository. The 'commit'
+ view shows information about commit in more detail, the 'commitdiff'
+ action shows changeset for given commit.
+
+patch::
+ Returns the commit in plain text mail format, suitable for applying with
+ linkgit:git-am[1].
+
+tag::
+ Display specific annotated tag (tag object).
+
+log::
+shortlog::
+ Shows log information (commit message or just commit subject) for a
+ given branch (starting from given revision).
++
+The 'shortlog' view is more compact; it shows one commit per line.
+
+history::
+ Shows history of the file or directory in a given repository path,
+ starting from given revision (defaults to HEAD, i.e. default branch).
++
+This view is similar to 'shortlog' view.
+
+rss::
+atom::
+ Generates an RSS (or Atom) feed of changes to repository.
+
+
+WEBSERVER CONFIGURATION
+-----------------------
+This section explains how to configure some common webservers to run gitweb. In
+all cases, `/path/to/gitweb` in the examples is the directory you ran installed
+gitweb in, and contains `gitweb_config.perl`.
+
+If you've configured a web server that isn't listed here for gitweb, please send
+in the instructions so they can be included in a future release.
+
+Apache as CGI
+~~~~~~~~~~~~~
+Apache must be configured to support CGI scripts in the directory in
+which gitweb is installed. Let's assume that it is '/var/www/cgi-bin'
+directory.
+
+-----------------------------------------------------------------------
+ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
+
+<Directory "/var/www/cgi-bin">
+ Options Indexes FollowSymlinks ExecCGI
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+ http://server/cgi-bin/gitweb.cgi
+
+Apache with mod_perl, via ModPerl::Registry
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+You can use mod_perl with gitweb. You must install Apache::Registry
+(for mod_perl 1.x) or ModPerl::Registry (for mod_perl 2.x) to enable
+this support.
+
+Assuming that gitweb is installed to '/var/www/perl', the following
+Apache configuration (for mod_perl 2.x) is suitable.
+
+-----------------------------------------------------------------------
+Alias /perl "/var/www/perl"
+
+<Directory "/var/www/perl">
+ SetHandler perl-script
+ PerlResponseHandler ModPerl::Registry
+ PerlOptions +ParseHeaders
+ Options Indexes FollowSymlinks +ExecCGI
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+ http://server/perl/gitweb.cgi
+
+Apache with FastCGI
+~~~~~~~~~~~~~~~~~~~
+Gitweb works with Apache and FastCGI. First you need to rename, copy
+or symlink gitweb.cgi to gitweb.fcgi. Let's assume that gitweb is
+installed in '/usr/share/gitweb' directory. The following Apache
+configuration is suitable (UNTESTED!)
+
+-----------------------------------------------------------------------
+FastCgiServer /usr/share/gitweb/gitweb.cgi
+ScriptAlias /gitweb /usr/share/gitweb/gitweb.cgi
+
+Alias /gitweb/static /usr/share/gitweb/static
+<Directory /usr/share/gitweb/static>
+ SetHandler default-handler
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+ http://server/gitweb
+
+
+ADVANCED WEB SERVER SETUP
+-------------------------
+All of those examples use request rewriting, and need `mod_rewrite`
+(or equivalent; examples below are written for Apache).
+
+Single URL for gitweb and for fetching
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you want to have one URL for both gitweb and your `http://`
+repositories, you can configure Apache like this:
+
+-----------------------------------------------------------------------
+<VirtualHost *:80>
+ ServerName git.example.org
+ DocumentRoot /pub/git
+ SetEnv GITWEB_CONFIG /etc/gitweb.conf
+
+ # turning on mod rewrite
+ RewriteEngine on
+
+ # make the front page an internal rewrite to the gitweb script
+ RewriteRule ^/$ /cgi-bin/gitweb.cgi
+
+ # make access for "dumb clients" work
+ RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \
+ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT]
+</VirtualHost>
+-----------------------------------------------------------------------
+
+The above configuration expects your public repositories to live under
+'/pub/git' and will serve them as `http://git.domain.org/dir-under-pub-git`,
+both as cloneable GIT URL and as browseable gitweb interface. If you then
+start your linkgit:git-daemon[1] with `--base-path=/pub/git --export-all`
+then you can even use the `git://` URL with exactly the same path.
+
+Setting the environment variable `GITWEB_CONFIG` will tell gitweb to use the
+named file (i.e. in this example '/etc/gitweb.conf') as a configuration for
+gitweb. You don't really need it in above example; it is required only if
+your configuration file is in different place than built-in (during
+compiling gitweb) 'gitweb_config.perl' or '/etc/gitweb.conf'. See
+linkgit:gitweb.conf[5] for details, especially information about precedence
+rules.
+
+If you use the rewrite rules from the example you *might* also need
+something like the following in your gitweb configuration file
+('/etc/gitweb.conf' following example):
+----------------------------------------------------------------------------
+@stylesheets = ("/some/absolute/path/gitweb.css");
+$my_uri = "/";
+$home_link = "/";
+$per_request_config = 1;
+----------------------------------------------------------------------------
+Nowadays though gitweb should create HTML base tag when needed (to set base
+URI for relative links), so it should work automatically.
+
+
+Webserver configuration with multiple projects' root
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you want to use gitweb with several project roots you can edit your
+Apache virtual host and gitweb configuration files in the following way.
+
+The virtual host configuration (in Apache configuration file) should look
+like this:
+--------------------------------------------------------------------------
+<VirtualHost *:80>
+ ServerName git.example.org
+ DocumentRoot /pub/git
+ SetEnv GITWEB_CONFIG /etc/gitweb.conf
+
+ # turning on mod rewrite
+ RewriteEngine on
+
+ # make the front page an internal rewrite to the gitweb script
+ RewriteRule ^/$ /cgi-bin/gitweb.cgi [QSA,L,PT]
+
+ # look for a public_git folder in unix users' home
+ # http://git.example.org/~<user>/
+ RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+ [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+ # http://git.example.org/+<user>/
+ #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+ [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+ # http://git.example.org/user/<user>/
+ #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+ [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+ # defined list of project roots
+ RewriteRule ^/scm(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+ [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT]
+ RewriteRule ^/var(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+ [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT]
+
+ # make access for "dumb clients" work
+ RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \
+ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT]
+</VirtualHost>
+--------------------------------------------------------------------------
+
+Here actual project root is passed to gitweb via `GITWEB_PROJECT_ROOT`
+environment variable from a web server, so you need to put the following
+line in gitweb configuration file ('/etc/gitweb.conf' in above example):
+--------------------------------------------------------------------------
+$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git";
+--------------------------------------------------------------------------
+*Note* that this requires to be set for each request, so either
+`$per_request_config` must be false, or the above must be put in code
+referenced by `$per_request_config`;
+
+These configurations enable two things. First, each unix user (`<user>`) of
+the server will be able to browse through gitweb git repositories found in
+'~/public_git/' with the following url:
+
+ http://git.example.org/~<user>/
+
+If you do not want this feature on your server just remove the second
+rewrite rule.
+
+If you already use `mod_userdir` in your virtual host or you don't want to
+use the \'~' as first character, just comment or remove the second rewrite
+rule, and uncomment one of the following according to what you want.
+
+Second, repositories found in '/pub/scm/' and '/var/git/' will be accessible
+through `http://git.example.org/scm/` and `http://git.example.org/var/`.
+You can add as many project roots as you want by adding rewrite rules like
+the third and the fourth.
+
+
+PATH_INFO usage
+~~~~~~~~~~~~~~~
+If you enable PATH_INFO usage in gitweb by putting
+----------------------------------------------------------------------------
+$feature{'pathinfo'}{'default'} = [1];
+----------------------------------------------------------------------------
+in your gitweb configuration file, it is possible to set up your server so
+that it consumes and produces URLs in the form
+
+ http://git.example.com/project.git/shortlog/sometag
+
+i.e. without 'gitweb.cgi' part, by using a configuration such as the
+following. This configuration assumes that '/var/www/gitweb' is the
+DocumentRoot of your webserver, contains the gitweb.cgi script and
+complementary static files (stylesheet, favicon, JavaScript):
+
+----------------------------------------------------------------------------
+<VirtualHost *:80>
+ ServerAlias git.example.com
+
+ DocumentRoot /var/www/gitweb
+
+ <Directory /var/www/gitweb>
+ Options ExecCGI
+ AddHandler cgi-script cgi
+
+ DirectoryIndex gitweb.cgi
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+ </Directory>
+</VirtualHost>
+----------------------------------------------------------------------------
+The rewrite rule guarantees that existing static files will be properly
+served, whereas any other URL will be passed to gitweb as PATH_INFO
+parameter.
+
+*Notice* that in this case you don't need special settings for
+`@stylesheets`, `$my_uri` and `$home_link`, but you lose "dumb client"
+access to your project .git dirs (described in "Single URL for gitweb and
+for fetching" section). A possible workaround for the latter is the
+following: in your project root dir (e.g. '/pub/git') have the projects
+named *without* a .git extension (e.g. '/pub/git/project' instead of
+'/pub/git/project.git') and configure Apache as follows:
+----------------------------------------------------------------------------
+<VirtualHost *:80>
+ ServerAlias git.example.com
+
+ DocumentRoot /var/www/gitweb
+
+ AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3
+ <Directory /var/www/gitweb>
+ Options ExecCGI
+ AddHandler cgi-script cgi
+
+ DirectoryIndex gitweb.cgi
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+ </Directory>
+</VirtualHost>
+----------------------------------------------------------------------------
+
+The additional AliasMatch makes it so that
+
+ http://git.example.com/project.git
+
+will give raw access to the project's git dir (so that the project can be
+cloned), while
+
+ http://git.example.com/project
+
+will provide human-friendly gitweb access.
+
+This solution is not 100% bulletproof, in the sense that if some project has
+a named ref (branch, tag) starting with 'git/', then paths such as
+
+ http://git.example.com/project/command/abranch..git/abranch
+
+will fail with a 404 error.
+
+
+BUGS
+----
+Please report any bugs or feature requests to git@vger.kernel.org,
+putting "gitweb" in the subject of email.
+
+SEE ALSO
+--------
+linkgit:gitweb.conf[5], linkgit:git-instaweb[1]
+
+'gitweb/README', 'gitweb/INSTALL'
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
index 5e4f362..8b8c6ae 100644
--- a/Documentation/gitworkflows.txt
+++ b/Documentation/gitworkflows.txt
@@ -39,8 +39,8 @@ To achieve this, try to split your work into small steps from the very
beginning. It is always easier to squash a few commits together than
to split one big commit into several. Don't be afraid of making too
small or imperfect steps along the way. You can always go back later
-and edit the commits with `git rebase \--interactive` before you
-publish them. You can use `git stash save \--keep-index` to run the
+and edit the commits with `git rebase --interactive` before you
+publish them. You can use `git stash save --keep-index` to run the
test suite independent of other uncommitted changes; see the EXAMPLES
section of linkgit:git-stash[1].
diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt
index d527b30..8823a37 100644
--- a/Documentation/howto/maintain-git.txt
+++ b/Documentation/howto/maintain-git.txt
@@ -176,7 +176,7 @@ by doing the following:
- 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
+ This step is helped with Meta/cook script (where Meta/ contains
a checkout of the 'todo' branch).
- Merge topics to 'next'. For each branch whose tip is not
@@ -197,10 +197,9 @@ by doing the following:
- 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.
+ - [** OBSOLETE **] Optionally rebase topics that do not have any commit
+ in next yet, when they can take advantage of low-level framework
+ change that is merged to 'master' already.
$ git rebase master ai/topic
@@ -209,7 +208,7 @@ by doing the following:
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'.
+ - [** OBSOLETE **] Rebuild "pu" to merge the tips of topics not in 'next'.
$ git checkout pu
$ git reset --hard next
@@ -241,7 +240,7 @@ by doing the following:
- Fetch html and man branches back from k.org, and push four
integration branches and the two documentation branches to
- repo.or.cz
+ repo.or.cz and other mirrors.
Some observations to be made.
diff --git a/Documentation/howto/using-merge-subtree.txt b/Documentation/howto/using-merge-subtree.txt
index 2933056..1ae8d12 100644
--- a/Documentation/howto/using-merge-subtree.txt
+++ b/Documentation/howto/using-merge-subtree.txt
@@ -25,7 +25,7 @@ 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'
+it can be a 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.
diff --git a/Documentation/howto/using-signed-tag-in-pull-request.txt b/Documentation/howto/using-signed-tag-in-pull-request.txt
new file mode 100644
index 0000000..98c0033
--- /dev/null
+++ b/Documentation/howto/using-signed-tag-in-pull-request.txt
@@ -0,0 +1,217 @@
+From: Junio C Hamano <gitster@pobox.com>
+Date: Tue, 17 Jan 2011 13:00:00 -0800
+Subject: Using signed tag in pull requests
+Abstract: Beginning v1.7.9, a contributor can push a signed tag to her
+ publishing repository and ask her integrator to pull it. This assures the
+ integrator that the pulled history is authentic and allows others to
+ later validate it.
+Content-type: text/asciidoc
+
+Using signed tag in pull requests
+=================================
+
+A typical distributed workflow using Git is for a contributor to fork a
+project, build on it, publish the result to her public repository, and ask
+the "upstream" person (often the owner of the project where she forked
+from) to pull from her public repository. Requesting such a "pull" is made
+easy by the `git request-pull` command.
+
+Earlier, a typical pull request may have started like this:
+
+------------
+ The following changes since commit 406da78032179...:
+
+ Froboz 3.2 (2011-09-30 14:20:57 -0700)
+
+ are available in the git repository at:
+
+ example.com:/git/froboz.git for-xyzzy
+------------
+
+followed by a shortlog of the changes and a diffstat.
+
+The request was for a branch name (e.g. `for-xyzzy`) in the public
+repository of the contributor, and even though it stated where the
+contributor forked her work from, the message did not say anything about
+the commit to expect at the tip of the for-xyzzy branch. If the site that
+hosts the public repository of the contributor cannot be fully trusted, it
+was unnecessarily hard to make sure what was pulled by the integrator was
+genuinely what the contributor had produced for the project. Also there
+was no easy way for third-party auditors to later verify the resulting
+history.
+
+Starting from Git release v1.7.9, a contributor can add a signed tag to
+the commit at the tip of the history and ask the integrator to pull that
+signed tag. When the integrator runs `git pull`, the signed tag is
+automatically verified to assure that the history is not tampered with.
+In addition, the resulting merge commit records the content of the signed
+tag, so that other people can verify that the branch merged by the
+integrator was signed by the contributor, without fetching the signed tag
+used to validate the pull request separately and keeping it in the refs
+namespace.
+
+This document describes the workflow between the contributor and the
+integrator, using Git v1.7.9 or later.
+
+
+A contributor or a lieutenant
+-----------------------------
+
+After preparing her work to be pulled, the contributor uses `git tag -s`
+to create a signed tag:
+
+------------
+ $ git checkout work
+ $ ... "git pull" from sublieutenants, "git commit" your own work ...
+ $ git tag -s -m "Completed frotz feature" frotz-for-xyzzy work
+------------
+
+Note that this example uses the `-m` option to create a signed tag with
+just a one-liner message, but this is for illustration purposes only. It
+is advisable to compose a well-written explanation of what the topic does
+to justify why it is worthwhile for the integrator to pull it, as this
+message will eventually become part of the final history after the
+integrator responds to the pull request (as we will see later).
+
+Then she pushes the tag out to her public repository:
+
+------------
+ $ git push example.com:/git/froboz.git/ +frotz-for-xyzzy
+------------
+
+There is no need to push the `work` branch or anything else.
+
+Note that the above command line used a plus sign at the beginning of
+`+frotz-for-xyzzy` to allow forcing the update of a tag, as the same
+contributor may want to reuse a signed tag with the same name after the
+previous pull request has already been responded to.
+
+The contributor then prepares a message to request a "pull":
+
+------------
+ $ git request-pull v3.2 example.com:/git/froboz.git/ frotz-for-xyzzy >msg.txt
+------------
+
+The arguments are:
+
+. the version of the integrator's commit the contributor based her work on;
+. the URL of the repository, to which the contributor has pushed what she
+ wants to get pulled; and
+. the name of the tag the contributor wants to get pulled (earlier, she could
+ write only a branch name here).
+
+The resulting msg.txt file begins like so:
+
+------------
+ The following changes since commit 406da78032179...:
+
+ Froboz 3.2 (2011-09-30 14:20:57 -0700)
+
+ are available in the git repository at:
+
+ example.com:/git/froboz.git tags/frotz-for-xyzzy
+
+ for you to fetch changes up to 703f05ad5835c...:
+
+ Add tests and documentation for frotz (2011-12-02 10:02:52 -0800)
+
+ -----------------------------------------------
+ Completed frotz feature
+ -----------------------------------------------
+------------
+
+followed by a shortlog of the changes and a diffstat. Comparing this with
+the earlier illustration of the output from the traditional `git request-pull`
+command, the reader should notice that:
+
+. The tip commit to expect is shown to the integrator; and
+. The signed tag message is shown prominently between the dashed lines
+ before the shortlog.
+
+The latter is why the contributor would want to justify why pulling her
+work is worthwhile when creating the signed tag. The contributor then
+opens her favorite MUA, reads msg.txt, edits and sends it to her upstream
+integrator.
+
+
+Integrator
+----------
+
+After receiving such a pull request message, the integrator fetches and
+integrates the tag named in the request, with:
+
+------------
+ $ git pull example.com:/git/froboz.git/ tags/frotz-for-xyzzy
+------------
+
+This operation will always open an editor to allow the integrator to fine
+tune the commit log message when merging a signed tag. Also, pulling a
+signed tag will always create a merge commit even when the integrator does
+not have any new commit since the contributor's work forked (i.e. 'fast
+forward'), so that the integrator can properly explain what the merge is
+about and why it was made.
+
+In the editor, the integrator will see something like this:
+
+------------
+ Merge tag 'frotz-for-xyzzy' of example.com:/git/froboz.git/
+
+ Completed frotz feature
+ # gpg: Signature made Fri 02 Dec 2011 10:03:01 AM PST using RSA key ID 96AFE6CB
+ # gpg: Good signature from "Con Tributor <nitfol@example.com>"
+------------
+
+Notice that the message recorded in the signed tag "Completed frotz
+feature" appears here, and again that is why it is important for the
+contributor to explain her work well when creating the signed tag.
+
+As usual, the lines commented with `#` are stripped out. The resulting
+commit records the signed tag used for this validation in a hidden field
+so that it can later be used by others to audit the history. There is no
+need for the integrator to keep a separate copy of the tag in his
+repository (i.e. `git tag -l` won't list the `frotz-for-xyzzy` tag in the
+above example), and there is no need to publish the tag to his public
+repository, either.
+
+After the integrator responds to the pull request and her work becomes
+part of the permanent history, the contributor can remove the tag from
+her public repository, if she chooses, in order to keep the tag namespace
+of her public repository clean, with:
+
+------------
+ $ git push example.com:/git/froboz.git :frotz-for-xyzzy
+------------
+
+
+Auditors
+--------
+
+The `--show-signature` option can be given to `git log` or `git show` and
+shows the verification status of the embedded signed tag in merge commits
+created when the integrator responded to a pull request of a signed tag.
+
+A typical output from `git show --show-signature` may look like this:
+
+------------
+ $ git show --show-signature
+ commit 02306ef6a3498a39118aef9df7975bdb50091585
+ merged tag 'frotz-for-xyzzy'
+ gpg: Signature made Fri 06 Jan 2012 12:41:49 PM PST using RSA key ID 96AFE6CB
+ gpg: Good signature from "Con Tributor <nitfol@example.com>"
+ Merge: 406da78 703f05a
+ Author: Inte Grator <xyzzy@example.com>
+ Date: Tue Jan 17 13:49:41 2012 -0800
+
+ Merge tag 'frotz-for-xyzzy' of example.com:/git/froboz.git/
+
+ Completed frotz feature
+
+ * tag 'frotz-for-xyzzy' (100 commits)
+ Add tests and documentation for frotz
+ ...
+------------
+
+There is no need for the auditor to explicitly fetch the contributor's
+signature, or to even be aware of what tag(s) the contributor and integrator
+used to communicate the signature. All the required information is recorded
+as part of the merge commit.
diff --git a/Documentation/install-doc-quick.sh b/Documentation/install-doc-quick.sh
index 35f4408..327f69b 100755
--- a/Documentation/install-doc-quick.sh
+++ b/Documentation/install-doc-quick.sh
@@ -1,31 +1,39 @@
#!/bin/sh
-# This requires a branch named in $head
-# (usually 'man' or 'html', provided by the git.git repository)
-set -e
-head="$1"
-mandir="$2"
-SUBDIRECTORY_OK=t
-USAGE='<refname> <target directory>'
-. "$(git --exec-path)"/git-sh-setup
-cd_to_toplevel
+# This requires git-manpages and/or git-htmldocs repositories
-test -z "$mandir" && usage
-if ! git rev-parse --verify "$head^0" >/dev/null; then
- echo >&2 "head: $head does not exist in the current repository"
- usage
+repository=${1?repository}
+destdir=${2?destination}
+
+head=master GIT_DIR=
+for d in "$repository/.git" "$repository"
+do
+ if GIT_DIR="$d" git rev-parse refs/heads/master >/dev/null 2>&1
+ then
+ GIT_DIR="$d"
+ export GIT_DIR
+ break
+ fi
+done
+
+if test -z "$GIT_DIR"
+then
+ echo >&2 "Neither $repository nor $repository/.git is a repository"
+ exit 1
fi
-GIT_INDEX_FILE=`pwd`/.quick-doc.index
-export GIT_INDEX_FILE
+GIT_WORK_TREE=$(pwd)
+GIT_INDEX_FILE=$(pwd)/.quick-doc.$$
+export GIT_INDEX_FILE GIT_WORK_TREE
rm -f "$GIT_INDEX_FILE"
trap 'rm -f "$GIT_INDEX_FILE"' 0
git read-tree $head
-git checkout-index -a -f --prefix="$mandir"/
+git checkout-index -a -f --prefix="$destdir"/
-if test -n "$GZ"; then
+if test -n "$GZ"
+then
git ls-tree -r --name-only $head |
- xargs printf "$mandir/%s\n" |
+ xargs printf "$destdir/%s\n" |
xargs gzip -f
fi
rm -f "$GIT_INDEX_FILE"
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index b613d4e..0bcbe0a 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -7,14 +7,35 @@ With --no-commit perform the merge but pretend the merge
failed and do not autocommit, to give the user a chance to
inspect and further tweak the merge result before committing.
+--edit::
+--no-edit::
+ Invoke an editor before committing successful mechanical merge to
+ further edit the auto-generated merge message, so that the user
+ can explain and justify the merge. The `--no-edit` option can be
+ used to accept the auto-generated message (this is generally
+ discouraged). The `--edit` option is still useful if you are
+ giving a draft message with the `-m` option from the command line
+ and want to edit it in the editor.
++
+Older scripts may depend on the historical behaviour of not allowing the
+user to edit the merge log message. They will see an editor opened when
+they run `git merge`. To make it easier to adjust such scripts to the
+updated behaviour, the environment variable `GIT_MERGE_AUTOEDIT` can be
+set to `no` at the beginning of them.
+
--ff::
+ When the merge resolves as a fast-forward, only update the branch
+ pointer, without creating a merge commit. This is the default
+ behavior.
+
--no-ff::
- Do not generate a merge commit if the merge resolved as
- a fast-forward, only update the branch pointer. This is
- the default behavior of git-merge.
-+
-With --no-ff Generate a merge commit even if the merge
-resolved as a fast-forward.
+ Create a merge commit even when the merge resolves as a
+ fast-forward.
+
+--ff-only::
+ Refuse to merge and exit with a non-zero status unless the
+ current `HEAD` is already up-to-date or the merge can be
+ resolved as a fast-forward.
--log[=<n>]::
--no-log::
@@ -49,11 +70,6 @@ merge.
With --no-squash perform the merge and commit the result. This
option can be used to override --squash.
---ff-only::
- Refuse to merge and exit with a non-zero status unless the
- current `HEAD` is already up-to-date or the merge can be
- resolved as a fast-forward.
-
-s <strategy>::
--strategy=<strategy>::
Use the given merge strategy; can be supplied more than
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 561cc9f..e3d8a83 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -130,8 +130,12 @@ The placeholders are:
- '%b': body
- '%B': raw body (unwrapped subject and body)
- '%N': commit notes
-- '%gD': reflog selector, e.g., `refs/stash@\{1\}`
-- '%gd': shortened reflog selector, e.g., `stash@\{1\}`
+- '%gD': reflog selector, e.g., `refs/stash@{1}`
+- '%gd': shortened reflog selector, e.g., `stash@{1}`
+- '%gn': reflog identity name
+- '%gN': reflog identity name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+- '%ge': reflog identity email
+- '%gE': reflog identity email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
- '%gs': reflog subject
- '%Cred': switch color to red
- '%Cgreen': switch color to green
@@ -151,7 +155,7 @@ insert an empty string unless we are traversing reflog entries (e.g., by
`git log -g`). The `%d` placeholder will use the "short" decoration
format if `--decorate` was not already provided on the command line.
-If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed
+If you add a `+` (plus sign) after '%' of a placeholder, a line-feed
is inserted immediately before the expansion if and only if the
placeholder expands to a non-empty string.
diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt
index 5dd6e5a..94a9d32 100644
--- a/Documentation/pull-fetch-param.txt
+++ b/Documentation/pull-fetch-param.txt
@@ -13,7 +13,7 @@ endif::git-pull[]
<refspec>::
The format of a <refspec> parameter is an optional plus
- `{plus}`, followed by the source ref <src>, followed
+ `+`, followed by the source ref <src>, followed
by a colon `:`, followed by the destination ref <dst>.
+
The remote ref that matches <src>
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 39e6207..84e34b1 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -117,27 +117,27 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
Pretend as if all the refs in `refs/heads` are listed
on the command line as '<commit>'. If '<pattern>' is given, limit
branches to ones matching given shell glob. If pattern lacks '?',
- '*', or '[', '/*' at the end is implied.
+ '{asterisk}', or '[', '/{asterisk}' at the end is implied.
--tags[=<pattern>]::
Pretend as if all the refs in `refs/tags` are listed
on the command line as '<commit>'. If '<pattern>' is given, limit
- tags to ones matching given shell glob. If pattern lacks '?', '*',
- or '[', '/*' at the end is implied.
+ tags to ones matching given shell glob. If pattern lacks '?', '{asterisk}',
+ or '[', '/{asterisk}' at the end is implied.
--remotes[=<pattern>]::
Pretend as if all the refs in `refs/remotes` are listed
on the command line as '<commit>'. If '<pattern>' is given, limit
remote-tracking branches to ones matching given shell glob.
- If pattern lacks '?', '*', or '[', '/*' at the end is implied.
+ If pattern lacks '?', '{asterisk}', or '[', '/{asterisk}' at the end is implied.
--glob=<glob-pattern>::
Pretend as if all the refs matching shell glob '<glob-pattern>'
are listed on the command line as '<commit>'. Leading 'refs/',
- is automatically prepended if missing. If pattern lacks '?', '*',
- or '[', '/*' at the end is implied.
+ is automatically prepended if missing. If pattern lacks '?', '{asterisk}',
+ or '[', '/{asterisk}' at the end is implied.
--ignore-missing::
@@ -198,7 +198,7 @@ excluded from the output.
+
For example, `--cherry-pick --right-only A...B` omits those
commits from `B` which are in `A` or are patch-equivalent to a commit in
-`A`. In other words, this lists the `{plus}` commits from `git cherry A B`.
+`A`. In other words, this lists the `+` commits from `git cherry A B`.
More precisely, `--cherry-pick --right-only --no-merges` gives the exact
list.
@@ -455,7 +455,7 @@ The effect of this is best shown by way of comparing to
`---------'
-----------------------------------------------------------------------
+
-Note the major differences in `N` and `P` over '\--full-history':
+Note the major differences in `N` and `P` over '--full-history':
+
--
* `N`'s parent list had `I` removed, because it is an ancestor of the
@@ -494,7 +494,7 @@ of course).
When we want to find out what commits in `M` are contaminated with the
bug introduced by `D` and need fixing, however, we might want to view
only the subset of 'D..M' that are actually descendants of `D`, i.e.
-excluding `C` and `K`. This is exactly what the '\--ancestry-path'
+excluding `C` and `K`. This is exactly what the '--ancestry-path'
option does. Applied to the 'D..M' range, it results in:
+
-----------------------------------------------------------------------
@@ -622,6 +622,7 @@ These options are mostly targeted for packing of git repositories.
--no-walk::
Only show the given revs, but do not traverse their ancestors.
+ This has no effect if a range is specified.
--do-walk::
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index b290b61..1725661 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -101,7 +101,7 @@ the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file.
'<rev>{tilde}<n>', e.g. 'master{tilde}3'::
A suffix '{tilde}<n>' to a revision parameter means the commit
- object that is the <n>th generation grand-parent of the named
+ object that is the <n>th generation ancestor of the named
commit object, following only the first parents. I.e. '<rev>{tilde}3' is
equivalent to '<rev>{caret}{caret}{caret}' which is equivalent to
'<rev>{caret}1{caret}1{caret}1'. See below for an illustration of
diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt
new file mode 100644
index 0000000..5747f44
--- /dev/null
+++ b/Documentation/sequencer.txt
@@ -0,0 +1,12 @@
+--continue::
+ Continue the operation in progress using the information in
+ '.git/sequencer'. Can be used to continue after resolving
+ conflicts in a failed cherry-pick or revert.
+
+--quit::
+ Forget about the current operation in progress. Can be used
+ to clear the sequencer state after a failed cherry-pick or
+ revert.
+
+--abort::
+ Cancel the operation and return to the pre-sequence state.
diff --git a/Documentation/technical/api-argv-array.txt b/Documentation/technical/api-argv-array.txt
new file mode 100644
index 0000000..1b7d8f1
--- /dev/null
+++ b/Documentation/technical/api-argv-array.txt
@@ -0,0 +1,51 @@
+argv-array API
+==============
+
+The argv-array API allows one to dynamically build and store
+NULL-terminated lists. An argv-array maintains the invariant that the
+`argv` member always points to a non-NULL array, and that the array is
+always NULL-terminated at the element pointed to by `argv[argc]`. This
+makes the result suitable for passing to functions expecting to receive
+argv from main(), or the link:api-run-command.html[run-command API].
+
+The link:api-string-list.html[string-list API] is similar, but cannot be
+used for these purposes; instead of storing a straight string pointer,
+it contains an item structure with a `util` field that is not compatible
+with the traditional argv interface.
+
+Each `argv_array` manages its own memory. Any strings pushed into the
+array are duplicated, and all memory is freed by argv_array_clear().
+
+Data Structures
+---------------
+
+`struct argv_array`::
+
+ A single array. This should be initialized by assignment from
+ `ARGV_ARRAY_INIT`, or by calling `argv_array_init`. The `argv`
+ member contains the actual array; the `argc` member contains the
+ number of elements in the array, not including the terminating
+ NULL.
+
+Functions
+---------
+
+`argv_array_init`::
+ Initialize an array. This is no different than assigning from
+ `ARGV_ARRAY_INIT`.
+
+`argv_array_push`::
+ Push a copy of a string onto the end of the array.
+
+`argv_array_pushl`::
+ Push a list of strings onto the end of the array. The arguments
+ should be a list of `const char *` strings, terminated by a NULL
+ argument.
+
+`argv_array_pushf`::
+ Format a string and push it onto the end of the array. This is a
+ convenience wrapper combining `strbuf_addf` and `argv_array_push`.
+
+`argv_array_clear`::
+ Free all memory associated with the array and return it to the
+ initial, empty state.
diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt
new file mode 100644
index 0000000..edf8dfb
--- /dev/null
+++ b/Documentation/technical/api-config.txt
@@ -0,0 +1,140 @@
+config API
+==========
+
+The config API gives callers a way to access git configuration files
+(and files which have the same syntax). See linkgit:git-config[1] for a
+discussion of the config file syntax.
+
+General Usage
+-------------
+
+Config files are parsed linearly, and each variable found is passed to a
+caller-provided callback function. The callback function is responsible
+for any actions to be taken on the config option, and is free to ignore
+some options. It is not uncommon for the configuration to be parsed
+several times during the run of a git program, with different callbacks
+picking out different variables useful to themselves.
+
+A config callback function takes three parameters:
+
+- the name of the parsed variable. This is in canonical "flat" form: the
+ section, subsection, and variable segments will be separated by dots,
+ and the section and variable segments will be all lowercase. E.g.,
+ `core.ignorecase`, `diff.SomeType.textconv`.
+
+- the value of the found variable, as a string. If the variable had no
+ value specified, the value will be NULL (typically this means it
+ should be interpreted as boolean true).
+
+- a void pointer passed in by the caller of the config API; this can
+ contain callback-specific data
+
+A config callback should return 0 for success, or -1 if the variable
+could not be parsed properly.
+
+Basic Config Querying
+---------------------
+
+Most programs will simply want to look up variables in all config files
+that git knows about, using the normal precedence rules. To do this,
+call `git_config` with a callback function and void data pointer.
+
+`git_config` will read all config sources in order of increasing
+priority. Thus a callback should typically overwrite previously-seen
+entries with new ones (e.g., if both the user-wide `~/.gitconfig` and
+repo-specific `.git/config` contain `color.ui`, the config machinery
+will first feed the user-wide one to the callback, and then the
+repo-specific one; by overwriting, the higher-priority repo-specific
+value is left at the end).
+
+The `git_config_with_options` function lets the caller examine config
+while adjusting some of the default behavior of `git_config`. It should
+almost never be used by "regular" git code that is looking up
+configuration variables. It is intended for advanced callers like
+`git-config`, which are intentionally tweaking the normal config-lookup
+process. It takes two extra parameters:
+
+`filename`::
+If this parameter is non-NULL, it specifies the name of a file to
+parse for configuration, rather than looking in the usual files. Regular
+`git_config` defaults to `NULL`.
+
+`respect_includes`::
+Specify whether include directives should be followed in parsed files.
+Regular `git_config` defaults to `1`.
+
+There is a special version of `git_config` called `git_config_early`.
+This version takes an additional parameter to specify the repository
+config, instead of having it looked up via `git_path`. This is useful
+early in a git program before the repository has been found. Unless
+you're working with early setup code, you probably don't want to use
+this.
+
+Reading Specific Files
+----------------------
+
+To read a specific file in git-config format, use
+`git_config_from_file`. This takes the same callback and data parameters
+as `git_config`.
+
+Value Parsing Helpers
+---------------------
+
+To aid in parsing string values, the config API provides callbacks with
+a number of helper functions, including:
+
+`git_config_int`::
+Parse the string to an integer, including unit factors. Dies on error;
+otherwise, returns the parsed result.
+
+`git_config_ulong`::
+Identical to `git_config_int`, but for unsigned longs.
+
+`git_config_bool`::
+Parse a string into a boolean value, respecting keywords like "true" and
+"false". Integer values are converted into true/false values (when they
+are non-zero or zero, respectively). Other values cause a die(). If
+parsing is successful, the return value is the result.
+
+`git_config_bool_or_int`::
+Same as `git_config_bool`, except that integers are returned as-is, and
+an `is_bool` flag is unset.
+
+`git_config_maybe_bool`::
+Same as `git_config_bool`, except that it returns -1 on error rather
+than dying.
+
+`git_config_string`::
+Allocates and copies the value string into the `dest` parameter; if no
+string is given, prints an error message and returns -1.
+
+`git_config_pathname`::
+Similar to `git_config_string`, but expands `~` or `~user` into the
+user's home directory when found at the beginning of the path.
+
+Include Directives
+------------------
+
+By default, the config parser does not respect include directives.
+However, a caller can use the special `git_config_include` wrapper
+callback to support them. To do so, you simply wrap your "real" callback
+function and data pointer in a `struct config_include_data`, and pass
+the wrapper to the regular config-reading functions. For example:
+
+-------------------------------------------
+int read_file_with_include(const char *file, config_fn_t fn, void *data)
+{
+ struct config_include_data inc = CONFIG_INCLUDE_INIT;
+ inc.fn = fn;
+ inc.data = data;
+ return git_config_from_file(git_config_include, file, &inc);
+}
+-------------------------------------------
+
+`git_config` respects includes automatically. The lower-level
+`git_config_from_file` does not.
+
+Writing Config Files
+--------------------
+
+TODO
diff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt
new file mode 100644
index 0000000..5977b58
--- /dev/null
+++ b/Documentation/technical/api-credentials.txt
@@ -0,0 +1,268 @@
+credentials API
+===============
+
+The credentials API provides an abstracted way of gathering username and
+password credentials from the user (even though credentials in the wider
+world can take many forms, in this document the word "credential" always
+refers to a username and password pair).
+
+This document describes two interfaces: the C API that the credential
+subsystem provides to the rest of git, and the protocol that git uses to
+communicate with system-specific "credential helpers". If you are
+writing git code that wants to look up or prompt for credentials, see
+the section "C API" below. If you want to write your own helper, see
+the section on "Credential Helpers" below.
+
+Typical setup
+-------------
+
+------------
++-----------------------+
+| git code (C) |--- to server requiring --->
+| | authentication
+|.......................|
+| C credential API |--- prompt ---> User
++-----------------------+
+ ^ |
+ | pipe |
+ | v
++-----------------------+
+| git credential helper |
++-----------------------+
+------------
+
+The git code (typically a remote-helper) will call the C API to obtain
+credential data like a login/password pair (credential_fill). The
+API will itself call a remote helper (e.g. "git credential-cache" or
+"git credential-store") that may retrieve credential data from a
+store. If the credential helper cannot find the information, the C API
+will prompt the user. Then, the caller of the API takes care of
+contacting the server, and does the actual authentication.
+
+C API
+-----
+
+The credential C API is meant to be called by git code which needs to
+acquire or store a credential. It is centered around an object
+representing a single credential and provides three basic operations:
+fill (acquire credentials by calling helpers and/or prompting the user),
+approve (mark a credential as successfully used so that it can be stored
+for later use), and reject (mark a credential as unsuccessful so that it
+can be erased from any persistent storage).
+
+Data Structures
+~~~~~~~~~~~~~~~
+
+`struct credential`::
+
+ This struct represents a single username/password combination
+ along with any associated context. All string fields should be
+ heap-allocated (or NULL if they are not known or not applicable).
+ The meaning of the individual context fields is the same as
+ their counterparts in the helper protocol; see the section below
+ for a description of each field.
++
+The `helpers` member of the struct is a `string_list` of helpers. Each
+string specifies an external helper which will be run, in order, to
+either acquire or store credentials. See the section on credential
+helpers below. This list is filled-in by the API functions
+according to the corresponding configuration variables before
+consulting helpers, so there usually is no need for a caller to
+modify the helpers field at all.
++
+This struct should always be initialized with `CREDENTIAL_INIT` or
+`credential_init`.
+
+
+Functions
+~~~~~~~~~
+
+`credential_init`::
+
+ Initialize a credential structure, setting all fields to empty.
+
+`credential_clear`::
+
+ Free any resources associated with the credential structure,
+ returning it to a pristine initialized state.
+
+`credential_fill`::
+
+ Instruct the credential subsystem to fill the username and
+ password fields of the passed credential struct by first
+ consulting helpers, then asking the user. After this function
+ returns, the username and password fields of the credential are
+ guaranteed to be non-NULL. If an error occurs, the function will
+ die().
+
+`credential_reject`::
+
+ Inform the credential subsystem that the provided credentials
+ have been rejected. This will cause the credential subsystem to
+ notify any helpers of the rejection (which allows them, for
+ example, to purge the invalid credentials from storage). It
+ will also free() the username and password fields of the
+ credential and set them to NULL (readying the credential for
+ another call to `credential_fill`). Any errors from helpers are
+ ignored.
+
+`credential_approve`::
+
+ Inform the credential subsystem that the provided credentials
+ were successfully used for authentication. This will cause the
+ credential subsystem to notify any helpers of the approval, so
+ that they may store the result to be used again. Any errors
+ from helpers are ignored.
+
+`credential_from_url`::
+
+ Parse a URL into broken-down credential fields.
+
+Example
+~~~~~~~
+
+The example below shows how the functions of the credential API could be
+used to login to a fictitious "foo" service on a remote host:
+
+-----------------------------------------------------------------------
+int foo_login(struct foo_connection *f)
+{
+ int status;
+ /*
+ * Create a credential with some context; we don't yet know the
+ * username or password.
+ */
+
+ struct credential c = CREDENTIAL_INIT;
+ c.protocol = xstrdup("foo");
+ c.host = xstrdup(f->hostname);
+
+ /*
+ * Fill in the username and password fields by contacting
+ * helpers and/or asking the user. The function will die if it
+ * fails.
+ */
+ credential_fill(&c);
+
+ /*
+ * Otherwise, we have a username and password. Try to use it.
+ */
+ status = send_foo_login(f, c.username, c.password);
+ switch (status) {
+ case FOO_OK:
+ /* It worked. Store the credential for later use. */
+ credential_accept(&c);
+ break;
+ case FOO_BAD_LOGIN:
+ /* Erase the credential from storage so we don't try it
+ * again. */
+ credential_reject(&c);
+ break;
+ default:
+ /*
+ * Some other error occured. We don't know if the
+ * credential is good or bad, so report nothing to the
+ * credential subsystem.
+ */
+ }
+
+ /* Free any associated resources. */
+ credential_clear(&c);
+
+ return status;
+}
+-----------------------------------------------------------------------
+
+
+Credential Helpers
+------------------
+
+Credential helpers are programs executed by git to fetch or save
+credentials from and to long-term storage (where "long-term" is simply
+longer than a single git process; e.g., credentials may be stored
+in-memory for a few minutes, or indefinitely on disk).
+
+Each helper is specified by a single string in the configuration
+variable `credential.helper` (and others, see linkgit:git-config[1]).
+The string is transformed by git into a command to be executed using
+these rules:
+
+ 1. If the helper string begins with "!", it is considered a shell
+ snippet, and everything after the "!" becomes the command.
+
+ 2. Otherwise, if the helper string begins with an absolute path, the
+ verbatim helper string becomes the command.
+
+ 3. Otherwise, the string "git credential-" is prepended to the helper
+ string, and the result becomes the command.
+
+The resulting command then has an "operation" argument appended to it
+(see below for details), and the result is executed by the shell.
+
+Here are some example specifications:
+
+----------------------------------------------------
+# run "git credential-foo"
+foo
+
+# same as above, but pass an argument to the helper
+foo --bar=baz
+
+# the arguments are parsed by the shell, so use shell
+# quoting if necessary
+foo --bar="whitespace arg"
+
+# you can also use an absolute path, which will not use the git wrapper
+/path/to/my/helper --with-arguments
+
+# or you can specify your own shell snippet
+!f() { echo "password=`cat $HOME/.secret`"; }; f
+----------------------------------------------------
+
+Generally speaking, rule (3) above is the simplest for users to specify.
+Authors of credential helpers should make an effort to assist their
+users by naming their program "git-credential-$NAME", and putting it in
+the $PATH or $GIT_EXEC_PATH during installation, which will allow a user
+to enable it with `git config credential.helper $NAME`.
+
+When a helper is executed, it will have one "operation" argument
+appended to its command line, which is one of:
+
+`get`::
+
+ Return a matching credential, if any exists.
+
+`store`::
+
+ Store the credential, if applicable to the helper.
+
+`erase`::
+
+ Remove a matching credential, if any, from the helper's storage.
+
+The details of the credential will be provided on the helper's stdin
+stream. The exact format is the same as the input/output format of the
+`git credential` plumbing command (see the section `INPUT/OUTPUT
+FORMAT` in linkgit:git-credential[7] for a detailed specification).
+
+For a `get` operation, the helper should produce a list of attributes
+on stdout in the same format. A helper is free to produce a subset, or
+even no values at all if it has nothing useful to provide. Any provided
+attributes will overwrite those already known about by git.
+
+For a `store` or `erase` operation, the helper's output is ignored.
+If it fails to perform the requested operation, it may complain to
+stderr to inform the user. If it does not support the requested
+operation (e.g., a read-only store), it should silently ignore the
+request.
+
+If a helper receives any other operation, it should silently ignore the
+request. This leaves room for future operations to be added (older
+helpers will just ignore the new requests).
+
+See also
+--------
+
+linkgit:gitcredentials[7]
+
+linkgit:git-config[5] (See configuration variables `credential.*`)
diff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt
index 9d97eaa..ce363b6 100644
--- a/Documentation/technical/api-gitattributes.txt
+++ b/Documentation/technical/api-gitattributes.txt
@@ -11,27 +11,15 @@ 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.
+ Pass the name 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. The name of the
+ attribute can be retrieved by calling `git_attr_name()`.
`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.
+ to `git_check_attr()` function, and receives the results.
Attribute Values
@@ -57,6 +45,19 @@ If none of the above returns true, `.value` member points at a string
value of the attribute for the path.
+Querying Specific Attributes
+----------------------------
+
+* 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_check_attr()` 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.
+
+
Example
-------
@@ -72,18 +73,18 @@ 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);
+ check[0].attr = git_attr("crlf");
+ check[1].attr = git_attr("ident");
}
------------
-. Call `git_checkattr()` with the prepared array of `struct git_attr_check`:
+. Call `git_check_attr()` with the prepared array of `struct git_attr_check`:
------------
const char *path;
setup_check();
- git_checkattr(path, ARRAY_SIZE(check), check);
+ git_check_attr(path, ARRAY_SIZE(check), check);
------------
. Act on `.value` member of the result, left in `check[]`:
@@ -108,4 +109,20 @@ static void setup_check(void)
}
------------
-(JC)
+
+Querying All Attributes
+-----------------------
+
+To get the values of all attributes associated with a file:
+
+* Call `git_all_attrs()`, which returns an array of `git_attr_check`
+ structures.
+
+* Iterate over the `git_attr_check` array to examine the attribute
+ names and values. The name of the attribute described by a
+ `git_attr_check` object can be retrieved via
+ `git_attr_name(check[i].attr)`. (Please note that no items will be
+ returned for unset attributes, so `ATTR_UNSET()` will return false
+ for all returned `git_array_check` objects.)
+
+* Free the `git_array_check` array.
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
index f6a4a36..3062389 100644
--- a/Documentation/technical/api-parse-options.txt
+++ b/Documentation/technical/api-parse-options.txt
@@ -21,7 +21,7 @@ that allow to change the behavior of a command.
* There are basically two forms of options:
'Short options' consist of one dash (`-`) and one alphanumeric
character.
- 'Long options' begin with two dashes (`\--`) and some
+ 'Long options' begin with two dashes (`--`) and some
alphanumeric characters.
* Options are case-sensitive.
@@ -31,7 +31,7 @@ The parse-options API allows:
* 'sticked' and 'separate form' of options with arguments.
`-oArg` is sticked, `-o Arg` is separate form.
- `\--option=Arg` is sticked, `\--option Arg` is separate form.
+ `--option=Arg` is sticked, `--option Arg` is separate form.
* Long options may be 'abbreviated', as long as the abbreviation
is unambiguous.
@@ -39,11 +39,12 @@ The parse-options API allows:
* Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
* Boolean long options can be 'negated' (or 'unset') by prepending
- `no-`, e.g. `\--no-abbrev` instead of `\--abbrev`.
+ `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
+ options that begin with `no-` can be 'negated' by removing it.
-* Options and non-option arguments can clearly be separated using the `\--`
- option, e.g. `-a -b \--option \-- \--this-is-a-file` indicates that
- `\--this-is-a-file` must not be processed as an option.
+* Options and non-option arguments can clearly be separated using the `--`
+ option, e.g. `-a -b --option -- --this-is-a-file` indicates that
+ `--this-is-a-file` must not be processed as an option.
Steps to parse options
----------------------
@@ -75,7 +76,7 @@ before the full parser, which in turn shows the full help message.
Flags are the bitwise-or of:
`PARSE_OPT_KEEP_DASHDASH`::
- Keep the `\--` that usually separates options from
+ Keep the `--` that usually separates options from
non-option arguments.
`PARSE_OPT_STOP_AT_NON_OPTION`::
@@ -113,31 +114,36 @@ say `static struct option builtin_add_options[]`.
There are some macros to easily define options:
`OPT__ABBREV(&int_var)`::
- Add `\--abbrev[=<n>]`.
+ Add `--abbrev[=<n>]`.
`OPT__COLOR(&int_var, description)`::
- Add `\--color[=<when>]` and `--no-color`.
+ Add `--color[=<when>]` and `--no-color`.
`OPT__DRY_RUN(&int_var, description)`::
- Add `-n, \--dry-run`.
+ Add `-n, --dry-run`.
`OPT__FORCE(&int_var, description)`::
- Add `-f, \--force`.
+ Add `-f, --force`.
`OPT__QUIET(&int_var, description)`::
- Add `-q, \--quiet`.
+ Add `-q, --quiet`.
`OPT__VERBOSE(&int_var, description)`::
- Add `-v, \--verbose`.
+ Add `-v, --verbose`.
`OPT_GROUP(description)`::
Start an option group. `description` is a short string that
describes the group or an empty string.
Start the description with an upper-case letter.
-`OPT_BOOLEAN(short, long, &int_var, description)`::
- Introduce a boolean option.
- `int_var` is incremented on each use.
+`OPT_BOOL(short, long, &int_var, description)`::
+ Introduce a boolean option. `int_var` is set to one with
+ `--option` and set to zero with `--no-option`.
+
+`OPT_COUNTUP(short, long, &int_var, description)`::
+ Introduce a count-up option.
+ `int_var` is incremented on each use of `--option`, and
+ reset to zero with `--no-option`.
`OPT_BIT(short, long, &int_var, description, mask)`::
Introduce a boolean option.
@@ -148,8 +154,9 @@ There are some macros to easily define options:
If used, `int_var` is bitwise-anded with the inverted `mask`.
`OPT_SET_INT(short, long, &int_var, description, integer)`::
- Introduce a boolean option.
- If used, set `int_var` to `integer`.
+ Introduce an integer option.
+ `int_var` is set to `integer` with `--option`, and
+ reset to zero with `--no-option`.
`OPT_SET_PTR(short, long, &ptr_var, description, ptr)`::
Introduce a boolean option.
@@ -198,16 +205,21 @@ There are some macros to easily define options:
"auto", set `int_var` to 1 if stdout is a tty or a pager,
0 otherwise.
+`OPT_NOOP_NOARG(short, long)`::
+ Introduce an option that has no effect and takes no arguments.
+ Use it to hide deprecated options that are still to be recognized
+ and ignored silently.
+
The last element of the array must be `OPT_END()`.
If not stated otherwise, interpret the arguments as follows:
* `short` is a character for the short option
- (e.g. `{apostrophe}e{apostrophe}` for `-e`, use `0` to omit),
+ (e.g. `'e'` for `-e`, use `0` to omit),
* `long` is a string for the long option
- (e.g. `"example"` for `\--example`, use `NULL` to omit),
+ (e.g. `"example"` for `--example`, use `NULL` to omit),
* `int_var` is an integer variable,
@@ -231,10 +243,10 @@ The function must be defined in this form:
The callback mechanism is as follows:
* Inside `func`, the only interesting member of the structure
- given by `opt` is the void pointer `opt\->value`.
- `\*opt\->value` will be the value that is saved into `var`, if you
+ given by `opt` is the void pointer `opt->value`.
+ `*opt->value` will be the value that is saved into `var`, if you
use `OPT_CALLBACK()`.
- For example, do `*(unsigned long *)opt\->value = 42;` to get 42
+ For example, do `*(unsigned long *)opt->value = 42;` to get 42
into an `unsigned long` variable.
* Return value `0` indicates success and non-zero return
diff --git a/Documentation/technical/api-revision-walking.txt b/Documentation/technical/api-revision-walking.txt
index 996da05..b7d0d9a 100644
--- a/Documentation/technical/api-revision-walking.txt
+++ b/Documentation/technical/api-revision-walking.txt
@@ -56,6 +56,11 @@ function.
returning a `struct commit *` each time you call it. The end of the
revision list is indicated by returning a NULL pointer.
+`reset_revision_walk`::
+
+ Reset the flags used by the revision walking api. You can use
+ this to do multiple sequencial revision walks.
+
Data structures
---------------
diff --git a/Documentation/technical/api-sha1-array.txt b/Documentation/technical/api-sha1-array.txt
new file mode 100644
index 0000000..4a4bae8
--- /dev/null
+++ b/Documentation/technical/api-sha1-array.txt
@@ -0,0 +1,79 @@
+sha1-array API
+==============
+
+The sha1-array API provides storage and manipulation of sets of SHA1
+identifiers. The emphasis is on storage and processing efficiency,
+making them suitable for large lists. Note that the ordering of items is
+not preserved over some operations.
+
+Data Structures
+---------------
+
+`struct sha1_array`::
+
+ A single array of SHA1 hashes. This should be initialized by
+ assignment from `SHA1_ARRAY_INIT`. The `sha1` member contains
+ the actual data. The `nr` member contains the number of items in
+ the set. The `alloc` and `sorted` members are used internally,
+ and should not be needed by API callers.
+
+Functions
+---------
+
+`sha1_array_append`::
+ Add an item to the set. The sha1 will be placed at the end of
+ the array (but note that some operations below may lose this
+ ordering).
+
+`sha1_array_sort`::
+ Sort the elements in the array.
+
+`sha1_array_lookup`::
+ Perform a binary search of the array for a specific sha1.
+ If found, returns the offset (in number of elements) of the
+ sha1. If not found, returns a negative integer. If the array is
+ not sorted, this function has the side effect of sorting it.
+
+`sha1_array_clear`::
+ Free all memory associated with the array and return it to the
+ initial, empty state.
+
+`sha1_array_for_each_unique`::
+ Efficiently iterate over each unique element of the list,
+ executing the callback function for each one. If the array is
+ not sorted, this function has the side effect of sorting it.
+
+Examples
+--------
+
+-----------------------------------------
+void print_callback(const unsigned char sha1[20],
+ void *data)
+{
+ printf("%s\n", sha1_to_hex(sha1));
+}
+
+void some_func(void)
+{
+ struct sha1_array hashes = SHA1_ARRAY_INIT;
+ unsigned char sha1[20];
+
+ /* Read objects into our set */
+ while (read_object_from_stdin(sha1))
+ sha1_array_append(&hashes, sha1);
+
+ /* Check if some objects are in our set */
+ while (read_object_from_stdin(sha1)) {
+ if (sha1_array_lookup(&hashes, sha1) >= 0)
+ printf("it's in there!\n");
+
+ /*
+ * Print the unique set of objects. We could also have
+ * avoided adding duplicate objects in the first place,
+ * but we would end up re-sorting the array repeatedly.
+ * Instead, this will sort once and then skip duplicates
+ * in linear time.
+ */
+ sha1_array_for_each_unique(&hashes, print_callback, NULL);
+}
+-----------------------------------------
diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt
index afe2759..95a8bf3 100644
--- a/Documentation/technical/api-strbuf.txt
+++ b/Documentation/technical/api-strbuf.txt
@@ -255,8 +255,24 @@ same behaviour as well.
`strbuf_getline`::
- Read a line from a FILE* pointer. The second argument specifies the line
+ Read a line from a FILE *, overwriting the existing contents
+ of the strbuf. The second argument specifies the line
terminator character, typically `'\n'`.
+ Reading stops after the terminator or at EOF. The terminator
+ is removed from the buffer before returning. Returns 0 unless
+ there was nothing left before EOF, in which case it returns `EOF`.
+
+`strbuf_getwholeline`::
+
+ Like `strbuf_getline`, but keeps the trailing terminator (if
+ any) in the buffer.
+
+`strbuf_getwholeline_fd`::
+
+ Like `strbuf_getwholeline`, but operates on a file descriptor.
+ It reads one character at a time, so it is very slow. Do not
+ use it unless you need the correct position in the file
+ descriptor.
`stripspace`::
diff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt
index 3f575bd..5a0c14f 100644
--- a/Documentation/technical/api-string-list.txt
+++ b/Documentation/technical/api-string-list.txt
@@ -29,6 +29,9 @@ member (you need this if you add things later) and you should set the
. Can sort an unsorted list using `sort_string_list`.
+. Can remove individual items of an unsorted list using
+ `unsorted_string_list_delete_item`.
+
. Finally it should free the list using `string_list_clear`.
Example:
@@ -80,7 +83,9 @@ Functions
Insert a new element to the string_list. The returned pointer can be
handy if you want to write something to the `util` pointer of the
- string_list_item containing the just added string.
+ string_list_item containing the just added string. If the given
+ string already exists the insertion will be skipped and the
+ pointer to the existing item returned.
+
Since this function uses xrealloc() (which die()s if it fails) if the
list needs to grow, it is safe not to check the pointer. I.e. you may
@@ -112,6 +117,13 @@ write `string_list_insert(...)->util = ...;`.
The above two functions need to look through all items, as opposed to their
counterpart for sorted lists, which performs a binary search.
+`unsorted_string_list_delete_item`::
+
+ Remove an item from a string_list. The `string` pointer of the items
+ will be freed in case the `strdup_strings` member of the string_list
+ is set. The third parameter controls if the `util` pointer of the
+ items should be freed or not.
+
Data structures
---------------
diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt
index 8930b3f..9d25b30 100644
--- a/Documentation/technical/index-format.txt
+++ b/Documentation/technical/index-format.txt
@@ -113,9 +113,22 @@ GIT index format
are encoded in 7-bit ASCII and the encoding cannot contain a NUL
byte (iow, this is a UNIX pathname).
+ (Version 4) In version 4, the entry path name is prefix-compressed
+ relative to the path name for the previous entry (the very first
+ entry is encoded as if the path name for the previous entry is an
+ empty string). At the beginning of an entry, an integer N in the
+ variable width encoding (the same encoding as the offset is encoded
+ for OFS_DELTA pack entries; see pack-format.txt) is stored, followed
+ by a NUL-terminated string S. Removing N bytes from the end of the
+ path name for the previous entry, and replacing it with the string S
+ yields the path name for this entry.
+
1-8 nul bytes as necessary to pad the entry to a multiple of eight bytes
while keeping the name NUL-terminated.
+ (Version 4) In version 4, the padding after the pathname does not
+ exist.
+
== Extensions
=== Cached tree
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index a7004c6..49cdc57 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -60,6 +60,13 @@ process on the server side over the Git protocol is this:
"0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
nc -v example.com 9418
+If the server refuses the request for some reasons, it could abort
+gracefully with an error message.
+
+----
+ error-line = PKT-LINE("ERR" SP explanation-text)
+----
+
SSH Transport
-------------
@@ -344,7 +351,7 @@ Then the server will start sending its packfile data.
A simple clone may look like this (with no 'have' lines):
----
- C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d\0multi_ack \
+ C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \
side-band-64k ofs-delta\n
C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
@@ -360,7 +367,7 @@ A simple clone may look like this (with no 'have' lines):
An incremental update (fetch) response might look like this:
----
- C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d\0multi_ack \
+ C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \
side-band-64k ofs-delta\n
C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
diff --git a/Documentation/technical/protocol-common.txt b/Documentation/technical/protocol-common.txt
index d30a1b9..fb7ff08 100644
--- a/Documentation/technical/protocol-common.txt
+++ b/Documentation/technical/protocol-common.txt
@@ -36,7 +36,7 @@ More specifically, they:
. They cannot have ASCII control characters (i.e. bytes whose
values are lower than \040, or \177 `DEL`), space, tilde `~`,
- caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
+ caret `^`, colon `:`, question-mark `?`, asterisk `*`,
or open bracket `[` anywhere.
. They cannot end with a slash `/` nor a dot `.`.
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index f13a846..02ed566 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -1582,7 +1582,7 @@ Checking the repository for corruption
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:
+time.
-------------------------------------------------
$ git fsck
@@ -1597,9 +1597,11 @@ 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.
+You will see informational messages on dangling objects. They are objects
+that still exist in the repository but are no longer referenced by any of
+your branches, and can (and will) be removed after a while with "gc".
+You can run `git fsck --no-dangling` to suppress these messages, and still
+view real errors.
[[recovering-lost-changes]]
Recovering lost changes
@@ -1609,7 +1611,7 @@ Recovering lost changes
Reflogs
^^^^^^^
-Say you modify a branch with `linkgit: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.
@@ -3295,15 +3297,12 @@ it is with linkgit:git-fsck[1]; this may be time-consuming.
Assume the output looks like this:
------------------------------------------------
-$ git fsck --full
+$ git fsck --full --no-dangling
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
@@ -4208,7 +4207,7 @@ commits one by one with the function `get_revision()`.
If you are interested in more details of the revision walking process,
just have a look at the first implementation of `cmd_log()`; call
-`git show v1.3.0{tilde}155^2{tilde}4` and scroll down to that function (note that you
+`git show v1.3.0~155^2~4` and scroll down to that function (note that you
no longer need to call `setup_pager()` directly).
Nowadays, `git log` is a builtin, which means that it is _contained_ in the
@@ -4271,9 +4270,9 @@ Two things are interesting here:
negative numbers in case of different errors--and 0 on success.
- the variable `sha1` in the function signature of `get_sha1()` is `unsigned
- char {asterisk}`, but is actually expected to be a pointer to `unsigned
+ char *`, but is actually expected to be a pointer to `unsigned
char[20]`. This variable will contain the 160-bit SHA-1 of the given
- commit. Note that whenever a SHA-1 is passed as `unsigned char {asterisk}`, it
+ commit. Note that whenever a SHA-1 is passed as `unsigned char *`, it
is the binary representation, as opposed to the ASCII representation in
hex characters, which is passed as `char *`.
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 0595495..fde74a6 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.6.5
+DEF_VER=v1.7.11.GIT
LF='
'
@@ -12,7 +12,7 @@ if test -f version
then
VN=$(cat version) || VN="$DEF_VER"
elif test -d .git -o -f .git &&
- VN=$(git describe --match "v[0-9]*" --abbrev=4 HEAD 2>/dev/null) &&
+ VN=$(git describe --match "v[0-9]*" --abbrev=7 HEAD 2>/dev/null) &&
case "$VN" in
*$LF*) (exit 1) ;;
v[0-9]*)
diff --git a/INSTALL b/INSTALL
index 16e45f1..28f34bd 100644
--- a/INSTALL
+++ b/INSTALL
@@ -25,6 +25,28 @@ set up install paths (via config.mak.autogen), so you can write instead
$ make all doc ;# as yourself
# make install install-doc install-html;# as root
+If you're willing to trade off (much) longer build time for a later
+faster git you can also do a profile feedback build with
+
+ $ make prefix=/usr PROFILE=BUILD all
+ # make prefix=/usr PROFILE=BUILD install
+
+This will run the complete test suite as training workload and then
+rebuild git with the generated profile feedback. This results in a git
+which is a few percent faster on CPU intensive workloads. This
+may be a good tradeoff for distribution packagers.
+
+Or if you just want to install a profile-optimized version of git into
+your home directory, you could run:
+
+ $ make PROFILE=BUILD install
+
+As a caveat: a profile-optimized build takes a *lot* longer since the
+git tree must be built twice, and in order for the profiling
+measurements to work properly, ccache must be disabled and the test
+suite has to be run using only a single CPU. In addition, the profile
+feedback build stage currently generates a lot of additional compiler
+warnings.
Issues of note:
@@ -70,7 +92,11 @@ Issues of note:
- "Perl" version 5.8 or later is needed to use some of the
features (e.g. preparing a partial commit using "git add -i/-p",
interacting with svn repositories with "git svn"). If you can
- live without these, use NO_PERL.
+ live without these, use NO_PERL. Note that recent releases of
+ Redhat/Fedora are reported to ship Perl binary package with some
+ core modules stripped away (see http://lwn.net/Articles/477234/),
+ so you might need to install additional packages other than Perl
+ itself, e.g. Time::HiRes.
- "openssl" library is used by git-imap-send to use IMAP over SSL.
If you don't need it, use NO_OPENSSL.
@@ -93,6 +119,21 @@ Issues of note:
history graphically, and in git-gui. If you don't want gitk or
git-gui, you can use NO_TCLTK.
+ - A gettext library is used by default for localizing Git. The
+ primary target is GNU libintl, but the Solaris gettext
+ implementation also works.
+
+ We need a gettext.h on the system for C code, gettext.sh (or
+ Solaris gettext(1)) for shell scripts, and libintl-perl for Perl
+ programs.
+
+ Set NO_GETTEXT to disable localization support and make Git only
+ use English. Under autoconf the configure script will do this
+ automatically if it can't find libintl on the system.
+
+ - Python version 2.6 or later is needed to use the git-p4
+ interface to Perforce.
+
- Some platform specific issues are dealt with Makefile rules,
but depending on your specific installation, you may not
have all the libraries/tools needed, or you may have
@@ -120,40 +161,15 @@ Issues of note:
makeinfo and docbook2X. Version 0.8.3 is known to work.
Building and installing the pdf file additionally requires
- dblatex. Version 0.2.7 with asciidoc >= 8.2.7 is known to work.
-
- The documentation is written for AsciiDoc 7, but by default
- uses some compatibility wrappers to work on AsciiDoc 8. If you have
- AsciiDoc 7, try "make ASCIIDOC7=YesPlease".
-
- Alternatively, pre-formatted documentation is available in
- "html" and "man" branches of the git repository itself. For
- example, you could:
-
- $ mkdir manual && cd manual
- $ git init
- $ git fetch-pack git://git.kernel.org/pub/scm/git/git.git man html |
- while read a b
- do
- echo $a >.git/$b
- done
- $ cp .git/refs/heads/man .git/refs/heads/master
- $ git checkout
-
- to checkout the pre-built man pages. Also in this repository:
-
- $ git checkout html
-
- would instead give you a copy of what you see at:
+ dblatex. Version >= 0.2.7 is known to work.
- http://www.kernel.org/pub/software/scm/git/docs/
+ All formats require at least asciidoc 8.4.1.
There are also "make quick-install-doc", "make quick-install-man"
and "make quick-install-html" which install preformatted man pages
- and html documentation.
- This does not require asciidoc/xmlto, but it only works from within
- a cloned checkout of git.git with these two extra branches, and will
- not work for the maintainer for obvious chicken-and-egg reasons.
+ and html documentation. To use these build targets, you need to
+ clone two separate git-htmldocs and git-manpages repositories next
+ to the clone of git itself.
It has been reported that docbook-xsl version 1.72 and 1.73 are
buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
diff --git a/Makefile b/Makefile
index 75b407c..169dda5 100644
--- a/Makefile
+++ b/Makefile
@@ -30,19 +30,42 @@ all::
# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in
# /foo/bar/include and /foo/bar/lib directories.
#
-# Define NO_CURL if you do not have libcurl installed. git-http-pull and
+# Define NO_CURL if you do not have libcurl installed. git-http-fetch and
# git-http-push are not built, and you cannot use http:// and https://
-# transports.
+# transports (neither smart nor dumb).
#
# Define CURLDIR=/foo/bar if your curl header and library files are in
# /foo/bar/include and /foo/bar/lib directories.
#
# Define NO_EXPAT if you do not have expat installed. git-http-push is
-# not built, and you cannot push using http:// and https:// transports.
+# not built, and you cannot push using http:// and https:// transports (dumb).
#
# Define EXPATDIR=/foo/bar if your expat header and library files are in
# /foo/bar/include and /foo/bar/lib directories.
#
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation,
+# plus libintl-perl at runtime.
+#
+# Define USE_GETTEXT_SCHEME and set it to 'fallthrough', if you don't trust
+# the installed gettext translation of the shell scripts output.
+#
+# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
+# trust the langinfo.h's nl_langinfo(CODESET) function to return the
+# current character set. GNU and Solaris have a nl_langinfo(CODESET),
+# FreeBSD can use either, but MinGW and some others need to use
+# libcharset.h's locale_charset() instead.
+#
+# Define CHARSET_LIB to you need to link with library other than -liconv to
+# use locale_charset() function. On some platforms this needs to set to
+# -lcharset
+#
+# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
+# need -lintl when linking.
+#
+# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
+# doesn't support GNU extensions like --check and --statistics
+#
# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
# it specifies.
#
@@ -57,8 +80,8 @@ all::
#
# Define NO_STRLCPY if you don't have strlcpy.
#
-# Define NO_STRTOUMAX if you don't have strtoumax in the C library.
-# If your compiler also does not support long long or does not have
+# Define NO_STRTOUMAX if you don't have both strtoimax and strtoumax in the
+# C library. If your compiler also does not support long long or does not have
# strtoull, define NO_STRTOULL.
#
# Define NO_SETENV if you don't have setenv in the C library.
@@ -115,6 +138,10 @@ all::
#
# Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin).
#
+# Define NEEDS_SSL_WITH_CURL if you need -lssl with -lcurl (Minix).
+#
+# Define NEEDS_IDN_WITH_CURL if you need -lidn when using -lcurl (Minix).
+#
# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
#
# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
@@ -131,6 +158,9 @@ all::
# Define NO_PREAD if you have a problem with pread() system call (e.g.
# cygwin1.dll before v1.5.22).
#
+# Define NO_THREAD_SAFE_PREAD if your pread() implementation is not
+# thread-safe. (e.g. compat/pread.c or cygwin)
+#
# Define NO_FAST_WORKING_DIRECTORY if accessing objects in pack files is
# generally faster on your platform than accessing the working directory.
#
@@ -139,6 +169,8 @@ all::
#
# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
#
+# Define NO_UNIX_SOCKETS if your system does not offer unix sockets.
+#
# Define NO_SOCKADDR_STORAGE if your platform does not have struct
# sockaddr_storage.
#
@@ -153,6 +185,9 @@ all::
# that tells runtime paths to dynamic libraries;
# "-Wl,-rpath=/path/lib" is used instead.
#
+# Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,
+# as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
+#
# Define USE_NSEC below if you want git to care about sub-second file mtimes
# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
@@ -171,8 +206,6 @@ all::
# Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks
# field that counts the on-disk footprint in 512-byte blocks.
#
-# Define ASCIIDOC7 if you want to format documentation with AsciiDoc 7
-#
# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
# (not v1.73 or v1.71).
#
@@ -215,11 +248,17 @@ all::
# Define NO_CROSS_DIRECTORY_HARDLINKS if you plan to distribute the installed
# programs as a tar, where bin/ and libexec/ might be on different file systems.
#
+# Define NO_INSTALL_HARDLINKS if you prefer to use either symbolic links or
+# copies to install built-in git commands e.g. git-cat-file.
+#
# Define USE_NED_ALLOCATOR if you want to replace the platforms default
# memory allocators with the nedmalloc allocator written by Niall Douglas.
#
# Define NO_REGEX if you have no or inferior regex support in your C library.
#
+# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
+# user.
+#
# Define GETTEXT_POISON if you are debugging the choice of strings marked
# for translation. In a GETTEXT_POISON build, you can turn all strings marked
# for translation into gibberish by setting the GIT_GETTEXT_POISON variable
@@ -243,14 +282,28 @@ all::
# DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
# DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
#
-# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option
-# and you want to avoid rebuilding objects when an unrelated header file
-# changes.
+# Define COMPUTE_HEADER_DEPENDENCIES to "yes" if you want dependencies on
+# header files to be automatically computed, to avoid rebuilding objects when
+# an unrelated header file changes. Define it to "no" to use the hard-coded
+# dependency rules. The default is "auto", which means to use computed header
+# dependencies if your compiler is detected to support it.
#
# Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
# dependency rules.
#
# Define NATIVE_CRLF if your platform uses CRLF for line endings.
+#
+# Define XDL_FAST_HASH to use an alternative line-hashing method in
+# the diff algorithm. It gives a nice speedup if your processor has
+# fast unaligned word loads. Does NOT work on big-endian systems!
+# Enabled by default on x86_64.
+#
+# Define GIT_USER_AGENT if you want to change how git identifies itself during
+# network interactions. The default is "git/$(GIT_VERSION)".
+#
+# Define DEFAULT_HELP_FORMAT to "man", "info" or "html"
+# (defaults to "man") if you want to have a different default when
+# "git help" is called without a parameter specifying the format.
GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -295,8 +348,10 @@ bindir = $(prefix)/$(bindir_relative)
mandir = share/man
infodir = share/info
gitexecdir = libexec/git-core
+mergetoolsdir = $(gitexecdir)/mergetools
sharedir = $(prefix)/share
gitwebdir = $(sharedir)/gitweb
+localedir = $(sharedir)/locale
template_dir = share/git-core/templates
htmldir = share/doc/git-doc
ETC_GITCONFIG = $(sysconfdir)/gitconfig
@@ -305,9 +360,9 @@ lib = lib
# DESTDIR=
pathsep = :
-export prefix bindir sharedir sysconfdir gitwebdir
+export prefix bindir sharedir sysconfdir gitwebdir localedir
-CC = gcc
+CC = cc
AR = ar
RM = rm -f
DIFF = diff
@@ -318,6 +373,7 @@ RPMBUILD = rpmbuild
TCL_PATH = tclsh
TCLTK_PATH = wish
XGETTEXT = xgettext
+MSGFMT = msgfmt
PTHREAD_LIBS = -lpthread
PTHREAD_CFLAGS =
GCOV = gcov
@@ -341,6 +397,12 @@ BUILTIN_OBJS =
BUILT_INS =
COMPAT_CFLAGS =
COMPAT_OBJS =
+XDIFF_H =
+XDIFF_OBJS =
+VCSSVN_H =
+VCSSVN_OBJS =
+VCSSVN_TEST_OBJS =
+MISC_H =
EXTRA_CPPFLAGS =
LIB_H =
LIB_OBJS =
@@ -395,6 +457,7 @@ SCRIPT_PERL += git-send-email.perl
SCRIPT_PERL += git-svn.perl
SCRIPT_PYTHON += git-remote-testgit.py
+SCRIPT_PYTHON += git-p4.py
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
@@ -409,14 +472,18 @@ EXTRA_PROGRAMS =
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS += $(EXTRA_PROGRAMS)
+PROGRAM_OBJS += credential-store.o
PROGRAM_OBJS += daemon.o
PROGRAM_OBJS += fast-import.o
+PROGRAM_OBJS += http-backend.o
PROGRAM_OBJS += imap-send.o
+PROGRAM_OBJS += sh-i18n--envsubst.o
PROGRAM_OBJS += shell.o
PROGRAM_OBJS += show-index.o
PROGRAM_OBJS += upload-pack.o
-PROGRAM_OBJS += http-backend.o
-PROGRAM_OBJS += sh-i18n--envsubst.o
+
+# Binary suffix, set to .exe for Windows builds
+X =
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
@@ -429,17 +496,17 @@ TEST_PROGRAMS_NEED_X += test-genrandom
TEST_PROGRAMS_NEED_X += test-index-version
TEST_PROGRAMS_NEED_X += test-line-buffer
TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-mergesort
TEST_PROGRAMS_NEED_X += test-mktemp
-TEST_PROGRAMS_NEED_X += test-obj-pool
TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-revision-walking
TEST_PROGRAMS_NEED_X += test-run-command
+TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
TEST_PROGRAMS_NEED_X += test-sha1
TEST_PROGRAMS_NEED_X += test-sigchain
-TEST_PROGRAMS_NEED_X += test-string-pool
TEST_PROGRAMS_NEED_X += test-subprocess
TEST_PROGRAMS_NEED_X += test-svn-fe
-TEST_PROGRAMS_NEED_X += test-treap
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
@@ -495,11 +562,43 @@ LIB_FILE=libgit.a
XDIFF_LIB=xdiff/lib.a
VCSSVN_LIB=vcs-svn/lib.a
+XDIFF_H += xdiff/xinclude.h
+XDIFF_H += xdiff/xmacros.h
+XDIFF_H += xdiff/xdiff.h
+XDIFF_H += xdiff/xtypes.h
+XDIFF_H += xdiff/xutils.h
+XDIFF_H += xdiff/xprepare.h
+XDIFF_H += xdiff/xdiffi.h
+XDIFF_H += xdiff/xemit.h
+
+VCSSVN_H += vcs-svn/line_buffer.h
+VCSSVN_H += vcs-svn/sliding_window.h
+VCSSVN_H += vcs-svn/repo_tree.h
+VCSSVN_H += vcs-svn/fast_export.h
+VCSSVN_H += vcs-svn/svndiff.h
+VCSSVN_H += vcs-svn/svndump.h
+
+MISC_H += bisect.h
+MISC_H += branch.h
+MISC_H += bundle.h
+MISC_H += common-cmds.h
+MISC_H += fetch-pack.h
+MISC_H += reachable.h
+MISC_H += send-pack.h
+MISC_H += shortlog.h
+MISC_H += tar.h
+MISC_H += thread-utils.h
+MISC_H += url.h
+MISC_H += walker.h
+MISC_H += wt-status.h
+
LIB_H += advice.h
LIB_H += archive.h
+LIB_H += argv-array.h
LIB_H += attr.h
LIB_H += blob.h
LIB_H += builtin.h
+LIB_H += bulk-checkin.h
LIB_H += cache.h
LIB_H += cache-tree.h
LIB_H += color.h
@@ -507,24 +606,32 @@ LIB_H += commit.h
LIB_H += compat/bswap.h
LIB_H += compat/cygwin.h
LIB_H += compat/mingw.h
+LIB_H += compat/obstack.h
+LIB_H += compat/terminal.h
+LIB_H += compat/win32/dirent.h
+LIB_H += compat/win32/poll.h
LIB_H += compat/win32/pthread.h
LIB_H += compat/win32/syslog.h
-LIB_H += compat/win32/sys/poll.h
-LIB_H += compat/win32/dirent.h
+LIB_H += connected.h
+LIB_H += convert.h
+LIB_H += credential.h
LIB_H += csum-file.h
LIB_H += decorate.h
LIB_H += delta.h
-LIB_H += diffcore.h
LIB_H += diff.h
+LIB_H += diffcore.h
LIB_H += dir.h
LIB_H += exec_cmd.h
+LIB_H += fmt-merge-msg.h
LIB_H += fsck.h
LIB_H += gettext.h
LIB_H += git-compat-util.h
+LIB_H += gpg-interface.h
LIB_H += graph.h
LIB_H += grep.h
LIB_H += hash.h
LIB_H += help.h
+LIB_H += kwset.h
LIB_H += levenshtein.h
LIB_H += list-objects.h
LIB_H += ll-merge.h
@@ -532,6 +639,7 @@ LIB_H += log-tree.h
LIB_H += mailmap.h
LIB_H += merge-file.h
LIB_H += merge-recursive.h
+LIB_H += mergesort.h
LIB_H += notes.h
LIB_H += notes-cache.h
LIB_H += notes-merge.h
@@ -543,6 +651,7 @@ LIB_H += parse-options.h
LIB_H += patch-ids.h
LIB_H += pkt-line.h
LIB_H += progress.h
+LIB_H += prompt.h
LIB_H += quote.h
LIB_H += reflog-walk.h
LIB_H += refs.h
@@ -551,20 +660,24 @@ LIB_H += rerere.h
LIB_H += resolve-undo.h
LIB_H += revision.h
LIB_H += run-command.h
+LIB_H += sequencer.h
LIB_H += sha1-array.h
LIB_H += sha1-lookup.h
LIB_H += sideband.h
LIB_H += sigchain.h
LIB_H += strbuf.h
+LIB_H += streaming.h
LIB_H += string-list.h
LIB_H += submodule.h
LIB_H += tag.h
+LIB_H += thread-utils.h
LIB_H += transport.h
LIB_H += tree.h
LIB_H += tree-walk.h
LIB_H += unpack-trees.h
LIB_H += userdiff.h
LIB_H += utf8.h
+LIB_H += varint.h
LIB_H += xdiff-interface.h
LIB_H += xdiff/xdiff.h
@@ -575,20 +688,27 @@ LIB_OBJS += alloc.o
LIB_OBJS += archive.o
LIB_OBJS += archive-tar.o
LIB_OBJS += archive-zip.o
+LIB_OBJS += argv-array.o
LIB_OBJS += attr.o
LIB_OBJS += base85.o
LIB_OBJS += bisect.o
LIB_OBJS += blob.o
LIB_OBJS += branch.o
+LIB_OBJS += bulk-checkin.o
LIB_OBJS += bundle.o
LIB_OBJS += cache-tree.o
LIB_OBJS += color.o
+LIB_OBJS += column.o
LIB_OBJS += combine-diff.o
LIB_OBJS += commit.o
+LIB_OBJS += compat/obstack.o
+LIB_OBJS += compat/terminal.o
LIB_OBJS += config.o
LIB_OBJS += connect.o
+LIB_OBJS += connected.o
LIB_OBJS += convert.o
LIB_OBJS += copy.o
+LIB_OBJS += credential.o
LIB_OBJS += csum-file.o
LIB_OBJS += ctype.o
LIB_OBJS += date.o
@@ -608,12 +728,15 @@ LIB_OBJS += entry.o
LIB_OBJS += environment.o
LIB_OBJS += exec_cmd.o
LIB_OBJS += fsck.o
+LIB_OBJS += gettext.o
+LIB_OBJS += gpg-interface.o
LIB_OBJS += graph.o
LIB_OBJS += grep.o
LIB_OBJS += hash.o
LIB_OBJS += help.o
LIB_OBJS += hex.o
LIB_OBJS += ident.o
+LIB_OBJS += kwset.o
LIB_OBJS += levenshtein.o
LIB_OBJS += list-objects.o
LIB_OBJS += ll-merge.o
@@ -623,6 +746,7 @@ LIB_OBJS += mailmap.o
LIB_OBJS += match-trees.o
LIB_OBJS += merge-file.o
LIB_OBJS += merge-recursive.o
+LIB_OBJS += mergesort.o
LIB_OBJS += name-hash.o
LIB_OBJS += notes.o
LIB_OBJS += notes-cache.o
@@ -634,6 +758,7 @@ LIB_OBJS += pack-revindex.o
LIB_OBJS += pack-write.o
LIB_OBJS += pager.o
LIB_OBJS += parse-options.o
+LIB_OBJS += parse-options-cb.o
LIB_OBJS += patch-delta.o
LIB_OBJS += patch-ids.o
LIB_OBJS += path.o
@@ -641,6 +766,7 @@ LIB_OBJS += pkt-line.o
LIB_OBJS += preload-index.o
LIB_OBJS += pretty.o
LIB_OBJS += progress.o
+LIB_OBJS += prompt.o
LIB_OBJS += quote.o
LIB_OBJS += reachable.o
LIB_OBJS += read-cache.o
@@ -652,6 +778,7 @@ LIB_OBJS += rerere.o
LIB_OBJS += resolve-undo.o
LIB_OBJS += revision.o
LIB_OBJS += run-command.o
+LIB_OBJS += sequencer.o
LIB_OBJS += server-info.o
LIB_OBJS += setup.o
LIB_OBJS += sha1-array.o
@@ -662,6 +789,7 @@ LIB_OBJS += shallow.o
LIB_OBJS += sideband.o
LIB_OBJS += sigchain.o
LIB_OBJS += strbuf.o
+LIB_OBJS += streaming.o
LIB_OBJS += string-list.o
LIB_OBJS += submodule.o
LIB_OBJS += symlinks.o
@@ -677,6 +805,8 @@ LIB_OBJS += url.o
LIB_OBJS += usage.o
LIB_OBJS += userdiff.o
LIB_OBJS += utf8.o
+LIB_OBJS += varint.o
+LIB_OBJS += version.o
LIB_OBJS += walker.o
LIB_OBJS += wrapper.o
LIB_OBJS += write_or_die.o
@@ -700,10 +830,12 @@ BUILTIN_OBJS += builtin/checkout-index.o
BUILTIN_OBJS += builtin/checkout.o
BUILTIN_OBJS += builtin/clean.o
BUILTIN_OBJS += builtin/clone.o
+BUILTIN_OBJS += builtin/column.o
BUILTIN_OBJS += builtin/commit-tree.o
BUILTIN_OBJS += builtin/commit.o
BUILTIN_OBJS += builtin/config.o
BUILTIN_OBJS += builtin/count-objects.o
+BUILTIN_OBJS += builtin/credential.o
BUILTIN_OBJS += builtin/describe.o
BUILTIN_OBJS += builtin/diff-files.o
BUILTIN_OBJS += builtin/diff-index.o
@@ -781,6 +913,8 @@ BUILTIN_OBJS += builtin/write-tree.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
EXTLIBS =
+GIT_USER_AGENT = git/$(GIT_VERSION)
+
#
# Platform specific tweaks
#
@@ -789,6 +923,9 @@ EXTLIBS =
# because maintaining the nesting to match is a pain. If
# we had "elif" things would have been much nicer...
+ifeq ($(uname_M),x86_64)
+ XDL_FAST_HASH = YesPlease
+endif
ifeq ($(uname_S),OSF1)
# Need this for u_short definitions et al
BASIC_CFLAGS += -D_OSF_SOURCE
@@ -800,11 +937,15 @@ ifeq ($(uname_S),Linux)
NO_STRLCPY = YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
+ LIBC_CONTAINS_LIBINTL = YesPlease
+ HAVE_DEV_TTY = YesPlease
endif
ifeq ($(uname_S),GNU/kFreeBSD)
NO_STRLCPY = YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
+ DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
+ LIBC_CONTAINS_LIBINTL = YesPlease
endif
ifeq ($(uname_S),UnixWare)
CC = cc
@@ -859,6 +1000,7 @@ ifeq ($(uname_S),Darwin)
endif
NO_MEMMEM = YesPlease
USE_ST_TIMESPEC = YesPlease
+ HAVE_DEV_TTY = YesPlease
endif
ifeq ($(uname_S),SunOS)
NEEDS_SOCKET = YesPlease
@@ -871,6 +1013,7 @@ ifeq ($(uname_S),SunOS)
NO_MKSTEMPS = YesPlease
NO_REGEX = YesPlease
NO_FNMATCH_CASEFOLD = YesPlease
+ NO_MSGFMT_EXTENDED_OPTIONS = YesPlease
ifeq ($(uname_R),5.6)
SOCKLEN_T = int
NO_HSTRERROR = YesPlease
@@ -919,6 +1062,7 @@ ifeq ($(uname_O),Cygwin)
NO_IPV6 = YesPlease
OLD_ICONV = UnfortunatelyYes
endif
+ NO_THREAD_SAFE_PREAD = YesPlease
NEEDS_LIBICONV = YesPlease
NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
NO_TRUSTABLE_FILEMODE = UnfortunatelyYes
@@ -994,6 +1138,7 @@ ifeq ($(uname_S),GNU)
NO_STRLCPY=YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
+ LIBC_CONTAINS_LIBINTL = YesPlease
endif
ifeq ($(uname_S),IRIX)
NO_SETENV = YesPlease
@@ -1070,8 +1215,10 @@ ifeq ($(uname_S),Windows)
NO_PREAD = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease
+ NO_SYS_POLL_H = YesPlease
NO_SYMLINK_HEAD = YesPlease
NO_IPV6 = YesPlease
+ NO_UNIX_SOCKETS = YesPlease
NO_SETENV = YesPlease
NO_UNSETENV = YesPlease
NO_STRCASESTR = YesPlease
@@ -1101,6 +1248,7 @@ ifeq ($(uname_S),Windows)
BLK_SHA1 = YesPlease
NO_POSIX_GOODIES = UnfortunatelyYes
NATIVE_CRLF = YesPlease
+ DEFAULT_HELP_FORMAT = html
CC = compat/vcbuild/scripts/clink.pl
AR = compat/vcbuild/scripts/lib.pl
@@ -1108,7 +1256,7 @@ ifeq ($(uname_S),Windows)
BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
COMPAT_OBJS = compat/msvc.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
- compat/win32/sys/poll.o compat/win32/dirent.o
+ compat/win32/poll.o compat/win32/dirent.o
COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
@@ -1124,8 +1272,6 @@ endif
X = .exe
endif
ifeq ($(uname_S),Interix)
- NO_SYS_POLL_H = YesPlease
- NO_INTTYPES_H = YesPlease
NO_INITGROUPS = YesPlease
NO_IPV6 = YesPlease
NO_MEMMEM = YesPlease
@@ -1136,18 +1282,38 @@ ifeq ($(uname_S),Interix)
ifeq ($(uname_R),3.5)
NO_INET_NTOP = YesPlease
NO_INET_PTON = YesPlease
+ NO_SOCKADDR_STORAGE = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
endif
ifeq ($(uname_R),5.2)
NO_INET_NTOP = YesPlease
NO_INET_PTON = YesPlease
+ NO_SOCKADDR_STORAGE = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
endif
endif
+ifeq ($(uname_S),Minix)
+ NO_IPV6 = YesPlease
+ NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
+ NO_NSEC = YesPlease
+ NEEDS_LIBGEN =
+ NEEDS_CRYPTO_WITH_SSL = YesPlease
+ NEEDS_IDN_WITH_CURL = YesPlease
+ NEEDS_SSL_WITH_CURL = YesPlease
+ NEEDS_RESOLV =
+ NO_HSTRERROR = YesPlease
+ NO_MMAP = YesPlease
+ NO_CURL =
+ NO_EXPAT =
+endif
ifneq (,$(findstring MINGW,$(uname_S)))
pathsep = ;
NO_PREAD = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease
+ NO_SYS_POLL_H = YesPlease
NO_SYMLINK_HEAD = YesPlease
+ NO_UNIX_SOCKETS = YesPlease
NO_SETENV = YesPlease
NO_UNSETENV = YesPlease
NO_STRCASESTR = YesPlease
@@ -1180,7 +1346,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
- compat/win32/sys/poll.o compat/win32/dirent.o
+ compat/win32/poll.o compat/win32/dirent.o
EXTLIBS += -lws2_32
PTHREAD_LIBS =
X = .exe
@@ -1192,6 +1358,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
EXTLIBS += /mingw/lib/libz.a
NO_R_TO_GCC_LINKER = YesPlease
INTERNAL_QSORT = YesPlease
+ HAVE_LIBCHARSET_H = YesPlease
else
NO_CURL = YesPlease
endif
@@ -1209,12 +1376,32 @@ endif
endif
ifdef CHECK_HEADER_DEPENDENCIES
-COMPUTE_HEADER_DEPENDENCIES =
+COMPUTE_HEADER_DEPENDENCIES = no
USE_COMPUTED_HEADER_DEPENDENCIES =
endif
-ifdef COMPUTE_HEADER_DEPENDENCIES
+ifndef COMPUTE_HEADER_DEPENDENCIES
+COMPUTE_HEADER_DEPENDENCIES = auto
+endif
+
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto)
+dep_check = $(shell $(CC) $(ALL_CFLAGS) \
+ -c -MF /dev/null -MMD -MP -x c /dev/null -o /dev/null 2>&1; \
+ echo $$?)
+ifeq ($(dep_check),0)
+override COMPUTE_HEADER_DEPENDENCIES = yes
+else
+override COMPUTE_HEADER_DEPENDENCIES = no
+endif
+endif
+
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease
+else
+ifneq ($(COMPUTE_HEADER_DEPENDENCIES),no)
+$(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \
+(not "$(COMPUTE_HEADER_DEPENDENCIES)"))
+endif
endif
ifdef SANE_TOOL_PATH
@@ -1286,6 +1473,16 @@ else
else
CURL_LIBCURL = -lcurl
endif
+ ifdef NEEDS_SSL_WITH_CURL
+ CURL_LIBCURL += -lssl
+ ifdef NEEDS_CRYPTO_WITH_SSL
+ CURL_LIBCURL += -lcrypto
+ endif
+ endif
+ ifdef NEEDS_IDN_WITH_CURL
+ CURL_LIBCURL += -lidn
+ endif
+
REMOTE_CURL_PRIMARY = git-remote-http$X
REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X
REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
@@ -1322,7 +1519,7 @@ ifndef NO_OPENSSL
OPENSSL_LINK =
endif
ifdef NEEDS_CRYPTO_WITH_SSL
- OPENSSL_LINK += -lcrypto
+ OPENSSL_LIBSSL += -lcrypto
endif
else
BASIC_CFLAGS += -DNO_OPENSSL
@@ -1350,6 +1547,11 @@ endif
ifdef NEEDS_LIBGEN
EXTLIBS += -lgen
endif
+ifndef NO_GETTEXT
+ifndef LIBC_CONTAINS_LIBINTL
+ EXTLIBS += -lintl
+endif
+endif
ifdef NEEDS_SOCKET
EXTLIBS += -lsocket
endif
@@ -1374,6 +1576,9 @@ endif
ifdef USE_ST_TIMESPEC
BASIC_CFLAGS += -DUSE_ST_TIMESPEC
endif
+ifdef NO_NORETURN
+ BASIC_CFLAGS += -DNO_NORETURN
+endif
ifdef NO_NSEC
BASIC_CFLAGS += -DNO_NSEC
endif
@@ -1389,9 +1594,12 @@ ifdef NO_SYMLINK_HEAD
BASIC_CFLAGS += -DNO_SYMLINK_HEAD
endif
ifdef GETTEXT_POISON
- LIB_OBJS += gettext.o
BASIC_CFLAGS += -DGETTEXT_POISON
endif
+ifdef NO_GETTEXT
+ BASIC_CFLAGS += -DNO_GETTEXT
+ USE_GETTEXT_SCHEME ?= fallthrough
+endif
ifdef NO_STRCASESTR
COMPAT_CFLAGS += -DNO_STRCASESTR
COMPAT_OBJS += compat/strcasestr.o
@@ -1402,7 +1610,7 @@ ifdef NO_STRLCPY
endif
ifdef NO_STRTOUMAX
COMPAT_CFLAGS += -DNO_STRTOUMAX
- COMPAT_OBJS += compat/strtoumax.o
+ COMPAT_OBJS += compat/strtoumax.o compat/strtoimax.o
endif
ifdef NO_STRTOULL
COMPAT_CFLAGS += -DNO_STRTOULL
@@ -1464,6 +1672,10 @@ endif
ifdef NO_PREAD
COMPAT_CFLAGS += -DNO_PREAD
COMPAT_OBJS += compat/pread.o
+ NO_THREAD_SAFE_PREAD = YesPlease
+endif
+ifdef NO_THREAD_SAFE_PREAD
+ BASIC_CFLAGS += -DNO_THREAD_SAFE_PREAD
endif
ifdef NO_FAST_WORKING_DIRECTORY
BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY
@@ -1492,6 +1704,12 @@ ifdef NO_INET_PTON
LIB_OBJS += compat/inet_pton.o
BASIC_CFLAGS += -DNO_INET_PTON
endif
+ifndef NO_UNIX_SOCKETS
+ LIB_OBJS += unix-socket.o
+ LIB_H += unix-socket.h
+ PROGRAM_OBJS += credential-cache.o
+ PROGRAM_OBJS += credential-cache--daemon.o
+endif
ifdef NO_ICONV
BASIC_CFLAGS += -DNO_ICONV
@@ -1554,6 +1772,15 @@ ifdef HAVE_PATHS_H
BASIC_CFLAGS += -DHAVE_PATHS_H
endif
+ifdef HAVE_LIBCHARSET_H
+ BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
+ EXTLIBS += $(CHARSET_LIB)
+endif
+
+ifdef HAVE_DEV_TTY
+ BASIC_CFLAGS += -DHAVE_DEV_TTY
+endif
+
ifdef DIR_HAS_BSD_GROUP_SEMANTICS
COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
endif
@@ -1574,6 +1801,14 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
export GIT_TEST_CMP_USE_COPIED_CONTEXT
endif
+ifndef NO_MSGFMT_EXTENDED_OPTIONS
+ MSGFMT += --check --statistics
+endif
+
+ifneq (,$(XDL_FAST_HASH))
+ BASIC_CFLAGS += -DXDL_FAST_HASH
+endif
+
ifeq ($(TCLTK_PATH),)
NO_TCLTK=NoThanks
endif
@@ -1604,6 +1839,7 @@ ifndef V
QUIET_GEN = @echo ' ' GEN $@;
QUIET_LNCP = @echo ' ' LN/CP $@;
QUIET_XGETTEXT = @echo ' ' XGETTEXT $@;
+ QUIET_MSGFMT = @echo ' ' MSGFMT $@;
QUIET_GCOV = @echo ' ' GCOV $@;
QUIET_SP = @echo ' ' SP $<;
QUIET_SUBDIR0 = +@subdir=
@@ -1615,8 +1851,28 @@ ifndef V
endif
endif
-ifdef ASCIIDOC7
- export ASCIIDOC7
+ifdef NO_INSTALL_HARDLINKS
+ export NO_INSTALL_HARDLINKS
+endif
+
+### profile feedback build
+#
+
+# Can adjust this to be a global directory if you want to do extended
+# data gathering
+PROFILE_DIR := $(CURDIR)
+
+ifeq ("$(PROFILE)","GEN")
+ CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1
+ EXTLIBS += -lgcov
+ export CCACHE_DISABLE=t
+ V=1
+else
+ifneq ("$(PROFILE)","")
+ CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1
+ export CCACHE_DISABLE=t
+ V=1
+endif
endif
# Shell quote (do not use $(call) to accommodate ancient setups);
@@ -1630,6 +1886,7 @@ bindir_SQ = $(subst ','\'',$(bindir))
bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
mandir_SQ = $(subst ','\'',$(mandir))
infodir_SQ = $(subst ','\'',$(infodir))
+localedir_SQ = $(subst ','\'',$(localedir))
gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
template_dir_SQ = $(subst ','\'',$(template_dir))
htmldir_SQ = $(subst ','\'',$(htmldir))
@@ -1664,6 +1921,22 @@ DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ))
BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
endif
+ifdef SHELL_PATH
+SHELL_PATH_CQ = "$(subst ",\",$(subst \,\\,$(SHELL_PATH)))"
+SHELL_PATH_CQ_SQ = $(subst ','\'',$(SHELL_PATH_CQ))
+
+BASIC_CFLAGS += -DSHELL_PATH='$(SHELL_PATH_CQ_SQ)'
+endif
+
+GIT_USER_AGENT_SQ = $(subst ','\'',$(GIT_USER_AGENT))
+GIT_USER_AGENT_CQ = "$(subst ",\",$(subst \,\\,$(GIT_USER_AGENT)))"
+GIT_USER_AGENT_CQ_SQ = $(subst ','\'',$(GIT_USER_AGENT_CQ))
+BASIC_CFLAGS += -DGIT_USER_AGENT='$(GIT_USER_AGENT_CQ_SQ)'
+
+ifdef DEFAULT_HELP_FORMAT
+BASIC_CFLAGS += -DDEFAULT_HELP_FORMAT='"$(DEFAULT_HELP_FORMAT)"'
+endif
+
ALL_CFLAGS += $(BASIC_CFLAGS)
ALL_LDFLAGS += $(BASIC_LDFLAGS)
@@ -1674,7 +1947,17 @@ export DIFF TAR INSTALL DESTDIR SHELL_PATH
SHELL = $(SHELL_PATH)
-all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
+all:: shell_compatibility_test
+
+ifeq "$(PROFILE)" "BUILD"
+ifeq ($(filter all,$(MAKECMDGOALS)),all)
+all:: profile-clean
+ $(MAKE) PROFILE=GEN all
+ $(MAKE) PROFILE=GEN -j1 test
+endif
+endif
+
+all:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
ifneq (,$X)
$(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
endif
@@ -1685,7 +1968,7 @@ ifndef NO_TCLTK
$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
endif
ifndef NO_PERL
- $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+ $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
endif
ifndef NO_PYTHON
$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
@@ -1701,7 +1984,7 @@ strip: $(PROGRAMS) git$X
$(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
git.o: common-cmds.h
-git.sp git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \
+git.sp git.s git.o: EXTRA_CPPFLAGS = \
'-DGIT_HTML_PATH="$(htmldir_SQ)"' \
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
'-DGIT_INFO_PATH="$(infodir_SQ)"'
@@ -1718,6 +2001,9 @@ builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
'-DGIT_INFO_PATH="$(infodir_SQ)"'
+version.sp version.s version.o: EXTRA_CPPFLAGS = \
+ '-DGIT_VERSION="$(GIT_VERSION)"'
+
$(BUILT_INS): git$X
$(QUIET_BUILT_IN)$(RM) $@ && \
ln git$X $@ 2>/dev/null || \
@@ -1735,7 +2021,10 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
-e 's|@@DIFF@@|$(DIFF_SQ)|' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+ -e 's|@@GIT_USER_AGENT@@|$(GIT_USER_AGENT_SQ)|g' \
+ -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
+ -e 's/@@USE_GETTEXT_SCHEME@@/$(USE_GETTEXT_SCHEME)/g' \
-e $(BROKEN_PATH_FIX) \
$@.sh >$@+
endef
@@ -1826,7 +2115,7 @@ configure: configure.ac
$(RM) $<+
# These can record GIT_VERSION
-git.o git.spec \
+version.o git.spec \
$(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
: GIT-VERSION-FILE
@@ -1837,20 +2126,32 @@ GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
ifndef NO_CURL
GIT_OBJS += http.o http-walker.o remote-curl.o
endif
-XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
- xdiff/xmerge.o xdiff/xpatience.o
-VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \
- vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o
-VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \
- test-line-buffer.o test-treap.o
+
+XDIFF_OBJS += xdiff/xdiffi.o
+XDIFF_OBJS += xdiff/xprepare.o
+XDIFF_OBJS += xdiff/xutils.o
+XDIFF_OBJS += xdiff/xemit.o
+XDIFF_OBJS += xdiff/xmerge.o
+XDIFF_OBJS += xdiff/xpatience.o
+XDIFF_OBJS += xdiff/xhistogram.o
+
+VCSSVN_OBJS += vcs-svn/line_buffer.o
+VCSSVN_OBJS += vcs-svn/sliding_window.o
+VCSSVN_OBJS += vcs-svn/repo_tree.o
+VCSSVN_OBJS += vcs-svn/fast_export.o
+VCSSVN_OBJS += vcs-svn/svndiff.o
+VCSSVN_OBJS += vcs-svn/svndump.o
+
+VCSSVN_TEST_OBJS += test-line-buffer.o
+
OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
-ifdef COMPUTE_HEADER_DEPENDENCIES
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
$(dep_dirs):
- mkdir -p $@
+ @mkdir -p $@
missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs))
dep_file = $(dir $@).depend/$(notdir $@).d
@@ -1861,7 +2162,7 @@ Please unset CHECK_HEADER_DEPENDENCIES and try again)
endif
endif
-ifndef COMPUTE_HEADER_DEPENDENCIES
+ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
ifndef CHECK_HEADER_DEPENDENCIES
dep_dirs =
missing_dep_dirs =
@@ -1951,26 +2252,20 @@ builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o tra
builtin/bundle.o bundle.o transport.o: bundle.h
builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
-builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h
+builtin/index-pack.o builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
builtin/send-pack.o transport.o: send-pack.h
builtin/log.o builtin/shortlog.o: shortlog.h
builtin/prune.o builtin/reflog.o reachable.o: reachable.h
builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
builtin/tar-tree.o archive-tar.o: tar.h
-connect.o transport.o http-backend.o: url.h
+connect.o transport.o url.o http-backend.o: url.h
+builtin/branch.o builtin/commit.o builtin/tag.o column.o help.o pager.o: column.h
http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
-xdiff-interface.o $(XDIFF_OBJS): \
- xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
- xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
-
-$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \
- vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \
- vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \
- vcs-svn/svndump.h
+xdiff-interface.o $(XDIFF_OBJS): $(XDIFF_H)
-test-svn-fe.o: vcs-svn/svndump.h
+$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) $(VCSSVN_H)
endif
exec_cmd.sp exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
@@ -1987,8 +2282,8 @@ config.sp config.s config.o: EXTRA_CPPFLAGS = \
attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \
-DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
-http.sp http.s http.o: EXTRA_CPPFLAGS = \
- -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
+gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \
+ -DGIT_LOCALE_PATH='"$(localedir_SQ)"'
ifdef NO_EXPAT
http-walker.sp http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT
@@ -2037,6 +2332,8 @@ $(XDIFF_LIB): $(XDIFF_OBJS)
$(VCSSVN_LIB): $(VCSSVN_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
+export DEFAULT_EDITOR DEFAULT_PAGER
+
doc:
$(MAKE) -C Documentation all
@@ -2060,32 +2357,57 @@ XGETTEXT_FLAGS = \
XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
--keyword=_ --keyword=N_ --keyword="Q_:1,2"
XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
-LOCALIZED_C := $(C_OBJ:o=c)
+XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
+LOCALIZED_C := $(C_OBJ:o=c) $(LIB_H) $(XDIFF_H) $(VCSSVN_H) $(MISC_H)
LOCALIZED_SH := $(SCRIPT_SH)
+LOCALIZED_PERL := $(SCRIPT_PERL)
+
+ifdef XGETTEXT_INCLUDE_TESTS
+LOCALIZED_C += t/t0200/test.c
+LOCALIZED_SH += t/t0200/test.sh
+LOCALIZED_PERL += t/t0200/test.perl
+endif
po/git.pot: $(LOCALIZED_C)
$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C)
$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \
$(LOCALIZED_SH)
+ $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_PERL) \
+ $(LOCALIZED_PERL)
mv $@+ $@
pot: po/git.pot
+POFILES := $(wildcard po/*.po)
+MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES))
+
+ifndef NO_GETTEXT
+all:: $(MOFILES)
+endif
+
+po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
+ $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
+
+FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
+ $(FIND) . \( -name .git -type d -prune \) \
+ -o \( -name '*.[hcS]' -type f -print \) )
+
$(ETAGS_TARGET): FORCE
$(RM) $(ETAGS_TARGET)
- $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET)
+ $(FIND_SOURCE_FILES) | xargs etags -a -o $(ETAGS_TARGET)
tags: FORCE
$(RM) tags
- $(FIND) . -name '*.[hcS]' -print | xargs ctags -a
+ $(FIND_SOURCE_FILES) | xargs ctags -a
cscope:
$(RM) cscope*
- $(FIND) . -name '*.[hcS]' -print | xargs cscope -b
+ $(FIND_SOURCE_FILES) | xargs cscope -b
### Detect prefix changes
TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
- $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
+ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
+ $(localedir_SQ):$(USE_GETTEXT_SCHEME)
GIT-CFLAGS: FORCE
@FLAGS='$(TRACK_CFLAGS)'; \
@@ -2116,13 +2438,30 @@ GIT-BUILD-OPTIONS: FORCE
@echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+ @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ifdef GIT_TEST_OPTS
+ @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
+endif
ifdef GIT_TEST_CMP
@echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@
endif
ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
endif
+ @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
@echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
+ifdef GIT_PERF_REPEAT_COUNT
+ @echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@
+endif
+ifdef GIT_PERF_REPO
+ @echo GIT_PERF_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPO)))'\' >>$@
+endif
+ifdef GIT_PERF_LARGE_REPO
+ @echo GIT_PERF_LARGE_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_LARGE_REPO)))'\' >>$@
+endif
+ifdef GIT_PERF_MAKE_OPTS
+ @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@
+endif
### Detect Tck/Tk interpreter path changes
ifndef NO_TCLTK
@@ -2158,6 +2497,11 @@ export NO_SVN_TESTS
test: all
$(MAKE) -C t/ all
+perf: all
+ $(MAKE) -C t/perf/ all
+
+.PHONY: test perf
+
test-ctype$X: ctype.o
test-date$X: date.o ctype.o
@@ -2166,9 +2510,7 @@ test-delta$X: diff-delta.o patch-delta.o
test-line-buffer$X: vcs-svn/lib.a
-test-parse-options$X: parse-options.o
-
-test-string-pool$X: vcs-svn/lib.a
+test-parse-options$X: parse-options.o parse-options-cb.o
test-svn-fe$X: vcs-svn/lib.a
@@ -2219,6 +2561,13 @@ endif
gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
export gitexec_instdir
+ifneq ($(filter /%,$(firstword $(mergetoolsdir))),)
+mergetools_instdir = $(mergetoolsdir)
+else
+mergetools_instdir = $(prefix)/$(mergetoolsdir)
+endif
+mergetools_instdir_SQ = $(subst ','\'',$(mergetools_instdir))
+
install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
install: all
@@ -2228,6 +2577,13 @@ install: all
$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
+ $(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
+ifndef NO_GETTEXT
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(localedir_SQ)'
+ (cd po/build/locale && $(TAR) cf - .) | \
+ (cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -)
+endif
ifndef NO_PERL
$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
$(MAKE) -C gitweb install
@@ -2248,19 +2604,21 @@ endif
{ test "$$bindir/" = "$$execdir/" || \
for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \
$(RM) "$$execdir/$$p" && \
- test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
+ test -z "$(NO_INSTALL_HARDLINKS)$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \
cp "$$bindir/$$p" "$$execdir/$$p" || exit; \
done; \
} && \
for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \
$(RM) "$$bindir/$$p" && \
+ test -z "$(NO_INSTALL_HARDLINKS)" && \
ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
cp "$$bindir/git$X" "$$bindir/$$p" || exit; \
done && \
for p in $(BUILT_INS); do \
$(RM) "$$execdir/$$p" && \
+ test -z "$(NO_INSTALL_HARDLINKS)" && \
ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
@@ -2268,6 +2626,7 @@ endif
remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \
for p in $$remote_curl_aliases; do \
$(RM) "$$execdir/$$p" && \
+ test -z "$(NO_INSTALL_HARDLINKS)" && \
ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \
@@ -2355,15 +2714,19 @@ dist-doc:
distclean: clean
$(RM) configure
- $(RM) po/git.pot
-clean:
+profile-clean:
+ $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
+ $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
+
+clean: profile-clean
$(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
$(RM) $(TEST_PROGRAMS)
$(RM) -r bin-wrappers
$(RM) -r $(dep_dirs)
+ $(RM) -r po/build/
$(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
$(RM) -r autom4te.cache
$(RM) config.log config.mak.autogen config.mak.append config.status config.cache
@@ -2386,7 +2749,7 @@ ifndef NO_TCLTK
endif
$(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
-.PHONY: all install clean strip
+.PHONY: all install profile-clean clean strip
.PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
.PHONY: FORCE cscope
@@ -2495,3 +2858,4 @@ cover_db: coverage-report
cover_db_html: cover_db
cover -report html -outputdir cover_db_html cover_db
+
diff --git a/README b/README
index 67cfeb2..d2690ec 100644
--- a/README
+++ b/README
@@ -42,10 +42,12 @@ including full documentation and Git related tools.
The user discussion and development of Git take place on the Git
mailing list -- everyone is welcome to post bug reports, feature
-requests, comments and patches to git@vger.kernel.org. To subscribe
-to the list, send an email with just "subscribe git" in the body to
-majordomo@vger.kernel.org. The mailing list archives are available at
-http://marc.theaimsgroup.com/?l=git and other archival sites.
+requests, comments and patches to git@vger.kernel.org (read
+Documentation/SubmittingPatches for instructions on patch submission).
+To subscribe to the list, send an email with just "subscribe git" in
+the body to majordomo@vger.kernel.org. The mailing list archives are
+available at http://marc.theaimsgroup.com/?l=git and other archival
+sites.
The messages titled "A note from the maintainer", "What's in
git.git (stable)" and "What's cooking in git.git (topics)" and
diff --git a/RelNotes b/RelNotes
index fb9320b..19bb2eb 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.6.6.txt \ No newline at end of file
+Documentation/RelNotes/1.7.12.txt \ No newline at end of file
diff --git a/abspath.c b/abspath.c
index f9494c4..f04ac18 100644
--- a/abspath.c
+++ b/abspath.c
@@ -40,7 +40,7 @@ const char *real_path(const char *path)
while (depth--) {
if (!is_directory(buf)) {
- char *last_slash = strrchr(buf, '/');
+ char *last_slash = find_last_dir_sep(buf);
if (last_slash) {
*last_slash = '\0';
last_elem = xstrdup(last_slash + 1);
@@ -65,7 +65,7 @@ const char *real_path(const char *path)
if (len + strlen(last_elem) + 2 > PATH_MAX)
die ("Too long path name: '%s/%s'",
buf, last_elem);
- if (len && buf[len-1] != '/')
+ if (len && !is_dir_sep(buf[len-1]))
buf[len++] = '/';
strcpy(buf + len, last_elem);
free(last_elem);
@@ -139,3 +139,31 @@ const char *absolute_path(const char *path)
}
return buf;
}
+
+/*
+ * Unlike prefix_path, this should be used if the named file does
+ * not have to interact with index entry; i.e. name of a random file
+ * on the filesystem.
+ */
+const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
+{
+ static char path[PATH_MAX];
+#ifndef WIN32
+ if (!pfx_len || is_absolute_path(arg))
+ return arg;
+ memcpy(path, pfx, pfx_len);
+ strcpy(path + pfx_len, arg);
+#else
+ char *p;
+ /* don't add prefix to absolute paths, but still replace '\' by '/' */
+ if (is_absolute_path(arg))
+ pfx_len = 0;
+ else if (pfx_len)
+ memcpy(path, pfx, pfx_len);
+ strcpy(path + pfx_len, arg);
+ for (p = path + pfx_len; *p; p++)
+ if (*p == '\\')
+ *p = '/';
+#endif
+ return path;
+}
diff --git a/advice.c b/advice.c
index 0be4b5f..a492eea 100644
--- a/advice.c
+++ b/advice.c
@@ -1,6 +1,9 @@
#include "cache.h"
int advice_push_nonfastforward = 1;
+int advice_push_non_ff_current = 1;
+int advice_push_non_ff_default = 1;
+int advice_push_non_ff_matching = 1;
int advice_status_hints = 1;
int advice_commit_before_merge = 1;
int advice_resolve_conflict = 1;
@@ -12,6 +15,9 @@ static struct {
int *preference;
} advice_config[] = {
{ "pushnonfastforward", &advice_push_nonfastforward },
+ { "pushnonffcurrent", &advice_push_non_ff_current },
+ { "pushnonffdefault", &advice_push_non_ff_default },
+ { "pushnonffmatching", &advice_push_non_ff_matching },
{ "statushints", &advice_status_hints },
{ "commitbeforemerge", &advice_commit_before_merge },
{ "resolveconflict", &advice_resolve_conflict },
@@ -19,6 +25,25 @@ static struct {
{ "detachedhead", &advice_detached_head },
};
+void advise(const char *advice, ...)
+{
+ struct strbuf buf = STRBUF_INIT;
+ va_list params;
+ const char *cp, *np;
+
+ va_start(params, advice);
+ strbuf_addf(&buf, advice, params);
+ va_end(params);
+
+ for (cp = buf.buf; *cp; cp = np) {
+ np = strchrnul(cp, '\n');
+ fprintf(stderr, _("hint: %.*s\n"), (int)(np - cp), cp);
+ if (*np)
+ np++;
+ }
+ strbuf_release(&buf);
+}
+
int git_default_advice_config(const char *var, const char *value)
{
const char *k = skip_prefix(var, "advice.");
@@ -34,16 +59,37 @@ int git_default_advice_config(const char *var, const char *value)
return 0;
}
-void NORETURN die_resolve_conflict(const char *me)
+int error_resolve_conflict(const char *me)
{
+ error("'%s' is not possible because you have unmerged files.", me);
if (advice_resolve_conflict)
/*
* Message used both when 'git commit' fails and when
* other commands doing a merge do.
*/
- die("'%s' is not possible because you have unmerged files.\n"
- "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n"
- "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me);
- else
- die("'%s' is not possible because you have unmerged files.", me);
+ advise(_("Fix them up in the work tree,\n"
+ "and then use 'git add/rm <file>' as\n"
+ "appropriate to mark resolution and make a commit,\n"
+ "or use 'git commit -a'."));
+ return -1;
+}
+
+void NORETURN die_resolve_conflict(const char *me)
+{
+ error_resolve_conflict(me);
+ die("Exiting because of an unresolved conflict.");
+}
+
+void detach_advice(const char *new_name)
+{
+ const char fmt[] =
+ "Note: checking out '%s'.\n\n"
+ "You are in 'detached HEAD' state. You can look around, make experimental\n"
+ "changes and commit them, and you can discard any commits you make in this\n"
+ "state without impacting any branches by performing another checkout.\n\n"
+ "If you want to create a new branch to retain commits you create, you may\n"
+ "do so (now or later) by using -b with the checkout command again. Example:\n\n"
+ " git checkout -b new_branch_name\n\n";
+
+ fprintf(stderr, fmt, new_name);
}
diff --git a/advice.h b/advice.h
index 3244ebb..f3cdbbf 100644
--- a/advice.h
+++ b/advice.h
@@ -4,6 +4,9 @@
#include "git-compat-util.h"
extern int advice_push_nonfastforward;
+extern int advice_push_non_ff_current;
+extern int advice_push_non_ff_default;
+extern int advice_push_non_ff_matching;
extern int advice_status_hints;
extern int advice_commit_before_merge;
extern int advice_resolve_conflict;
@@ -11,7 +14,9 @@ extern int advice_implicit_identity;
extern int advice_detached_head;
int git_default_advice_config(const char *var, const char *value);
-
+void advise(const char *advice, ...);
+int error_resolve_conflict(const char *me);
extern void NORETURN die_resolve_conflict(const char *me);
+void detach_advice(const char *new_name);
#endif /* ADVICE_H */
diff --git a/archive-tar.c b/archive-tar.c
index cee06ce..0ba3f25 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -4,6 +4,8 @@
#include "cache.h"
#include "tar.h"
#include "archive.h"
+#include "streaming.h"
+#include "run-command.h"
#define RECORDSIZE (512)
#define BLOCKSIZE (RECORDSIZE * 20)
@@ -13,6 +15,9 @@ static unsigned long offset;
static int tar_umask = 002;
+static int write_tar_filter_archive(const struct archiver *ar,
+ struct archiver_args *args);
+
/* writes out the whole block, but only if it is full */
static void write_if_needed(void)
{
@@ -26,10 +31,9 @@ static void write_if_needed(void)
* queues up writes, so that all our write(2) calls write exactly one
* full block; pads writes to RECORDSIZE
*/
-static void write_blocked(const void *data, unsigned long size)
+static void do_write_blocked(const void *data, unsigned long size)
{
const char *buf = data;
- unsigned long tail;
if (offset) {
unsigned long chunk = BLOCKSIZE - offset;
@@ -50,6 +54,11 @@ static void write_blocked(const void *data, unsigned long size)
memcpy(block + offset, buf, size);
offset += size;
}
+}
+
+static void finish_record(void)
+{
+ unsigned long tail;
tail = offset % RECORDSIZE;
if (tail) {
memset(block + offset, 0, RECORDSIZE - tail);
@@ -58,6 +67,12 @@ static void write_blocked(const void *data, unsigned long size)
write_if_needed();
}
+static void write_blocked(const void *data, unsigned long size)
+{
+ do_write_blocked(data, size);
+ finish_record();
+}
+
/*
* The end of tar archives is marked by 2*512 nul bytes and after that
* follows the rest of the block (if any).
@@ -74,6 +89,33 @@ static void write_trailer(void)
}
/*
+ * queues up writes, so that all our write(2) calls write exactly one
+ * full block; pads writes to RECORDSIZE
+ */
+static int stream_blocked(const unsigned char *sha1)
+{
+ struct git_istream *st;
+ enum object_type type;
+ unsigned long sz;
+ char buf[BLOCKSIZE];
+ ssize_t readlen;
+
+ st = open_istream(sha1, &type, &sz, NULL);
+ if (!st)
+ return error("cannot stream blob %s", sha1_to_hex(sha1));
+ for (;;) {
+ readlen = read_istream(st, buf, sizeof(buf));
+ if (readlen <= 0)
+ break;
+ do_write_blocked(buf, readlen);
+ }
+ close_istream(st);
+ if (!readlen)
+ finish_record();
+ return readlen;
+}
+
+/*
* pax extended header records have the format "%u %s=%s\n". %u contains
* the size of the whole string (including the %u), the first %s is the
* keyword, the second one is the value. This function constructs such a
@@ -97,13 +139,13 @@ static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
static unsigned int ustar_header_chksum(const struct ustar_header *header)
{
- char *p = (char *)header;
+ const unsigned char *p = (const unsigned char *)header;
unsigned int chksum = 0;
- while (p < header->chksum)
+ while (p < (const unsigned char *)header->chksum)
chksum += *p++;
chksum += sizeof(header->chksum) * ' ';
p += sizeof(header->chksum);
- while (p < (char *)header + sizeof(struct ustar_header))
+ while (p < (const unsigned char *)header + sizeof(struct ustar_header))
chksum += *p++;
return chksum;
}
@@ -119,56 +161,101 @@ static size_t get_path_prefix(const char *path, size_t pathlen, size_t maxlen)
return i;
}
+static void prepare_header(struct archiver_args *args,
+ struct ustar_header *header,
+ unsigned int mode, unsigned long size)
+{
+ sprintf(header->mode, "%07o", mode & 07777);
+ sprintf(header->size, "%011lo", S_ISREG(mode) ? size : 0);
+ sprintf(header->mtime, "%011lo", (unsigned long) args->time);
+
+ sprintf(header->uid, "%07o", 0);
+ sprintf(header->gid, "%07o", 0);
+ strlcpy(header->uname, "root", sizeof(header->uname));
+ strlcpy(header->gname, "root", sizeof(header->gname));
+ sprintf(header->devmajor, "%07o", 0);
+ sprintf(header->devminor, "%07o", 0);
+
+ memcpy(header->magic, "ustar", 6);
+ memcpy(header->version, "00", 2);
+
+ sprintf(header->chksum, "%07o", ustar_header_chksum(header));
+}
+
+static int write_extended_header(struct archiver_args *args,
+ const unsigned char *sha1,
+ const void *buffer, unsigned long size)
+{
+ struct ustar_header header;
+ unsigned int mode;
+ memset(&header, 0, sizeof(header));
+ *header.typeflag = TYPEFLAG_EXT_HEADER;
+ mode = 0100666;
+ sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+ prepare_header(args, &header, mode, size);
+ write_blocked(&header, sizeof(header));
+ write_blocked(buffer, size);
+ return 0;
+}
+
static int write_tar_entry(struct archiver_args *args,
- const unsigned char *sha1, const char *path, size_t pathlen,
- unsigned int mode, void *buffer, unsigned long size)
+ const unsigned char *sha1,
+ const char *path, size_t pathlen,
+ unsigned int mode)
{
struct ustar_header header;
struct strbuf ext_header = STRBUF_INIT;
+ unsigned int old_mode = mode;
+ unsigned long size;
+ void *buffer;
int err = 0;
memset(&header, 0, sizeof(header));
- if (!sha1) {
- *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
- mode = 0100666;
- strcpy(header.name, "pax_global_header");
- } else if (!path) {
- *header.typeflag = TYPEFLAG_EXT_HEADER;
- mode = 0100666;
- sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+ if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
+ *header.typeflag = TYPEFLAG_DIR;
+ mode = (mode | 0777) & ~tar_umask;
+ } else if (S_ISLNK(mode)) {
+ *header.typeflag = TYPEFLAG_LNK;
+ mode |= 0777;
+ } else if (S_ISREG(mode)) {
+ *header.typeflag = TYPEFLAG_REG;
+ mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
} else {
- if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
- *header.typeflag = TYPEFLAG_DIR;
- mode = (mode | 0777) & ~tar_umask;
- } else if (S_ISLNK(mode)) {
- *header.typeflag = TYPEFLAG_LNK;
- mode |= 0777;
- } else if (S_ISREG(mode)) {
- *header.typeflag = TYPEFLAG_REG;
- mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
+ return error("unsupported file mode: 0%o (SHA1: %s)",
+ mode, sha1_to_hex(sha1));
+ }
+ if (pathlen > sizeof(header.name)) {
+ size_t plen = get_path_prefix(path, pathlen,
+ sizeof(header.prefix));
+ size_t rest = pathlen - plen - 1;
+ if (plen > 0 && rest <= sizeof(header.name)) {
+ memcpy(header.prefix, path, plen);
+ memcpy(header.name, path + plen + 1, rest);
} else {
- return error("unsupported file mode: 0%o (SHA1: %s)",
- mode, sha1_to_hex(sha1));
+ sprintf(header.name, "%s.data",
+ sha1_to_hex(sha1));
+ strbuf_append_ext_header(&ext_header, "path",
+ path, pathlen);
}
- if (pathlen > sizeof(header.name)) {
- size_t plen = get_path_prefix(path, pathlen,
- sizeof(header.prefix));
- size_t rest = pathlen - plen - 1;
- if (plen > 0 && rest <= sizeof(header.name)) {
- memcpy(header.prefix, path, plen);
- memcpy(header.name, path + plen + 1, rest);
- } else {
- sprintf(header.name, "%s.data",
- sha1_to_hex(sha1));
- strbuf_append_ext_header(&ext_header, "path",
- path, pathlen);
- }
- } else
- memcpy(header.name, path, pathlen);
+ } else
+ memcpy(header.name, path, pathlen);
+
+ if (S_ISREG(mode) && !args->convert &&
+ sha1_object_info(sha1, &size) == OBJ_BLOB &&
+ size > big_file_threshold)
+ buffer = NULL;
+ else if (S_ISLNK(mode) || S_ISREG(mode)) {
+ enum object_type type;
+ buffer = sha1_file_to_archive(args, path, sha1, old_mode, &type, &size);
+ if (!buffer)
+ return error("cannot read %s", sha1_to_hex(sha1));
+ } else {
+ buffer = NULL;
+ size = 0;
}
- if (S_ISLNK(mode) && buffer) {
+ if (S_ISLNK(mode)) {
if (size > sizeof(header.linkname)) {
sprintf(header.linkname, "see %s.paxheader",
sha1_to_hex(sha1));
@@ -178,32 +265,25 @@ static int write_tar_entry(struct archiver_args *args,
memcpy(header.linkname, buffer, size);
}
- sprintf(header.mode, "%07o", mode & 07777);
- sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
- sprintf(header.mtime, "%011lo", (unsigned long) args->time);
-
- sprintf(header.uid, "%07o", 0);
- sprintf(header.gid, "%07o", 0);
- strlcpy(header.uname, "root", sizeof(header.uname));
- strlcpy(header.gname, "root", sizeof(header.gname));
- sprintf(header.devmajor, "%07o", 0);
- sprintf(header.devminor, "%07o", 0);
-
- memcpy(header.magic, "ustar", 6);
- memcpy(header.version, "00", 2);
-
- sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
+ prepare_header(args, &header, mode, size);
if (ext_header.len > 0) {
- err = write_tar_entry(args, sha1, NULL, 0, 0, ext_header.buf,
- ext_header.len);
- if (err)
+ err = write_extended_header(args, sha1, ext_header.buf,
+ ext_header.len);
+ if (err) {
+ free(buffer);
return err;
+ }
}
strbuf_release(&ext_header);
write_blocked(&header, sizeof(header));
- if (S_ISREG(mode) && buffer && size > 0)
- write_blocked(buffer, size);
+ if (S_ISREG(mode) && size > 0) {
+ if (buffer)
+ write_blocked(buffer, size);
+ else
+ err = stream_blocked(sha1);
+ }
+ free(buffer);
return err;
}
@@ -211,15 +291,83 @@ static int write_global_extended_header(struct archiver_args *args)
{
const unsigned char *sha1 = args->commit_sha1;
struct strbuf ext_header = STRBUF_INIT;
- int err;
+ struct ustar_header header;
+ unsigned int mode;
+ int err = 0;
strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
- err = write_tar_entry(args, NULL, NULL, 0, 0, ext_header.buf,
- ext_header.len);
+ memset(&header, 0, sizeof(header));
+ *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
+ mode = 0100666;
+ strcpy(header.name, "pax_global_header");
+ prepare_header(args, &header, mode, ext_header.len);
+ write_blocked(&header, sizeof(header));
+ write_blocked(ext_header.buf, ext_header.len);
strbuf_release(&ext_header);
return err;
}
+static struct archiver **tar_filters;
+static int nr_tar_filters;
+static int alloc_tar_filters;
+
+static struct archiver *find_tar_filter(const char *name, int len)
+{
+ int i;
+ for (i = 0; i < nr_tar_filters; i++) {
+ struct archiver *ar = tar_filters[i];
+ if (!strncmp(ar->name, name, len) && !ar->name[len])
+ return ar;
+ }
+ return NULL;
+}
+
+static int tar_filter_config(const char *var, const char *value, void *data)
+{
+ struct archiver *ar;
+ const char *dot;
+ const char *name;
+ const char *type;
+ int namelen;
+
+ if (prefixcmp(var, "tar."))
+ return 0;
+ dot = strrchr(var, '.');
+ if (dot == var + 9)
+ return 0;
+
+ name = var + 4;
+ namelen = dot - name;
+ type = dot + 1;
+
+ ar = find_tar_filter(name, namelen);
+ if (!ar) {
+ ar = xcalloc(1, sizeof(*ar));
+ ar->name = xmemdupz(name, namelen);
+ ar->write_archive = write_tar_filter_archive;
+ ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS;
+ ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters);
+ tar_filters[nr_tar_filters++] = ar;
+ }
+
+ if (!strcmp(type, "command")) {
+ if (!value)
+ return config_error_nonbool(var);
+ free(ar->data);
+ ar->data = xstrdup(value);
+ return 0;
+ }
+ if (!strcmp(type, "remote")) {
+ if (git_config_bool(var, value))
+ ar->flags |= ARCHIVER_REMOTE;
+ else
+ ar->flags &= ~ARCHIVER_REMOTE;
+ return 0;
+ }
+
+ return 0;
+}
+
static int git_tar_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "tar.umask")) {
@@ -231,15 +379,15 @@ static int git_tar_config(const char *var, const char *value, void *cb)
}
return 0;
}
- return git_default_config(var, value, cb);
+
+ return tar_filter_config(var, value, cb);
}
-int write_tar_archive(struct archiver_args *args)
+static int write_tar_archive(const struct archiver *ar,
+ struct archiver_args *args)
{
int err = 0;
- git_config(git_tar_config, NULL);
-
if (args->commit_sha1)
err = write_global_extended_header(args);
if (!err)
@@ -248,3 +396,65 @@ int write_tar_archive(struct archiver_args *args)
write_trailer();
return err;
}
+
+static int write_tar_filter_archive(const struct archiver *ar,
+ struct archiver_args *args)
+{
+ struct strbuf cmd = STRBUF_INIT;
+ struct child_process filter;
+ const char *argv[2];
+ int r;
+
+ if (!ar->data)
+ die("BUG: tar-filter archiver called with no filter defined");
+
+ strbuf_addstr(&cmd, ar->data);
+ if (args->compression_level >= 0)
+ strbuf_addf(&cmd, " -%d", args->compression_level);
+
+ memset(&filter, 0, sizeof(filter));
+ argv[0] = cmd.buf;
+ argv[1] = NULL;
+ filter.argv = argv;
+ filter.use_shell = 1;
+ filter.in = -1;
+
+ if (start_command(&filter) < 0)
+ die_errno("unable to start '%s' filter", argv[0]);
+ close(1);
+ if (dup2(filter.in, 1) < 0)
+ die_errno("unable to redirect descriptor");
+ close(filter.in);
+
+ r = write_tar_archive(ar, args);
+
+ close(1);
+ if (finish_command(&filter) != 0)
+ die("'%s' filter reported error", argv[0]);
+
+ strbuf_release(&cmd);
+ return r;
+}
+
+static struct archiver tar_archiver = {
+ "tar",
+ write_tar_archive,
+ ARCHIVER_REMOTE
+};
+
+void init_tar_archiver(void)
+{
+ int i;
+ register_archiver(&tar_archiver);
+
+ tar_filter_config("tar.tgz.command", "gzip -cn", NULL);
+ tar_filter_config("tar.tgz.remote", "true", NULL);
+ tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL);
+ tar_filter_config("tar.tar.gz.remote", "true", NULL);
+ git_config(git_tar_config, NULL);
+ for (i = 0; i < nr_tar_filters; i++) {
+ /* omit any filters that never had a command configured */
+ if (tar_filters[i]->data)
+ register_archiver(tar_filters[i]);
+ }
+}
diff --git a/archive-zip.c b/archive-zip.c
index 72d55a5..f5af81f 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -3,6 +3,7 @@
*/
#include "cache.h"
#include "archive.h"
+#include "streaming.h"
static int zip_date;
static int zip_time;
@@ -15,6 +16,7 @@ static unsigned int zip_dir_offset;
static unsigned int zip_dir_entries;
#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024)
+#define ZIP_STREAM (8)
struct zip_local_header {
unsigned char magic[4];
@@ -31,6 +33,14 @@ struct zip_local_header {
unsigned char _end[1];
};
+struct zip_data_desc {
+ unsigned char magic[4];
+ unsigned char crc32[4];
+ unsigned char compressed_size[4];
+ unsigned char size[4];
+ unsigned char _end[1];
+};
+
struct zip_dir_header {
unsigned char magic[4];
unsigned char creator_version[2];
@@ -70,6 +80,7 @@ struct zip_dir_trailer {
* we're interested in.
*/
#define ZIP_LOCAL_HEADER_SIZE offsetof(struct zip_local_header, _end)
+#define ZIP_DATA_DESC_SIZE offsetof(struct zip_data_desc, _end)
#define ZIP_DIR_HEADER_SIZE offsetof(struct zip_dir_header, _end)
#define ZIP_DIR_TRAILER_SIZE offsetof(struct zip_dir_trailer, _end)
@@ -120,20 +131,59 @@ static void *zlib_deflate(void *data, unsigned long size,
return buffer;
}
+static void write_zip_data_desc(unsigned long size,
+ unsigned long compressed_size,
+ unsigned long crc)
+{
+ struct zip_data_desc trailer;
+
+ copy_le32(trailer.magic, 0x08074b50);
+ copy_le32(trailer.crc32, crc);
+ copy_le32(trailer.compressed_size, compressed_size);
+ copy_le32(trailer.size, size);
+ write_or_die(1, &trailer, ZIP_DATA_DESC_SIZE);
+}
+
+static void set_zip_dir_data_desc(struct zip_dir_header *header,
+ unsigned long size,
+ unsigned long compressed_size,
+ unsigned long crc)
+{
+ copy_le32(header->crc32, crc);
+ copy_le32(header->compressed_size, compressed_size);
+ copy_le32(header->size, size);
+}
+
+static void set_zip_header_data_desc(struct zip_local_header *header,
+ unsigned long size,
+ unsigned long compressed_size,
+ unsigned long crc)
+{
+ copy_le32(header->crc32, crc);
+ copy_le32(header->compressed_size, compressed_size);
+ copy_le32(header->size, size);
+}
+
+#define STREAM_BUFFER_SIZE (1024 * 16)
+
static int write_zip_entry(struct archiver_args *args,
- const unsigned char *sha1, const char *path, size_t pathlen,
- unsigned int mode, void *buffer, unsigned long size)
+ const unsigned char *sha1,
+ const char *path, size_t pathlen,
+ unsigned int mode)
{
struct zip_local_header header;
struct zip_dir_header dirent;
unsigned long attr2;
unsigned long compressed_size;
- unsigned long uncompressed_size;
unsigned long crc;
unsigned long direntsize;
int method;
unsigned char *out;
void *deflated = NULL;
+ void *buffer;
+ struct git_istream *stream = NULL;
+ unsigned long flags = 0;
+ unsigned long size;
crc = crc32(0, NULL, 0);
@@ -146,24 +196,43 @@ static int write_zip_entry(struct archiver_args *args,
method = 0;
attr2 = 16;
out = NULL;
- uncompressed_size = 0;
+ size = 0;
compressed_size = 0;
+ buffer = NULL;
+ size = 0;
} else if (S_ISREG(mode) || S_ISLNK(mode)) {
+ enum object_type type = sha1_object_info(sha1, &size);
+
method = 0;
attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
(mode & 0111) ? ((mode) << 16) : 0;
- if (S_ISREG(mode) && args->compression_level != 0)
+ if (S_ISREG(mode) && args->compression_level != 0 && size > 0)
method = 8;
- crc = crc32(crc, buffer, size);
- out = buffer;
- uncompressed_size = size;
compressed_size = size;
+
+ if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert &&
+ size > big_file_threshold) {
+ stream = open_istream(sha1, &type, &size, NULL);
+ if (!stream)
+ return error("cannot stream blob %s",
+ sha1_to_hex(sha1));
+ flags |= ZIP_STREAM;
+ out = buffer = NULL;
+ } else {
+ buffer = sha1_file_to_archive(args, path, sha1, mode,
+ &type, &size);
+ if (!buffer)
+ return error("cannot read %s",
+ sha1_to_hex(sha1));
+ crc = crc32(crc, buffer, size);
+ out = buffer;
+ }
} else {
return error("unsupported file mode: 0%o (SHA1: %s)", mode,
sha1_to_hex(sha1));
}
- if (method == 8) {
+ if (buffer && method == 8) {
deflated = zlib_deflate(buffer, size, args->compression_level,
&compressed_size);
if (deflated && compressed_size - 6 < size) {
@@ -188,13 +257,11 @@ static int write_zip_entry(struct archiver_args *args,
copy_le16(dirent.creator_version,
S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0);
copy_le16(dirent.version, 10);
- copy_le16(dirent.flags, 0);
+ copy_le16(dirent.flags, flags);
copy_le16(dirent.compression_method, method);
copy_le16(dirent.mtime, zip_time);
copy_le16(dirent.mdate, zip_date);
- copy_le32(dirent.crc32, crc);
- copy_le32(dirent.compressed_size, compressed_size);
- copy_le32(dirent.size, uncompressed_size);
+ set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
copy_le16(dirent.filename_length, pathlen);
copy_le16(dirent.extra_length, 0);
copy_le16(dirent.comment_length, 0);
@@ -202,33 +269,120 @@ static int write_zip_entry(struct archiver_args *args,
copy_le16(dirent.attr1, 0);
copy_le32(dirent.attr2, attr2);
copy_le32(dirent.offset, zip_offset);
- memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
- zip_dir_offset += ZIP_DIR_HEADER_SIZE;
- memcpy(zip_dir + zip_dir_offset, path, pathlen);
- zip_dir_offset += pathlen;
- zip_dir_entries++;
copy_le32(header.magic, 0x04034b50);
copy_le16(header.version, 10);
- copy_le16(header.flags, 0);
+ copy_le16(header.flags, flags);
copy_le16(header.compression_method, method);
copy_le16(header.mtime, zip_time);
copy_le16(header.mdate, zip_date);
- copy_le32(header.crc32, crc);
- copy_le32(header.compressed_size, compressed_size);
- copy_le32(header.size, uncompressed_size);
+ if (flags & ZIP_STREAM)
+ set_zip_header_data_desc(&header, 0, 0, 0);
+ else
+ set_zip_header_data_desc(&header, size, compressed_size, crc);
copy_le16(header.filename_length, pathlen);
copy_le16(header.extra_length, 0);
write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
zip_offset += ZIP_LOCAL_HEADER_SIZE;
write_or_die(1, path, pathlen);
zip_offset += pathlen;
- if (compressed_size > 0) {
+ if (stream && method == 0) {
+ unsigned char buf[STREAM_BUFFER_SIZE];
+ ssize_t readlen;
+
+ for (;;) {
+ readlen = read_istream(stream, buf, sizeof(buf));
+ if (readlen <= 0)
+ break;
+ crc = crc32(crc, buf, readlen);
+ write_or_die(1, buf, readlen);
+ }
+ close_istream(stream);
+ if (readlen)
+ return readlen;
+
+ compressed_size = size;
+ zip_offset += compressed_size;
+
+ write_zip_data_desc(size, compressed_size, crc);
+ zip_offset += ZIP_DATA_DESC_SIZE;
+
+ set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
+ } else if (stream && method == 8) {
+ unsigned char buf[STREAM_BUFFER_SIZE];
+ ssize_t readlen;
+ git_zstream zstream;
+ int result;
+ size_t out_len;
+ unsigned char compressed[STREAM_BUFFER_SIZE * 2];
+
+ memset(&zstream, 0, sizeof(zstream));
+ git_deflate_init(&zstream, args->compression_level);
+
+ compressed_size = 0;
+ zstream.next_out = compressed;
+ zstream.avail_out = sizeof(compressed);
+
+ for (;;) {
+ readlen = read_istream(stream, buf, sizeof(buf));
+ if (readlen <= 0)
+ break;
+ crc = crc32(crc, buf, readlen);
+
+ zstream.next_in = buf;
+ zstream.avail_in = readlen;
+ result = git_deflate(&zstream, 0);
+ if (result != Z_OK)
+ die("deflate error (%d)", result);
+ out = compressed;
+ if (!compressed_size)
+ out += 2;
+ out_len = zstream.next_out - out;
+
+ if (out_len > 0) {
+ write_or_die(1, out, out_len);
+ compressed_size += out_len;
+ zstream.next_out = compressed;
+ zstream.avail_out = sizeof(compressed);
+ }
+
+ }
+ close_istream(stream);
+ if (readlen)
+ return readlen;
+
+ zstream.next_in = buf;
+ zstream.avail_in = 0;
+ result = git_deflate(&zstream, Z_FINISH);
+ if (result != Z_STREAM_END)
+ die("deflate error (%d)", result);
+
+ git_deflate_end(&zstream);
+ out = compressed;
+ if (!compressed_size)
+ out += 2;
+ out_len = zstream.next_out - out - 4;
+ write_or_die(1, out, out_len);
+ compressed_size += out_len;
+ zip_offset += compressed_size;
+
+ write_zip_data_desc(size, compressed_size, crc);
+ zip_offset += ZIP_DATA_DESC_SIZE;
+
+ set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
+ } else if (compressed_size > 0) {
write_or_die(1, out, compressed_size);
zip_offset += compressed_size;
}
free(deflated);
+ free(buffer);
+
+ memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
+ zip_dir_offset += ZIP_DIR_HEADER_SIZE;
+ memcpy(zip_dir + zip_dir_offset, path, pathlen);
+ zip_dir_offset += pathlen;
+ zip_dir_entries++;
return 0;
}
@@ -261,7 +415,8 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time)
*dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048;
}
-int write_zip_archive(struct archiver_args *args)
+static int write_zip_archive(const struct archiver *ar,
+ struct archiver_args *args)
{
int err;
@@ -278,3 +433,14 @@ int write_zip_archive(struct archiver_args *args)
return err;
}
+
+static struct archiver zip_archiver = {
+ "zip",
+ write_zip_archive,
+ ARCHIVER_WANT_COMPRESSION_LEVELS|ARCHIVER_REMOTE
+};
+
+void init_zip_archiver(void)
+{
+ register_archiver(&zip_archiver);
+}
diff --git a/archive.c b/archive.c
index 42f2d2f..a484433 100644
--- a/archive.c
+++ b/archive.c
@@ -14,16 +14,15 @@ static char const * const archive_usage[] = {
NULL
};
-#define USES_ZLIB_COMPRESSION 1
-
-static const struct archiver {
- const char *name;
- write_archive_fn_t write_archive;
- unsigned int flags;
-} archivers[] = {
- { "tar", write_tar_archive },
- { "zip", write_zip_archive, USES_ZLIB_COMPRESSION },
-};
+static const struct archiver **archivers;
+static int nr_archivers;
+static int alloc_archivers;
+
+void register_archiver(struct archiver *ar)
+{
+ ALLOC_GROW(archivers, nr_archivers + 1, alloc_archivers);
+ archivers[nr_archivers++] = ar;
+}
static void format_subst(const struct commit *commit,
const char *src, size_t len,
@@ -60,12 +59,15 @@ static void format_subst(const struct commit *commit,
free(to_free);
}
-static 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 *sha1_file_to_archive(const struct archiver_args *args,
+ const char *path, const unsigned char *sha1,
+ unsigned int mode, enum object_type *type,
+ unsigned long *sizep)
{
void *buffer;
+ const struct commit *commit = args->convert ? args->commit : NULL;
+ path += args->baselen;
buffer = read_sha1_file(sha1, type, sizep);
if (buffer && S_ISREG(mode)) {
struct strbuf buf = STRBUF_INIT;
@@ -110,12 +112,9 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
write_archive_entry_fn_t write_entry = c->write_entry;
struct git_attr_check check[2];
const char *path_without_prefix;
- int convert = 0;
int err;
- enum object_type type;
- unsigned long size;
- void *buffer;
+ args->convert = 0;
strbuf_reset(&path);
strbuf_grow(&path, PATH_MAX);
strbuf_add(&path, args->base, args->baselen);
@@ -124,31 +123,25 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
path_without_prefix = path.buf + args->baselen;
setup_archive_check(check);
- if (!git_checkattr(path_without_prefix, ARRAY_SIZE(check), check)) {
+ if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) {
if (ATTR_TRUE(check[0].value))
return 0;
- convert = ATTR_TRUE(check[1].value);
+ args->convert = ATTR_TRUE(check[1].value);
}
if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
strbuf_addch(&path, '/');
if (args->verbose)
fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
- err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0);
+ err = write_entry(args, sha1, path.buf, path.len, mode);
if (err)
return err;
return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
}
- buffer = sha1_file_to_archive(path_without_prefix, sha1, mode,
- &type, &size, convert ? args->commit : NULL);
- if (!buffer)
- return error("cannot read %s", sha1_to_hex(sha1));
if (args->verbose)
fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
- err = write_entry(args, sha1, path.buf, path.len, mode, buffer, size);
- free(buffer);
- return err;
+ return write_entry(args, sha1, path.buf, path.len, mode);
}
int write_archive_entries(struct archiver_args *args,
@@ -168,7 +161,7 @@ int write_archive_entries(struct archiver_args *args,
if (args->verbose)
fprintf(stderr, "%.*s\n", (int)len, args->base);
err = write_entry(args, args->tree->object.sha1, args->base,
- len, 040777, NULL, 0);
+ len, 040777);
if (err)
return err;
}
@@ -208,9 +201,9 @@ static const struct archiver *lookup_archiver(const char *name)
if (!name)
return NULL;
- for (i = 0; i < ARRAY_SIZE(archivers); i++) {
- if (!strcmp(name, archivers[i].name))
- return &archivers[i];
+ for (i = 0; i < nr_archivers; i++) {
+ if (!strcmp(name, archivers[i]->name))
+ return archivers[i];
}
return NULL;
}
@@ -248,7 +241,8 @@ static void parse_pathspec_arg(const char **pathspec,
}
static void parse_treeish_arg(const char **argv,
- struct archiver_args *ar_args, const char *prefix)
+ struct archiver_args *ar_args, const char *prefix,
+ int remote)
{
const char *name = argv[0];
const unsigned char *commit_sha1;
@@ -257,6 +251,17 @@ static void parse_treeish_arg(const char **argv,
const struct commit *commit;
unsigned char sha1[20];
+ /* Remotes are only allowed to fetch actual refs */
+ if (remote) {
+ char *ref = NULL;
+ const char *colon = strchr(name, ':');
+ int refnamelen = colon ? colon - name : strlen(name);
+
+ if (!dwim_ref(name, refnamelen, sha1, &ref))
+ die("no such ref: %.*s", refnamelen, name);
+ free(ref);
+ }
+
if (get_sha1(name, sha1))
die("Not a valid object name");
@@ -299,9 +304,10 @@ static void parse_treeish_arg(const char **argv,
PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, NULL, (p) }
static int parse_archive_args(int argc, const char **argv,
- const struct archiver **ar, struct archiver_args *args)
+ const struct archiver **ar, struct archiver_args *args,
+ const char *name_hint, int is_remote)
{
- const char *format = "tar";
+ const char *format = NULL;
const char *base = NULL;
const char *remote = NULL;
const char *exec = NULL;
@@ -318,7 +324,7 @@ static int parse_archive_args(int argc, const char **argv,
"prepend prefix to each pathname in the archive"),
OPT_STRING('o', "output", &output, "file",
"write the archive to this file"),
- OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes,
+ OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
"read .gitattributes in working directory"),
OPT__VERBOSE(&verbose, "report archived files on stderr"),
OPT__COMPR('0', &compression_level, "store only", 0),
@@ -332,7 +338,7 @@ static int parse_archive_args(int argc, const char **argv,
OPT__COMPR_HIDDEN('8', &compression_level, 8),
OPT__COMPR('9', &compression_level, "compress better", 9),
OPT_GROUP(""),
- OPT_BOOLEAN('l', "list", &list,
+ OPT_BOOL('l', "list", &list,
"list supported archive formats"),
OPT_GROUP(""),
OPT_STRING(0, "remote", &remote, "repo",
@@ -355,21 +361,27 @@ static int parse_archive_args(int argc, const char **argv,
base = "";
if (list) {
- for (i = 0; i < ARRAY_SIZE(archivers); i++)
- printf("%s\n", archivers[i].name);
+ for (i = 0; i < nr_archivers; i++)
+ if (!is_remote || archivers[i]->flags & ARCHIVER_REMOTE)
+ printf("%s\n", archivers[i]->name);
exit(0);
}
+ if (!format && name_hint)
+ format = archive_format_from_filename(name_hint);
+ if (!format)
+ format = "tar";
+
/* We need at least one parameter -- tree-ish */
if (argc < 1)
usage_with_options(archive_usage, opts);
*ar = lookup_archiver(format);
- if (!*ar)
+ if (!*ar || (is_remote && !((*ar)->flags & ARCHIVER_REMOTE)))
die("Unknown archive format '%s'", format);
args->compression_level = Z_DEFAULT_COMPRESSION;
if (compression_level != -1) {
- if ((*ar)->flags & USES_ZLIB_COMPRESSION)
+ if ((*ar)->flags & ARCHIVER_WANT_COMPRESSION_LEVELS)
args->compression_level = compression_level;
else {
die("Argument not supported for format '%s': -%d",
@@ -385,19 +397,55 @@ static int parse_archive_args(int argc, const char **argv,
}
int write_archive(int argc, const char **argv, const char *prefix,
- int setup_prefix)
+ int setup_prefix, const char *name_hint, int remote)
{
+ int nongit = 0;
const struct archiver *ar = NULL;
struct archiver_args args;
- argc = parse_archive_args(argc, argv, &ar, &args);
if (setup_prefix && prefix == NULL)
- prefix = setup_git_directory();
+ prefix = setup_git_directory_gently(&nongit);
+
+ git_config(git_default_config, NULL);
+ init_tar_archiver();
+ init_zip_archiver();
+
+ argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote);
+ if (nongit) {
+ /*
+ * We know this will die() with an error, so we could just
+ * die ourselves; but its error message will be more specific
+ * than what we could write here.
+ */
+ setup_git_directory();
+ }
- parse_treeish_arg(argv, &args, prefix);
+ parse_treeish_arg(argv, &args, prefix, remote);
parse_pathspec_arg(argv + 1, &args);
- git_config(git_default_config, NULL);
+ return ar->write_archive(ar, &args);
+}
+
+static int match_extension(const char *filename, const char *ext)
+{
+ int prefixlen = strlen(filename) - strlen(ext);
+
+ /*
+ * We need 1 character for the '.', and 1 character to ensure that the
+ * prefix is non-empty (k.e., we don't match .tar.gz with no actual
+ * filename).
+ */
+ if (prefixlen < 2 || filename[prefixlen-1] != '.')
+ return 0;
+ return !strcmp(filename + prefixlen, ext);
+}
+
+const char *archive_format_from_filename(const char *filename)
+{
+ int i;
- return ar->write_archive(&args);
+ for (i = 0; i < nr_archivers; i++)
+ if (match_extension(filename, archivers[i]->name))
+ return archivers[i]->name;
+ return NULL;
}
diff --git a/archive.h b/archive.h
index 038ac35..895afcd 100644
--- a/archive.h
+++ b/archive.h
@@ -11,20 +11,35 @@ struct archiver_args {
const char **pathspec;
unsigned int verbose : 1;
unsigned int worktree_attributes : 1;
+ unsigned int convert : 1;
int compression_level;
};
-typedef int (*write_archive_fn_t)(struct archiver_args *);
+#define ARCHIVER_WANT_COMPRESSION_LEVELS 1
+#define ARCHIVER_REMOTE 2
+struct archiver {
+ const char *name;
+ int (*write_archive)(const struct archiver *, struct archiver_args *);
+ unsigned flags;
+ void *data;
+};
+extern void register_archiver(struct archiver *);
-typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size);
+extern void init_tar_archiver(void);
+extern void init_zip_archiver(void);
-/*
- * Archive-format specific backends.
- */
-extern int write_tar_archive(struct archiver_args *);
-extern int write_zip_archive(struct archiver_args *);
+typedef int (*write_archive_entry_fn_t)(struct archiver_args *args,
+ const unsigned char *sha1,
+ const char *path, size_t pathlen,
+ unsigned int mode);
extern int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry);
-extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix);
+extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix, const char *name_hint, int remote);
+
+const char *archive_format_from_filename(const char *filename);
+extern void *sha1_file_to_archive(const struct archiver_args *args,
+ const char *path, const unsigned char *sha1,
+ unsigned int mode, enum object_type *type,
+ unsigned long *sizep);
#endif /* ARCHIVE_H */
diff --git a/argv-array.c b/argv-array.c
new file mode 100644
index 0000000..0b5f889
--- /dev/null
+++ b/argv-array.c
@@ -0,0 +1,61 @@
+#include "cache.h"
+#include "argv-array.h"
+#include "strbuf.h"
+
+const char *empty_argv[] = { NULL };
+
+void argv_array_init(struct argv_array *array)
+{
+ array->argv = empty_argv;
+ array->argc = 0;
+ array->alloc = 0;
+}
+
+static void argv_array_push_nodup(struct argv_array *array, const char *value)
+{
+ if (array->argv == empty_argv)
+ array->argv = NULL;
+
+ ALLOC_GROW(array->argv, array->argc + 2, array->alloc);
+ array->argv[array->argc++] = value;
+ array->argv[array->argc] = NULL;
+}
+
+void argv_array_push(struct argv_array *array, const char *value)
+{
+ argv_array_push_nodup(array, xstrdup(value));
+}
+
+void argv_array_pushf(struct argv_array *array, const char *fmt, ...)
+{
+ va_list ap;
+ struct strbuf v = STRBUF_INIT;
+
+ va_start(ap, fmt);
+ strbuf_vaddf(&v, fmt, ap);
+ va_end(ap);
+
+ argv_array_push_nodup(array, strbuf_detach(&v, NULL));
+}
+
+void argv_array_pushl(struct argv_array *array, ...)
+{
+ va_list ap;
+ const char *arg;
+
+ va_start(ap, array);
+ while((arg = va_arg(ap, const char *)))
+ argv_array_push(array, arg);
+ va_end(ap);
+}
+
+void argv_array_clear(struct argv_array *array)
+{
+ if (array->argv != empty_argv) {
+ int i;
+ for (i = 0; i < array->argc; i++)
+ free((char **)array->argv[i]);
+ free(array->argv);
+ }
+ argv_array_init(array);
+}
diff --git a/argv-array.h b/argv-array.h
new file mode 100644
index 0000000..b93a69c
--- /dev/null
+++ b/argv-array.h
@@ -0,0 +1,21 @@
+#ifndef ARGV_ARRAY_H
+#define ARGV_ARRAY_H
+
+extern const char *empty_argv[];
+
+struct argv_array {
+ const char **argv;
+ int argc;
+ int alloc;
+};
+
+#define ARGV_ARRAY_INIT { empty_argv, 0, 0 }
+
+void argv_array_init(struct argv_array *);
+void argv_array_push(struct argv_array *, const char *);
+__attribute__((format (printf,2,3)))
+void argv_array_pushf(struct argv_array *, const char *fmt, ...);
+void argv_array_pushl(struct argv_array *, ...);
+void argv_array_clear(struct argv_array *);
+
+#endif /* ARGV_ARRAY_H */
diff --git a/attr.c b/attr.c
index af40835..aef93d8 100644
--- a/attr.c
+++ b/attr.c
@@ -1,7 +1,17 @@
+/*
+ * Handle git attributes. See gitattributes(5) for a description of
+ * the file syntax, and Documentation/technical/api-gitattributes.txt
+ * for a description of the API.
+ *
+ * One basic design decision here is that we are not going to support
+ * an insanely large number of attributes.
+ */
+
#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "exec_cmd.h"
#include "attr.h"
+#include "dir.h"
const char git_attr__true[] = "(builtin)true";
const char git_attr__false[] = "\0(builtin)false";
@@ -11,14 +21,7 @@ static const char git_attr__unknown[] = "(builtin)unknown";
#define ATTR__UNSET NULL
#define ATTR__UNKNOWN git_attr__unknown
-static const char *attributes_file;
-
-/*
- * The basic design decision here is that we are not going to have
- * insanely large number of attributes.
- *
- * This is a randomly chosen prime.
- */
+/* This is a randomly chosen prime. */
#define HASHSIZE 257
#ifndef DEBUG_ATTR
@@ -36,6 +39,11 @@ static int attr_nr;
static struct git_attr_check *check_all_attr;
static struct git_attr *(git_attr_hash[HASHSIZE]);
+char *git_attr_name(struct git_attr *attr)
+{
+ return attr->name;
+}
+
static unsigned hash_name(const char *name, int namelen)
{
unsigned val = 0, c;
@@ -50,12 +58,10 @@ static unsigned hash_name(const char *name, int namelen)
static int invalid_attr_name(const char *name, int namelen)
{
/*
- * Attribute name cannot begin with '-' and from
- * [-A-Za-z0-9_.]. We'd specifically exclude '=' for now,
- * as we might later want to allow non-binary value for
- * attributes, e.g. "*.svg merge=special-merge-program-for-svg"
+ * Attribute name cannot begin with '-' and must consist of
+ * characters from [-A-Za-z0-9_.].
*/
- if (*name == '-')
+ if (namelen <= 0 || *name == '-')
return -1;
while (namelen--) {
char ch = *name++;
@@ -103,22 +109,26 @@ struct git_attr *git_attr(const char *name)
return git_attr_internal(name, strlen(name));
}
-/*
- * .gitattributes file is one line per record, each of which is
- *
- * (1) glob pattern.
- * (2) whitespace
- * (3) whitespace separated list of attribute names, each of which
- * could be prefixed with '-' to mean "set to false", '!' to mean
- * "unset".
- */
-
/* What does a matched pattern decide? */
struct attr_state {
struct git_attr *attr;
const char *setto;
};
+/*
+ * One rule, as from a .gitattributes file.
+ *
+ * If is_macro is true, then u.attr is a pointer to the git_attr being
+ * defined.
+ *
+ * If is_macro is false, then u.pattern points at the filename pattern
+ * to which the rule applies. (The memory pointed to is part of the
+ * memory block allocated for the match_attr instance.)
+ *
+ * In either case, num_attr is the number of attributes affected by
+ * this rule, and state is an array listing them. The attributes are
+ * listed as they appear in the file (macros unexpanded).
+ */
struct match_attr {
union {
char *pattern;
@@ -131,8 +141,15 @@ struct match_attr {
static const char blank[] = " \t\r\n";
+/*
+ * Parse a whitespace-delimited attribute state (i.e., "attr",
+ * "-attr", "!attr", or "attr=value") from the string starting at src.
+ * If e is not NULL, write the results to *e. Return a pointer to the
+ * remainder of the string (with leading whitespace removed), or NULL
+ * if there was an error.
+ */
static const char *parse_attr(const char *src, int lineno, const char *cp,
- int *num_attr, struct match_attr *res)
+ struct attr_state *e)
{
const char *ep, *equals;
int len;
@@ -145,7 +162,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
len = equals - cp;
else
len = ep - cp;
- if (!res) {
+ if (!e) {
if (*cp == '-' || *cp == '!') {
cp++;
len--;
@@ -157,9 +174,6 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
return NULL;
}
} else {
- struct attr_state *e;
-
- e = &(res->state[*num_attr]);
if (*cp == '-' || *cp == '!') {
e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
cp++;
@@ -172,7 +186,6 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
}
e->attr = git_attr_internal(cp, len);
}
- (*num_attr)++;
return ep + strspn(ep, blank);
}
@@ -180,10 +193,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
int lineno, int macro_ok)
{
int namelen;
- int num_attr;
- const char *cp, *name;
+ int num_attr, i;
+ const char *cp, *name, *states;
struct match_attr *res = NULL;
- int pass;
int is_macro;
cp = line + strspn(line, blank);
@@ -212,32 +224,35 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
else
is_macro = 0;
- for (pass = 0; pass < 2; pass++) {
- /* pass 0 counts and allocates, pass 1 fills */
- num_attr = 0;
- cp = name + namelen;
- cp = cp + strspn(cp, blank);
- while (*cp) {
- cp = parse_attr(src, lineno, cp, &num_attr, res);
- if (!cp)
- return NULL;
- }
- if (pass)
- break;
- res = xcalloc(1,
- sizeof(*res) +
- sizeof(struct attr_state) * num_attr +
- (is_macro ? 0 : namelen + 1));
- if (is_macro)
- res->u.attr = git_attr_internal(name, namelen);
- else {
- res->u.pattern = (char *)&(res->state[num_attr]);
- memcpy(res->u.pattern, name, namelen);
- res->u.pattern[namelen] = 0;
- }
- res->is_macro = is_macro;
- res->num_attr = num_attr;
+ states = name + namelen;
+ states += strspn(states, blank);
+
+ /* First pass to count the attr_states */
+ for (cp = states, num_attr = 0; *cp; num_attr++) {
+ cp = parse_attr(src, lineno, cp, NULL);
+ if (!cp)
+ return NULL;
}
+
+ res = xcalloc(1,
+ sizeof(*res) +
+ sizeof(struct attr_state) * num_attr +
+ (is_macro ? 0 : namelen + 1));
+ if (is_macro)
+ res->u.attr = git_attr_internal(name, namelen);
+ else {
+ res->u.pattern = (char *)&(res->state[num_attr]);
+ memcpy(res->u.pattern, name, namelen);
+ res->u.pattern[namelen] = 0;
+ }
+ res->is_macro = is_macro;
+ res->num_attr = num_attr;
+
+ /* Second pass to fill the attr_states */
+ for (cp = states, i = 0; *cp; i++) {
+ cp = parse_attr(src, lineno, cp, &(res->state[i]));
+ }
+
return res;
}
@@ -479,17 +494,10 @@ static int git_attr_system(void)
return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
}
-static int git_attr_config(const char *var, const char *value, void *dummy)
-{
- if (!strcmp(var, "core.attributesfile"))
- return git_config_pathname(&attributes_file, var, value);
-
- return 0;
-}
-
static void bootstrap_attr_stack(void)
{
struct attr_stack *elem;
+ char *xdg_attributes_file;
if (attr_stack)
return;
@@ -508,19 +516,20 @@ static void bootstrap_attr_stack(void)
}
}
- git_config(git_attr_config, NULL);
- if (attributes_file) {
- elem = read_attr_from_file(attributes_file, 1);
- if (elem) {
- elem->origin = NULL;
- elem->prev = attr_stack;
- attr_stack = elem;
- }
+ if (!git_attributes_file) {
+ home_config_paths(NULL, &xdg_attributes_file, "attributes");
+ git_attributes_file = xdg_attributes_file;
+ }
+ elem = read_attr_from_file(git_attributes_file, 1);
+ if (elem) {
+ elem->origin = NULL;
+ elem->prev = attr_stack;
+ attr_stack = elem;
}
if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
elem = read_attr(GITATTRIBUTES_FILE, 1);
- elem->origin = strdup("");
+ elem->origin = xstrdup("");
elem->prev = attr_stack;
attr_stack = elem;
debug_push(elem);
@@ -534,13 +543,17 @@ static void bootstrap_attr_stack(void)
attr_stack = elem;
}
-static void prepare_attr_stack(const char *path, int dirlen)
+static void prepare_attr_stack(const char *path)
{
struct attr_stack *elem, *info;
- int len;
- struct strbuf pathbuf;
+ int dirlen, len;
+ const char *cp;
- strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
+ cp = strrchr(path, '/');
+ if (!cp)
+ dirlen = 0;
+ else
+ dirlen = cp - path;
/*
* At the bottom of the attribute stack is the built-in
@@ -557,8 +570,7 @@ static void prepare_attr_stack(const char *path, int dirlen)
* .gitattributes in deeper directories to shallower ones,
* and finally use the built-in set as the default.
*/
- if (!attr_stack)
- bootstrap_attr_stack();
+ bootstrap_attr_stack();
/*
* Pop the "info" one that is always at the top of the stack.
@@ -596,28 +608,29 @@ static void prepare_attr_stack(const char *path, int dirlen)
* root element whose attr_stack->origin is set to an
* empty string.
*/
+ struct strbuf pathbuf = STRBUF_INIT;
+
assert(attr_stack->origin);
while (1) {
- char *cp;
-
len = strlen(attr_stack->origin);
if (dirlen <= len)
break;
- strbuf_reset(&pathbuf);
- strbuf_add(&pathbuf, path, dirlen);
+ cp = memchr(path + len + 1, '/', dirlen - len - 1);
+ if (!cp)
+ cp = path + dirlen;
+ strbuf_add(&pathbuf, path, cp - path);
strbuf_addch(&pathbuf, '/');
- cp = strchr(pathbuf.buf + len + 1, '/');
- strcpy(cp + 1, GITATTRIBUTES_FILE);
+ strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);
elem = read_attr(pathbuf.buf, 0);
- *cp = '\0';
- elem->origin = strdup(pathbuf.buf);
+ strbuf_setlen(&pathbuf, cp - path);
+ elem->origin = strbuf_detach(&pathbuf, NULL);
elem->prev = attr_stack;
attr_stack = elem;
debug_push(elem);
}
- }
- strbuf_release(&pathbuf);
+ strbuf_release(&pathbuf);
+ }
/*
* Finally push the "info" one at the top of the stack.
@@ -634,7 +647,7 @@ static int path_matches(const char *pathname, int pathlen,
/* match basename */
const char *basename = strrchr(pathname, '/');
basename = basename ? basename + 1 : pathname;
- return (fnmatch(pattern, basename, 0) == 0);
+ return (fnmatch_icase(pattern, basename, 0) == 0);
}
/*
* match with FNM_PATHNAME; the pattern has base implicitly
@@ -648,7 +661,7 @@ static int path_matches(const char *pathname, int pathlen,
return 0;
if (baselen != 0)
baselen++;
- return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+ return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
}
static int macroexpand_one(int attr_nr, int rem);
@@ -715,26 +728,30 @@ static int macroexpand_one(int attr_nr, int rem)
return rem;
}
-int git_checkattr(const char *path, int num, struct git_attr_check *check)
+/*
+ * Collect all attributes for path into the array pointed to by
+ * check_all_attr.
+ */
+static void collect_all_attrs(const char *path)
{
struct attr_stack *stk;
- const char *cp;
- int dirlen, pathlen, i, rem;
+ int i, pathlen, rem;
- bootstrap_attr_stack();
+ prepare_attr_stack(path);
for (i = 0; i < attr_nr; i++)
check_all_attr[i].value = ATTR__UNKNOWN;
pathlen = strlen(path);
- cp = strrchr(path, '/');
- if (!cp)
- dirlen = 0;
- else
- dirlen = cp - path;
- prepare_attr_stack(path, dirlen);
rem = attr_nr;
for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
rem = fill(path, pathlen, stk, rem);
+}
+
+int git_check_attr(const char *path, int num, struct git_attr_check *check)
+{
+ int i;
+
+ collect_all_attrs(path);
for (i = 0; i < num; i++) {
const char *value = check_all_attr[check[i].attr->attr_nr].value;
@@ -746,6 +763,34 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check)
return 0;
}
+int git_all_attrs(const char *path, int *num, struct git_attr_check **check)
+{
+ int i, count, j;
+
+ collect_all_attrs(path);
+
+ /* Count the number of attributes that are set. */
+ count = 0;
+ for (i = 0; i < attr_nr; i++) {
+ const char *value = check_all_attr[i].value;
+ if (value != ATTR__UNSET && value != ATTR__UNKNOWN)
+ ++count;
+ }
+ *num = count;
+ *check = xmalloc(sizeof(**check) * count);
+ j = 0;
+ for (i = 0; i < attr_nr; i++) {
+ const char *value = check_all_attr[i].value;
+ if (value != ATTR__UNSET && value != ATTR__UNKNOWN) {
+ (*check)[j].attr = check_all_attr[i].attr;
+ (*check)[j].value = value;
+ ++j;
+ }
+ }
+
+ return 0;
+}
+
void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
{
enum git_attr_direction old = direction;
diff --git a/attr.h b/attr.h
index 8b3f19b..8b08d33 100644
--- a/attr.h
+++ b/attr.h
@@ -20,7 +20,7 @@ extern const char git_attr__false[];
#define ATTR_UNSET(v) ((v) == NULL)
/*
- * Send one or more git_attr_check to git_checkattr(), and
+ * Send one or more git_attr_check to git_check_attr(), and
* each 'value' member tells what its value is.
* Unset one is returned as NULL.
*/
@@ -29,7 +29,23 @@ struct git_attr_check {
const char *value;
};
-int git_checkattr(const char *path, int, struct git_attr_check *);
+/*
+ * Return the name of the attribute represented by the argument. The
+ * return value is a pointer to a null-delimited string that is part
+ * of the internal data structure; it should not be modified or freed.
+ */
+char *git_attr_name(struct git_attr *);
+
+int git_check_attr(const char *path, int, struct git_attr_check *);
+
+/*
+ * Retrieve all attributes that apply to the specified path. *num
+ * will be set to the number of attributes on the path; **check will
+ * be set to point at a newly-allocated array of git_attr_check
+ * objects describing the attributes and their values. *check must be
+ * free()ed by the caller.
+ */
+int git_all_attrs(const char *path, int *num, struct git_attr_check **check);
enum git_attr_direction {
GIT_ATTR_CHECKIN,
diff --git a/bisect.c b/bisect.c
index dd7e8ed..48acf73 100644
--- a/bisect.c
+++ b/bisect.c
@@ -10,20 +10,16 @@
#include "log-tree.h"
#include "bisect.h"
#include "sha1-array.h"
+#include "argv-array.h"
static struct sha1_array good_revs;
static struct sha1_array skipped_revs;
static const unsigned char *current_bad_sha1;
-struct argv_array {
- const char **argv;
- int argv_nr;
- int argv_alloc;
-};
-
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
+static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL};
/* bits #0-15 in revision.h */
@@ -404,21 +400,6 @@ struct commit_list *find_bisection(struct commit_list *list,
return best;
}
-static void argv_array_push(struct argv_array *array, const char *string)
-{
- ALLOC_GROW(array->argv, array->argv_nr + 1, array->argv_alloc);
- array->argv[array->argv_nr++] = string;
-}
-
-static void argv_array_push_sha1(struct argv_array *array,
- const unsigned char *sha1,
- const char *format)
-{
- struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, format, sha1_to_hex(sha1));
- argv_array_push(array, strbuf_detach(&buf, NULL));
-}
-
static int register_ref(const char *refname, const unsigned char *sha1,
int flags, void *cb_data)
{
@@ -448,16 +429,10 @@ static void read_bisect_paths(struct argv_array *array)
die_errno("Could not open file '%s'", filename);
while (strbuf_getline(&str, fp, '\n') != EOF) {
- char *quoted;
- int res;
-
strbuf_trim(&str);
- quoted = strbuf_detach(&str, NULL);
- res = sq_dequote_to_argv(quoted, &array->argv,
- &array->argv_nr, &array->argv_alloc);
- if (res)
+ if (sq_dequote_to_argv_array(str.buf, array))
die("Badly quoted content in file '%s': %s",
- filename, quoted);
+ filename, str.buf);
}
strbuf_release(&str);
@@ -622,7 +597,7 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
const char *bad_format, const char *good_format,
int read_paths)
{
- struct argv_array rev_argv = { NULL, 0, 0 };
+ struct argv_array rev_argv = ARGV_ARRAY_INIT;
int i;
init_revisions(revs, prefix);
@@ -630,17 +605,17 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
revs->commit_format = CMIT_FMT_UNSPECIFIED;
/* rev_argv.argv[0] will be ignored by setup_revisions */
- argv_array_push(&rev_argv, xstrdup("bisect_rev_setup"));
- argv_array_push_sha1(&rev_argv, current_bad_sha1, bad_format);
+ argv_array_push(&rev_argv, "bisect_rev_setup");
+ argv_array_pushf(&rev_argv, bad_format, sha1_to_hex(current_bad_sha1));
for (i = 0; i < good_revs.nr; i++)
- argv_array_push_sha1(&rev_argv, good_revs.sha1[i],
- good_format);
- argv_array_push(&rev_argv, xstrdup("--"));
+ argv_array_pushf(&rev_argv, good_format,
+ sha1_to_hex(good_revs.sha1[i]));
+ argv_array_push(&rev_argv, "--");
if (read_paths)
read_bisect_paths(&rev_argv);
- argv_array_push(&rev_argv, NULL);
- setup_revisions(rev_argv.argv_nr, rev_argv.argv, revs, NULL);
+ setup_revisions(rev_argv.argc, rev_argv.argv, revs, NULL);
+ /* XXX leak rev_argv, as "revs" may still be pointing to it */
}
static void bisect_common(struct rev_info *revs)
@@ -707,16 +682,23 @@ static void mark_expected_rev(char *bisect_rev_hex)
die("closing file %s: %s", filename, strerror(errno));
}
-static int bisect_checkout(char *bisect_rev_hex)
+static int bisect_checkout(char *bisect_rev_hex, int no_checkout)
{
int res;
mark_expected_rev(bisect_rev_hex);
argv_checkout[2] = bisect_rev_hex;
- res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
- if (res)
- exit(res);
+ if (no_checkout) {
+ argv_update_ref[3] = bisect_rev_hex;
+ if (run_command_v_opt(argv_update_ref, RUN_GIT_CMD))
+ die("update-ref --no-deref HEAD failed on %s",
+ bisect_rev_hex);
+ } else {
+ res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
+ if (res)
+ exit(res);
+ }
argv_show_branch[1] = bisect_rev_hex;
return run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
@@ -788,7 +770,7 @@ static void handle_skipped_merge_base(const unsigned char *mb)
* - If one is "skipped", we can't know but we should warn.
* - If we don't know, we should check it out and ask the user to test.
*/
-static void check_merge_bases(void)
+static void check_merge_bases(int no_checkout)
{
struct commit_list *result;
int rev_nr;
@@ -806,7 +788,7 @@ static void check_merge_bases(void)
handle_skipped_merge_base(mb);
} else {
printf("Bisecting: a merge base must be tested\n");
- exit(bisect_checkout(sha1_to_hex(mb)));
+ exit(bisect_checkout(sha1_to_hex(mb), no_checkout));
}
}
@@ -818,25 +800,25 @@ static int check_ancestors(const char *prefix)
{
struct rev_info revs;
struct object_array pending_copy;
- int i, res;
+ int res;
bisect_rev_setup(&revs, prefix, "^%s", "%s", 0);
/* Save pending objects, so they can be cleaned up later. */
- memset(&pending_copy, 0, sizeof(pending_copy));
- for (i = 0; i < revs.pending.nr; i++)
- add_object_array(revs.pending.objects[i].item,
- revs.pending.objects[i].name,
- &pending_copy);
+ pending_copy = revs.pending;
+ revs.leak_pending = 1;
+ /*
+ * bisect_common calls prepare_revision_walk right away, which
+ * (together with .leak_pending = 1) makes us the sole owner of
+ * the list of pending objects.
+ */
bisect_common(&revs);
res = (revs.commits != NULL);
/* Clean up objects used, as they will be reused. */
- for (i = 0; i < pending_copy.nr; i++) {
- struct object *o = pending_copy.objects[i].item;
- clear_commit_marks((struct commit *)o, ALL_REV_FLAGS);
- }
+ clear_commit_marks_for_object_array(&pending_copy, ALL_REV_FLAGS);
+ free(pending_copy.objects);
return res;
}
@@ -849,9 +831,9 @@ static int check_ancestors(const char *prefix)
* If a merge base must be tested by the user, its source code will be
* checked out to be tested by the user and we will exit.
*/
-static void check_good_are_ancestors_of_bad(const char *prefix)
+static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
{
- const char *filename = git_path("BISECT_ANCESTORS_OK");
+ char *filename = xstrdup(git_path("BISECT_ANCESTORS_OK"));
struct stat st;
int fd;
@@ -860,15 +842,15 @@ static void check_good_are_ancestors_of_bad(const char *prefix)
/* Check if file BISECT_ANCESTORS_OK exists. */
if (!stat(filename, &st) && S_ISREG(st.st_mode))
- return;
+ goto done;
/* Bisecting with no good rev is ok. */
if (good_revs.nr == 0)
- return;
+ goto done;
/* Check if all good revs are ancestor of the bad rev. */
if (check_ancestors(prefix))
- check_merge_bases();
+ check_merge_bases(no_checkout);
/* Create file BISECT_ANCESTORS_OK. */
fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
@@ -877,6 +859,8 @@ static void check_good_are_ancestors_of_bad(const char *prefix)
filename, strerror(errno));
else
close(fd);
+ done:
+ free(filename);
}
/*
@@ -908,8 +892,11 @@ static void show_diff_tree(const char *prefix, struct commit *commit)
* We use the convention that exiting with an exit code 10 means that
* the bisection process finished successfully.
* In this case the calling shell script should exit 0.
+ *
+ * If no_checkout is non-zero, the bisection process does not
+ * checkout the trial commit but instead simply updates BISECT_HEAD.
*/
-int bisect_next_all(const char *prefix)
+int bisect_next_all(const char *prefix, int no_checkout)
{
struct rev_info revs;
struct commit_list *tried;
@@ -920,7 +907,7 @@ int bisect_next_all(const char *prefix)
if (read_bisect_refs())
die("reading bisect refs failed");
- check_good_are_ancestors_of_bad(prefix);
+ check_good_are_ancestors_of_bad(prefix, no_checkout);
bisect_rev_setup(&revs, prefix, "%s", "^%s", 1);
revs.limited = 1;
@@ -966,6 +953,6 @@ int bisect_next_all(const char *prefix)
"(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
steps, (steps == 1 ? "" : "s"));
- return bisect_checkout(bisect_rev_hex);
+ return bisect_checkout(bisect_rev_hex, no_checkout);
}
diff --git a/bisect.h b/bisect.h
index 0862ce5..ec3c3ff 100644
--- a/bisect.h
+++ b/bisect.h
@@ -15,19 +15,18 @@ extern void print_commit_list(struct commit_list *list,
const char *format_cur,
const char *format_last);
-/* bisect_show_flags flags in struct rev_list_info */
#define BISECT_SHOW_ALL (1<<0)
-#define BISECT_SHOW_TRIED (1<<1)
+#define REV_LIST_QUIET (1<<1)
struct rev_list_info {
struct rev_info *revs;
- int bisect_show_flags;
+ int flags;
int show_timestamp;
int hdr_termination;
const char *header_prefix;
};
-extern int bisect_next_all(const char *prefix);
+extern int bisect_next_all(const char *prefix, int no_checkout);
extern int estimate_bisect_steps(int all);
diff --git a/branch.c b/branch.c
index d62cc01..2bef1e7 100644
--- a/branch.c
+++ b/branch.c
@@ -74,25 +74,33 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
strbuf_addf(&key, "branch.%s.rebase", local);
git_config_set(key.buf, "true");
}
+ strbuf_release(&key);
if (flag & BRANCH_CONFIG_VERBOSE) {
- strbuf_reset(&key);
-
- strbuf_addstr(&key, origin ? "remote" : "local");
-
- /* Are we tracking a proper "branch"? */
- if (remote_is_branch) {
- strbuf_addf(&key, " branch %s", shortname);
- if (origin)
- strbuf_addf(&key, " from %s", origin);
- }
+ if (remote_is_branch && origin)
+ printf(rebasing ?
+ "Branch %s set up to track remote branch %s from %s by rebasing.\n" :
+ "Branch %s set up to track remote branch %s from %s.\n",
+ local, shortname, origin);
+ else if (remote_is_branch && !origin)
+ printf(rebasing ?
+ "Branch %s set up to track local branch %s by rebasing.\n" :
+ "Branch %s set up to track local branch %s.\n",
+ local, shortname);
+ else if (!remote_is_branch && origin)
+ printf(rebasing ?
+ "Branch %s set up to track remote ref %s by rebasing.\n" :
+ "Branch %s set up to track remote ref %s.\n",
+ local, remote);
+ else if (!remote_is_branch && !origin)
+ printf(rebasing ?
+ "Branch %s set up to track local ref %s by rebasing.\n" :
+ "Branch %s set up to track local ref %s.\n",
+ local, remote);
else
- strbuf_addf(&key, " ref %s", remote);
- printf("Branch %s set up to track %s%s.\n",
- local, key.buf,
- rebasing ? " by rebasing" : "");
+ die("BUG: impossible combination of %d and %p",
+ remote_is_branch, origin);
}
- strbuf_release(&key);
}
/*
@@ -101,9 +109,10 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
* config.
*/
static int setup_tracking(const char *new_ref, const char *orig_ref,
- enum branch_track track)
+ enum branch_track track, int quiet)
{
struct tracking tracking;
+ int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
if (strlen(new_ref) > 1024 - 7 - 7 - 1)
return error("Tracking not set up: name too long: %s",
@@ -128,16 +137,70 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
return error("Not tracking: ambiguous information for ref %s",
orig_ref);
- install_branch_config(BRANCH_CONFIG_VERBOSE, new_ref, tracking.remote,
+ install_branch_config(config_flags, new_ref, tracking.remote,
tracking.src ? tracking.src : orig_ref);
free(tracking.src);
return 0;
}
+struct branch_desc_cb {
+ const char *config_name;
+ const char *value;
+};
+
+static int read_branch_desc_cb(const char *var, const char *value, void *cb)
+{
+ struct branch_desc_cb *desc = cb;
+ if (strcmp(desc->config_name, var))
+ return 0;
+ free((char *)desc->value);
+ return git_config_string(&desc->value, var, value);
+}
+
+int read_branch_desc(struct strbuf *buf, const char *branch_name)
+{
+ struct branch_desc_cb cb;
+ struct strbuf name = STRBUF_INIT;
+ strbuf_addf(&name, "branch.%s.description", branch_name);
+ cb.config_name = name.buf;
+ cb.value = NULL;
+ if (git_config(read_branch_desc_cb, &cb) < 0) {
+ strbuf_release(&name);
+ return -1;
+ }
+ if (cb.value)
+ strbuf_addstr(buf, cb.value);
+ strbuf_release(&name);
+ return 0;
+}
+
+int validate_new_branchname(const char *name, struct strbuf *ref,
+ int force, int attr_only)
+{
+ if (strbuf_check_branch_ref(ref, name))
+ die("'%s' is not a valid branch name.", name);
+
+ if (!ref_exists(ref->buf))
+ return 0;
+ else if (!force && !attr_only)
+ die("A branch named '%s' already exists.", ref->buf + strlen("refs/heads/"));
+
+ if (!attr_only) {
+ const char *head;
+ unsigned char sha1[20];
+
+ head = resolve_ref_unsafe("HEAD", sha1, 0, NULL);
+ if (!is_bare_repository() && head && !strcmp(head, ref->buf))
+ die("Cannot force update the current branch.");
+ }
+ return 1;
+}
+
void create_branch(const char *head,
const char *name, const char *start_name,
- int force, int reflog, enum branch_track track)
+ int force, int reflog, int clobber_head,
+ int quiet, enum branch_track track)
{
struct ref_lock *lock = NULL;
struct commit *commit;
@@ -151,17 +214,13 @@ void create_branch(const char *head,
if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
explicit_tracking = 1;
- if (strbuf_check_branch_ref(&ref, name))
- die("'%s' is not a valid branch name.", name);
-
- if (resolve_ref(ref.buf, sha1, 1, NULL)) {
- if (!force && track == BRANCH_TRACK_OVERRIDE)
+ if (validate_new_branchname(name, &ref, force,
+ track == BRANCH_TRACK_OVERRIDE ||
+ clobber_head)) {
+ if (!force)
dont_change_ref = 1;
- else if (!force)
- die("A branch named '%s' already exists.", name);
- else if (!is_bare_repository() && head && !strcmp(head, name))
- die("Cannot force update the current branch.");
- forcing = 1;
+ else
+ forcing = 1;
}
real_ref = NULL;
@@ -210,7 +269,7 @@ void create_branch(const char *head,
start_name);
if (real_ref && track)
- setup_tracking(ref.buf+11, real_ref, track);
+ setup_tracking(ref.buf+11, real_ref, track, quiet);
if (!dont_change_ref)
if (write_ref_sha1(lock, sha1, msg) < 0)
@@ -223,6 +282,7 @@ void create_branch(const char *head,
void remove_branch_state(void)
{
unlink(git_path("CHERRY_PICK_HEAD"));
+ unlink(git_path("REVERT_HEAD"));
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_RR"));
unlink(git_path("MERGE_MSG"));
diff --git a/branch.h b/branch.h
index 4026e38..64173ab 100644
--- a/branch.h
+++ b/branch.h
@@ -13,7 +13,26 @@
* branch for (if any).
*/
void create_branch(const char *head, const char *name, const char *start_name,
- int force, int reflog, enum branch_track track);
+ int force, int reflog,
+ int clobber_head, int quiet, enum branch_track track);
+
+/*
+ * Validates that the requested branch may be created, returning the
+ * interpreted ref in ref, force indicates whether (non-head) branches
+ * may be overwritten. A non-zero return value indicates that the force
+ * parameter was non-zero and the branch already exists.
+ *
+ * Contrary to all of the above, when attr_only is 1, the caller is
+ * not interested in verifying if it is Ok to update the named
+ * branch to point at a potentially different commit. It is merely
+ * asking if it is OK to change some attribute for the named branch
+ * (e.g. tracking upstream).
+ *
+ * NEEDSWORK: This needs to be split into two separate functions in the
+ * longer run for sanity.
+ *
+ */
+int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only);
/*
* Remove information about the state of working on the current
@@ -28,4 +47,9 @@ void remove_branch_state(void);
#define BRANCH_CONFIG_VERBOSE 01
extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote);
+/*
+ * Read branch description
+ */
+extern int read_branch_desc(struct strbuf *, const char *branch_name);
+
#endif
diff --git a/builtin.h b/builtin.h
index 0e9da90..ba6626b 100644
--- a/builtin.h
+++ b/builtin.h
@@ -9,13 +9,18 @@
#define DEFAULT_MERGE_LOG_LEN 20
-extern const char git_version_string[];
extern const char git_usage_string[];
extern const char git_more_info_string[];
extern void prune_packed_objects(int);
+
+struct fmt_merge_msg_opts {
+ unsigned add_title:1;
+ int shortlog_len;
+};
+
extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
- int merge_title, int shortlog_len);
+ struct fmt_merge_msg_opts *);
extern void commit_notes(struct notes_tree *t, const char *msg);
struct notes_rewrite_cfg {
@@ -35,6 +40,8 @@ int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
extern int check_pager_config(const char *cmd);
+struct diff_options;
+extern void setup_diff_pager(struct diff_options *);
extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size);
@@ -55,10 +62,12 @@ 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_clone(int argc, const char **argv, const char *prefix);
extern int cmd_clean(int argc, const char **argv, const char *prefix);
+extern int cmd_column(int argc, const char **argv, const char *prefix);
extern int cmd_commit(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
extern int cmd_config(int argc, const char **argv, const char *prefix);
extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
+extern int cmd_credential(int argc, const char **argv, const char *prefix);
extern int cmd_describe(int argc, const char **argv, const char *prefix);
extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
@@ -76,7 +85,6 @@ 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_hash_object(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_index_pack(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);
@@ -101,7 +109,6 @@ extern int cmd_notes(int argc, const char **argv, const char *prefix);
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);
extern int cmd_patch_id(int argc, const char **argv, const char *prefix);
-extern int cmd_pickaxe(int argc, const char **argv, const char *prefix);
extern int cmd_prune(int argc, const char **argv, const char *prefix);
extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
extern int cmd_push(int argc, const char **argv, const char *prefix);
@@ -133,7 +140,7 @@ extern int cmd_update_index(int argc, const char **argv, const char *prefix);
extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
extern int cmd_update_server_info(int argc, const char **argv, const char *prefix);
extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
-extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
+extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix);
extern int cmd_var(int argc, const char **argv, const char *prefix);
extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
extern int cmd_version(int argc, const char **argv, const char *prefix);
diff --git a/builtin/add.c b/builtin/add.c
index c59b0c9..87446cf 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -13,6 +13,7 @@
#include "diff.h"
#include "diffcore.h"
#include "revision.h"
+#include "bulk-checkin.h"
static const char * const builtin_add_usage[] = {
"git add [options] [--] <filepattern>...",
@@ -279,6 +280,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
argc = setup_revisions(argc, argv, &rev, NULL);
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+ DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
out = open(file, O_CREAT | O_WRONLY, 0644);
if (out < 0)
die (_("Could not open '%s' for writing."), file);
@@ -441,6 +443,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (pathspec) {
int i;
+ struct path_exclude_check check;
+
+ path_exclude_check_init(&check, &dir);
if (!seen)
seen = find_used_pathspec(pathspec);
for (i = 0; pathspec[i]; i++) {
@@ -448,7 +453,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
&& !file_exists(pathspec[i])) {
if (ignore_missing) {
int dtype = DT_UNKNOWN;
- if (excluded(&dir, pathspec[i], &dtype))
+ if (path_excluded(&check, pathspec[i], -1, &dtype))
dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
} else
die(_("pathspec '%s' did not match any files"),
@@ -456,13 +461,18 @@ int cmd_add(int argc, const char **argv, const char *prefix)
}
}
free(seen);
+ path_exclude_check_clear(&check);
}
+ plug_bulk_checkin();
+
exit_status |= add_files_to_cache(prefix, pathspec, flags);
if (add_new_files)
exit_status |= add_files(&dir, flags);
+ unplug_bulk_checkin();
+
finish:
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
diff --git a/builtin/apply.c b/builtin/apply.c
index f2edc52..b4428ea 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -14,6 +14,7 @@
#include "builtin.h"
#include "string-list.h"
#include "dir.h"
+#include "diff.h"
#include "parse-options.h"
/*
@@ -49,7 +50,7 @@ static const char *fake_ancestor;
static int line_termination = '\n';
static unsigned int p_context = UINT_MAX;
static const char * const apply_usage[] = {
- "git apply [options] [<patch>...]",
+ N_("git apply [options] [<patch>...]"),
NULL
};
@@ -102,7 +103,7 @@ static void parse_whitespace_option(const char *option)
ws_error_action = correct_ws_error;
return;
}
- die("unrecognized whitespace option '%s'", option);
+ die(_("unrecognized whitespace option '%s'"), option);
}
static void parse_ignorewhitespace_option(const char *option)
@@ -117,7 +118,7 @@ static void parse_ignorewhitespace_option(const char *option)
ws_ignore_action = ignore_ws_change;
return;
}
- die("unrecognized whitespace ignore option '%s'", option);
+ die(_("unrecognized whitespace ignore option '%s'"), option);
}
static void set_default_whitespace_mode(const char *whitespace_option)
@@ -151,9 +152,14 @@ struct fragment {
unsigned long leading, trailing;
unsigned long oldpos, oldlines;
unsigned long newpos, newlines;
+ /*
+ * 'patch' is usually borrowed from buf in apply_patch(),
+ * but some codepaths store an allocated buffer.
+ */
const char *patch;
+ unsigned free_patch:1,
+ rejected:1;
int size;
- int rejected;
int linenr;
struct fragment *next;
};
@@ -195,6 +201,36 @@ struct patch {
struct patch *next;
};
+static void free_fragment_list(struct fragment *list)
+{
+ while (list) {
+ struct fragment *next = list->next;
+ if (list->free_patch)
+ free((char *)list->patch);
+ free(list);
+ list = next;
+ }
+}
+
+static void free_patch(struct patch *patch)
+{
+ free_fragment_list(patch->fragments);
+ free(patch->def_name);
+ free(patch->old_name);
+ free(patch->new_name);
+ free(patch->result);
+ free(patch);
+}
+
+static void free_patch_list(struct patch *list)
+{
+ while (list) {
+ struct patch *next = list->next;
+ free_patch(list);
+ list = next;
+ }
+}
+
/*
* A line in a file, len-bytes long (includes the terminating LF,
* except for an incomplete line at the end if the file ends with
@@ -250,9 +286,6 @@ static int fuzzy_matchlines(const char *s1, size_t n1,
const char *last2 = s2 + n2 - 1;
int result = 0;
- if (n1 < 0 || n2 < 0)
- return 0;
-
/* ignore line endings */
while ((*last1 == '\r') || (*last1 == '\n'))
last1--;
@@ -304,6 +337,11 @@ static void add_line_info(struct image *img, const char *bol, size_t len, unsign
img->nr++;
}
+/*
+ * "buf" has the file contents to be patched (read from various sources).
+ * attach it to "image" and add line-based index to it.
+ * "image" now owns the "buf".
+ */
static void prepare_image(struct image *image, char *buf, size_t len,
int prepare_linetable)
{
@@ -337,25 +375,27 @@ static void clear_image(struct image *image)
image->len = 0;
}
-static void say_patch_name(FILE *output, const char *pre,
- struct patch *patch, const char *post)
+/* fmt must contain _one_ %s and no other substitution */
+static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
{
- fputs(pre, output);
+ struct strbuf sb = STRBUF_INIT;
+
if (patch->old_name && patch->new_name &&
strcmp(patch->old_name, patch->new_name)) {
- quote_c_style(patch->old_name, NULL, output, 0);
- fputs(" => ", output);
- quote_c_style(patch->new_name, NULL, output, 0);
+ quote_c_style(patch->old_name, &sb, NULL, 0);
+ strbuf_addstr(&sb, " => ");
+ quote_c_style(patch->new_name, &sb, NULL, 0);
} else {
const char *n = patch->new_name;
if (!n)
n = patch->old_name;
- quote_c_style(n, NULL, output, 0);
+ quote_c_style(n, &sb, NULL, 0);
}
- fputs(post, output);
+ fprintf(output, fmt, sb.buf);
+ fputc('\n', output);
+ strbuf_release(&sb);
}
-#define CHUNKSIZE (8192)
#define SLOP (16)
static void read_patch_file(struct strbuf *sb, int fd)
@@ -418,7 +458,7 @@ static char *squash_slash(char *name)
return name;
}
-static char *find_name_gnu(const char *line, char *def, int p_value)
+static char *find_name_gnu(const char *line, const char *def, int p_value)
{
struct strbuf name = STRBUF_INIT;
char *cp;
@@ -441,11 +481,7 @@ static char *find_name_gnu(const char *line, char *def, int p_value)
cp++;
}
- /* name can later be freed, so we need
- * to memmove, not just return cp
- */
strbuf_remove(&name, 0, cp - name.buf);
- free(def);
if (root)
strbuf_insert(&name, 0, root, root_len);
return squash_slash(strbuf_detach(&name, NULL));
@@ -610,8 +646,13 @@ static size_t diff_timestamp_len(const char *line, size_t len)
return line + len - end;
}
-static char *find_name_common(const char *line, char *def, int p_value,
- const char *end, int terminate)
+static char *null_strdup(const char *s)
+{
+ return s ? xstrdup(s) : NULL;
+}
+
+static char *find_name_common(const char *line, const char *def,
+ int p_value, const char *end, int terminate)
{
int len;
const char *start = NULL;
@@ -632,10 +673,10 @@ static char *find_name_common(const char *line, char *def, int p_value,
start = line;
}
if (!start)
- return squash_slash(def);
+ return squash_slash(null_strdup(def));
len = line - start;
if (!len)
- return squash_slash(def);
+ return squash_slash(null_strdup(def));
/*
* Generally we prefer the shorter name, especially
@@ -646,8 +687,7 @@ static char *find_name_common(const char *line, char *def, int p_value,
if (def) {
int deflen = strlen(def);
if (deflen < len && !strncmp(start, def, deflen))
- return squash_slash(def);
- free(def);
+ return squash_slash(xstrdup(def));
}
if (root) {
@@ -772,7 +812,7 @@ static int has_epoch_timestamp(const char *nameline)
if (!stamp) {
stamp = xmalloc(sizeof(*stamp));
if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
- warning("Cannot prepare timestamp regexp %s",
+ warning(_("Cannot prepare timestamp regexp %s"),
stamp_regexp);
return 0;
}
@@ -781,7 +821,7 @@ static int has_epoch_timestamp(const char *nameline)
status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
if (status) {
if (status != REG_NOMATCH)
- warning("regexec returned %d for input: %s",
+ warning(_("regexec returned %d for input: %s"),
status, timestamp);
return 0;
}
@@ -844,8 +884,10 @@ static void parse_traditional_patch(const char *first, const char *second, struc
name = find_name_traditional(first, NULL, p_value);
patch->old_name = name;
} else {
- name = find_name_traditional(first, NULL, p_value);
- name = find_name_traditional(second, name, p_value);
+ char *first_name;
+ first_name = find_name_traditional(first, NULL, p_value);
+ name = find_name_traditional(second, first_name, p_value);
+ free(first_name);
if (has_epoch_timestamp(first)) {
patch->is_new = 1;
patch->is_delete = 0;
@@ -855,11 +897,12 @@ static void parse_traditional_patch(const char *first, const char *second, struc
patch->is_delete = 1;
patch->old_name = name;
} else {
- patch->old_name = patch->new_name = name;
+ patch->old_name = name;
+ patch->new_name = xstrdup(name);
}
}
if (!name)
- die("unable to find filename in patch at line %d", linenr);
+ die(_("unable to find filename in patch at line %d"), linenr);
}
static int gitdiff_hdrend(const char *line, struct patch *patch)
@@ -876,7 +919,10 @@ static int gitdiff_hdrend(const char *line, struct patch *patch)
* their names against any previous information, just
* to make sure..
*/
-static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
+#define DIFF_OLD_NAME 0
+#define DIFF_NEW_NAME 1
+
+static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, int side)
{
if (!orig_name && !isnull)
return find_name(line, NULL, p_value, TERM_TAB);
@@ -888,30 +934,40 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
name = orig_name;
len = strlen(name);
if (isnull)
- die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
+ die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr);
another = find_name(line, NULL, p_value, TERM_TAB);
if (!another || memcmp(another, name, len + 1))
- die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+ die((side == DIFF_NEW_NAME) ?
+ _("git apply: bad git-diff - inconsistent new filename on line %d") :
+ _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr);
free(another);
return orig_name;
}
else {
/* expect "/dev/null" */
if (memcmp("/dev/null", line, 9) || line[9] != '\n')
- die("git apply: bad git-diff - expected /dev/null on line %d", linenr);
+ die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr);
return NULL;
}
}
static int gitdiff_oldname(const char *line, struct patch *patch)
{
- patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
+ char *orig = patch->old_name;
+ patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name,
+ DIFF_OLD_NAME);
+ if (orig != patch->old_name)
+ free(orig);
return 0;
}
static int gitdiff_newname(const char *line, struct patch *patch)
{
- patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
+ char *orig = patch->new_name;
+ patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name,
+ DIFF_NEW_NAME);
+ if (orig != patch->new_name)
+ free(orig);
return 0;
}
@@ -930,20 +986,23 @@ static int gitdiff_newmode(const char *line, struct patch *patch)
static int gitdiff_delete(const char *line, struct patch *patch)
{
patch->is_delete = 1;
- patch->old_name = patch->def_name;
+ free(patch->old_name);
+ patch->old_name = null_strdup(patch->def_name);
return gitdiff_oldmode(line, patch);
}
static int gitdiff_newfile(const char *line, struct patch *patch)
{
patch->is_new = 1;
- patch->new_name = patch->def_name;
+ free(patch->new_name);
+ patch->new_name = null_strdup(patch->def_name);
return gitdiff_newmode(line, patch);
}
static int gitdiff_copysrc(const char *line, struct patch *patch)
{
patch->is_copy = 1;
+ free(patch->old_name);
patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -951,6 +1010,7 @@ static int gitdiff_copysrc(const char *line, struct patch *patch)
static int gitdiff_copydst(const char *line, struct patch *patch)
{
patch->is_copy = 1;
+ free(patch->new_name);
patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -958,6 +1018,7 @@ static int gitdiff_copydst(const char *line, struct patch *patch)
static int gitdiff_renamesrc(const char *line, struct patch *patch)
{
patch->is_rename = 1;
+ free(patch->old_name);
patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -965,6 +1026,7 @@ static int gitdiff_renamesrc(const char *line, struct patch *patch)
static int gitdiff_renamedst(const char *line, struct patch *patch)
{
patch->is_rename = 1;
+ free(patch->new_name);
patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -1046,7 +1108,7 @@ static const char *stop_at_slash(const char *line, int llen)
* creation or deletion of an empty file. In any of these cases,
* both sides are the same name under a/ and b/ respectively.
*/
-static char *git_header_name(char *line, int llen)
+static char *git_header_name(const char *line, int llen)
{
const char *name;
const char *second = NULL;
@@ -1173,7 +1235,7 @@ static char *git_header_name(char *line, int llen)
}
/* Verify that we recognize the lines following a git header */
-static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
+static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch)
{
unsigned long offset;
@@ -1289,7 +1351,7 @@ static int parse_range(const char *line, int len, int offset, const char *expect
return offset + ex;
}
-static void recount_diff(char *line, int size, struct fragment *fragment)
+static void recount_diff(const char *line, int size, struct fragment *fragment)
{
int oldlines = 0, newlines = 0, ret = 0;
@@ -1329,7 +1391,7 @@ static void recount_diff(char *line, int size, struct fragment *fragment)
break;
}
if (ret) {
- warning("recount: unexpected line: %.*s",
+ warning(_("recount: unexpected line: %.*s"),
(int)linelen(line, size), line);
return;
}
@@ -1343,7 +1405,7 @@ static void recount_diff(char *line, int size, struct fragment *fragment)
* Parse a unified diff fragment header of the
* form "@@ -a,b +c,d @@"
*/
-static int parse_fragment_header(char *line, int len, struct fragment *fragment)
+static int parse_fragment_header(const char *line, int len, struct fragment *fragment)
{
int offset;
@@ -1357,7 +1419,7 @@ static int parse_fragment_header(char *line, int len, struct fragment *fragment)
return offset;
}
-static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
+static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch)
{
unsigned long offset, len;
@@ -1386,7 +1448,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
struct fragment dummy;
if (parse_fragment_header(line, len, &dummy) < 0)
continue;
- die("patch fragment without header at line %d: %.*s",
+ die(_("patch fragment without header at line %d: %.*s"),
linenr, (int)len-1, line);
}
@@ -1403,10 +1465,18 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
continue;
if (!patch->old_name && !patch->new_name) {
if (!patch->def_name)
- die("git diff header lacks filename information when removing "
- "%d leading pathname components (line %d)" , p_value, linenr);
- patch->old_name = patch->new_name = patch->def_name;
+ die(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component (line %d)",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components (line %d)",
+ p_value),
+ p_value, linenr);
+ patch->old_name = xstrdup(patch->def_name);
+ patch->new_name = xstrdup(patch->def_name);
}
+ if (!patch->is_delete && !patch->new_name)
+ die("git diff header lacks filename information "
+ "(line %d)", linenr);
patch->is_toplevel_relative = 1;
*hdrsize = git_hdr_len;
return offset;
@@ -1465,7 +1535,7 @@ static void check_whitespace(const char *line, int len, unsigned ws_rule)
* 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,
+static int parse_fragment(const char *line, unsigned long size,
struct patch *patch, struct fragment *fragment)
{
int added, deleted;
@@ -1555,13 +1625,21 @@ static int parse_fragment(char *line, unsigned long size,
patch->lines_deleted += deleted;
if (0 < patch->is_new && oldlines)
- return error("new file depends on old contents");
+ return error(_("new file depends on old contents"));
if (0 < patch->is_delete && newlines)
- return error("deleted file still has contents");
+ return error(_("deleted file still has contents"));
return offset;
}
-static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+/*
+ * We have seen "diff --git a/... b/..." header (or a traditional patch
+ * header). Read hunks that belong to this patch into fragments and hang
+ * them to the given patch structure.
+ *
+ * The (fragment->patch, fragment->size) pair points into the memory given
+ * by the caller, not a copy, when we return.
+ */
+static int parse_single_patch(const char *line, unsigned long size, struct patch *patch)
{
unsigned long offset = 0;
unsigned long oldlines = 0, newlines = 0, context = 0;
@@ -1575,7 +1653,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
fragment->linenr = linenr;
len = parse_fragment(line, size, patch, fragment);
if (len <= 0)
- die("corrupt patch at line %d", linenr);
+ die(_("corrupt patch at line %d"), linenr);
fragment->patch = line;
fragment->size = len;
oldlines += fragment->oldlines;
@@ -1611,12 +1689,14 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
patch->is_delete = 0;
if (0 < patch->is_new && oldlines)
- die("new file %s depends on old contents", patch->new_name);
+ die(_("new file %s depends on old contents"), patch->new_name);
if (0 < patch->is_delete && newlines)
- die("deleted file %s still has contents", patch->old_name);
+ die(_("deleted file %s still has contents"), patch->old_name);
if (!patch->is_delete && !newlines && context)
- fprintf(stderr, "** warning: file %s becomes empty but "
- "is not deleted\n", patch->new_name);
+ fprintf_ln(stderr,
+ _("** warning: "
+ "file %s becomes empty but is not deleted"),
+ patch->new_name);
return offset;
}
@@ -1654,6 +1734,11 @@ static char *inflate_it(const void *data, unsigned long size,
return out;
}
+/*
+ * Read a binary hunk and return a new fragment; fragment->patch
+ * points at an allocated memory that the caller must free, so
+ * it is marked as "->free_patch = 1".
+ */
static struct fragment *parse_binary_hunk(char **buf_p,
unsigned long *sz_p,
int *status_p,
@@ -1741,6 +1826,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
frag = xcalloc(1, sizeof(*frag));
frag->patch = inflate_it(data, hunk_size, origlen);
+ frag->free_patch = 1;
if (!frag->patch)
goto corrupt;
free(data);
@@ -1754,7 +1840,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
corrupt:
free(data);
*status_p = -1;
- error("corrupt binary patch at line %d: %.*s",
+ error(_("corrupt binary patch at line %d: %.*s"),
linenr-1, llen-1, buffer);
return NULL;
}
@@ -1783,7 +1869,7 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
forward = parse_binary_hunk(&buffer, &size, &status, &used);
if (!forward && !status)
/* there has to be one hunk (forward hunk) */
- return error("unrecognized binary patch at line %d", linenr-1);
+ return error(_("unrecognized binary patch at line %d"), linenr-1);
if (status)
/* otherwise we already gave an error message */
return status;
@@ -1806,6 +1892,13 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
return used;
}
+/*
+ * Read the patch text in "buffer" taht extends for "size" bytes; stop
+ * reading after seeing a single patch (i.e. changes to a single file).
+ * Create fragments (i.e. patch hunks) and hang them to the given patch.
+ * Return the number of bytes consumed, so that the caller can call us
+ * again for the next patch.
+ */
static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
{
int hdrsize, patchsize;
@@ -1862,7 +1955,7 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
*/
if ((apply || check) &&
(!patch->is_binary && !metadata_changes(patch)))
- die("patch with only garbage at line %d", linenr);
+ die(_("patch with only garbage at line %d"), linenr);
}
return offset + hdrsize + patchsize;
@@ -1952,11 +2045,11 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
switch (st->st_mode & S_IFMT) {
case S_IFLNK:
if (strbuf_readlink(buf, path, st->st_size) < 0)
- return error("unable to read symlink %s", path);
+ return error(_("unable to read symlink %s"), path);
return 0;
case S_IFREG:
if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
- return error("unable to open or read %s", path);
+ return error(_("unable to open or read %s"), path);
convert_to_git(path, buf->buf, buf->len, buf, 0);
return 0;
default:
@@ -2027,7 +2120,7 @@ static void update_pre_post_images(struct image *preimage,
ctx++;
}
if (preimage->nr <= ctx)
- die("oops");
+ die(_("oops"));
/* and copy it in, while fixing the line length */
len = preimage->line[ctx].len;
@@ -2366,6 +2459,11 @@ static void remove_last_line(struct image *img)
img->len -= img->line[--img->nr].len;
}
+/*
+ * The change from "preimage" and "postimage" has been found to
+ * apply at applied_pos (counts in line numbers) in "img".
+ * Update "img" to remove "preimage" and replace it with "postimage".
+ */
static void update_image(struct image *img,
int applied_pos,
struct image *preimage,
@@ -2437,6 +2535,11 @@ static void update_image(struct image *img,
img->nr = nr;
}
+/*
+ * Use the patch-hunk text in "frag" to prepare two images (preimage and
+ * postimage) for the hunk. Find lines that match "preimage" in "img" and
+ * replace the part of "img" with "postimage" text.
+ */
static int apply_one_fragment(struct image *img, struct fragment *frag,
int inaccurate_eof, unsigned ws_rule,
int nth_fragment)
@@ -2447,6 +2550,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
char *old, *oldlines;
struct strbuf newlines;
int new_blank_lines_at_end = 0;
+ int found_new_blank_lines_at_end = 0;
+ int hunk_linenr = frag->linenr;
unsigned long leading, trailing;
int pos, applied_pos;
struct image preimage;
@@ -2537,17 +2642,21 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
break;
default:
if (apply_verbosely)
- error("invalid start of line: '%c'", first);
+ error(_("invalid start of line: '%c'"), first);
return -1;
}
- if (added_blank_line)
+ if (added_blank_line) {
+ if (!new_blank_lines_at_end)
+ found_new_blank_lines_at_end = hunk_linenr;
new_blank_lines_at_end++;
+ }
else if (is_blank_context)
;
else
new_blank_lines_at_end = 0;
patch += len;
size -= len;
+ hunk_linenr++;
}
if (inaccurate_eof &&
old > oldlines && old[-1] == '\n' &&
@@ -2629,7 +2738,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
preimage.nr + applied_pos >= img->nr &&
(ws_rule & WS_BLANK_AT_EOF) &&
ws_error_action != nowarn_ws_error) {
- record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr);
+ record_ws_error(WS_BLANK_AT_EOF, "+", 1,
+ found_new_blank_lines_at_end);
if (ws_error_action == correct_ws_error) {
while (new_blank_lines_at_end--)
remove_last_line(&postimage);
@@ -2649,9 +2759,11 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
int offset = applied_pos - pos;
if (apply_in_reverse)
offset = 0 - offset;
- fprintf(stderr,
- "Hunk #%d succeeded at %d (offset %d lines).\n",
- nth_fragment, applied_pos + 1, offset);
+ fprintf_ln(stderr,
+ Q_("Hunk #%d succeeded at %d (offset %d line).",
+ "Hunk #%d succeeded at %d (offset %d lines).",
+ offset),
+ nth_fragment, applied_pos + 1, offset);
}
/*
@@ -2660,13 +2772,13 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
*/
if ((leading != frag->leading) ||
(trailing != frag->trailing))
- fprintf(stderr, "Context reduced to (%ld/%ld)"
- " to apply fragment at %d\n",
- leading, trailing, applied_pos+1);
+ fprintf_ln(stderr, _("Context reduced to (%ld/%ld)"
+ " to apply fragment at %d"),
+ leading, trailing, applied_pos+1);
update_image(img, applied_pos, &preimage, &postimage);
} else {
if (apply_verbosely)
- error("while searching for:\n%.*s",
+ error(_("while searching for:\n%.*s"),
(int)(old - oldlines), oldlines);
}
@@ -2685,7 +2797,7 @@ static int apply_binary_fragment(struct image *img, struct patch *patch)
void *dst;
if (!fragment)
- return error("missing binary patch data for '%s'",
+ return error(_("missing binary patch data for '%s'"),
patch->new_name ?
patch->new_name :
patch->old_name);
@@ -2720,6 +2832,12 @@ static int apply_binary_fragment(struct image *img, struct patch *patch)
return -1;
}
+/*
+ * Replace "img" with the result of applying the binary patch.
+ * The binary patch data itself in patch->fragment is still kept
+ * but the preimage prepared by the caller in "img" is freed here
+ * or in the helper function apply_binary_fragment() this calls.
+ */
static int apply_binary(struct image *img, struct patch *patch)
{
const char *name = patch->old_name ? patch->old_name : patch->new_name;
@@ -2782,13 +2900,13 @@ static int apply_binary(struct image *img, struct patch *patch)
* in the patch->fragments->{patch,size}.
*/
if (apply_binary_fragment(img, patch))
- return error("binary patch does not apply to '%s'",
+ return error(_("binary patch does not apply to '%s'"),
name);
/* verify that the result matches */
hash_sha1_file(img->buf, img->len, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
- return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
+ return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
name, patch->new_sha1_prefix, sha1_to_hex(sha1));
}
@@ -2809,7 +2927,7 @@ static int apply_fragments(struct image *img, struct patch *patch)
while (frag) {
nth++;
if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) {
- error("patch failed: %s:%ld", name, frag->oldpos);
+ error(_("patch failed: %s:%ld"), name, frag->oldpos);
if (!apply_with_reject)
return -1;
frag->rejected = 1;
@@ -2924,14 +3042,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
if (!(patch->is_copy || patch->is_rename) &&
(tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
if (was_deleted(tpatch)) {
- return error("patch %s has been renamed/deleted",
+ return error(_("patch %s has been renamed/deleted"),
patch->old_name);
}
- /* We have a patched copy in memory use that */
+ /* We have a patched copy in memory; use that. */
strbuf_add(&buf, tpatch->result, tpatch->resultsize);
} else if (cached) {
if (read_file_or_gitlink(ce, &buf))
- return error("read of %s failed", patch->old_name);
+ return error(_("read of %s failed"), patch->old_name);
} else if (patch->old_name) {
if (S_ISGITLINK(patch->old_mode)) {
if (ce) {
@@ -2940,12 +3058,15 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
/*
* There is no way to apply subproject
* patch without looking at the index.
+ * NEEDSWORK: shouldn't this be flagged
+ * as an error???
*/
+ free_fragment_list(patch->fragments);
patch->fragments = NULL;
}
} else {
if (read_old_data(st, patch->old_name, &buf))
- return error("read of %s failed", patch->old_name);
+ return error(_("read of %s failed"), patch->old_name);
}
}
@@ -2960,7 +3081,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
free(image.line_allocated);
if (0 < patch->is_delete && patch->resultsize)
- return error("removal patch leaves file contents");
+ return error(_("removal patch leaves file contents"));
return 0;
}
@@ -2981,7 +3102,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists)
if (has_symlink_leading_path(new_name, strlen(new_name)))
return 0;
- return error("%s: already exists in working directory", new_name);
+ return error(_("%s: already exists in working directory"), new_name);
}
else if ((errno != ENOENT) && (errno != ENOTDIR))
return error("%s: %s", new_name, strerror(errno));
@@ -3019,12 +3140,12 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
if (!(patch->is_copy || patch->is_rename) &&
(tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
if (was_deleted(tpatch))
- return error("%s: has been deleted/renamed", old_name);
+ return error(_("%s: has been deleted/renamed"), old_name);
st_mode = tpatch->new_mode;
} else if (!cached) {
stat_ret = lstat(old_name, st);
if (stat_ret && errno != ENOENT)
- return error("%s: %s", old_name, strerror(errno));
+ return error(_("%s: %s"), old_name, strerror(errno));
}
if (to_be_deleted(tpatch))
@@ -3035,7 +3156,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
if (pos < 0) {
if (patch->is_new < 0)
goto is_new;
- return error("%s: does not exist in index", old_name);
+ return error(_("%s: does not exist in index"), old_name);
}
*ce = active_cache[pos];
if (stat_ret < 0) {
@@ -3049,13 +3170,13 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
return -1;
}
if (!cached && verify_index_match(*ce, st))
- return error("%s: does not match index", old_name);
+ return error(_("%s: does not match index"), old_name);
if (cached)
st_mode = (*ce)->ce_mode;
} else if (stat_ret < 0) {
if (patch->is_new < 0)
goto is_new;
- return error("%s: %s", old_name, strerror(errno));
+ return error(_("%s: %s"), old_name, strerror(errno));
}
if (!cached && !tpatch)
@@ -3066,9 +3187,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
if (!patch->old_mode)
patch->old_mode = st_mode;
if ((st_mode ^ patch->old_mode) & S_IFMT)
- return error("%s: wrong type", old_name);
+ return error(_("%s: wrong type"), old_name);
if (st_mode != patch->old_mode)
- warning("%s has type %o, expected %o",
+ warning(_("%s has type %o, expected %o"),
old_name, st_mode, patch->old_mode);
if (!patch->new_mode && !patch->is_delete)
patch->new_mode = st_mode;
@@ -3077,10 +3198,15 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
is_new:
patch->is_new = 1;
patch->is_delete = 0;
+ free(patch->old_name);
patch->old_name = NULL;
return 0;
}
+/*
+ * Check and apply the patch in-core; leave the result in patch->result
+ * for the caller to write it out to the final destination.
+ */
static int check_patch(struct patch *patch)
{
struct stat st;
@@ -3118,7 +3244,7 @@ static int check_patch(struct patch *patch)
if (check_index &&
cache_name_pos(new_name, strlen(new_name)) >= 0 &&
!ok_if_exists)
- return error("%s: already exists in index", new_name);
+ return error(_("%s: already exists in index"), new_name);
if (!cached) {
int err = check_to_create_blob(new_name, ok_if_exists);
if (err)
@@ -3136,14 +3262,22 @@ static int check_patch(struct patch *patch)
int same = !strcmp(old_name, new_name);
if (!patch->new_mode)
patch->new_mode = patch->old_mode;
- if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
- return error("new mode (%o) of %s does not match old mode (%o)%s%s",
- patch->new_mode, new_name, patch->old_mode,
- same ? "" : " of ", same ? "" : old_name);
+ if ((patch->old_mode ^ patch->new_mode) & S_IFMT) {
+ if (same)
+ return error(_("new mode (%o) of %s does not "
+ "match old mode (%o)"),
+ patch->new_mode, new_name,
+ patch->old_mode);
+ else
+ return error(_("new mode (%o) of %s does not "
+ "match old mode (%o) of %s"),
+ patch->new_mode, new_name,
+ patch->old_mode, old_name);
+ }
}
if (apply_data(patch, &st, ce) < 0)
- return error("%s: patch does not apply", name);
+ return error(_("%s: patch does not apply"), name);
patch->rejected = 0;
return 0;
}
@@ -3156,7 +3290,7 @@ static int check_patch_list(struct patch *patch)
while (patch) {
if (apply_verbosely)
say_patch_name(stderr,
- "Checking patch ", patch, "...\n");
+ _("Checking patch %s..."), patch);
err |= check_patch(patch);
patch = patch->next;
}
@@ -3211,7 +3345,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename)
ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
if (!ce)
- die("make_cache_entry failed for path '%s'", name);
+ die(_("make_cache_entry failed for path '%s'"), name);
if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
die ("Could not add %s to temporary index", name);
}
@@ -3234,7 +3368,7 @@ static void stat_patch_list(struct patch *patch)
show_stats(patch);
}
- printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+ print_stat_summary(stdout, files, adds, dels);
}
static void numstat_patch_list(struct patch *patch)
@@ -3354,7 +3488,7 @@ static void remove_file(struct patch *patch, int rmdir_empty)
{
if (update_index) {
if (remove_file_from_cache(patch->old_name) < 0)
- die("unable to remove %s from index", patch->old_name);
+ die(_("unable to remove %s from index"), patch->old_name);
}
if (!cached) {
if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
@@ -3381,19 +3515,19 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
const char *s = buf;
if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
- die("corrupt patch for subproject %s", path);
+ die(_("corrupt patch for subproject %s"), path);
} else {
if (!cached) {
if (lstat(path, &st) < 0)
- die_errno("unable to stat newly created file '%s'",
+ die_errno(_("unable to stat newly created file '%s'"),
path);
fill_stat_cache_info(ce, &st);
}
if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
- die("unable to create backing store for newly created file %s", path);
+ die(_("unable to create backing store for newly created file %s"), path);
}
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
- die("unable to add cache entry for %s", path);
+ die(_("unable to add cache entry for %s"), path);
}
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
@@ -3426,7 +3560,7 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
strbuf_release(&nbuf);
if (close(fd) < 0)
- die_errno("closing file '%s'", path);
+ die_errno(_("closing file '%s'"), path);
return 0;
}
@@ -3475,7 +3609,7 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
++nr;
}
}
- die_errno("unable to write file '%s' mode %o", path, mode);
+ die_errno(_("unable to write file '%s' mode %o"), path, mode);
}
static void create_file(struct patch *patch)
@@ -3520,6 +3654,7 @@ static int write_out_one_reject(struct patch *patch)
char namebuf[PATH_MAX];
struct fragment *frag;
int cnt = 0;
+ struct strbuf sb = STRBUF_INIT;
for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
if (!frag->rejected)
@@ -3530,7 +3665,7 @@ static int write_out_one_reject(struct patch *patch)
if (!cnt) {
if (apply_verbosely)
say_patch_name(stderr,
- "Applied patch ", patch, " cleanly.\n");
+ _("Applied patch %s cleanly."), patch);
return 0;
}
@@ -3538,16 +3673,20 @@ static int write_out_one_reject(struct patch *patch)
* contents are marked "rejected" at the patch level.
*/
if (!patch->new_name)
- die("internal error");
+ die(_("internal error"));
/* Say this even without --verbose */
- say_patch_name(stderr, "Applying patch ", patch, " with");
- fprintf(stderr, " %d rejects...\n", cnt);
+ strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...",
+ "Applying patch %%s with %d rejects...",
+ cnt),
+ cnt);
+ say_patch_name(stderr, sb.buf, patch);
+ strbuf_release(&sb);
cnt = strlen(patch->new_name);
if (ARRAY_SIZE(namebuf) <= cnt + 5) {
cnt = ARRAY_SIZE(namebuf) - 5;
- warning("truncating .rej filename to %.*s.rej",
+ warning(_("truncating .rej filename to %.*s.rej"),
cnt - 1, patch->new_name);
}
memcpy(namebuf, patch->new_name, cnt);
@@ -3555,7 +3694,7 @@ static int write_out_one_reject(struct patch *patch)
rej = fopen(namebuf, "w");
if (!rej)
- return error("cannot open %s: %s", namebuf, strerror(errno));
+ return error(_("cannot open %s: %s"), namebuf, strerror(errno));
/* Normal git tools never deal with .rej, so do not pretend
* this is a git patch by saying --git nor give extended
@@ -3568,10 +3707,10 @@ static int write_out_one_reject(struct patch *patch)
frag;
cnt++, frag = frag->next) {
if (!frag->rejected) {
- fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+ fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt);
continue;
}
- fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+ fprintf_ln(stderr, _("Rejected hunk #%d."), cnt);
fprintf(rej, "%.*s", frag->size, frag->patch);
if (frag->patch[frag->size-1] != '\n')
fputc('\n', rej);
@@ -3580,15 +3719,12 @@ static int write_out_one_reject(struct patch *patch)
return -1;
}
-static int write_out_results(struct patch *list, int skipped_patch)
+static int write_out_results(struct patch *list)
{
int phase;
int errs = 0;
struct patch *l;
- if (!list && !skipped_patch)
- return error("No changes");
-
for (phase = 0; phase < 2; phase++) {
l = list;
while (l) {
@@ -3660,15 +3796,8 @@ static void prefix_patches(struct patch *p)
if (!prefix || p->is_toplevel_relative)
return;
for ( ; p; p = p->next) {
- if (p->new_name == p->old_name) {
- char *prefixed = p->new_name;
- prefix_one(&prefixed);
- p->new_name = p->old_name = prefixed;
- }
- else {
- prefix_one(&p->new_name);
- prefix_one(&p->old_name);
- }
+ prefix_one(&p->new_name);
+ prefix_one(&p->old_name);
}
}
@@ -3678,12 +3807,10 @@ static void prefix_patches(struct patch *p)
static int apply_patch(int fd, const char *filename, int options)
{
size_t offset;
- struct strbuf buf = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT; /* owns the patch text */
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
- /* FIXME - memory leak when using multiple patch files as inputs */
- memset(&fn_table, 0, sizeof(struct string_list));
patch_input_file = filename;
read_patch_file(&buf, fd);
offset = 0;
@@ -3707,13 +3834,15 @@ static int apply_patch(int fd, const char *filename, int options)
listp = &patch->next;
}
else {
- /* perhaps free it a bit better? */
- free(patch);
+ free_patch(patch);
skipped_patch++;
}
offset += nr;
}
+ if (!list && !skipped_patch)
+ die(_("unrecognized input"));
+
if (whitespace_error && (ws_error_action == die_on_ws_error))
apply = 0;
@@ -3723,7 +3852,7 @@ static int apply_patch(int fd, const char *filename, int options)
if (check_index) {
if (read_cache() < 0)
- die("unable to read index file");
+ die(_("unable to read index file"));
}
if ((check || apply) &&
@@ -3731,7 +3860,7 @@ static int apply_patch(int fd, const char *filename, int options)
!apply_with_reject)
exit(1);
- if (apply && write_out_results(list, skipped_patch))
+ if (apply && write_out_results(list))
exit(1);
if (fake_ancestor)
@@ -3746,7 +3875,9 @@ static int apply_patch(int fd, const char *filename, int options)
if (summary)
summary_patch_list(list);
+ free_patch_list(list);
strbuf_release(&buf);
+ string_list_clear(&fn_table, 0);
return 0;
}
@@ -3831,76 +3962,71 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
int i;
int errs = 0;
int is_not_gitdir = !startup_info->have_repository;
- int binary;
int force_apply = 0;
const char *whitespace_option = NULL;
struct option builtin_apply_options[] = {
- { OPTION_CALLBACK, 0, "exclude", NULL, "path",
- "don't apply changes matching the given path",
+ { OPTION_CALLBACK, 0, "exclude", NULL, N_("path"),
+ N_("don't apply changes matching the given path"),
0, option_parse_exclude },
- { OPTION_CALLBACK, 0, "include", NULL, "path",
- "apply changes matching the given path",
+ { OPTION_CALLBACK, 0, "include", NULL, N_("path"),
+ N_("apply changes matching the given path"),
0, option_parse_include },
- { OPTION_CALLBACK, 'p', NULL, NULL, "num",
- "remove <num> leading slashes from traditional diff paths",
+ { OPTION_CALLBACK, 'p', NULL, NULL, N_("num"),
+ N_("remove <num> leading slashes from traditional diff paths"),
0, option_parse_p },
OPT_BOOLEAN(0, "no-add", &no_add,
- "ignore additions made by the patch"),
+ N_("ignore additions made by the patch")),
OPT_BOOLEAN(0, "stat", &diffstat,
- "instead of applying the patch, output diffstat for the input"),
- { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
- NULL, "old option, now no-op",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
- { OPTION_BOOLEAN, 0, "binary", &binary,
- NULL, "old option, now no-op",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
+ N_("instead of applying the patch, output diffstat for the input")),
+ OPT_NOOP_NOARG(0, "allow-binary-replacement"),
+ OPT_NOOP_NOARG(0, "binary"),
OPT_BOOLEAN(0, "numstat", &numstat,
- "shows number of added and deleted lines in decimal notation"),
+ N_("shows number of added and deleted lines in decimal notation")),
OPT_BOOLEAN(0, "summary", &summary,
- "instead of applying the patch, output a summary for the input"),
+ N_("instead of applying the patch, output a summary for the input")),
OPT_BOOLEAN(0, "check", &check,
- "instead of applying the patch, see if the patch is applicable"),
+ N_("instead of applying the patch, see if the patch is applicable")),
OPT_BOOLEAN(0, "index", &check_index,
- "make sure the patch is applicable to the current index"),
+ N_("make sure the patch is applicable to the current index")),
OPT_BOOLEAN(0, "cached", &cached,
- "apply a patch without touching the working tree"),
+ N_("apply a patch without touching the working tree")),
OPT_BOOLEAN(0, "apply", &force_apply,
- "also apply the patch (use with --stat/--summary/--check)"),
+ N_("also apply the patch (use with --stat/--summary/--check)")),
OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
- "build a temporary index based on embedded index information"),
+ N_("build a temporary index based on embedded index information")),
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- "paths are separated with NUL character",
+ N_("paths are separated with NUL character"),
PARSE_OPT_NOARG, option_parse_z },
OPT_INTEGER('C', NULL, &p_context,
- "ensure at least <n> lines of context match"),
- { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
- "detect new or modified lines that have whitespace errors",
+ N_("ensure at least <n> lines of context match")),
+ { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, N_("action"),
+ N_("detect new or modified lines that have whitespace errors"),
0, option_parse_whitespace },
{ OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
- "ignore changes in whitespace when finding context",
+ N_("ignore changes in whitespace when finding context"),
PARSE_OPT_NOARG, option_parse_space_change },
{ OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
- "ignore changes in whitespace when finding context",
+ N_("ignore changes in whitespace when finding context"),
PARSE_OPT_NOARG, option_parse_space_change },
OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
- "apply the patch in reverse"),
+ N_("apply the patch in reverse")),
OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
- "don't expect at least one line of context"),
+ N_("don't expect at least one line of context")),
OPT_BOOLEAN(0, "reject", &apply_with_reject,
- "leave the rejected hunks in corresponding *.rej files"),
+ N_("leave the rejected hunks in corresponding *.rej files")),
OPT_BOOLEAN(0, "allow-overlap", &allow_overlap,
- "allow overlapping hunks"),
- OPT__VERBOSE(&apply_verbosely, "be verbose"),
+ N_("allow overlapping hunks")),
+ OPT__VERBOSE(&apply_verbosely, N_("be verbose")),
OPT_BIT(0, "inaccurate-eof", &options,
- "tolerate incorrectly detected missing new-line at the end of file",
+ N_("tolerate incorrectly detected missing new-line at the end of file"),
INACCURATE_EOF),
OPT_BIT(0, "recount", &options,
- "do not trust the line counts in the hunk headers",
+ N_("do not trust the line counts in the hunk headers"),
RECOUNT),
- { OPTION_CALLBACK, 0, "directory", NULL, "root",
- "prepend <root> to all filenames",
+ { OPTION_CALLBACK, 0, "directory", NULL, N_("root"),
+ N_("prepend <root> to all filenames"),
0, option_parse_directory },
OPT_END()
};
@@ -3921,10 +4047,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
apply = 0;
if (check_index && is_not_gitdir)
- die("--index outside a repository");
+ die(_("--index outside a repository"));
if (cached) {
if (is_not_gitdir)
- die("--cached outside a repository");
+ die(_("--cached outside a repository"));
check_index = 1;
}
for (i = 0; i < argc; i++) {
@@ -3940,7 +4066,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
fd = open(arg, O_RDONLY);
if (fd < 0)
- die_errno("can't open patch '%s'", arg);
+ die_errno(_("can't open patch '%s'"), arg);
read_stdin = 0;
set_default_whitespace_mode(whitespace_option);
errs |= apply_patch(fd, arg, options);
@@ -3954,32 +4080,32 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
squelch_whitespace_errors < whitespace_error) {
int squelched =
whitespace_error - squelch_whitespace_errors;
- warning("squelched %d "
- "whitespace error%s",
- squelched,
- squelched == 1 ? "" : "s");
+ warning(Q_("squelched %d whitespace error",
+ "squelched %d whitespace errors",
+ squelched),
+ squelched);
}
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" : "");
+ die(Q_("%d line adds whitespace errors.",
+ "%d lines add whitespace errors.",
+ whitespace_error),
+ whitespace_error);
if (applied_after_fixing_ws && apply)
warning("%d line%s applied after"
" fixing whitespace errors.",
applied_after_fixing_ws,
applied_after_fixing_ws == 1 ? "" : "s");
else if (whitespace_error)
- warning("%d line%s add%s whitespace errors.",
- whitespace_error,
- whitespace_error == 1 ? "" : "s",
- whitespace_error == 1 ? "s" : "");
+ warning(Q_("%d line adds whitespace errors.",
+ "%d lines add whitespace errors.",
+ whitespace_error),
+ whitespace_error);
}
if (update_index) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
- die("Unable to write new index file");
+ die(_("Unable to write new index file"));
}
return !!errs;
diff --git a/builtin/archive.c b/builtin/archive.c
index b14eaba..931956d 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -24,7 +24,8 @@ static void create_output_file(const char *output_file)
}
static int run_remote_archiver(int argc, const char **argv,
- const char *remote, const char *exec)
+ const char *remote, const char *exec,
+ const char *name_hint)
{
char buf[LARGE_PACKET_MAX];
int fd[2], i, len, rv;
@@ -37,6 +38,17 @@ static int run_remote_archiver(int argc, const char **argv,
transport = transport_get(_remote, _remote->url[0]);
transport_connect(transport, "git-upload-archive", exec, fd);
+ /*
+ * Inject a fake --format field at the beginning of the
+ * arguments, with the format inferred from our output
+ * filename. This way explicit --format options can override
+ * it.
+ */
+ if (name_hint) {
+ const char *format = archive_format_from_filename(name_hint);
+ if (format)
+ packet_write(fd[1], "argument --format=%s\n", format);
+ }
for (i = 1; i < argc; i++)
packet_write(fd[1], "argument %s\n", argv[i]);
packet_flush(fd[1]);
@@ -49,6 +61,8 @@ static int run_remote_archiver(int argc, const char **argv,
if (strcmp(buf, "ACK")) {
if (len > 5 && !prefixcmp(buf, "NACK "))
die(_("git archive: NACK %s"), buf + 5);
+ if (len > 4 && !prefixcmp(buf, "ERR "))
+ die(_("remote error: %s"), buf + 4);
die(_("git archive: protocol error"));
}
@@ -63,17 +77,6 @@ static int run_remote_archiver(int argc, const char **argv,
return !!rv;
}
-static const char *format_from_name(const char *filename)
-{
- const char *ext = strrchr(filename, '.');
- if (!ext)
- return NULL;
- ext++;
- if (!strcasecmp(ext, "zip"))
- return "--format=zip";
- return NULL;
-}
-
#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \
PARSE_OPT_KEEP_ARGV0 | \
PARSE_OPT_KEEP_UNKNOWN | \
@@ -84,7 +87,6 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
const char *exec = "git-upload-archive";
const char *output = NULL;
const char *remote = NULL;
- const char *format_option = NULL;
struct option local_opts[] = {
OPT_STRING('o', "output", &output, "file",
"write the archive to this file"),
@@ -98,32 +100,13 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, local_opts, NULL,
PARSE_OPT_KEEP_ALL);
- if (output) {
+ if (output)
create_output_file(output);
- format_option = format_from_name(output);
- }
-
- /*
- * We have enough room in argv[] to muck it in place, because
- * --output must have been given on the original command line
- * if we get to this point, and parse_options() must have eaten
- * it, i.e. we can add back one element to the array.
- *
- * We add a fake --format option at the beginning, with the
- * format inferred from our output filename. This way explicit
- * --format options can override it, and the fake option is
- * inserted before any "--" that might have been given.
- */
- if (format_option) {
- memmove(argv + 2, argv + 1, sizeof(*argv) * argc);
- argv[1] = format_option;
- argv[++argc] = NULL;
- }
if (remote)
- return run_remote_archiver(argc, argv, remote, exec);
+ return run_remote_archiver(argc, argv, remote, exec, output);
setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
- return write_archive(argc, argv, prefix, 1);
+ return write_archive(argc, argv, prefix, 1, output, 0);
}
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 5b22639..8d325a5 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -4,16 +4,19 @@
#include "bisect.h"
static const char * const git_bisect_helper_usage[] = {
- "git bisect--helper --next-all",
+ "git bisect--helper --next-all [--no-checkout]",
NULL
};
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
{
int next_all = 0;
+ int no_checkout = 0;
struct option options[] = {
OPT_BOOLEAN(0, "next-all", &next_all,
"perform 'git bisect next'"),
+ OPT_BOOLEAN(0, "no-checkout", &no_checkout,
+ "update BISECT_HEAD instead of checking out the current commit"),
OPT_END()
};
@@ -24,5 +27,5 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
usage_with_options(git_bisect_helper_usage, options);
/* next-all */
- return bisect_next_all(prefix);
+ return bisect_next_all(prefix, no_checkout);
}
diff --git a/builtin/blame.c b/builtin/blame.c
index 3e1f7e1..960c58d 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -88,6 +88,20 @@ struct origin {
char path[FLEX_ARRAY];
};
+static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen,
+ xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
+{
+ xpparam_t xpp = {0};
+ xdemitconf_t xecfg = {0};
+ xdemitcb_t ecb = {NULL};
+
+ xpp.flags = xdl_opts;
+ xecfg.ctxlen = ctxlen;
+ xecfg.hunk_func = hunk_func;
+ ecb.priv = cb_data;
+ return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
+}
+
/*
* Prepare diff_filespec and convert it using diff textconv API
* if the textconv driver exists.
@@ -759,12 +773,14 @@ struct blame_chunk_cb_data {
long tlno;
};
-static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
+static int blame_chunk_cb(long start_a, long count_a,
+ long start_b, long count_b, void *data)
{
struct blame_chunk_cb_data *d = data;
- blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
- d->plno = p_next;
- d->tlno = t_next;
+ blame_chunk(d->sb, d->tlno, d->plno, start_b, d->target, d->parent);
+ d->plno = start_a + count_a;
+ d->tlno = start_b + count_b;
+ return 0;
}
/*
@@ -779,8 +795,7 @@ static int pass_blame_to_parent(struct scoreboard *sb,
int last_in_target;
mmfile_t file_p, file_o;
struct blame_chunk_cb_data d;
- xpparam_t xpp;
- xdemitconf_t xecfg;
+
memset(&d, 0, sizeof(d));
d.sb = sb; d.target = target; d.parent = parent;
last_in_target = find_last_in_target(sb, target);
@@ -791,11 +806,7 @@ static int pass_blame_to_parent(struct scoreboard *sb,
fill_origin_blob(&sb->revs->diffopt, target, &file_o);
num_get_patch++;
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 0;
- xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
+ diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
/* The rest (i.e. anything after tlno) are the same as the parent */
blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
@@ -899,12 +910,15 @@ struct handle_split_cb_data {
long tlno;
};
-static void handle_split_cb(void *data, long same, long p_next, long t_next)
+static int handle_split_cb(long start_a, long count_a,
+ long start_b, long count_b, void *data)
{
struct handle_split_cb_data *d = data;
- handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
- d->plno = p_next;
- d->tlno = t_next;
+ handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent,
+ d->split);
+ d->plno = start_a + count_a;
+ d->tlno = start_b + count_b;
+ return 0;
}
/*
@@ -922,8 +936,7 @@ static void find_copy_in_blob(struct scoreboard *sb,
int cnt;
mmfile_t file_o;
struct handle_split_cb_data d;
- xpparam_t xpp;
- xdemitconf_t xecfg;
+
memset(&d, 0, sizeof(d));
d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
/*
@@ -943,12 +956,8 @@ static void find_copy_in_blob(struct scoreboard *sb,
* file_o is a part of final image we are annotating.
* file_p partially may match that image.
*/
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 1;
memset(split, 0, sizeof(struct blame_entry [3]));
- xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
+ diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
/* remainder, if any, all match the preimage */
handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
}
@@ -1828,16 +1837,14 @@ static int read_ancestry(const char *graft_file)
return 0;
}
-/*
- * How many columns do we need to show line numbers in decimal?
- */
-static int lineno_width(int lines)
+static int update_auto_abbrev(int auto_abbrev, struct origin *suspect)
{
- int i, width;
-
- for (width = 1, i = 10; i <= lines; width++)
- i *= 10;
- return width;
+ const char *uniq = find_unique_abbrev(suspect->commit->object.sha1,
+ auto_abbrev);
+ int len = strlen(uniq);
+ if (auto_abbrev < len)
+ return len;
+ return auto_abbrev;
}
/*
@@ -1850,12 +1857,16 @@ static void find_alignment(struct scoreboard *sb, int *option)
int longest_dst_lines = 0;
unsigned largest_score = 0;
struct blame_entry *e;
+ int compute_auto_abbrev = (abbrev < 0);
+ int auto_abbrev = default_abbrev;
for (e = sb->ent; e; e = e->next) {
struct origin *suspect = e->suspect;
struct commit_info ci;
int num;
+ if (compute_auto_abbrev)
+ auto_abbrev = update_auto_abbrev(auto_abbrev, suspect);
if (strcmp(suspect->path, sb->path))
*option |= OUTPUT_SHOW_NAME;
num = strlen(suspect->path);
@@ -1880,9 +1891,13 @@ static void find_alignment(struct scoreboard *sb, int *option)
if (largest_score < ent_score(sb, e))
largest_score = ent_score(sb, e);
}
- max_orig_digits = lineno_width(longest_src_lines);
- max_digits = lineno_width(longest_dst_lines);
- max_score_digits = lineno_width(largest_score);
+ max_orig_digits = decimal_width(longest_src_lines);
+ max_digits = decimal_width(longest_dst_lines);
+ max_score_digits = decimal_width(largest_score);
+
+ if (compute_auto_abbrev)
+ /* one more abbrev length is needed for the boundary commit */
+ abbrev = auto_abbrev + 1;
}
/*
@@ -2050,14 +2065,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
return 0;
}
- switch (userdiff_config(var, value)) {
- case 0:
- break;
- case -1:
+ if (userdiff_config(var, value) < 0)
return -1;
- default:
- return 0;
- }
return git_default_config(var, value, cb);
}
@@ -2096,6 +2105,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
if (!contents_from || strcmp("-", contents_from)) {
struct stat st;
const char *read_from;
+ char *buf_ptr;
unsigned long buf_len;
if (contents_from) {
@@ -2113,8 +2123,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
switch (st.st_mode & S_IFMT) {
case S_IFREG:
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
- textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len))
- buf.len = buf_len;
+ textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len))
+ strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
die_errno("cannot open or read '%s'", read_from);
break;
@@ -2319,6 +2329,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
+ OPT_BIT(0, "minimal", &xdl_opts, "Spend extra cycles to find better match", XDF_NEED_MINIMAL),
OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
{ OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
@@ -2360,10 +2371,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
parse_done:
argc = parse_options_end(&ctx);
- if (abbrev == -1)
- abbrev = default_abbrev;
- /* one more abbrev length is needed for the boundary commit */
- abbrev++;
+ if (0 < abbrev)
+ /* one more abbrev length is needed for the boundary commit */
+ abbrev++;
if (revs_file && read_ancestry(revs_file))
die_errno("reading graft file '%s' failed", revs_file);
diff --git a/builtin/branch.c b/builtin/branch.c
index 3142daa..0e060f2 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -15,6 +15,8 @@
#include "branch.h"
#include "diff.h"
#include "revision.h"
+#include "string-list.h"
+#include "column.h"
static const char * const builtin_branch_usage[] = {
"git branch [options] [-r | -a] [--merged | --no-merged]",
@@ -53,6 +55,9 @@ static enum merge_filter {
} merge_filter;
static unsigned char merge_filter_ref[20];
+static struct string_list output = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
+
static int parse_branch_color_slot(const char *var, int ofs)
{
if (!strcasecmp(var+ofs, "plain"))
@@ -70,8 +75,10 @@ static int parse_branch_color_slot(const char *var, int ofs)
static int git_branch_config(const char *var, const char *value, void *cb)
{
+ if (!prefixcmp(var, "column."))
+ return git_column_config(var, value, "branch", &colopts);
if (!strcmp(var, "color.branch")) {
- branch_use_color = git_config_colorbool(var, value, -1);
+ branch_use_color = git_config_colorbool(var, value);
return 0;
}
if (!prefixcmp(var, "color.branch.")) {
@@ -88,7 +95,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)
static const char *branch_get_color(enum color_branch ix)
{
- if (branch_use_color > 0)
+ if (want_color(branch_use_color))
return branch_colors[ix];
return "";
}
@@ -104,6 +111,7 @@ static int branch_merged(int kind, const char *name,
*/
struct commit *reference_rev = NULL;
const char *reference_name = NULL;
+ void *reference_name_to_free = NULL;
int merged;
if (kind == REF_LOCAL_BRANCH) {
@@ -114,8 +122,8 @@ static int branch_merged(int kind, const char *name,
branch->merge &&
branch->merge[0] &&
branch->merge[0]->dst &&
- (reference_name =
- resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+ (reference_name = reference_name_to_free =
+ resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1);
}
if (!reference_rev)
@@ -141,29 +149,32 @@ static int branch_merged(int kind, const char *name,
" '%s', even though it is merged to HEAD."),
name, reference_name);
}
+ free(reference_name_to_free);
return merged;
}
-static int delete_branches(int argc, const char **argv, int force, int kinds)
+static int delete_branches(int argc, const char **argv, int force, int kinds,
+ int quiet)
{
struct commit *rev, *head_rev = NULL;
unsigned char sha1[20];
char *name = NULL;
- const char *fmt, *remote;
+ const char *fmt;
int i;
int ret = 0;
+ int remote_branch = 0;
struct strbuf bname = STRBUF_INIT;
switch (kinds) {
case REF_REMOTE_BRANCH:
fmt = "refs/remotes/%s";
- /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */
- remote = _("remote ");
+ /* For subsequent UI messages */
+ remote_branch = 1;
+
force = 1;
break;
case REF_LOCAL_BRANCH:
fmt = "refs/heads/%s";
- remote = "";
break;
default:
die(_("cannot use -a with -d"));
@@ -186,9 +197,10 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
free(name);
name = xstrdup(mkpath(fmt, bname.buf));
- if (!resolve_ref(name, sha1, 1, NULL)) {
- error(_("%sbranch '%s' not found."),
- remote, bname.buf);
+ if (read_ref(name, sha1)) {
+ error(remote_branch
+ ? _("remote branch '%s' not found.")
+ : _("branch '%s' not found."), bname.buf);
ret = 1;
continue;
}
@@ -209,14 +221,19 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
}
if (delete_ref(name, sha1, 0)) {
- error(_("Error deleting %sbranch '%s'"), remote,
+ error(remote_branch
+ ? _("Error deleting remote branch '%s'")
+ : _("Error deleting branch '%s'"),
bname.buf);
ret = 1;
} else {
struct strbuf buf = STRBUF_INIT;
- printf(_("Deleted %sbranch %s (was %s).\n"), remote,
- bname.buf,
- find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ if (!quiet)
+ printf(remote_branch
+ ? _("Deleted remote branch %s (was %s).\n")
+ : _("Deleted branch %s (was %s).\n"),
+ bname.buf,
+ find_unique_abbrev(sha1, DEFAULT_ABBREV));
strbuf_addf(&buf, "branch.%s", bname.buf);
if (git_config_rename_section(buf.buf, NULL) < 0)
warning(_("Update of config-file failed"));
@@ -250,7 +267,7 @@ static char *resolve_symref(const char *src, const char *prefix)
int flag;
const char *dst, *cp;
- dst = resolve_ref(src, sha1, 0, &flag);
+ dst = resolve_ref_unsafe(src, sha1, 0, &flag);
if (!(dst && (flag & REF_ISSYMREF)))
return NULL;
if (prefix && (cp = skip_prefix(dst, prefix)))
@@ -260,9 +277,22 @@ static char *resolve_symref(const char *src, const char *prefix)
struct append_ref_cb {
struct ref_list *ref_list;
+ const char **pattern;
int ret;
};
+static int match_patterns(const char **pattern, const char *refname)
+{
+ if (!*pattern)
+ return 1; /* no pattern always matches */
+ while (*pattern) {
+ if (!fnmatch(*pattern, refname, 0))
+ return 1;
+ pattern++;
+ }
+ return 0;
+}
+
static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
{
struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
@@ -297,6 +327,9 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
if ((kind & ref_list->kinds) == 0)
return 0;
+ if (!match_patterns(cb->pattern, refname))
+ return 0;
+
commit = NULL;
if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
commit = lookup_commit_reference_gently(sha1, 1);
@@ -358,6 +391,7 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
int show_upstream_ref)
{
int ours, theirs;
+ char *ref = NULL;
struct branch *branch = branch_get(branch_name);
if (!stat_tracking_info(branch, &ours, &theirs)) {
@@ -368,16 +402,29 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
return;
}
- strbuf_addch(stat, '[');
if (show_upstream_ref)
- strbuf_addf(stat, "%s: ",
- shorten_unambiguous_ref(branch->merge[0]->dst, 0));
- if (!ours)
- strbuf_addf(stat, _("behind %d] "), theirs);
- else if (!theirs)
- strbuf_addf(stat, _("ahead %d] "), ours);
- else
- strbuf_addf(stat, _("ahead %d, behind %d] "), ours, theirs);
+ ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
+ if (!ours) {
+ if (ref)
+ strbuf_addf(stat, _("[%s: behind %d]"), ref, theirs);
+ else
+ strbuf_addf(stat, _("[behind %d]"), theirs);
+
+ } else if (!theirs) {
+ if (ref)
+ strbuf_addf(stat, _("[%s: ahead %d]"), ref, ours);
+ else
+ strbuf_addf(stat, _("[ahead %d]"), ours);
+ } else {
+ if (ref)
+ strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
+ ref, ours, theirs);
+ else
+ strbuf_addf(stat, _("[ahead %d, behind %d]"),
+ ours, theirs);
+ }
+ strbuf_addch(stat, ' ');
+ free(ref);
}
static int matches_merge_filter(struct commit *commit)
@@ -456,7 +503,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
else if (verbose)
/* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
add_verbose_info(&out, item, verbose, abbrev);
- printf("%s\n", out.buf);
+ if (column_active(colopts)) {
+ assert(!verbose && "--column and --verbose are incompatible");
+ string_list_append(&output, out.buf);
+ } else {
+ printf("%s\n", out.buf);
+ }
strbuf_release(&name);
strbuf_release(&out);
}
@@ -492,7 +544,7 @@ static void show_detached(struct ref_list *ref_list)
}
}
-static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
{
int i;
struct append_ref_cb cb;
@@ -506,11 +558,16 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
if (merge_filter != NO_FILTER)
init_revisions(&ref_list.revs, NULL);
cb.ref_list = &ref_list;
+ cb.pattern = pattern;
cb.ret = 0;
for_each_rawref(append_ref, &cb);
if (merge_filter != NO_FILTER) {
struct commit *filter;
filter = lookup_commit_reference_gently(merge_filter_ref, 0);
+ if (!filter)
+ die("object '%s' does not point to a commit",
+ sha1_to_hex(merge_filter_ref));
+
filter->object.flags |= UNINTERESTING;
add_pending_object(&ref_list.revs,
(struct object *) filter, "");
@@ -523,7 +580,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
detached = (detached && (kinds & REF_LOCAL_BRANCH));
- if (detached)
+ if (detached && match_patterns(pattern, "HEAD"))
show_detached(&ref_list);
for (i = 0; i < ref_list.index; i++) {
@@ -548,9 +605,9 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
static void rename_branch(const char *oldname, const char *newname, int force)
{
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
- unsigned char sha1[20];
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
int recovery = 0;
+ int clobber_head_ok;
if (!oldname)
die(_("cannot rename the current branch while not on any."));
@@ -560,17 +617,19 @@ static void rename_branch(const char *oldname, const char *newname, int force)
* Bad name --- this could be an attempt to rename a
* ref that we used to allow to be created by accident.
*/
- if (resolve_ref(oldref.buf, sha1, 1, NULL))
+ if (ref_exists(oldref.buf))
recovery = 1;
else
die(_("Invalid branch name: '%s'"), oldname);
}
- if (strbuf_check_branch_ref(&newref, newname))
- die(_("Invalid branch name: '%s'"), newname);
+ /*
+ * A command like "git branch -M currentbranch currentbranch" cannot
+ * cause the worktree to become inconsistent with HEAD, so allow it.
+ */
+ clobber_head_ok = !strcmp(oldname, newname);
- if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
- die(_("A branch named '%s' already exists."), newref.buf + 11);
+ validate_new_branchname(newname, &newref, force, clobber_head_ok);
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
oldref.buf, newref.buf);
@@ -610,11 +669,50 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int
return 0;
}
+static const char edit_description[] = "BRANCH_DESCRIPTION";
+
+static int edit_branch_description(const char *branch_name)
+{
+ FILE *fp;
+ int status;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf name = STRBUF_INIT;
+
+ read_branch_desc(&buf, branch_name);
+ if (!buf.len || buf.buf[buf.len-1] != '\n')
+ strbuf_addch(&buf, '\n');
+ strbuf_addf(&buf,
+ "# Please edit the description for the branch\n"
+ "# %s\n"
+ "# Lines starting with '#' will be stripped.\n",
+ branch_name);
+ fp = fopen(git_path(edit_description), "w");
+ if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+ strbuf_release(&buf);
+ return error(_("could not write branch description template: %s"),
+ strerror(errno));
+ }
+ strbuf_reset(&buf);
+ if (launch_editor(git_path(edit_description), &buf, NULL)) {
+ strbuf_release(&buf);
+ return -1;
+ }
+ stripspace(&buf, 1);
+
+ strbuf_addf(&name, "branch.%s.description", branch_name);
+ status = git_config_set(name.buf, buf.buf);
+ strbuf_release(&name);
+ strbuf_release(&buf);
+
+ return status;
+}
+
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, rename = 0, force_create = 0;
- int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
- int reflog = 0;
+ int delete = 0, rename = 0, force_create = 0, list = 0;
+ int verbose = 0, abbrev = -1, detached = 0;
+ int reflog = 0, edit_description = 0;
+ int quiet = 0;
enum branch_track track;
int kinds = REF_LOCAL_BRANCH;
struct commit_list *with_commit = NULL;
@@ -623,12 +721,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_GROUP("Generic options"),
OPT__VERBOSE(&verbose,
"show hash and subject, give twice for upstream branch"),
+ OPT__QUIET(&quiet, "suppress informational messages"),
OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
BRANCH_TRACK_EXPLICIT),
OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
BRANCH_TRACK_OVERRIDE),
OPT__COLOR(&branch_use_color, "use colored output"),
- OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
+ OPT_SET_INT('r', "remotes", &kinds, "act on remote-tracking branches",
REF_REMOTE_BRANCH),
{
OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
@@ -645,13 +744,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT__ABBREV(&abbrev),
OPT_GROUP("Specific git-branch actions:"),
- OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+ OPT_SET_INT('a', "all", &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', "delete", &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', "move", &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(0, "list", &list, "list branch names"),
+ OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"),
+ OPT_BOOLEAN(0, "edit-description", &edit_description,
+ "edit the description for the branch"),
OPT__FORCE(&force_create, "force creation (when already exists)"),
{
OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
@@ -665,6 +767,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
opt_parse_merge_filter, (intptr_t) "HEAD",
},
+ OPT_COLUMN(0, "column", &colopts, "list branches in columns"),
OPT_END(),
};
@@ -673,15 +776,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
git_config(git_branch_config, NULL);
- if (branch_use_color == -1)
- branch_use_color = git_use_color_default;
-
track = git_branch_track;
- head = resolve_ref("HEAD", head_sha1, 0, NULL);
+ head = resolve_refdup("HEAD", head_sha1, 0, NULL);
if (!head)
die(_("Failed to resolve HEAD as a valid ref."));
- head = xstrdup(head);
if (!strcmp(head, "HEAD")) {
detached = 1;
} else {
@@ -691,24 +790,73 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
}
hashcpy(merge_filter_ref, head_sha1);
+
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
- if (!!delete + !!rename + !!force_create > 1)
+
+ if (!delete && !rename && !edit_description && argc == 0)
+ list = 1;
+
+ if (!!delete + !!rename + !!force_create + !!list > 1)
usage_with_options(builtin_branch_usage, options);
+ if (abbrev == -1)
+ abbrev = DEFAULT_ABBREV;
+ finalize_colopts(&colopts, -1);
+ if (verbose) {
+ if (explicitly_enable_column(colopts))
+ die(_("--column and --verbose are incompatible"));
+ colopts = 0;
+ }
+
if (delete)
- return delete_branches(argc, argv, delete > 1, kinds);
- else if (argc == 0)
- return 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) {
+ return delete_branches(argc, argv, delete > 1, kinds, quiet);
+ else if (list) {
+ int ret = print_ref_list(kinds, detached, verbose, abbrev,
+ with_commit, argv);
+ print_columns(&output, colopts, NULL);
+ string_list_clear(&output, 0);
+ return ret;
+ }
+ else if (edit_description) {
+ const char *branch_name;
+ struct strbuf branch_ref = STRBUF_INIT;
+
+ if (detached)
+ die("Cannot give description to detached HEAD");
+ if (!argc)
+ branch_name = head;
+ else if (argc == 1)
+ branch_name = argv[0];
+ else
+ usage_with_options(builtin_branch_usage, options);
+
+ strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
+ if (!ref_exists(branch_ref.buf)) {
+ strbuf_release(&branch_ref);
+
+ if (!argc)
+ return error("No commit on branch '%s' yet.",
+ branch_name);
+ else
+ return error("No such branch '%s'.", branch_name);
+ }
+ strbuf_release(&branch_ref);
+
+ if (edit_branch_description(branch_name))
+ return 1;
+ } else if (rename) {
+ if (argc == 1)
+ rename_branch(head, argv[0], rename > 1);
+ else if (argc == 2)
+ rename_branch(argv[0], argv[1], rename > 1);
+ else
+ usage_with_options(builtin_branch_usage, options);
+ } else if (argc > 0 && argc <= 2) {
if (kinds != REF_LOCAL_BRANCH)
die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
- force_create, reflog, track);
+ force_create, reflog, 0, quiet, track);
} else
usage_with_options(builtin_branch_usage, options);
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 81046a9..92a8a60 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -58,7 +58,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
} else if (!strcmp(cmd, "unbundle")) {
if (!startup_info->have_repository)
die(_("Need a repository to unbundle."));
- return !!unbundle(&header, bundle_fd) ||
+ return !!unbundle(&header, bundle_fd, 0) ||
list_bundle_refs(&header, argc, argv);
} else
usage(builtin_bundle_usage);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 07bd984..36a9104 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -11,6 +11,7 @@
#include "parse-options.h"
#include "diff.h"
#include "userdiff.h"
+#include "streaming.h"
#define BATCH 1
#define BATCH_CHECK 2
@@ -127,6 +128,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
return cmd_ls_tree(2, ls_args, NULL);
}
+ if (type == OBJ_BLOB)
+ return stream_blob_to_fd(1, sha1, NULL, 0);
buf = read_sha1_file(sha1, &type, &size);
if (!buf)
die("Cannot read object %s", obj_name);
@@ -149,6 +152,28 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
break;
case 0:
+ if (type_from_string(exp_type) == OBJ_BLOB) {
+ unsigned char blob_sha1[20];
+ if (sha1_object_info(sha1, NULL) == OBJ_TAG) {
+ enum object_type type;
+ unsigned long size;
+ char *buffer = read_sha1_file(sha1, &type, &size);
+ if (memcmp(buffer, "object ", 7) ||
+ get_sha1_hex(buffer + 7, blob_sha1))
+ die("%s not a valid tag", sha1_to_hex(sha1));
+ free(buffer);
+ } else
+ hashcpy(blob_sha1, sha1);
+
+ if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+ return stream_blob_to_fd(1, blob_sha1, NULL, 0);
+ /*
+ * we attempted to dereference a tag to a blob
+ * and failed; there may be new dereference
+ * mechanisms this code is not aware of.
+ * fall-back to the usual case.
+ */
+ }
buf = read_object_with_reference(sha1, exp_type, &size, NULL);
break;
@@ -226,14 +251,8 @@ static const char * const cat_file_usage[] = {
static int git_cat_file_config(const char *var, const char *value, void *cb)
{
- switch (userdiff_config(var, value)) {
- case 0:
- break;
- case -1:
+ if (userdiff_config(var, value) < 0)
return -1;
- default:
- return 0;
- }
return git_default_config(var, value, cb);
}
diff --git a/builtin/check-attr.c b/builtin/check-attr.c
index 3016d29..44c421e 100644
--- a/builtin/check-attr.c
+++ b/builtin/check-attr.c
@@ -4,28 +4,30 @@
#include "quote.h"
#include "parse-options.h"
+static int all_attrs;
+static int cached_attrs;
static int stdin_paths;
static const char * const check_attr_usage[] = {
-"git check-attr attr... [--] pathname...",
-"git check-attr --stdin attr... < <list-of-paths>",
+"git check-attr [-a | --all | attr...] [--] pathname...",
+"git check-attr --stdin [-a | --all | attr...] < <list-of-paths>",
NULL
};
static int null_term_line;
static const struct option check_attr_options[] = {
+ OPT_BOOLEAN('a', "all", &all_attrs, "report all attributes set on file"),
+ OPT_BOOLEAN(0, "cached", &cached_attrs, "use .gitattributes only from the index"),
OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
OPT_BOOLEAN('z', NULL, &null_term_line,
"input paths are terminated by a null character"),
OPT_END()
};
-static void check_attr(int cnt, struct git_attr_check *check,
- const char** name, const char *file)
+static void output_attr(int cnt, struct git_attr_check *check,
+ const char *file)
{
int j;
- if (git_checkattr(file, cnt, check))
- die("git_checkattr died");
for (j = 0; j < cnt; j++) {
const char *value = check[j].value;
@@ -37,12 +39,30 @@ static void check_attr(int cnt, struct git_attr_check *check,
value = "unspecified";
quote_c_style(file, NULL, stdout, 0);
- printf(": %s: %s\n", name[j], value);
+ printf(": %s: %s\n", git_attr_name(check[j].attr), value);
}
}
-static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
- const char** name)
+static void check_attr(const char *prefix, int cnt,
+ struct git_attr_check *check, const char *file)
+{
+ char *full_path =
+ prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
+ if (check != NULL) {
+ if (git_check_attr(full_path, cnt, check))
+ die("git_check_attr died");
+ output_attr(cnt, check, file);
+ } else {
+ if (git_all_attrs(full_path, &cnt, &check))
+ die("git_all_attrs died");
+ output_attr(cnt, check, file);
+ free(check);
+ }
+ free(full_path);
+}
+
+static void check_attr_stdin_paths(const char *prefix, int cnt,
+ struct git_attr_check *check)
{
struct strbuf buf, nbuf;
int line_termination = null_term_line ? 0 : '\n';
@@ -56,67 +76,99 @@ static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
die("line is badly quoted");
strbuf_swap(&buf, &nbuf);
}
- check_attr(cnt, check, name, buf.buf);
+ check_attr(prefix, cnt, check, buf.buf);
maybe_flush_or_die(stdout, "attribute to stdout");
}
strbuf_release(&buf);
strbuf_release(&nbuf);
}
+static NORETURN void error_with_usage(const char *msg)
+{
+ error("%s", msg);
+ usage_with_options(check_attr_usage, check_attr_options);
+}
+
int cmd_check_attr(int argc, const char **argv, const char *prefix)
{
struct git_attr_check *check;
- int cnt, i, doubledash;
- const char *errstr = NULL;
+ int cnt, i, doubledash, filei;
+
+ git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, check_attr_options,
check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
- if (!argc)
- usage_with_options(check_attr_usage, check_attr_options);
if (read_cache() < 0) {
die("invalid cache");
}
+ if (cached_attrs)
+ git_attr_set_direction(GIT_ATTR_INDEX, NULL);
+
doubledash = -1;
for (i = 0; doubledash < 0 && i < argc; i++) {
if (!strcmp(argv[i], "--"))
doubledash = i;
}
- /* If there is no double dash, we handle only one attribute */
- if (doubledash < 0) {
- cnt = 1;
- doubledash = 0;
- } else
+ /* Process --all and/or attribute arguments: */
+ if (all_attrs) {
+ if (doubledash >= 1)
+ error_with_usage("Attributes and --all both specified");
+
+ cnt = 0;
+ filei = doubledash + 1;
+ } else if (doubledash == 0) {
+ error_with_usage("No attribute specified");
+ } else if (doubledash < 0) {
+ if (!argc)
+ error_with_usage("No attribute specified");
+
+ if (stdin_paths) {
+ /* Treat all arguments as attribute names. */
+ cnt = argc;
+ filei = argc;
+ } else {
+ /* Treat exactly one argument as an attribute name. */
+ cnt = 1;
+ filei = 1;
+ }
+ } else {
cnt = doubledash;
- doubledash++;
-
- if (cnt <= 0)
- errstr = "No attribute specified";
- else if (stdin_paths && doubledash < argc)
- errstr = "Can't specify files with --stdin";
- if (errstr) {
- error("%s", errstr);
- usage_with_options(check_attr_usage, check_attr_options);
+ filei = doubledash + 1;
}
- check = xcalloc(cnt, sizeof(*check));
- for (i = 0; i < cnt; i++) {
- const char *name;
- struct git_attr *a;
- name = argv[i];
- a = git_attr(name);
- if (!a)
- return error("%s: not a valid attribute name", name);
- check[i].attr = a;
+ /* Check file argument(s): */
+ if (stdin_paths) {
+ if (filei < argc)
+ error_with_usage("Can't specify files with --stdin");
+ } else {
+ if (filei >= argc)
+ error_with_usage("No file specified");
+ }
+
+ if (all_attrs) {
+ check = NULL;
+ } else {
+ check = xcalloc(cnt, sizeof(*check));
+ for (i = 0; i < cnt; i++) {
+ const char *name;
+ struct git_attr *a;
+ name = argv[i];
+ a = git_attr(name);
+ if (!a)
+ return error("%s: not a valid attribute name",
+ name);
+ check[i].attr = a;
+ }
}
if (stdin_paths)
- check_attr_stdin_paths(cnt, check, argv);
+ check_attr_stdin_paths(prefix, cnt, check);
else {
- for (i = doubledash; i < argc; i++)
- check_attr(cnt, check, argv, argv[i]);
+ for (i = filei; i < argc; i++)
+ check_attr(prefix, cnt, check, argv[i]);
maybe_flush_or_die(stdout, "attribute to stdout");
}
return 0;
diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c
index 0723cf2..28a7320 100644
--- a/builtin/check-ref-format.c
+++ b/builtin/check-ref-format.c
@@ -8,29 +8,32 @@
#include "strbuf.h"
static const char builtin_check_ref_format_usage[] =
-"git check-ref-format [--print] <refname>\n"
+"git check-ref-format [--normalize] [options] <refname>\n"
" or: git check-ref-format --branch <branchname-shorthand>";
/*
- * Remove leading slashes and replace each run of adjacent slashes in
- * src with a single slash, and write the result to dst.
+ * Return a copy of refname but with leading slashes removed and runs
+ * of adjacent slashes replaced with single slashes.
*
* This function is similar to normalize_path_copy(), but stripped down
* to meet check_ref_format's simpler needs.
*/
-static void collapse_slashes(char *dst, const char *src)
+static char *collapse_slashes(const char *refname)
{
+ char *ret = xmalloc(strlen(refname) + 1);
char ch;
char prev = '/';
+ char *cp = ret;
- while ((ch = *src++) != '\0') {
+ while ((ch = *refname++) != '\0') {
if (prev == '/' && ch == prev)
continue;
- *dst++ = ch;
+ *cp++ = ch;
prev = ch;
}
- *dst = '\0';
+ *cp = '\0';
+ return ret;
}
static int check_ref_format_branch(const char *arg)
@@ -45,27 +48,41 @@ static int check_ref_format_branch(const char *arg)
return 0;
}
-static int check_ref_format_print(const char *arg)
-{
- char *refname = xmalloc(strlen(arg) + 1);
-
- if (check_ref_format(arg))
- return 1;
- collapse_slashes(refname, arg);
- printf("%s\n", refname);
- return 0;
-}
-
int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
{
+ int i;
+ int normalize = 0;
+ int flags = 0;
+ const char *refname;
+
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(builtin_check_ref_format_usage);
if (argc == 3 && !strcmp(argv[1], "--branch"))
return check_ref_format_branch(argv[2]);
- if (argc == 3 && !strcmp(argv[1], "--print"))
- return check_ref_format_print(argv[2]);
- if (argc != 2)
+
+ for (i = 1; i < argc && argv[i][0] == '-'; i++) {
+ if (!strcmp(argv[i], "--normalize") || !strcmp(argv[i], "--print"))
+ normalize = 1;
+ else if (!strcmp(argv[i], "--allow-onelevel"))
+ flags |= REFNAME_ALLOW_ONELEVEL;
+ else if (!strcmp(argv[i], "--no-allow-onelevel"))
+ flags &= ~REFNAME_ALLOW_ONELEVEL;
+ else if (!strcmp(argv[i], "--refspec-pattern"))
+ flags |= REFNAME_REFSPEC_PATTERN;
+ else
+ usage(builtin_check_ref_format_usage);
+ }
+ if (! (i == argc - 1))
usage(builtin_check_ref_format_usage);
- return !!check_ref_format(argv[1]);
+
+ refname = argv[i];
+ if (normalize)
+ refname = collapse_slashes(refname);
+ if (check_refname_format(refname, flags))
+ return 1;
+ if (normalize)
+ printf("%s\n", refname);
+
+ return 0;
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a94b553..3980d5d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -19,6 +19,7 @@
#include "ll-merge.h"
#include "resolve-undo.h"
#include "submodule.h"
+#include "argv-array.h"
static const char * const checkout_usage[] = {
"git checkout [options] <branch>",
@@ -33,6 +34,7 @@ struct checkout_opts {
int force_detach;
int writeout_stage;
int writeout_error;
+ int overwrite_ignore;
/* not set by parse_options */
int branch_exists;
@@ -71,7 +73,7 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
hashcpy(ce->sha1, sha1);
memcpy(ce->name, base, baselen);
memcpy(ce->name + baselen, pathname, len - baselen);
- ce->ce_flags = create_ce_flags(len, 0);
+ ce->ce_flags = create_ce_flags(len, 0) | CE_UPDATE;
ce->ce_mode = create_ce_mode(mode);
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
return 0;
@@ -113,16 +115,21 @@ static int check_stage(int stage, struct cache_entry *ce, int pos)
return error(_("path '%s' does not have their version"), ce->name);
}
-static int check_all_stages(struct cache_entry *ce, int pos)
+static int check_stages(unsigned stages, struct cache_entry *ce, int pos)
{
- if (ce_stage(ce) != 1 ||
- active_nr <= pos + 2 ||
- strcmp(active_cache[pos+1]->name, ce->name) ||
- ce_stage(active_cache[pos+1]) != 2 ||
- strcmp(active_cache[pos+2]->name, ce->name) ||
- ce_stage(active_cache[pos+2]) != 3)
- return error(_("path '%s' does not have all three versions"),
- ce->name);
+ unsigned seen = 0;
+ const char *name = ce->name;
+
+ while (pos < active_nr) {
+ ce = active_cache[pos];
+ if (strcmp(name, ce->name))
+ break;
+ seen |= (1 << ce_stage(ce));
+ pos++;
+ }
+ if ((stages & seen) != stages)
+ return error(_("path '%s' does not have all necessary versions"),
+ name);
return 0;
}
@@ -149,18 +156,27 @@ static int checkout_merged(int pos, struct checkout *state)
int status;
unsigned char sha1[20];
mmbuffer_t result_buf;
+ unsigned char threeway[3][20];
+ unsigned mode = 0;
+
+ memset(threeway, 0, sizeof(threeway));
+ while (pos < active_nr) {
+ int stage;
+ stage = ce_stage(ce);
+ if (!stage || strcmp(path, ce->name))
+ break;
+ hashcpy(threeway[stage - 1], ce->sha1);
+ if (stage == 2)
+ mode = create_ce_mode(ce->ce_mode);
+ pos++;
+ ce = active_cache[pos];
+ }
+ if (is_null_sha1(threeway[1]) || is_null_sha1(threeway[2]))
+ return error(_("path '%s' does not have necessary versions"), path);
- if (ce_stage(ce) != 1 ||
- active_nr <= pos + 2 ||
- strcmp(active_cache[pos+1]->name, path) ||
- ce_stage(active_cache[pos+1]) != 2 ||
- strcmp(active_cache[pos+2]->name, path) ||
- ce_stage(active_cache[pos+2]) != 3)
- return error(_("path '%s' does not have all 3 versions"), path);
-
- read_mmblob(&ancestor, active_cache[pos]->sha1);
- read_mmblob(&ours, active_cache[pos+1]->sha1);
- read_mmblob(&theirs, active_cache[pos+2]->sha1);
+ read_mmblob(&ancestor, threeway[0]);
+ read_mmblob(&ours, threeway[1]);
+ read_mmblob(&theirs, threeway[2]);
/*
* NEEDSWORK: re-create conflicts from merges with
@@ -191,9 +207,7 @@ static int checkout_merged(int pos, struct checkout *state)
if (write_sha1_file(result_buf.ptr, result_buf.size,
blob_type, sha1))
die(_("Unable to add merge result for '%s'"), path);
- ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
- sha1,
- path, 2, 0);
+ ce = make_cache_entry(mode, sha1, path, 2, 0);
if (!ce)
die(_("make_cache_entry failed for path '%s'"), path);
status = checkout_entry(ce, state, NULL);
@@ -228,6 +242,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
+ if (source_tree && !(ce->ce_flags & CE_UPDATE))
+ continue;
match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
}
@@ -249,7 +265,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
} else if (stage) {
errs |= check_stage(stage, ce, pos);
} else if (opts->merge) {
- errs |= check_all_stages(ce, pos);
+ errs |= check_stages((1<<2) | (1<<3), ce, pos);
} else {
errs = 1;
error(_("path '%s' is unmerged"), ce->name);
@@ -266,6 +282,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
state.refresh_cache = 1;
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
+ if (source_tree && !(ce->ce_flags & CE_UPDATE))
+ continue;
if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
if (!ce_stage(ce)) {
errs |= checkout_entry(ce, &state, NULL);
@@ -283,7 +301,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
commit_locked_index(lock_file))
die(_("unable to write new index file"));
- resolve_ref("HEAD", rev, 0, &flag);
+ read_ref_full("HEAD", rev, 0, &flag);
head = lookup_commit_reference_gently(rev, 1);
errs |= post_checkout_hook(head, head, 0);
@@ -325,7 +343,7 @@ static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
opts.reset = 1;
opts.merge = 1;
opts.fn = oneway_merge;
- opts.verbose_update = !o->quiet;
+ opts.verbose_update = !o->quiet && isatty(2);
opts.src_index = &the_index;
opts.dst_index = &the_index;
parse_tree(tree);
@@ -402,11 +420,13 @@ static int merge_working_tree(struct checkout_opts *opts,
topts.update = 1;
topts.merge = 1;
topts.gently = opts->merge && old->commit;
- topts.verbose_update = !opts->quiet;
+ topts.verbose_update = !opts->quiet && isatty(2);
topts.fn = twoway_merge;
- topts.dir = xcalloc(1, sizeof(*topts.dir));
- topts.dir->flags |= DIR_SHOW_IGNORED;
- topts.dir->exclude_per_dir = ".gitignore";
+ if (opts->overwrite_ignore) {
+ topts.dir = xcalloc(1, sizeof(*topts.dir));
+ topts.dir->flags |= DIR_SHOW_IGNORED;
+ setup_standard_excludes(topts.dir);
+ }
tree = parse_tree_indirect(old->commit ?
old->commit->object.sha1 :
EMPTY_TREE_SHA1_BIN);
@@ -494,20 +514,6 @@ static void report_tracking(struct branch_info *new)
strbuf_release(&sb);
}
-static void detach_advice(const char *old_path, const char *new_name)
-{
- const char fmt[] =
- "Note: checking out '%s'.\n\n"
- "You are in 'detached HEAD' state. You can look around, make experimental\n"
- "changes and commit them, and you can discard any commits you make in this\n"
- "state without impacting any branches by performing another checkout.\n\n"
- "If you want to create a new branch to retain commits you create, you may\n"
- "do so (now or later) by using -b with the checkout command again. Example:\n\n"
- " git checkout -b new_branch_name\n\n";
-
- fprintf(stderr, fmt, new_name);
-}
-
static void update_refs_for_switch(struct checkout_opts *opts,
struct branch_info *old,
struct branch_info *new)
@@ -535,7 +541,10 @@ static void update_refs_for_switch(struct checkout_opts *opts,
else
create_branch(old->name, opts->new_branch, new->name,
opts->new_branch_force ? 1 : 0,
- opts->new_branch_log, opts->track);
+ opts->new_branch_log,
+ opts->new_branch_force ? 1 : 0,
+ opts->quiet,
+ opts->track);
new->name = opts->new_branch;
setup_branch_path(new);
}
@@ -553,15 +562,19 @@ static void update_refs_for_switch(struct checkout_opts *opts,
REF_NODEREF, DIE_ON_ERR);
if (!opts->quiet) {
if (old->path && advice_detached_head)
- detach_advice(old->path, new->name);
+ detach_advice(new->name);
describe_detached_head(_("HEAD is now at"), new->commit);
}
} else if (new->path) { /* Switch branches. */
create_symref("HEAD", new->path, msg.buf);
if (!opts->quiet) {
if (old->path && !strcmp(new->path, old->path)) {
- fprintf(stderr, _("Already on '%s'\n"),
- new->name);
+ if (opts->new_branch_force)
+ fprintf(stderr, _("Reset branch '%s'\n"),
+ new->name);
+ else
+ fprintf(stderr, _("Already on '%s'\n"),
+ new->name);
} else if (opts->new_branch) {
if (opts->branch_exists)
fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name);
@@ -588,35 +601,11 @@ static void update_refs_for_switch(struct checkout_opts *opts,
report_tracking(new);
}
-struct rev_list_args {
- int argc;
- int alloc;
- const char **argv;
-};
-
-static void add_one_rev_list_arg(struct rev_list_args *args, const char *s)
-{
- ALLOC_GROW(args->argv, args->argc + 1, args->alloc);
- args->argv[args->argc++] = s;
-}
-
-static int add_one_ref_to_rev_list_arg(const char *refname,
- const unsigned char *sha1,
- int flags,
- void *cb_data)
-{
- add_one_rev_list_arg(cb_data, refname);
- return 0;
-}
-
-static int clear_commit_marks_from_one_ref(const char *refname,
- const unsigned char *sha1,
- int flags,
- void *cb_data)
+static int add_pending_uninteresting_ref(const char *refname,
+ const unsigned char *sha1,
+ int flags, void *cb_data)
{
- struct commit *commit = lookup_commit_reference_gently(sha1, 1);
- if (commit)
- clear_commit_marks(commit, -1);
+ add_pending_sha1(cb_data, refname, sha1, flags | UNINTERESTING);
return 0;
}
@@ -657,24 +646,25 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
"Warning: you are leaving %d commit behind, "
"not connected to\n"
"any of your branches:\n\n"
- "%s\n"
- "If you want to keep it by creating a new branch, "
- "this may be a good time\nto do so with:\n\n"
- " git branch new_branch_name %s\n\n",
+ "%s\n",
/* The plural version */
"Warning: you are leaving %d commits behind, "
"not connected to\n"
"any of your branches:\n\n"
- "%s\n"
- "If you want to keep them by creating a new branch, "
- "this may be a good time\nto do so with:\n\n"
- " git branch new_branch_name %s\n\n",
+ "%s\n",
/* Give ngettext() the count */
lost),
lost,
- sb.buf,
- sha1_to_hex(commit->object.sha1));
+ sb.buf);
strbuf_release(&sb);
+
+ if (advice_detached_head)
+ fprintf(stderr,
+ _(
+ "If you want to keep them by creating a new branch, "
+ "this may be a good time\nto do so with:\n\n"
+ " git branch new_branch_name %s\n\n"),
+ sha1_to_hex(commit->object.sha1));
}
/*
@@ -682,45 +672,47 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
* HEAD. If it is not reachable from any ref, this is the last chance
* for the user to do so without resorting to reflog.
*/
-static void orphaned_commit_warning(struct commit *commit)
+static void orphaned_commit_warning(struct commit *old, struct commit *new)
{
- struct rev_list_args args = { 0, 0, NULL };
struct rev_info revs;
-
- add_one_rev_list_arg(&args, "(internal)");
- add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1));
- add_one_rev_list_arg(&args, "--not");
- for_each_ref(add_one_ref_to_rev_list_arg, &args);
- add_one_rev_list_arg(&args, "--");
- add_one_rev_list_arg(&args, NULL);
+ struct object *object = &old->object;
+ struct object_array refs;
init_revisions(&revs, NULL);
- if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1)
- die(_("internal error: only -- alone should have been left"));
+ setup_revisions(0, NULL, &revs, NULL);
+
+ object->flags &= ~UNINTERESTING;
+ add_pending_object(&revs, object, sha1_to_hex(object->sha1));
+
+ for_each_ref(add_pending_uninteresting_ref, &revs);
+ add_pending_sha1(&revs, "HEAD", new->object.sha1, UNINTERESTING);
+
+ refs = revs.pending;
+ revs.leak_pending = 1;
+
if (prepare_revision_walk(&revs))
die(_("internal error in revision walk"));
- if (!(commit->object.flags & UNINTERESTING))
- suggest_reattach(commit, &revs);
+ if (!(old->object.flags & UNINTERESTING))
+ suggest_reattach(old, &revs);
else
- describe_detached_head(_("Previous HEAD position was"), commit);
+ describe_detached_head(_("Previous HEAD position was"), old);
- clear_commit_marks(commit, -1);
- for_each_ref(clear_commit_marks_from_one_ref, NULL);
+ clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
+ free(refs.objects);
}
static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
{
int ret = 0;
struct branch_info old;
+ void *path_to_free;
unsigned char rev[20];
int flag;
memset(&old, 0, sizeof(old));
- old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag));
+ old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag);
old.commit = lookup_commit_reference_gently(rev, 1);
- if (!(flag & REF_ISSYMREF)) {
- free((char *)old.path);
+ if (!(flag & REF_ISSYMREF))
old.path = NULL;
- }
if (old.path && !prefixcmp(old.path, "refs/heads/"))
old.name = old.path + strlen("refs/heads/");
@@ -734,16 +726,18 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
}
ret = merge_working_tree(opts, &old, new);
- if (ret)
+ if (ret) {
+ free(path_to_free);
return ret;
+ }
if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
- orphaned_commit_warning(old.commit);
+ orphaned_commit_warning(old.commit, new->commit);
update_refs_for_switch(opts, &old, new);
ret = post_checkout_hook(old.commit, new->commit, 1);
- free((char *)old.path);
+ free(path_to_free);
return ret || opts->writeout_error;
}
@@ -881,8 +875,8 @@ static int parse_branchname_arg(int argc, const char **argv,
new->name = arg;
setup_branch_path(new);
- if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
- resolve_ref(new->path, branch_rev, 1, NULL))
+ if (!check_refname_format(new->path, 0) &&
+ !read_ref(new->path, branch_rev))
hashcpy(rev, branch_rev);
else
new->path = NULL; /* not an existing branch */
@@ -955,6 +949,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
3),
OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"),
OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"),
+ OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, "update ignored files (default)"),
OPT_STRING(0, "conflict", &conflict_style, "style",
"conflict style (merge or diff3)"),
OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
@@ -966,6 +961,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
memset(&opts, 0, sizeof(opts));
memset(&new, 0, sizeof(new));
+ opts.overwrite_ignore = 1;
gitmodules_config();
git_config(git_checkout_config, &opts);
@@ -1084,15 +1080,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
if (opts.new_branch) {
struct strbuf buf = STRBUF_INIT;
- if (strbuf_check_branch_ref(&buf, opts.new_branch))
- die(_("git checkout: we do not like '%s' as a branch name."),
- opts.new_branch);
- if (ref_exists(buf.buf)) {
- opts.branch_exists = 1;
- if (!opts.new_branch_force)
- die(_("git checkout: branch %s already exists"),
- opts.new_branch);
- }
+
+ opts.branch_exists = validate_new_branchname(opts.new_branch, &buf,
+ !!opts.new_branch_force,
+ !!opts.new_branch_force);
+
strbuf_release(&buf);
}
@@ -1102,12 +1094,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
if (opts.writeout_stage)
die(_("--ours/--theirs is incompatible with switching branches."));
- if (!new.commit) {
+ if (!new.commit && opts.new_branch) {
unsigned char rev[20];
int flag;
- resolve_ref("HEAD", rev, 0, &flag);
- if ((flag & REF_ISSYMREF) && is_null_sha1(rev))
+ if (!read_ref_full("HEAD", rev, 0, &flag) &&
+ (flag & REF_ISSYMREF) && is_null_sha1(rev))
return switch_unborn_to_new_branch(&opts);
}
return switch_branches(&opts, &new);
diff --git a/builtin/clean.c b/builtin/clean.c
index 75697f7..0c7b3d0 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -54,7 +54,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN('d', NULL, &remove_directories,
"remove whole directories"),
{ OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern",
- "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb },
+ "add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb },
OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
OPT_BOOLEAN('X', NULL, &ignored_only,
"remove only ignored files"),
@@ -98,7 +98,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
setup_standard_excludes(&dir);
for (i = 0; i < exclude_list.nr; i++)
- add_exclude(exclude_list.items[i].string, "", 0, dir.exclude_list);
+ add_exclude(exclude_list.items[i].string, "", 0,
+ &dir.exclude_list[EXC_CMDL]);
pathspec = get_pathspec(prefix, argv);
diff --git a/builtin/clone.c b/builtin/clone.c
index 5f20082..d3b7fdc 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -37,15 +37,16 @@ static const char * const builtin_clone_usage[] = {
NULL
};
-static int option_no_checkout, option_bare, option_mirror;
-static int option_local, option_no_hardlinks, option_shared, option_recursive;
+static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
+static int option_local = -1, option_no_hardlinks, option_shared, option_recursive;
static char *option_template, *option_depth;
static char *option_origin = NULL;
static char *option_branch = NULL;
static const char *real_git_dir;
static char *option_upload_pack = "git-upload-pack";
static int option_verbosity;
-static int option_progress;
+static int option_progress = -1;
+static struct string_list option_config;
static struct string_list option_reference;
static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
@@ -59,8 +60,8 @@ static int opt_parse_reference(const struct option *opt, const char *arg, int un
static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
- OPT_BOOLEAN(0, "progress", &option_progress,
- "force progress reporting"),
+ OPT_BOOL(0, "progress", &option_progress,
+ "force progress reporting"),
OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
"don't create a checkout"),
OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
@@ -69,8 +70,8 @@ static struct option builtin_clone_options[] = {
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
OPT_BOOLEAN(0, "mirror", &option_mirror,
"create a mirror repository (implies bare)"),
- OPT_BOOLEAN('l', "local", &option_local,
- "to clone from a local repository"),
+ OPT_BOOL('l', "local", &option_local,
+ "to clone from a local repository"),
OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
"don't use local hardlinks, always copy"),
OPT_BOOLEAN('s', "shared", &option_shared,
@@ -83,17 +84,20 @@ static struct option builtin_clone_options[] = {
"directory from which templates will be used"),
OPT_CALLBACK(0 , "reference", &option_reference, "repo",
"reference repository", &opt_parse_reference),
- OPT_STRING('o', "origin", &option_origin, "branch",
- "use <branch> instead of 'origin' to track upstream"),
+ OPT_STRING('o', "origin", &option_origin, "name",
+ "use <name> instead of 'origin' to track upstream"),
OPT_STRING('b', "branch", &option_branch, "branch",
"checkout <branch> instead of the remote's HEAD"),
OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
"path to git-upload-pack on the remote"),
OPT_STRING(0, "depth", &option_depth, "depth",
"create a shallow clone of that depth"),
+ OPT_BOOL(0, "single-branch", &option_single_branch,
+ "clone only one branch, HEAD or --branch"),
OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
"separate git dir from working tree"),
-
+ OPT_STRING_LIST('c', "config", &option_config, "key=value",
+ "set config inside the new repository"),
OPT_END()
};
@@ -103,7 +107,7 @@ static const char *argv_submodule[] = {
static char *get_repo_path(const char *repo, int *is_bundle)
{
- static char *suffix[] = { "/.git", ".git", "" };
+ static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
static char *bundle_suffix[] = { ".bundle", "" };
struct stat st;
int i;
@@ -113,7 +117,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
path = mkpath("%s%s", repo, suffix[i]);
if (stat(path, &st))
continue;
- if (S_ISDIR(st.st_mode)) {
+ if (S_ISDIR(st.st_mode) && is_git_directory(path)) {
*is_bundle = 0;
return xstrdup(absolute_path(path));
} else if (S_ISREG(st.st_mode) && st.st_size > 8) {
@@ -228,9 +232,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
{
char *ref_git;
struct strbuf alternate = STRBUF_INIT;
- struct remote *remote;
- struct transport *transport;
- const struct ref *extra;
/* Beware: real_path() and mkpath() return static buffer */
ref_git = xstrdup(real_path(item->string));
@@ -245,14 +246,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
strbuf_addf(&alternate, "%s/objects", ref_git);
add_to_alternates_file(alternate.buf);
strbuf_release(&alternate);
-
- remote = remote_get(ref_git);
- transport = transport_get(remote, ref_git);
- for (extra = transport_get_remote_refs(transport); extra;
- extra = extra->next)
- add_extra_ref(extra->name, extra->old_sha1, 0);
-
- transport_disconnect(transport);
free(ref_git);
return 0;
}
@@ -349,7 +342,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
if (!option_no_hardlinks) {
if (!link(src->buf, dest->buf))
continue;
- if (option_local)
+ if (option_local > 0)
die_errno(_("failed to create link '%s'"), dest->buf);
option_no_hardlinks = 1;
}
@@ -359,13 +352,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
closedir(dir);
}
-static const struct ref *clone_local(const char *src_repo,
- const char *dest_repo)
+static void clone_local(const char *src_repo, const char *dest_repo)
{
- const struct ref *ret;
- struct remote *remote;
- struct transport *transport;
-
if (option_shared) {
struct strbuf alt = STRBUF_INIT;
strbuf_addf(&alt, "%s/objects", src_repo);
@@ -381,13 +369,8 @@ static const struct ref *clone_local(const char *src_repo,
strbuf_release(&dest);
}
- remote = remote_get(src_repo);
- transport = transport_get(remote, src_repo);
- ret = transport_get_remote_refs(transport);
- transport_disconnect(transport);
if (0 <= option_verbosity)
printf(_("done.\n"));
- return ret;
}
static const char *junk_work_tree;
@@ -418,14 +401,57 @@ static void remove_junk_on_signal(int signo)
raise(signo);
}
+static struct ref *find_remote_branch(const struct ref *refs, const char *branch)
+{
+ struct ref *ref;
+ struct strbuf head = STRBUF_INIT;
+ strbuf_addstr(&head, "refs/heads/");
+ strbuf_addstr(&head, branch);
+ ref = find_ref_by_name(refs, head.buf);
+ strbuf_release(&head);
+
+ if (ref)
+ return ref;
+
+ strbuf_addstr(&head, "refs/tags/");
+ strbuf_addstr(&head, branch);
+ ref = find_ref_by_name(refs, head.buf);
+ strbuf_release(&head);
+
+ return ref;
+}
+
static struct ref *wanted_peer_refs(const struct ref *refs,
struct refspec *refspec)
{
- struct ref *local_refs = NULL;
- struct ref **tail = &local_refs;
+ struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
+ struct ref *local_refs = head;
+ struct ref **tail = head ? &head->next : &local_refs;
+
+ if (option_single_branch) {
+ struct ref *remote_head = NULL;
+
+ if (!option_branch)
+ remote_head = guess_remote_head(head, refs, 0);
+ else {
+ local_refs = NULL;
+ tail = &local_refs;
+ remote_head = copy_ref(find_remote_branch(refs, option_branch));
+ }
+
+ if (!remote_head && option_branch)
+ warning(_("Could not find remote branch %s to clone."),
+ option_branch);
+ else {
+ get_fetch_map(remote_head, refspec, &tail, 0);
- get_fetch_map(refs, refspec, &tail, 0);
- if (!option_mirror)
+ /* if --branch=tag, pull the requested tag explicitly */
+ get_fetch_map(remote_head, tag_refspec, &tail, 0);
+ }
+ } else
+ get_fetch_map(refs, refspec, &tail, 0);
+
+ if (!option_mirror && !option_single_branch)
get_fetch_map(refs, tag_refspec, &tail, 0);
return local_refs;
@@ -435,11 +461,153 @@ static void write_remote_refs(const struct ref *local_refs)
{
const struct ref *r;
- for (r = local_refs; r; r = r->next)
- add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+ for (r = local_refs; r; r = r->next) {
+ if (!r->peer_ref)
+ continue;
+ add_packed_ref(r->peer_ref->name, r->old_sha1);
+ }
pack_refs(PACK_REFS_ALL);
- clear_extra_refs();
+}
+
+static void write_followtags(const struct ref *refs, const char *msg)
+{
+ const struct ref *ref;
+ for (ref = refs; ref; ref = ref->next) {
+ if (prefixcmp(ref->name, "refs/tags/"))
+ continue;
+ if (!suffixcmp(ref->name, "^{}"))
+ continue;
+ if (!has_sha1_file(ref->old_sha1))
+ continue;
+ update_ref(msg, ref->name, ref->old_sha1,
+ NULL, 0, DIE_ON_ERR);
+ }
+}
+
+static void update_remote_refs(const struct ref *refs,
+ const struct ref *mapped_refs,
+ const struct ref *remote_head_points_at,
+ const char *branch_top,
+ const char *msg)
+{
+ if (refs) {
+ write_remote_refs(mapped_refs);
+ if (option_single_branch)
+ write_followtags(refs, msg);
+ }
+
+ if (remote_head_points_at && !option_bare) {
+ struct strbuf head_ref = STRBUF_INIT;
+ strbuf_addstr(&head_ref, branch_top);
+ strbuf_addstr(&head_ref, "HEAD");
+ create_symref(head_ref.buf,
+ remote_head_points_at->peer_ref->name,
+ msg);
+ }
+}
+
+static void update_head(const struct ref *our, const struct ref *remote,
+ const char *msg)
+{
+ if (our && !prefixcmp(our->name, "refs/heads/")) {
+ /* Local default branch link */
+ create_symref("HEAD", our->name, NULL);
+ if (!option_bare) {
+ const char *head = skip_prefix(our->name, "refs/heads/");
+ update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+ install_branch_config(0, head, option_origin, our->name);
+ }
+ } else if (our) {
+ struct commit *c = lookup_commit_reference(our->old_sha1);
+ /* --branch specifies a non-branch (i.e. tags), detach HEAD */
+ update_ref(msg, "HEAD", c->object.sha1,
+ NULL, REF_NODEREF, DIE_ON_ERR);
+ } else if (remote) {
+ /*
+ * We know remote HEAD points to a non-branch, or
+ * HEAD points to a branch but we don't know which one.
+ * Detach HEAD in all these cases.
+ */
+ update_ref(msg, "HEAD", remote->old_sha1,
+ NULL, REF_NODEREF, DIE_ON_ERR);
+ }
+}
+
+static int checkout(void)
+{
+ unsigned char sha1[20];
+ char *head;
+ struct lock_file *lock_file;
+ struct unpack_trees_options opts;
+ struct tree *tree;
+ struct tree_desc t;
+ int err = 0, fd;
+
+ if (option_no_checkout)
+ return 0;
+
+ head = resolve_refdup("HEAD", sha1, 1, NULL);
+ if (!head) {
+ warning(_("remote HEAD refers to nonexistent ref, "
+ "unable to checkout.\n"));
+ return 0;
+ }
+ if (!strcmp(head, "HEAD")) {
+ if (advice_detached_head)
+ detach_advice(sha1_to_hex(sha1));
+ } else {
+ if (prefixcmp(head, "refs/heads/"))
+ die(_("HEAD not found below refs/heads!"));
+ }
+ free(head);
+
+ /* We need to be in the new work tree for the checkout */
+ setup_work_tree();
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+ fd = hold_locked_index(lock_file, 1);
+
+ memset(&opts, 0, sizeof opts);
+ opts.update = 1;
+ opts.merge = 1;
+ opts.fn = oneway_merge;
+ opts.verbose_update = (option_verbosity >= 0);
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+
+ tree = parse_tree_indirect(sha1);
+ parse_tree(tree);
+ init_tree_desc(&t, tree->buffer, tree->size);
+ unpack_trees(1, &t, &opts);
+
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die(_("unable to write new index file"));
+
+ err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+ sha1_to_hex(sha1), "1", NULL);
+
+ if (!err && option_recursive)
+ err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+
+ return err;
+}
+
+static int write_one_config(const char *key, const char *value, void *data)
+{
+ return git_config_set_multivar(key, value ? value : "true", "^$", 0);
+}
+
+static void write_config(struct string_list *config)
+{
+ int i;
+
+ for (i = 0; i < config->nr; i++) {
+ if (git_config_parse_parameter(config->items[i].string,
+ write_one_config, NULL) < 0)
+ die("unable to write parameters to config file");
+ }
}
int cmd_clone(int argc, const char **argv, const char *prefix)
@@ -453,11 +621,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
const struct ref *remote_head_points_at;
const struct ref *our_head_points_at;
struct ref *mapped_refs;
+ const struct ref *ref;
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;
- char *src_ref_prefix = "refs/heads/";
- int err = 0;
+ const char *src_ref_prefix = "refs/heads/";
+ struct remote *remote;
+ int err = 0, complete_refs_before_fetch = 1;
struct refspec *refspec;
const char *fetch_pattern;
@@ -476,6 +646,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
usage_msg_opt(_("You must specify a repository to clone."),
builtin_clone_usage, builtin_clone_options);
+ if (option_single_branch == -1)
+ option_single_branch = option_depth ? 1 : 0;
+
if (option_mirror)
option_bare = 1;
@@ -498,7 +671,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
die(_("repository '%s' does not exist"), repo_name);
else
repo = repo_name;
- is_local = path && !is_bundle;
+ is_local = option_local != 0 && path && !is_bundle;
if (is_local && option_depth)
warning(_("--depth is ignored in local clones; use file:// instead."));
@@ -555,11 +728,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (0 <= option_verbosity) {
if (option_bare)
- printf(_("Cloning into bare repository %s...\n"), dir);
+ printf(_("Cloning into bare repository '%s'...\n"), dir);
else
- printf(_("Cloning into %s...\n"), dir);
+ printf(_("Cloning into '%s'...\n"), dir);
}
init_db(option_template, INIT_DB_QUIET);
+ write_config(&option_config);
/*
* At this point, the config exists, so we do not need the
@@ -607,13 +781,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
strbuf_reset(&value);
- if (is_local) {
- refs = clone_local(path, git_dir);
- mapped_refs = wanted_peer_refs(refs, refspec);
- } else {
- struct remote *remote = remote_get(option_origin);
- transport = transport_get(remote, remote->url[0]);
+ remote = remote_get(option_origin);
+ transport = transport_get(remote, remote->url[0]);
+ if (!is_local) {
if (!transport->get_refs_list || !transport->fetch)
die(_("Don't know how to clone %s"), transport->url);
@@ -622,49 +793,57 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_depth)
transport_set_option(transport, TRANS_OPT_DEPTH,
option_depth);
+ if (option_single_branch)
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
transport_set_verbosity(transport, option_verbosity, option_progress);
if (option_upload_pack)
transport_set_option(transport, TRANS_OPT_UPLOADPACK,
option_upload_pack);
-
- refs = transport_get_remote_refs(transport);
- if (refs) {
- mapped_refs = wanted_peer_refs(refs, refspec);
- transport_fetch_refs(transport, mapped_refs);
- }
}
+ refs = transport_get_remote_refs(transport);
+
if (refs) {
- clear_extra_refs();
+ mapped_refs = wanted_peer_refs(refs, refspec);
+ /*
+ * transport_get_remote_refs() may return refs with null sha-1
+ * in mapped_refs (see struct transport->get_refs_list
+ * comment). In that case we need fetch it early because
+ * remote_head code below relies on it.
+ *
+ * for normal clones, transport_get_remote_refs() should
+ * return reliable ref set, we can delay cloning until after
+ * remote HEAD check.
+ */
+ for (ref = refs; ref; ref = ref->next)
+ if (is_null_sha1(ref->old_sha1)) {
+ complete_refs_before_fetch = 0;
+ break;
+ }
- write_remote_refs(mapped_refs);
+ if (!is_local && !complete_refs_before_fetch)
+ transport_fetch_refs(transport, mapped_refs);
remote_head = find_ref_by_name(refs, "HEAD");
remote_head_points_at =
guess_remote_head(remote_head, mapped_refs, 0);
if (option_branch) {
- struct strbuf head = STRBUF_INIT;
- strbuf_addstr(&head, src_ref_prefix);
- strbuf_addstr(&head, option_branch);
our_head_points_at =
- find_ref_by_name(mapped_refs, head.buf);
- strbuf_release(&head);
-
- if (!our_head_points_at) {
- warning(_("Remote branch %s not found in "
- "upstream %s, using HEAD instead"),
- option_branch, option_origin);
- our_head_points_at = remote_head_points_at;
- }
+ find_remote_branch(mapped_refs, option_branch);
+
+ if (!our_head_points_at)
+ die(_("Remote branch %s not found in upstream %s"),
+ option_branch, option_origin);
}
else
our_head_points_at = remote_head_points_at;
}
else {
warning(_("You appear to have cloned an empty repository."));
+ mapped_refs = NULL;
our_head_points_at = NULL;
remote_head_points_at = NULL;
remote_head = NULL;
@@ -674,84 +853,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
"refs/heads/master");
}
- if (remote_head_points_at && !option_bare) {
- struct strbuf head_ref = STRBUF_INIT;
- strbuf_addstr(&head_ref, branch_top.buf);
- strbuf_addstr(&head_ref, "HEAD");
- create_symref(head_ref.buf,
- remote_head_points_at->peer_ref->name,
- reflog_msg.buf);
- }
+ if (is_local)
+ clone_local(path, git_dir);
+ else if (refs && complete_refs_before_fetch)
+ transport_fetch_refs(transport, mapped_refs);
- if (our_head_points_at) {
- /* Local default branch link */
- create_symref("HEAD", our_head_points_at->name, NULL);
- if (!option_bare) {
- const char *head = skip_prefix(our_head_points_at->name,
- "refs/heads/");
- update_ref(reflog_msg.buf, "HEAD",
- our_head_points_at->old_sha1,
- NULL, 0, DIE_ON_ERR);
- install_branch_config(0, head, option_origin,
- our_head_points_at->name);
- }
- } else if (remote_head) {
- /* Source had detached HEAD pointing somewhere. */
- if (!option_bare) {
- update_ref(reflog_msg.buf, "HEAD",
- remote_head->old_sha1,
- NULL, REF_NODEREF, DIE_ON_ERR);
- our_head_points_at = remote_head;
- }
- } else {
- /* Nothing to checkout out */
- if (!option_no_checkout)
- warning(_("remote HEAD refers to nonexistent ref, "
- "unable to checkout.\n"));
- option_no_checkout = 1;
- }
+ update_remote_refs(refs, mapped_refs, remote_head_points_at,
+ branch_top.buf, reflog_msg.buf);
- if (transport) {
- transport_unlock_pack(transport);
- transport_disconnect(transport);
- }
+ update_head(our_head_points_at, remote_head, reflog_msg.buf);
- if (!option_no_checkout) {
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
- struct unpack_trees_options opts;
- struct tree *tree;
- struct tree_desc t;
- int fd;
-
- /* We need to be in the new work tree for the checkout */
- setup_work_tree();
-
- fd = hold_locked_index(lock_file, 1);
-
- memset(&opts, 0, sizeof opts);
- opts.update = 1;
- opts.merge = 1;
- opts.fn = oneway_merge;
- opts.verbose_update = (option_verbosity > 0);
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
-
- tree = parse_tree_indirect(our_head_points_at->old_sha1);
- parse_tree(tree);
- init_tree_desc(&t, tree->buffer, tree->size);
- unpack_trees(1, &t, &opts);
-
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
- die(_("unable to write new index file"));
-
- err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
- sha1_to_hex(our_head_points_at->old_sha1), "1",
- NULL);
-
- if (!err && option_recursive)
- err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
- }
+ transport_unlock_pack(transport);
+ transport_disconnect(transport);
+
+ err = checkout();
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
diff --git a/builtin/column.c b/builtin/column.c
new file mode 100644
index 0000000..5ea798a
--- /dev/null
+++ b/builtin/column.c
@@ -0,0 +1,59 @@
+#include "builtin.h"
+#include "cache.h"
+#include "strbuf.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "column.h"
+
+static const char * const builtin_column_usage[] = {
+ "git column [options]",
+ NULL
+};
+static unsigned int colopts;
+
+static int column_config(const char *var, const char *value, void *cb)
+{
+ return git_column_config(var, value, cb, &colopts);
+}
+
+int cmd_column(int argc, const char **argv, const char *prefix)
+{
+ struct string_list list = STRING_LIST_INIT_DUP;
+ struct strbuf sb = STRBUF_INIT;
+ struct column_options copts;
+ const char *command = NULL, *real_command = NULL;
+ struct option options[] = {
+ OPT_STRING(0, "command", &real_command, "name", "lookup config vars"),
+ OPT_COLUMN(0, "mode", &colopts, "layout to use"),
+ OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"),
+ OPT_INTEGER(0, "width", &copts.width, "Maximum width"),
+ OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"),
+ OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"),
+ OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"),
+ OPT_END()
+ };
+
+ /* This one is special and must be the first one */
+ if (argc > 1 && !prefixcmp(argv[1], "--command=")) {
+ command = argv[1] + 10;
+ git_config(column_config, (void *)command);
+ } else
+ git_config(column_config, NULL);
+
+ memset(&copts, 0, sizeof(copts));
+ copts.width = term_columns();
+ copts.padding = 1;
+ argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
+ if (argc)
+ usage_with_options(builtin_column_usage, options);
+ if (real_command || command) {
+ if (!real_command || !command || strcmp(real_command, command))
+ die(_("--command must be the first argument"));
+ }
+ finalize_colopts(&colopts, -1);
+ while (!strbuf_getline(&sb, stdin, '\n'))
+ string_list_append(&list, sb.buf);
+
+ print_columns(&list, colopts, &copts);
+ return 0;
+}
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index d083795..164b655 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -8,8 +8,9 @@
#include "tree.h"
#include "builtin.h"
#include "utf8.h"
+#include "gpg-interface.h"
-static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
+static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog";
static void new_parent(struct commit *parent, struct commit_list **parents_p)
{
@@ -25,38 +26,98 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
commit_list_insert(parent, parents_p);
}
+static int commit_tree_config(const char *var, const char *value, void *cb)
+{
+ int status = git_gpg_config(var, value, NULL);
+ if (status)
+ return status;
+ return git_default_config(var, value, cb);
+}
+
int cmd_commit_tree(int argc, const char **argv, const char *prefix)
{
- int i;
+ int i, got_tree = 0;
struct commit_list *parents = NULL;
unsigned char tree_sha1[20];
unsigned char commit_sha1[20];
struct strbuf buffer = STRBUF_INIT;
+ const char *sign_commit = NULL;
- git_config(git_default_config, NULL);
+ git_config(commit_tree_config, NULL);
if (argc < 2 || !strcmp(argv[1], "-h"))
usage(commit_tree_usage);
+
if (get_sha1(argv[1], tree_sha1))
die("Not a valid object name %s", argv[1]);
- for (i = 2; i < argc; i += 2) {
- unsigned char sha1[20];
- const char *a, *b;
- a = argv[i]; b = argv[i+1];
- if (!b || strcmp(a, "-p"))
- usage(commit_tree_usage);
-
- if (get_sha1(b, sha1))
- die("Not a valid object name %s", b);
- assert_sha1_type(sha1, OBJ_COMMIT);
- new_parent(lookup_commit(sha1), &parents);
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "-p")) {
+ unsigned char sha1[20];
+ if (argc <= ++i)
+ usage(commit_tree_usage);
+ if (get_sha1(argv[i], sha1))
+ die("Not a valid object name %s", argv[i]);
+ assert_sha1_type(sha1, OBJ_COMMIT);
+ new_parent(lookup_commit(sha1), &parents);
+ continue;
+ }
+
+ if (!memcmp(arg, "-S", 2)) {
+ sign_commit = arg + 2;
+ continue;
+ }
+
+ if (!strcmp(arg, "-m")) {
+ if (argc <= ++i)
+ usage(commit_tree_usage);
+ if (buffer.len)
+ strbuf_addch(&buffer, '\n');
+ strbuf_addstr(&buffer, argv[i]);
+ strbuf_complete_line(&buffer);
+ continue;
+ }
+
+ if (!strcmp(arg, "-F")) {
+ int fd;
+
+ if (argc <= ++i)
+ usage(commit_tree_usage);
+ if (buffer.len)
+ strbuf_addch(&buffer, '\n');
+ if (!strcmp(argv[i], "-"))
+ fd = 0;
+ else {
+ fd = open(argv[i], O_RDONLY);
+ if (fd < 0)
+ die_errno("git commit-tree: failed to open '%s'",
+ argv[i]);
+ }
+ if (strbuf_read(&buffer, fd, 0) < 0)
+ die_errno("git commit-tree: failed to read '%s'",
+ argv[i]);
+ if (fd && close(fd))
+ die_errno("git commit-tree: failed to close '%s'",
+ argv[i]);
+ strbuf_complete_line(&buffer);
+ continue;
+ }
+
+ if (get_sha1(arg, tree_sha1))
+ die("Not a valid object name %s", arg);
+ if (got_tree)
+ die("Cannot give more than one trees");
+ got_tree = 1;
}
- if (strbuf_read(&buffer, 0, 0) < 0)
- die_errno("git commit-tree: failed to read");
+ if (!buffer.len) {
+ if (strbuf_read(&buffer, 0, 0) < 0)
+ die_errno("git commit-tree: failed to read");
+ }
- if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+ if (commit_tree(&buffer, tree_sha1, parents, commit_sha1,
+ NULL, sign_commit)) {
strbuf_release(&buffer);
return 1;
}
diff --git a/builtin/commit.c b/builtin/commit.c
index 9679a99..f43eaaf 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -26,6 +26,8 @@
#include "unpack-trees.h"
#include "quote.h"
#include "submodule.h"
+#include "gpg-interface.h"
+#include "column.h"
static const char * const builtin_commit_usage[] = {
"git commit [options] [--] <filepattern>...",
@@ -62,8 +64,6 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\
"\n"
"Otherwise, please use 'git reset'\n");
-static unsigned char head_sha1[20];
-
static const char *use_message_buffer;
static const char commit_editmsg[] = "COMMIT_EDITMSG";
static struct lock_file index_lock; /* real index */
@@ -83,10 +83,13 @@ static const char *template_file;
static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message;
static char *fixup_message, *squash_message;
-static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
+static int all, also, interactive, patch_interactive, only, amend, signoff;
+static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int no_post_rewrite, allow_empty_message;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *sign_commit;
+
/*
* The default commit message cleanup mode will remove the lines
* beginning with # (shell comments) and leading and trailing
@@ -102,18 +105,16 @@ static enum {
static char *cleanup_arg;
static enum commit_whence whence;
-static int use_editor = 1, initial_commit, include_status = 1;
+static int use_editor = 1, include_status = 1;
static int show_ignored_in_status;
static const char *only_include_assumed;
-static struct strbuf message;
+static struct strbuf message = STRBUF_INIT;
-static int null_termination;
static enum {
STATUS_FORMAT_LONG,
STATUS_FORMAT_SHORT,
STATUS_FORMAT_PORCELAIN
} status_format = STATUS_FORMAT_LONG;
-static int status_show_branch;
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
{
@@ -127,57 +128,6 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
return 0;
}
-static struct option builtin_commit_options[] = {
- OPT__QUIET(&quiet, "suppress summary after successful commit"),
- OPT__VERBOSE(&verbose, "show diff in commit message template"),
-
- OPT_GROUP("Commit message options"),
- OPT_FILENAME('F', "file", &logfile, "read message from file"),
- OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
- OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
- OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
- OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
- OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
- OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
- OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
- OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
- OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
- OPT_FILENAME('t', "template", &template_file, "use specified template file"),
- OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
- OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
- OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
- /* end commit message options */
-
- 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('p', "patch", &patch_interactive, "interactively add changes"),
- OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
- OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
- OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
- OPT_SET_INT(0, "short", &status_format, "show status concisely",
- STATUS_FORMAT_SHORT),
- OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"),
- OPT_SET_INT(0, "porcelain", &status_format,
- "machine-readable output", STATUS_FORMAT_PORCELAIN),
- OPT_BOOLEAN('z', "null", &null_termination,
- "terminate entries with NUL"),
- OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
- OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
- { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
- /* end commit contents options */
-
- { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
- "ok to record an empty change",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL,
- "ok to record a change with an empty message",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
-
- OPT_END()
-};
-
static void determine_whence(struct wt_status *s)
{
if (file_exists(git_path("MERGE_HEAD")))
@@ -190,24 +140,6 @@ static void determine_whence(struct wt_status *s)
s->whence = whence;
}
-static const char *whence_s(void)
-{
- char *s = "";
-
- switch (whence) {
- case FROM_COMMIT:
- break;
- case FROM_MERGE:
- s = "merge";
- break;
- case FROM_CHERRY_PICK:
- s = "cherry-pick";
- break;
- }
-
- return s;
-}
-
static void rollback_index_files(void)
{
switch (commit_style) {
@@ -256,8 +188,11 @@ static int list_paths(struct string_list *list, const char *with_tree,
;
m = xcalloc(1, i);
- if (with_tree)
- overlay_tree_on_cache(with_tree, prefix);
+ if (with_tree) {
+ char *max_prefix = common_prefix(pattern);
+ overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix);
+ free(max_prefix);
+ }
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
@@ -294,13 +229,13 @@ static void add_remove_files(struct string_list *list)
}
}
-static void create_base_index(void)
+static void create_base_index(const struct commit *current_head)
{
struct tree *tree;
struct unpack_trees_options opts;
struct tree_desc t;
- if (initial_commit) {
+ if (!current_head) {
discard_cache();
return;
}
@@ -313,7 +248,7 @@ static void create_base_index(void)
opts.dst_index = &the_index;
opts.fn = oneway_merge;
- tree = parse_tree_indirect(head_sha1);
+ tree = parse_tree_indirect(current_head->object.sha1);
if (!tree)
die(_("failed to unpack HEAD tree object"));
parse_tree(tree);
@@ -332,7 +267,8 @@ static void refresh_cache_or_die(int refresh_flags)
die_resolve_conflict("commit");
}
-static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
+static char *prepare_index(int argc, const char **argv, const char *prefix,
+ const struct commit *current_head, int is_status)
{
int fd;
struct string_list partial;
@@ -392,6 +328,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
fd = hold_locked_index(&index_lock, 1);
add_files_to_cache(also ? prefix : NULL, pathspec, 0);
refresh_cache_or_die(refresh_flags);
+ update_main_cache_tree(WRITE_TREE_SILENT);
if (write_cache(fd, active_cache, active_nr) ||
close_lock_file(&index_lock))
die(_("unable to write new_index file"));
@@ -412,6 +349,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
fd = hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
if (active_cache_changed) {
+ update_main_cache_tree(WRITE_TREE_SILENT);
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(&index_lock))
die(_("unable to write new_index file"));
@@ -443,12 +381,16 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
*/
commit_style = COMMIT_PARTIAL;
- if (whence != FROM_COMMIT)
- die(_("cannot do a partial commit during a %s."), whence_s());
+ if (whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("cannot do a partial commit during a merge."));
+ else if (whence == FROM_CHERRY_PICK)
+ die(_("cannot do a partial commit during a cherry-pick."));
+ }
memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1;
- if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
+ if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
exit(1);
discard_cache();
@@ -467,7 +409,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
(uintmax_t) getpid()),
LOCK_DIE_ON_ERROR);
- create_base_index();
+ create_base_index(current_head);
add_remove_files(&partial);
refresh_cache(REFRESH_QUIET);
@@ -503,10 +445,10 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
switch (status_format) {
case STATUS_FORMAT_SHORT:
- wt_shortstatus_print(s, null_termination, status_show_branch);
+ wt_shortstatus_print(s);
break;
case STATUS_FORMAT_PORCELAIN:
- wt_porcelain_print(s, null_termination);
+ wt_porcelain_print(s);
break;
case STATUS_FORMAT_LONG:
wt_status_print(s);
@@ -516,19 +458,27 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
return s->commitable;
}
-static int is_a_merge(const unsigned char *sha1)
+static int is_a_merge(const struct commit *current_head)
{
- struct commit *commit = lookup_commit(sha1);
- if (!commit || parse_commit(commit))
- die(_("could not parse HEAD commit"));
- return !!(commit->parents && commit->parents->next);
+ return !!(current_head->parents && current_head->parents->next);
}
static const char sign_off_header[] = "Signed-off-by: ";
+static void export_one(const char *var, const char *s, const char *e, int hack)
+{
+ struct strbuf buf = STRBUF_INIT;
+ if (hack)
+ strbuf_addch(&buf, hack);
+ strbuf_addf(&buf, "%.*s", (int)(e - s), s);
+ setenv(var, buf.buf, 1);
+ strbuf_release(&buf);
+}
+
static void determine_author_info(struct strbuf *author_ident)
{
char *name, *email, *date;
+ struct ident_split author;
name = getenv("GIT_AUTHOR_NAME");
email = getenv("GIT_AUTHOR_EMAIL");
@@ -536,6 +486,7 @@ static void determine_author_info(struct strbuf *author_ident)
if (author_message) {
const char *a, *lb, *rb, *eol;
+ size_t len;
a = strstr(author_message_buffer, "\nauthor ");
if (!a)
@@ -556,6 +507,11 @@ static void determine_author_info(struct strbuf *author_ident)
(a + strlen("\nauthor "))));
email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<")));
date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> ")));
+ len = eol - (rb + strlen("> "));
+ date = xmalloc(len + 2);
+ *date = '@';
+ memcpy(date + 1, rb + strlen("> "), len);
+ date[len + 1] = '\0';
}
if (force_author) {
@@ -570,8 +526,12 @@ static void determine_author_info(struct strbuf *author_ident)
if (force_date)
date = force_date;
- strbuf_addstr(author_ident, fmt_ident(name, email, date,
- IDENT_ERROR_ON_NO_NAME));
+ strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
+ if (!split_ident_line(&author, author_ident->buf, author_ident->len)) {
+ export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
+ export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
+ export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
+ }
}
static int ends_rfc2822_footer(struct strbuf *sb)
@@ -625,6 +585,7 @@ static char *cut_ident_timestamp_part(char *string)
}
static int prepare_to_commit(const char *index_file, const char *prefix,
+ struct commit *current_head,
struct wt_status *s,
struct strbuf *author_ident)
{
@@ -638,6 +599,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
int ident_shown = 0;
int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
+ /* This checks and barfs if author is badly specified */
+ determine_author_info(author_ident);
+
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
@@ -757,37 +721,37 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
strbuf_release(&sb);
- /* This checks and barfs if author is badly specified */
- determine_author_info(author_ident);
-
/* This checks if committer ident is explicitly given */
strbuf_addstr(&committer_ident, git_committer_info(0));
if (use_editor && include_status) {
char *ai_tmp, *ci_tmp;
if (whence != FROM_COMMIT)
status_printf_ln(s, GIT_COLOR_NORMAL,
- _("\n"
- "It looks like you may be committing a %s.\n"
- "If this is not correct, please remove the file\n"
- " %s\n"
- "and try again.\n"
- ""),
- whence_s(),
+ whence == FROM_MERGE
+ ? _("\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"
+ "It looks like you may be committing a cherry-pick.\n"
+ "If this is not correct, please remove the file\n"
+ " %s\n"
+ "and try again.\n"),
git_path(whence == FROM_MERGE
? "MERGE_HEAD"
: "CHERRY_PICK_HEAD"));
fprintf(s->fp, "\n");
- status_printf(s, GIT_COLOR_NORMAL,
- _("Please enter the commit message for your changes."));
if (cleanup_mode == CLEANUP_ALL)
- status_printf_more(s, GIT_COLOR_NORMAL,
- _(" Lines starting\n"
- "with '#' will be ignored, and an empty"
+ status_printf(s, GIT_COLOR_NORMAL,
+ _("Please enter the commit message for your changes."
+ " Lines starting\nwith '#' will be ignored, and an empty"
" message aborts the commit.\n"));
else /* CLEANUP_SPACE, that is. */
- status_printf_more(s, GIT_COLOR_NORMAL,
- _(" Lines starting\n"
+ status_printf(s, GIT_COLOR_NORMAL,
+ _("Please enter the commit message for your changes."
+ " Lines starting\n"
"with '#' will be kept; you may remove them"
" yourself if you want to.\n"
"An empty message aborts the commit.\n"));
@@ -846,7 +810,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
* empty due to conflict resolution, which the user should okay.
*/
if (!commitable && whence != FROM_MERGE && !allow_empty &&
- !(amend && is_a_merge(head_sha1))) {
+ !(amend && is_a_merge(current_head))) {
run_status(stdout, index_file, prefix, 0, s);
if (amend)
fputs(_(empty_amend_advice), stderr);
@@ -862,10 +826,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
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) {
+ if (update_main_cache_tree(0)) {
error(_("Error building trees"));
return 0;
}
@@ -894,27 +855,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
return 1;
}
-/*
- * Find out if the message in the strbuf contains only whitespace and
- * Signed-off-by lines.
- */
-static int message_is_empty(struct strbuf *sb)
+static int rest_is_empty(struct strbuf *sb, int start)
{
- struct strbuf tmpl = STRBUF_INIT;
+ int i, eol;
const char *nl;
- int eol, i, start = 0;
-
- if (cleanup_mode == CLEANUP_NONE && sb->len)
- return 0;
-
- /* See if the template is just a prefix of the message. */
- 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++) {
@@ -937,6 +881,40 @@ static int message_is_empty(struct strbuf *sb)
return 1;
}
+/*
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
+ */
+static int message_is_empty(struct strbuf *sb)
+{
+ if (cleanup_mode == CLEANUP_NONE && sb->len)
+ return 0;
+ return rest_is_empty(sb, 0);
+}
+
+/*
+ * See if the user edited the message in the editor or left what
+ * was in the template intact
+ */
+static int template_untouched(struct strbuf *sb)
+{
+ struct strbuf tmpl = STRBUF_INIT;
+ char *start;
+
+ if (cleanup_mode == CLEANUP_NONE && sb->len)
+ return 0;
+
+ if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
+ return 0;
+
+ stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
+ start = (char *)skip_prefix(sb->buf, tmpl.buf);
+ if (!start)
+ start = sb->buf;
+ strbuf_release(&tmpl);
+ return rest_is_empty(sb, start - sb->buf);
+}
+
static const char *find_author_by_nickname(const char *name)
{
struct rev_info revs;
@@ -1002,14 +980,15 @@ static const char *read_commit_message(const char *name)
}
static int parse_and_validate_options(int argc, const char *argv[],
+ const struct option *options,
const char * const usage[],
const char *prefix,
+ struct commit *current_head,
struct wt_status *s)
{
int f = 0;
- argc = parse_options(argc, argv, prefix, builtin_commit_options, usage,
- 0);
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
if (force_author && !strchr(force_author, '>'))
force_author = find_author_by_nickname(force_author);
@@ -1019,19 +998,20 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (logfile || message.len || use_message || fixup_message)
use_editor = 0;
- if (edit_flag)
- use_editor = 1;
+ if (0 <= edit_flag)
+ use_editor = edit_flag;
if (!use_editor)
setenv("GIT_EDITOR", ":", 1);
- if (get_sha1("HEAD", head_sha1))
- initial_commit = 1;
-
/* Sanity check options */
- if (amend && initial_commit)
+ if (amend && !current_head)
die(_("You have nothing to amend."));
- if (amend && whence != FROM_COMMIT)
- die(_("You are in the middle of a %s -- cannot amend."), whence_s());
+ if (amend && whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("You are in the middle of a merge -- cannot amend."));
+ else if (whence == FROM_CHERRY_PICK)
+ die(_("You are in the middle of a cherry-pick -- cannot amend."));
+ }
if (fixup_message && squash_message)
die(_("Options --squash and --fixup cannot be used together"));
if (use_message)
@@ -1046,6 +1026,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
die(_("Only one of -c/-C/-F/--fixup can be used."));
if (message.len && f > 0)
die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
+ if (f || message.len)
+ template_file = NULL;
if (edit_message)
use_message = edit_message;
if (amend && !use_message && !fixup_message)
@@ -1091,7 +1073,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (all && argc > 0)
die(_("Paths with -a does not make sense."));
- if (null_termination && status_format == STATUS_FORMAT_LONG)
+ if (s->null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
if (status_format != STATUS_FORMAT_LONG)
dry_run = 1;
@@ -1100,12 +1082,12 @@ static int parse_and_validate_options(int argc, const char *argv[],
}
static int dry_run_commit(int argc, const char **argv, const char *prefix,
- struct wt_status *s)
+ const struct commit *current_head, struct wt_status *s)
{
int commitable;
const char *index_file;
- index_file = prepare_index(argc, argv, prefix, 1);
+ index_file = prepare_index(argc, argv, prefix, current_head, 1);
commitable = run_status(stdout, index_file, prefix, 0, s);
rollback_index_files();
@@ -1136,6 +1118,8 @@ static int git_status_config(const char *k, const char *v, void *cb)
{
struct wt_status *s = cb;
+ if (!prefixcmp(k, "column."))
+ return git_column_config(k, v, "status", &s->colopts);
if (!strcmp(k, "status.submodulesummary")) {
int is_bool;
s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
@@ -1144,7 +1128,7 @@ static int git_status_config(const char *k, const char *v, void *cb)
return 0;
}
if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
- s->use_color = git_config_colorbool(k, v, -1);
+ s->use_color = git_config_colorbool(k, v);
return 0;
}
if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
@@ -1178,19 +1162,19 @@ static int git_status_config(const char *k, const char *v, void *cb)
int cmd_status(int argc, const char **argv, const char *prefix)
{
- struct wt_status s;
+ static struct wt_status s;
int fd;
unsigned char sha1[20];
static struct option builtin_status_options[] = {
OPT__VERBOSE(&verbose, "be verbose"),
OPT_SET_INT('s', "short", &status_format,
"show status concisely", STATUS_FORMAT_SHORT),
- OPT_BOOLEAN('b', "branch", &status_show_branch,
+ OPT_BOOLEAN('b', "branch", &s.show_branch,
"show branch information"),
OPT_SET_INT(0, "porcelain", &status_format,
"machine-readable output",
STATUS_FORMAT_PORCELAIN),
- OPT_BOOLEAN('z', "null", &null_termination,
+ OPT_BOOLEAN('z', "null", &s.null_termination,
"terminate entries with NUL"),
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
"mode",
@@ -1201,6 +1185,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
{ OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when",
"ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)",
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ OPT_COLUMN(0, "column", &s.colopts, "list untracked files in columns"),
OPT_END(),
};
@@ -1214,8 +1199,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix,
builtin_status_options,
builtin_status_usage, 0);
+ finalize_colopts(&s.colopts, -1);
- if (null_termination && status_format == STATUS_FORMAT_LONG)
+ if (s.null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
handle_untracked_files_arg(&s);
@@ -1237,17 +1223,13 @@ int cmd_status(int argc, const char **argv, const char *prefix)
if (s.relative_paths)
s.prefix = prefix;
- if (s.use_color == -1)
- s.use_color = git_use_color_default;
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
switch (status_format) {
case STATUS_FORMAT_SHORT:
- wt_shortstatus_print(&s, null_termination, status_show_branch);
+ wt_shortstatus_print(&s);
break;
case STATUS_FORMAT_PORCELAIN:
- wt_porcelain_print(&s, null_termination);
+ wt_porcelain_print(&s);
break;
case STATUS_FORMAT_LONG:
s.verbose = verbose;
@@ -1258,13 +1240,14 @@ int cmd_status(int argc, const char **argv, const char *prefix)
return 0;
}
-static void print_summary(const char *prefix, const unsigned char *sha1)
+static void print_summary(const char *prefix, const unsigned char *sha1,
+ int initial_commit)
{
struct rev_info rev;
struct commit *commit;
struct strbuf format = STRBUF_INIT;
unsigned char junk_sha1[20];
- const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+ const char *head;
struct pretty_print_context pctx = {0};
struct strbuf author_ident = STRBUF_INIT;
struct strbuf committer_ident = STRBUF_INIT;
@@ -1309,6 +1292,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
+ head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
printf("[%s%s ",
!prefixcmp(head, "refs/heads/") ?
head + 11 :
@@ -1329,6 +1313,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
static int git_commit_config(const char *k, const char *v, void *cb)
{
struct wt_status *s = cb;
+ int status;
if (!strcmp(k, "commit.template"))
return git_config_pathname(&template_file, k, v);
@@ -1337,6 +1322,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
return 0;
}
+ status = git_gpg_config(k, v, NULL);
+ if (status)
+ return status;
return git_status_config(k, v, s);
}
@@ -1376,16 +1364,71 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
int cmd_commit(int argc, const char **argv, const char *prefix)
{
+ static struct wt_status s;
+ static struct option builtin_commit_options[] = {
+ OPT__QUIET(&quiet, "suppress summary after successful commit"),
+ OPT__VERBOSE(&verbose, "show diff in commit message template"),
+
+ OPT_GROUP("Commit message options"),
+ OPT_FILENAME('F', "file", &logfile, "read message from file"),
+ OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
+ OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
+ OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
+ OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
+ OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
+ OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
+ OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
+ OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"),
+ OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
+ OPT_FILENAME('t', "template", &template_file, "use specified template file"),
+ OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"),
+ OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+ OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+ { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+ "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ /* end commit message options */
+
+ 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('p', "patch", &patch_interactive, "interactively add changes"),
+ OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
+ OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
+ OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
+ OPT_SET_INT(0, "short", &status_format, "show status concisely",
+ STATUS_FORMAT_SHORT),
+ OPT_BOOLEAN(0, "branch", &s.show_branch, "show branch information"),
+ OPT_SET_INT(0, "porcelain", &status_format,
+ "machine-readable output", STATUS_FORMAT_PORCELAIN),
+ OPT_BOOLEAN('z', "null", &s.null_termination,
+ "terminate entries with NUL"),
+ OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+ OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
+ { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ /* end commit contents options */
+
+ { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
+ "ok to record an empty change",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL,
+ "ok to record a change with an empty message",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+
+ OPT_END()
+ };
+
struct strbuf sb = STRBUF_INIT;
struct strbuf author_ident = STRBUF_INIT;
const char *index_file, *reflog_msg;
char *nl, *p;
- unsigned char commit_sha1[20];
+ unsigned char sha1[20];
struct ref_lock *ref_lock;
struct commit_list *parents = NULL, **pptr = &parents;
struct stat statbuf;
int allow_fast_forward = 1;
- struct wt_status s;
+ struct commit *current_head = NULL;
+ struct commit_extra_header *extra = NULL;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_usage, builtin_commit_options);
@@ -1393,41 +1436,41 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
wt_status_prepare(&s);
git_config(git_commit_config, &s);
determine_whence(&s);
+ s.colopts = 0;
- if (s.use_color == -1)
- s.use_color = git_use_color_default;
- argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
- prefix, &s);
- if (dry_run) {
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
- return dry_run_commit(argc, argv, prefix, &s);
+ if (get_sha1("HEAD", sha1))
+ current_head = NULL;
+ else {
+ current_head = lookup_commit_or_die(sha1, "HEAD");
+ if (!current_head || parse_commit(current_head))
+ die(_("could not parse HEAD commit"));
}
- index_file = prepare_index(argc, argv, prefix, 0);
+ argc = parse_and_validate_options(argc, argv, builtin_commit_options,
+ builtin_commit_usage,
+ prefix, current_head, &s);
+ if (dry_run)
+ return dry_run_commit(argc, argv, prefix, current_head, &s);
+ index_file = prepare_index(argc, argv, prefix, current_head, 0);
/* Set up everything for writing the commit object. This includes
running hooks, writing the trees, and interacting with the user. */
- if (!prepare_to_commit(index_file, prefix, &s, &author_ident)) {
+ if (!prepare_to_commit(index_file, prefix,
+ current_head, &s, &author_ident)) {
rollback_index_files();
return 1;
}
/* Determine parents */
reflog_msg = getenv("GIT_REFLOG_ACTION");
- if (initial_commit) {
+ if (!current_head) {
if (!reflog_msg)
reflog_msg = "commit (initial)";
} else if (amend) {
struct commit_list *c;
- struct commit *commit;
if (!reflog_msg)
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)
+ for (c = current_head->parents; c; c = c->next)
pptr = &commit_list_insert(c->item, pptr)->next;
} else if (whence == FROM_MERGE) {
struct strbuf m = STRBUF_INIT;
@@ -1435,16 +1478,18 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (!reflog_msg)
reflog_msg = "commit (merge)";
- pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+ pptr = &commit_list_insert(current_head, pptr)->next;
fp = fopen(git_path("MERGE_HEAD"), "r");
if (fp == NULL)
die_errno(_("could not open '%s' for reading"),
git_path("MERGE_HEAD"));
while (strbuf_getline(&m, fp, '\n') != EOF) {
- unsigned char sha1[20];
- if (get_sha1_hex(m.buf, sha1) < 0)
+ struct commit *parent;
+
+ parent = get_merge_parent(m.buf);
+ if (!parent)
die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
- pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
+ pptr = &commit_list_insert(parent, pptr)->next;
}
fclose(fp);
strbuf_release(&m);
@@ -1461,7 +1506,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
reflog_msg = (whence == FROM_CHERRY_PICK)
? "commit (cherry-pick)"
: "commit";
- pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+ pptr = &commit_list_insert(current_head, pptr)->next;
}
/* Finally, get the commit message */
@@ -1481,21 +1526,37 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (cleanup_mode != CLEANUP_NONE)
stripspace(&sb, cleanup_mode == CLEANUP_ALL);
+ if (template_untouched(&sb) && !allow_empty_message) {
+ rollback_index_files();
+ fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
+ exit(1);
+ }
if (message_is_empty(&sb) && !allow_empty_message) {
rollback_index_files();
fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
exit(1);
}
- if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
- author_ident.buf)) {
+ if (amend) {
+ const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+ extra = read_commit_extra_headers(current_head, exclude_gpgsig);
+ } else {
+ struct commit_extra_header **tail = &extra;
+ append_merge_tag_headers(parents, &tail);
+ }
+
+ if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
+ author_ident.buf, sign_commit, extra)) {
rollback_index_files();
die(_("failed to write commit object"));
}
strbuf_release(&author_ident);
+ free_commit_extra_headers(extra);
ref_lock = lock_any_ref_for_update("HEAD",
- initial_commit ? NULL : head_sha1,
+ !current_head
+ ? NULL
+ : current_head->object.sha1,
0);
nl = strchr(sb.buf, '\n');
@@ -1510,12 +1571,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
rollback_index_files();
die(_("cannot lock HEAD ref"));
}
- if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+ if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
rollback_index_files();
die(_("cannot update HEAD ref"));
}
unlink(git_path("CHERRY_PICK_HEAD"));
+ unlink(git_path("REVERT_HEAD"));
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_MSG"));
unlink(git_path("MERGE_MODE"));
@@ -1532,13 +1594,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
struct notes_rewrite_cfg *cfg;
cfg = init_copy_notes_for_rewrite("amend");
if (cfg) {
- copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+ /* we are amending, so current_head is not NULL */
+ copy_note_for_rewrite(cfg, current_head->object.sha1, sha1);
finish_copy_notes_for_rewrite(cfg);
}
- run_rewrite_hook(head_sha1, commit_sha1);
+ run_rewrite_hook(current_head->object.sha1, sha1);
}
if (!quiet)
- print_summary(prefix, commit_sha1);
+ print_summary(prefix, sha1, !current_head);
return 0;
}
diff --git a/builtin/config.c b/builtin/config.c
index 211e118..e8e1c0a 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -25,6 +25,7 @@ static const char *given_config_file;
static int actions, types;
static const char *get_color_slot, *get_colorbool_slot;
static int end_null;
+static int respect_includes = -1;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
@@ -74,6 +75,7 @@ static struct option builtin_config_options[] = {
OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
OPT_GROUP("Other"),
OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
+ OPT_BOOL(0, "includes", &respect_includes, "respect include directives on lookup"),
OPT_END(),
};
@@ -99,6 +101,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
const char *vptr = value;
int must_free_vptr = 0;
int dup_error = 0;
+ int must_print_delim = 0;
if (!use_key_regexp && strcmp(key_, key))
return 0;
@@ -109,10 +112,8 @@ static int show_config(const char *key_, const char *value_, void *cb)
return 0;
if (show_keys) {
- if (value_)
- printf("%s%c", key_, key_delim);
- else
- printf("%s", key_);
+ printf("%s", key_);
+ must_print_delim = 1;
}
if (seen && !do_all)
dup_error = 1;
@@ -130,16 +131,23 @@ static int show_config(const char *key_, const char *value_, void *cb)
} else if (types == TYPE_PATH) {
git_config_pathname(&vptr, key_, value_);
must_free_vptr = 1;
+ } else if (value_) {
+ vptr = value_;
+ } else {
+ /* Just show the key name */
+ vptr = "";
+ must_print_delim = 0;
}
- else
- vptr = value_?value_:"";
seen++;
if (dup_error) {
error("More than one value for the key %s: %s",
key_, vptr);
}
- else
+ else {
+ if (must_print_delim)
+ printf("%c", key_delim);
printf("%s%c", vptr, term);
+ }
if (must_free_vptr)
/* If vptr must be freed, it's a pointer to a
* dynamically allocated buffer, it's safe to cast to
@@ -153,17 +161,18 @@ static int show_config(const char *key_, const char *value_, void *cb)
static int get_value(const char *key_, const char *regex_)
{
int ret = -1;
- char *global = NULL, *repo_config = NULL;
+ char *global = NULL, *xdg = NULL, *repo_config = NULL;
const char *system_wide = NULL, *local;
+ struct config_include_data inc = CONFIG_INCLUDE_INIT;
+ config_fn_t fn;
+ void *data;
- local = config_exclusive_filename;
+ local = given_config_file;
if (!local) {
- const char *home = getenv("HOME");
local = repo_config = git_pathdup("config");
- if (home)
- global = xstrdup(mkpath("%s/.gitconfig", home));
if (git_config_system())
system_wide = git_etc_gitconfig();
+ home_config_paths(&global, &xdg, "config");
}
if (use_key_regexp) {
@@ -207,19 +216,32 @@ static int get_value(const char *key_, const char *regex_)
}
}
+ fn = show_config;
+ data = NULL;
+ if (respect_includes) {
+ inc.fn = fn;
+ inc.data = data;
+ fn = git_config_include;
+ data = &inc;
+ }
+
if (do_all && system_wide)
- git_config_from_file(show_config, system_wide, NULL);
+ git_config_from_file(fn, system_wide, data);
+ if (do_all && xdg)
+ git_config_from_file(fn, xdg, data);
if (do_all && global)
- git_config_from_file(show_config, global, NULL);
+ git_config_from_file(fn, global, data);
if (do_all)
- git_config_from_file(show_config, local, NULL);
- git_config_from_parameters(show_config, NULL);
+ git_config_from_file(fn, local, data);
+ git_config_from_parameters(fn, data);
if (!do_all && !seen)
- git_config_from_file(show_config, local, NULL);
+ git_config_from_file(fn, local, data);
if (!do_all && !seen && global)
- git_config_from_file(show_config, global, NULL);
+ git_config_from_file(fn, global, data);
+ if (!do_all && !seen && xdg)
+ git_config_from_file(fn, xdg, data);
if (!do_all && !seen && system_wide)
- git_config_from_file(show_config, system_wide, NULL);
+ git_config_from_file(fn, system_wide, data);
free(key);
if (regexp) {
@@ -235,6 +257,7 @@ static int get_value(const char *key_, const char *regex_)
free_strings:
free(repo_config);
free(global);
+ free(xdg);
return ret;
}
@@ -295,7 +318,8 @@ static void get_color(const char *def_color)
{
get_color_found = 0;
parsed_color[0] = '\0';
- git_config(git_get_color_config, NULL);
+ git_config_with_options(git_get_color_config, NULL,
+ given_config_file, respect_includes);
if (!get_color_found && def_color)
color_parse(def_color, "command line", parsed_color);
@@ -303,24 +327,18 @@ static void get_color(const char *def_color)
fputs(parsed_color, stdout);
}
-static int stdout_is_tty;
static int get_colorbool_found;
static int get_diff_color_found;
+static int get_color_ui_found;
static int git_get_colorbool_config(const char *var, const char *value,
void *cb)
{
- if (!strcmp(var, get_colorbool_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);
- }
- if (!strcmp(var, "color.ui")) {
- git_use_color_default = git_config_colorbool(var, value, stdout_is_tty);
- return 0;
- }
+ if (!strcmp(var, get_colorbool_slot))
+ get_colorbool_found = git_config_colorbool(var, value);
+ else if (!strcmp(var, "diff.color"))
+ get_diff_color_found = git_config_colorbool(var, value);
+ else if (!strcmp(var, "color.ui"))
+ get_color_ui_found = git_config_colorbool(var, value);
return 0;
}
@@ -328,15 +346,18 @@ static int get_colorbool(int print)
{
get_colorbool_found = -1;
get_diff_color_found = -1;
- git_config(git_get_colorbool_config, NULL);
+ git_config_with_options(git_get_colorbool_config, NULL,
+ given_config_file, respect_includes);
if (get_colorbool_found < 0) {
if (!strcmp(get_colorbool_slot, "color.diff"))
get_colorbool_found = get_diff_color_found;
if (get_colorbool_found < 0)
- get_colorbool_found = git_use_color_default;
+ get_colorbool_found = get_color_ui_found;
}
+ get_colorbool_found = want_color(get_colorbool_found);
+
if (print) {
printf("%s\n", get_colorbool_found ? "true" : "false");
return 0;
@@ -349,7 +370,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
int nongit = !startup_info->have_repository;
char *value;
- config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
+ given_config_file = getenv(CONFIG_ENVIRONMENT);
argc = parse_options(argc, argv, prefix, builtin_config_options,
builtin_config_usage,
@@ -361,27 +382,33 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
if (use_global_config) {
- char *home = getenv("HOME");
- if (home) {
- char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
- config_exclusive_filename = user_config;
- } else {
+ char *user_config = NULL;
+ char *xdg_config = NULL;
+
+ home_config_paths(&user_config, &xdg_config, "config");
+
+ if (access(user_config, R_OK) && !access(xdg_config, R_OK))
+ given_config_file = xdg_config;
+ else if (user_config)
+ given_config_file = user_config;
+ else
die("$HOME not set");
- }
}
else if (use_system_config)
- config_exclusive_filename = git_etc_gitconfig();
+ given_config_file = git_etc_gitconfig();
else if (use_local_config)
- config_exclusive_filename = git_pathdup("config");
+ given_config_file = git_pathdup("config");
else if (given_config_file) {
if (!is_absolute_path(given_config_file) && prefix)
- config_exclusive_filename = prefix_filename(prefix,
- strlen(prefix),
- given_config_file);
- else
- config_exclusive_filename = given_config_file;
+ given_config_file =
+ xstrdup(prefix_filename(prefix,
+ strlen(prefix),
+ given_config_file));
}
+ if (respect_includes == -1)
+ respect_includes = !given_config_file;
+
if (end_null) {
term = '\0';
delim = '\n';
@@ -418,47 +445,52 @@ int cmd_config(int argc, const char **argv, const char *prefix)
if (actions == ACTION_LIST) {
check_argc(argc, 0, 0);
- if (git_config(show_all_config, NULL) < 0) {
- if (config_exclusive_filename)
+ if (git_config_with_options(show_all_config, NULL,
+ given_config_file,
+ respect_includes) < 0) {
+ if (given_config_file)
die_errno("unable to read config file '%s'",
- config_exclusive_filename);
+ given_config_file);
else
die("error processing config file(s)");
}
}
else if (actions == ACTION_EDIT) {
check_argc(argc, 0, 0);
- if (!config_exclusive_filename && nongit)
+ if (!given_config_file && nongit)
die("not in a git directory");
git_config(git_default_config, NULL);
- launch_editor(config_exclusive_filename ?
- config_exclusive_filename : git_path("config"),
+ launch_editor(given_config_file ?
+ given_config_file : git_path("config"),
NULL, NULL);
}
else if (actions == ACTION_SET) {
int ret;
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
- ret = git_config_set(argv[0], value);
+ ret = git_config_set_in_file(given_config_file, argv[0], value);
if (ret == CONFIG_NOTHING_SET)
error("cannot overwrite multiple values with a single value\n"
- " Use a regexp, --add or --set-all to change %s.", argv[0]);
+ " Use a regexp, --add or --replace-all to change %s.", argv[0]);
return ret;
}
else if (actions == ACTION_SET_ALL) {
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, argv[2], 0);
+ return git_config_set_multivar_in_file(given_config_file,
+ argv[0], value, argv[2], 0);
}
else if (actions == ACTION_ADD) {
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, "^$", 0);
+ return git_config_set_multivar_in_file(given_config_file,
+ argv[0], value, "^$", 0);
}
else if (actions == ACTION_REPLACE_ALL) {
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, argv[2], 1);
+ return git_config_set_multivar_in_file(given_config_file,
+ argv[0], value, argv[2], 1);
}
else if (actions == ACTION_GET) {
check_argc(argc, 1, 2);
@@ -479,18 +511,22 @@ int cmd_config(int argc, const char **argv, const char *prefix)
else if (actions == ACTION_UNSET) {
check_argc(argc, 1, 2);
if (argc == 2)
- return git_config_set_multivar(argv[0], NULL, argv[1], 0);
+ return git_config_set_multivar_in_file(given_config_file,
+ argv[0], NULL, argv[1], 0);
else
- return git_config_set(argv[0], NULL);
+ return git_config_set_in_file(given_config_file,
+ argv[0], NULL);
}
else if (actions == ACTION_UNSET_ALL) {
check_argc(argc, 1, 2);
- return git_config_set_multivar(argv[0], NULL, argv[1], 1);
+ return git_config_set_multivar_in_file(given_config_file,
+ argv[0], NULL, argv[1], 1);
}
else if (actions == ACTION_RENAME_SECTION) {
int ret;
check_argc(argc, 2, 2);
- ret = git_config_rename_section(argv[0], argv[1]);
+ ret = git_config_rename_section_in_file(given_config_file,
+ argv[0], argv[1]);
if (ret < 0)
return ret;
if (ret == 0)
@@ -499,7 +535,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
else if (actions == ACTION_REMOVE_SECTION) {
int ret;
check_argc(argc, 1, 1);
- ret = git_config_rename_section(argv[0], NULL);
+ ret = git_config_rename_section_in_file(given_config_file,
+ argv[0], NULL);
if (ret < 0)
return ret;
if (ret == 0)
@@ -510,9 +547,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_GET_COLORBOOL) {
if (argc == 1)
- stdout_is_tty = git_config_bool("command line", argv[0]);
- else if (argc == 0)
- stdout_is_tty = isatty(1);
+ color_stdout_is_tty = git_config_bool("command line", argv[0]);
return get_colorbool(argc != 0);
}
diff --git a/builtin/credential.c b/builtin/credential.c
new file mode 100644
index 0000000..0412fa0
--- /dev/null
+++ b/builtin/credential.c
@@ -0,0 +1,31 @@
+#include "git-compat-util.h"
+#include "credential.h"
+#include "builtin.h"
+
+static const char usage_msg[] =
+ "git credential [fill|approve|reject]";
+
+int cmd_credential(int argc, const char **argv, const char *prefix)
+{
+ const char *op;
+ struct credential c = CREDENTIAL_INIT;
+
+ op = argv[1];
+ if (!op)
+ usage(usage_msg);
+
+ if (credential_read(&c, stdin) < 0)
+ die("unable to read credential from stdin");
+
+ if (!strcmp(op, "fill")) {
+ credential_fill(&c);
+ credential_write(&c, stdout);
+ } else if (!strcmp(op, "approve")) {
+ credential_approve(&c);
+ } else if (!strcmp(op, "reject")) {
+ credential_reject(&c);
+ } else {
+ usage(usage_msg);
+ }
+ return 0;
+}
diff --git a/builtin/diff.c b/builtin/diff.c
index 69cd5ee..da8f6aa 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -14,6 +14,7 @@
#include "log-tree.h"
#include "builtin.h"
#include "submodule.h"
+#include "sha1-array.h"
struct blobinfo {
unsigned char sha1[20];
@@ -169,7 +170,7 @@ static int builtin_diff_combined(struct rev_info *revs,
struct object_array_entry *ent,
int ents)
{
- const unsigned char (*parent)[20];
+ struct sha1_array parents = SHA1_ARRAY_INIT;
int i;
if (argc > 1)
@@ -177,12 +178,11 @@ static int builtin_diff_combined(struct rev_info *revs,
if (!revs->dense_combined_merges && !revs->combine_merges)
revs->dense_combined_merges = revs->combine_merges = 1;
- parent = xmalloc(ents * sizeof(*parent));
- for (i = 0; i < ents; i++)
- hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
- diff_tree_combined(parent[0], parent + 1, ents - 1,
+ for (i = 1; i < ents; i++)
+ sha1_array_append(&parents, ent[i].item->sha1);
+ diff_tree_combined(ent[0].item->sha1, &parents,
revs->dense_combined_merges, revs);
- free(parent);
+ sha1_array_clear(&parents);
return 0;
}
@@ -277,9 +277,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
gitmodules_config();
git_config(git_diff_ui_config, NULL);
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
init_revisions(&rev, prefix);
/* If this is a no-index diff, just run it and exit there. */
@@ -288,6 +285,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
/* Otherwise, we are doing the usual "git" diff */
rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
+ /* Scale to real terminal size and respect statGraphWidth config */
+ rev.diffopt.stat_width = -1;
+ rev.diffopt.stat_graph_width = -1;
+
/* Default to let external and textconv be used */
DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
@@ -303,13 +304,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
- /*
- * 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 (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) &&
- check_pager_config("diff") != 0)
- setup_pager();
+ setup_diff_pager(&rev.diffopt);
/*
* Do we have --cached and not have a pending object, then
@@ -326,7 +321,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
add_head_to_pending(&rev);
if (!rev.pending.nr) {
struct tree *tree;
- tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN);
+ tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
add_pending_object(&rev, &tree->object, "HEAD");
}
break;
@@ -420,3 +415,19 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
refresh_index_quietly();
return result;
}
+
+void setup_diff_pager(struct diff_options *opt)
+{
+ /*
+ * If the user asked for our exit code, then either they want --quiet
+ * or --exit-code. We should definitely not bother with a pager in the
+ * former case, as we will generate no output. Since we still properly
+ * report our exit code even when a pager is run, we _could_ run a
+ * pager with --exit-code. But since we have not done so historically,
+ * and because it is easy to find people oneline advising "git diff
+ * --exit-code" in hooks and other scripts, we do not do so.
+ */
+ if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+ check_pager_config("diff") != 0)
+ setup_pager();
+}
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 48bd5e6..ef7c012 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -25,8 +25,9 @@ static const char *fast_export_usage[] = {
static int progress;
static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
-static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
+static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR;
static int fake_missing_tagger;
+static int use_done_feature;
static int no_data;
static int full_tree;
@@ -50,7 +51,7 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt,
const char *arg, int unset)
{
if (unset || !strcmp(arg, "abort"))
- tag_of_filtered_mode = ABORT;
+ tag_of_filtered_mode = ERROR;
else if (!strcmp(arg, "drop"))
tag_of_filtered_mode = DROP;
else if (!strcmp(arg, "rewrite"))
@@ -609,7 +610,7 @@ static void import_marks(char *input_file)
die ("Could not read blob %s", sha1_to_hex(sha1));
if (object->flags & SHOWN)
- error("Object %s already has a mark", sha1);
+ error("Object %s already has a mark", sha1_to_hex(sha1));
mark_object(object, mark);
if (last_idnum < mark)
@@ -644,9 +645,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
"Fake a tagger when tags lack one"),
OPT_BOOLEAN(0, "full-tree", &full_tree,
"Output full tree for each commit"),
- { OPTION_NEGBIT, 0, "data", &no_data, NULL,
- "Skip output of blob data",
- PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
+ OPT_BOOLEAN(0, "use-done-feature", &use_done_feature,
+ "Use the done feature to terminate the stream"),
+ OPT_BOOL(0, "no-data", &no_data, "Skip output of blob data"),
OPT_END()
};
@@ -665,6 +666,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (argc > 1)
usage_with_options (fast_export_usage, options);
+ if (use_done_feature)
+ printf("feature done\n");
+
if (import_filename)
import_marks(import_filename);
@@ -692,5 +696,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (export_filename)
export_marks(export_filename);
+ if (use_done_feature)
+ printf("done\n");
+
return 0;
}
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index 3c871c2..149db88 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -15,13 +15,17 @@ static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
static int unpack_limit = 100;
static int prefer_ofs_delta = 1;
-static int no_done = 0;
+static int no_done;
+static int fetch_fsck_objects = -1;
+static int transfer_fsck_objects = -1;
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] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
+"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
+"[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
+"[--no-progress] [-v] [<host>:]<directory> [<refs>...]";
#define COMPLETE (1U << 0)
#define COMMON (1U << 1)
@@ -56,9 +60,9 @@ static void rev_list_push(struct commit *commit, int mark)
}
}
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
- struct object *o = deref_tag(parse_object(sha1), path, 0);
+ struct object *o = deref_tag(parse_object(sha1), refname, 0);
if (o && o->type == OBJ_COMMIT)
rev_list_push((struct commit *)o, SEEN);
@@ -66,9 +70,9 @@ static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int
return 0;
}
-static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
- struct object *o = deref_tag(parse_object(sha1), path, 0);
+ struct object *o = deref_tag(parse_object(sha1), refname, 0);
if (o && o->type == OBJ_COMMIT)
clear_commit_marks((struct commit *)o,
@@ -185,6 +189,36 @@ static void consume_shallow_list(int fd)
}
}
+struct write_shallow_data {
+ struct strbuf *out;
+ int use_pack_protocol;
+ int count;
+};
+
+static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
+{
+ struct write_shallow_data *data = cb_data;
+ const char *hex = sha1_to_hex(graft->sha1);
+ data->count++;
+ if (data->use_pack_protocol)
+ packet_buf_write(data->out, "shallow %s", hex);
+ else {
+ strbuf_addstr(data->out, hex);
+ strbuf_addch(data->out, '\n');
+ }
+ return 0;
+}
+
+static int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
+{
+ struct write_shallow_data data;
+ data.out = out;
+ data.use_pack_protocol = use_pack_protocol;
+ data.count = 0;
+ for_each_commit_graft(write_one_shallow, &data);
+ return data.count;
+}
+
static enum ack_type get_ack(int fd, unsigned char *result_sha1)
{
static char line[1000];
@@ -224,11 +258,6 @@ static void insert_one_alternate_ref(const struct ref *ref, void *unused)
rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL);
}
-static void insert_alternate_refs(void)
-{
- for_each_alternate_ref(insert_one_alternate_ref, NULL);
-}
-
#define INITIAL_FLUSH 16
#define PIPESAFE_FLUSH 32
#define LARGE_FLUSH 1024
@@ -263,7 +292,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
marked = 1;
for_each_ref(rev_list_insert_ref, NULL);
- insert_alternate_refs();
+ for_each_alternate_ref(insert_one_alternate_ref, NULL);
fetching = 0;
for ( ; refs ; refs = refs->next) {
@@ -461,7 +490,7 @@ done:
static struct commit_list *complete;
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *o = parse_object(sha1);
@@ -499,6 +528,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
struct ref **newtail = &newlist;
struct ref *ref, *next;
struct ref *fastarray[32];
+ int match_pos;
if (nr_match && !args.fetch_all) {
if (ARRAY_SIZE(fastarray) < nr_match)
@@ -511,10 +541,11 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
else
return_refs = NULL;
+ match_pos = 0;
for (ref = *refs; ref; ref = next) {
next = ref->next;
if (!memcmp(ref->name, "refs/", 5) &&
- check_ref_format(ref->name + 5))
+ check_refname_format(ref->name + 5, 0))
; /* trash */
else if (args.fetch_all &&
(!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
@@ -524,11 +555,21 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
continue;
}
else {
- int order = path_match(ref->name, nr_match, match);
- if (order) {
- return_refs[order-1] = ref;
- continue; /* we will link it later */
+ int cmp = -1;
+ while (match_pos < nr_match) {
+ cmp = strcmp(ref->name, match[match_pos]);
+ if (cmp < 0) /* definitely do not have it */
+ break;
+ else if (cmp == 0) { /* definitely have it */
+ match[match_pos][0] = '\0';
+ return_refs[match_pos] = ref;
+ break;
+ }
+ else /* might have it; keep looking */
+ match_pos++;
}
+ if (!cmp)
+ continue; /* we will link it later */
}
free(ref);
}
@@ -549,6 +590,11 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
*refs = newlist;
}
+static void mark_alternate_complete(const struct ref *ref, void *unused)
+{
+ mark_complete(NULL, ref->old_sha1, 0, NULL);
+}
+
static int everything_local(struct ref **refs, int nr_match, char **match)
{
struct ref *ref;
@@ -577,6 +623,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
if (!args.depth) {
for_each_ref(mark_complete, NULL);
+ for_each_alternate_ref(mark_alternate_complete, NULL);
if (cutoff)
mark_recent_complete_commits(cutoff);
}
@@ -699,11 +746,17 @@ static int get_pack(int xd[2], char **pack_lockfile)
}
else {
*av++ = "unpack-objects";
- if (args.quiet)
+ if (args.quiet || args.no_progress)
*av++ = "-q";
}
if (*hdr_arg)
*av++ = hdr_arg;
+ if (fetch_fsck_objects >= 0
+ ? fetch_fsck_objects
+ : transfer_fsck_objects >= 0
+ ? transfer_fsck_objects
+ : 0)
+ *av++ = "--strict";
*av++ = NULL;
cmd.in = demux.out;
@@ -731,6 +784,8 @@ static struct ref *do_fetch_pack(int fd[2],
struct ref *ref = copy_ref_list(orig_ref);
unsigned char sha1[20];
+ sort_ref_list(&ref, ref_compare_name);
+
if (is_repository_shallow() && !server_supports("shallow"))
die("Server does not support shallow clients");
if (server_supports("multi_ack_detailed")) {
@@ -788,21 +843,12 @@ static int remove_duplicates(int nr_heads, char **heads)
{
int src, dst;
- for (src = dst = 0; src < nr_heads; src++) {
- /* If heads[src] is different from any of
- * heads[0..dst], push it in.
- */
- int i;
- for (i = 0; i < dst; i++) {
- if (!strcmp(heads[i], heads[src]))
- break;
- }
- if (i < dst)
- continue;
- if (src != dst)
- heads[dst] = heads[src];
- dst++;
- }
+ if (!nr_heads)
+ return 0;
+
+ for (src = dst = 1; src < nr_heads; src++)
+ if (strcmp(heads[src], heads[dst-1]))
+ heads[dst++] = heads[src];
return dst;
}
@@ -823,6 +869,16 @@ static int fetch_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (!strcmp(var, "fetch.fsckobjects")) {
+ fetch_fsck_objects = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (!strcmp(var, "transfer.fsckobjects")) {
+ transfer_fsck_objects = git_config_bool(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -843,9 +899,11 @@ static void fetch_pack_setup(void)
int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
{
- int i, ret, nr_heads;
+ int i, ret;
struct ref *ref = NULL;
- char *dest = NULL, **heads;
+ const char *dest = NULL;
+ int alloc_heads = 0, nr_heads = 0;
+ char **heads = NULL;
int fd[2];
char *pack_lockfile = NULL;
char **pack_lockfile_ptr = NULL;
@@ -853,82 +911,115 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
packet_trace_identity("fetch-pack");
- nr_heads = 0;
- heads = NULL;
- for (i = 1; i < argc; i++) {
+ for (i = 1; i < argc && *argv[i] == '-'; i++) {
const char *arg = argv[i];
- if (*arg == '-') {
- if (!prefixcmp(arg, "--upload-pack=")) {
- args.uploadpack = arg + 14;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- args.uploadpack = arg + 7;
- continue;
- }
- if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
- args.quiet = 1;
- continue;
- }
- if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
- args.lock_pack = args.keep_pack;
- args.keep_pack = 1;
- continue;
- }
- if (!strcmp("--thin", arg)) {
- args.use_thin_pack = 1;
- continue;
- }
- if (!strcmp("--include-tag", arg)) {
- args.include_tag = 1;
- continue;
- }
- if (!strcmp("--all", arg)) {
- args.fetch_all = 1;
- continue;
- }
- if (!strcmp("-v", arg)) {
- args.verbose = 1;
- continue;
- }
- if (!prefixcmp(arg, "--depth=")) {
- args.depth = strtol(arg + 8, NULL, 0);
- continue;
- }
- if (!strcmp("--no-progress", arg)) {
- args.no_progress = 1;
- continue;
- }
- if (!strcmp("--stateless-rpc", arg)) {
- args.stateless_rpc = 1;
- continue;
+ if (!prefixcmp(arg, "--upload-pack=")) {
+ args.uploadpack = arg + 14;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ args.uploadpack = arg + 7;
+ continue;
+ }
+ if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
+ args.quiet = 1;
+ continue;
+ }
+ if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
+ args.lock_pack = args.keep_pack;
+ args.keep_pack = 1;
+ continue;
+ }
+ if (!strcmp("--thin", arg)) {
+ args.use_thin_pack = 1;
+ continue;
+ }
+ if (!strcmp("--include-tag", arg)) {
+ args.include_tag = 1;
+ continue;
+ }
+ if (!strcmp("--all", arg)) {
+ args.fetch_all = 1;
+ continue;
+ }
+ if (!strcmp("--stdin", arg)) {
+ args.stdin_refs = 1;
+ continue;
+ }
+ if (!strcmp("-v", arg)) {
+ args.verbose = 1;
+ continue;
+ }
+ if (!prefixcmp(arg, "--depth=")) {
+ args.depth = strtol(arg + 8, NULL, 0);
+ continue;
+ }
+ if (!strcmp("--no-progress", arg)) {
+ args.no_progress = 1;
+ continue;
+ }
+ if (!strcmp("--stateless-rpc", arg)) {
+ args.stateless_rpc = 1;
+ continue;
+ }
+ if (!strcmp("--lock-pack", arg)) {
+ args.lock_pack = 1;
+ pack_lockfile_ptr = &pack_lockfile;
+ continue;
+ }
+ usage(fetch_pack_usage);
+ }
+
+ if (i < argc)
+ dest = argv[i++];
+ else
+ usage(fetch_pack_usage);
+
+ /*
+ * Copy refs from cmdline to growable list, then append any
+ * refs from the standard input:
+ */
+ ALLOC_GROW(heads, argc - i, alloc_heads);
+ for (; i < argc; i++)
+ heads[nr_heads++] = xstrdup(argv[i]);
+ if (args.stdin_refs) {
+ if (args.stateless_rpc) {
+ /* in stateless RPC mode we use pkt-line to read
+ * from stdin, until we get a flush packet
+ */
+ static char line[1000];
+ for (;;) {
+ int n = packet_read_line(0, line, sizeof(line));
+ if (!n)
+ break;
+ if (line[n-1] == '\n')
+ n--;
+ ALLOC_GROW(heads, nr_heads + 1, alloc_heads);
+ heads[nr_heads++] = xmemdupz(line, n);
}
- if (!strcmp("--lock-pack", arg)) {
- args.lock_pack = 1;
- pack_lockfile_ptr = &pack_lockfile;
- continue;
+ }
+ else {
+ /* read from stdin one ref per line, until EOF */
+ struct strbuf line = STRBUF_INIT;
+ while (strbuf_getline(&line, stdin, '\n') != EOF) {
+ ALLOC_GROW(heads, nr_heads + 1, alloc_heads);
+ heads[nr_heads++] = strbuf_detach(&line, NULL);
}
- usage(fetch_pack_usage);
+ strbuf_release(&line);
}
- dest = (char *)arg;
- heads = (char **)(argv + i + 1);
- nr_heads = argc - i - 1;
- break;
}
- if (!dest)
- usage(fetch_pack_usage);
if (args.stateless_rpc) {
conn = NULL;
fd[0] = 0;
fd[1] = 1;
} else {
- conn = git_connect(fd, (char *)dest, args.uploadpack,
+ conn = git_connect(fd, dest, args.uploadpack,
args.verbose ? CONNECT_VERBOSE : 0);
}
- get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
+ get_remote_heads(fd[0], &ref, 0, NULL);
ref = fetch_pack(&args, fd, conn, ref, dest,
nr_heads, heads, pack_lockfile_ptr);
@@ -963,6 +1054,11 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
return ret;
}
+static int compare_heads(const void *a, const void *b)
+{
+ return strcmp(*(const char **)a, *(const char **)b);
+}
+
struct ref *fetch_pack(struct fetch_pack_args *my_args,
int fd[], struct child_process *conn,
const struct ref *ref,
@@ -982,8 +1078,11 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
st.st_mtime = 0;
}
- if (heads && nr_heads)
+ if (heads && nr_heads) {
+ qsort(heads, nr_heads, sizeof(*heads), compare_heads);
nr_heads = remove_duplicates(nr_heads, heads);
+ }
+
if (!ref) {
packet_flush(fd[1]);
die("no matching remote head");
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 914cb91..bb9a074 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -13,6 +13,7 @@
#include "sigchain.h"
#include "transport.h"
#include "submodule.h"
+#include "connected.h"
static const char * const builtin_fetch_usage[] = {
"git fetch [<options>] [<repository> [<refspec>...]]",
@@ -29,7 +30,7 @@ enum {
};
static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
static int tags = TAGS_DEFAULT;
static const char *depth;
static const char *upload_pack;
@@ -77,7 +78,7 @@ static struct option builtin_fetch_options[] = {
OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
"allow updating of HEAD ref"),
- OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+ OPT_BOOL(0, "progress", &progress, "force progress reporting"),
OPT_STRING(0, "depth", &depth, "depth",
"deepen history of shallow clone"),
{ OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir",
@@ -239,6 +240,7 @@ static int s_update_ref(const char *action,
static int update_local_ref(struct ref *ref,
const char *remote,
+ const struct ref *remote_ref,
struct strbuf *display)
{
struct commit *current = NULL, *updated;
@@ -292,18 +294,26 @@ static int update_local_ref(struct ref *ref,
const char *msg;
const char *what;
int r;
- if (!strncmp(ref->name, "refs/tags/", 10)) {
+ /*
+ * Nicely describe the new ref we're fetching.
+ * Base this on the remote's ref name, as it's
+ * more likely to follow a standard layout.
+ */
+ const char *name = remote_ref ? remote_ref->name : "";
+ if (!prefixcmp(name, "refs/tags/")) {
msg = "storing tag";
what = _("[new tag]");
- }
- else {
+ } else if (!prefixcmp(name, "refs/heads/")) {
msg = "storing head";
what = _("[new branch]");
- if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
- (recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(ref->new_sha1);
+ } else {
+ msg = "storing ref";
+ what = _("[new ref]");
}
+ if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
+ (recurse_submodules != RECURSE_SUBMODULES_ON))
+ check_for_new_submodule_commits(ref->new_sha1);
r = s_update_ref(msg, ref, 0);
strbuf_addf(display, "%c %-*s %-*s -> %s%s",
r ? '!' : '*',
@@ -354,6 +364,18 @@ static int update_local_ref(struct ref *ref,
}
}
+static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
+{
+ struct ref **rm = cb_data;
+ struct ref *ref = *rm;
+
+ if (!ref)
+ return -1; /* end of the list */
+ *rm = ref->next;
+ hashcpy(sha1, ref->old_sha1);
+ return 0;
+}
+
static int store_updated_refs(const char *raw_url, const char *remote_name,
struct ref *ref_map)
{
@@ -364,6 +386,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
const char *what, *kind;
struct ref *rm;
char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+ int want_merge;
fp = fopen(filename, "a");
if (!fp)
@@ -373,94 +396,114 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
url = transport_anonymize_url(raw_url);
else
url = xstrdup("foreign");
- 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;
- }
+ rm = ref_map;
+ if (check_everything_connected(iterate_ref_map, 0, &rm)) {
+ rc = error(_("%s did not send all necessary objects\n"), url);
+ goto abort;
+ }
- commit = lookup_commit_reference_gently(rm->old_sha1, 1);
- if (!commit)
- rm->merge = 0;
+ /*
+ * The first pass writes objects to be merged and then the
+ * second pass writes the rest, in order to allow using
+ * FETCH_HEAD as a refname to refer to the ref to be merged.
+ */
+ for (want_merge = 1; 0 <= want_merge; want_merge--) {
+ for (rm = ref_map; rm; rm = rm->next) {
+ struct ref *ref = NULL;
+
+ commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+ if (!commit)
+ rm->merge = 0;
+
+ if (rm->merge != want_merge)
+ continue;
+
+ 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;
+ }
- 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-tracking 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;
-
- strbuf_reset(&note);
- if (*what) {
- if (*kind)
- strbuf_addf(&note, "%s ", kind);
- strbuf_addf(&note, "'%s' of ", what);
- }
- fprintf(fp, "%s\t%s\t%s",
- sha1_to_hex(commit ? commit->object.sha1 :
- rm->old_sha1),
- rm->merge ? "" : "not-for-merge",
- note.buf);
- for (i = 0; i < url_len; ++i)
- if ('\n' == url[i])
- fputs("\\n", fp);
- else
- fputc(url[i], fp);
- fputc('\n', fp);
-
- strbuf_reset(&note);
- if (ref) {
- rc |= update_local_ref(ref, what, &note);
- free(ref);
- } else
- strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
- TRANSPORT_SUMMARY_WIDTH,
- *kind ? kind : "branch",
- REFCOL_WIDTH,
- *what ? what : "HEAD");
- if (note.len) {
- if (verbosity >= 0 && !shown_url) {
- fprintf(stderr, _("From %.*s\n"),
- url_len, url);
- shown_url = 1;
+ 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-tracking 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;
+
+ strbuf_reset(&note);
+ if (*what) {
+ if (*kind)
+ strbuf_addf(&note, "%s ", kind);
+ strbuf_addf(&note, "'%s' of ", what);
+ }
+ fprintf(fp, "%s\t%s\t%s",
+ sha1_to_hex(rm->old_sha1),
+ rm->merge ? "" : "not-for-merge",
+ note.buf);
+ for (i = 0; i < url_len; ++i)
+ if ('\n' == url[i])
+ fputs("\\n", fp);
+ else
+ fputc(url[i], fp);
+ fputc('\n', fp);
+
+ strbuf_reset(&note);
+ if (ref) {
+ rc |= update_local_ref(ref, what, rm, &note);
+ free(ref);
+ } else
+ strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
+ TRANSPORT_SUMMARY_WIDTH,
+ *kind ? kind : "branch",
+ REFCOL_WIDTH,
+ *what ? what : "HEAD");
+ if (note.len) {
+ if (verbosity >= 0 && !shown_url) {
+ fprintf(stderr, _("From %.*s\n"),
+ url_len, url);
+ shown_url = 1;
+ }
+ if (verbosity >= 0)
+ fprintf(stderr, " %s\n", note.buf);
}
- if (verbosity >= 0)
- fprintf(stderr, " %s\n", note.buf);
}
}
- free(url);
- fclose(fp);
+
if (rc & STORE_REF_ERROR_DF_CONFLICT)
error(_("some local refs could not be updated; try running\n"
" 'git remote prune %s' to remove any old, conflicting "
"branches"), remote_name);
+
+ abort:
strbuf_release(&note);
+ free(url);
+ fclose(fp);
return rc;
}
@@ -468,23 +511,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
* We would want to bypass the object transfer altogether if
* everything we are going to fetch already exists and is connected
* locally.
- *
- * The refs we are going to fetch are in ref_map. If running
- *
- * $ git rev-list --objects --stdin --not --all
- *
- * (feeding all the refs in ref_map on its standard input)
- * 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;
- int err;
- const char *argv[] = {"rev-list",
- "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
+ struct ref *rm = ref_map;
/*
* If we are deepening a shallow clone we already have these
@@ -495,47 +525,7 @@ static int quickfetch(struct ref *ref_map)
*/
if (depth)
return -1;
-
- if (!ref_map)
- return 0;
-
- memset(&revlist, 0, sizeof(revlist));
- revlist.argv = argv;
- revlist.git_cmd = 1;
- revlist.no_stdout = 1;
- revlist.no_stderr = 1;
- revlist.in = -1;
-
- err = start_command(&revlist);
- if (err) {
- error(_("could not run rev-list"));
- return err;
- }
-
- /*
- * If rev-list --stdin encounters an unknown commit, it terminates,
- * which will cause SIGPIPE in the write loop below.
- */
- sigchain_push(SIGPIPE, SIG_IGN);
-
- for (ref = ref_map; ref; ref = ref->next) {
- if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
- write_str_in_full(revlist.in, "\n") < 0) {
- if (errno != EPIPE && errno != EINVAL)
- error(_("failed write to rev-list: %s"), strerror(errno));
- err = -1;
- break;
- }
- }
-
- if (close(revlist.in)) {
- error(_("failed to close rev-list's stdin: %s"), strerror(errno));
- err = -1;
- }
-
- sigchain_pop(SIGPIPE);
-
- return finish_command(&revlist) || err;
+ return check_everything_connected(iterate_ref_map, 1, &rm);
}
static int fetch_refs(struct transport *transport, struct ref *ref_map)
@@ -551,13 +541,13 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
return ret;
}
-static int prune_refs(struct transport *transport, struct ref *ref_map)
+static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
{
int result = 0;
- struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+ struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
const char *dangling_msg = dry_run
- ? _(" (%s will become dangling)\n")
- : _(" (%s has become dangling)\n");
+ ? _(" (%s will become dangling)")
+ : _(" (%s has become dangling)");
for (ref = stale_refs; ref; ref = ref->next) {
if (!dry_run)
@@ -604,7 +594,7 @@ static void find_non_local_tags(struct transport *transport,
for_each_ref(add_existing, &existing_refs);
for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
- if (prefixcmp(ref->name, "refs/tags"))
+ if (prefixcmp(ref->name, "refs/tags/"))
continue;
/*
@@ -745,8 +735,31 @@ static int do_fetch(struct transport *transport,
free_refs(ref_map);
return 1;
}
- if (prune)
- prune_refs(transport, ref_map);
+ if (prune) {
+ /* If --tags was specified, pretend the user gave us the canonical tags refspec */
+ if (tags == TAGS_SET) {
+ const char *tags_str = "refs/tags/*:refs/tags/*";
+ struct refspec *tags_refspec, *refspec;
+
+ /* Copy the refspec and add the tags to it */
+ refspec = xcalloc(ref_count + 1, sizeof(struct refspec));
+ tags_refspec = parse_fetch_refspec(1, &tags_str);
+ memcpy(refspec, refs, ref_count * sizeof(struct refspec));
+ memcpy(&refspec[ref_count], tags_refspec, sizeof(struct refspec));
+ ref_count++;
+
+ prune_refs(refspec, ref_count, ref_map);
+
+ ref_count--;
+ /* The rest of the strings belong to fetch_one */
+ free_refspec(1, tags_refspec);
+ free(refspec);
+ } else if (ref_count) {
+ prune_refs(refs, ref_count, ref_map);
+ } else {
+ prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map);
+ }
+ }
free_refs(ref_map);
/* if neither --no-tags nor --tags was specified, do automated tag
@@ -929,7 +942,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
atexit(unlock_pack);
refspec = parse_fetch_refspec(ref_nr, refs);
exit_code = do_fetch(transport, refspec, ref_nr);
- free(refspec);
+ free_refspec(ref_nr, refspec);
transport_disconnect(transport);
transport = NULL;
return exit_code;
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index 7581632..2c4d435 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -5,32 +5,45 @@
#include "revision.h"
#include "tag.h"
#include "string-list.h"
+#include "branch.h"
+#include "fmt-merge-msg.h"
+#include "gpg-interface.h"
static const char * const fmt_merge_msg_usage[] = {
"git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
NULL
};
-static int shortlog_len;
+static int use_branch_desc;
-static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+int fmt_merge_msg_config(const char *key, const char *value, void *cb)
{
if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
int is_bool;
- shortlog_len = git_config_bool_or_int(key, value, &is_bool);
- if (!is_bool && shortlog_len < 0)
+ merge_log_config = git_config_bool_or_int(key, value, &is_bool);
+ if (!is_bool && merge_log_config < 0)
return error("%s: negative length %s", key, value);
- if (is_bool && shortlog_len)
- shortlog_len = DEFAULT_MERGE_LOG_LEN;
+ if (is_bool && merge_log_config)
+ merge_log_config = DEFAULT_MERGE_LOG_LEN;
+ } else if (!strcmp(key, "merge.branchdesc")) {
+ use_branch_desc = git_config_bool(key, value);
+ } else {
+ return git_default_config(key, value, cb);
}
return 0;
}
+/* merge data per repository where the merged tips came from */
struct src_data {
struct string_list branch, tag, r_branch, generic;
int head_status;
};
+struct origin_data {
+ unsigned char sha1[20];
+ unsigned is_local_branch:1;
+};
+
static void init_src_data(struct src_data *data)
{
data->branch.strdup_strings = 1;
@@ -42,14 +55,56 @@ static void init_src_data(struct src_data *data)
static struct string_list srcs = STRING_LIST_INIT_DUP;
static struct string_list origins = STRING_LIST_INIT_DUP;
-static int handle_line(char *line)
+struct merge_parents {
+ int alloc, nr;
+ struct merge_parent {
+ unsigned char given[20];
+ unsigned char commit[20];
+ unsigned char used;
+ } *item;
+};
+
+/*
+ * I know, I know, this is inefficient, but you won't be pulling and merging
+ * hundreds of heads at a time anyway.
+ */
+static struct merge_parent *find_merge_parent(struct merge_parents *table,
+ unsigned char *given,
+ unsigned char *commit)
+{
+ int i;
+ for (i = 0; i < table->nr; i++) {
+ if (given && hashcmp(table->item[i].given, given))
+ continue;
+ if (commit && hashcmp(table->item[i].commit, commit))
+ continue;
+ return &table->item[i];
+ }
+ return NULL;
+}
+
+static void add_merge_parent(struct merge_parents *table,
+ unsigned char *given,
+ unsigned char *commit)
+{
+ if (table->nr && find_merge_parent(table, given, commit))
+ return;
+ ALLOC_GROW(table->item, table->nr + 1, table->alloc);
+ hashcpy(table->item[table->nr].given, given);
+ hashcpy(table->item[table->nr].commit, commit);
+ table->item[table->nr].used = 0;
+ table->nr++;
+}
+
+static int handle_line(char *line, struct merge_parents *merge_parents)
{
int i, len = strlen(line);
- unsigned char *sha1;
+ struct origin_data *origin_data;
char *src, *origin;
struct src_data *src_data;
struct string_list_item *item;
int pulling_head = 0;
+ unsigned char sha1[20];
if (len < 43 || line[40] != '\t')
return 1;
@@ -60,17 +115,25 @@ static int handle_line(char *line)
if (line[41] != '\t')
return 2;
- line[40] = 0;
- sha1 = xmalloc(20);
- i = get_sha1(line, sha1);
- line[40] = '\t';
+ i = get_sha1_hex(line, sha1);
if (i)
return 3;
+ if (!find_merge_parent(merge_parents, sha1, NULL))
+ return 0; /* subsumed by other parents */
+
+ origin_data = xcalloc(1, sizeof(struct origin_data));
+ hashcpy(origin_data->sha1, sha1);
+
if (line[len - 1] == '\n')
line[len - 1] = 0;
line += 42;
+ /*
+ * At this point, line points at the beginning of comment e.g.
+ * "branch 'frotz' of git://that/repository.git".
+ * Find the repository name and point it with src.
+ */
src = strstr(line, " of ");
if (src) {
*src = 0;
@@ -93,6 +156,7 @@ static int handle_line(char *line)
origin = src;
src_data->head_status |= 1;
} else if (!prefixcmp(line, "branch ")) {
+ origin_data->is_local_branch = 1;
origin = line + 7;
string_list_append(&src_data->branch, origin);
src_data->head_status |= 2;
@@ -119,7 +183,9 @@ static int handle_line(char *line)
sprintf(new_origin, "%s of %s", origin, src);
origin = new_origin;
}
- string_list_append(&origins, origin)->util = sha1;
+ if (strcmp(".", src))
+ origin_data->is_local_branch = 0;
+ string_list_append(&origins, origin)->util = origin_data;
return 0;
}
@@ -140,23 +206,141 @@ static void print_joined(const char *singular, const char *plural,
}
}
-static void shortlog(const char *name, unsigned char *sha1,
- struct commit *head, struct rev_info *rev, int limit,
- struct strbuf *out)
+static void add_branch_desc(struct strbuf *out, const char *name)
+{
+ struct strbuf desc = STRBUF_INIT;
+
+ if (!read_branch_desc(&desc, name)) {
+ const char *bp = desc.buf;
+ while (*bp) {
+ const char *ep = strchrnul(bp, '\n');
+ if (*ep)
+ ep++;
+ strbuf_addf(out, " : %.*s", (int)(ep - bp), bp);
+ bp = ep;
+ }
+ if (out->buf[out->len - 1] != '\n')
+ strbuf_addch(out, '\n');
+ }
+ strbuf_release(&desc);
+}
+
+#define util_as_integral(elem) ((intptr_t)((elem)->util))
+
+static void record_person(int which, struct string_list *people,
+ struct commit *commit)
+{
+ char *name_buf, *name, *name_end;
+ struct string_list_item *elem;
+ const char *field = (which == 'a') ? "\nauthor " : "\ncommitter ";
+
+ name = strstr(commit->buffer, field);
+ if (!name)
+ return;
+ name += strlen(field);
+ name_end = strchrnul(name, '<');
+ if (*name_end)
+ name_end--;
+ while (isspace(*name_end) && name <= name_end)
+ name_end--;
+ if (name_end < name)
+ return;
+ name_buf = xmemdupz(name, name_end - name + 1);
+
+ elem = string_list_lookup(people, name_buf);
+ if (!elem) {
+ elem = string_list_insert(people, name_buf);
+ elem->util = (void *)0;
+ }
+ elem->util = (void*)(util_as_integral(elem) + 1);
+ free(name_buf);
+}
+
+static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
+{
+ const struct string_list_item *a = a_, *b = b_;
+ return util_as_integral(b) - util_as_integral(a);
+}
+
+static void add_people_count(struct strbuf *out, struct string_list *people)
+{
+ if (people->nr == 1)
+ strbuf_addf(out, "%s", people->items[0].string);
+ else if (people->nr == 2)
+ strbuf_addf(out, "%s (%d) and %s (%d)",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]),
+ people->items[1].string,
+ (int)util_as_integral(&people->items[1]));
+ else if (people->nr)
+ strbuf_addf(out, "%s (%d) and others",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]));
+}
+
+static void credit_people(struct strbuf *out,
+ struct string_list *them,
+ int kind)
+{
+ const char *label;
+ const char *me;
+
+ if (kind == 'a') {
+ label = "\n# By ";
+ me = git_author_info(IDENT_NO_DATE);
+ } else {
+ label = "\n# Via ";
+ me = git_committer_info(IDENT_NO_DATE);
+ }
+
+ if (!them->nr ||
+ (them->nr == 1 &&
+ me &&
+ (me = skip_prefix(me, them->items->string)) != NULL &&
+ skip_prefix(me, " <")))
+ return;
+ strbuf_addstr(out, label);
+ add_people_count(out, them);
+}
+
+static void add_people_info(struct strbuf *out,
+ struct string_list *authors,
+ struct string_list *committers)
+{
+ if (authors->nr)
+ qsort(authors->items,
+ authors->nr, sizeof(authors->items[0]),
+ cmp_string_list_util_as_integral);
+ if (committers->nr)
+ qsort(committers->items,
+ committers->nr, sizeof(committers->items[0]),
+ cmp_string_list_util_as_integral);
+
+ credit_people(out, authors, 'a');
+ credit_people(out, committers, 'c');
+}
+
+static void shortlog(const char *name,
+ struct origin_data *origin_data,
+ struct commit *head,
+ struct rev_info *rev, int limit,
+ struct strbuf *out)
{
int i, count = 0;
struct commit *commit;
struct object *branch;
struct string_list subjects = STRING_LIST_INIT_DUP;
+ struct string_list authors = STRING_LIST_INIT_DUP;
+ struct string_list committers = STRING_LIST_INIT_DUP;
int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
struct strbuf sb = STRBUF_INIT;
+ const unsigned char *sha1 = origin_data->sha1;
branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
if (!branch || branch->type != OBJ_COMMIT)
return;
setup_revisions(0, NULL, rev, NULL);
- rev->ignore_merges = 1;
add_pending_object(rev, branch, name);
add_pending_object(rev, &head->object, "^HEAD");
head->object.flags |= UNINTERESTING;
@@ -165,10 +349,15 @@ static void shortlog(const char *name, unsigned char *sha1,
while ((commit = get_revision(rev)) != NULL) {
struct pretty_print_context ctx = {0};
- /* ignore merges */
- if (commit->parents && commit->parents->next)
+ if (commit->parents && commit->parents->next) {
+ /* do not list a merge but count committer */
+ record_person('c', &committers, commit);
continue;
-
+ }
+ if (!count)
+ /* the 'tip' committer */
+ record_person('c', &committers, commit);
+ record_person('a', &authors, commit);
count++;
if (subjects.nr > limit)
continue;
@@ -183,11 +372,15 @@ static void shortlog(const char *name, unsigned char *sha1,
string_list_append(&subjects, strbuf_detach(&sb, NULL));
}
+ add_people_info(out, &authors, &committers);
if (count > limit)
strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else
strbuf_addf(out, "\n* %s:\n", name);
+ if (origin_data->is_local_branch && use_branch_desc)
+ add_branch_desc(out, name);
+
for (i = 0; i < subjects.nr; i++)
if (i >= limit)
strbuf_addf(out, " ...\n");
@@ -200,10 +393,12 @@ static void shortlog(const char *name, unsigned char *sha1,
rev->commits = NULL;
rev->pending.nr = 0;
+ string_list_clear(&authors, 0);
+ string_list_clear(&committers, 0);
string_list_clear(&subjects, 0);
}
-static void do_fmt_merge_msg_title(struct strbuf *out,
+static void fmt_merge_msg_title(struct strbuf *out,
const char *current_branch) {
int i = 0;
char *sep = "";
@@ -256,19 +451,155 @@ static void do_fmt_merge_msg_title(struct strbuf *out,
strbuf_addf(out, " into %s\n", current_branch);
}
-static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
- struct strbuf *out, int shortlog_len) {
+static void fmt_tag_signature(struct strbuf *tagbuf,
+ struct strbuf *sig,
+ const char *buf,
+ unsigned long len)
+{
+ const char *tag_body = strstr(buf, "\n\n");
+ if (tag_body) {
+ tag_body += 2;
+ strbuf_add(tagbuf, tag_body, buf + len - tag_body);
+ }
+ strbuf_complete_line(tagbuf);
+ if (sig->len) {
+ strbuf_addch(tagbuf, '\n');
+ strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
+ }
+}
+
+static void fmt_merge_msg_sigs(struct strbuf *out)
+{
+ int i, tag_number = 0, first_tag = 0;
+ struct strbuf tagbuf = STRBUF_INIT;
+
+ for (i = 0; i < origins.nr; i++) {
+ unsigned char *sha1 = origins.items[i].util;
+ enum object_type type;
+ unsigned long size, len;
+ char *buf = read_sha1_file(sha1, &type, &size);
+ struct strbuf sig = STRBUF_INIT;
+
+ if (!buf || type != OBJ_TAG)
+ goto next;
+ len = parse_signature(buf, size);
+
+ if (size == len)
+ ; /* merely annotated */
+ else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) {
+ if (!sig.len)
+ strbuf_addstr(&sig, "gpg verification failed.\n");
+ }
+
+ if (!tag_number++) {
+ fmt_tag_signature(&tagbuf, &sig, buf, len);
+ first_tag = i;
+ } else {
+ if (tag_number == 2) {
+ struct strbuf tagline = STRBUF_INIT;
+ strbuf_addf(&tagline, "\n# %s\n",
+ origins.items[first_tag].string);
+ strbuf_insert(&tagbuf, 0, tagline.buf,
+ tagline.len);
+ strbuf_release(&tagline);
+ }
+ strbuf_addf(&tagbuf, "\n# %s\n",
+ origins.items[i].string);
+ fmt_tag_signature(&tagbuf, &sig, buf, len);
+ }
+ strbuf_release(&sig);
+ next:
+ free(buf);
+ }
+ if (tagbuf.len) {
+ strbuf_addch(out, '\n');
+ strbuf_addbuf(out, &tagbuf);
+ }
+ strbuf_release(&tagbuf);
+}
+
+static void find_merge_parents(struct merge_parents *result,
+ struct strbuf *in, unsigned char *head)
+{
+ struct commit_list *parents, *next;
+ struct commit *head_commit;
+ int pos = 0, i, j;
+
+ parents = NULL;
+ while (pos < in->len) {
+ int len;
+ char *p = in->buf + pos;
+ char *newline = strchr(p, '\n');
+ unsigned char sha1[20];
+ struct commit *parent;
+ struct object *obj;
+
+ len = newline ? newline - p : strlen(p);
+ pos += len + !!newline;
+
+ if (len < 43 ||
+ get_sha1_hex(p, sha1) ||
+ p[40] != '\t' ||
+ p[41] != '\t')
+ continue; /* skip not-for-merge */
+ /*
+ * Do not use get_merge_parent() here; we do not have
+ * "name" here and we do not want to contaminate its
+ * util field yet.
+ */
+ obj = parse_object(sha1);
+ parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
+ if (!parent)
+ continue;
+ commit_list_insert(parent, &parents);
+ add_merge_parent(result, obj->sha1, parent->object.sha1);
+ }
+ head_commit = lookup_commit(head);
+ if (head_commit)
+ commit_list_insert(head_commit, &parents);
+ parents = reduce_heads(parents);
+
+ while (parents) {
+ for (i = 0; i < result->nr; i++)
+ if (!hashcmp(result->item[i].commit,
+ parents->item->object.sha1))
+ result->item[i].used = 1;
+ next = parents->next;
+ free(parents);
+ parents = next;
+ }
+
+ for (i = j = 0; i < result->nr; i++) {
+ if (result->item[i].used) {
+ if (i != j)
+ result->item[j] = result->item[i];
+ j++;
+ }
+ }
+ result->nr = j;
+}
+
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+ struct fmt_merge_msg_opts *opts)
+{
int i = 0, pos = 0;
unsigned char head_sha1[20];
const char *current_branch;
+ void *current_branch_to_free;
+ struct merge_parents merge_parents;
+
+ memset(&merge_parents, 0, sizeof(merge_parents));
/* get current branch */
- current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
+ current_branch = current_branch_to_free =
+ resolve_refdup("HEAD", head_sha1, 1, NULL);
if (!current_branch)
die("No current branch");
if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11;
+ find_merge_parents(&merge_parents, in, head_sha1);
+
/* get a line */
while (pos < in->len) {
int len;
@@ -279,45 +610,45 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
pos += len + !!newline;
i++;
p[len] = 0;
- if (handle_line(p))
+ if (handle_line(p, &merge_parents))
die ("Error in line %d: %.*s", i, len, p);
}
- if (!srcs.nr)
- return 0;
+ if (opts->add_title && srcs.nr)
+ fmt_merge_msg_title(out, current_branch);
- if (merge_title)
- do_fmt_merge_msg_title(out, current_branch);
+ if (origins.nr)
+ fmt_merge_msg_sigs(out);
- if (shortlog_len) {
+ if (opts->shortlog_len) {
struct commit *head;
struct rev_info rev;
- head = lookup_commit(head_sha1);
+ head = lookup_commit_or_die(head_sha1, "HEAD");
init_revisions(&rev, NULL);
rev.commit_format = CMIT_FMT_ONELINE;
rev.ignore_merges = 1;
rev.limited = 1;
- if (suffixcmp(out->buf, "\n"))
- strbuf_addch(out, '\n');
+ strbuf_complete_line(out);
for (i = 0; i < origins.nr; i++)
- shortlog(origins.items[i].string, origins.items[i].util,
- head, &rev, shortlog_len, out);
+ shortlog(origins.items[i].string,
+ origins.items[i].util,
+ head, &rev, opts->shortlog_len, out);
}
- return 0;
-}
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
- int merge_title, int shortlog_len) {
- return do_fmt_merge_msg(merge_title, in, out, shortlog_len);
+ strbuf_complete_line(out);
+ free(current_branch_to_free);
+ free(merge_parents.item);
+ return 0;
}
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
const char *inpath = NULL;
const char *message = NULL;
+ int shortlog_len = -1;
struct option options[] = {
{ OPTION_INTEGER, 0, "log", &shortlog_len, "n",
"populate log with at most <n> entries from shortlog",
@@ -335,20 +666,15 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
FILE *in = stdin;
struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
int ret;
+ struct fmt_merge_msg_opts opts;
git_config(fmt_merge_msg_config, NULL);
argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
0);
if (argc > 0)
usage_with_options(fmt_merge_msg_usage, options);
- if (message && !shortlog_len) {
- char nl = '\n';
- write_in_full(STDOUT_FILENO, message, strlen(message));
- write_in_full(STDOUT_FILENO, &nl, 1);
- return 0;
- }
if (shortlog_len < 0)
- die("Negative --log=%d", shortlog_len);
+ shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
if (inpath && strcmp(inpath, "-")) {
in = fopen(inpath, "r");
@@ -361,10 +687,12 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
if (message)
strbuf_addstr(&output, message);
- ret = fmt_merge_msg(&input, &output,
- message ? 0 : 1,
- shortlog_len);
+ memset(&opts, 0, sizeof(opts));
+ opts.add_title = !message;
+ opts.shortlog_len = shortlog_len;
+
+ ret = fmt_merge_msg(&input, &output, &opts);
if (ret)
return ret;
write_in_full(STDOUT_FILENO, output.buf, output.len);
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 89e75c6..b01d76a 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -69,6 +69,9 @@ static struct {
{ "subject" },
{ "body" },
{ "contents" },
+ { "contents:subject" },
+ { "contents:body" },
+ { "contents:signature" },
{ "upstream" },
{ "symref" },
{ "flag" },
@@ -361,6 +364,18 @@ static const char *copy_email(const char *buf)
return xmemdupz(email, eoemail + 1 - email);
}
+static char *copy_subject(const char *buf, unsigned long len)
+{
+ char *r = xmemdupz(buf, len);
+ int i;
+
+ for (i = 0; i < len; i++)
+ if (r[i] == '\n')
+ r[i] = ' ';
+
+ return r;
+}
+
static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
{
const char *eoemail = strstr(buf, "> ");
@@ -458,38 +473,56 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
}
}
-static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+static void find_subpos(const char *buf, unsigned long sz,
+ const char **sub, unsigned long *sublen,
+ const char **body, unsigned long *bodylen,
+ unsigned long *nonsiglen,
+ const char **sig, unsigned long *siglen)
{
- while (*buf) {
- const char *eol = strchr(buf, '\n');
- if (!eol)
- return;
- if (eol[1] == '\n') {
- buf = eol + 1;
- break; /* found end of header */
- }
- buf = eol + 1;
+ const char *eol;
+ /* skip past header until we hit empty line */
+ while (*buf && *buf != '\n') {
+ eol = strchrnul(buf, '\n');
+ if (*eol)
+ eol++;
+ buf = eol;
}
+ /* skip any empty lines */
while (*buf == '\n')
buf++;
- if (!*buf)
- return;
- *sub = buf; /* first non-empty line */
- buf = strchr(buf, '\n');
- if (!buf) {
- *body = "";
- return; /* no body */
+
+ /* parse signature first; we might not even have a subject line */
+ *sig = buf + parse_signature(buf, strlen(buf));
+ *siglen = strlen(*sig);
+
+ /* subject is first non-empty line */
+ *sub = buf;
+ /* subject goes to first empty line */
+ while (buf < *sig && *buf && *buf != '\n') {
+ eol = strchrnul(buf, '\n');
+ if (*eol)
+ eol++;
+ buf = eol;
}
+ *sublen = buf - *sub;
+ /* drop trailing newline, if present */
+ if (*sublen && (*sub)[*sublen - 1] == '\n')
+ *sublen -= 1;
+
+ /* skip any empty lines */
while (*buf == '\n')
- buf++; /* skip blank between subject and body */
+ buf++;
*body = buf;
+ *bodylen = strlen(buf);
+ *nonsiglen = *sig - buf;
}
/* See grab_values */
static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
{
int i;
- const char *subpos = NULL, *bodypos = NULL;
+ const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
+ unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
for (i = 0; i < used_atom_cnt; i++) {
const char *name = used_atom[i];
@@ -500,17 +533,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
name++;
if (strcmp(name, "subject") &&
strcmp(name, "body") &&
- strcmp(name, "contents"))
+ strcmp(name, "contents") &&
+ strcmp(name, "contents:subject") &&
+ strcmp(name, "contents:body") &&
+ strcmp(name, "contents:signature"))
continue;
if (!subpos)
- find_subpos(buf, sz, &subpos, &bodypos);
- if (!subpos)
- return;
+ find_subpos(buf, sz,
+ &subpos, &sublen,
+ &bodypos, &bodylen, &nonsiglen,
+ &sigpos, &siglen);
if (!strcmp(name, "subject"))
- v->s = copy_line(subpos);
+ v->s = copy_subject(subpos, sublen);
+ else if (!strcmp(name, "contents:subject"))
+ v->s = copy_subject(subpos, sublen);
else if (!strcmp(name, "body"))
- v->s = xstrdup(bodypos);
+ v->s = xmemdupz(bodypos, bodylen);
+ else if (!strcmp(name, "contents:body"))
+ v->s = xmemdupz(bodypos, nonsiglen);
+ else if (!strcmp(name, "contents:signature"))
+ v->s = xmemdupz(sigpos, siglen);
else if (!strcmp(name, "contents"))
v->s = xstrdup(subpos);
}
@@ -585,11 +628,8 @@ static void populate_value(struct refinfo *ref)
if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
unsigned char unused1[20];
- const char *symref;
- symref = resolve_ref(ref->refname, unused1, 1, NULL);
- if (symref)
- ref->symref = xstrdup(symref);
- else
+ ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL);
+ if (!ref->symref)
ref->symref = "";
}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 5ae0366..a710227 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -11,6 +11,8 @@
#include "fsck.h"
#include "parse-options.h"
#include "dir.h"
+#include "progress.h"
+#include "streaming.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
@@ -27,8 +29,11 @@ static const char *head_points_at;
static int errors_found;
static int write_lost_and_found;
static int verbose;
+static int show_progress = -1;
+static int show_dangling = 1;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
+#define ERROR_PACK 04
#ifdef NO_D_INO_IN_DIRENT
#define SORT_DIRENT 0
@@ -137,7 +142,11 @@ static int traverse_one_object(struct object *obj)
static int traverse_reachable(void)
{
+ struct progress *progress = NULL;
+ unsigned int nr = 0;
int result = 0;
+ if (show_progress)
+ progress = start_progress_delay("Checking connectivity", 0, 0, 2);
while (pending.nr) {
struct object_array_entry *entry;
struct object *obj;
@@ -145,7 +154,9 @@ static int traverse_reachable(void)
entry = pending.objects + --pending.nr;
obj = entry->item;
result |= traverse_one_object(obj);
+ display_progress(progress, ++nr);
}
+ stop_progress(&progress);
return !!result;
}
@@ -212,8 +223,9 @@ static void check_unreachable_object(struct object *obj)
* start looking at, for example.
*/
if (!obj->used) {
- printf("dangling %s %s\n", typename(obj->type),
- sha1_to_hex(obj->sha1));
+ if (show_dangling)
+ printf("dangling %s %s\n", typename(obj->type),
+ sha1_to_hex(obj->sha1));
if (write_lost_and_found) {
char *filename = git_path("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
@@ -227,16 +239,8 @@ static void check_unreachable_object(struct object *obj)
if (!(f = fopen(filename, "w")))
die_errno("Could not open '%s'", filename);
if (obj->type == OBJ_BLOB) {
- enum object_type type;
- unsigned long size;
- char *buf = read_sha1_file(obj->sha1,
- &type, &size);
- if (buf) {
- if (fwrite(buf, size, 1, f) != 1)
- die_errno("Could not write '%s'",
- filename);
- free(buf);
- }
+ if (stream_blob_to_fd(fileno(f), obj->sha1, NULL, 1))
+ die_errno("Could not write '%s'", filename);
} else
fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
if (fclose(f))
@@ -284,14 +288,8 @@ static void check_connectivity(void)
}
}
-static int fsck_sha1(const unsigned char *sha1)
+static int fsck_obj(struct object *obj)
{
- struct object *obj = parse_object(sha1);
- if (!obj) {
- errors_found |= ERROR_OBJECT;
- return error("%s: object corrupt or missing",
- sha1_to_hex(sha1));
- }
if (obj->flags & SEEN)
return 0;
obj->flags |= SEEN;
@@ -334,6 +332,29 @@ static int fsck_sha1(const unsigned char *sha1)
return 0;
}
+static int fsck_sha1(const unsigned char *sha1)
+{
+ struct object *obj = parse_object(sha1);
+ if (!obj) {
+ errors_found |= ERROR_OBJECT;
+ return error("%s: object corrupt or missing",
+ sha1_to_hex(sha1));
+ }
+ return fsck_obj(obj);
+}
+
+static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
+ unsigned long size, void *buffer, int *eaten)
+{
+ struct object *obj;
+ obj = parse_object_buffer(sha1, type, size, buffer, eaten);
+ if (!obj) {
+ errors_found |= ERROR_OBJECT;
+ return error("%s: object corrupt or missing", sha1_to_hex(sha1));
+ }
+ return fsck_obj(obj);
+}
+
/*
* This is the sorting chunk size: make it reasonably
* big so that we can sort well..
@@ -515,15 +536,20 @@ static void get_default_heads(void)
static void fsck_object_dir(const char *path)
{
int i;
+ struct progress *progress = NULL;
if (verbose)
fprintf(stderr, "Checking object directory\n");
+ if (show_progress)
+ progress = start_progress("Checking object directories", 256);
for (i = 0; i < 256; i++) {
static char dir[4096];
sprintf(dir, "%s/%02x", path, i);
fsck_dir(i, dir);
+ display_progress(progress, i+1);
}
+ stop_progress(&progress);
fsck_sha1_list();
}
@@ -535,7 +561,7 @@ static int fsck_head_link(void)
if (verbose)
fprintf(stderr, "Checking HEAD link\n");
- head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
+ head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag);
if (!head_points_at)
return error("Invalid HEAD");
if (!strcmp(head_points_at, "HEAD"))
@@ -586,6 +612,7 @@ static char const * const fsck_usage[] = {
static struct option fsck_opts[] = {
OPT__VERBOSE(&verbose, "be verbose"),
OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
+ OPT_BOOL(0, "dangling", &show_dangling, "show dangling 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"),
@@ -594,6 +621,7 @@ static struct option fsck_opts[] = {
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_BOOL(0, "progress", &show_progress, "show progress"),
OPT_END(),
};
@@ -606,6 +634,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
read_replace_refs = 0;
argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
+
+ if (show_progress == -1)
+ show_progress = isatty(2);
+ if (verbose)
+ show_progress = 0;
+
if (write_lost_and_found) {
check_full = 1;
include_reflogs = 0;
@@ -625,20 +659,28 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
if (check_full) {
struct packed_git *p;
+ uint32_t total = 0, count = 0;
+ struct progress *progress = NULL;
prepare_packed_git();
- for (p = packed_git; p; p = p->next)
- /* verify gives error messages itself */
- verify_pack(p);
+ if (show_progress) {
+ for (p = packed_git; p; p = p->next) {
+ if (open_pack_index(p))
+ continue;
+ total += p->num_objects;
+ }
+
+ progress = start_progress("Checking objects", total);
+ }
for (p = packed_git; p; p = p->next) {
- uint32_t j, num;
- if (open_pack_index(p))
- continue;
- num = p->num_objects;
- for (j = 0; j < num; j++)
- fsck_sha1(nth_packed_object_sha1(p, j));
+ /* verify gives error messages itself */
+ if (verify_pack(p, fsck_obj_buffer,
+ progress, count))
+ errors_found |= ERROR_PACK;
+ count += p->num_objects;
}
+ stop_progress(&progress);
}
heads = 0;
diff --git a/builtin/gc.c b/builtin/gc.c
index 0498094..9b4232c 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -14,6 +14,7 @@
#include "cache.h"
#include "parse-options.h"
#include "run-command.h"
+#include "argv-array.h"
#define FAILED_RUN "failed to run %s"
@@ -28,12 +29,11 @@ static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
static const char *prune_expire = "2.weeks.ago";
-#define MAX_ADD 10
-static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
-static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
-static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
-static const char *argv_rerere[] = {"rerere", "gc", NULL};
+static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
+static struct argv_array reflog = ARGV_ARRAY_INIT;
+static struct argv_array repack = ARGV_ARRAY_INIT;
+static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array rerere = ARGV_ARRAY_INIT;
static int gc_config(const char *var, const char *value, void *cb)
{
@@ -67,19 +67,6 @@ static int gc_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
-static void append_option(const char **cmd, const char *opt, int max_length)
-{
- int i;
-
- for (i = 0; cmd[i]; i++)
- ;
-
- if (i + 2 >= max_length)
- die(_("Too many options specified"));
- cmd[i++] = opt;
- cmd[i] = NULL;
-}
-
static int too_many_loose_objects(void)
{
/*
@@ -144,6 +131,17 @@ static int too_many_packs(void)
return gc_auto_pack_limit <= cnt;
}
+static void add_repack_all_option(void)
+{
+ if (prune_expire && !strcmp(prune_expire, "now"))
+ argv_array_push(&repack, "-a");
+ else {
+ argv_array_push(&repack, "-A");
+ if (prune_expire)
+ argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
+ }
+}
+
static int need_to_gc(void)
{
/*
@@ -160,10 +158,7 @@ static int need_to_gc(void)
* there is no need.
*/
if (too_many_packs())
- append_option(argv_repack,
- prune_expire && !strcmp(prune_expire, "now") ?
- "-a" : "-A",
- MAX_ADD);
+ add_repack_all_option();
else if (!too_many_loose_objects())
return 0;
@@ -177,7 +172,6 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
int aggressive = 0;
int auto_gc = 0;
int quiet = 0;
- char buf[80];
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, "suppress progress reporting"),
@@ -192,6 +186,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_gc_usage, builtin_gc_options);
+ argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
+ argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
+ argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
+ argv_array_pushl(&prune, "prune", "--expire", NULL );
+ argv_array_pushl(&rerere, "rerere", "gc", NULL);
+
git_config(gc_config, NULL);
if (pack_refs < 0)
@@ -203,15 +203,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_gc_usage, builtin_gc_options);
if (aggressive) {
- append_option(argv_repack, "-f", MAX_ADD);
- append_option(argv_repack, "--depth=250", MAX_ADD);
- if (aggressive_window > 0) {
- sprintf(buf, "--window=%d", aggressive_window);
- append_option(argv_repack, buf, MAX_ADD);
- }
+ argv_array_push(&repack, "-f");
+ argv_array_push(&repack, "--depth=250");
+ if (aggressive_window > 0)
+ argv_array_pushf(&repack, "--window=%d", aggressive_window);
}
if (quiet)
- append_option(argv_repack, "-q", MAX_ADD);
+ argv_array_push(&repack, "-q");
if (auto_gc) {
/*
@@ -227,28 +225,27 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
"run \"git gc\" manually. See "
"\"git help gc\" for more information.\n"));
} else
- append_option(argv_repack,
- prune_expire && !strcmp(prune_expire, "now")
- ? "-a" : "-A",
- MAX_ADD);
+ add_repack_all_option();
- if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_pack_refs[0]);
+ if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, pack_refs_cmd.argv[0]);
- if (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_reflog[0]);
+ if (run_command_v_opt(reflog.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, reflog.argv[0]);
- if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_repack[0]);
+ if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, repack.argv[0]);
if (prune_expire) {
- argv_prune[2] = prune_expire;
- if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_prune[0]);
+ argv_array_push(&prune, prune_expire);
+ if (quiet)
+ argv_array_push(&prune, "--no-progress");
+ if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, prune.argv[0]);
}
- if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_rerere[0]);
+ if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, rerere.argv[0]);
if (auto_gc && too_many_loose_objects())
warning(_("There are too many unreachable loose objects; "
diff --git a/builtin/grep.c b/builtin/grep.c
index 871afaa..29adb0a 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -17,7 +17,6 @@
#include "grep.h"
#include "quote.h"
#include "dir.h"
-#include "thread-utils.h"
static char const * const grep_usage[] = {
"git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
@@ -30,25 +29,12 @@ static int use_threads = 1;
#define THREADS 8
static pthread_t threads[THREADS];
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
- const char *name);
-static void *load_file(const char *filename, size_t *sz);
-
-enum work_type {WORK_SHA1, WORK_FILE};
-
/* We use one producer thread and THREADS consumer
* threads. The producer adds struct work_items to 'todo' and the
* consumers pick work items from the same array.
*/
struct work_item {
- enum work_type type;
- char *name;
-
- /* if type == WORK_SHA1, then 'identifier' is a SHA1,
- * otherwise type == WORK_FILE, and 'identifier' is a NUL
- * terminated filename.
- */
- void *identifier;
+ struct grep_source source;
char done;
struct strbuf out;
};
@@ -74,13 +60,17 @@ static int all_work_added;
/* This lock protects all the variables above. */
static pthread_mutex_t grep_mutex;
-/* Used to serialize calls to read_sha1_file. */
-static pthread_mutex_t read_sha1_mutex;
+static inline void grep_lock(void)
+{
+ if (use_threads)
+ pthread_mutex_lock(&grep_mutex);
+}
-#define grep_lock() pthread_mutex_lock(&grep_mutex)
-#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
-#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
-#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
+static inline void grep_unlock(void)
+{
+ if (use_threads)
+ pthread_mutex_unlock(&grep_mutex);
+}
/* Signalled when a new work_item is added to todo. */
static pthread_cond_t cond_add;
@@ -93,10 +83,10 @@ static pthread_cond_t cond_write;
/* Signalled when we are finished with everything. */
static pthread_cond_t cond_result;
-static int print_hunk_marks_between_files;
-static int printed_something;
+static int skip_first_line;
-static void add_work(enum work_type type, char *name, void *id)
+static void add_work(struct grep_opt *opt, enum grep_source_type type,
+ const char *name, const void *id)
{
grep_lock();
@@ -104,9 +94,9 @@ static void add_work(enum work_type type, char *name, void *id)
pthread_cond_wait(&cond_write, &grep_mutex);
}
- todo[todo_end].type = type;
- todo[todo_end].name = name;
- todo[todo_end].identifier = id;
+ grep_source_init(&todo[todo_end].source, type, name, id);
+ if (opt->binary != GREP_BINARY_TEXT)
+ grep_source_load_driver(&todo[todo_end].source);
todo[todo_end].done = 0;
strbuf_reset(&todo[todo_end].out);
todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
@@ -134,21 +124,6 @@ static struct work_item *get_work(void)
return ret;
}
-static void grep_sha1_async(struct grep_opt *opt, char *name,
- const unsigned char *sha1)
-{
- unsigned char *s;
- s = xmalloc(20);
- memcpy(s, sha1, 20);
- add_work(WORK_SHA1, name, s);
-}
-
-static void grep_file_async(struct grep_opt *opt, char *name,
- const char *filename)
-{
- add_work(WORK_FILE, name, xstrdup(filename));
-}
-
static void work_done(struct work_item *w)
{
int old_done;
@@ -160,13 +135,22 @@ static void work_done(struct work_item *w)
todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
w = &todo[todo_done];
if (w->out.len) {
- if (print_hunk_marks_between_files && printed_something)
- write_or_die(1, "--\n", 3);
- write_or_die(1, w->out.buf, w->out.len);
- printed_something = 1;
+ const char *p = w->out.buf;
+ size_t len = w->out.len;
+
+ /* Skip the leading hunk mark of the first file. */
+ if (skip_first_line) {
+ while (len) {
+ len--;
+ if (*p++ == '\n')
+ break;
+ }
+ skip_first_line = 0;
+ }
+
+ write_or_die(1, p, len);
}
- free(w->name);
- free(w->identifier);
+ grep_source_clear(&w->source);
}
if (old_done != todo_done)
@@ -189,25 +173,8 @@ static void *run(void *arg)
break;
opt->output_priv = w;
- if (w->type == WORK_SHA1) {
- unsigned long sz;
- void* data = load_sha1(w->identifier, &sz, w->name);
-
- if (data) {
- hit |= grep_buffer(opt, w->name, data, sz);
- free(data);
- }
- } else if (w->type == WORK_FILE) {
- size_t sz;
- void* data = load_file(w->identifier, &sz);
- if (data) {
- hit |= grep_buffer(opt, w->name, data, sz);
- free(data);
- }
- } else {
- assert(0);
- }
-
+ hit |= grep_source(opt, &w->source);
+ grep_source_clear_data(&w->source);
work_done(w);
}
free_grep_patterns(arg);
@@ -227,10 +194,12 @@ static void start_threads(struct grep_opt *opt)
int i;
pthread_mutex_init(&grep_mutex, NULL);
- pthread_mutex_init(&read_sha1_mutex, NULL);
+ pthread_mutex_init(&grep_read_mutex, NULL);
+ pthread_mutex_init(&grep_attr_mutex, NULL);
pthread_cond_init(&cond_add, NULL);
pthread_cond_init(&cond_write, NULL);
pthread_cond_init(&cond_result, NULL);
+ grep_use_locks = 1;
for (i = 0; i < ARRAY_SIZE(todo); i++) {
strbuf_init(&todo[i].out, 0);
@@ -274,16 +243,16 @@ static int wait_all(void)
}
pthread_mutex_destroy(&grep_mutex);
- pthread_mutex_destroy(&read_sha1_mutex);
+ pthread_mutex_destroy(&grep_read_mutex);
+ pthread_mutex_destroy(&grep_attr_mutex);
pthread_cond_destroy(&cond_add);
pthread_cond_destroy(&cond_write);
pthread_cond_destroy(&cond_result);
+ grep_use_locks = 0;
return hit;
}
#else /* !NO_PTHREADS */
-#define read_sha1_lock()
-#define read_sha1_unlock()
static int wait_all(void)
{
@@ -296,11 +265,8 @@ static int grep_config(const char *var, const char *value, void *cb)
struct grep_opt *opt = cb;
char *color = NULL;
- switch (userdiff_config(var, value)) {
- case 0: break;
- case -1: return -1;
- default: return 0;
- }
+ if (userdiff_config(var, value) < 0)
+ return -1;
if (!strcmp(var, "grep.extendedregexp")) {
if (git_config_bool(var, value))
@@ -316,7 +282,7 @@ static int grep_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "color.grep"))
- opt->color = git_config_colorbool(var, value, -1);
+ opt->color = git_config_colorbool(var, value);
else if (!strcmp(var, "color.grep.context"))
color = opt->color_context;
else if (!strcmp(var, "color.grep.filename"))
@@ -345,25 +311,9 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type
{
void *data;
- if (use_threads) {
- read_sha1_lock();
- data = read_sha1_file(sha1, type, size);
- read_sha1_unlock();
- } else {
- data = read_sha1_file(sha1, type, size);
- }
- return data;
-}
-
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
- const char *name)
-{
- enum object_type type;
- void *data = lock_and_read_sha1_file(sha1, &type, size);
-
- if (!data)
- error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1));
-
+ grep_read_lock();
+ data = read_sha1_file(sha1, type, size);
+ grep_read_unlock();
return data;
}
@@ -371,7 +321,6 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
const char *filename, int tree_name_len)
{
struct strbuf pathbuf = STRBUF_INIT;
- char *name;
if (opt->relative && opt->prefix_length) {
quote_path_relative(filename + tree_name_len, -1, &pathbuf,
@@ -381,87 +330,51 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
strbuf_addstr(&pathbuf, filename);
}
- name = strbuf_detach(&pathbuf, NULL);
-
#ifndef NO_PTHREADS
if (use_threads) {
- grep_sha1_async(opt, name, sha1);
+ add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, sha1);
+ strbuf_release(&pathbuf);
return 0;
} else
#endif
{
+ struct grep_source gs;
int hit;
- unsigned long sz;
- void *data = load_sha1(sha1, &sz, name);
- if (!data)
- hit = 0;
- else
- hit = grep_buffer(opt, name, data, sz);
- free(data);
- free(name);
- return hit;
- }
-}
+ grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, sha1);
+ strbuf_release(&pathbuf);
+ hit = grep_source(opt, &gs);
-static void *load_file(const char *filename, size_t *sz)
-{
- struct stat st;
- char *data;
- int i;
-
- if (lstat(filename, &st) < 0) {
- err_ret:
- if (errno != ENOENT)
- error(_("'%s': %s"), filename, strerror(errno));
- return NULL;
- }
- if (!S_ISREG(st.st_mode))
- return NULL;
- *sz = xsize_t(st.st_size);
- i = open(filename, O_RDONLY);
- if (i < 0)
- goto err_ret;
- data = xmalloc(*sz + 1);
- if (st.st_size != read_in_full(i, data, *sz)) {
- error(_("'%s': short read %s"), filename, strerror(errno));
- close(i);
- free(data);
- return NULL;
+ grep_source_clear(&gs);
+ return hit;
}
- close(i);
- data[*sz] = 0;
- return data;
}
static int grep_file(struct grep_opt *opt, const char *filename)
{
struct strbuf buf = STRBUF_INIT;
- char *name;
if (opt->relative && opt->prefix_length)
quote_path_relative(filename, -1, &buf, opt->prefix);
else
strbuf_addstr(&buf, filename);
- name = strbuf_detach(&buf, NULL);
#ifndef NO_PTHREADS
if (use_threads) {
- grep_file_async(opt, name, filename);
+ add_work(opt, GREP_SOURCE_FILE, buf.buf, filename);
+ strbuf_release(&buf);
return 0;
} else
#endif
{
+ struct grep_source gs;
int hit;
- size_t sz;
- void *data = load_file(filename, &sz);
- if (!data)
- hit = 0;
- else
- hit = grep_buffer(opt, name, data, sz);
- free(data);
- free(name);
+ grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename);
+ strbuf_release(&buf);
+ hit = grep_source(opt, &gs);
+
+ grep_source_clear(&gs);
return hit;
}
}
@@ -533,18 +446,19 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
struct tree_desc *tree, struct strbuf *base, int tn_len)
{
- int hit = 0, match = 0;
+ int hit = 0;
+ enum interesting match = entry_not_interesting;
struct name_entry entry;
int old_baselen = base->len;
while (tree_entry(tree, &entry)) {
- int te_len = tree_entry_len(entry.path, entry.sha1);
+ int te_len = tree_entry_len(&entry);
- if (match != 2) {
+ if (match != all_entries_interesting) {
match = tree_entry_interesting(&entry, base, tn_len, pathspec);
- if (match < 0)
+ if (match == all_entries_not_interesting)
break;
- if (match == 0)
+ if (match == entry_not_interesting)
continue;
}
@@ -589,8 +503,11 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
struct strbuf base;
int hit, len;
+ grep_read_lock();
data = read_object_with_reference(obj->sha1, tree_type,
&size, NULL);
+ grep_read_unlock();
+
if (!data)
die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
@@ -628,13 +545,15 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
return hit;
}
-static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
+static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
+ int exc_std)
{
struct dir_struct dir;
int i, hit = 0;
memset(&dir, 0, sizeof(dir));
- setup_standard_excludes(&dir);
+ if (exc_std)
+ setup_standard_excludes(&dir);
fill_directory(&dir, pathspec->raw);
for (i = 0; i < dir.nr; i++) {
@@ -681,15 +600,12 @@ static int file_callback(const struct option *opt, const char *arg, int unset)
if (!patterns)
die_errno(_("cannot open '%s'"), arg);
while (strbuf_getline(&sb, patterns, '\n') == 0) {
- char *s;
- size_t len;
-
/* ignore empty line like grep does */
if (sb.len == 0)
continue;
- s = strbuf_detach(&sb, &len);
- append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN);
+ append_grep_pat(grep_opt, sb.buf, sb.len, arg, ++lno,
+ GREP_PATTERN);
}
if (!from_stdin)
fclose(patterns);
@@ -741,7 +657,7 @@ static int help_callback(const struct option *opt, const char *arg, int unset)
int cmd_grep(int argc, const char **argv, const char *prefix)
{
int hit = 0;
- int cached = 0;
+ int cached = 0, untracked = 0, opt_exclude = -1;
int seen_dashdash = 0;
int external_grep_allowed__ignored;
const char *show_in_pager = NULL, *default_pager = "dummy";
@@ -765,8 +681,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT_BOOLEAN(0, "cached", &cached,
"search in index instead of in the work tree"),
- OPT_BOOLEAN(0, "index", &use_index,
- "--no-index finds in contents not managed by git"),
+ OPT_NEGBIT(0, "no-index", &use_index,
+ "finds in contents not managed by git", 1),
+ OPT_BOOLEAN(0, "untracked", &untracked,
+ "search in both tracked and untracked files"),
+ OPT_SET_INT(0, "exclude-standard", &opt_exclude,
+ "search also in ignored files", 1),
OPT_GROUP(""),
OPT_BOOLEAN('v', "invert-match", &opt.invert,
"show non-matching lines"),
@@ -813,18 +733,24 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN('c', "count", &opt.count,
"show the number of matches instead of matching lines"),
OPT__COLOR(&opt.color, "highlight matches"),
+ OPT_BOOLEAN(0, "break", &opt.file_break,
+ "print empty line between matches from different files"),
+ OPT_BOOLEAN(0, "heading", &opt.heading,
+ "show filename only once above matches from same file"),
OPT_GROUP(""),
- OPT_CALLBACK('C', NULL, &opt, "n",
+ OPT_CALLBACK('C', "context", &opt, "n",
"show <n> context lines before and after matches",
context_callback),
- OPT_INTEGER('B', NULL, &opt.pre_context,
+ OPT_INTEGER('B', "before-context", &opt.pre_context,
"show <n> context lines before matches"),
- OPT_INTEGER('A', NULL, &opt.post_context,
+ OPT_INTEGER('A', "after-context", &opt.post_context,
"show <n> context lines after matches"),
OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
context_callback),
OPT_BOOLEAN('p', "show-function", &opt.funcname,
"show a line with the function name before matches"),
+ OPT_BOOLEAN('W', "function-context", &opt.funcbody,
+ "show the surrounding function"),
OPT_GROUP(""),
OPT_CALLBACK('f', NULL, &opt, "file",
"read patterns from file", file_callback),
@@ -883,8 +809,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
strcpy(opt.color_sep, GIT_COLOR_CYAN);
opt.color = -1;
git_config(grep_config, &opt);
- if (opt.color == -1)
- opt.color = git_use_color_default;
/*
* If there is no -- then the paths must exist in the working
@@ -962,19 +886,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!opt.fixed && opt.ignore_case)
opt.regflags |= REG_ICASE;
-#ifndef NO_PTHREADS
- if (online_cpus() == 1 || !grep_threads_ok(&opt))
- use_threads = 0;
-
- if (use_threads) {
- if (opt.pre_context || opt.post_context)
- print_hunk_marks_between_files = 1;
- start_threads(&opt);
- }
-#else
- use_threads = 0;
-#endif
-
compile_grep_patterns(&opt);
/* Check revs and then paths */
@@ -996,11 +907,28 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
break;
}
+#ifndef NO_PTHREADS
+ if (list.nr || cached || online_cpus() == 1)
+ use_threads = 0;
+#else
+ use_threads = 0;
+#endif
+
+#ifndef NO_PTHREADS
+ if (use_threads) {
+ if (!(opt.name_only || opt.unmatch_name_only || opt.count)
+ && (opt.pre_context || opt.post_context ||
+ opt.file_break || opt.funcbody))
+ skip_first_line = 1;
+ start_threads(&opt);
+ }
+#endif
+
/* The rest are paths */
if (!seen_dashdash) {
int j;
for (j = i; j < argc; j++)
- verify_filename(prefix, argv[j]);
+ verify_filename(prefix, argv[j], j == i);
}
paths = get_pathspec(prefix, argv + i);
@@ -1031,13 +959,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!show_in_pager)
setup_pager();
+ if (!use_index && (untracked || cached))
+ die(_("--cached or --untracked cannot be used with --no-index."));
- if (!use_index) {
- if (cached)
- die(_("--cached cannot be used with --no-index."));
+ if (!use_index || untracked) {
+ int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
if (list.nr)
- die(_("--no-index cannot be used with revs."));
- hit = grep_directory(&opt, &pathspec);
+ die(_("--no-index or --untracked cannot be used with revs."));
+ hit = grep_directory(&opt, &pathspec, use_exclude);
+ } else if (0 <= opt_exclude) {
+ die(_("--[no-]exclude-standard cannot be used for tracked contents."));
} else if (!list.nr) {
if (!cached)
setup_work_tree();
diff --git a/builtin/help.c b/builtin/help.c
index 61ff798..8f9cd60 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -9,8 +9,13 @@
#include "common-cmds.h"
#include "parse-options.h"
#include "run-command.h"
+#include "column.h"
#include "help.h"
+#ifndef DEFAULT_HELP_FORMAT
+#define DEFAULT_HELP_FORMAT "man"
+#endif
+
static struct man_viewer_list {
struct man_viewer_list *next;
char name[FLEX_ARRAY];
@@ -30,6 +35,7 @@ enum help_format {
};
static int show_all = 0;
+static unsigned int colopts;
static enum help_format help_format = HELP_FORMAT_NONE;
static struct option builtin_help_options[] = {
OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
@@ -54,7 +60,7 @@ static enum help_format parse_help_format(const char *format)
return HELP_FORMAT_INFO;
if (!strcmp(format, "web") || !strcmp(format, "html"))
return HELP_FORMAT_WEB;
- die("unrecognized help format '%s'", format);
+ die(_("unrecognized help format '%s'"), format);
}
static const char *get_man_viewer_info(const char *name)
@@ -82,7 +88,7 @@ static int check_emacsclient_version(void)
ec_process.err = -1;
ec_process.stdout_to_stderr = 1;
if (start_command(&ec_process))
- return error("Failed to start emacsclient.");
+ return error(_("Failed to start emacsclient."));
strbuf_read(&buffer, ec_process.err, 20);
close(ec_process.err);
@@ -95,7 +101,7 @@ static int check_emacsclient_version(void)
if (prefixcmp(buffer.buf, "emacsclient")) {
strbuf_release(&buffer);
- return error("Failed to parse emacsclient version.");
+ return error(_("Failed to parse emacsclient version."));
}
strbuf_remove(&buffer, 0, strlen("emacsclient"));
@@ -103,7 +109,7 @@ static int check_emacsclient_version(void)
if (version < 22) {
strbuf_release(&buffer);
- return error("emacsclient version '%d' too old (< 22).",
+ return error(_("emacsclient version '%d' too old (< 22)."),
version);
}
@@ -121,7 +127,7 @@ static void exec_woman_emacs(const char *path, const char *page)
path = "emacsclient";
strbuf_addf(&man_page, "(woman \"%s\")", page);
execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
}
@@ -149,7 +155,7 @@ static void exec_man_konqueror(const char *path, const char *page)
path = "kfmclient";
strbuf_addf(&man_page, "man:%s(1)", page);
execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
}
@@ -158,7 +164,7 @@ static void exec_man_man(const char *path, const char *page)
if (!path)
path = "man";
execlp(path, "man", page, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
static void exec_man_cmd(const char *cmd, const char *page)
@@ -166,7 +172,7 @@ static void exec_man_cmd(const char *cmd, const char *page)
struct strbuf shell_cmd = STRBUF_INIT;
strbuf_addf(&shell_cmd, "%s %s", cmd, page);
execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
- warning("failed to exec '%s': %s", cmd, strerror(errno));
+ warning(_("failed to exec '%s': %s"), cmd, strerror(errno));
}
static void add_man_viewer(const char *name)
@@ -206,8 +212,8 @@ static int add_man_viewer_path(const char *name,
if (supported_man_viewer(name, len))
do_add_man_viewer_info(name, len, value);
else
- warning("'%s': path for unsupported man viewer.\n"
- "Please consider using 'man.<tool>.cmd' instead.",
+ warning(_("'%s': path for unsupported man viewer.\n"
+ "Please consider using 'man.<tool>.cmd' instead."),
name);
return 0;
@@ -218,8 +224,8 @@ static int add_man_viewer_cmd(const char *name,
const char *value)
{
if (supported_man_viewer(name, len))
- warning("'%s': cmd for supported man viewer.\n"
- "Please consider using 'man.<tool>.path' instead.",
+ warning(_("'%s': cmd for supported man viewer.\n"
+ "Please consider using 'man.<tool>.path' instead."),
name);
else
do_add_man_viewer_info(name, len, value);
@@ -251,6 +257,8 @@ static int add_man_viewer_info(const char *var, const char *value)
static int git_help_config(const char *var, const char *value, void *cb)
{
+ if (!prefixcmp(var, "column."))
+ return git_column_config(var, value, "help", &colopts);
if (!strcmp(var, "help.format")) {
if (!value)
return config_error_nonbool(var);
@@ -280,11 +288,11 @@ void list_common_cmds_help(void)
longest = strlen(common_cmds[i].name);
}
- puts("The most commonly used git commands are:");
+ puts(_("The most commonly used git commands are:"));
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
printf(" %s ", common_cmds[i].name);
mput_char(' ', longest - strlen(common_cmds[i].name));
- puts(common_cmds[i].help);
+ puts(_(common_cmds[i].help));
}
}
@@ -348,7 +356,7 @@ static void exec_viewer(const char *name, const char *page)
else if (info)
exec_man_cmd(info, page);
else
- warning("'%s': unknown man viewer.", name);
+ warning(_("'%s': unknown man viewer."), name);
}
static void show_man_page(const char *git_cmd)
@@ -365,7 +373,7 @@ static void show_man_page(const char *git_cmd)
if (fallback)
exec_viewer(fallback, page);
exec_viewer("man", page);
- die("no man viewer handled the request");
+ die(_("no man viewer handled the request"));
}
static void show_info_page(const char *git_cmd)
@@ -373,7 +381,7 @@ static void show_info_page(const char *git_cmd)
const char *page = cmd_to_page(git_cmd);
setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
execlp("info", "info", "gitman", page, (char *)NULL);
- die("no info viewer handled the request");
+ die(_("no info viewer handled the request"));
}
static void get_html_page_path(struct strbuf *page_path, const char *page)
@@ -384,7 +392,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page)
/* Check that we have a git documentation directory. */
if (stat(mkpath("%s/git.html", html_path), &st)
|| !S_ISREG(st.st_mode))
- die("'%s': not a documentation directory.", html_path);
+ die(_("'%s': not a documentation directory."), html_path);
strbuf_init(page_path, 0);
strbuf_addf(page_path, "%s/%s.html", html_path, page);
@@ -424,16 +432,17 @@ int cmd_help(int argc, const char **argv, const char *prefix)
parsed_help_format = help_format;
if (show_all) {
- printf("usage: %s\n\n", git_usage_string);
- list_commands("git commands", &main_cmds, &other_cmds);
- printf("%s\n", git_more_info_string);
+ git_config(git_help_config, NULL);
+ printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
+ list_commands(colopts, &main_cmds, &other_cmds);
+ printf("%s\n", _(git_more_info_string));
return 0;
}
if (!argv[0]) {
- printf("usage: %s\n\n", git_usage_string);
+ printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
list_common_cmds_help();
- printf("\n%s\n", git_more_info_string);
+ printf("\n%s\n", _(git_more_info_string));
return 0;
}
@@ -442,10 +451,12 @@ int cmd_help(int argc, const char **argv, const char *prefix)
if (parsed_help_format != HELP_FORMAT_NONE)
help_format = parsed_help_format;
+ if (help_format == HELP_FORMAT_NONE)
+ help_format = parse_help_format(DEFAULT_HELP_FORMAT);
alias = alias_lookup(argv[0]);
if (alias && !is_git_command(argv[0])) {
- printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+ printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
return 0;
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 81cdc28..5a0372a 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -9,9 +9,11 @@
#include "progress.h"
#include "fsck.h"
#include "exec_cmd.h"
+#include "streaming.h"
+#include "thread-utils.h"
static const char index_pack_usage[] =
-"git index-pack [-v] [-o <index-file>] [ --keep | --keep=<msg> ] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
+"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
struct object_entry {
struct pack_idx_entry idx;
@@ -19,6 +21,8 @@ struct object_entry {
unsigned int hdr_size;
enum object_type type;
enum object_type real_type;
+ unsigned delta_depth;
+ int base_object_no;
};
union delta_base {
@@ -32,6 +36,21 @@ struct base_data {
struct object_entry *obj;
void *data;
unsigned long size;
+ int ref_first, ref_last;
+ int ofs_first, ofs_last;
+};
+
+#if !defined(NO_PTHREADS) && defined(NO_THREAD_SAFE_PREAD)
+/* pread() emulation is not thread-safe. Disable threading. */
+#define NO_PTHREADS
+#endif
+
+struct thread_local {
+#ifndef NO_PTHREADS
+ pthread_t thread;
+#endif
+ struct base_data *base_cache;
+ size_t base_cache_used;
};
/*
@@ -50,11 +69,11 @@ struct delta_entry {
static struct object_entry *objects;
static struct delta_entry *deltas;
-static struct base_data *base_cache;
-static size_t base_cache_used;
+static struct thread_local nothread_data;
static int nr_objects;
static int nr_deltas;
static int nr_resolved_deltas;
+static int nr_threads;
static int from_stdin;
static int strict;
@@ -66,17 +85,89 @@ static struct progress *progress;
static unsigned char input_buffer[4096];
static unsigned int input_offset, input_len;
static off_t consumed_bytes;
+static unsigned deepest_delta;
static git_SHA_CTX input_ctx;
static uint32_t input_crc32;
static int input_fd, output_fd, pack_fd;
+#ifndef NO_PTHREADS
+
+static struct thread_local *thread_data;
+static int nr_dispatched;
+static int threads_active;
+
+static pthread_mutex_t read_mutex;
+#define read_lock() lock_mutex(&read_mutex)
+#define read_unlock() unlock_mutex(&read_mutex)
+
+static pthread_mutex_t counter_mutex;
+#define counter_lock() lock_mutex(&counter_mutex)
+#define counter_unlock() unlock_mutex(&counter_mutex)
+
+static pthread_mutex_t work_mutex;
+#define work_lock() lock_mutex(&work_mutex)
+#define work_unlock() unlock_mutex(&work_mutex)
+
+static pthread_key_t key;
+
+static inline void lock_mutex(pthread_mutex_t *mutex)
+{
+ if (threads_active)
+ pthread_mutex_lock(mutex);
+}
+
+static inline void unlock_mutex(pthread_mutex_t *mutex)
+{
+ if (threads_active)
+ pthread_mutex_unlock(mutex);
+}
+
+/*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+static void init_thread(void)
+{
+ init_recursive_mutex(&read_mutex);
+ pthread_mutex_init(&counter_mutex, NULL);
+ pthread_mutex_init(&work_mutex, NULL);
+ pthread_key_create(&key, NULL);
+ thread_data = xcalloc(nr_threads, sizeof(*thread_data));
+ threads_active = 1;
+}
+
+static void cleanup_thread(void)
+{
+ if (!threads_active)
+ return;
+ threads_active = 0;
+ pthread_mutex_destroy(&read_mutex);
+ pthread_mutex_destroy(&counter_mutex);
+ pthread_mutex_destroy(&work_mutex);
+ pthread_key_delete(key);
+ free(thread_data);
+}
+
+#else
+
+#define read_lock()
+#define read_unlock()
+
+#define counter_lock()
+#define counter_unlock()
+
+#define work_lock()
+#define work_unlock()
+
+#endif
+
+
static int mark_link(struct object *obj, int type, void *data)
{
if (!obj)
return -1;
if (type != OBJ_ANY && obj->type != type)
- die("object type mismatch at %s", sha1_to_hex(obj->sha1));
+ die(_("object type mismatch at %s"), sha1_to_hex(obj->sha1));
obj->flags |= FLAG_LINK;
return 0;
@@ -96,7 +187,7 @@ static void check_object(struct object *obj)
unsigned long size;
int type = sha1_object_info(obj->sha1, &size);
if (type != obj->type || type <= 0)
- die("object of unexpected type");
+ die(_("object of unexpected type"));
obj->flags |= FLAG_CHECKED;
return;
}
@@ -133,15 +224,18 @@ static void *fill(int min)
if (min <= input_len)
return input_buffer + input_offset;
if (min > sizeof(input_buffer))
- die("cannot fill %d bytes", min);
+ die(Q_("cannot fill %d byte",
+ "cannot fill %d bytes",
+ min),
+ min);
flush();
do {
ssize_t ret = xread(input_fd, input_buffer + input_len,
sizeof(input_buffer) - input_len);
if (ret <= 0) {
if (!ret)
- die("early EOF");
- die_errno("read error on input");
+ die(_("early EOF"));
+ die_errno(_("read error on input"));
}
input_len += ret;
if (from_stdin)
@@ -153,14 +247,14 @@ static void *fill(int min)
static void use(int bytes)
{
if (bytes > input_len)
- die("used more bytes than were available");
+ die(_("used more bytes than were available"));
input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
input_len -= bytes;
input_offset += bytes;
/* make sure off_t is sufficiently large not to wrap */
if (signed_add_overflows(consumed_bytes, bytes))
- die("pack too large for current definition of off_t");
+ die(_("pack too large for current definition of off_t"));
consumed_bytes += bytes;
}
@@ -169,19 +263,19 @@ static const char *open_pack_file(const char *pack_name)
if (from_stdin) {
input_fd = 0;
if (!pack_name) {
- static char tmpfile[PATH_MAX];
- output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+ static char tmp_file[PATH_MAX];
+ output_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
"pack/tmp_pack_XXXXXX");
- pack_name = xstrdup(tmpfile);
+ pack_name = xstrdup(tmp_file);
} else
output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
if (output_fd < 0)
- die_errno("unable to create '%s'", pack_name);
+ die_errno(_("unable to create '%s'"), pack_name);
pack_fd = output_fd;
} else {
input_fd = open(pack_name, O_RDONLY);
if (input_fd < 0)
- die_errno("cannot open packfile '%s'", pack_name);
+ die_errno(_("cannot open packfile '%s'"), pack_name);
output_fd = -1;
pack_fd = input_fd;
}
@@ -195,7 +289,7 @@ static void parse_pack_header(void)
/* Header consistency check */
if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
- die("pack signature mismatch");
+ die(_("pack signature mismatch"));
if (!pack_version_ok(hdr->hdr_version))
die("pack version %"PRIu32" unsupported",
ntohl(hdr->hdr_version));
@@ -215,7 +309,35 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...)
va_start(params, format);
vsnprintf(buf, sizeof(buf), format, params);
va_end(params);
- die("pack has bad object at offset %lu: %s", offset, buf);
+ die(_("pack has bad object at offset %lu: %s"), offset, buf);
+}
+
+static inline struct thread_local *get_thread_data(void)
+{
+#ifndef NO_PTHREADS
+ if (threads_active)
+ return pthread_getspecific(key);
+ assert(!threads_active &&
+ "This should only be reached when all threads are gone");
+#endif
+ return &nothread_data;
+}
+
+#ifndef NO_PTHREADS
+static void set_thread_data(struct thread_local *data)
+{
+ if (threads_active)
+ pthread_setspecific(key, data);
+}
+#endif
+
+static struct base_data *alloc_base_data(void)
+{
+ struct base_data *base = xmalloc(sizeof(struct base_data));
+ memset(base, 0, sizeof(*base));
+ base->ref_last = -1;
+ base->ofs_last = -1;
+ return base;
}
static void free_base_data(struct base_data *c)
@@ -223,15 +345,16 @@ static void free_base_data(struct base_data *c)
if (c->data) {
free(c->data);
c->data = NULL;
- base_cache_used -= c->size;
+ get_thread_data()->base_cache_used -= c->size;
}
}
static void prune_base_data(struct base_data *retain)
{
struct base_data *b;
- for (b = base_cache;
- base_cache_used > delta_base_cache_limit && b;
+ struct thread_local *data = get_thread_data();
+ for (b = data->base_cache;
+ data->base_cache_used > delta_base_cache_limit && b;
b = b->child) {
if (b->data && b != retain)
free_base_data(b);
@@ -243,12 +366,12 @@ static void link_base_data(struct base_data *base, struct base_data *c)
if (base)
base->child = c;
else
- base_cache = c;
+ get_thread_data()->base_cache = c;
c->base = base;
c->child = NULL;
if (c->data)
- base_cache_used += c->size;
+ get_thread_data()->base_cache_used += c->size;
prune_base_data(c);
}
@@ -258,34 +381,66 @@ static void unlink_base_data(struct base_data *c)
if (base)
base->child = NULL;
else
- base_cache = NULL;
+ get_thread_data()->base_cache = NULL;
free_base_data(c);
}
-static void *unpack_entry_data(unsigned long offset, unsigned long size)
+static int is_delta_type(enum object_type type)
+{
+ return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+}
+
+static void *unpack_entry_data(unsigned long offset, unsigned long size,
+ enum object_type type, unsigned char *sha1)
{
+ static char fixed_buf[8192];
int status;
git_zstream stream;
- void *buf = xmalloc(size);
+ void *buf;
+ git_SHA_CTX c;
+ char hdr[32];
+ int hdrlen;
+
+ if (!is_delta_type(type)) {
+ hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1;
+ git_SHA1_Init(&c);
+ git_SHA1_Update(&c, hdr, hdrlen);
+ } else
+ sha1 = NULL;
+ if (type == OBJ_BLOB && size > big_file_threshold)
+ buf = fixed_buf;
+ else
+ buf = xmalloc(size);
memset(&stream, 0, sizeof(stream));
git_inflate_init(&stream);
stream.next_out = buf;
- stream.avail_out = size;
+ stream.avail_out = buf == fixed_buf ? sizeof(fixed_buf) : size;
do {
+ unsigned char *last_out = stream.next_out;
stream.next_in = fill(1);
stream.avail_in = input_len;
status = git_inflate(&stream, 0);
use(input_len - stream.avail_in);
+ if (sha1)
+ git_SHA1_Update(&c, last_out, stream.next_out - last_out);
+ if (buf == fixed_buf) {
+ stream.next_out = buf;
+ stream.avail_out = sizeof(fixed_buf);
+ }
} while (status == Z_OK);
if (stream.total_out != size || status != Z_STREAM_END)
- bad_object(offset, "inflate returned %d", status);
+ bad_object(offset, _("inflate returned %d"), status);
git_inflate_end(&stream);
- return buf;
+ if (sha1)
+ git_SHA1_Final(sha1, &c);
+ return buf == fixed_buf ? NULL : buf;
}
-static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
+static void *unpack_raw_entry(struct object_entry *obj,
+ union delta_base *delta_base,
+ unsigned char *sha1)
{
unsigned char *p;
unsigned long size, c;
@@ -325,7 +480,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
while (c & 128) {
base_offset += 1;
if (!base_offset || MSB(base_offset, 7))
- bad_object(obj->idx.offset, "offset value overflow for delta base object");
+ bad_object(obj->idx.offset, _("offset value overflow for delta base object"));
p = fill(1);
c = *p;
use(1);
@@ -333,7 +488,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
}
delta_base->offset = obj->idx.offset - base_offset;
if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
- bad_object(obj->idx.offset, "delta base offset is out of bound");
+ bad_object(obj->idx.offset, _("delta base offset is out of bound"));
break;
case OBJ_COMMIT:
case OBJ_TREE:
@@ -341,16 +496,18 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
case OBJ_TAG:
break;
default:
- bad_object(obj->idx.offset, "unknown object type %d", obj->type);
+ bad_object(obj->idx.offset, _("unknown object type %d"), obj->type);
}
obj->hdr_size = consumed_bytes - obj->idx.offset;
- data = unpack_entry_data(obj->idx.offset, obj->size);
+ data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, sha1);
obj->idx.crc32 = input_crc32;
return data;
}
-static void *get_data_from_pack(struct object_entry *obj)
+static void *unpack_data(struct object_entry *obj,
+ int (*consume)(const unsigned char *, unsigned long, void *),
+ void *cb_data)
{
off_t from = obj[0].idx.offset + obj[0].hdr_size;
unsigned long len = obj[1].idx.offset - from;
@@ -358,38 +515,71 @@ static void *get_data_from_pack(struct object_entry *obj)
git_zstream stream;
int status;
- data = xmalloc(obj->size);
+ data = xmalloc(consume ? 64*1024 : obj->size);
inbuf = xmalloc((len < 64*1024) ? len : 64*1024);
memset(&stream, 0, sizeof(stream));
git_inflate_init(&stream);
stream.next_out = data;
- stream.avail_out = obj->size;
+ stream.avail_out = consume ? 64*1024 : obj->size;
do {
+ unsigned char *last_out = stream.next_out;
ssize_t n = (len < 64*1024) ? len : 64*1024;
n = pread(pack_fd, inbuf, n, from);
if (n < 0)
- die_errno("cannot pread pack file");
+ die_errno(_("cannot pread pack file"));
if (!n)
- die("premature end of pack file, %lu bytes missing", len);
+ die(Q_("premature end of pack file, %lu byte missing",
+ "premature end of pack file, %lu bytes missing",
+ len),
+ len);
from += n;
len -= n;
stream.next_in = inbuf;
stream.avail_in = n;
status = git_inflate(&stream, 0);
+ if (consume) {
+ if (consume(last_out, stream.next_out - last_out, cb_data)) {
+ free(inbuf);
+ free(data);
+ return NULL;
+ }
+ stream.next_out = data;
+ stream.avail_out = 64*1024;
+ }
} while (len && status == Z_OK && !stream.avail_in);
/* This has been inflated OK when first encountered, so... */
if (status != Z_STREAM_END || stream.total_out != obj->size)
- die("serious inflate inconsistency");
+ die(_("serious inflate inconsistency"));
git_inflate_end(&stream);
free(inbuf);
+ if (consume) {
+ free(data);
+ data = NULL;
+ }
return data;
}
-static int find_delta(const union delta_base *base)
+static void *get_data_from_pack(struct object_entry *obj)
+{
+ return unpack_data(obj, NULL, NULL);
+}
+
+static int compare_delta_bases(const union delta_base *base1,
+ const union delta_base *base2,
+ enum object_type type1,
+ enum object_type type2)
+{
+ int cmp = type1 - type2;
+ if (cmp)
+ return cmp;
+ return memcmp(base1, base2, UNION_BASE_SZ);
+}
+
+static int find_delta(const union delta_base *base, enum object_type type)
{
int first = 0, last = nr_deltas;
@@ -398,7 +588,8 @@ static int find_delta(const union delta_base *base)
struct delta_entry *delta = &deltas[next];
int cmp;
- cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
+ cmp = compare_delta_bases(base, &delta->base,
+ type, objects[delta->obj_no].type);
if (!cmp)
return next;
if (cmp < 0) {
@@ -411,9 +602,10 @@ static int find_delta(const union delta_base *base)
}
static void find_delta_children(const union delta_base *base,
- int *first_index, int *last_index)
+ int *first_index, int *last_index,
+ enum object_type type)
{
- int first = find_delta(base);
+ int first = find_delta(base, type);
int last = first;
int end = nr_deltas - 1;
@@ -430,45 +622,130 @@ static void find_delta_children(const union delta_base *base,
*last_index = last;
}
-static void sha1_object(const void *data, unsigned long size,
- enum object_type type, unsigned char *sha1)
+struct compare_data {
+ struct object_entry *entry;
+ struct git_istream *st;
+ unsigned char *buf;
+ unsigned long buf_size;
+};
+
+static int compare_objects(const unsigned char *buf, unsigned long size,
+ void *cb_data)
+{
+ struct compare_data *data = cb_data;
+
+ if (data->buf_size < size) {
+ free(data->buf);
+ data->buf = xmalloc(size);
+ data->buf_size = size;
+ }
+
+ while (size) {
+ ssize_t len = read_istream(data->st, data->buf, size);
+ if (len == 0)
+ die(_("SHA1 COLLISION FOUND WITH %s !"),
+ sha1_to_hex(data->entry->idx.sha1));
+ if (len < 0)
+ die(_("unable to read %s"),
+ sha1_to_hex(data->entry->idx.sha1));
+ if (memcmp(buf, data->buf, len))
+ die(_("SHA1 COLLISION FOUND WITH %s !"),
+ sha1_to_hex(data->entry->idx.sha1));
+ size -= len;
+ buf += len;
+ }
+ return 0;
+}
+
+static int check_collison(struct object_entry *entry)
{
- hash_sha1_file(data, size, typename(type), sha1);
- if (has_sha1_file(sha1)) {
+ struct compare_data data;
+ enum object_type type;
+ unsigned long size;
+
+ if (entry->size <= big_file_threshold || entry->type != OBJ_BLOB)
+ return -1;
+
+ memset(&data, 0, sizeof(data));
+ data.entry = entry;
+ data.st = open_istream(entry->idx.sha1, &type, &size, NULL);
+ if (!data.st)
+ return -1;
+ if (size != entry->size || type != entry->type)
+ die(_("SHA1 COLLISION FOUND WITH %s !"),
+ sha1_to_hex(entry->idx.sha1));
+ unpack_data(entry, compare_objects, &data);
+ close_istream(data.st);
+ free(data.buf);
+ return 0;
+}
+
+static void sha1_object(const void *data, struct object_entry *obj_entry,
+ unsigned long size, enum object_type type,
+ const unsigned char *sha1)
+{
+ void *new_data = NULL;
+ int collision_test_needed;
+
+ assert(data || obj_entry);
+
+ read_lock();
+ collision_test_needed = has_sha1_file(sha1);
+ read_unlock();
+
+ if (collision_test_needed && !data) {
+ read_lock();
+ if (!check_collison(obj_entry))
+ collision_test_needed = 0;
+ read_unlock();
+ }
+ if (collision_test_needed) {
void *has_data;
enum object_type has_type;
unsigned long has_size;
+ read_lock();
+ has_type = sha1_object_info(sha1, &has_size);
+ if (has_type != type || has_size != size)
+ die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1));
has_data = read_sha1_file(sha1, &has_type, &has_size);
+ read_unlock();
+ if (!data)
+ data = new_data = get_data_from_pack(obj_entry);
if (!has_data)
- die("cannot read existing object %s", sha1_to_hex(sha1));
+ die(_("cannot read existing object %s"), sha1_to_hex(sha1));
if (size != has_size || type != has_type ||
memcmp(data, has_data, size) != 0)
- die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+ die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1));
free(has_data);
}
+
if (strict) {
+ read_lock();
if (type == OBJ_BLOB) {
struct blob *blob = lookup_blob(sha1);
if (blob)
blob->object.flags |= FLAG_CHECKED;
else
- die("invalid blob object %s", sha1_to_hex(sha1));
+ die(_("invalid blob object %s"), sha1_to_hex(sha1));
} else {
struct object *obj;
int eaten;
void *buf = (void *) data;
+ if (!buf)
+ buf = new_data = get_data_from_pack(obj_entry);
+
/*
* we do not need to free the memory here, as the
* buf is deleted by the caller.
*/
obj = parse_object_buffer(sha1, type, size, buf, &eaten);
if (!obj)
- die("invalid %s", typename(type));
+ die(_("invalid %s"), typename(type));
if (fsck_object(obj, 1, fsck_error_function))
- die("Error in object");
+ die(_("Error in object"));
if (fsck_walk(obj, mark_link, NULL))
- die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
+ die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
if (obj->type == OBJ_TREE) {
struct tree *item = (struct tree *) obj;
@@ -480,31 +757,69 @@ static void sha1_object(const void *data, unsigned long size,
}
obj->flags |= FLAG_CHECKED;
}
+ read_unlock();
}
+
+ free(new_data);
}
+/*
+ * This function is part of find_unresolved_deltas(). There are two
+ * walkers going in the opposite ways.
+ *
+ * The first one in find_unresolved_deltas() traverses down from
+ * parent node to children, deflating nodes along the way. However,
+ * memory for deflated nodes is limited by delta_base_cache_limit, so
+ * at some point parent node's deflated content may be freed.
+ *
+ * The second walker is this function, which goes from current node up
+ * to top parent if necessary to deflate the node. In normal
+ * situation, its parent node would be already deflated, so it just
+ * needs to apply delta.
+ *
+ * In the worst case scenario, parent node is no longer deflated because
+ * we're running out of delta_base_cache_limit; we need to re-deflate
+ * parents, possibly up to the top base.
+ *
+ * All deflated objects here are subject to be freed if we exceed
+ * delta_base_cache_limit, just like in find_unresolved_deltas(), we
+ * just need to make sure the last node is not freed.
+ */
static void *get_base_data(struct base_data *c)
{
if (!c->data) {
struct object_entry *obj = c->obj;
+ struct base_data **delta = NULL;
+ int delta_nr = 0, delta_alloc = 0;
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
- void *base = get_base_data(c->base);
- void *raw = get_data_from_pack(obj);
+ while (is_delta_type(c->obj->type) && !c->data) {
+ ALLOC_GROW(delta, delta_nr + 1, delta_alloc);
+ delta[delta_nr++] = c;
+ c = c->base;
+ }
+ if (!delta_nr) {
+ c->data = get_data_from_pack(obj);
+ c->size = obj->size;
+ get_thread_data()->base_cache_used += c->size;
+ prune_base_data(c);
+ }
+ for (; delta_nr > 0; delta_nr--) {
+ void *base, *raw;
+ c = delta[delta_nr - 1];
+ obj = c->obj;
+ base = get_base_data(c->base);
+ raw = get_data_from_pack(obj);
c->data = patch_delta(
base, c->base->size,
raw, obj->size,
&c->size);
free(raw);
if (!c->data)
- bad_object(obj->idx.offset, "failed to apply delta");
- } else {
- c->data = get_data_from_pack(obj);
- c->size = obj->size;
+ bad_object(obj->idx.offset, _("failed to apply delta"));
+ get_thread_data()->base_cache_used += c->size;
+ prune_base_data(c);
}
-
- base_cache_used += c->size;
- prune_base_data(c);
+ free(delta);
}
return c->data;
}
@@ -515,6 +830,10 @@ static void resolve_delta(struct object_entry *delta_obj,
void *base_data, *delta_data;
delta_obj->real_type = base->obj->real_type;
+ delta_obj->delta_depth = base->obj->delta_depth + 1;
+ if (deepest_delta < delta_obj->delta_depth)
+ deepest_delta = delta_obj->delta_depth;
+ delta_obj->base_object_no = base->obj - objects;
delta_data = get_data_from_pack(delta_obj);
base_data = get_base_data(base);
result->obj = delta_obj;
@@ -522,98 +841,161 @@ static void resolve_delta(struct object_entry *delta_obj,
delta_data, delta_obj->size, &result->size);
free(delta_data);
if (!result->data)
- bad_object(delta_obj->idx.offset, "failed to apply delta");
- sha1_object(result->data, result->size, delta_obj->real_type,
+ bad_object(delta_obj->idx.offset, _("failed to apply delta"));
+ hash_sha1_file(result->data, result->size,
+ typename(delta_obj->real_type), delta_obj->idx.sha1);
+ sha1_object(result->data, NULL, result->size, delta_obj->real_type,
delta_obj->idx.sha1);
+ counter_lock();
nr_resolved_deltas++;
+ counter_unlock();
}
-static void find_unresolved_deltas(struct base_data *base,
- struct base_data *prev_base)
+static struct base_data *find_unresolved_deltas_1(struct base_data *base,
+ struct base_data *prev_base)
{
- int i, ref_first, ref_last, ofs_first, ofs_last;
-
- /*
- * This is a recursive function. Those brackets should help reducing
- * stack usage by limiting the scope of the delta_base union.
- */
- {
+ if (base->ref_last == -1 && base->ofs_last == -1) {
union delta_base base_spec;
hashcpy(base_spec.sha1, base->obj->idx.sha1);
- find_delta_children(&base_spec, &ref_first, &ref_last);
+ find_delta_children(&base_spec,
+ &base->ref_first, &base->ref_last, OBJ_REF_DELTA);
memset(&base_spec, 0, sizeof(base_spec));
base_spec.offset = base->obj->idx.offset;
- find_delta_children(&base_spec, &ofs_first, &ofs_last);
- }
+ find_delta_children(&base_spec,
+ &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA);
- if (ref_last == -1 && ofs_last == -1) {
- free(base->data);
- return;
+ if (base->ref_last == -1 && base->ofs_last == -1) {
+ free(base->data);
+ return NULL;
+ }
+
+ link_base_data(prev_base, base);
}
- link_base_data(prev_base, base);
+ if (base->ref_first <= base->ref_last) {
+ struct object_entry *child = objects + deltas[base->ref_first].obj_no;
+ struct base_data *result = alloc_base_data();
- for (i = ref_first; i <= ref_last; i++) {
- struct object_entry *child = objects + deltas[i].obj_no;
- if (child->real_type == OBJ_REF_DELTA) {
- struct base_data result;
- resolve_delta(child, base, &result);
- if (i == ref_last && ofs_last == -1)
- free_base_data(base);
- find_unresolved_deltas(&result, base);
- }
+ assert(child->real_type == OBJ_REF_DELTA);
+ resolve_delta(child, base, result);
+ if (base->ref_first == base->ref_last && base->ofs_last == -1)
+ free_base_data(base);
+
+ base->ref_first++;
+ return result;
}
- for (i = ofs_first; i <= ofs_last; i++) {
- struct object_entry *child = objects + deltas[i].obj_no;
- if (child->real_type == OBJ_OFS_DELTA) {
- struct base_data result;
- resolve_delta(child, base, &result);
- if (i == ofs_last)
- free_base_data(base);
- find_unresolved_deltas(&result, base);
- }
+ if (base->ofs_first <= base->ofs_last) {
+ struct object_entry *child = objects + deltas[base->ofs_first].obj_no;
+ struct base_data *result = alloc_base_data();
+
+ assert(child->real_type == OBJ_OFS_DELTA);
+ resolve_delta(child, base, result);
+ if (base->ofs_first == base->ofs_last)
+ free_base_data(base);
+
+ base->ofs_first++;
+ return result;
}
unlink_base_data(base);
+ return NULL;
+}
+
+static void find_unresolved_deltas(struct base_data *base)
+{
+ struct base_data *new_base, *prev_base = NULL;
+ for (;;) {
+ new_base = find_unresolved_deltas_1(base, prev_base);
+
+ if (new_base) {
+ prev_base = base;
+ base = new_base;
+ } else {
+ free(base);
+ base = prev_base;
+ if (!base)
+ return;
+ prev_base = base->base;
+ }
+ }
}
static int compare_delta_entry(const void *a, const void *b)
{
const struct delta_entry *delta_a = a;
const struct delta_entry *delta_b = b;
- return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
+
+ /* group by type (ref vs ofs) and then by value (sha-1 or offset) */
+ return compare_delta_bases(&delta_a->base, &delta_b->base,
+ objects[delta_a->obj_no].type,
+ objects[delta_b->obj_no].type);
}
-/* Parse all objects and return the pack content SHA1 hash */
+static void resolve_base(struct object_entry *obj)
+{
+ struct base_data *base_obj = alloc_base_data();
+ base_obj->obj = obj;
+ base_obj->data = NULL;
+ find_unresolved_deltas(base_obj);
+}
+
+#ifndef NO_PTHREADS
+static void *threaded_second_pass(void *data)
+{
+ set_thread_data(data);
+ for (;;) {
+ int i;
+ work_lock();
+ display_progress(progress, nr_resolved_deltas);
+ while (nr_dispatched < nr_objects &&
+ is_delta_type(objects[nr_dispatched].type))
+ nr_dispatched++;
+ if (nr_dispatched >= nr_objects) {
+ work_unlock();
+ break;
+ }
+ i = nr_dispatched++;
+ work_unlock();
+
+ resolve_base(&objects[i]);
+ }
+ return NULL;
+}
+#endif
+
+/*
+ * First pass:
+ * - find locations of all objects;
+ * - calculate SHA1 of all non-delta objects;
+ * - remember base (SHA1 or offset) for all deltas.
+ */
static void parse_pack_objects(unsigned char *sha1)
{
- int i;
+ int i, nr_delays = 0;
struct delta_entry *delta = deltas;
struct stat st;
- /*
- * First pass:
- * - find locations of all objects;
- * - calculate SHA1 of all non-delta objects;
- * - remember base (SHA1 or offset) for all deltas.
- */
if (verbose)
progress = start_progress(
- from_stdin ? "Receiving objects" : "Indexing objects",
+ from_stdin ? _("Receiving objects") : _("Indexing objects"),
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- void *data = unpack_raw_entry(obj, &delta->base);
+ void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1);
obj->real_type = obj->type;
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ if (is_delta_type(obj->type)) {
nr_deltas++;
delta->obj_no = i;
delta++;
+ } else if (!data) {
+ /* large blobs, check later */
+ obj->real_type = OBJ_BAD;
+ nr_delays++;
} else
- sha1_object(data, obj->size, obj->type, obj->idx.sha1);
+ sha1_object(data, NULL, obj->size, obj->type, obj->idx.sha1);
free(data);
display_progress(progress, i+1);
}
@@ -624,15 +1006,39 @@ static void parse_pack_objects(unsigned char *sha1)
flush();
git_SHA1_Final(sha1, &input_ctx);
if (hashcmp(fill(20), sha1))
- die("pack is corrupted (SHA1 mismatch)");
+ die(_("pack is corrupted (SHA1 mismatch)"));
use(20);
/* If input_fd is a file, we should have reached its end now. */
if (fstat(input_fd, &st))
- die_errno("cannot fstat packfile");
+ die_errno(_("cannot fstat packfile"));
if (S_ISREG(st.st_mode) &&
lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
- die("pack has junk at the end");
+ die(_("pack has junk at the end"));
+
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = &objects[i];
+ if (obj->real_type != OBJ_BAD)
+ continue;
+ obj->real_type = obj->type;
+ sha1_object(NULL, obj, obj->size, obj->type, obj->idx.sha1);
+ nr_delays--;
+ }
+ if (nr_delays)
+ die(_("confusion beyond insanity in parse_pack_objects()"));
+}
+
+/*
+ * Second pass:
+ * - for all non-delta objects, look if it is used as a base for
+ * deltas;
+ * - if used as a base, uncompress the object and apply all deltas,
+ * recursively checking if the resulting object is used as a base
+ * for some more deltas.
+ */
+static void resolve_deltas(void)
+{
+ int i;
if (!nr_deltas)
return;
@@ -641,29 +1047,83 @@ static void parse_pack_objects(unsigned char *sha1)
qsort(deltas, nr_deltas, sizeof(struct delta_entry),
compare_delta_entry);
- /*
- * Second pass:
- * - for all non-delta objects, look if it is used as a base for
- * deltas;
- * - if used as a base, uncompress the object and apply all deltas,
- * recursively checking if the resulting object is used as a base
- * for some more deltas.
- */
if (verbose)
- progress = start_progress("Resolving deltas", nr_deltas);
+ progress = start_progress(_("Resolving deltas"), nr_deltas);
+
+#ifndef NO_PTHREADS
+ nr_dispatched = 0;
+ if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) {
+ init_thread();
+ for (i = 0; i < nr_threads; i++) {
+ int ret = pthread_create(&thread_data[i].thread, NULL,
+ threaded_second_pass, thread_data + i);
+ if (ret)
+ die("unable to create thread: %s", strerror(ret));
+ }
+ for (i = 0; i < nr_threads; i++)
+ pthread_join(thread_data[i].thread, NULL);
+ cleanup_thread();
+ return;
+ }
+#endif
+
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- struct base_data base_obj;
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
+ if (is_delta_type(obj->type))
continue;
- base_obj.obj = obj;
- base_obj.data = NULL;
- find_unresolved_deltas(&base_obj, NULL);
+ resolve_base(obj);
display_progress(progress, nr_resolved_deltas);
}
}
+/*
+ * Third pass:
+ * - append objects to convert thin pack to full pack if required
+ * - write the final 20-byte SHA-1
+ */
+static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved);
+static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1)
+{
+ if (nr_deltas == nr_resolved_deltas) {
+ stop_progress(&progress);
+ /* Flush remaining pack final 20-byte SHA1. */
+ flush();
+ return;
+ }
+
+ if (fix_thin_pack) {
+ struct sha1file *f;
+ unsigned char read_sha1[20], tail_sha1[20];
+ char msg[48];
+ int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_objects_initial = nr_objects;
+ if (nr_unresolved <= 0)
+ die(_("confusion beyond insanity"));
+ objects = xrealloc(objects,
+ (nr_objects + nr_unresolved + 1)
+ * sizeof(*objects));
+ f = sha1fd(output_fd, curr_pack);
+ fix_unresolved_deltas(f, nr_unresolved);
+ sprintf(msg, "completed with %d local objects",
+ nr_objects - nr_objects_initial);
+ stop_progress_msg(&progress, msg);
+ sha1close(f, tail_sha1, 0);
+ hashcpy(read_sha1, pack_sha1);
+ fixup_pack_header_footer(output_fd, pack_sha1,
+ curr_pack, nr_objects,
+ read_sha1, consumed_bytes-20);
+ if (hashcmp(read_sha1, tail_sha1) != 0)
+ die("Unexpected tail checksum for %s "
+ "(disk corruption?)", curr_pack);
+ }
+ if (nr_deltas != nr_resolved_deltas)
+ die(Q_("pack has %d unresolved delta",
+ "pack has %d unresolved deltas",
+ nr_deltas - nr_resolved_deltas),
+ nr_deltas - nr_resolved_deltas);
+}
+
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
{
git_zstream stream;
@@ -683,7 +1143,7 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size)
} while (status == Z_OK);
if (status != Z_STREAM_END)
- die("unable to deflate appended object (%d)", status);
+ die(_("unable to deflate appended object (%d)"), status);
size = stream.total_out;
git_deflate_end(&stream);
return size;
@@ -752,20 +1212,20 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
for (i = 0; i < n; i++) {
struct delta_entry *d = sorted_by_pos[i];
enum object_type type;
- struct base_data base_obj;
+ struct base_data *base_obj = alloc_base_data();
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
- if (!base_obj.data)
+ base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size);
+ if (!base_obj->data)
continue;
- if (check_sha1_signature(d->base.sha1, base_obj.data,
- base_obj.size, typename(type)))
- die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
- base_obj.obj = append_obj_to_pack(f, d->base.sha1,
- base_obj.data, base_obj.size, type);
- find_unresolved_deltas(&base_obj, NULL);
+ if (check_sha1_signature(d->base.sha1, base_obj->data,
+ base_obj->size, typename(type)))
+ die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1));
+ base_obj->obj = append_obj_to_pack(f, d->base.sha1,
+ base_obj->data, base_obj->size, type);
+ find_unresolved_deltas(base_obj);
display_progress(progress, nr_resolved_deltas);
}
free(sorted_by_pos);
@@ -786,7 +1246,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
fsync_or_die(output_fd, curr_pack_name);
err = close(output_fd);
if (err)
- die_errno("error while closing pack file");
+ die_errno(_("error while closing pack file"));
}
if (keep_msg) {
@@ -799,7 +1259,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
if (keep_fd < 0) {
if (errno != EEXIST)
- die_errno("cannot write keep file '%s'",
+ die_errno(_("cannot write keep file '%s'"),
keep_name);
} else {
if (keep_msg_len > 0) {
@@ -807,7 +1267,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
write_or_die(keep_fd, "\n", 1);
}
if (close(keep_fd) != 0)
- die_errno("cannot close written keep file '%s'",
+ die_errno(_("cannot close written keep file '%s'"),
keep_name);
report = "keep";
}
@@ -820,7 +1280,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
final_pack_name = name;
}
if (move_temp_to_file(curr_pack_name, final_pack_name))
- die("cannot store pack file");
+ die(_("cannot store pack file"));
} else if (from_stdin)
chmod(final_pack_name, 0444);
@@ -831,7 +1291,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
final_index_name = name;
}
if (move_temp_to_file(curr_index_name, final_index_name))
- die("cannot store index file");
+ die(_("cannot store index file"));
} else
chmod(final_index_name, 0444);
@@ -859,24 +1319,152 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
static int git_index_pack_config(const char *k, const char *v, void *cb)
{
+ struct pack_idx_option *opts = cb;
+
if (!strcmp(k, "pack.indexversion")) {
- pack_idx_default_version = git_config_int(k, v);
- if (pack_idx_default_version > 2)
- die("bad pack.indexversion=%"PRIu32,
- pack_idx_default_version);
+ opts->version = git_config_int(k, v);
+ if (opts->version > 2)
+ die("bad pack.indexversion=%"PRIu32, opts->version);
+ return 0;
+ }
+ if (!strcmp(k, "pack.threads")) {
+ nr_threads = git_config_int(k, v);
+ if (nr_threads < 0)
+ die("invalid number of threads specified (%d)",
+ nr_threads);
+#ifdef NO_PTHREADS
+ if (nr_threads != 1)
+ warning("no threads support, ignoring %s", k);
+ nr_threads = 1;
+#endif
return 0;
}
return git_default_config(k, v, cb);
}
+static int cmp_uint32(const void *a_, const void *b_)
+{
+ uint32_t a = *((uint32_t *)a_);
+ uint32_t b = *((uint32_t *)b_);
+
+ return (a < b) ? -1 : (a != b);
+}
+
+static void read_v2_anomalous_offsets(struct packed_git *p,
+ struct pack_idx_option *opts)
+{
+ const uint32_t *idx1, *idx2;
+ uint32_t i;
+
+ /* The address of the 4-byte offset table */
+ idx1 = (((const uint32_t *)p->index_data)
+ + 2 /* 8-byte header */
+ + 256 /* fan out */
+ + 5 * p->num_objects /* 20-byte SHA-1 table */
+ + p->num_objects /* CRC32 table */
+ );
+
+ /* The address of the 8-byte offset table */
+ idx2 = idx1 + p->num_objects;
+
+ for (i = 0; i < p->num_objects; i++) {
+ uint32_t off = ntohl(idx1[i]);
+ if (!(off & 0x80000000))
+ continue;
+ off = off & 0x7fffffff;
+ if (idx2[off * 2])
+ continue;
+ /*
+ * The real offset is ntohl(idx2[off * 2]) in high 4
+ * octets, and ntohl(idx2[off * 2 + 1]) in low 4
+ * octets. But idx2[off * 2] is Zero!!!
+ */
+ ALLOC_GROW(opts->anomaly, opts->anomaly_nr + 1, opts->anomaly_alloc);
+ opts->anomaly[opts->anomaly_nr++] = ntohl(idx2[off * 2 + 1]);
+ }
+
+ if (1 < opts->anomaly_nr)
+ qsort(opts->anomaly, opts->anomaly_nr, sizeof(uint32_t), cmp_uint32);
+}
+
+static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
+{
+ struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1);
+
+ if (!p)
+ die(_("Cannot open existing pack file '%s'"), pack_name);
+ if (open_pack_index(p))
+ die(_("Cannot open existing pack idx file for '%s'"), pack_name);
+
+ /* Read the attributes from the existing idx file */
+ opts->version = p->index_version;
+
+ if (opts->version == 2)
+ read_v2_anomalous_offsets(p, opts);
+
+ /*
+ * Get rid of the idx file as we do not need it anymore.
+ * NEEDSWORK: extract this bit from free_pack_by_name() in
+ * sha1_file.c, perhaps? It shouldn't matter very much as we
+ * know we haven't installed this pack (hence we never have
+ * read anything from it).
+ */
+ close_pack_index(p);
+ free(p);
+}
+
+static void show_pack_info(int stat_only)
+{
+ int i, baseobjects = nr_objects - nr_deltas;
+ unsigned long *chain_histogram = NULL;
+
+ if (deepest_delta)
+ chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long));
+
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = &objects[i];
+
+ if (is_delta_type(obj->type))
+ chain_histogram[obj->delta_depth - 1]++;
+ if (stat_only)
+ continue;
+ printf("%s %-6s %lu %lu %"PRIuMAX,
+ sha1_to_hex(obj->idx.sha1),
+ typename(obj->real_type), obj->size,
+ (unsigned long)(obj[1].idx.offset - obj->idx.offset),
+ (uintmax_t)obj->idx.offset);
+ if (is_delta_type(obj->type)) {
+ struct object_entry *bobj = &objects[obj->base_object_no];
+ printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1));
+ }
+ putchar('\n');
+ }
+
+ if (baseobjects)
+ printf_ln(Q_("non delta: %d object",
+ "non delta: %d objects",
+ baseobjects),
+ baseobjects);
+ for (i = 0; i < deepest_delta; i++) {
+ if (!chain_histogram[i])
+ continue;
+ printf_ln(Q_("chain length = %d: %lu object",
+ "chain length = %d: %lu objects",
+ chain_histogram[i]),
+ i + 1,
+ chain_histogram[i]);
+ }
+}
+
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
- int i, fix_thin_pack = 0;
+ int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0;
const char *curr_pack, *curr_index;
const char *index_name = NULL, *pack_name = NULL;
const char *keep_name = NULL, *keep_msg = NULL;
char *index_name_buf = NULL, *keep_name_buf = NULL;
struct pack_idx_entry **idx_objects;
+ struct pack_idx_option opts;
unsigned char pack_sha1[20];
if (argc == 2 && !strcmp(argv[1], "-h"))
@@ -884,9 +1472,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
read_replace_refs = 0;
- git_config(git_index_pack_config, NULL);
+ reset_pack_idx_option(&opts);
+ git_config(git_index_pack_config, &opts);
if (prefix && chdir(prefix))
- die("Cannot come back to cwd");
+ die(_("Cannot come back to cwd"));
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -898,10 +1487,30 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
fix_thin_pack = 1;
} else if (!strcmp(arg, "--strict")) {
strict = 1;
+ } else if (!strcmp(arg, "--verify")) {
+ verify = 1;
+ } else if (!strcmp(arg, "--verify-stat")) {
+ verify = 1;
+ stat = 1;
+ } else if (!strcmp(arg, "--verify-stat-only")) {
+ verify = 1;
+ stat = 1;
+ stat_only = 1;
} else if (!strcmp(arg, "--keep")) {
keep_msg = "";
} else if (!prefixcmp(arg, "--keep=")) {
keep_msg = arg + 7;
+ } else if (!prefixcmp(arg, "--threads=")) {
+ char *end;
+ nr_threads = strtoul(arg+10, &end, 0);
+ if (!arg[10] || *end || nr_threads < 0)
+ usage(index_pack_usage);
+#ifdef NO_PTHREADS
+ if (nr_threads != 1)
+ warning("no threads support, "
+ "ignoring %s", arg);
+ nr_threads = 1;
+#endif
} else if (!prefixcmp(arg, "--pack_header=")) {
struct pack_header *hdr;
char *c;
@@ -910,10 +1519,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
hdr->hdr_signature = htonl(PACK_SIGNATURE);
hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
if (*c != ',')
- die("bad %s", arg);
+ die(_("bad %s"), arg);
hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
if (*c)
- die("bad %s", arg);
+ die(_("bad %s"), arg);
input_len = sizeof(*hdr);
} else if (!strcmp(arg, "-v")) {
verbose = 1;
@@ -923,13 +1532,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
index_name = argv[++i];
} else if (!prefixcmp(arg, "--index-version=")) {
char *c;
- pack_idx_default_version = strtoul(arg + 16, &c, 10);
- if (pack_idx_default_version > 2)
- die("bad %s", arg);
+ opts.version = strtoul(arg + 16, &c, 10);
+ if (opts.version > 2)
+ die(_("bad %s"), arg);
if (*c == ',')
- pack_idx_off32_limit = strtoul(c+1, &c, 0);
- if (*c || pack_idx_off32_limit & 0x80000000)
- die("bad %s", arg);
+ opts.off32_limit = strtoul(c+1, &c, 0);
+ if (*c || opts.off32_limit & 0x80000000)
+ die(_("bad %s"), arg);
} else
usage(index_pack_usage);
continue;
@@ -943,11 +1552,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (!pack_name && !from_stdin)
usage(index_pack_usage);
if (fix_thin_pack && !from_stdin)
- die("--fix-thin cannot be used without --stdin");
+ die(_("--fix-thin cannot be used without --stdin"));
if (!index_name && pack_name) {
int len = strlen(pack_name);
if (!has_extension(pack_name, ".pack"))
- die("packfile name '%s' does not end with '.pack'",
+ die(_("packfile name '%s' does not end with '.pack'"),
pack_name);
index_name_buf = xmalloc(len);
memcpy(index_name_buf, pack_name, len - 5);
@@ -957,67 +1566,58 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (keep_msg && !keep_name && pack_name) {
int len = strlen(pack_name);
if (!has_extension(pack_name, ".pack"))
- die("packfile name '%s' does not end with '.pack'",
+ die(_("packfile name '%s' does not end with '.pack'"),
pack_name);
keep_name_buf = xmalloc(len);
memcpy(keep_name_buf, pack_name, len - 5);
strcpy(keep_name_buf + len - 5, ".keep");
keep_name = keep_name_buf;
}
+ if (verify) {
+ if (!index_name)
+ die(_("--verify with no packfile name given"));
+ read_idx_option(&opts, index_name);
+ opts.flags |= WRITE_IDX_VERIFY | WRITE_IDX_STRICT;
+ }
+ if (strict)
+ opts.flags |= WRITE_IDX_STRICT;
+
+#ifndef NO_PTHREADS
+ if (!nr_threads) {
+ nr_threads = online_cpus();
+ /* An experiment showed that more threads does not mean faster */
+ if (nr_threads > 3)
+ nr_threads = 3;
+ }
+#endif
curr_pack = open_pack_file(pack_name);
parse_pack_header();
- objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
- deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+ objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
+ deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
parse_pack_objects(pack_sha1);
- if (nr_deltas == nr_resolved_deltas) {
- stop_progress(&progress);
- /* Flush remaining pack final 20-byte SHA1. */
- flush();
- } else {
- if (fix_thin_pack) {
- struct sha1file *f;
- unsigned char read_sha1[20], tail_sha1[20];
- char msg[48];
- int nr_unresolved = nr_deltas - nr_resolved_deltas;
- int nr_objects_initial = nr_objects;
- if (nr_unresolved <= 0)
- die("confusion beyond insanity");
- objects = xrealloc(objects,
- (nr_objects + nr_unresolved + 1)
- * sizeof(*objects));
- f = sha1fd(output_fd, curr_pack);
- fix_unresolved_deltas(f, nr_unresolved);
- sprintf(msg, "completed with %d local objects",
- nr_objects - nr_objects_initial);
- stop_progress_msg(&progress, msg);
- sha1close(f, tail_sha1, 0);
- hashcpy(read_sha1, pack_sha1);
- fixup_pack_header_footer(output_fd, pack_sha1,
- curr_pack, nr_objects,
- read_sha1, consumed_bytes-20);
- if (hashcmp(read_sha1, tail_sha1) != 0)
- die("Unexpected tail checksum for %s "
- "(disk corruption?)", curr_pack);
- }
- if (nr_deltas != nr_resolved_deltas)
- die("pack has %d unresolved deltas",
- nr_deltas - nr_resolved_deltas);
- }
+ resolve_deltas();
+ conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
free(deltas);
if (strict)
check_objects();
+ if (stat)
+ show_pack_info(stat_only);
+
idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
for (i = 0; i < nr_objects; i++)
idx_objects[i] = &objects[i].idx;
- curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
+ curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1);
free(idx_objects);
- final(pack_name, curr_pack,
- index_name, curr_index,
- keep_name, keep_msg,
- pack_sha1);
+ if (!verify)
+ final(pack_name, curr_pack,
+ index_name, curr_index,
+ keep_name, keep_msg,
+ pack_sha1);
+ else
+ close(input_fd);
free(objects);
free(index_name_buf);
free(keep_name_buf);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index d07554c..0dacb8b 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -351,7 +351,7 @@ static void separate_git_dir(const char *git_dir)
else if (S_ISDIR(st.st_mode))
src = git_link;
else
- die(_("unable to handle file type %d"), st.st_mode);
+ die(_("unable to handle file type %d"), (int)st.st_mode);
if (rename(src, git_dir))
die_errno(_("unable to move %s to %s"), src, git_dir);
diff --git a/builtin/log.c b/builtin/log.c
index 5c2af59..4f1b42a 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -19,6 +19,9 @@
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
+#include "branch.h"
+#include "streaming.h"
+#include "version.h"
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
@@ -72,12 +75,12 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
static void cmd_log_init_defaults(struct rev_info *rev)
{
- rev->abbrev = DEFAULT_ABBREV;
- rev->commit_format = CMIT_FMT_DEFAULT;
if (fmt_pretty)
get_commit_format(fmt_pretty, rev);
rev->verbose_header = 1;
DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
+ rev->diffopt.stat_width = -1; /* use full terminal width */
+ rev->diffopt.stat_graph_width = -1; /* respect statGraphWidth config */
rev->abbrev_commit = default_abbrev_commit;
rev->show_root_diff = default_show_root;
rev->subject_prefix = fmt_patch_subject_prefix;
@@ -359,9 +362,6 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
git_config(git_log_config, NULL);
- 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;
@@ -385,8 +385,13 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
strbuf_release(&out);
}
-static int show_object(const unsigned char *sha1, int show_tag_object,
- struct rev_info *rev)
+static int show_blob_object(const unsigned char *sha1, struct rev_info *rev)
+{
+ fflush(stdout);
+ return stream_blob_to_fd(1, sha1, NULL, 0);
+}
+
+static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
{
unsigned long size;
enum object_type type;
@@ -396,16 +401,16 @@ static int show_object(const unsigned char *sha1, int show_tag_object,
if (!buf)
return error(_("Could not read object %s"), sha1_to_hex(sha1));
- 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;
- }
+ assert(type == OBJ_TAG);
+ 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;
+ }
if (offset < size)
fwrite(buf + offset, size - offset, 1, stdout);
@@ -446,14 +451,13 @@ int cmd_show(int argc, const char **argv, const char *prefix)
git_config(git_log_config, NULL);
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
init_pathspec(&match_all, NULL);
init_revisions(&rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;
rev.no_walk = 1;
+ rev.diffopt.stat_width = -1; /* Scale to real terminal size */
+
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
opt.tweak = show_rev_tweak_rev;
@@ -466,7 +470,7 @@ 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, NULL);
+ ret = show_blob_object(o->sha1, NULL);
break;
case OBJ_TAG: {
struct tag *t = (struct tag *)o;
@@ -477,7 +481,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
t->tag,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- ret = show_object(o->sha1, 1, &rev);
+ ret = show_tag_object(o->sha1, &rev);
rev.shown_one = 1;
if (ret)
break;
@@ -524,9 +528,6 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
git_config(git_log_config, NULL);
- 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.verbose_header = 1;
@@ -549,9 +550,6 @@ int cmd_log(int argc, const char **argv, const char *prefix)
git_config(git_log_config, NULL);
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
init_revisions(&rev, prefix);
rev.always_show_header = 1;
memset(&opt, 0, sizeof(opt));
@@ -620,7 +618,8 @@ static int git_format_config(const char *var, const char *value, void *cb)
string_list_append(&extra_cc, value);
return 0;
}
- if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
+ if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") ||
+ !strcmp(var, "color.ui")) {
return 0;
}
if (!strcmp(var, "format.numbered")) {
@@ -665,7 +664,8 @@ static FILE *realstdout = NULL;
static const char *output_directory = NULL;
static int outdir_offset;
-static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet)
+static int reopen_stdout(struct commit *commit, const char *subject,
+ struct rev_info *rev, int quiet)
{
struct strbuf filename = STRBUF_INIT;
int suffix_len = strlen(fmt_patch_suffix) + 1;
@@ -679,7 +679,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet)
strbuf_addch(&filename, '/');
}
- get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
+ get_patch_filename(commit, subject, rev->nr, fmt_patch_suffix, &filename);
if (!quiet)
fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
@@ -739,15 +739,10 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
static void gen_message_id(struct rev_info *info, char *base)
{
- const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
- const char *email_start = strrchr(committer, '<');
- const char *email_end = strrchr(committer, '>');
struct strbuf buf = STRBUF_INIT;
- if (!email_start || !email_end || email_start > email_end - 1)
- die(_("Could not extract email from committer identity."));
- strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
+ strbuf_addf(&buf, "%s.%lu.git.%s", base,
(unsigned long) time(NULL),
- (int)(email_end - email_start - 1), email_start + 1);
+ git_committer_info(IDENT_NO_NAME|IDENT_NO_DATE|IDENT_STRICT));
info->message_id = strbuf_detach(&buf, NULL);
}
@@ -757,10 +752,24 @@ static void print_signature(void)
printf("-- \n%s\n\n", signature);
}
+static void add_branch_description(struct strbuf *buf, const char *branch_name)
+{
+ struct strbuf desc = STRBUF_INIT;
+ if (!branch_name || !*branch_name)
+ return;
+ read_branch_desc(&desc, branch_name);
+ if (desc.len) {
+ strbuf_addch(buf, '\n');
+ strbuf_add(buf, desc.buf, desc.len);
+ strbuf_addch(buf, '\n');
+ }
+}
+
static void make_cover_letter(struct rev_info *rev, int use_stdout,
int numbered, int numbered_files,
struct commit *origin,
int nr, struct commit **list, struct commit *head,
+ const char *branch_name,
int quiet)
{
const char *committer;
@@ -772,7 +781,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
const char *encoding = "UTF-8";
struct diff_options opts;
int need_8bit_cte = 0;
- struct commit *commit = NULL;
struct pretty_print_context pp = {0};
if (rev->commit_format != CMIT_FMT_EMAIL)
@@ -780,31 +788,10 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
committer = git_committer_info(0);
- if (!numbered_files) {
- /*
- * We fake a commit for the cover letter so we get the filename
- * desired.
- */
- commit = xcalloc(1, sizeof(*commit));
- commit->buffer = xmalloc(400);
- snprintf(commit->buffer, 400,
- "tree 0000000000000000000000000000000000000000\n"
- "parent %s\n"
- "author %s\n"
- "committer %s\n\n"
- "cover letter\n",
- sha1_to_hex(head->object.sha1), committer, committer);
- }
-
- if (!use_stdout && reopen_stdout(commit, rev, quiet))
+ if (!use_stdout &&
+ reopen_stdout(NULL, numbered_files ? NULL : "cover-letter", rev, quiet))
return;
- if (commit) {
-
- free(commit->buffer);
- free(commit);
- }
-
log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
&need_8bit_cte);
@@ -818,6 +805,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
pp_user_info(&pp, NULL, &sb, committer, encoding);
pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
pp_remainder(&pp, &msg, &sb, 0);
+ add_branch_description(&sb, branch_name);
printf("%s\n", sb.buf);
strbuf_release(&sb);
@@ -1017,6 +1005,35 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
+static char *find_branch_name(struct rev_info *rev)
+{
+ int i, positive = -1;
+ unsigned char branch_sha1[20];
+ struct strbuf buf = STRBUF_INIT;
+ const char *branch;
+
+ for (i = 0; i < rev->cmdline.nr; i++) {
+ if (rev->cmdline.rev[i].flags & UNINTERESTING)
+ continue;
+ if (positive < 0)
+ positive = i;
+ else
+ return NULL;
+ }
+ if (positive < 0)
+ return NULL;
+ strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name);
+ branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL);
+ if (!branch ||
+ prefixcmp(branch, "refs/heads/") ||
+ hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1))
+ branch = NULL;
+ strbuf_release(&buf);
+ if (branch)
+ return xstrdup(rev->cmdline.rev[positive].name);
+ return NULL;
+}
+
int cmd_format_patch(int argc, const char **argv, const char *prefix)
{
struct commit *commit;
@@ -1038,6 +1055,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
struct strbuf buf = STRBUF_INIT;
int use_patch_format = 0;
int quiet = 0;
+ char *branch_name = NULL;
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
"use [PATCH n/m] even with a single patch",
@@ -1130,7 +1148,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (do_signoff) {
const char *committer;
const char *endpos;
- committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ committer = git_committer_info(IDENT_STRICT);
endpos = strchr(committer, '>');
if (!endpos)
die(_("bogus committer info %s"), committer);
@@ -1228,8 +1246,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
* origin" that prepares what the origin side still
* does not have.
*/
+ unsigned char sha1[20];
+ const char *ref;
+
rev.pending.objects[0].item->flags |= UNINTERESTING;
add_head_to_pending(&rev);
+ ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
+ if (ref && !prefixcmp(ref, "refs/heads/"))
+ branch_name = xstrdup(ref + strlen("refs/heads/"));
+ else
+ branch_name = xstrdup(""); /* no branch */
}
/*
* Otherwise, it is "format-patch -22 HEAD", and/or
@@ -1245,16 +1271,26 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.show_root_diff = 1;
if (cover_letter) {
- /* remember the range */
+ /*
+ * NEEDSWORK:randomly pick one positive commit to show
+ * diffstat; this is often the tip and the command
+ * happens to do the right thing in most cases, but a
+ * complex command like "--cover-letter a b c ^bottom"
+ * picks "c" and shows diffstat between bottom..c
+ * which may not match what the series represents at
+ * all and totally broken.
+ */
int i;
for (i = 0; i < rev.pending.nr; i++) {
struct object *o = rev.pending.objects[i].item;
if (!(o->flags & UNINTERESTING))
head = (struct commit *)o;
}
- /* We can't generate a cover letter without any patches */
+ /* There is nothing to show; it is not an error, though. */
if (!head)
return 0;
+ if (!branch_name)
+ branch_name = find_branch_name(&rev);
}
if (ignore_if_in_upstream) {
@@ -1305,7 +1341,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, use_stdout, numbered, numbered_files,
- origin, nr, list, head, quiet);
+ origin, nr, list, head, branch_name, quiet);
total++;
start_number--;
}
@@ -1350,8 +1386,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
}
- if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
- &rev, quiet))
+ if (!use_stdout &&
+ reopen_stdout(numbered_files ? NULL : commit, NULL, &rev, quiet))
die(_("Failed to create output files"));
shown = log_tree_commit(&rev, commit);
free(commit->buffer);
@@ -1377,6 +1413,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
fclose(stdout);
}
free(list);
+ free(branch_name);
string_list_clear(&extra_to, 0);
string_list_clear(&extra_cc, 0);
string_list_clear(&extra_hdr, 0);
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 468bb13..31b3f2d 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -200,9 +200,19 @@ static void show_ru_info(void)
}
}
+static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce)
+{
+ int dtype = ce_to_dtype(ce);
+ return path_excluded(check, ce->name, ce_namelen(ce), &dtype);
+}
+
static void show_files(struct dir_struct *dir)
{
int i;
+ struct path_exclude_check check;
+
+ if ((dir->flags & DIR_SHOW_IGNORED))
+ path_exclude_check_init(&check, dir);
/* For cached/deleted files we don't need to even do the readdir */
if (show_others || show_killed) {
@@ -215,9 +225,8 @@ static void show_files(struct dir_struct *dir)
if (show_cached | show_stage) {
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
- int dtype = ce_to_dtype(ce);
- if (dir->flags & DIR_SHOW_IGNORED &&
- !excluded(dir, ce->name, &dtype))
+ if ((dir->flags & DIR_SHOW_IGNORED) &&
+ !ce_excluded(&check, ce))
continue;
if (show_unmerged && !ce_stage(ce))
continue;
@@ -232,9 +241,8 @@ static void show_files(struct dir_struct *dir)
struct cache_entry *ce = active_cache[i];
struct stat st;
int err;
- int dtype = ce_to_dtype(ce);
- if (dir->flags & DIR_SHOW_IGNORED &&
- !excluded(dir, ce->name, &dtype))
+ if ((dir->flags & DIR_SHOW_IGNORED) &&
+ !ce_excluded(&check, ce))
continue;
if (ce->ce_flags & CE_UPDATE)
continue;
@@ -247,6 +255,9 @@ static void show_files(struct dir_struct *dir)
show_ce_entry(tag_modified, ce);
}
}
+
+ if ((dir->flags & DIR_SHOW_IGNORED))
+ path_exclude_check_clear(&check);
}
/*
@@ -276,41 +287,6 @@ static void prune_cache(const char *prefix)
active_nr = last;
}
-static const char *pathspec_prefix(const char *prefix)
-{
- const char **p, *n, *prev;
- unsigned long max;
-
- if (!pathspec) {
- max_prefix_len = prefix ? strlen(prefix) : 0;
- return prefix;
- }
-
- prev = NULL;
- max = PATH_MAX;
- for (p = pathspec; (n = *p) != NULL; p++) {
- int i, len = 0;
- for (i = 0; i < max; i++) {
- char c = n[i];
- if (prev && prev[i] != c)
- break;
- if (!c || c == '*' || c == '?')
- break;
- if (c == '/')
- len = i+1;
- }
- prev = n;
- if (len < max) {
- max = len;
- if (!max)
- break;
- }
- }
-
- max_prefix_len = max;
- return max ? xmemdupz(prev, max) : NULL;
-}
-
static void strip_trailing_slash_from_submodules(void)
{
const char **p;
@@ -580,7 +556,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
strip_trailing_slash_from_submodules();
/* Find common prefix for all pathspec's */
- max_prefix = pathspec_prefix(prefix);
+ max_prefix = common_prefix(pathspec);
+ max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
/* Treat unmatching pathspec elements as errors */
if (pathspec && error_unmatch) {
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 1022309..41c88a9 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -43,6 +43,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
struct transport *transport;
const struct ref *ref;
+ if (argc == 2 && !strcmp("-h", argv[1]))
+ usage(ls_remote_usage);
+
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index bfb32b7..eaf9e15 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -250,8 +250,17 @@ static void cleanup_subject(struct strbuf *subject)
(7 <= remove &&
memmem(subject->buf + at, remove, "PATCH", 5)))
strbuf_remove(subject, at, remove);
- else
+ else {
at += remove;
+ /*
+ * If the input had a space after the ], keep
+ * it. We don't bother with finding the end of
+ * the space, since we later normalize it
+ * anyway.
+ */
+ if (isspace(subject->buf[at]))
+ at += 1;
+ }
continue;
}
break;
diff --git a/builtin/merge-file.c b/builtin/merge-file.c
index 237abd3..6f0efef 100644
--- a/builtin/merge-file.c
+++ b/builtin/merge-file.c
@@ -63,7 +63,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
if (quiet) {
if (!freopen("/dev/null", "w", stderr))
return error("failed to redirect stderr to /dev/null: "
- "%s\n", strerror(errno));
+ "%s", strerror(errno));
}
if (prefix)
@@ -76,7 +76,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
if (read_mmfile(mmfs + i, fname))
return -1;
if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
- return error("Cannot merge binary files: %s\n",
+ return error("Cannot merge binary files: %s",
argv[i]);
}
diff --git a/builtin/merge.c b/builtin/merge.c
index 325891e..dd50a0c 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -26,6 +26,8 @@
#include "merge-recursive.h"
#include "resolve-undo.h"
#include "remote.h"
+#include "fmt-merge-msg.h"
+#include "gpg-interface.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -44,13 +46,12 @@ static const char * const builtin_merge_usage[] = {
NULL
};
-static int show_diffstat = 1, shortlog_len, squash;
+static int show_diffstat = 1, shortlog_len = -1, squash;
static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only;
+static int fast_forward_only, option_edit = -1;
static int allow_trivial = 1, have_message;
-static struct strbuf merge_msg;
-static struct commit_list *remoteheads;
-static unsigned char head[20], stash[20];
+static int overwrite_ignore = 1;
+static struct strbuf merge_msg = STRBUF_INIT;
static struct strategy **use_strategies;
static size_t use_strategies_nr, use_strategies_alloc;
static const char **xopts;
@@ -63,6 +64,7 @@ static int allow_rerere_auto;
static int abort_current_merge;
static int show_progress = -1;
static int default_to_upstream;
+static const char *sign_commit;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -190,6 +192,8 @@ static struct option builtin_merge_options[] = {
"create a single commit instead of doing a merge"),
OPT_BOOLEAN(0, "commit", &option_commit,
"perform a commit if the merge succeeds (default)"),
+ OPT_BOOL('e', "edit", &option_edit,
+ "edit message before committing"),
OPT_BOOLEAN(0, "ff", &allow_fast_forward,
"allow fast-forward (default)"),
OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
@@ -206,6 +210,9 @@ static struct option builtin_merge_options[] = {
OPT_BOOLEAN(0, "abort", &abort_current_merge,
"abort the current in-progress merge"),
OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+ { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+ "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"),
OPT_END()
};
@@ -217,7 +224,7 @@ static void drop_save(void)
unlink(git_path("MERGE_MODE"));
}
-static void save_state(void)
+static int save_state(unsigned char *stash)
{
int len;
struct child_process cp;
@@ -236,11 +243,12 @@ static void save_state(void)
if (finish_command(&cp) || len < 0)
die(_("stash failed"));
- else if (!len)
- return;
+ else if (!len) /* no changes */
+ return -1;
strbuf_setlen(&buffer, buffer.len-1);
if (get_sha1(buffer.buf, stash))
die(_("not a valid object: %s"), buffer.buf);
+ return 0;
}
static void read_empty(unsigned const char *sha1, int verbose)
@@ -278,7 +286,8 @@ static void reset_hard(unsigned const char *sha1, int verbose)
die(_("read-tree failed"));
}
-static void restore_state(void)
+static void restore_state(const unsigned char *head,
+ const unsigned char *stash)
{
struct strbuf sb = STRBUF_INIT;
const char *args[] = { "stash", "apply", NULL, NULL };
@@ -308,25 +317,25 @@ static void finish_up_to_date(const char *msg)
drop_save();
}
-static void squash_message(void)
+static void squash_message(struct commit *commit, struct commit_list *remoteheads)
{
struct rev_info rev;
- struct commit *commit;
struct strbuf out = STRBUF_INIT;
struct commit_list *j;
+ const char *filename;
int fd;
struct pretty_print_context ctx = {0};
printf(_("Squash commit -- not updating HEAD\n"));
- fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ filename = git_path("SQUASH_MSG");
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
if (fd < 0)
- die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG"));
+ die_errno(_("Could not write to '%s'"), filename);
init_revisions(&rev, NULL);
rev.ignore_merges = 1;
rev.commit_format = CMIT_FMT_MEDIUM;
- commit = lookup_commit(head);
commit->object.flags |= UNINTERESTING;
add_pending_object(&rev, &commit->object, NULL);
@@ -355,9 +364,12 @@ static void squash_message(void)
strbuf_release(&out);
}
-static void finish(const unsigned char *new_head, const char *msg)
+static void finish(struct commit *head_commit,
+ struct commit_list *remoteheads,
+ const unsigned char *new_head, const char *msg)
{
struct strbuf reflog_message = STRBUF_INIT;
+ const unsigned char *head = head_commit->object.sha1;
if (!msg)
strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
@@ -368,7 +380,7 @@ static void finish(const unsigned char *new_head, const char *msg)
getenv("GIT_REFLOG_ACTION"), msg);
}
if (squash) {
- squash_message();
+ squash_message(head_commit, remoteheads);
} else {
if (verbosity >= 0 && !merge_msg.len)
printf(_("No merge message -- not updating HEAD\n"));
@@ -387,11 +399,11 @@ static void finish(const unsigned char *new_head, const char *msg)
if (new_head && show_diffstat) {
struct diff_options opts;
diff_setup(&opts);
+ opts.stat_width = -1; /* use full terminal width */
+ opts.stat_graph_width = -1; /* respect statGraphWidth config */
opts.output_format |=
DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
opts.detect_rename = DIFF_DETECT_RENAME;
- if (diff_use_color_default > 0)
- DIFF_OPT_SET(&opts, COLOR_DIFF);
if (diff_setup_done(&opts) < 0)
die(_("diff_setup_done failed"));
diff_tree_sha1(head, new_head, "", &opts);
@@ -408,8 +420,8 @@ static void finish(const unsigned char *new_head, const char *msg)
/* Get the name for the merge commit's message. */
static void merge_name(const char *remote, struct strbuf *msg)
{
- struct object *remote_head;
- unsigned char branch_head[20], buf_sha[20];
+ struct commit *remote_head;
+ unsigned char branch_head[20];
struct strbuf buf = STRBUF_INIT;
struct strbuf bname = STRBUF_INIT;
const char *ptr;
@@ -420,7 +432,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
remote = bname.buf;
memset(branch_head, 0, sizeof(branch_head));
- remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ remote_head = get_merge_parent(remote);
if (!remote_head)
die(_("'%s' does not point to a commit"), remote);
@@ -430,6 +442,11 @@ static void merge_name(const char *remote, struct strbuf *msg)
sha1_to_hex(branch_head), remote);
goto cleanup;
}
+ if (!prefixcmp(found_ref, "refs/tags/")) {
+ strbuf_addf(msg, "%s\t\ttag '%s' of .\n",
+ sha1_to_hex(branch_head), remote);
+ goto cleanup;
+ }
if (!prefixcmp(found_ref, "refs/remotes/")) {
strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
sha1_to_hex(branch_head), remote);
@@ -468,10 +485,10 @@ static void merge_name(const char *remote, struct strbuf *msg)
strbuf_addstr(&truname, "refs/heads/");
strbuf_addstr(&truname, remote);
strbuf_setlen(&truname, truname.len - len);
- if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
+ if (ref_exists(truname.buf)) {
strbuf_addf(msg,
"%s\t\tbranch '%s'%s of .\n",
- sha1_to_hex(remote_head->sha1),
+ sha1_to_hex(remote_head->object.sha1),
truname.buf + 11,
(early ? " (early part)" : ""));
strbuf_release(&truname);
@@ -481,14 +498,16 @@ static void merge_name(const char *remote, struct strbuf *msg)
if (!strcmp(remote, "FETCH_HEAD") &&
!access(git_path("FETCH_HEAD"), R_OK)) {
+ const char *filename;
FILE *fp;
struct strbuf line = STRBUF_INIT;
char *ptr;
- fp = fopen(git_path("FETCH_HEAD"), "r");
+ filename = git_path("FETCH_HEAD");
+ fp = fopen(filename, "r");
if (!fp)
die_errno(_("could not open '%s' for reading"),
- git_path("FETCH_HEAD"));
+ filename);
strbuf_getline(&line, fp, '\n');
fclose(fp);
ptr = strstr(line.buf, "\tnot-for-merge\t");
@@ -499,7 +518,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
goto cleanup;
}
strbuf_addf(msg, "%s\t\tcommit '%s'\n",
- sha1_to_hex(remote_head->sha1), remote);
+ sha1_to_hex(remote_head->object.sha1), remote);
cleanup:
strbuf_release(&buf);
strbuf_release(&bname);
@@ -527,6 +546,8 @@ static void parse_branch_merge_options(char *bmo)
static int git_merge_config(const char *k, const char *v, void *cb)
{
+ int status;
+
if (branch && !prefixcmp(k, "branch.") &&
!prefixcmp(k + 7, branch) &&
!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
@@ -543,15 +564,7 @@ static int git_merge_config(const char *k, const char *v, void *cb)
return git_config_string(&pull_octopus, k, v);
else if (!strcmp(k, "merge.renormalize"))
option_renormalize = git_config_bool(k, v);
- else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
- int is_bool;
- shortlog_len = git_config_bool_or_int(k, v, &is_bool);
- if (!is_bool && shortlog_len < 0)
- return error(_("%s: negative length %s"), k, v);
- if (is_bool && shortlog_len)
- shortlog_len = DEFAULT_MERGE_LOG_LEN;
- return 0;
- } else if (!strcmp(k, "merge.ff")) {
+ else if (!strcmp(k, "merge.ff")) {
int boolval = git_config_maybe_bool(k, v);
if (0 <= boolval) {
allow_fast_forward = boolval;
@@ -564,6 +577,13 @@ static int git_merge_config(const char *k, const char *v, void *cb)
default_to_upstream = git_config_bool(k, v);
return 0;
}
+
+ status = fmt_merge_msg_config(k, v, cb);
+ if (status)
+ return status;
+ status = git_gpg_config(k, v, NULL);
+ if (status)
+ return status;
return git_diff_ui_config(k, v, cb);
}
@@ -663,7 +683,8 @@ int try_merge_command(const char *strategy, size_t xopts_nr,
}
static int try_merge_strategy(const char *strategy, struct commit_list *common,
- const char *head_arg)
+ struct commit_list *remoteheads,
+ struct commit *head, const char *head_arg)
{
int index_fd;
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
@@ -703,13 +724,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
o.branch1 = head_arg;
- o.branch2 = remoteheads->item->util;
+ o.branch2 = merge_remote_util(remoteheads->item)->name;
for (j = common; j; j = j->next)
commit_list_insert(j->item, &reversed);
index_fd = hold_locked_index(lock, 1);
- clean = merge_recursive(&o, lookup_commit(head),
+ clean = merge_recursive(&o, head,
remoteheads->item, reversed, &result);
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
@@ -758,10 +779,12 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote
memset(&trees, 0, sizeof(trees));
memset(&opts, 0, sizeof(opts));
memset(&t, 0, sizeof(t));
- memset(&dir, 0, sizeof(dir));
- dir.flags |= DIR_SHOW_IGNORED;
- dir.exclude_per_dir = ".gitignore";
- opts.dir = &dir;
+ if (overwrite_ignore) {
+ memset(&dir, 0, sizeof(dir));
+ dir.flags |= DIR_SHOW_IGNORED;
+ setup_standard_excludes(&dir);
+ opts.dir = &dir;
+ }
opts.head_idx = 1;
opts.src_index = &the_index;
@@ -834,77 +857,110 @@ static void add_strategies(const char *string, unsigned attr)
}
-static void write_merge_msg(void)
+static void write_merge_msg(struct strbuf *msg)
{
- int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ const char *filename = git_path("MERGE_MSG");
+ int fd = open(filename, O_WRONLY | O_CREAT, 0666);
if (fd < 0)
die_errno(_("Could not open '%s' for writing"),
- git_path("MERGE_MSG"));
- if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
- die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
+ filename);
+ if (write_in_full(fd, msg->buf, msg->len) != msg->len)
+ die_errno(_("Could not write to '%s'"), filename);
close(fd);
}
-static void read_merge_msg(void)
+static void read_merge_msg(struct strbuf *msg)
+{
+ const char *filename = git_path("MERGE_MSG");
+ strbuf_reset(msg);
+ if (strbuf_read_file(msg, filename, 0) < 0)
+ die_errno(_("Could not read from '%s'"), filename);
+}
+
+static void write_merge_state(struct commit_list *);
+static void abort_commit(struct commit_list *remoteheads, const char *err_msg)
{
- strbuf_reset(&merge_msg);
- if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
- die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
+ if (err_msg)
+ error("%s", err_msg);
+ fprintf(stderr,
+ _("Not committing merge; use 'git commit' to complete the merge.\n"));
+ write_merge_state(remoteheads);
+ exit(1);
}
-static void run_prepare_commit_msg(void)
+static const char merge_editor_comment[] =
+N_("Please enter a commit message to explain why this merge is necessary,\n"
+ "especially if it merges an updated upstream into a topic branch.\n"
+ "\n"
+ "Lines starting with '#' will be ignored, and an empty message aborts\n"
+ "the commit.\n");
+
+static void prepare_to_commit(struct commit_list *remoteheads)
{
- write_merge_msg();
+ struct strbuf msg = STRBUF_INIT;
+ const char *comment = _(merge_editor_comment);
+ strbuf_addbuf(&msg, &merge_msg);
+ strbuf_addch(&msg, '\n');
+ if (0 < option_edit)
+ strbuf_add_lines(&msg, "# ", comment, strlen(comment));
+ write_merge_msg(&msg);
run_hook(get_index_file(), "prepare-commit-msg",
git_path("MERGE_MSG"), "merge", NULL, NULL);
- read_merge_msg();
+ if (0 < option_edit) {
+ if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
+ abort_commit(remoteheads, NULL);
+ }
+ read_merge_msg(&msg);
+ stripspace(&msg, 0 < option_edit);
+ if (!msg.len)
+ abort_commit(remoteheads, _("Empty commit message."));
+ strbuf_release(&merge_msg);
+ strbuf_addbuf(&merge_msg, &msg);
+ strbuf_release(&msg);
}
-static int merge_trivial(void)
+static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
{
unsigned char result_tree[20], result_commit[20];
struct commit_list *parent = xmalloc(sizeof(*parent));
write_tree_trivial(result_tree);
printf(_("Wonderful.\n"));
- parent->item = lookup_commit(head);
+ parent->item = head;
parent->next = xmalloc(sizeof(*parent->next));
parent->next->item = remoteheads->item;
parent->next->next = NULL;
- run_prepare_commit_msg();
- commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
- finish(result_commit, "In-index merge");
+ prepare_to_commit(remoteheads);
+ if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
+ sign_commit))
+ die(_("failed to write commit object"));
+ finish(head, remoteheads, result_commit, "In-index merge");
drop_save();
return 0;
}
-static int finish_automerge(struct commit_list *common,
+static int finish_automerge(struct commit *head,
+ int head_subsumed,
+ struct commit_list *common,
+ struct commit_list *remoteheads,
unsigned char *result_tree,
const char *wt_strategy)
{
- struct commit_list *parents = NULL, *j;
+ struct commit_list *parents = NULL;
struct strbuf buf = STRBUF_INIT;
unsigned char result_commit[20];
free_commit_list(common);
- if (allow_fast_forward) {
- parents = remoteheads;
- commit_list_insert(lookup_commit(head), &parents);
- parents = reduce_heads(parents);
- } else {
- struct commit_list **pptr = &parents;
-
- pptr = &commit_list_insert(lookup_commit(head),
- pptr)->next;
- for (j = remoteheads; j; j = j->next)
- pptr = &commit_list_insert(j->item, pptr)->next;
- }
- free_commit_list(remoteheads);
+ parents = remoteheads;
+ if (!head_subsumed || !allow_fast_forward)
+ commit_list_insert(head, &parents);
strbuf_addch(&merge_msg, '\n');
- run_prepare_commit_msg();
- commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
- strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
- finish(result_commit, buf.buf);
+ prepare_to_commit(remoteheads);
+ if (commit_tree(&merge_msg, result_tree, parents, result_commit,
+ NULL, sign_commit))
+ die(_("failed to write commit object"));
+ strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
+ finish(head, remoteheads, result_commit, buf.buf);
strbuf_release(&buf);
drop_save();
return 0;
@@ -912,13 +968,14 @@ static int finish_automerge(struct commit_list *common,
static int suggest_conflicts(int renormalizing)
{
+ const char *filename;
FILE *fp;
int pos;
- fp = fopen(git_path("MERGE_MSG"), "a");
+ filename = git_path("MERGE_MSG");
+ fp = fopen(filename, "a");
if (!fp)
- die_errno(_("Could not open '%s' for writing"),
- git_path("MERGE_MSG"));
+ die_errno(_("Could not open '%s' for writing"), filename);
fprintf(fp, "\nConflicts:\n");
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
@@ -938,7 +995,8 @@ static int suggest_conflicts(int renormalizing)
return 1;
}
-static struct commit *is_old_style_invocation(int argc, const char **argv)
+static struct commit *is_old_style_invocation(int argc, const char **argv,
+ const unsigned char *head)
{
struct commit *second_token = NULL;
if (argc > 2) {
@@ -1007,16 +1065,119 @@ static int setup_with_upstream(const char ***argv)
return i;
}
+static void write_merge_state(struct commit_list *remoteheads)
+{
+ const char *filename;
+ int fd;
+ struct commit_list *j;
+ struct strbuf buf = STRBUF_INIT;
+
+ for (j = remoteheads; j; j = j->next) {
+ unsigned const char *sha1;
+ struct commit *c = j->item;
+ if (c->util && merge_remote_util(c)->obj) {
+ sha1 = merge_remote_util(c)->obj->sha1;
+ } else {
+ sha1 = c->object.sha1;
+ }
+ strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+ }
+ filename = git_path("MERGE_HEAD");
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die_errno(_("Could not write to '%s'"), filename);
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ write_merge_msg(&merge_msg);
+
+ filename = git_path("MERGE_MODE");
+ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ strbuf_reset(&buf);
+ if (!allow_fast_forward)
+ strbuf_addf(&buf, "no-ff");
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die_errno(_("Could not write to '%s'"), filename);
+ close(fd);
+}
+
+static int default_edit_option(void)
+{
+ static const char name[] = "GIT_MERGE_AUTOEDIT";
+ const char *e = getenv(name);
+ struct stat st_stdin, st_stdout;
+
+ if (have_message)
+ /* an explicit -m msg without --[no-]edit */
+ return 0;
+
+ if (e) {
+ int v = git_config_maybe_bool(name, e);
+ if (v < 0)
+ die("Bad value '%s' in environment '%s'", e, name);
+ return v;
+ }
+
+ /* Use editor if stdin and stdout are the same and is a tty */
+ return (!fstat(0, &st_stdin) &&
+ !fstat(1, &st_stdout) &&
+ isatty(0) && isatty(1) &&
+ st_stdin.st_dev == st_stdout.st_dev &&
+ st_stdin.st_ino == st_stdout.st_ino &&
+ st_stdin.st_mode == st_stdout.st_mode);
+}
+
+static struct commit_list *collect_parents(struct commit *head_commit,
+ int *head_subsumed,
+ int argc, const char **argv)
+{
+ int i;
+ struct commit_list *remoteheads = NULL, *parents, *next;
+ struct commit_list **remotes = &remoteheads;
+
+ if (head_commit)
+ remotes = &commit_list_insert(head_commit, remotes)->next;
+ for (i = 0; i < argc; i++) {
+ struct commit *commit = get_merge_parent(argv[i]);
+ if (!commit)
+ die(_("%s - not something we can merge"), argv[i]);
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+ *remotes = NULL;
+
+ parents = reduce_heads(remoteheads);
+
+ *head_subsumed = 1; /* we will flip this to 0 when we find it */
+ for (remoteheads = NULL, remotes = &remoteheads;
+ parents;
+ parents = next) {
+ struct commit *commit = parents->item;
+ next = parents->next;
+ if (commit == head_commit)
+ *head_subsumed = 0;
+ else
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+ return remoteheads;
+}
+
int cmd_merge(int argc, const char **argv, const char *prefix)
{
unsigned char result_tree[20];
+ unsigned char stash[20];
+ unsigned char head_sha1[20];
+ struct commit *head_commit;
struct strbuf buf = STRBUF_INIT;
const char *head_arg;
- int flag, head_invalid = 0, i;
+ int flag, i, ret = 0, head_subsumed;
int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
struct commit_list *common = NULL;
const char *best_strategy = NULL, *wt_strategy = NULL;
- struct commit_list **remotes = &remoteheads;
+ struct commit_list *remoteheads, *p;
+ void *branch_to_free;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_merge_usage, builtin_merge_options);
@@ -1025,22 +1186,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
*/
- branch = resolve_ref("HEAD", head, 0, &flag);
+ branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
if (branch && !prefixcmp(branch, "refs/heads/"))
branch += 11;
- if (is_null_sha1(head))
- head_invalid = 1;
+ if (!branch || is_null_sha1(head_sha1))
+ head_commit = NULL;
+ else
+ head_commit = lookup_commit_or_die(head_sha1, "HEAD");
git_config(git_merge_config, NULL);
- /* for color.ui */
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
if (branch_mergeoptions)
parse_branch_merge_options(branch_mergeoptions);
argc = parse_options(argc, argv, prefix, builtin_merge_options,
builtin_merge_usage, 0);
+ if (shortlog_len < 0)
+ shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
if (verbosity < 0 && show_progress == -1)
show_progress = 0;
@@ -1053,7 +1214,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die(_("There is no merge to abort (MERGE_HEAD missing)."));
/* Invoke 'git reset --merge' */
- return cmd_reset(nargc, nargv, prefix);
+ ret = cmd_reset(nargc, nargv, prefix);
+ goto done;
}
if (read_cache_unmerged())
@@ -1092,9 +1254,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die(_("You cannot combine --no-ff with --ff-only."));
if (!abort_current_merge) {
- if (!argc && default_to_upstream)
- argc = setup_with_upstream(&argv);
- else if (argc == 1 && !strcmp(argv[0], "-"))
+ if (!argc) {
+ if (default_to_upstream)
+ argc = setup_with_upstream(&argv);
+ else
+ die(_("No commit specified and merge.defaultToUpstream not set."));
+ } else if (argc == 1 && !strcmp(argv[0], "-"))
argv[0] = "@{-1}";
}
if (!argc)
@@ -1110,13 +1275,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* additional safety measure to check for it.
*/
- if (!have_message && is_old_style_invocation(argc, argv)) {
+ if (!have_message && head_commit &&
+ is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
strbuf_addstr(&merge_msg, argv[0]);
head_arg = argv[1];
argv += 2;
argc -= 2;
- } else if (head_invalid) {
- struct object *remote_head;
+ remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+ } else if (!head_commit) {
+ struct commit *remote_head;
/*
* If the merged head is a valid one there is no reason
* to forbid "git merge" into a branch yet to be born.
@@ -1130,13 +1297,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (!allow_fast_forward)
die(_("Non-fast-forward commit does not make sense into "
"an empty head"));
- remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+ remote_head = remoteheads->item;
if (!remote_head)
die(_("%s - not something we can merge"), argv[0]);
- read_empty(remote_head->sha1, 0);
- update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
- DIE_ON_ERR);
- return 0;
+ read_empty(remote_head->object.sha1, 0);
+ update_ref("initial pull", "HEAD", remote_head->object.sha1,
+ NULL, 0, DIE_ON_ERR);
+ goto done;
} else {
struct strbuf merge_names = STRBUF_INIT;
@@ -1144,52 +1312,56 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
head_arg = "HEAD";
/*
- * All the rest are the commits being merged;
- * prepare the standard merge summary message to
- * be appended to the given message. If remote
- * is invalid we will die later in the common
- * codepath so we discard the error in this
- * loop.
+ * All the rest are the commits being merged; prepare
+ * the standard merge summary message to be appended
+ * to the given message.
*/
- for (i = 0; i < argc; i++)
- merge_name(argv[i], &merge_names);
+ remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+ for (p = remoteheads; p; p = p->next)
+ merge_name(merge_remote_util(p->item)->name, &merge_names);
if (!have_message || shortlog_len) {
- fmt_merge_msg(&merge_names, &merge_msg, !have_message,
- shortlog_len);
+ struct fmt_merge_msg_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.add_title = !have_message;
+ opts.shortlog_len = shortlog_len;
+
+ fmt_merge_msg(&merge_names, &merge_msg, &opts);
if (merge_msg.len)
strbuf_setlen(&merge_msg, merge_msg.len - 1);
}
}
- if (head_invalid || !argc)
+ if (!head_commit || !argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
strbuf_addstr(&buf, "merge");
- for (i = 0; i < argc; i++)
- strbuf_addf(&buf, " %s", argv[i]);
+ for (p = remoteheads; p; p = p->next)
+ strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name);
setenv("GIT_REFLOG_ACTION", buf.buf, 0);
strbuf_reset(&buf);
- for (i = 0; i < argc; i++) {
- struct object *o;
- struct commit *commit;
-
- o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
- if (!o)
- die(_("%s - not something we can merge"), argv[i]);
- commit = lookup_commit(o->sha1);
- commit->util = (void *)argv[i];
- remotes = &commit_list_insert(commit, remotes)->next;
-
- strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
- setenv(buf.buf, argv[i], 1);
+ for (p = remoteheads; p; p = p->next) {
+ struct commit *commit = p->item;
+ strbuf_addf(&buf, "GITHEAD_%s",
+ sha1_to_hex(commit->object.sha1));
+ setenv(buf.buf, merge_remote_util(commit)->name, 1);
strbuf_reset(&buf);
+ if (!fast_forward_only &&
+ merge_remote_util(commit) &&
+ merge_remote_util(commit)->obj &&
+ merge_remote_util(commit)->obj->type == OBJ_TAG)
+ allow_fast_forward = 0;
}
+ if (option_edit < 0)
+ option_edit = default_edit_option();
+
if (!use_strategies) {
- if (!remoteheads->next)
+ if (!remoteheads)
+ ; /* already up-to-date */
+ else if (!remoteheads->next)
add_strategies(pull_twohead, DEFAULT_TWOHEAD);
else
add_strategies(pull_octopus, DEFAULT_OCTOPUS);
@@ -1202,38 +1374,40 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
allow_trivial = 0;
}
- if (!remoteheads->next)
- common = get_merge_bases(lookup_commit(head),
- remoteheads->item, 1);
+ if (!remoteheads)
+ ; /* already up-to-date */
+ else if (!remoteheads->next)
+ common = get_merge_bases(head_commit, remoteheads->item, 1);
else {
struct commit_list *list = remoteheads;
- commit_list_insert(lookup_commit(head), &list);
+ commit_list_insert(head_commit, &list);
common = get_octopus_merge_bases(list);
free(list);
}
- update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
- DIE_ON_ERR);
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
+ NULL, 0, DIE_ON_ERR);
- if (!common)
+ if (remoteheads && !common)
; /* No common ancestors found. We need a real merge. */
- else if (!remoteheads->next && !common->next &&
- common->item == remoteheads->item) {
+ else if (!remoteheads ||
+ (!remoteheads->next && !common->next &&
+ common->item == remoteheads->item)) {
/*
* If head can reach all the merge then we are up to date.
* but first the most common case of merging one remote.
*/
finish_up_to_date("Already up-to-date.");
- return 0;
+ goto done;
} else if (allow_fast_forward && !remoteheads->next &&
!common->next &&
- !hashcmp(common->item->object.sha1, head)) {
+ !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
/* Again the most common case of merging one remote. */
struct strbuf msg = STRBUF_INIT;
- struct object *o;
+ struct commit *commit;
char hex[41];
- strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+ strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
if (verbosity >= 0)
printf(_("Updating %s..%s\n"),
@@ -1244,17 +1418,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (have_message)
strbuf_addstr(&msg,
" (no commit created; -m option ignored)");
- o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
- 0, NULL, OBJ_COMMIT);
- if (!o)
- return 1;
+ commit = remoteheads->item;
+ if (!commit) {
+ ret = 1;
+ goto done;
+ }
- if (checkout_fast_forward(head, remoteheads->item->object.sha1))
- return 1;
+ if (checkout_fast_forward(head_commit->object.sha1,
+ commit->object.sha1)) {
+ ret = 1;
+ goto done;
+ }
- finish(o->sha1, msg.buf);
+ finish(head_commit, remoteheads, commit->object.sha1, msg.buf);
drop_save();
- return 0;
+ goto done;
} else if (!remoteheads->next && common->next)
;
/*
@@ -1269,11 +1447,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
refresh_cache(REFRESH_QUIET);
if (allow_trivial && !fast_forward_only) {
/* See if it is really trivial. */
- git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ git_committer_info(IDENT_STRICT);
printf(_("Trying really trivial in-index merge...\n"));
if (!read_tree_trivial(common->item->object.sha1,
- head, remoteheads->item->object.sha1))
- return merge_trivial();
+ head_commit->object.sha1,
+ remoteheads->item->object.sha1)) {
+ ret = merge_trivial(head_commit, remoteheads);
+ goto done;
+ }
printf(_("Nope.\n"));
}
} else {
@@ -1292,8 +1473,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* merge_bases again, otherwise "git merge HEAD^
* HEAD^^" would be missed.
*/
- common_one = get_merge_bases(lookup_commit(head),
- j->item, 1);
+ common_one = get_merge_bases(head_commit, j->item, 1);
if (hashcmp(common_one->item->object.sha1,
j->item->object.sha1)) {
up_to_date = 0;
@@ -1302,7 +1482,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
if (up_to_date) {
finish_up_to_date("Already up-to-date. Yeeah!");
- return 0;
+ goto done;
}
}
@@ -1310,7 +1490,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die(_("Not possible to fast-forward, aborting."));
/* We are going to make a new commit. */
- git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ git_committer_info(IDENT_STRICT);
/*
* At this point, we need a real merge. No matter what strategy
@@ -1320,21 +1500,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* sync with the head commit. The strategies are responsible
* to ensure this.
*/
- if (use_strategies_nr != 1) {
- /*
- * Stash away the local changes so that we can try more
- * than one.
- */
- save_state();
- } else {
- memcpy(stash, null_sha1, 20);
- }
+ if (use_strategies_nr == 1 ||
+ /*
+ * Stash away the local changes so that we can try more than one.
+ */
+ save_state(stash))
+ hashcpy(stash, null_sha1);
for (i = 0; i < use_strategies_nr; i++) {
int ret;
if (i) {
printf(_("Rewinding the tree to pristine...\n"));
- restore_state();
+ restore_state(head_commit->object.sha1, stash);
}
if (use_strategies_nr != 1)
printf(_("Trying merge strategy %s...\n"),
@@ -1346,7 +1523,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
wt_strategy = use_strategies[i]->name;
ret = try_merge_strategy(use_strategies[i]->name,
- common, head_arg);
+ common, remoteheads,
+ head_commit, head_arg);
if (!option_commit && !ret) {
merge_was_ok = 1;
/*
@@ -1387,66 +1565,50 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* If we have a resulting tree, that means the strategy module
* auto resolved the merge cleanly.
*/
- if (automerge_was_ok)
- return finish_automerge(common, result_tree, wt_strategy);
+ if (automerge_was_ok) {
+ ret = finish_automerge(head_commit, head_subsumed,
+ common, remoteheads,
+ result_tree, wt_strategy);
+ goto done;
+ }
/*
* Pick the result from the best strategy and have the user fix
* it up.
*/
if (!best_strategy) {
- restore_state();
+ restore_state(head_commit->object.sha1, stash);
if (use_strategies_nr > 1)
fprintf(stderr,
_("No merge strategy handled the merge.\n"));
else
fprintf(stderr, _("Merge with strategy %s failed.\n"),
use_strategies[0]->name);
- return 2;
+ ret = 2;
+ goto done;
} else if (best_strategy == wt_strategy)
; /* We already have its result in the working tree. */
else {
printf(_("Rewinding the tree to pristine...\n"));
- restore_state();
+ restore_state(head_commit->object.sha1, stash);
printf(_("Using the %s to prepare resolving by hand.\n"),
best_strategy);
- try_merge_strategy(best_strategy, common, head_arg);
+ try_merge_strategy(best_strategy, common, remoteheads,
+ head_commit, head_arg);
}
if (squash)
- finish(NULL, NULL);
- else {
- int fd;
- struct commit_list *j;
-
- for (j = remoteheads; j; j = j->next)
- strbuf_addf(&buf, "%s\n",
- sha1_to_hex(j->item->object.sha1));
- fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno(_("Could not open '%s' for writing"),
- git_path("MERGE_HEAD"));
- if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
- close(fd);
- strbuf_addch(&merge_msg, '\n');
- write_merge_msg();
- fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
- if (fd < 0)
- die_errno(_("Could not open '%s' for writing"),
- git_path("MERGE_MODE"));
- strbuf_reset(&buf);
- if (!allow_fast_forward)
- strbuf_addf(&buf, "no-ff");
- if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
- close(fd);
- }
+ finish(head_commit, remoteheads, NULL, NULL);
+ else
+ write_merge_state(remoteheads);
- if (merge_was_ok) {
+ if (merge_was_ok)
fprintf(stderr, _("Automatic merge went well; "
"stopped before committing as requested\n"));
- return 0;
- } else
- return suggest_conflicts(option_renormalize);
+ else
+ ret = suggest_conflicts(option_renormalize);
+
+done:
+ free(branch_to_free);
+ return ret;
}
diff --git a/builtin/mktree.c b/builtin/mktree.c
index 098395f..4ae1c41 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -60,6 +60,7 @@ static void write_tree(unsigned char *sha1)
}
write_sha1_file(buf.buf, buf.len, tree_type, sha1);
+ strbuf_release(&buf);
}
static const char *mktree_usage[] = {
diff --git a/builtin/mv.c b/builtin/mv.c
index 40f33ca..2a144b0 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -29,7 +29,11 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
to_copy--;
if (to_copy != length || base_name) {
char *it = xmemdupz(result[i], to_copy);
- result[i] = base_name ? strdup(basename(it)) : it;
+ if (base_name) {
+ result[i] = xstrdup(basename(it));
+ free(it);
+ } else
+ result[i] = it;
}
}
return get_pathspec(prefix, result);
@@ -55,6 +59,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
int i, newfd;
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT__DRY_RUN(&show_only, "dry run"),
OPT__FORCE(&force, "force move/rename even if target exists"),
OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
@@ -89,7 +94,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
destination = copy_pathspec(dest_path[0], argv, argc, 1);
} else {
if (argc != 1)
- usage_with_options(builtin_mv_usage, builtin_mv_options);
+ die("destination '%s' is not a directory", dest_path[0]);
destination = dest_path;
}
@@ -172,7 +177,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
* check both source and destination
*/
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
- warning(_("%s; will overwrite!"), bad);
+ if (verbose)
+ warning(_("overwriting '%s'"), dst);
bad = NULL;
} else
bad = _("Cannot overwrite");
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index e8862b5..1b37458 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -172,7 +172,9 @@ static void show_name(const struct object *obj,
}
static char const * const name_rev_usage[] = {
- "git name-rev [options] ( --all | --stdin | <commit>... )",
+ "git name-rev [options] <commit>...",
+ "git name-rev [options] --all",
+ "git name-rev [options] --stdin",
NULL
};
diff --git a/builtin/notes.c b/builtin/notes.c
index f8e437d..3644d14 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -301,12 +301,12 @@ void commit_notes(struct notes_tree *t, const char *msg)
return; /* don't have to commit an unchanged tree */
/* Prepare commit message and reflog message */
- strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
strbuf_addstr(&buf, msg);
if (buf.buf[buf.len - 1] != '\n')
strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
- create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
+ create_notes_commit(t, NULL, &buf, commit_sha1);
+ strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
strbuf_release(&buf);
@@ -804,6 +804,8 @@ static int merge_commit(struct notes_merge_options *o)
struct notes_tree *t;
struct commit *partial;
struct pretty_print_context pretty_ctx;
+ void *local_ref_to_free;
+ int ret;
/*
* Read partial merge result from .git/NOTES_MERGE_PARTIAL,
@@ -825,7 +827,8 @@ static int merge_commit(struct notes_merge_options *o)
t = xcalloc(1, sizeof(struct notes_tree));
init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
- o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL);
+ o->local_ref = local_ref_to_free =
+ resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL);
if (!o->local_ref)
die("Failed to resolve NOTES_MERGE_REF");
@@ -843,7 +846,9 @@ static int merge_commit(struct notes_merge_options *o)
free_notes(t);
strbuf_release(&msg);
- return merge_abort(o);
+ ret = merge_abort(o);
+ free(local_ref_to_free);
+ return ret;
}
static int merge(int argc, const char **argv, const char *prefix)
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 8bfe3a6..f334820 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -16,18 +16,14 @@
#include "list-objects.h"
#include "progress.h"
#include "refs.h"
+#include "streaming.h"
#include "thread-utils.h"
-static const char pack_usage[] =
- "git pack-objects [ -q | --progress | --all-progress ]\n"
- " [--all-progress-implied]\n"
- " [--max-pack-size=<n>] [--local] [--incremental]\n"
- " [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n"
- " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
- " [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n"
- " [--reflog] [--stdout | base-name] [--include-tag]\n"
- " [--keep-unreachable | --unpack-unreachable]\n"
- " [< ref-list | < object-list]";
+static const char *pack_usage[] = {
+ "git pack-objects --stdout [options...] [< ref-list | < object-list]",
+ "git pack-objects [options...] base-name [< ref-list | < object-list]",
+ NULL
+};
struct object_entry {
struct pack_idx_entry idx;
@@ -51,6 +47,8 @@ struct object_entry {
* objects against.
*/
unsigned char no_try_delta;
+ unsigned char tagged; /* near the very tip of refs */
+ unsigned char filled; /* assigned write-order */
};
/*
@@ -66,14 +64,16 @@ static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
static int non_empty;
static int reuse_delta = 1, reuse_object = 1;
static int keep_unreachable, unpack_unreachable, include_tag;
+static unsigned long unpack_unreachable_expiration;
static int local;
static int incremental;
static int ignore_packed_keep;
static int allow_ofs_delta;
+static struct pack_idx_option pack_idx_opts;
static const char *base_name;
static int progress = 1;
static int window = 10;
-static unsigned long pack_size_limit, pack_size_limit_cfg;
+static unsigned long pack_size_limit;
static int depth = 50;
static int delta_search_threads;
static int pack_to_stdout;
@@ -95,6 +95,7 @@ static unsigned long window_memory_limit = 0;
*/
static int *object_ix;
static int object_ix_hashsz;
+static struct object_entry *locate_object_entry(const unsigned char *sha1);
/*
* stats
@@ -150,6 +151,46 @@ static unsigned long do_compress(void **pptr, unsigned long size)
return stream.total_out;
}
+static unsigned long write_large_blob_data(struct git_istream *st, struct sha1file *f,
+ const unsigned char *sha1)
+{
+ git_zstream stream;
+ unsigned char ibuf[1024 * 16];
+ unsigned char obuf[1024 * 16];
+ unsigned long olen = 0;
+
+ memset(&stream, 0, sizeof(stream));
+ git_deflate_init(&stream, pack_compression_level);
+
+ for (;;) {
+ ssize_t readlen;
+ int zret = Z_OK;
+ readlen = read_istream(st, ibuf, sizeof(ibuf));
+ if (readlen == -1)
+ die(_("unable to read %s"), sha1_to_hex(sha1));
+
+ stream.next_in = ibuf;
+ stream.avail_in = readlen;
+ while ((stream.avail_in || readlen == 0) &&
+ (zret == Z_OK || zret == Z_BUF_ERROR)) {
+ stream.next_out = obuf;
+ stream.avail_out = sizeof(obuf);
+ zret = git_deflate(&stream, readlen ? 0 : Z_FINISH);
+ sha1write(f, obuf, stream.next_out - obuf);
+ olen += stream.next_out - obuf;
+ }
+ if (stream.avail_in)
+ die(_("deflate error (%d)"), zret);
+ if (readlen == 0) {
+ if (zret != Z_STREAM_END)
+ die(_("deflate error (%d)"), zret);
+ break;
+ }
+ }
+ git_deflate_end(&stream);
+ return olen;
+}
+
/*
* we are going to reuse the existing object data as is. make
* sure it is not corrupt.
@@ -199,22 +240,199 @@ static void copy_pack_data(struct sha1file *f,
}
}
-static unsigned long write_object(struct sha1file *f,
- struct object_entry *entry,
- off_t write_offset)
+/* Return 0 if we will bust the pack-size limit */
+static unsigned long write_no_reuse_object(struct sha1file *f, struct object_entry *entry,
+ unsigned long limit, int usable_delta)
{
- unsigned long size, limit, datalen;
- void *buf;
+ unsigned long size, datalen;
unsigned char header[10], dheader[10];
unsigned hdrlen;
enum object_type type;
+ void *buf;
+ struct git_istream *st = NULL;
+
+ if (!usable_delta) {
+ if (entry->type == OBJ_BLOB &&
+ entry->size > big_file_threshold &&
+ (st = open_istream(entry->idx.sha1, &type, &size, NULL)) != NULL)
+ buf = NULL;
+ else {
+ buf = read_sha1_file(entry->idx.sha1, &type, &size);
+ if (!buf)
+ die(_("unable to read %s"), sha1_to_hex(entry->idx.sha1));
+ }
+ /*
+ * make sure no cached delta data remains from a
+ * previous attempt before a pack split occurred.
+ */
+ free(entry->delta_data);
+ entry->delta_data = NULL;
+ entry->z_delta_size = 0;
+ } else if (entry->delta_data) {
+ size = entry->delta_size;
+ buf = entry->delta_data;
+ entry->delta_data = NULL;
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ } else {
+ buf = get_delta(entry);
+ size = entry->delta_size;
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ }
+
+ if (st) /* large blob case, just assume we don't compress well */
+ datalen = size;
+ else if (entry->z_delta_size)
+ datalen = entry->z_delta_size;
+ else
+ datalen = do_compress(&buf, size);
+
+ /*
+ * The object header is a byte of 'type' followed by zero or
+ * more bytes of length.
+ */
+ hdrlen = encode_in_pack_object_header(type, size, header);
+
+ if (type == OBJ_OFS_DELTA) {
+ /*
+ * Deltas with relative base contain an additional
+ * encoding of the relative offset for the delta
+ * base from this object's position in the pack.
+ */
+ off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+ unsigned pos = sizeof(dheader) - 1;
+ dheader[pos] = ofs & 127;
+ while (ofs >>= 7)
+ dheader[--pos] = 128 | (--ofs & 127);
+ if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+ if (st)
+ close_istream(st);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ } else if (type == OBJ_REF_DELTA) {
+ /*
+ * Deltas with a base reference contain
+ * an additional 20 bytes for the base sha1.
+ */
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ if (st)
+ close_istream(st);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
+ hdrlen += 20;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit) {
+ if (st)
+ close_istream(st);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ }
+ if (st) {
+ datalen = write_large_blob_data(st, f, entry->idx.sha1);
+ close_istream(st);
+ } else {
+ sha1write(f, buf, datalen);
+ free(buf);
+ }
+
+ return hdrlen + datalen;
+}
+
+/* Return 0 if we will bust the pack-size limit */
+static unsigned long write_reuse_object(struct sha1file *f, struct object_entry *entry,
+ unsigned long limit, int usable_delta)
+{
+ struct packed_git *p = entry->in_pack;
+ struct pack_window *w_curs = NULL;
+ struct revindex_entry *revidx;
+ off_t offset;
+ enum object_type type = entry->type;
+ unsigned long datalen;
+ unsigned char header[10], dheader[10];
+ unsigned hdrlen;
+
+ if (entry->delta)
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ hdrlen = encode_in_pack_object_header(type, entry->size, header);
+
+ offset = entry->in_pack_offset;
+ revidx = find_pack_revindex(p, offset);
+ datalen = revidx[1].offset - offset;
+ if (!pack_to_stdout && p->index_version > 1 &&
+ check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
+ error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
+ unuse_pack(&w_curs);
+ return write_no_reuse_object(f, entry, limit, usable_delta);
+ }
+
+ offset += entry->in_pack_header_size;
+ datalen -= entry->in_pack_header_size;
+
+ if (!pack_to_stdout && p->index_version == 1 &&
+ check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
+ error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
+ unuse_pack(&w_curs);
+ return write_no_reuse_object(f, entry, limit, usable_delta);
+ }
+
+ if (type == OBJ_OFS_DELTA) {
+ off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+ unsigned pos = sizeof(dheader) - 1;
+ dheader[pos] = ofs & 127;
+ while (ofs >>= 7)
+ dheader[--pos] = 128 | (--ofs & 127);
+ if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ reused_delta++;
+ } else if (type == OBJ_REF_DELTA) {
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
+ hdrlen += 20;
+ reused_delta++;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ }
+ copy_pack_data(f, p, &w_curs, offset, datalen);
+ unuse_pack(&w_curs);
+ reused++;
+ return hdrlen + datalen;
+}
+
+/* Return 0 if we will bust the pack-size limit */
+static unsigned long write_object(struct sha1file *f,
+ struct object_entry *entry,
+ off_t write_offset)
+{
+ unsigned long limit, len;
int usable_delta, to_reuse;
if (!pack_to_stdout)
crc32_begin(f);
- type = entry->type;
-
/* apply size limit if limited packsize and not first object */
if (!pack_size_limit || !nr_written)
limit = 0;
@@ -242,11 +460,11 @@ static unsigned long write_object(struct sha1file *f,
to_reuse = 0; /* explicit */
else if (!entry->in_pack)
to_reuse = 0; /* can't reuse what we don't have */
- else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
+ else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA)
/* check_object() decided it for us ... */
to_reuse = usable_delta;
/* ... but pack split may override that */
- else if (type != entry->in_pack_type)
+ else if (entry->type != entry->in_pack_type)
to_reuse = 0; /* pack has delta which is unusable */
else if (entry->delta)
to_reuse = 0; /* we want to pack afresh */
@@ -255,174 +473,71 @@ static unsigned long write_object(struct sha1file *f,
* and we do not need to deltify it.
*/
- if (!to_reuse) {
- no_reuse:
- if (!usable_delta) {
- buf = read_sha1_file(entry->idx.sha1, &type, &size);
- if (!buf)
- die("unable to read %s", sha1_to_hex(entry->idx.sha1));
- /*
- * make sure no cached delta data remains from a
- * previous attempt before a pack split occurred.
- */
- free(entry->delta_data);
- entry->delta_data = NULL;
- entry->z_delta_size = 0;
- } else if (entry->delta_data) {
- size = entry->delta_size;
- buf = entry->delta_data;
- entry->delta_data = NULL;
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- } else {
- buf = get_delta(entry);
- size = entry->delta_size;
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- }
-
- if (entry->z_delta_size)
- datalen = entry->z_delta_size;
- else
- datalen = do_compress(&buf, size);
-
- /*
- * The object header is a byte of 'type' followed by zero or
- * more bytes of length.
- */
- hdrlen = encode_in_pack_object_header(type, size, header);
-
- if (type == OBJ_OFS_DELTA) {
- /*
- * Deltas with relative base contain an additional
- * encoding of the relative offset for the delta
- * base from this object's position in the pack.
- */
- off_t ofs = entry->idx.offset - entry->delta->idx.offset;
- unsigned pos = sizeof(dheader) - 1;
- dheader[pos] = ofs & 127;
- while (ofs >>= 7)
- dheader[--pos] = 128 | (--ofs & 127);
- if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
- free(buf);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, dheader + pos, sizeof(dheader) - pos);
- hdrlen += sizeof(dheader) - pos;
- } else if (type == OBJ_REF_DELTA) {
- /*
- * Deltas with a base reference contain
- * an additional 20 bytes for the base sha1.
- */
- if (limit && hdrlen + 20 + datalen + 20 >= limit) {
- free(buf);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, entry->delta->idx.sha1, 20);
- hdrlen += 20;
- } else {
- if (limit && hdrlen + datalen + 20 >= limit) {
- free(buf);
- return 0;
- }
- sha1write(f, header, hdrlen);
- }
- sha1write(f, buf, datalen);
- free(buf);
- }
- else {
- struct packed_git *p = entry->in_pack;
- struct pack_window *w_curs = NULL;
- struct revindex_entry *revidx;
- off_t offset;
-
- if (entry->delta)
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- hdrlen = encode_in_pack_object_header(type, entry->size, header);
-
- offset = entry->in_pack_offset;
- revidx = find_pack_revindex(p, offset);
- datalen = revidx[1].offset - offset;
- if (!pack_to_stdout && p->index_version > 1 &&
- check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
- error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
- unuse_pack(&w_curs);
- goto no_reuse;
- }
-
- offset += entry->in_pack_header_size;
- datalen -= entry->in_pack_header_size;
- if (!pack_to_stdout && p->index_version == 1 &&
- check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
- error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
- unuse_pack(&w_curs);
- goto no_reuse;
- }
+ if (!to_reuse)
+ len = write_no_reuse_object(f, entry, limit, usable_delta);
+ else
+ len = write_reuse_object(f, entry, limit, usable_delta);
+ if (!len)
+ return 0;
- if (type == OBJ_OFS_DELTA) {
- off_t ofs = entry->idx.offset - entry->delta->idx.offset;
- unsigned pos = sizeof(dheader) - 1;
- dheader[pos] = ofs & 127;
- while (ofs >>= 7)
- dheader[--pos] = 128 | (--ofs & 127);
- if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, dheader + pos, sizeof(dheader) - pos);
- hdrlen += sizeof(dheader) - pos;
- reused_delta++;
- } else if (type == OBJ_REF_DELTA) {
- if (limit && hdrlen + 20 + datalen + 20 >= limit) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, entry->delta->idx.sha1, 20);
- hdrlen += 20;
- reused_delta++;
- } else {
- if (limit && hdrlen + datalen + 20 >= limit) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- }
- copy_pack_data(f, p, &w_curs, offset, datalen);
- unuse_pack(&w_curs);
- reused++;
- }
if (usable_delta)
written_delta++;
written++;
if (!pack_to_stdout)
entry->idx.crc32 = crc32_end(f);
- return hdrlen + datalen;
+ return len;
}
-static int write_one(struct sha1file *f,
- struct object_entry *e,
- off_t *offset)
+enum write_one_status {
+ WRITE_ONE_SKIP = -1, /* already written */
+ WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */
+ WRITE_ONE_WRITTEN = 1, /* normal */
+ WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */
+};
+
+static enum write_one_status write_one(struct sha1file *f,
+ struct object_entry *e,
+ off_t *offset)
{
unsigned long size;
+ int recursing;
- /* offset is non zero if object is written already. */
- if (e->idx.offset || e->preferred_base)
- return -1;
+ /*
+ * we set offset to 1 (which is an impossible value) to mark
+ * the fact that this object is involved in "write its base
+ * first before writing a deltified object" recursion.
+ */
+ recursing = (e->idx.offset == 1);
+ if (recursing) {
+ warning("recursive delta detected for object %s",
+ sha1_to_hex(e->idx.sha1));
+ return WRITE_ONE_RECURSIVE;
+ } else if (e->idx.offset || e->preferred_base) {
+ /* offset is non zero if object is written already. */
+ return WRITE_ONE_SKIP;
+ }
/* if we are deltified, write out base object first. */
- if (e->delta && !write_one(f, e->delta, offset))
- return 0;
+ if (e->delta) {
+ e->idx.offset = 1; /* now recurse */
+ switch (write_one(f, e->delta, offset)) {
+ case WRITE_ONE_RECURSIVE:
+ /* we cannot depend on this one */
+ e->delta = NULL;
+ break;
+ default:
+ break;
+ case WRITE_ONE_BREAK:
+ e->idx.offset = recursing;
+ return WRITE_ONE_BREAK;
+ }
+ }
e->idx.offset = *offset;
size = write_object(f, e, *offset);
if (!size) {
- e->idx.offset = 0;
- return 0;
+ e->idx.offset = recursing;
+ return WRITE_ONE_BREAK;
}
written_list[nr_written++] = &e->idx;
@@ -430,7 +545,171 @@ static int write_one(struct sha1file *f,
if (signed_add_overflows(*offset, size))
die("pack too large for current definition of off_t");
*offset += size;
- return 1;
+ return WRITE_ONE_WRITTEN;
+}
+
+static int mark_tagged(const char *path, const unsigned char *sha1, int flag,
+ void *cb_data)
+{
+ unsigned char peeled[20];
+ struct object_entry *entry = locate_object_entry(sha1);
+
+ if (entry)
+ entry->tagged = 1;
+ if (!peel_ref(path, peeled)) {
+ entry = locate_object_entry(peeled);
+ if (entry)
+ entry->tagged = 1;
+ }
+ return 0;
+}
+
+static inline void add_to_write_order(struct object_entry **wo,
+ unsigned int *endp,
+ struct object_entry *e)
+{
+ if (e->filled)
+ return;
+ wo[(*endp)++] = e;
+ e->filled = 1;
+}
+
+static void add_descendants_to_write_order(struct object_entry **wo,
+ unsigned int *endp,
+ struct object_entry *e)
+{
+ int add_to_order = 1;
+ while (e) {
+ if (add_to_order) {
+ struct object_entry *s;
+ /* add this node... */
+ add_to_write_order(wo, endp, e);
+ /* all its siblings... */
+ for (s = e->delta_sibling; s; s = s->delta_sibling) {
+ add_to_write_order(wo, endp, s);
+ }
+ }
+ /* drop down a level to add left subtree nodes if possible */
+ if (e->delta_child) {
+ add_to_order = 1;
+ e = e->delta_child;
+ } else {
+ add_to_order = 0;
+ /* our sibling might have some children, it is next */
+ if (e->delta_sibling) {
+ e = e->delta_sibling;
+ continue;
+ }
+ /* go back to our parent node */
+ e = e->delta;
+ while (e && !e->delta_sibling) {
+ /* we're on the right side of a subtree, keep
+ * going up until we can go right again */
+ e = e->delta;
+ }
+ if (!e) {
+ /* done- we hit our original root node */
+ return;
+ }
+ /* pass it off to sibling at this level */
+ e = e->delta_sibling;
+ }
+ };
+}
+
+static void add_family_to_write_order(struct object_entry **wo,
+ unsigned int *endp,
+ struct object_entry *e)
+{
+ struct object_entry *root;
+
+ for (root = e; root->delta; root = root->delta)
+ ; /* nothing */
+ add_descendants_to_write_order(wo, endp, root);
+}
+
+static struct object_entry **compute_write_order(void)
+{
+ unsigned int i, wo_end, last_untagged;
+
+ struct object_entry **wo = xmalloc(nr_objects * sizeof(*wo));
+
+ for (i = 0; i < nr_objects; i++) {
+ objects[i].tagged = 0;
+ objects[i].filled = 0;
+ objects[i].delta_child = NULL;
+ objects[i].delta_sibling = NULL;
+ }
+
+ /*
+ * Fully connect delta_child/delta_sibling network.
+ * Make sure delta_sibling is sorted in the original
+ * recency order.
+ */
+ for (i = nr_objects; i > 0;) {
+ struct object_entry *e = &objects[--i];
+ if (!e->delta)
+ continue;
+ /* Mark me as the first child */
+ e->delta_sibling = e->delta->delta_child;
+ e->delta->delta_child = e;
+ }
+
+ /*
+ * Mark objects that are at the tip of tags.
+ */
+ for_each_tag_ref(mark_tagged, NULL);
+
+ /*
+ * Give the objects in the original recency order until
+ * we see a tagged tip.
+ */
+ for (i = wo_end = 0; i < nr_objects; i++) {
+ if (objects[i].tagged)
+ break;
+ add_to_write_order(wo, &wo_end, &objects[i]);
+ }
+ last_untagged = i;
+
+ /*
+ * Then fill all the tagged tips.
+ */
+ for (; i < nr_objects; i++) {
+ if (objects[i].tagged)
+ add_to_write_order(wo, &wo_end, &objects[i]);
+ }
+
+ /*
+ * And then all remaining commits and tags.
+ */
+ for (i = last_untagged; i < nr_objects; i++) {
+ if (objects[i].type != OBJ_COMMIT &&
+ objects[i].type != OBJ_TAG)
+ continue;
+ add_to_write_order(wo, &wo_end, &objects[i]);
+ }
+
+ /*
+ * And then all the trees.
+ */
+ for (i = last_untagged; i < nr_objects; i++) {
+ if (objects[i].type != OBJ_TREE)
+ continue;
+ add_to_write_order(wo, &wo_end, &objects[i]);
+ }
+
+ /*
+ * Finally all the rest in really tight order
+ */
+ for (i = last_untagged; i < nr_objects; i++) {
+ if (!objects[i].filled)
+ add_family_to_write_order(wo, &wo_end, &objects[i]);
+ }
+
+ if (wo_end != nr_objects)
+ die("ordered %u objects, expected %"PRIu32, wo_end, nr_objects);
+
+ return wo;
}
static void write_pack_file(void)
@@ -438,37 +717,31 @@ static void write_pack_file(void)
uint32_t i = 0, j;
struct sha1file *f;
off_t offset;
- struct pack_header hdr;
uint32_t nr_remaining = nr_result;
time_t last_mtime = 0;
+ struct object_entry **write_order;
if (progress > pack_to_stdout)
progress_state = start_progress("Writing objects", nr_result);
written_list = xmalloc(nr_objects * sizeof(*written_list));
+ write_order = compute_write_order();
do {
unsigned char sha1[20];
char *pack_tmp_name = NULL;
- if (pack_to_stdout) {
+ if (pack_to_stdout)
f = sha1fd_throughput(1, "<stdout>", progress_state);
- } else {
- char tmpname[PATH_MAX];
- int fd;
- fd = odb_mkstemp(tmpname, sizeof(tmpname),
- "pack/tmp_pack_XXXXXX");
- pack_tmp_name = xstrdup(tmpname);
- f = sha1fd(fd, pack_tmp_name);
- }
+ else
+ f = create_tmp_packfile(&pack_tmp_name);
- hdr.hdr_signature = htonl(PACK_SIGNATURE);
- hdr.hdr_version = htonl(PACK_VERSION);
- hdr.hdr_entries = htonl(nr_remaining);
- sha1write(f, &hdr, sizeof(hdr));
- offset = sizeof(hdr);
+ offset = write_pack_header(f, nr_remaining);
+ if (!offset)
+ die_errno("unable to write pack header");
nr_written = 0;
for (; i < nr_objects; i++) {
- if (!write_one(f, objects + i, &offset))
+ struct object_entry *e = write_order[i];
+ if (write_one(f, e, &offset) == WRITE_ONE_BREAK)
break;
display_progress(progress_state, written);
}
@@ -490,20 +763,8 @@ static void write_pack_file(void)
if (!pack_to_stdout) {
struct stat st;
- const char *idx_tmp_name;
char tmpname[PATH_MAX];
- 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));
- free_pack_by_name(tmpname);
- if (adjust_shared_perm(pack_tmp_name))
- die_errno("unable to make temporary pack file readable");
- if (rename(pack_tmp_name, tmpname))
- die_errno("unable to rename temporary pack file");
-
/*
* Packs are runtime accessed in their mtime
* order since newer packs are more likely to contain
@@ -511,28 +772,27 @@ static void write_pack_file(void)
* packs then we should modify the mtime of later ones
* to preserve this property.
*/
- if (stat(tmpname, &st) < 0) {
+ if (stat(pack_tmp_name, &st) < 0) {
warning("failed to stat %s: %s",
- tmpname, strerror(errno));
+ pack_tmp_name, strerror(errno));
} else if (!last_mtime) {
last_mtime = st.st_mtime;
} else {
struct utimbuf utb;
utb.actime = st.st_atime;
utb.modtime = --last_mtime;
- if (utime(tmpname, &utb) < 0)
+ if (utime(pack_tmp_name, &utb) < 0)
warning("failed utime() on %s: %s",
tmpname, strerror(errno));
}
- snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
- base_name, sha1_to_hex(sha1));
- if (adjust_shared_perm(idx_tmp_name))
- die_errno("unable to make temporary index file readable");
- if (rename(idx_tmp_name, tmpname))
- die_errno("unable to rename temporary index file");
-
- free((void *) idx_tmp_name);
+ /* Enough space for "-<sha-1>.pack"? */
+ if (sizeof(tmpname) <= strlen(base_name) + 50)
+ die("pack base name '%s' too long", base_name);
+ snprintf(tmpname, sizeof(tmpname), "%s-", base_name);
+ finish_tmp_packfile(tmpname, pack_tmp_name,
+ written_list, nr_written,
+ &pack_idx_opts, sha1);
free(pack_tmp_name);
puts(sha1_to_hex(sha1));
}
@@ -545,6 +805,7 @@ static void write_pack_file(void)
} while (nr_remaining && i < nr_objects);
free(written_list);
+ free(write_order);
stop_progress(&progress_state);
if (written != nr_result)
die("wrote %"PRIu32" objects while expecting %"PRIu32,
@@ -633,7 +894,7 @@ static int no_try_delta(const char *path)
struct git_attr_check check[1];
setup_delta_attr_check(check);
- if (git_checkattr(path, ARRAY_SIZE(check), check))
+ if (git_check_attr(path, ARRAY_SIZE(check), check))
return 0;
if (ATTR_FALSE(check->value))
return 1;
@@ -667,6 +928,10 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
off_t offset = find_pack_entry_one(sha1, p);
if (offset) {
if (!found_pack) {
+ if (!is_pack_valid(p)) {
+ warning("packfile %s cannot be accessed", p->pack_name);
+ continue;
+ }
found_offset = offset;
found_pack = p;
}
@@ -838,7 +1103,7 @@ static void add_pbase_object(struct tree_desc *tree,
while (tree_entry(tree,&entry)) {
if (S_ISGITLINK(entry.mode))
continue;
- cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
+ cmp = tree_entry_len(&entry) != cmplen ? 1 :
memcmp(name, entry.path, cmplen);
if (cmp > 0)
continue;
@@ -1145,7 +1410,7 @@ static void get_object_details(void)
for (i = 0; i < nr_objects; i++) {
struct object_entry *entry = sorted_by_offset[i];
check_object(entry);
- if (big_file_threshold <= entry->size)
+ if (big_file_threshold < entry->size)
entry->no_try_delta = 1;
}
@@ -1889,14 +2154,10 @@ static int git_pack_config(const char *k, const char *v, void *cb)
return 0;
}
if (!strcmp(k, "pack.indexversion")) {
- pack_idx_default_version = git_config_int(k, v);
- if (pack_idx_default_version > 2)
+ pack_idx_opts.version = git_config_int(k, v);
+ if (pack_idx_opts.version > 2)
die("bad pack.indexversion=%"PRIu32,
- pack_idx_default_version);
- return 0;
- }
- if (!strcmp(k, "pack.packsizelimit")) {
- pack_size_limit_cfg = git_config_ulong(k, v);
+ pack_idx_opts.version);
return 0;
}
return git_default_config(k, v, cb);
@@ -1941,7 +2202,9 @@ static void show_commit(struct commit *commit, void *data)
commit->object.flags |= OBJECT_ADDED;
}
-static void show_object(struct object *obj, const struct name_path *path, const char *last)
+static void show_object(struct object *obj,
+ const struct name_path *path, const char *last,
+ void *data)
{
char *name = path_name(path, last);
@@ -2070,6 +2333,10 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
if (!p->pack_local || p->pack_keep)
continue;
+ if (unpack_unreachable_expiration &&
+ p->mtime < unpack_unreachable_expiration)
+ continue;
+
if (open_pack_index(p))
die("cannot open pack index");
@@ -2121,203 +2388,175 @@ static void get_object_list(int ac, const char **av)
loosen_unused_packed_objects(&revs);
}
+static int option_parse_index_version(const struct option *opt,
+ const char *arg, int unset)
+{
+ char *c;
+ const char *val = arg;
+ pack_idx_opts.version = strtoul(val, &c, 10);
+ if (pack_idx_opts.version > 2)
+ die(_("unsupported index version %s"), val);
+ if (*c == ',' && c[1])
+ pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
+ if (*c || pack_idx_opts.off32_limit & 0x80000000)
+ die(_("bad index version '%s'"), val);
+ return 0;
+}
+
+static int option_parse_unpack_unreachable(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset) {
+ unpack_unreachable = 0;
+ unpack_unreachable_expiration = 0;
+ }
+ else {
+ unpack_unreachable = 1;
+ if (arg)
+ unpack_unreachable_expiration = approxidate(arg);
+ }
+ return 0;
+}
+
+static int option_parse_ulong(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ die(_("option %s does not accept negative form"),
+ opt->long_name);
+
+ if (!git_parse_ulong(arg, opt->value))
+ die(_("unable to parse value '%s' for option %s"),
+ arg, opt->long_name);
+ return 0;
+}
+
+#define OPT_ULONG(s, l, v, h) \
+ { OPTION_CALLBACK, (s), (l), (v), "n", (h), \
+ PARSE_OPT_NONEG, option_parse_ulong }
+
int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{
int use_internal_rev_list = 0;
int thin = 0;
int all_progress_implied = 0;
- uint32_t i;
- const char **rp_av;
- int rp_ac_alloc = 64;
- int rp_ac;
+ const char *rp_av[6];
+ int rp_ac = 0;
+ int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
+ struct option pack_objects_options[] = {
+ OPT_SET_INT('q', "quiet", &progress,
+ "do not show progress meter", 0),
+ OPT_SET_INT(0, "progress", &progress,
+ "show progress meter", 1),
+ OPT_SET_INT(0, "all-progress", &progress,
+ "show progress meter during object writing phase", 2),
+ OPT_BOOL(0, "all-progress-implied",
+ &all_progress_implied,
+ "similar to --all-progress when progress meter is shown"),
+ { OPTION_CALLBACK, 0, "index-version", NULL, "version[,offset]",
+ "write the pack index file in the specified idx format version",
+ 0, option_parse_index_version },
+ OPT_ULONG(0, "max-pack-size", &pack_size_limit,
+ "maximum size of each output pack file"),
+ OPT_BOOL(0, "local", &local,
+ "ignore borrowed objects from alternate object store"),
+ OPT_BOOL(0, "incremental", &incremental,
+ "ignore packed objects"),
+ OPT_INTEGER(0, "window", &window,
+ "limit pack window by objects"),
+ OPT_ULONG(0, "window-memory", &window_memory_limit,
+ "limit pack window by memory in addition to object limit"),
+ OPT_INTEGER(0, "depth", &depth,
+ "maximum length of delta chain allowed in the resulting pack"),
+ OPT_BOOL(0, "reuse-delta", &reuse_delta,
+ "reuse existing deltas"),
+ OPT_BOOL(0, "reuse-object", &reuse_object,
+ "reuse existing objects"),
+ OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta,
+ "use OFS_DELTA objects"),
+ OPT_INTEGER(0, "threads", &delta_search_threads,
+ "use threads when searching for best delta matches"),
+ OPT_BOOL(0, "non-empty", &non_empty,
+ "do not create an empty pack output"),
+ OPT_BOOL(0, "revs", &use_internal_rev_list,
+ "read revision arguments from standard input"),
+ { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL,
+ "limit the objects to those that are not yet packed",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ { OPTION_SET_INT, 0, "all", &rev_list_all, NULL,
+ "include objects reachable from any reference",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL,
+ "include objects referred by reflog entries",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ OPT_BOOL(0, "stdout", &pack_to_stdout,
+ "output pack to stdout"),
+ OPT_BOOL(0, "include-tag", &include_tag,
+ "include tag objects that refer to objects to be packed"),
+ OPT_BOOL(0, "keep-unreachable", &keep_unreachable,
+ "keep unreachable objects"),
+ { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, "time",
+ "unpack unreachable objects newer than <time>",
+ PARSE_OPT_OPTARG, option_parse_unpack_unreachable },
+ OPT_BOOL(0, "thin", &thin,
+ "create thin packs"),
+ OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep,
+ "ignore packs that have companion .keep file"),
+ OPT_INTEGER(0, "compression", &pack_compression_level,
+ "pack compression level"),
+ OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents,
+ "do not hide commits by grafts", 0),
+ OPT_END(),
+ };
read_replace_refs = 0;
- rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
-
- rp_av[0] = "pack-objects";
- rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
- rp_ac = 2;
-
+ reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
if (!pack_compression_seen && core_compression_seen)
pack_compression_level = core_compression_level;
progress = isatty(2);
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
+ argc = parse_options(argc, argv, prefix, pack_objects_options,
+ pack_usage, 0);
- if (*arg != '-')
- break;
+ if (argc) {
+ base_name = argv[0];
+ argc--;
+ }
+ if (pack_to_stdout != !base_name || argc)
+ usage_with_options(pack_usage, pack_objects_options);
+
+ rp_av[rp_ac++] = "pack-objects";
+ if (thin) {
+ use_internal_rev_list = 1;
+ rp_av[rp_ac++] = "--objects-edge";
+ } else
+ rp_av[rp_ac++] = "--objects";
+
+ if (rev_list_all) {
+ use_internal_rev_list = 1;
+ rp_av[rp_ac++] = "--all";
+ }
+ if (rev_list_reflog) {
+ use_internal_rev_list = 1;
+ rp_av[rp_ac++] = "--reflog";
+ }
+ if (rev_list_unpacked) {
+ use_internal_rev_list = 1;
+ rp_av[rp_ac++] = "--unpacked";
+ }
- if (!strcmp("--non-empty", arg)) {
- non_empty = 1;
- continue;
- }
- if (!strcmp("--local", arg)) {
- local = 1;
- continue;
- }
- if (!strcmp("--incremental", arg)) {
- incremental = 1;
- continue;
- }
- if (!strcmp("--honor-pack-keep", arg)) {
- ignore_packed_keep = 1;
- continue;
- }
- if (!prefixcmp(arg, "--compression=")) {
- char *end;
- int level = strtoul(arg+14, &end, 0);
- if (!arg[14] || *end)
- usage(pack_usage);
- 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;
- continue;
- }
- if (!prefixcmp(arg, "--max-pack-size=")) {
- pack_size_limit_cfg = 0;
- if (!git_parse_ulong(arg+16, &pack_size_limit))
- usage(pack_usage);
- continue;
- }
- if (!prefixcmp(arg, "--window=")) {
- char *end;
- window = strtoul(arg+9, &end, 0);
- if (!arg[9] || *end)
- usage(pack_usage);
- continue;
- }
- if (!prefixcmp(arg, "--window-memory=")) {
- if (!git_parse_ulong(arg+16, &window_memory_limit))
- usage(pack_usage);
- continue;
- }
- if (!prefixcmp(arg, "--threads=")) {
- char *end;
- delta_search_threads = strtoul(arg+10, &end, 0);
- if (!arg[10] || *end || delta_search_threads < 0)
- usage(pack_usage);
+ if (!reuse_object)
+ reuse_delta = 0;
+ if (pack_compression_level == -1)
+ pack_compression_level = Z_DEFAULT_COMPRESSION;
+ else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION)
+ die("bad pack compression level %d", pack_compression_level);
#ifdef NO_PTHREADS
- if (delta_search_threads != 1)
- warning("no threads support, "
- "ignoring %s", arg);
+ if (delta_search_threads != 1)
+ warning("no threads support, ignoring --threads");
#endif
- continue;
- }
- if (!prefixcmp(arg, "--depth=")) {
- char *end;
- depth = strtoul(arg+8, &end, 0);
- if (!arg[8] || *end)
- usage(pack_usage);
- continue;
- }
- if (!strcmp("--progress", arg)) {
- progress = 1;
- continue;
- }
- if (!strcmp("--all-progress", arg)) {
- progress = 2;
- continue;
- }
- if (!strcmp("--all-progress-implied", arg)) {
- all_progress_implied = 1;
- continue;
- }
- if (!strcmp("-q", arg)) {
- progress = 0;
- continue;
- }
- if (!strcmp("--no-reuse-delta", arg)) {
- reuse_delta = 0;
- continue;
- }
- if (!strcmp("--no-reuse-object", arg)) {
- reuse_object = reuse_delta = 0;
- continue;
- }
- if (!strcmp("--delta-base-offset", arg)) {
- allow_ofs_delta = 1;
- continue;
- }
- if (!strcmp("--stdout", arg)) {
- pack_to_stdout = 1;
- continue;
- }
- if (!strcmp("--revs", arg)) {
- use_internal_rev_list = 1;
- continue;
- }
- if (!strcmp("--keep-unreachable", arg)) {
- keep_unreachable = 1;
- continue;
- }
- if (!strcmp("--unpack-unreachable", arg)) {
- unpack_unreachable = 1;
- continue;
- }
- if (!strcmp("--include-tag", arg)) {
- include_tag = 1;
- continue;
- }
- if (!strcmp("--unpacked", arg) ||
- !strcmp("--reflog", arg) ||
- !strcmp("--all", arg)) {
- use_internal_rev_list = 1;
- if (rp_ac >= rp_ac_alloc - 1) {
- rp_ac_alloc = alloc_nr(rp_ac_alloc);
- rp_av = xrealloc(rp_av,
- rp_ac_alloc * sizeof(*rp_av));
- }
- rp_av[rp_ac++] = arg;
- continue;
- }
- if (!strcmp("--thin", arg)) {
- use_internal_rev_list = 1;
- thin = 1;
- rp_av[1] = "--objects-edge";
- continue;
- }
- if (!prefixcmp(arg, "--index-version=")) {
- char *c;
- pack_idx_default_version = strtoul(arg + 16, &c, 10);
- if (pack_idx_default_version > 2)
- die("bad %s", arg);
- if (*c == ',')
- pack_idx_off32_limit = strtoul(c+1, &c, 0);
- if (*c || pack_idx_off32_limit & 0x80000000)
- die("bad %s", arg);
- continue;
- }
- if (!strcmp(arg, "--keep-true-parents")) {
- grafts_replace_parents = 0;
- continue;
- }
- usage(pack_usage);
- }
-
- /* Traditionally "pack-objects [options] base extra" failed;
- * we would however want to take refs parameter that would
- * have been given to upstream rev-list ourselves, which means
- * we somehow want to say what the base name is. So the
- * syntax would be:
- *
- * pack-objects [options] base <refs...>
- *
- * in other words, we would treat the first non-option as the
- * base_name and send everything else to the internal revision
- * walker.
- */
-
- if (!pack_to_stdout)
- base_name = argv[i++];
-
- 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)
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index f821eb3..3cfe02d 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -56,13 +56,13 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
return 1;
}
-static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
+static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf)
{
- static char line[1000];
int patchlen = 0, found_next = 0;
int before = -1, after = -1;
- while (fgets(line, sizeof(line), stdin) != NULL) {
+ while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
+ char *line = line_buf->buf;
char *p = line;
int len;
@@ -133,14 +133,16 @@ static void generate_id_list(void)
unsigned char sha1[20], n[20];
git_SHA_CTX ctx;
int patchlen;
+ struct strbuf line_buf = STRBUF_INIT;
git_SHA1_Init(&ctx);
hashclr(sha1);
while (!feof(stdin)) {
- patchlen = get_one_patchid(n, &ctx);
+ patchlen = get_one_patchid(n, &ctx, &line_buf);
flush_current_id(patchlen, sha1, &ctx);
hashcpy(sha1, n);
}
+ strbuf_release(&line_buf);
}
static const char patch_id_usage[] = "git patch-id < patch";
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
index f9463de..b58a2e1 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -35,8 +35,6 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
unlink_or_warn(pathname);
display_progress(progress, i + 1);
}
- pathname[len] = 0;
- rmdir(pathname);
}
void prune_packed_objects(int opts)
@@ -65,6 +63,8 @@ void prune_packed_objects(int opts)
continue;
prune_dir(i, d, pathname, len + 3, opts);
closedir(d);
+ pathname[len + 2] = '\0';
+ rmdir(pathname);
}
stop_progress(&progress);
}
diff --git a/builtin/prune.c b/builtin/prune.c
index e65690b..b99b635 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -5,6 +5,7 @@
#include "builtin.h"
#include "reachable.h"
#include "parse-options.h"
+#include "progress.h"
#include "dir.h"
static const char * const prune_usage[] = {
@@ -14,6 +15,7 @@ static const char * const prune_usage[] = {
static int show_only;
static int verbose;
static unsigned long expire;
+static int show_progress = -1;
static int prune_tmp_object(const char *path, const char *filename)
{
@@ -83,9 +85,9 @@ static int prune_dir(int i, char *path)
}
fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
}
+ closedir(dir);
if (!show_only)
rmdir(path);
- closedir(dir);
return 0;
}
@@ -124,9 +126,11 @@ static void remove_temporary_files(const char *path)
int cmd_prune(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
+ struct progress *progress = NULL;
const struct option options[] = {
OPT__DRY_RUN(&show_only, "do not remove, show only"),
OPT__VERBOSE(&verbose, "report pruned objects"),
+ OPT_BOOL(0, "progress", &show_progress, "show progress"),
OPT_DATE(0, "expire", &expire,
"expire objects older than <time>"),
OPT_END()
@@ -152,7 +156,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
else
die("unrecognized argument: %s", name);
}
- mark_reachable_objects(&revs, 1);
+
+ if (show_progress == -1)
+ show_progress = isatty(2);
+ if (show_progress)
+ progress = start_progress_delay("Checking connectivity", 0, 0, 2);
+
+ mark_reachable_objects(&revs, 1, progress);
+ stop_progress(&progress);
prune_object_dir(get_object_directory());
prune_packed_objects(show_only);
diff --git a/builtin/push.c b/builtin/push.c
index 9cebf9e..fdfcc6c 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -8,6 +8,7 @@
#include "remote.h"
#include "transport.h"
#include "parse-options.h"
+#include "submodule.h"
static const char * const push_usage[] = {
"git push [<options>] [<repository> [<refspec>...]]",
@@ -18,11 +19,12 @@ static int thin;
static int deleterefs;
static const char *receivepack;
static int verbosity;
-static int progress;
+static int progress = -1;
static const char **refspec;
static int refspec_nr;
static int refspec_alloc;
+static int default_matching_used;
static void add_refspec(const char *ref)
{
@@ -64,7 +66,54 @@ static void set_refspecs(const char **refs, int nr)
}
}
-static void setup_push_upstream(struct remote *remote)
+static int push_url_of_remote(struct remote *remote, const char ***url_p)
+{
+ if (remote->pushurl_nr) {
+ *url_p = remote->pushurl;
+ return remote->pushurl_nr;
+ }
+ *url_p = remote->url;
+ return remote->url_nr;
+}
+
+static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) {
+ /*
+ * There's no point in using shorten_unambiguous_ref here,
+ * as the ambiguity would be on the remote side, not what
+ * we have locally. Plus, this is supposed to be the simple
+ * mode. If the user is doing something crazy like setting
+ * upstream to a non-branch, we should probably be showing
+ * them the big ugly fully qualified ref.
+ */
+ const char *advice_maybe = "";
+ const char *short_upstream =
+ skip_prefix(branch->merge[0]->src, "refs/heads/");
+
+ if (!short_upstream)
+ short_upstream = branch->merge[0]->src;
+ /*
+ * Don't show advice for people who explicitely set
+ * push.default.
+ */
+ if (push_default == PUSH_DEFAULT_UNSPECIFIED)
+ advice_maybe = _("\n"
+ "To choose either option permanently, "
+ "see push.default in 'git help config'.");
+ die(_("The upstream branch of your current branch does not match\n"
+ "the name of your current branch. To push to the upstream branch\n"
+ "on the remote, use\n"
+ "\n"
+ " git push %s HEAD:%s\n"
+ "\n"
+ "To push to the branch of the same name on the remote, use\n"
+ "\n"
+ " git push %s %s\n"
+ "%s"),
+ remote->name, short_upstream,
+ remote->name, branch->name, advice_maybe);
+}
+
+static void setup_push_upstream(struct remote *remote, int simple)
{
struct strbuf refspec = STRBUF_INIT;
struct branch *branch = branch_get(NULL);
@@ -75,7 +124,7 @@ static void setup_push_upstream(struct remote *remote)
"\n"
" git push %s HEAD:<name-of-remote-branch>\n"),
remote->name);
- if (!branch->merge_nr || !branch->merge)
+ if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n"
"\n"
@@ -86,6 +135,14 @@ static void setup_push_upstream(struct remote *remote)
if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name);
+ if (strcmp(branch->remote_name, remote->name))
+ die(_("You are pushing to remote '%s', which is not the upstream of\n"
+ "your current branch '%s', without telling me what to push\n"
+ "to update which remote branch."),
+ remote->name, branch->name);
+ if (simple && strcmp(branch->refname, branch->merge[0]->src))
+ die_push_simple(branch, remote);
+
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf);
}
@@ -94,12 +151,19 @@ static void setup_default_push_refspecs(struct remote *remote)
{
switch (push_default) {
default:
+ case PUSH_DEFAULT_UNSPECIFIED:
+ default_matching_used = 1;
+ /* fallthru */
case PUSH_DEFAULT_MATCHING:
add_refspec(":");
break;
+ case PUSH_DEFAULT_SIMPLE:
+ setup_push_upstream(remote, 1);
+ break;
+
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote);
+ setup_push_upstream(remote, 0);
break;
case PUSH_DEFAULT_CURRENT:
@@ -113,6 +177,45 @@ static void setup_default_push_refspecs(struct remote *remote)
}
}
+static const char message_advice_pull_before_push[] =
+ N_("Updates were rejected because the tip of your current branch is behind\n"
+ "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+ "before pushing again.\n"
+ "See the 'Note about fast-forwards' in 'git push --help' for details.");
+
+static const char message_advice_use_upstream[] =
+ N_("Updates were rejected because a pushed branch tip is behind its remote\n"
+ "counterpart. If you did not intend to push that branch, you may want to\n"
+ "specify branches to push or set the 'push.default' configuration\n"
+ "variable to 'current' or 'upstream' to push only the current branch.");
+
+static const char message_advice_checkout_pull_push[] =
+ N_("Updates were rejected because a pushed branch tip is behind its remote\n"
+ "counterpart. Check out this branch and merge the remote changes\n"
+ "(e.g. 'git pull') before pushing again.\n"
+ "See the 'Note about fast-forwards' in 'git push --help' for details.");
+
+static void advise_pull_before_push(void)
+{
+ if (!advice_push_non_ff_current || !advice_push_nonfastforward)
+ return;
+ advise(_(message_advice_pull_before_push));
+}
+
+static void advise_use_upstream(void)
+{
+ if (!advice_push_non_ff_default || !advice_push_nonfastforward)
+ return;
+ advise(_(message_advice_use_upstream));
+}
+
+static void advise_checkout_pull_push(void)
+{
+ if (!advice_push_non_ff_matching || !advice_push_nonfastforward)
+ return;
+ advise(_(message_advice_checkout_pull_push));
+}
+
static int push_with_options(struct transport *transport, int flags)
{
int err;
@@ -134,14 +237,21 @@ static int push_with_options(struct transport *transport, int flags)
error(_("failed to push some refs to '%s'"), transport->url);
err |= transport_disconnect(transport);
-
if (!err)
return 0;
- if (nonfastforward && advice_push_nonfastforward) {
- fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n"
- "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"
- "'Note about fast-forwards' section of 'git push --help' for details.\n"));
+ switch (nonfastforward) {
+ default:
+ break;
+ case NON_FF_HEAD:
+ advise_pull_before_push();
+ break;
+ case NON_FF_OTHER:
+ if (default_matching_used)
+ advise_use_upstream();
+ else
+ advise_checkout_pull_push();
+ break;
}
return 1;
@@ -195,13 +305,7 @@ static int do_push(const char *repo, int flags)
setup_default_push_refspecs(remote);
}
errs = 0;
- if (remote->pushurl_nr) {
- url = remote->pushurl;
- url_nr = remote->pushurl_nr;
- } else {
- url = remote->url;
- url_nr = remote->url_nr;
- }
+ url_nr = push_url_of_remote(remote, &url);
if (url_nr) {
for (i = 0; i < url_nr; i++) {
struct transport *transport =
@@ -219,6 +323,29 @@ static int do_push(const char *repo, int flags)
return !!errs;
}
+static int option_parse_recurse_submodules(const struct option *opt,
+ const char *arg, int unset)
+{
+ int *flags = opt->value;
+
+ if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |
+ TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))
+ die("%s can only be used once.", opt->long_name);
+
+ if (arg) {
+ if (!strcmp(arg, "check"))
+ *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
+ else if (!strcmp(arg, "on-demand"))
+ *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
+ else
+ die("bad %s argument: %s", opt->long_name, arg);
+ } else
+ die("option %s needs an argument (check|on-demand)",
+ opt->long_name);
+
+ return 0;
+}
+
int cmd_push(int argc, const char **argv, const char *prefix)
{
int flags = 0;
@@ -236,12 +363,17 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
+ { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check",
+ "controls recursive pushing of submodules",
+ PARSE_OPT_OPTARG, option_parse_recurse_submodules },
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_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
TRANSPORT_PUSH_SET_UPSTREAM),
- OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+ OPT_BOOL(0, "progress", &progress, "force progress reporting"),
+ OPT_BIT(0, "prune", &flags, "prune locally removed refs",
+ TRANSPORT_PUSH_PRUNE),
OPT_END()
};
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e1a687a..0afb8b2 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -11,6 +11,7 @@
#include "transport.h"
#include "string-list.h"
#include "sha1-array.h"
+#include "connected.h"
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
@@ -25,16 +26,19 @@ static int deny_deletes;
static int deny_non_fast_forwards;
static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
-static int receive_fsck_objects;
+static int receive_fsck_objects = -1;
+static int transfer_fsck_objects = -1;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int unpack_limit = 100;
static int report_status;
static int use_sideband;
+static int quiet;
static int prefer_ofs_delta = 1;
static int auto_update_server_info;
static int auto_gc = 1;
static const char *head_name;
+static void *head_name_to_free;
static int sent_capabilities;
static enum deny_action parse_deny_action(const char *var, const char *value)
@@ -79,6 +83,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "transfer.fsckobjects") == 0) {
+ transfer_fsck_objects = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "receive.denycurrentbranch")) {
deny_current_branch = parse_deny_action(var, value);
return 0;
@@ -107,31 +116,65 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
-static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static void show_ref(const char *path, const unsigned char *sha1)
{
if (sent_capabilities)
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
else
packet_write(1, "%s %s%c%s%s\n",
sha1_to_hex(sha1), path, 0,
- " report-status delete-refs side-band-64k",
+ " report-status delete-refs side-band-64k quiet",
prefer_ofs_delta ? " ofs-delta" : "");
sent_capabilities = 1;
+}
+
+static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
+{
+ path = strip_namespace(path);
+ /*
+ * Advertise refs outside our current namespace as ".have"
+ * refs, so that the client can use them to minimize data
+ * transfer but will otherwise ignore them. This happens to
+ * cover ".have" that are thrown in by add_one_alternate_ref()
+ * to mark histories that are complete in our alternates as
+ * well.
+ */
+ if (!path)
+ path = ".have";
+ show_ref(path, sha1);
return 0;
}
+static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
+{
+ show_ref(".have", sha1);
+}
+
+static void collect_one_alternate_ref(const struct ref *ref, void *data)
+{
+ struct sha1_array *sa = data;
+ sha1_array_append(sa, ref->old_sha1);
+}
+
static void write_head_info(void)
{
- for_each_ref(show_ref, NULL);
+ struct sha1_array sa = SHA1_ARRAY_INIT;
+ for_each_alternate_ref(collect_one_alternate_ref, &sa);
+ sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
+ sha1_array_clear(&sa);
+ for_each_ref(show_ref_cb, NULL);
if (!sent_capabilities)
- show_ref("capabilities^{}", null_sha1, 0, NULL);
+ show_ref("capabilities^{}", null_sha1);
+ /* EOF */
+ packet_flush(1);
}
struct command {
struct command *next;
const char *error_string;
- unsigned int skip_update;
+ unsigned int skip_update:1,
+ did_not_exist:1;
unsigned char old_sha1[20];
unsigned char new_sha1[20];
char ref_name[FLEX_ARRAY]; /* more */
@@ -189,21 +232,15 @@ static int copy_to_sideband(int in, int out, void *arg)
return 0;
}
-static int run_receive_hook(struct command *commands, const char *hook_name)
+typedef int (*feed_fn)(void *, const char **, size_t *);
+static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
{
- static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
- struct command *cmd;
struct child_process proc;
struct async muxer;
const char *argv[2];
- int have_input = 0, code;
-
- for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
- if (!cmd->error_string)
- have_input = 1;
- }
+ int code;
- if (!have_input || access(hook_name, X_OK) < 0)
+ if (access(hook_name, X_OK) < 0)
return 0;
argv[0] = hook_name;
@@ -231,15 +268,13 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
return code;
}
- for (cmd = commands; cmd; cmd = cmd->next) {
- if (!cmd->error_string) {
- size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
- sha1_to_hex(cmd->old_sha1),
- sha1_to_hex(cmd->new_sha1),
- cmd->ref_name);
- if (write_in_full(proc.in, buf, n) != n)
- break;
- }
+ while (1) {
+ const char *buf;
+ size_t n;
+ if (feed(feed_state, &buf, &n))
+ break;
+ if (write_in_full(proc.in, buf, n) != n)
+ break;
}
close(proc.in);
if (use_sideband)
@@ -247,6 +282,51 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
return finish_command(&proc);
}
+struct receive_hook_feed_state {
+ struct command *cmd;
+ int skip_broken;
+ struct strbuf buf;
+};
+
+static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
+{
+ struct receive_hook_feed_state *state = state_;
+ struct command *cmd = state->cmd;
+
+ while (cmd &&
+ state->skip_broken && (cmd->error_string || cmd->did_not_exist))
+ cmd = cmd->next;
+ if (!cmd)
+ return -1; /* EOF */
+ strbuf_reset(&state->buf);
+ strbuf_addf(&state->buf, "%s %s %s\n",
+ sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),
+ cmd->ref_name);
+ state->cmd = cmd->next;
+ if (bufp) {
+ *bufp = state->buf.buf;
+ *sizep = state->buf.len;
+ }
+ return 0;
+}
+
+static int run_receive_hook(struct command *commands, const char *hook_name,
+ int skip_broken)
+{
+ struct receive_hook_feed_state state;
+ int status;
+
+ strbuf_init(&state.buf, 0);
+ state.cmd = commands;
+ state.skip_broken = skip_broken;
+ if (feed_receive_hook(&state, NULL, NULL))
+ return 0;
+ state.cmd = commands;
+ status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
+ strbuf_release(&state.buf);
+ return status;
+}
+
static int run_update_hook(struct command *cmd)
{
static const char update_hook[] = "hooks/update";
@@ -333,17 +413,22 @@ static void refuse_unconfigured_deny_delete_current(void)
static const char *update(struct command *cmd)
{
const char *name = cmd->ref_name;
+ struct strbuf namespaced_name_buf = STRBUF_INIT;
+ const char *namespaced_name;
unsigned char *old_sha1 = cmd->old_sha1;
unsigned char *new_sha1 = cmd->new_sha1;
struct ref_lock *lock;
/* only refs/... are allowed */
- if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+ if (prefixcmp(name, "refs/") || check_refname_format(name + 5, 0)) {
rp_error("refusing to create funny ref '%s' remotely", name);
return "funny refname";
}
- if (is_ref_checked_out(name)) {
+ strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
+ namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);
+
+ if (is_ref_checked_out(namespaced_name)) {
switch (deny_current_branch) {
case DENY_IGNORE:
break;
@@ -371,7 +456,7 @@ static const char *update(struct command *cmd)
return "deletion prohibited";
}
- if (!strcmp(name, head_name)) {
+ if (!strcmp(namespaced_name, head_name)) {
switch (deny_delete_current) {
case DENY_IGNORE:
break;
@@ -424,17 +509,22 @@ static const char *update(struct command *cmd)
if (is_null_sha1(new_sha1)) {
if (!parse_object(old_sha1)) {
- rp_warning("Allowing deletion of corrupt ref.");
old_sha1 = NULL;
+ if (ref_exists(name)) {
+ rp_warning("Allowing deletion of corrupt ref.");
+ } else {
+ rp_warning("Deleting a non-existent ref.");
+ cmd->did_not_exist = 1;
+ }
}
- if (delete_ref(name, old_sha1, 0)) {
+ if (delete_ref(namespaced_name, old_sha1, 0)) {
rp_error("failed to delete %s", name);
return "failed to delete";
}
return NULL; /* good */
}
else {
- lock = lock_any_ref_for_update(name, old_sha1, 0);
+ lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
if (!lock) {
rp_error("failed to lock %s", name);
return "failed to lock";
@@ -456,7 +546,7 @@ static void run_update_post_hook(struct command *commands)
struct child_process proc;
for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
- if (cmd->error_string)
+ if (cmd->error_string || cmd->did_not_exist)
continue;
argc++;
}
@@ -467,7 +557,7 @@ static void run_update_post_hook(struct command *commands)
for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
char *p;
- if (cmd->error_string)
+ if (cmd->error_string || cmd->did_not_exist)
continue;
p = xmalloc(strlen(cmd->ref_name) + 1);
strcpy(p, cmd->ref_name);
@@ -491,17 +581,29 @@ static void run_update_post_hook(struct command *commands)
static void check_aliased_update(struct command *cmd, struct string_list *list)
{
+ struct strbuf buf = STRBUF_INIT;
+ const char *dst_name;
struct string_list_item *item;
struct command *dst_cmd;
unsigned char sha1[20];
char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
int flag;
- const char *dst_name = resolve_ref(cmd->ref_name, sha1, 0, &flag);
+ strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
+ dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);
+ strbuf_release(&buf);
if (!(flag & REF_ISSYMREF))
return;
+ dst_name = strip_namespace(dst_name);
+ if (!dst_name) {
+ rp_error("refusing update to broken symref '%s'", cmd->ref_name);
+ cmd->skip_update = 1;
+ cmd->error_string = "broken symref";
+ return;
+ }
+
if ((item = string_list_lookup(list, dst_name)) == NULL)
return;
@@ -540,12 +642,56 @@ static void check_aliased_updates(struct command *commands)
}
sort_string_list(&ref_list);
- for (cmd = commands; cmd; cmd = cmd->next)
- check_aliased_update(cmd, &ref_list);
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->error_string)
+ check_aliased_update(cmd, &ref_list);
+ }
string_list_clear(&ref_list, 0);
}
+static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
+{
+ struct command **cmd_list = cb_data;
+ struct command *cmd = *cmd_list;
+
+ if (!cmd || is_null_sha1(cmd->new_sha1))
+ return -1; /* end of list */
+ *cmd_list = NULL; /* this returns only one */
+ hashcpy(sha1, cmd->new_sha1);
+ return 0;
+}
+
+static void set_connectivity_errors(struct command *commands)
+{
+ struct command *cmd;
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ struct command *singleton = cmd;
+ if (!check_everything_connected(command_singleton_iterator,
+ 0, &singleton))
+ continue;
+ cmd->error_string = "missing necessary objects";
+ }
+}
+
+static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
+{
+ struct command **cmd_list = cb_data;
+ struct command *cmd = *cmd_list;
+
+ while (cmd) {
+ if (!is_null_sha1(cmd->new_sha1)) {
+ hashcpy(sha1, cmd->new_sha1);
+ *cmd_list = cmd->next;
+ return 0;
+ }
+ cmd = cmd->next;
+ }
+ *cmd_list = NULL;
+ return -1; /* end of list */
+}
+
static void execute_commands(struct command *commands, const char *unpacker_error)
{
struct command *cmd;
@@ -557,19 +703,33 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
return;
}
- if (run_receive_hook(commands, pre_receive_hook)) {
- for (cmd = commands; cmd; cmd = cmd->next)
- cmd->error_string = "pre-receive hook declined";
+ cmd = commands;
+ if (check_everything_connected(iterate_receive_command_list,
+ 0, &cmd))
+ set_connectivity_errors(commands);
+
+ if (run_receive_hook(commands, pre_receive_hook, 0)) {
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->error_string)
+ cmd->error_string = "pre-receive hook declined";
+ }
return;
}
check_aliased_updates(commands);
- head_name = resolve_ref("HEAD", sha1, 0, NULL);
+ free(head_name_to_free);
+ head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (cmd->error_string)
+ continue;
- for (cmd = commands; cmd; cmd = cmd->next)
- if (!cmd->skip_update)
- cmd->error_string = update(cmd);
+ if (cmd->skip_update)
+ continue;
+
+ cmd->error_string = update(cmd);
+ }
}
static struct command *read_head_info(void)
@@ -599,10 +759,13 @@ static struct command *read_head_info(void)
refname = line + 82;
reflen = strlen(refname);
if (reflen + 82 < len) {
- if (strstr(refname + reflen + 1, "report-status"))
+ const char *feature_list = refname + reflen + 1;
+ if (parse_feature_request(feature_list, "report-status"))
report_status = 1;
- if (strstr(refname + reflen + 1, "side-band-64k"))
+ if (parse_feature_request(feature_list, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
+ if (parse_feature_request(feature_list, "quiet"))
+ quiet = 1;
}
cmd = xcalloc(1, sizeof(struct command) + len - 80);
hashcpy(cmd->old_sha1, old_sha1);
@@ -641,6 +804,11 @@ static const char *unpack(void)
struct pack_header hdr;
const char *hdr_err;
char hdr_arg[38];
+ int fsck_objects = (receive_fsck_objects >= 0
+ ? receive_fsck_objects
+ : transfer_fsck_objects >= 0
+ ? transfer_fsck_objects
+ : 0);
hdr_err = parse_pack_header(&hdr);
if (hdr_err)
@@ -651,9 +819,11 @@ static const char *unpack(void)
if (ntohl(hdr.hdr_entries) < unpack_limit) {
int code, i = 0;
- const char *unpacker[4];
+ const char *unpacker[5];
unpacker[i++] = "unpack-objects";
- if (receive_fsck_objects)
+ if (quiet)
+ unpacker[i++] = "-q";
+ if (fsck_objects)
unpacker[i++] = "--strict";
unpacker[i++] = hdr_arg;
unpacker[i++] = NULL;
@@ -673,7 +843,7 @@ static const char *unpack(void)
keeper[i++] = "index-pack";
keeper[i++] = "--stdin";
- if (receive_fsck_objects)
+ if (fsck_objects)
keeper[i++] = "--strict";
keeper[i++] = "--fix-thin";
keeper[i++] = hdr_arg;
@@ -732,25 +902,6 @@ static int delete_only(struct command *commands)
return 1;
}
-static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused)
-{
- add_extra_ref(".have", sha1, 0);
-}
-
-static void collect_one_alternate_ref(const struct ref *ref, void *data)
-{
- struct sha1_array *sa = data;
- sha1_array_append(sa, ref->old_sha1);
-}
-
-static void add_alternate_refs(void)
-{
- struct sha1_array sa = SHA1_ARRAY_INIT;
- for_each_alternate_ref(collect_one_alternate_ref, &sa);
- sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL);
- sha1_array_clear(&sa);
-}
-
int cmd_receive_pack(int argc, const char **argv, const char *prefix)
{
int advertise_refs = 0;
@@ -766,6 +917,11 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
const char *arg = *argv++;
if (*arg == '-') {
+ if (!strcmp(arg, "--quiet")) {
+ quiet = 1;
+ continue;
+ }
+
if (!strcmp(arg, "--advertise-refs")) {
advertise_refs = 1;
continue;
@@ -800,12 +956,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
unpack_limit = receive_unpack_limit;
if (advertise_refs || !stateless_rpc) {
- add_alternate_refs();
write_head_info();
- clear_extra_refs();
-
- /* EOF */
- packet_flush(1);
}
if (advertise_refs)
return 0;
@@ -820,7 +971,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
unlink_or_warn(pack_lockfile);
if (report_status)
report(commands, unpack_status);
- run_receive_hook(commands, post_receive_hook);
+ run_receive_hook(commands, post_receive_hook, 1);
run_update_post_hook(commands);
if (auto_gc) {
const char *argv_gc_auto[] = {
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 3a9c80f..b3c9e27 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -330,8 +330,10 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
printf("keep %s", message);
return 0;
prune:
- if (!cb->newlog || cb->cmd->verbose)
- printf("%sprune %s", cb->newlog ? "" : "would ", message);
+ if (!cb->newlog)
+ printf("would prune %s", message);
+ else if (cb->cmd->verbose)
+ printf("prune %s", message);
return 0;
}
@@ -647,7 +649,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
init_revisions(&cb.revs, prefix);
if (cb.verbose)
printf("Marking reachable objects...");
- mark_reachable_objects(&cb.revs, 0);
+ mark_reachable_objects(&cb.revs, 0, NULL);
if (cb.verbose)
putchar('\n');
}
diff --git a/builtin/remote.c b/builtin/remote.c
index 9c746af..920262d 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -9,15 +9,15 @@
static const char * const builtin_remote_usage[] = {
"git remote [-v | --verbose]",
- "git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>",
+ "git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>",
"git remote rename <old> <new>",
"git remote rm <name>",
"git remote set-head <name> (-a | -d | <branch>)",
"git remote [-v | --verbose] show [-n] <name>",
"git remote prune [-n | --dry-run] <name>",
"git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]",
- "git remote set-branches <name> [--add] <branch>...",
- "git remote set-url <name> <newurl> [<oldurl>]",
+ "git remote set-branches [--add] <name> <branch>...",
+ "git remote set-url [--push] <name> <newurl> [<oldurl>]",
"git remote set-url --add <name> <newurl>",
"git remote set-url --delete <name> <url>",
NULL
@@ -88,16 +88,6 @@ static inline int postfixcmp(const char *string, const char *postfix)
return strcmp(string + len1 - len2, postfix);
}
-static int opt_parse_track(const struct option *opt, const char *arg, int not)
-{
- struct string_list *list = opt->value;
- if (not)
- string_list_clear(list, 0);
- else
- string_list_append(list, arg);
- return 0;
-}
-
static int fetch_remote(const char *name)
{
const char *argv[] = { "fetch", name, NULL, NULL };
@@ -105,9 +95,9 @@ static int fetch_remote(const char *name)
argv[1] = "-v";
argv[2] = name;
}
- printf("Updating %s\n", name);
+ printf_ln(_("Updating %s"), name);
if (run_command_v_opt(argv, RUN_GIT_CMD))
- return error("Could not fetch %s", name);
+ return error(_("Could not fetch %s"), name);
return 0;
}
@@ -137,8 +127,8 @@ static int add_branch(const char *key, const char *branchname,
}
static const char mirror_advice[] =
-"--mirror is dangerous and deprecated; please\n"
-"\t use --mirror=fetch or --mirror=push instead";
+N_("--mirror is dangerous and deprecated; please\n"
+ "\t use --mirror=fetch or --mirror=push instead");
static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
{
@@ -146,7 +136,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
if (not)
*mirror = MIRROR_NONE;
else if (!arg) {
- warning("%s", mirror_advice);
+ warning("%s", _(mirror_advice));
*mirror = MIRROR_BOTH;
}
else if (!strcmp(arg, "fetch"))
@@ -154,7 +144,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
else if (!strcmp(arg, "push"))
*mirror = MIRROR_PUSH;
else
- return error("unknown mirror argument: %s", arg);
+ return error(_("unknown mirror argument: %s"), arg);
return 0;
}
@@ -176,8 +166,8 @@ static int add(int argc, const char **argv)
TAGS_SET),
OPT_SET_INT(0, NULL, &fetch_tags,
"or do not fetch any tag at all (--no-tags)", TAGS_UNSET),
- OPT_CALLBACK('t', "track", &track, "branch",
- "branch(es) to track", opt_parse_track),
+ OPT_STRING_LIST('t', "track", &track, "branch",
+ "branch(es) to track"),
OPT_STRING('m', "master", &master, "branch", "master branch"),
{ OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch",
"set up remote as a mirror to push to or fetch from",
@@ -192,9 +182,9 @@ static int add(int argc, const char **argv)
usage_with_options(builtin_remote_add_usage, options);
if (mirror && master)
- die("specifying a master branch makes no sense with --mirror");
+ die(_("specifying a master branch makes no sense with --mirror"));
if (mirror && !(mirror & MIRROR_FETCH) && track.nr)
- die("specifying branches to track makes sense only with fetch mirrors");
+ die(_("specifying branches to track makes sense only with fetch mirrors"));
name = argv[0];
url = argv[1];
@@ -202,11 +192,11 @@ static int add(int argc, const char **argv)
remote = remote_get(name);
if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
remote->fetch_refspec_nr))
- die("remote %s already exists.", name);
+ die(_("remote %s already exists."), name);
strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
if (!valid_fetch_refspec(buf2.buf))
- die("'%s' is not a valid remote name", name);
+ die(_("'%s' is not a valid remote name"), name);
strbuf_addf(&buf, "remote.%s.url", name);
if (git_config_set(buf.buf, url))
@@ -250,7 +240,7 @@ static int add(int argc, const char **argv)
strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
if (create_symref(buf.buf, buf2.buf, "remote add"))
- return error("Could not setup master '%s'", master);
+ return error(_("Could not setup master '%s'"), master);
}
strbuf_release(&buf);
@@ -306,7 +296,7 @@ static int config_read_branches(const char *key, const char *value, void *cb)
info = item->util;
if (type == REMOTE) {
if (info->remote_name)
- warning("more than one %s", orig_key);
+ warning(_("more than one %s"), orig_key);
info->remote_name = xstrdup(value);
} else if (type == MERGE) {
char *space = strchr(value, ' ');
@@ -346,20 +336,20 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
for (i = 0; i < states->remote->fetch_refspec_nr; i++)
if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
- die("Could not get fetch map for refspec %s",
+ die(_("Could not get fetch map for refspec %s"),
states->remote->fetch_refspec[i]);
states->new.strdup_strings = 1;
states->tracked.strdup_strings = 1;
states->stale.strdup_strings = 1;
for (ref = fetch_map; ref; ref = ref->next) {
- unsigned char sha1[20];
- if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
+ if (!ref->peer_ref || !ref_exists(ref->peer_ref->name))
string_list_append(&states->new, abbrev_branch(ref->name));
else
string_list_append(&states->tracked, abbrev_branch(ref->name));
}
- stale_refs = get_stale_heads(states->remote, fetch_map);
+ stale_refs = get_stale_heads(states->remote->fetch,
+ states->remote->fetch_refspec_nr, fetch_map);
for (ref = stale_refs; ref; ref = ref->next) {
struct string_list_item *item =
string_list_append(&states->stale, abbrev_branch(ref->name));
@@ -399,8 +389,8 @@ static int get_push_ref_states(const struct ref *remote_refs,
local_refs = get_local_heads();
push_map = copy_ref_list(remote_refs);
- match_refs(local_refs, &push_map, remote->push_refspec_nr,
- remote->push_refspec, MATCH_REFS_NONE);
+ match_push_refs(local_refs, &push_map, remote->push_refspec_nr,
+ remote->push_refspec, MATCH_REFS_NONE);
states->push.strdup_strings = 1;
for (ref = push_map; ref; ref = ref->next) {
@@ -447,7 +437,7 @@ static int get_push_ref_states_noquery(struct ref_states *states)
states->push.strdup_strings = 1;
if (!remote->push_refspec_nr) {
- item = string_list_append(&states->push, "(matching)");
+ item = string_list_append(&states->push, _("(matching)"));
info = item->util = xcalloc(sizeof(struct push_info), 1);
info->status = PUSH_STATUS_NOTQUERIED;
info->dest = xstrdup(item->string);
@@ -455,11 +445,11 @@ static int get_push_ref_states_noquery(struct ref_states *states)
for (i = 0; i < remote->push_refspec_nr; i++) {
struct refspec *spec = remote->push + i;
if (spec->matching)
- item = string_list_append(&states->push, "(matching)");
+ item = string_list_append(&states->push, _("(matching)"));
else if (strlen(spec->src))
item = string_list_append(&states->push, spec->src);
else
- item = string_list_append(&states->push, "(delete)");
+ item = string_list_append(&states->push, _("(delete)"));
info = item->util = xcalloc(sizeof(struct push_info), 1);
info->forced = spec->force;
@@ -544,7 +534,7 @@ static int add_branch_for_removal(const char *refname,
}
/* don't delete non-remote-tracking refs */
- if (prefixcmp(refname, "refs/remotes")) {
+ if (prefixcmp(refname, "refs/remotes/")) {
/* advise user how to delete local branches */
if (!prefixcmp(refname, "refs/heads/"))
string_list_append(branches->skipped,
@@ -583,7 +573,7 @@ static int read_remote_branches(const char *refname,
strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
if (!prefixcmp(refname, buf.buf)) {
item = string_list_append(rename->remote_branches, xstrdup(refname));
- symref = resolve_ref(refname, orig_sha1, 1, &flag);
+ symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag);
if (flag & REF_ISSYMREF)
item->util = xstrdup(symref);
else
@@ -602,19 +592,19 @@ static int migrate_file(struct remote *remote)
strbuf_addf(&buf, "remote.%s.url", remote->name);
for (i = 0; i < remote->url_nr; i++)
if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->url[i], buf.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.push", remote->name);
for (i = 0; i < remote->push_refspec_nr; i++)
if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->push_refspec[i], buf.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.fetch", remote->name);
for (i = 0; i < remote->fetch_refspec_nr; i++)
if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->fetch_refspec[i], buf.buf);
if (remote->origin == REMOTE_REMOTES)
path = git_path("remotes/%s", remote->name);
@@ -646,30 +636,30 @@ static int mv(int argc, const char **argv)
oldremote = remote_get(rename.old);
if (!oldremote)
- die("No such remote: %s", rename.old);
+ die(_("No such remote: %s"), rename.old);
if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
return migrate_file(oldremote);
newremote = remote_get(rename.new);
if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
- die("remote %s already exists.", rename.new);
+ die(_("remote %s already exists."), rename.new);
strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
if (!valid_fetch_refspec(buf.buf))
- die("'%s' is not a valid remote name", rename.new);
+ die(_("'%s' is not a valid remote name"), rename.new);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s", rename.old);
strbuf_addf(&buf2, "remote.%s", rename.new);
if (git_config_rename_section(buf.buf, buf2.buf) < 1)
- return error("Could not rename config section '%s' to '%s'",
+ return error(_("Could not rename config section '%s' to '%s'"),
buf.buf, buf2.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.fetch", rename.new);
if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
- return error("Could not remove config section '%s'", buf.buf);
+ return error(_("Could not remove config section '%s'"), buf.buf);
strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
char *ptr;
@@ -684,13 +674,13 @@ static int mv(int argc, const char **argv)
strlen(rename.old), rename.new,
strlen(rename.new));
} else
- warning("Not updating non-default fetch respec\n"
- "\t%s\n"
- "\tPlease update the configuration manually if necessary.",
+ warning(_("Not updating non-default fetch refspec\n"
+ "\t%s\n"
+ "\tPlease update the configuration manually if necessary."),
buf2.buf);
if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
- return error("Could not append '%s'", buf.buf);
+ return error(_("Could not append '%s'"), buf.buf);
}
read_branches();
@@ -701,7 +691,7 @@ static int mv(int argc, const char **argv)
strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.remote", item->string);
if (git_config_set(buf.buf, rename.new)) {
- return error("Could not set '%s'", buf.buf);
+ return error(_("Could not set '%s'"), buf.buf);
}
}
}
@@ -719,11 +709,11 @@ static int mv(int argc, const char **argv)
int flag = 0;
unsigned char sha1[20];
- resolve_ref(item->string, sha1, 1, &flag);
+ read_ref_full(item->string, sha1, 1, &flag);
if (!(flag & REF_ISSYMREF))
continue;
if (delete_ref(item->string, NULL, REF_NODEREF))
- die("deleting '%s' failed", item->string);
+ die(_("deleting '%s' failed"), item->string);
}
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
@@ -738,7 +728,7 @@ static int mv(int argc, const char **argv)
strbuf_addf(&buf2, "remote: renamed %s to %s",
item->string, buf.buf);
if (rename_ref(item->string, buf.buf, buf2.buf))
- die("renaming '%s' failed", item->string);
+ die(_("renaming '%s' failed"), item->string);
}
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
@@ -757,7 +747,7 @@ static int mv(int argc, const char **argv)
strbuf_addf(&buf3, "remote: renamed %s to %s",
item->string, buf.buf);
if (create_symref(buf.buf, buf2.buf, buf3.buf))
- die("creating '%s' failed", buf.buf);
+ die(_("creating '%s' failed"), buf.buf);
}
return 0;
}
@@ -771,7 +761,7 @@ static int remove_branches(struct string_list *branches)
unsigned char *sha1 = item->util;
if (delete_ref(refname, sha1, 0))
- result |= error("Could not remove branch %s", refname);
+ result |= error(_("Could not remove branch %s"), refname);
}
return result;
}
@@ -799,14 +789,14 @@ static int rm(int argc, const char **argv)
remote = remote_get(argv[1]);
if (!remote)
- die("No such remote: %s", argv[1]);
+ die(_("No such remote: %s"), argv[1]);
known_remotes.to_delete = remote;
for_each_remote(add_known_remote, &known_remotes);
strbuf_addf(&buf, "remote.%s", remote->name);
if (git_config_rename_section(buf.buf, NULL) < 1)
- return error("Could not remove config section '%s'", buf.buf);
+ return error(_("Could not remove config section '%s'"), buf.buf);
read_branches();
for (i = 0; i < branch_list.nr; i++) {
@@ -840,11 +830,12 @@ static int rm(int argc, const char **argv)
string_list_clear(&branches, 1);
if (skipped.nr) {
- fprintf(stderr, skipped.nr == 1 ?
- "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
- "to delete it, use:\n" :
- "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
- "to delete them, use:\n");
+ fprintf_ln(stderr,
+ Q_("Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+ "to delete it, use:",
+ "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+ "to delete them, use:",
+ skipped.nr));
for (i = 0; i < skipped.nr; i++)
fprintf(stderr, " git branch -d %s\n",
skipped.items[i].string);
@@ -896,7 +887,7 @@ static int get_remote_ref_states(const char *name,
states->remote = remote_get(name);
if (!states->remote)
- return error("No such remote: %s", name);
+ return error(_("No such remote: %s"), name);
read_branches();
@@ -949,14 +940,14 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data)
const char *fmt = "%s";
const char *arg = "";
if (string_list_has_string(&states->new, name)) {
- fmt = " new (next fetch will store in remotes/%s)";
+ fmt = _(" new (next fetch will store in remotes/%s)");
arg = states->remote->name;
} else if (string_list_has_string(&states->tracked, name))
- arg = " tracked";
+ arg = _(" tracked");
else if (string_list_has_string(&states->stale, name))
- arg = " stale (use 'git remote prune' to remove)";
+ arg = _(" stale (use 'git remote prune' to remove)");
else
- arg = " ???";
+ arg = _(" ???");
printf(" %-*s", info->width, name);
printf(fmt, arg);
printf("\n");
@@ -997,21 +988,21 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
int i;
if (branch_info->rebase && branch_info->merge.nr > 1) {
- error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+ error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"),
item->string);
return 0;
}
printf(" %-*s ", show_info->width, item->string);
if (branch_info->rebase) {
- printf("rebases onto remote %s\n", merge->items[0].string);
+ printf_ln(_("rebases onto remote %s"), merge->items[0].string);
return 0;
} else if (show_info->any_rebase) {
- printf(" merges with remote %s\n", merge->items[0].string);
- also = " and with remote";
+ printf_ln(_(" merges with remote %s"), merge->items[0].string);
+ also = _(" and with remote");
} else {
- printf("merges with remote %s\n", merge->items[0].string);
- also = " and with remote";
+ printf_ln(_("merges with remote %s"), merge->items[0].string);
+ also = _(" and with remote");
}
for (i = 1; i < merge->nr; i++)
printf(" %-*s %s %s\n", show_info->width, "", also,
@@ -1053,36 +1044,43 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data)
{
struct show_info *show_info = cb_data;
struct push_info *push_info = item->util;
- char *src = item->string, *status = NULL;
+ const char *src = item->string, *status = NULL;
switch (push_info->status) {
case PUSH_STATUS_CREATE:
- status = "create";
+ status = _("create");
break;
case PUSH_STATUS_DELETE:
- status = "delete";
- src = "(none)";
+ status = _("delete");
+ src = _("(none)");
break;
case PUSH_STATUS_UPTODATE:
- status = "up to date";
+ status = _("up to date");
break;
case PUSH_STATUS_FASTFORWARD:
- status = "fast-forwardable";
+ status = _("fast-forwardable");
break;
case PUSH_STATUS_OUTOFDATE:
- status = "local out of date";
+ status = _("local out of date");
break;
case PUSH_STATUS_NOTQUERIED:
break;
}
- if (status)
- printf(" %-*s %s to %-*s (%s)\n", show_info->width, src,
- push_info->forced ? "forces" : "pushes",
- show_info->width2, push_info->dest, status);
- else
- printf(" %-*s %s to %s\n", show_info->width, src,
- push_info->forced ? "forces" : "pushes",
- push_info->dest);
+ if (status) {
+ if (push_info->forced)
+ printf_ln(_(" %-*s forces to %-*s (%s)"), show_info->width, src,
+ show_info->width2, push_info->dest, status);
+ else
+ printf_ln(_(" %-*s pushes to %-*s (%s)"), show_info->width, src,
+ show_info->width2, push_info->dest, status);
+ } else {
+ if (push_info->forced)
+ printf_ln(_(" %-*s forces to %s"), show_info->width, src,
+ push_info->dest);
+ else
+ printf_ln(_(" %-*s pushes to %s"), show_info->width, src,
+ push_info->dest);
+ }
return 0;
}
@@ -1117,9 +1115,9 @@ static int show(int argc, const char **argv)
get_remote_ref_states(*argv, &states, query_flag);
- printf("* remote %s\n", *argv);
- printf(" Fetch URL: %s\n", states.remote->url_nr > 0 ?
- states.remote->url[0] : "(no URL)");
+ printf_ln(_("* remote %s"), *argv);
+ printf_ln(_(" Fetch URL: %s"), states.remote->url_nr > 0 ?
+ states.remote->url[0] : _("(no URL)"));
if (states.remote->pushurl_nr) {
url = states.remote->pushurl;
url_nr = states.remote->pushurl_nr;
@@ -1128,18 +1126,18 @@ static int show(int argc, const char **argv)
url_nr = states.remote->url_nr;
}
for (i = 0; i < url_nr; i++)
- printf(" Push URL: %s\n", url[i]);
+ printf_ln(_(" Push URL: %s"), url[i]);
if (!i)
- printf(" Push URL: %s\n", "(no URL)");
+ printf_ln(_(" Push URL: %s"), "(no URL)");
if (no_query)
- printf(" HEAD branch: (not queried)\n");
+ printf_ln(_(" HEAD branch: %s"), "(not queried)");
else if (!states.heads.nr)
- printf(" HEAD branch: (unknown)\n");
+ printf_ln(_(" HEAD branch: %s"), "(unknown)");
else if (states.heads.nr == 1)
- printf(" HEAD branch: %s\n", states.heads.items[0].string);
+ printf_ln(_(" HEAD branch: %s"), states.heads.items[0].string);
else {
- printf(" HEAD branch (remote HEAD is ambiguous,"
- " may be one of the following):\n");
+ printf(_(" HEAD branch (remote HEAD is ambiguous,"
+ " may be one of the following):\n"));
for (i = 0; i < states.heads.nr; i++)
printf(" %s\n", states.heads.items[i].string);
}
@@ -1150,9 +1148,10 @@ static int show(int argc, const char **argv)
for_each_string_list(&states.tracked, add_remote_to_show_info, &info);
for_each_string_list(&states.stale, add_remote_to_show_info, &info);
if (info.list->nr)
- printf(" Remote branch%s:%s\n",
- info.list->nr > 1 ? "es" : "",
- no_query ? " (status not queried)" : "");
+ printf_ln(Q_(" Remote branch:%s",
+ " Remote branches:%s",
+ info.list->nr),
+ no_query ? _(" (status not queried)") : "");
for_each_string_list(info.list, show_remote_info_item, &info);
string_list_clear(info.list, 0);
@@ -1161,23 +1160,25 @@ static int show(int argc, const char **argv)
info.any_rebase = 0;
for_each_string_list(&branch_list, add_local_to_show_info, &info);
if (info.list->nr)
- printf(" Local branch%s configured for 'git pull':\n",
- info.list->nr > 1 ? "es" : "");
+ printf_ln(Q_(" Local branch configured for 'git pull':",
+ " Local branches configured for 'git pull':",
+ info.list->nr));
for_each_string_list(info.list, show_local_info_item, &info);
string_list_clear(info.list, 0);
/* git push info */
if (states.remote->mirror)
- printf(" Local refs will be mirrored by 'git push'\n");
+ printf_ln(_(" Local refs will be mirrored by 'git push'"));
info.width = info.width2 = 0;
for_each_string_list(&states.push, add_push_to_show_info, &info);
qsort(info.list->items, info.list->nr,
sizeof(*info.list->items), cmp_string_with_push);
if (info.list->nr)
- printf(" Local ref%s configured for 'git push'%s:\n",
- info.list->nr > 1 ? "s" : "",
- no_query ? " (status not queried)" : "");
+ printf_ln(Q_(" Local ref configured for 'git push'%s:",
+ " Local refs configured for 'git push'%s:",
+ info.list->nr),
+ no_query ? _(" (status not queried)") : "");
for_each_string_list(info.list, show_push_info_item, &info);
string_list_clear(info.list, 0);
@@ -1212,10 +1213,10 @@ static int set_head(int argc, const char **argv)
memset(&states, 0, sizeof(states));
get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
if (!states.heads.nr)
- result |= error("Cannot determine remote HEAD");
+ result |= error(_("Cannot determine remote HEAD"));
else if (states.heads.nr > 1) {
- result |= error("Multiple remote HEAD branches. "
- "Please choose one explicitly with:");
+ result |= error(_("Multiple remote HEAD branches. "
+ "Please choose one explicitly with:"));
for (i = 0; i < states.heads.nr; i++)
fprintf(stderr, " git remote set-head %s %s\n",
argv[0], states.heads.items[i].string);
@@ -1224,18 +1225,17 @@ static int set_head(int argc, const char **argv)
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
if (delete_ref(buf.buf, NULL, REF_NODEREF))
- result |= error("Could not delete %s", buf.buf);
+ result |= error(_("Could not delete %s"), buf.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
- unsigned char sha1[20];
strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!resolve_ref(buf2.buf, sha1, 1, NULL))
- result |= error("Not a valid ref: %s", buf2.buf);
+ if (!ref_exists(buf2.buf))
+ result |= error(_("Not a valid ref: %s"), buf2.buf);
else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
- result |= error("Could not setup %s", buf.buf);
+ result |= error(_("Could not setup %s"), buf.buf);
if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
@@ -1271,18 +1271,18 @@ static int prune_remote(const char *remote, int dry_run)
int result = 0, i;
struct ref_states states;
const char *dangling_msg = dry_run
- ? " %s will become dangling!\n"
- : " %s has become dangling!\n";
+ ? _(" %s will become dangling!")
+ : _(" %s has become dangling!");
memset(&states, 0, sizeof(states));
get_remote_ref_states(remote, &states, GET_REF_STATES);
if (states.stale.nr) {
- printf("Pruning %s\n", remote);
- printf("URL: %s\n",
+ printf_ln(_("Pruning %s"), remote);
+ printf_ln(_("URL: %s"),
states.remote->url_nr
? states.remote->url[0]
- : "(no URL)");
+ : _("(no URL)"));
}
for (i = 0; i < states.stale.nr; i++) {
@@ -1291,8 +1291,12 @@ static int prune_remote(const char *remote, int dry_run)
if (!dry_run)
result |= delete_ref(refname, NULL, 0);
- printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
- abbrev_ref(refname, "refs/remotes/"));
+ if (dry_run)
+ printf_ln(_(" * [would prune] %s"),
+ abbrev_ref(refname, "refs/remotes/"));
+ else
+ printf_ln(_(" * [pruned] %s"),
+ abbrev_ref(refname, "refs/remotes/"));
warn_dangling_symref(stdout, dangling_msg, refname);
}
@@ -1380,7 +1384,7 @@ static int set_remote_branches(const char *remotename, const char **branches,
strbuf_addf(&key, "remote.%s.fetch", remotename);
if (!remote_is_configured(remotename))
- die("No such remote '%s'", remotename);
+ die(_("No such remote '%s'"), remotename);
remote = remote_get(remotename);
if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
@@ -1407,8 +1411,8 @@ static int set_branches(int argc, const char **argv)
argc = parse_options(argc, argv, NULL, options,
builtin_remote_setbranches_usage, 0);
if (argc == 0) {
- error("no remote specified");
- usage_with_options(builtin_remote_seturl_usage, options);
+ error(_("no remote specified"));
+ usage_with_options(builtin_remote_setbranches_usage, options);
}
argv[argc] = NULL;
@@ -1436,11 +1440,11 @@ static int set_url(int argc, const char **argv)
"delete URLs"),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage,
PARSE_OPT_KEEP_ARGV0);
if (add_mode && delete_mode)
- die("--add --delete doesn't make sense");
+ die(_("--add --delete doesn't make sense"));
if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
usage_with_options(builtin_remote_seturl_usage, options);
@@ -1454,7 +1458,7 @@ static int set_url(int argc, const char **argv)
oldurl = newurl;
if (!remote_is_configured(remotename))
- die("No such remote '%s'", remotename);
+ die(_("No such remote '%s'"), remotename);
remote = remote_get(remotename);
if (push_mode) {
@@ -1480,7 +1484,7 @@ static int set_url(int argc, const char **argv)
/* Old URL specified. Demand that one matches. */
if (regcomp(&old_regex, oldurl, REG_EXTENDED))
- die("Invalid old URL pattern: %s", oldurl);
+ die(_("Invalid old URL pattern: %s"), oldurl);
for (i = 0; i < urlset_nr; i++)
if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
@@ -1488,9 +1492,9 @@ static int set_url(int argc, const char **argv)
else
negative_matches++;
if (!delete_mode && !matches)
- die("No such URL found: %s", oldurl);
+ die(_("No such URL found: %s"), oldurl);
if (delete_mode && !negative_matches && !push_mode)
- die("Will not delete all non-push URLs");
+ die(_("Will not delete all non-push URLs"));
regfree(&old_regex);
@@ -1591,7 +1595,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
else if (!strcmp(argv[0], "update"))
result = update(argc, argv);
else {
- error("Unknown subcommand: %s", argv[0]);
+ error(_("Unknown subcommand: %s"), argv[0]);
usage_with_options(builtin_remote_usage, options);
}
diff --git a/builtin/replace.c b/builtin/replace.c
index fe3a647..4a8970e 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -58,7 +58,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
had_error = 1;
continue;
}
- if (!resolve_ref(ref, sha1, 1, NULL)) {
+ if (read_ref(ref, sha1)) {
error("replace ref '%s' not found.", *p);
had_error = 1;
continue;
@@ -94,10 +94,10 @@ static int replace_object(const char *object_ref, const char *replace_ref,
"refs/replace/%s",
sha1_to_hex(object)) > sizeof(ref) - 1)
die("replace ref name too long: %.*s...", 50, ref);
- if (check_ref_format(ref))
+ if (check_refname_format(ref, 0))
die("'%s' is not a valid ref name.", ref);
- if (!resolve_ref(ref, prev, 1, NULL))
+ if (read_ref(ref, prev))
hashclr(prev);
else if (!force)
die("replace ref '%s' already exists", ref);
diff --git a/builtin/reset.c b/builtin/reset.c
index 811e8e2..4cc34c9 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -43,6 +43,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
int nr = 1;
int newfd;
struct tree_desc desc[2];
+ struct tree *tree;
struct unpack_trees_options opts;
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
@@ -84,6 +85,12 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
return error(_("Failed to find tree of %s."), sha1_to_hex(sha1));
if (unpack_trees(nr, desc, &opts))
return -1;
+
+ if (reset_type == MIXED || reset_type == HARD) {
+ tree = parse_tree_indirect(sha1);
+ prime_cache_tree(&active_cache_tree, tree);
+ }
+
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock))
return error(_("Could not write new index file."));
@@ -278,7 +285,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
rev = argv[i++];
} else {
/* Otherwise we treat this as a filename */
- verify_filename(prefix, argv[i]);
+ verify_filename(prefix, argv[i], 1);
}
}
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 56727e8..ff5a383 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -52,6 +52,11 @@ static void show_commit(struct commit *commit, void *data)
struct rev_list_info *info = data;
struct rev_info *revs = info->revs;
+ if (info->flags & REV_LIST_QUIET) {
+ finish_commit(commit, data);
+ return;
+ }
+
graph_show_commit(revs->graph);
if (revs->count) {
@@ -104,6 +109,7 @@ static void show_commit(struct commit *commit, void *data)
struct pretty_print_context ctx = {0};
ctx.abbrev = revs->abbrev;
ctx.date_mode = revs->date_mode;
+ ctx.date_mode_explicit = revs->date_mode_explicit;
ctx.fmt = revs->commit_format;
pretty_print_commit(&ctx, commit, &buf);
if (revs->graph) {
@@ -168,29 +174,26 @@ static void finish_commit(struct commit *commit, void *data)
commit->buffer = NULL;
}
-static void finish_object(struct object *obj, const struct name_path *path, const char *name)
+static void finish_object(struct object *obj,
+ const struct name_path *path, const char *name,
+ void *cb_data)
{
+ struct rev_list_info *info = cb_data;
if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
die("missing blob object '%s'", sha1_to_hex(obj->sha1));
+ if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
+ parse_object(obj->sha1);
}
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
+static void show_object(struct object *obj,
+ const struct name_path *path, const char *component,
+ void *cb_data)
{
- char *name = path_name(path, component);
- /* An object with name "foo\n0000000..." can be used to
- * confuse downstream "git pack-objects" very badly.
- */
- const char *ep = strchr(name, '\n');
-
- finish_object(obj, path, name);
- if (ep) {
- printf("%s %.*s\n", sha1_to_hex(obj->sha1),
- (int) (ep - name),
- name);
- }
- else
- printf("%s %s\n", sha1_to_hex(obj->sha1), name);
- free(name);
+ struct rev_list_info *info = cb_data;
+ finish_object(obj, path, component, cb_data);
+ if (info->flags & REV_LIST_QUIET)
+ return;
+ show_object_with_name(stdout, obj, path, component);
}
static void show_edge(struct commit *commit)
@@ -247,13 +250,6 @@ void print_commit_list(struct commit_list *list,
}
}
-static void show_tried_revs(struct commit_list *tried)
-{
- printf("bisect_tried='");
- print_commit_list(tried, "%s|", "%s");
- printf("'\n");
-}
-
static void print_var_str(const char *var, const char *val)
{
printf("%s='%s'\n", var, val);
@@ -266,12 +262,12 @@ static void print_var_int(const char *var, int val)
static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
{
- int cnt, flags = info->bisect_show_flags;
+ int cnt, flags = info->flags;
char hex[41] = "";
struct commit_list *tried;
struct rev_info *revs = info->revs;
- if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
+ if (!revs->commits)
return 1;
revs->commits = filter_skipped(revs->commits, &tried,
@@ -299,9 +295,6 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
printf("------\n");
}
- if (flags & BISECT_SHOW_TRIED)
- show_tried_revs(tried);
-
print_var_str("bisect_rev", hex);
print_var_int("bisect_nr", cnt - 1);
print_var_int("bisect_good", all - reaches - 1);
@@ -320,7 +313,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
int bisect_list = 0;
int bisect_show_vars = 0;
int bisect_find_all = 0;
- int quiet = 0;
git_config(git_default_config, NULL);
init_revisions(&revs, prefix);
@@ -333,7 +325,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (revs.bisect)
bisect_list = 1;
- quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
+ if (DIFF_OPT_TST(&revs.diffopt, QUICK))
+ info.flags |= REV_LIST_QUIET;
for (i = 1 ; i < argc; i++) {
const char *arg = argv[i];
@@ -352,7 +345,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--bisect-all")) {
bisect_list = 1;
bisect_find_all = 1;
- info.bisect_show_flags = BISECT_SHOW_ALL;
+ info.flags |= BISECT_SHOW_ALL;
revs.show_decorations = 1;
continue;
}
@@ -403,10 +396,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
return show_bisect_vars(&info, reaches, all);
}
- traverse_commit_list(&revs,
- quiet ? finish_commit : show_commit,
- quiet ? finish_object : show_object,
- &info);
+ traverse_commit_list(&revs, show_commit, show_object, &info);
if (revs.count) {
if (revs.left_right && revs.cherry_mark)
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 4c19f84..13495b8 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -468,6 +468,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
return 0;
}
+ if (argc > 2 && !strcmp(argv[1], "--resolve-git-dir")) {
+ const char *gitdir = resolve_gitdir(argv[2]);
+ if (!gitdir)
+ die("not a gitdir '%s'", argv[2]);
+ puts(gitdir);
+ return 0;
+ }
+
if (argc > 1 && !strcmp("-h", argv[1]))
usage(builtin_rev_parse_usage);
@@ -478,7 +486,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (as_is) {
if (show_file(arg) && as_is < 2)
- verify_filename(prefix, arg);
+ verify_filename(prefix, arg, 0);
continue;
}
if (!strcmp(arg,"-n")) {
@@ -626,6 +634,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--show-prefix")) {
if (prefix)
puts(prefix);
+ else
+ putchar('\n');
continue;
}
if (!strcmp(arg, "--show-cdup")) {
@@ -724,7 +734,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
as_is = 1;
if (!show_file(arg))
continue;
- verify_filename(prefix, arg);
+ verify_filename(prefix, arg, 1);
}
if (verify) {
if (revs_count == 1) {
diff --git a/builtin/revert.c b/builtin/revert.c
index 1f27c63..82d1bf8 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -1,18 +1,11 @@
#include "cache.h"
#include "builtin.h"
-#include "object.h"
-#include "commit.h"
-#include "tag.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "utf8.h"
#include "parse-options.h"
-#include "cache-tree.h"
#include "diff.h"
#include "revision.h"
#include "rerere.h"
-#include "merge-recursive.h"
-#include "refs.h"
+#include "dir.h"
+#include "sequencer.h"
/*
* This implements the builtins revert and cherry-pick.
@@ -27,579 +20,219 @@
static const char * const revert_usage[] = {
"git revert [options] <commit-ish>",
+ "git revert <subcommand>",
NULL
};
static const char * const cherry_pick_usage[] = {
"git cherry-pick [options] <commit-ish>",
+ "git cherry-pick <subcommand>",
NULL
};
-static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
-static enum { REVERT, CHERRY_PICK } action;
-static struct commit *commit;
-static int commit_argc;
-static const char **commit_argv;
-static int allow_rerere_auto;
-
-static const char *me;
-
-/* Merge strategy. */
-static const char *strategy;
-static const char **xopts;
-static size_t xopts_nr, xopts_alloc;
-
-#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-
-static char *get_encoding(const char *message);
+static const char *action_name(const struct replay_opts *opts)
+{
+ return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
+}
-static const char * const *revert_or_cherry_pick_usage(void)
+static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
{
- return action == REVERT ? revert_usage : cherry_pick_usage;
+ return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;
}
static int option_parse_x(const struct option *opt,
const char *arg, int unset)
{
+ struct replay_opts **opts_ptr = opt->value;
+ struct replay_opts *opts = *opts_ptr;
+
if (unset)
return 0;
- ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
- xopts[xopts_nr++] = xstrdup(arg);
- return 0;
-}
-
-static void parse_args(int argc, const char **argv)
-{
- const char * const * usage_str = revert_or_cherry_pick_usage();
- 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"),
- { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
- OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
- OPT_INTEGER('m', "mainline", &mainline, "parent number"),
- OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
- OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
- OPT_CALLBACK('X', "strategy-option", &xopts, "option",
- "option for merge strategy", option_parse_x),
- OPT_END(),
- OPT_END(),
- OPT_END(),
- };
-
- if (action == CHERRY_PICK) {
- struct option cp_extra[] = {
- OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
- OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
- OPT_END(),
- };
- if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
- die(_("program error"));
- }
-
- commit_argc = parse_options(argc, argv, NULL, options, usage_str,
- PARSE_OPT_KEEP_ARGV0 |
- PARSE_OPT_KEEP_UNKNOWN);
- if (commit_argc < 2)
- usage_with_options(usage_str, options);
-
- commit_argv = argv;
-}
-
-struct commit_message {
- char *parent_label;
- const char *label;
- const char *subject;
- char *reencoded_message;
- const char *message;
-};
-
-static int get_message(const char *raw_message, struct commit_message *out)
-{
- const char *encoding;
- const char *abbrev, *subject;
- int abbrev_len, subject_len;
- char *q;
-
- if (!raw_message)
- return -1;
- encoding = get_encoding(raw_message);
- if (!encoding)
- encoding = "UTF-8";
- if (!git_commit_encoding)
- git_commit_encoding = "UTF-8";
-
- out->reencoded_message = NULL;
- out->message = raw_message;
- if (strcmp(encoding, git_commit_encoding))
- out->reencoded_message = reencode_string(raw_message,
- git_commit_encoding, encoding);
- if (out->reencoded_message)
- out->message = out->reencoded_message;
-
- abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
- abbrev_len = strlen(abbrev);
-
- subject_len = find_commit_subject(out->message, &subject);
-
- out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
- strlen("... ") + subject_len + 1);
- q = out->parent_label;
- q = mempcpy(q, "parent of ", strlen("parent of "));
- out->label = q;
- q = mempcpy(q, abbrev, abbrev_len);
- q = mempcpy(q, "... ", strlen("... "));
- out->subject = q;
- q = mempcpy(q, subject, subject_len);
- *q = '\0';
+ ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+ opts->xopts[opts->xopts_nr++] = xstrdup(arg);
return 0;
}
-static void free_message(struct commit_message *msg)
-{
- free(msg->parent_label);
- free(msg->reencoded_message);
-}
-
-static char *get_encoding(const char *message)
+static void verify_opt_compatible(const char *me, const char *base_opt, ...)
{
- const char *p = message, *eol;
+ const char *this_opt;
+ va_list ap;
- if (!p)
- die (_("Could not read commit message of %s"),
- sha1_to_hex(commit->object.sha1));
- while (*p && *p != '\n') {
- for (eol = p + 1; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
- if (!prefixcmp(p, "encoding ")) {
- char *result = xmalloc(eol - 8 - p);
- strlcpy(result, p + 9, eol - 8 - p);
- return result;
- }
- p = eol;
- if (*p == '\n')
- p++;
+ va_start(ap, base_opt);
+ while ((this_opt = va_arg(ap, const char *))) {
+ if (va_arg(ap, int))
+ break;
}
- return NULL;
-}
-
-static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
-{
- const char *p = message;
- while (*p && (*p != '\n' || p[1] != '\n'))
- p++;
-
- if (!*p)
- strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1));
-
- p += 2;
- strbuf_addstr(msgbuf, p);
-}
-
-static void write_cherry_pick_head(void)
-{
- int fd;
- struct strbuf buf = STRBUF_INIT;
-
- strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
+ va_end(ap);
- fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno(_("Could not open '%s' for writing"),
- git_path("CHERRY_PICK_HEAD"));
- if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
- die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD"));
- strbuf_release(&buf);
+ if (this_opt)
+ die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
}
-static void advise(const char *advice, ...)
+static void verify_opt_mutually_compatible(const char *me, ...)
{
- va_list params;
+ const char *opt1, *opt2 = NULL;
+ va_list ap;
- va_start(params, advice);
- vreportf("hint: ", advice, params);
- va_end(params);
-}
-
-static void print_advice(void)
-{
- char *msg = getenv("GIT_CHERRY_PICK_HELP");
-
- if (msg) {
- fprintf(stderr, "%s\n", msg);
- /*
- * A conflict has occured but the porcelain
- * (typically rebase --interactive) wants to take care
- * of the commit itself so remove CHERRY_PICK_HEAD
- */
- unlink(git_path("CHERRY_PICK_HEAD"));
- return;
+ va_start(ap, me);
+ while ((opt1 = va_arg(ap, const char *))) {
+ if (va_arg(ap, int))
+ break;
}
-
- advise("after resolving the conflicts, mark the corrected paths");
- advise("with 'git add <paths>' or 'git rm <paths>'");
- advise("and commit the result with 'git commit'");
-}
-
-static void write_message(struct strbuf *msgbuf, const char *filename)
-{
- static struct lock_file msg_file;
-
- int msg_fd = hold_lock_file_for_update(&msg_file, filename,
- LOCK_DIE_ON_ERROR);
- if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
- die_errno(_("Could not write to %s."), filename);
- strbuf_release(msgbuf);
- if (commit_lock_file(&msg_file) < 0)
- die(_("Error wrapping up %s"), filename);
-}
-
-static struct tree *empty_tree(void)
-{
- struct tree *tree = xcalloc(1, sizeof(struct tree));
-
- tree->object.parsed = 1;
- tree->object.type = OBJ_TREE;
- pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
- return tree;
-}
-
-static NORETURN void die_dirty_index(const char *me)
-{
- if (read_cache_unmerged()) {
- die_resolve_conflict(me);
- } else {
- if (advice_commit_before_merge) {
- if (action == REVERT)
- die(_("Your local changes would be overwritten by revert.\n"
- "Please, commit your changes or stash them to proceed."));
- else
- die(_("Your local changes would be overwritten by cherry-pick.\n"
- "Please, commit your changes or stash them to proceed."));
- } else {
- if (action == REVERT)
- die(_("Your local changes would be overwritten by revert.\n"));
- else
- die(_("Your local changes would be overwritten by cherry-pick.\n"));
+ if (opt1) {
+ while ((opt2 = va_arg(ap, const char *))) {
+ if (va_arg(ap, int))
+ break;
}
}
-}
-
-static int fast_forward_to(const unsigned char *to, const unsigned char *from)
-{
- struct ref_lock *ref_lock;
+ va_end(ap);
- read_cache();
- if (checkout_fast_forward(from, to))
- exit(1); /* the callee should have complained already */
- ref_lock = lock_any_ref_for_update("HEAD", from, 0);
- return write_ref_sha1(ref_lock, to, "cherry-pick");
+ if (opt1 && opt2)
+ die(_("%s: %s cannot be used with %s"), me, opt1, opt2);
}
-static int do_recursive_merge(struct commit *base, struct commit *next,
- const char *base_label, const char *next_label,
- unsigned char *head, struct strbuf *msgbuf)
+static void parse_args(int argc, const char **argv, struct replay_opts *opts)
{
- struct merge_options o;
- struct tree *result, *next_tree, *base_tree, *head_tree;
- int clean, index_fd;
- const char **xopt;
- static struct lock_file index_lock;
-
- index_fd = hold_locked_index(&index_lock, 1);
-
- read_cache();
-
- init_merge_options(&o);
- o.ancestor = base ? base_label : "(empty tree)";
- o.branch1 = "HEAD";
- o.branch2 = next ? next_label : "(empty tree)";
-
- head_tree = parse_tree_indirect(head);
- next_tree = next ? next->tree : empty_tree();
- base_tree = base ? base->tree : empty_tree();
-
- for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
- parse_merge_opt(&o, *xopt);
-
- clean = merge_trees(&o,
- head_tree,
- next_tree, base_tree, &result);
-
- if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(&index_lock)))
- /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
- die(_("%s: Unable to write new index file"), me);
- rollback_lock_file(&index_lock);
-
- if (!clean) {
- int i;
- strbuf_addstr(msgbuf, "\nConflicts:\n\n");
- for (i = 0; i < active_nr;) {
- struct cache_entry *ce = active_cache[i++];
- if (ce_stage(ce)) {
- strbuf_addch(msgbuf, '\t');
- strbuf_addstr(msgbuf, ce->name);
- strbuf_addch(msgbuf, '\n');
- while (i < active_nr && !strcmp(ce->name,
- active_cache[i]->name))
- i++;
- }
- }
- }
-
- return !clean;
-}
-
-/*
- * If we are cherry-pick, and if the merge did not result in
- * hand-editing, we will hit this commit and inherit the original
- * author date and name.
- * If we are revert, or if our cherry-pick results in a hand merge,
- * we had better say that the current user is responsible for that.
- */
-static int run_git_commit(const char *defmsg)
-{
- /* 6 is max possible length of our args array including NULL */
- const char *args[6];
- int i = 0;
-
- args[i++] = "commit";
- args[i++] = "-n";
- if (signoff)
- args[i++] = "-s";
- if (!edit) {
- args[i++] = "-F";
- args[i++] = defmsg;
- }
- args[i] = NULL;
-
- return run_command_v_opt(args, RUN_GIT_CMD);
-}
-
-static int do_pick_commit(void)
-{
- unsigned char head[20];
- struct commit *base, *next, *parent;
- const char *base_label, *next_label;
- struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
- char *defmsg = NULL;
- struct strbuf msgbuf = STRBUF_INIT;
- int res;
-
- if (no_commit) {
- /*
- * We do not intend to commit immediately. We just want to
- * merge the differences in, so let's compute the tree
- * that represents the "current" state for merge-recursive
- * to work on.
- */
- if (write_cache_as_tree(head, 0, NULL))
- die (_("Your index file is unmerged."));
- } else {
- if (get_sha1("HEAD", head))
- die (_("You do not have a valid HEAD"));
- if (index_differs_from("HEAD", 0))
- die_dirty_index(me);
- }
- discard_cache();
+ const char * const * usage_str = revert_or_cherry_pick_usage(opts);
+ const char *me = action_name(opts);
+ int remove_state = 0;
+ int contin = 0;
+ int rollback = 0;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"),
+ OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"),
+ OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"),
+ OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
+ OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
+ OPT_NOOP_NOARG('r', NULL),
+ OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"),
+ OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"),
+ OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
+ OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"),
+ OPT_CALLBACK('X', "strategy-option", &opts, "option",
+ "option for merge strategy", option_parse_x),
+ OPT_END(),
+ OPT_END(),
+ OPT_END(),
+ OPT_END(),
+ OPT_END(),
+ };
- if (!commit->parents) {
- parent = NULL;
+ if (opts->action == REPLAY_PICK) {
+ struct option cp_extra[] = {
+ OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
+ OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
+ OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve initially empty commits"),
+ OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, "keep redundant, empty commits"),
+ OPT_END(),
+ };
+ if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
+ die(_("program error"));
}
- else 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));
+ argc = parse_options(argc, argv, NULL, options, usage_str,
+ PARSE_OPT_KEEP_ARGV0 |
+ PARSE_OPT_KEEP_UNKNOWN);
+
+ /* Check for incompatible subcommands */
+ verify_opt_mutually_compatible(me,
+ "--quit", remove_state,
+ "--continue", contin,
+ "--abort", rollback,
+ NULL);
+
+ /* implies allow_empty */
+ if (opts->keep_redundant_commits)
+ opts->allow_empty = 1;
+
+ /* Set the subcommand */
+ if (remove_state)
+ opts->subcommand = REPLAY_REMOVE_STATE;
+ else if (contin)
+ opts->subcommand = REPLAY_CONTINUE;
+ else if (rollback)
+ opts->subcommand = REPLAY_ROLLBACK;
else
- parent = commit->parents->item;
-
- if (allow_ff && parent && !hashcmp(parent->object.sha1, head))
- return fast_forward_to(commit->object.sha1, head);
-
- if (parent && parse_commit(parent) < 0)
- /* TRANSLATORS: The first %s will be "revert" or
- "cherry-pick", the second %s a SHA1 */
- die(_("%s: cannot parse parent commit %s"),
- me, sha1_to_hex(parent->object.sha1));
-
- if (get_message(commit->buffer, &msg) != 0)
- die(_("Cannot get commit message for %s"),
- sha1_to_hex(commit->object.sha1));
-
- /*
- * "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.
- */
-
- defmsg = git_pathdup("MERGE_MSG");
-
- if (action == REVERT) {
- base = commit;
- base_label = msg.label;
- next = parent;
- next_label = msg.parent_label;
- strbuf_addstr(&msgbuf, "Revert \"");
- strbuf_addstr(&msgbuf, msg.subject);
- strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
- strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
-
- if (commit->parents && commit->parents->next) {
- strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
- strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
+ opts->subcommand = REPLAY_NONE;
+
+ /* Check for incompatible command line arguments */
+ if (opts->subcommand != REPLAY_NONE) {
+ char *this_operation;
+ if (opts->subcommand == REPLAY_REMOVE_STATE)
+ this_operation = "--quit";
+ else if (opts->subcommand == REPLAY_CONTINUE)
+ this_operation = "--continue";
+ else {
+ assert(opts->subcommand == REPLAY_ROLLBACK);
+ this_operation = "--abort";
}
- strbuf_addstr(&msgbuf, ".\n");
- } else {
- base = parent;
- base_label = msg.parent_label;
- next = commit;
- next_label = msg.label;
- add_message_to_msg(&msgbuf, msg.message);
- if (no_replay) {
- strbuf_addstr(&msgbuf, "(cherry picked from commit ");
- strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
- strbuf_addstr(&msgbuf, ")\n");
- }
- if (!no_commit)
- write_cherry_pick_head();
- }
- if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
- res = do_recursive_merge(base, next, base_label, next_label,
- head, &msgbuf);
- write_message(&msgbuf, defmsg);
- } else {
- struct commit_list *common = NULL;
- struct commit_list *remotes = NULL;
-
- write_message(&msgbuf, defmsg);
-
- commit_list_insert(base, &common);
- commit_list_insert(next, &remotes);
- res = try_merge_command(strategy, xopts_nr, xopts, common,
- sha1_to_hex(head), remotes);
- free_commit_list(common);
- free_commit_list(remotes);
+ verify_opt_compatible(me, this_operation,
+ "--no-commit", opts->no_commit,
+ "--signoff", opts->signoff,
+ "--mainline", opts->mainline,
+ "--strategy", opts->strategy ? 1 : 0,
+ "--strategy-option", opts->xopts ? 1 : 0,
+ "-x", opts->record_origin,
+ "--ff", opts->allow_ff,
+ NULL);
}
- if (res) {
- error(action == REVERT
- ? _("could not revert %s... %s")
- : _("could not apply %s... %s"),
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
- msg.subject);
- print_advice();
- rerere(allow_rerere_auto);
+ if (opts->allow_ff)
+ verify_opt_compatible(me, "--ff",
+ "--signoff", opts->signoff,
+ "--no-commit", opts->no_commit,
+ "-x", opts->record_origin,
+ "--edit", opts->edit,
+ NULL);
+
+ if (opts->subcommand != REPLAY_NONE) {
+ opts->revs = NULL;
} else {
- if (!no_commit)
- res = run_git_commit(defmsg);
+ struct setup_revision_opt s_r_opt;
+ opts->revs = xmalloc(sizeof(*opts->revs));
+ init_revisions(opts->revs, NULL);
+ opts->revs->no_walk = 1;
+ if (argc < 2)
+ usage_with_options(usage_str, options);
+ memset(&s_r_opt, 0, sizeof(s_r_opt));
+ s_r_opt.assume_dashdash = 1;
+ argc = setup_revisions(argc, argv, opts->revs, &s_r_opt);
}
- free_message(&msg);
- free(defmsg);
-
- return res;
-}
-
-static void prepare_revs(struct rev_info *revs)
-{
- int argc;
-
- init_revisions(revs, NULL);
- revs->no_walk = 1;
- if (action != REVERT)
- revs->reverse = 1;
-
- argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
if (argc > 1)
- usage(*revert_or_cherry_pick_usage());
-
- if (prepare_revision_walk(revs))
- die(_("revision walk setup failed"));
-
- if (!revs->commits)
- die(_("empty commit set passed"));
-}
-
-static void read_and_refresh_cache(const char *me)
-{
- static struct lock_file index_lock;
- int index_fd = hold_locked_index(&index_lock, 0);
- if (read_index_preload(&the_index, NULL) < 0)
- die(_("git %s: failed to read the index"), me);
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
- if (the_index.cache_changed) {
- if (write_index(&the_index, index_fd) ||
- commit_locked_index(&index_lock))
- die(_("git %s: failed to refresh the index"), me);
- }
- rollback_lock_file(&index_lock);
-}
-
-static int revert_or_cherry_pick(int argc, const char **argv)
-{
- struct rev_info revs;
-
- git_config(git_default_config, NULL);
- me = action == REVERT ? "revert" : "cherry-pick";
- setenv(GIT_REFLOG_ACTION, me, 0);
- parse_args(argc, argv);
-
- if (allow_ff) {
- if (signoff)
- die(_("cherry-pick --ff cannot be used with --signoff"));
- if (no_commit)
- die(_("cherry-pick --ff cannot be used with --no-commit"));
- if (no_replay)
- die(_("cherry-pick --ff cannot be used with -x"));
- if (edit)
- die(_("cherry-pick --ff cannot be used with --edit"));
- }
-
- read_and_refresh_cache(me);
-
- prepare_revs(&revs);
-
- while ((commit = get_revision(&revs))) {
- int res = do_pick_commit();
- if (res)
- return res;
- }
-
- return 0;
+ usage_with_options(usage_str, options);
}
int cmd_revert(int argc, const char **argv, const char *prefix)
{
+ struct replay_opts opts;
+ int res;
+
+ memset(&opts, 0, sizeof(opts));
if (isatty(0))
- edit = 1;
- action = REVERT;
- return revert_or_cherry_pick(argc, argv);
+ opts.edit = 1;
+ opts.action = REPLAY_REVERT;
+ git_config(git_default_config, NULL);
+ parse_args(argc, argv, &opts);
+ res = sequencer_pick_revisions(&opts);
+ if (res < 0)
+ die(_("revert failed"));
+ return res;
}
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
{
- action = CHERRY_PICK;
- return revert_or_cherry_pick(argc, argv);
+ struct replay_opts opts;
+ int res;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.action = REPLAY_PICK;
+ git_config(git_default_config, NULL);
+ parse_args(argc, argv, &opts);
+ res = sequencer_pick_revisions(&opts);
+ if (res < 0)
+ die(_("cherry-pick failed"));
+ return res;
}
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index c1f6ddd..d5d7105 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -58,7 +58,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
argv[i++] = "--thin";
if (args->use_ofs_delta)
argv[i++] = "--delta-base-offset";
- if (args->quiet)
+ if (args->quiet || !args->progress)
argv[i++] = "-q";
if (args->progress)
argv[i++] = "--progress";
@@ -250,6 +250,7 @@ int send_pack(struct send_pack_args *args,
int allow_deleting_refs = 0;
int status_report = 0;
int use_sideband = 0;
+ int quiet_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
@@ -263,6 +264,8 @@ int send_pack(struct send_pack_args *args,
args->use_ofs_delta = 1;
if (server_supports("side-band-64k"))
use_sideband = 1;
+ if (server_supports("quiet"))
+ quiet_supported = 1;
if (!remote_refs) {
fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
@@ -300,16 +303,18 @@ int send_pack(struct send_pack_args *args,
} else {
char *old_hex = sha1_to_hex(ref->old_sha1);
char *new_hex = sha1_to_hex(ref->new_sha1);
-
- if (!cmds_sent && (status_report || use_sideband)) {
- packet_buf_write(&req_buf, "%s %s %s%c%s%s",
- old_hex, new_hex, ref->name, 0,
- status_report ? " report-status" : "",
- use_sideband ? " side-band-64k" : "");
+ int quiet = quiet_supported && (args->quiet || !args->progress);
+
+ if (!cmds_sent && (status_report || use_sideband || args->quiet)) {
+ packet_buf_write(&req_buf, "%s %s %s%c%s%s%s",
+ old_hex, new_hex, ref->name, 0,
+ status_report ? " report-status" : "",
+ use_sideband ? " side-band-64k" : "",
+ quiet ? " quiet" : "");
}
else
packet_buf_write(&req_buf, "%s %s %s",
- old_hex, new_hex, ref->name);
+ old_hex, new_hex, ref->name);
ref->status = status_report ?
REF_STATUS_EXPECTING_REPORT :
REF_STATUS_OK;
@@ -334,7 +339,7 @@ int send_pack(struct send_pack_args *args,
demux.data = fd;
demux.out = -1;
if (start_async(&demux))
- die("receive-pack: unable to fork off sideband demultiplexer");
+ die("send-pack: unable to fork off sideband demultiplexer");
in = demux.out;
}
@@ -405,6 +410,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
const char *receivepack = "git-receive-pack";
int flags;
int nonfastforward = 0;
+ int progress = -1;
argv++;
for (i = 1; i < argc; i++, argv++) {
@@ -439,10 +445,22 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.force_update = 1;
continue;
}
+ if (!strcmp(arg, "--quiet")) {
+ args.quiet = 1;
+ continue;
+ }
if (!strcmp(arg, "--verbose")) {
args.verbose = 1;
continue;
}
+ if (!strcmp(arg, "--progress")) {
+ progress = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--no-progress")) {
+ progress = 0;
+ continue;
+ }
if (!strcmp(arg, "--thin")) {
args.use_thin_pack = 1;
continue;
@@ -483,6 +501,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
}
}
+ if (progress == -1)
+ progress = !args.quiet && isatty(2);
+ args.progress = progress;
+
if (args.stateless_rpc) {
conn = NULL;
fd[0] = 0;
@@ -494,8 +516,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
memset(&extra_have, 0, sizeof(extra_have));
- get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
- &extra_have);
+ get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have);
transport_verify_remote_names(nr_refspecs, refspecs);
@@ -509,7 +530,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
flags |= MATCH_REFS_MIRROR;
/* match them up */
- if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
+ if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
return -1;
set_ref_status_for_push(remote_refs, args.send_mirror,
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index facc63a..a59e088 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -26,14 +26,14 @@ static const char **default_arg;
static const char *get_color_code(int idx)
{
- if (showbranch_use_color)
+ if (want_color(showbranch_use_color))
return column_colors_ansi[idx % column_colors_ansi_max];
return "";
}
static const char *get_color_reset_code(void)
{
- if (showbranch_use_color)
+ if (want_color(showbranch_use_color))
return GIT_COLOR_RESET;
return "";
}
@@ -573,7 +573,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "color.showbranch")) {
- showbranch_use_color = git_config_colorbool(var, value, -1);
+ showbranch_use_color = git_config_colorbool(var, value);
return 0;
}
@@ -685,9 +685,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
git_config(git_show_branch_config, NULL);
- if (showbranch_use_color == -1)
- showbranch_use_color = git_use_color_default;
-
/* If nothing is specified, try the default first */
if (ac == 1 && default_num) {
ac = default_num;
@@ -729,10 +726,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (ac == 0) {
static const char *fake_av[2];
- const char *refname;
- refname = resolve_ref("HEAD", sha1, 1, NULL);
- fake_av[0] = xstrdup(refname);
+ fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL);
fake_av[1] = NULL;
av = fake_av;
ac = 1;
@@ -794,7 +789,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
}
}
- head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+ head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL);
if (head_p) {
head_len = strlen(head_p);
memcpy(head, head_p, head_len + 1);
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 45f0340..3911661 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -145,7 +145,7 @@ static int exclude_existing(const char *match)
if (strncmp(ref, match, matchlen))
continue;
}
- if (check_ref_format(ref)) {
+ if (check_refname_format(ref, 0)) {
warning("ref '%s' ignored", ref);
continue;
}
@@ -225,7 +225,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
unsigned char sha1[20];
if (!prefixcmp(*pattern, "refs/") &&
- resolve_ref(*pattern, sha1, 1, NULL)) {
+ !read_ref(*pattern, sha1)) {
if (!quiet)
show_one(*pattern, sha1);
}
diff --git a/builtin/stripspace.c b/builtin/stripspace.c
index 1288ffc..f16986c 100644
--- a/builtin/stripspace.c
+++ b/builtin/stripspace.c
@@ -75,7 +75,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix)
!strcmp(argv[1], "--strip-comments")))
strip_comments = 1;
else if (argc > 1)
- usage("git stripspace [-s | --strip-comments] < <stream>");
+ usage("git stripspace [-s | --strip-comments] < input");
if (strbuf_read(&buf, 0, 1024) < 0)
die_errno("could not read the input");
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index dea849c..801d62e 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -8,13 +8,15 @@ static const char * const git_symbolic_ref_usage[] = {
NULL
};
+static int shorten;
+
static void check_symref(const char *HEAD, int quiet)
{
unsigned char sha1[20];
int flag;
- const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+ const char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag);
- if (!refs_heads_master)
+ if (!refname)
die("No such ref: %s", HEAD);
else if (!(flag & REF_ISSYMREF)) {
if (!quiet)
@@ -22,7 +24,9 @@ static void check_symref(const char *HEAD, int quiet)
else
exit(1);
}
- puts(refs_heads_master);
+ if (shorten)
+ refname = shorten_unambiguous_ref(refname, 0);
+ puts(refname);
}
int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
@@ -32,6 +36,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT__QUIET(&quiet,
"suppress error message for non-symbolic (detached) refs"),
+ OPT_BOOL(0, "short", &shorten, "shorten ref output"),
OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
OPT_END(),
};
diff --git a/builtin/tag.c b/builtin/tag.c
index 667515e..7b1be85 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -14,23 +14,28 @@
#include "parse-options.h"
#include "diff.h"
#include "revision.h"
+#include "gpg-interface.h"
+#include "sha1-array.h"
+#include "column.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 -l [-n[<num>]] [--contains <commit>] [--points-at <object>] "
+ "\n\t\t[<pattern>...]",
"git tag -v <tagname>...",
NULL
};
-static char signingkey[1000];
-
struct tag_filter {
const char **patterns;
int lines;
struct commit_list *with_commit;
};
+static struct sha1_array points_at;
+static unsigned int colopts;
+
static int match_pattern(const char **patterns, const char *ref)
{
/* no pattern means match everything */
@@ -42,6 +47,24 @@ static int match_pattern(const char **patterns, const char *ref)
return 0;
}
+static const unsigned char *match_points_at(const char *refname,
+ const unsigned char *sha1)
+{
+ const unsigned char *tagged_sha1 = NULL;
+ struct object *obj;
+
+ if (sha1_array_lookup(&points_at, sha1) >= 0)
+ return sha1;
+ obj = parse_object(sha1);
+ if (!obj)
+ die(_("malformed object at '%s'"), refname);
+ if (obj->type == OBJ_TAG)
+ tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
+ if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
+ return tagged_sha1;
+ return NULL;
+}
+
static int in_commit_list(const struct commit_list *want, struct commit *c)
{
for (; want; want = want->next)
@@ -84,18 +107,51 @@ static int contains(struct commit *candidate, const struct commit_list *want)
return contains_recurse(candidate, want);
}
+static void show_tag_lines(const unsigned char *sha1, int lines)
+{
+ int i;
+ unsigned long size;
+ enum object_type type;
+ char *buf, *sp, *eol;
+ size_t len;
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ die_errno("unable to read object %s", sha1_to_hex(sha1));
+ if (type != OBJ_COMMIT && type != OBJ_TAG)
+ goto free_return;
+ if (!size)
+ die("an empty %s object %s?",
+ typename(type), sha1_to_hex(sha1));
+
+ /* skip header */
+ sp = strstr(buf, "\n\n");
+ if (!sp)
+ goto free_return;
+
+ /* only take up to "lines" lines, and strip the signature from a tag */
+ if (type == OBJ_TAG)
+ size = parse_signature(buf, size);
+ for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
+ if (i)
+ printf("\n ");
+ eol = memchr(sp, '\n', size - (sp - buf));
+ len = eol ? eol - sp : size - (sp - buf);
+ fwrite(sp, len, 1, stdout);
+ if (!eol)
+ break;
+ sp = eol + 1;
+ }
+free_return:
+ free(buf);
+}
+
static int show_reference(const char *refname, const unsigned char *sha1,
int flag, void *cb_data)
{
struct tag_filter *filter = cb_data;
if (match_pattern(filter->patterns, refname)) {
- int i;
- unsigned long size;
- enum object_type type;
- char *buf, *sp, *eol;
- size_t len;
-
if (filter->with_commit) {
struct commit *commit;
@@ -106,38 +162,16 @@ static int show_reference(const char *refname, const unsigned char *sha1,
return 0;
}
+ if (points_at.nr && !match_points_at(refname, sha1))
+ return 0;
+
if (!filter->lines) {
printf("%s\n", refname);
return 0;
}
printf("%-15s ", refname);
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf || !size)
- return 0;
-
- /* skip header */
- sp = strstr(buf, "\n\n");
- if (!sp) {
- free(buf);
- return 0;
- }
- /* only take up to "lines" lines, and strip the signature */
- size = parse_signature(buf, size);
- for (i = 0, sp += 2;
- i < filter->lines && sp < buf + size;
- i++) {
- if (i)
- printf("\n ");
- eol = memchr(sp, '\n', size - (sp - buf));
- len = eol ? eol - sp : size - (sp - buf);
- fwrite(sp, len, 1, stdout);
- if (!eol)
- break;
- sp = eol + 1;
- }
+ show_tag_lines(sha1, filter->lines);
putchar('\n');
- free(buf);
}
return 0;
@@ -174,7 +208,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
had_error = 1;
continue;
}
- if (!resolve_ref(ref, sha1, 1, NULL)) {
+ if (read_ref(ref, sha1)) {
error(_("tag '%s' not found."), *p);
had_error = 1;
continue;
@@ -208,83 +242,31 @@ static int verify_tag(const char *name, const char *ref,
static int do_sign(struct strbuf *buffer)
{
- struct child_process gpg;
- const char *args[4];
- char *bracket;
- int len;
- int i, j;
-
- if (!*signingkey) {
- 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, '>');
- if (bracket)
- bracket[1] = '\0';
- }
-
- /* When the username signingkey is bad, program could be terminated
- * because gpg exits without reading and then write gets SIGPIPE. */
- signal(SIGPIPE, SIG_IGN);
-
- memset(&gpg, 0, sizeof(gpg));
- gpg.argv = args;
- gpg.in = -1;
- gpg.out = -1;
- args[0] = "gpg";
- args[1] = "-bsau";
- args[2] = signingkey;
- args[3] = NULL;
-
- if (start_command(&gpg))
- return error(_("could not run gpg."));
-
- if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
- close(gpg.in);
- close(gpg.out);
- finish_command(&gpg);
- return error(_("gpg did not accept the tag data"));
- }
- close(gpg.in);
- len = strbuf_read(buffer, gpg.out, 1024);
- close(gpg.out);
-
- if (finish_command(&gpg) || !len || len < 0)
- return error(_("gpg failed to sign the tag"));
-
- /* Strip CR from the line endings, in case we are on Windows. */
- for (i = j = 0; i < buffer->len; i++)
- if (buffer->buf[i] != '\r') {
- if (i != j)
- buffer->buf[j] = buffer->buf[i];
- j++;
- }
- strbuf_setlen(buffer, j);
-
- return 0;
+ return sign_buffer(buffer, buffer, get_signing_key());
}
static const char tag_template[] =
N_("\n"
"#\n"
"# Write a tag message\n"
+ "# Lines starting with '#' will be ignored.\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 const char tag_template_nocleanup[] =
+ N_("\n"
+ "#\n"
+ "# Write a tag message\n"
+ "# Lines starting with '#' will be kept; you may remove them"
+ " yourself if you want to.\n"
+ "#\n");
static int git_tag_config(const char *var, const char *value, void *cb)
{
- if (!strcmp(var, "user.signingkey")) {
- if (!value)
- return config_error_nonbool(var);
- set_signingkey(value);
- return 0;
- }
-
+ int status = git_gpg_config(var, value, cb);
+ if (status)
+ return status;
+ if (!prefixcmp(var, "column."))
+ return git_column_config(var, value, "tag", &colopts);
return git_default_config(var, value, cb);
}
@@ -319,8 +301,18 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
return 0;
}
+struct create_tag_options {
+ unsigned int message_given:1;
+ unsigned int sign;
+ enum {
+ CLEANUP_NONE,
+ CLEANUP_SPACE,
+ CLEANUP_ALL
+ } cleanup_mode;
+};
+
static void create_tag(const unsigned char *object, const char *tag,
- struct strbuf *buf, int message, int sign,
+ struct strbuf *buf, struct create_tag_options *opt,
unsigned char *prev, unsigned char *result)
{
enum object_type type;
@@ -340,12 +332,12 @@ static void create_tag(const unsigned char *object, const char *tag,
sha1_to_hex(object),
typename(type),
tag,
- git_committer_info(IDENT_ERROR_ON_NO_NAME));
+ git_committer_info(IDENT_STRICT));
if (header_len > sizeof(header_buf) - 1)
die(_("tag header too big."));
- if (!message) {
+ if (!opt->message_given) {
int fd;
/* write the template message before editing: */
@@ -356,8 +348,12 @@ static void create_tag(const unsigned char *object, const char *tag,
if (!is_null_sha1(prev))
write_tag_body(fd, prev);
+ else if (opt->cleanup_mode == CLEANUP_ALL)
+ write_or_die(fd, _(tag_template),
+ strlen(_(tag_template)));
else
- write_or_die(fd, _(tag_template), strlen(_(tag_template)));
+ write_or_die(fd, _(tag_template_nocleanup),
+ strlen(_(tag_template_nocleanup)));
close(fd);
if (launch_editor(path, buf, NULL)) {
@@ -367,14 +363,15 @@ static void create_tag(const unsigned char *object, const char *tag,
}
}
- stripspace(buf, 1);
+ if (opt->cleanup_mode != CLEANUP_NONE)
+ stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
- if (!message && !buf->len)
+ if (!opt->message_given && !buf->len)
die(_("no tag message?"));
strbuf_insert(buf, 0, header_buf, header_len);
- if (build_tag_object(buf, sign, result) < 0) {
+ if (build_tag_object(buf, opt->sign, result) < 0) {
if (path)
fprintf(stderr, _("The tag message has been left in %s\n"),
path);
@@ -407,12 +404,29 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
{
if (name[0] == '-')
- return CHECK_REF_FORMAT_ERROR;
+ return -1;
strbuf_reset(sb);
strbuf_addf(sb, "refs/tags/%s", name);
- return check_ref_format(sb->buf);
+ return check_refname_format(sb->buf, 0);
+}
+
+static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
+ const char *arg, int unset)
+{
+ unsigned char sha1[20];
+
+ if (unset) {
+ sha1_array_clear(&points_at);
+ return 0;
+ }
+ if (!arg)
+ return error(_("switch 'points-at' requires an object"));
+ if (get_sha1(arg, sha1))
+ return error(_("malformed object name '%s'"), arg);
+ sha1_array_append(&points_at, sha1);
+ return 0;
}
int cmd_tag(int argc, const char **argv, const char *prefix)
@@ -422,30 +436,34 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
unsigned char object[20], prev[20];
const char *object_ref, *tag;
struct ref_lock *lock;
-
- int annotate = 0, sign = 0, force = 0, lines = -1,
- list = 0, delete = 0, verify = 0;
+ struct create_tag_options opt;
+ char *cleanup_arg = NULL;
+ int annotate = 0, force = 0, lines = -1, list = 0,
+ delete = 0, verify = 0;
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT };
struct commit_list *with_commit = NULL;
struct option options[] = {
- OPT_BOOLEAN('l', NULL, &list, "list tag names"),
+ OPT_BOOLEAN('l', "list", &list, "list tag names"),
{ OPTION_INTEGER, 'n', NULL, &lines, "n",
"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_BOOLEAN('d', "delete", &delete, "delete tags"),
+ OPT_BOOLEAN('v', "verify", &verify, "verify tags"),
OPT_GROUP("Tag creation options"),
- OPT_BOOLEAN('a', NULL, &annotate,
+ OPT_BOOLEAN('a', "annotate", &annotate,
"annotated tag, needs a message"),
- OPT_CALLBACK('m', NULL, &msg, "message",
+ OPT_CALLBACK('m', "message", &msg, "message",
"tag message", parse_msg_arg),
- OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
- OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
- OPT_STRING('u', NULL, &keyid, "key-id",
+ OPT_FILENAME('F', "file", &msgfile, "read message from file"),
+ OPT_BOOLEAN('s', "sign", &opt.sign, "annotated and GPG-signed tag"),
+ OPT_STRING(0, "cleanup", &cleanup_arg, "mode",
+ "how to strip spaces and #comments from message"),
+ OPT_STRING('u', "local-user", &keyid, "key-id",
"use another key to sign the tag"),
OPT__FORCE(&force, "replace the tag if exists"),
+ OPT_COLUMN(0, "column", &colopts, "show tag list in columns"),
OPT_GROUP("Tag listing options"),
{
@@ -454,18 +472,24 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
PARSE_OPT_LASTARG_DEFAULT,
parse_opt_with_commit, (intptr_t)"HEAD",
},
+ {
+ OPTION_CALLBACK, 0, "points-at", NULL, "object",
+ "print only tags of the object", 0, parse_opt_points_at
+ },
OPT_END()
};
git_config(git_tag_config, NULL);
+ memset(&opt, 0, sizeof(opt));
+
argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
if (keyid) {
- sign = 1;
- set_signingkey(keyid);
+ opt.sign = 1;
+ set_signing_key(keyid);
}
- if (sign)
+ if (opt.sign)
annotate = 1;
if (argc == 0 && !(delete || verify))
list = 1;
@@ -476,13 +500,31 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (list + delete + verify > 1)
usage_with_options(git_tag_usage, options);
- if (list)
- return list_tags(argv, lines == -1 ? 0 : lines,
- with_commit);
+ finalize_colopts(&colopts, -1);
+ if (list && lines != -1) {
+ if (explicitly_enable_column(colopts))
+ die(_("--column and -n are incompatible"));
+ colopts = 0;
+ }
+ if (list) {
+ int ret;
+ if (column_active(colopts)) {
+ struct column_options copts;
+ memset(&copts, 0, sizeof(copts));
+ copts.padding = 2;
+ run_column_filter(colopts, &copts);
+ }
+ ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
+ if (column_active(colopts))
+ stop_column_filter();
+ return ret;
+ }
if (lines != -1)
die(_("-n option is only allowed with -l."));
if (with_commit)
die(_("--contains option is only allowed with -l."));
+ if (points_at.nr)
+ die(_("--points-at option is only allowed with -l."));
if (delete)
return for_each_tag_name(argv, delete_tag);
if (verify)
@@ -518,14 +560,24 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (strbuf_check_tag_ref(&ref, tag))
die(_("'%s' is not a valid tag name."), tag);
- if (!resolve_ref(ref.buf, prev, 1, NULL))
+ if (read_ref(ref.buf, prev))
hashclr(prev);
else if (!force)
die(_("tag '%s' already exists"), tag);
+ opt.message_given = msg.given || msgfile;
+
+ if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
+ opt.cleanup_mode = CLEANUP_ALL;
+ else if (!strcmp(cleanup_arg, "verbatim"))
+ opt.cleanup_mode = CLEANUP_NONE;
+ else if (!strcmp(cleanup_arg, "whitespace"))
+ opt.cleanup_mode = CLEANUP_SPACE;
+ else
+ die(_("Invalid cleanup mode %s"), cleanup_arg);
+
if (annotate)
- create_tag(object, tag, &buf, msg.given || msgfile,
- sign, prev, object);
+ create_tag(object, tag, &buf, &opt, prev, object);
lock = lock_any_ref_for_update(ref.buf, prev, 0);
if (!lock)
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 14e04e6..2217d7b 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -107,7 +107,7 @@ static void *get_data(unsigned long size)
if (stream.total_out == size && ret == Z_STREAM_END)
break;
if (ret != Z_OK) {
- error("inflate returned %d\n", ret);
+ error("inflate returned %d", ret);
free(buf);
buf = NULL;
if (!recover)
diff --git a/builtin/update-index.c b/builtin/update-index.c
index a6a23fa..5a4e9ea 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -211,12 +211,6 @@ static int process_path(const char *path)
if (S_ISDIR(st.st_mode))
return process_directory(path, len, &st);
- /*
- * Process a regular file
- */
- 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);
}
@@ -708,6 +702,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
int newfd, entries, has_errors = 0, line_termination = '\n';
int read_from_stdin = 0;
int prefix_length = prefix ? strlen(prefix) : 0;
+ int preferred_index_format = 0;
char set_executable_bit = 0;
struct refresh_params refresh_args = {0, &has_errors};
int lock_error = 0;
@@ -791,6 +786,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
"(for porcelains) forget saved unresolved conflicts",
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
resolve_undo_clear_callback},
+ OPT_INTEGER(0, "index-version", &preferred_index_format,
+ "write index in this format"),
OPT_END()
};
@@ -851,6 +848,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
}
}
argc = parse_options_end(&ctx);
+ if (preferred_index_format) {
+ if (preferred_index_format < INDEX_FORMAT_LB ||
+ INDEX_FORMAT_UB < preferred_index_format)
+ die("index-version %d not in range: %d..%d",
+ preferred_index_format,
+ INDEX_FORMAT_LB, INDEX_FORMAT_UB);
+
+ if (the_index.version != preferred_index_format)
+ active_cache_changed = 1;
+ the_index.version = preferred_index_format;
+ }
if (read_from_stdin) {
struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c
index b90dce6..0d63c44 100644
--- a/builtin/update-server-info.c
+++ b/builtin/update-server-info.c
@@ -15,6 +15,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options,
update_server_info_usage, 0);
if (argc > 0)
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index 73f788e..b928beb 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -6,6 +6,7 @@
#include "archive.h"
#include "pkt-line.h"
#include "sideband.h"
+#include "run-command.h"
static const char upload_archive_usage[] =
"git upload-archive <repo>";
@@ -13,12 +14,9 @@ static const char upload_archive_usage[] =
static const char deadchild[] =
"git upload-archive: archiver died with error";
-static const char lostchild[] =
-"git upload-archive: archiver process was lost";
-
#define MAX_ARGS (64)
-static int run_upload_archive(int argc, const char **argv, const char *prefix)
+int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
{
const char *sent_argv[MAX_ARGS];
const char *arg_cmd = "argument ";
@@ -64,7 +62,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
sent_argv[sent_argc] = NULL;
/* parse all options sent by the client */
- return write_archive(sent_argc, sent_argv, prefix, 0);
+ return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1);
}
__attribute__((format (printf, 1, 2)))
@@ -96,8 +94,8 @@ static ssize_t process_input(int child_fd, int band)
int cmd_upload_archive(int argc, const char **argv, const char *prefix)
{
- pid_t writer;
- int fd1[2], fd2[2];
+ struct child_process writer = { argv };
+
/*
* Set up sideband subprocess.
*
@@ -105,39 +103,24 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
* multiplexed out to our fd#1. If the child dies, we tell the other
* end over channel #3.
*/
- if (pipe(fd1) < 0 || pipe(fd2) < 0) {
- int err = errno;
- packet_write(1, "NACK pipe failed on the remote side\n");
- die("upload-archive: %s", strerror(err));
- }
- writer = fork();
- if (writer < 0) {
+ argv[0] = "upload-archive--writer";
+ writer.out = writer.err = -1;
+ writer.git_cmd = 1;
+ if (start_command(&writer)) {
int err = errno;
- packet_write(1, "NACK fork failed on the remote side\n");
+ packet_write(1, "NACK unable to spawn subprocess\n");
die("upload-archive: %s", strerror(err));
}
- if (!writer) {
- /* child - connect fd#1 and fd#2 to the pipe */
- dup2(fd1[1], 1);
- dup2(fd2[1], 2);
- close(fd1[1]); close(fd2[1]);
- close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
-
- exit(run_upload_archive(argc, argv, prefix));
- }
- /* parent - read from child, multiplex and send out to fd#1 */
- close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
packet_write(1, "ACK\n");
packet_flush(1);
while (1) {
struct pollfd pfd[2];
- int status;
- pfd[0].fd = fd1[0];
+ pfd[0].fd = writer.out;
pfd[0].events = POLLIN;
- pfd[1].fd = fd2[0];
+ pfd[1].fd = writer.err;
pfd[1].events = POLLIN;
if (poll(pfd, 2, -1) < 0) {
if (errno != EINTR) {
@@ -156,9 +139,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
if (process_input(pfd[0].fd, 1))
continue;
- if (waitpid(writer, &status, 0) < 0)
- error_clnt("%s", lostchild);
- else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+ if (finish_command(&writer))
error_clnt("%s", deadchild);
packet_flush(1);
break;
diff --git a/builtin/var.c b/builtin/var.c
index 99d068a..aedbb53 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -11,7 +11,7 @@ static const char *editor(int flag)
{
const char *pgm = git_editor();
- if (!pgm && flag & IDENT_ERROR_ON_NO_NAME)
+ if (!pgm && flag & IDENT_STRICT)
die("Terminal is dumb, but EDITOR unset");
return pgm;
@@ -55,7 +55,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(IDENT_ERROR_ON_NO_NAME);
+ val = ptr->read(IDENT_STRICT);
break;
}
}
diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c
index b6079ae..e841b4a 100644
--- a/builtin/verify-pack.c
+++ b/builtin/verify-pack.c
@@ -1,134 +1,53 @@
#include "builtin.h"
#include "cache.h"
-#include "pack.h"
-#include "pack-revindex.h"
+#include "run-command.h"
#include "parse-options.h"
-#define MAX_CHAIN 50
-
#define VERIFY_PACK_VERBOSE 01
#define VERIFY_PACK_STAT_ONLY 02
-static void show_pack_info(struct packed_git *p, unsigned int flags)
-{
- uint32_t nr_objects, i;
- int cnt;
- int stat_only = flags & VERIFY_PACK_STAT_ONLY;
- unsigned long chain_histogram[MAX_CHAIN+1], baseobjects;
-
- nr_objects = p->num_objects;
- memset(chain_histogram, 0, sizeof(chain_histogram));
- baseobjects = 0;
-
- for (i = 0; i < nr_objects; i++) {
- const unsigned char *sha1;
- unsigned char base_sha1[20];
- const char *type;
- unsigned long size;
- unsigned long store_size;
- off_t offset;
- unsigned int delta_chain_length;
-
- sha1 = nth_packed_object_sha1(p, i);
- if (!sha1)
- die("internal error pack-check nth-packed-object");
- offset = nth_packed_object_offset(p, i);
- type = packed_object_info_detail(p, offset, &size, &store_size,
- &delta_chain_length,
- base_sha1);
- if (!stat_only)
- printf("%s ", sha1_to_hex(sha1));
- if (!delta_chain_length) {
- if (!stat_only)
- printf("%-6s %lu %lu %"PRIuMAX"\n",
- type, size, store_size, (uintmax_t)offset);
- baseobjects++;
- }
- else {
- if (!stat_only)
- printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
- type, size, store_size, (uintmax_t)offset,
- delta_chain_length, sha1_to_hex(base_sha1));
- if (delta_chain_length <= MAX_CHAIN)
- chain_histogram[delta_chain_length]++;
- else
- chain_histogram[0]++;
- }
- }
-
- if (baseobjects)
- printf("non delta: %lu object%s\n",
- baseobjects, baseobjects > 1 ? "s" : "");
-
- for (cnt = 1; cnt <= MAX_CHAIN; cnt++) {
- if (!chain_histogram[cnt])
- continue;
- printf("chain length = %d: %lu object%s\n", cnt,
- chain_histogram[cnt],
- chain_histogram[cnt] > 1 ? "s" : "");
- }
- if (chain_histogram[0])
- printf("chain length > %d: %lu object%s\n", MAX_CHAIN,
- chain_histogram[0],
- chain_histogram[0] > 1 ? "s" : "");
-}
-
static int verify_one_pack(const char *path, unsigned int flags)
{
- char arg[PATH_MAX];
- int len;
+ struct child_process index_pack;
+ const char *argv[] = {"index-pack", NULL, NULL, NULL };
+ struct strbuf arg = STRBUF_INIT;
int verbose = flags & VERIFY_PACK_VERBOSE;
int stat_only = flags & VERIFY_PACK_STAT_ONLY;
- struct packed_git *pack;
int err;
- len = strlcpy(arg, path, PATH_MAX);
- if (len >= PATH_MAX)
- return error("name too long: %s", path);
-
- /*
- * In addition to "foo.idx" we accept "foo.pack" and "foo";
- * normalize these forms to "foo.idx" for add_packed_git().
- */
- if (has_extension(arg, ".pack")) {
- strcpy(arg + len - 5, ".idx");
- len--;
- } else if (!has_extension(arg, ".idx")) {
- if (len + 4 >= PATH_MAX)
- return error("name too long: %s.idx", arg);
- strcpy(arg + len, ".idx");
- len += 4;
- }
+ if (stat_only)
+ argv[1] = "--verify-stat-only";
+ else if (verbose)
+ argv[1] = "--verify-stat";
+ else
+ argv[1] = "--verify";
/*
- * add_packed_git() uses our buffer (containing "foo.idx") to
- * build the pack filename ("foo.pack"). Make sure it fits.
+ * In addition to "foo.pack" we accept "foo.idx" and "foo";
+ * normalize these forms to "foo.pack" for "index-pack --verify".
*/
- if (len + 1 >= PATH_MAX) {
- arg[len - 4] = '\0';
- return error("name too long: %s.pack", arg);
- }
-
- pack = add_packed_git(arg, len, 1);
- if (!pack)
- return error("packfile %s not found.", arg);
+ strbuf_addstr(&arg, path);
+ if (has_extension(arg.buf, ".idx"))
+ strbuf_splice(&arg, arg.len - 3, 3, "pack", 4);
+ else if (!has_extension(arg.buf, ".pack"))
+ strbuf_add(&arg, ".pack", 5);
+ argv[2] = arg.buf;
- install_packed_git(pack);
+ memset(&index_pack, 0, sizeof(index_pack));
+ index_pack.argv = argv;
+ index_pack.git_cmd = 1;
- if (!stat_only)
- err = verify_pack(pack);
- else
- err = open_pack_index(pack);
+ err = run_command(&index_pack);
if (verbose || stat_only) {
if (err)
- printf("%s: bad\n", pack->pack_name);
+ printf("%s: bad\n", arg.buf);
else {
- show_pack_info(pack, flags);
if (!stat_only)
- printf("%s: ok\n", pack->pack_name);
+ printf("%s: ok\n", arg.buf);
}
}
+ strbuf_release(&arg);
return err;
}
@@ -159,7 +78,6 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
for (i = 0; i < argc; i++) {
if (verify_one_pack(argv[i], flags))
err = 1;
- discard_revindex();
}
return err;
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 3134766..986789f 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -11,6 +11,7 @@
#include "run-command.h"
#include <signal.h>
#include "parse-options.h"
+#include "gpg-interface.h"
static const char * const verify_tag_usage[] = {
"git verify-tag [-v|--verbose] <tag>...",
@@ -19,42 +20,16 @@ static const char * const verify_tag_usage[] = {
static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
{
- struct child_process gpg;
- const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
- char path[PATH_MAX];
- size_t len;
- int fd, ret;
-
- fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
- if (fd < 0)
- return error("could not create temporary file '%s': %s",
- path, strerror(errno));
- if (write_in_full(fd, buf, size) < 0)
- return error("failed writing temporary file '%s': %s",
- path, strerror(errno));
- close(fd);
-
- /* find the length without signature */
+ int len;
+
len = parse_signature(buf, size);
if (verbose)
write_in_full(1, buf, len);
- memset(&gpg, 0, sizeof(gpg));
- gpg.argv = args_gpg;
- gpg.in = -1;
- args_gpg[2] = path;
- if (start_command(&gpg)) {
- unlink(path);
- return error("could not run gpg.");
- }
-
- write_in_full(gpg.in, buf, len);
- close(gpg.in);
- ret = finish_command(&gpg);
+ if (size == len)
+ return error("no signature found");
- unlink_or_warn(path);
-
- return ret;
+ return verify_signed_buffer(buf, len, buf + len, size - len, NULL);
}
static int verify_tag(const char *name, int verbose)
@@ -83,6 +58,14 @@ static int verify_tag(const char *name, int verbose)
return ret;
}
+static int git_verify_tag_config(const char *var, const char *value, void *cb)
+{
+ int status = git_gpg_config(var, value, cb);
+ if (status)
+ return status;
+ return git_default_config(var, value, cb);
+}
+
int cmd_verify_tag(int argc, const char **argv, const char *prefix)
{
int i = 1, verbose = 0, had_error = 0;
@@ -91,7 +74,7 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
OPT_END()
};
- git_config(git_default_config, NULL);
+ git_config(git_verify_tag_config, NULL);
argc = parse_options(argc, argv, prefix, verify_tag_options,
verify_tag_usage, PARSE_OPT_KEEP_ARGV0);
diff --git a/bulk-checkin.c b/bulk-checkin.c
new file mode 100644
index 0000000..6b0b6d4
--- /dev/null
+++ b/bulk-checkin.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#include "bulk-checkin.h"
+#include "csum-file.h"
+#include "pack.h"
+
+static int pack_compression_level = Z_DEFAULT_COMPRESSION;
+
+static struct bulk_checkin_state {
+ unsigned plugged:1;
+
+ char *pack_tmp_name;
+ struct sha1file *f;
+ off_t offset;
+ struct pack_idx_option pack_idx_opts;
+
+ struct pack_idx_entry **written;
+ uint32_t alloc_written;
+ uint32_t nr_written;
+} state;
+
+static void finish_bulk_checkin(struct bulk_checkin_state *state)
+{
+ unsigned char sha1[20];
+ char packname[PATH_MAX];
+ int i;
+
+ if (!state->f)
+ return;
+
+ if (state->nr_written == 0) {
+ close(state->f->fd);
+ unlink(state->pack_tmp_name);
+ goto clear_exit;
+ } else if (state->nr_written == 1) {
+ sha1close(state->f, sha1, CSUM_FSYNC);
+ } else {
+ int fd = sha1close(state->f, sha1, 0);
+ fixup_pack_header_footer(fd, sha1, state->pack_tmp_name,
+ state->nr_written, sha1,
+ state->offset);
+ close(fd);
+ }
+
+ sprintf(packname, "%s/pack/pack-", get_object_directory());
+ finish_tmp_packfile(packname, state->pack_tmp_name,
+ state->written, state->nr_written,
+ &state->pack_idx_opts, sha1);
+ for (i = 0; i < state->nr_written; i++)
+ free(state->written[i]);
+
+clear_exit:
+ free(state->written);
+ memset(state, 0, sizeof(*state));
+
+ /* Make objects we just wrote available to ourselves */
+ reprepare_packed_git();
+}
+
+static int already_written(struct bulk_checkin_state *state, unsigned char sha1[])
+{
+ int i;
+
+ /* The object may already exist in the repository */
+ if (has_sha1_file(sha1))
+ return 1;
+
+ /* Might want to keep the list sorted */
+ for (i = 0; i < state->nr_written; i++)
+ if (!hashcmp(state->written[i]->sha1, sha1))
+ return 1;
+
+ /* This is a new object we need to keep */
+ return 0;
+}
+
+/*
+ * Read the contents from fd for size bytes, streaming it to the
+ * packfile in state while updating the hash in ctx. Signal a failure
+ * by returning a negative value when the resulting pack would exceed
+ * the pack size limit and this is not the first object in the pack,
+ * so that the caller can discard what we wrote from the current pack
+ * by truncating it and opening a new one. The caller will then call
+ * us again after rewinding the input fd.
+ *
+ * The already_hashed_to pointer is kept untouched by the caller to
+ * make sure we do not hash the same byte when we are called
+ * again. This way, the caller does not have to checkpoint its hash
+ * status before calling us just in case we ask it to call us again
+ * with a new pack.
+ */
+static int stream_to_pack(struct bulk_checkin_state *state,
+ git_SHA_CTX *ctx, off_t *already_hashed_to,
+ int fd, size_t size, enum object_type type,
+ const char *path, unsigned flags)
+{
+ git_zstream s;
+ unsigned char obuf[16384];
+ unsigned hdrlen;
+ int status = Z_OK;
+ int write_object = (flags & HASH_WRITE_OBJECT);
+ off_t offset = 0;
+
+ memset(&s, 0, sizeof(s));
+ git_deflate_init(&s, pack_compression_level);
+
+ hdrlen = encode_in_pack_object_header(type, size, obuf);
+ s.next_out = obuf + hdrlen;
+ s.avail_out = sizeof(obuf) - hdrlen;
+
+ while (status != Z_STREAM_END) {
+ unsigned char ibuf[16384];
+
+ if (size && !s.avail_in) {
+ ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
+ if (xread(fd, ibuf, rsize) != rsize)
+ die("failed to read %d bytes from '%s'",
+ (int)rsize, path);
+ offset += rsize;
+ if (*already_hashed_to < offset) {
+ size_t hsize = offset - *already_hashed_to;
+ if (rsize < hsize)
+ hsize = rsize;
+ if (hsize)
+ git_SHA1_Update(ctx, ibuf, hsize);
+ *already_hashed_to = offset;
+ }
+ s.next_in = ibuf;
+ s.avail_in = rsize;
+ size -= rsize;
+ }
+
+ status = git_deflate(&s, size ? 0 : Z_FINISH);
+
+ if (!s.avail_out || status == Z_STREAM_END) {
+ if (write_object) {
+ size_t written = s.next_out - obuf;
+
+ /* would we bust the size limit? */
+ if (state->nr_written &&
+ pack_size_limit_cfg &&
+ pack_size_limit_cfg < state->offset + written) {
+ git_deflate_abort(&s);
+ return -1;
+ }
+
+ sha1write(state->f, obuf, written);
+ state->offset += written;
+ }
+ s.next_out = obuf;
+ s.avail_out = sizeof(obuf);
+ }
+
+ switch (status) {
+ case Z_OK:
+ case Z_BUF_ERROR:
+ case Z_STREAM_END:
+ continue;
+ default:
+ die("unexpected deflate failure: %d", status);
+ }
+ }
+ git_deflate_end(&s);
+ return 0;
+}
+
+/* Lazily create backing packfile for the state */
+static void prepare_to_stream(struct bulk_checkin_state *state,
+ unsigned flags)
+{
+ if (!(flags & HASH_WRITE_OBJECT) || state->f)
+ return;
+
+ state->f = create_tmp_packfile(&state->pack_tmp_name);
+ reset_pack_idx_option(&state->pack_idx_opts);
+
+ /* Pretend we are going to write only one object */
+ state->offset = write_pack_header(state->f, 1);
+ if (!state->offset)
+ die_errno("unable to write pack header");
+}
+
+static int deflate_to_pack(struct bulk_checkin_state *state,
+ unsigned char result_sha1[],
+ int fd, size_t size,
+ enum object_type type, const char *path,
+ unsigned flags)
+{
+ off_t seekback, already_hashed_to;
+ git_SHA_CTX ctx;
+ unsigned char obuf[16384];
+ unsigned header_len;
+ struct sha1file_checkpoint checkpoint;
+ struct pack_idx_entry *idx = NULL;
+
+ seekback = lseek(fd, 0, SEEK_CUR);
+ if (seekback == (off_t) -1)
+ return error("cannot find the current offset");
+
+ header_len = sprintf((char *)obuf, "%s %" PRIuMAX,
+ typename(type), (uintmax_t)size) + 1;
+ git_SHA1_Init(&ctx);
+ git_SHA1_Update(&ctx, obuf, header_len);
+
+ /* Note: idx is non-NULL when we are writing */
+ if ((flags & HASH_WRITE_OBJECT) != 0)
+ idx = xcalloc(1, sizeof(*idx));
+
+ already_hashed_to = 0;
+
+ while (1) {
+ prepare_to_stream(state, flags);
+ if (idx) {
+ sha1file_checkpoint(state->f, &checkpoint);
+ idx->offset = state->offset;
+ crc32_begin(state->f);
+ }
+ if (!stream_to_pack(state, &ctx, &already_hashed_to,
+ fd, size, type, path, flags))
+ break;
+ /*
+ * Writing this object to the current pack will make
+ * it too big; we need to truncate it, start a new
+ * pack, and write into it.
+ */
+ if (!idx)
+ die("BUG: should not happen");
+ sha1file_truncate(state->f, &checkpoint);
+ state->offset = checkpoint.offset;
+ finish_bulk_checkin(state);
+ if (lseek(fd, seekback, SEEK_SET) == (off_t) -1)
+ return error("cannot seek back");
+ }
+ git_SHA1_Final(result_sha1, &ctx);
+ if (!idx)
+ return 0;
+
+ idx->crc32 = crc32_end(state->f);
+ if (already_written(state, result_sha1)) {
+ sha1file_truncate(state->f, &checkpoint);
+ state->offset = checkpoint.offset;
+ free(idx);
+ } else {
+ hashcpy(idx->sha1, result_sha1);
+ ALLOC_GROW(state->written,
+ state->nr_written + 1,
+ state->alloc_written);
+ state->written[state->nr_written++] = idx;
+ }
+ return 0;
+}
+
+int index_bulk_checkin(unsigned char *sha1,
+ int fd, size_t size, enum object_type type,
+ const char *path, unsigned flags)
+{
+ int status = deflate_to_pack(&state, sha1, fd, size, type,
+ path, flags);
+ if (!state.plugged)
+ finish_bulk_checkin(&state);
+ return status;
+}
+
+void plug_bulk_checkin(void)
+{
+ state.plugged = 1;
+}
+
+void unplug_bulk_checkin(void)
+{
+ state.plugged = 0;
+ if (state.f)
+ finish_bulk_checkin(&state);
+}
diff --git a/bulk-checkin.h b/bulk-checkin.h
new file mode 100644
index 0000000..4f599f8
--- /dev/null
+++ b/bulk-checkin.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef BULK_CHECKIN_H
+#define BULK_CHECKIN_H
+
+#include "cache.h"
+
+extern int index_bulk_checkin(unsigned char sha1[],
+ int fd, size_t size, enum object_type type,
+ const char *path, unsigned flags);
+
+extern void plug_bulk_checkin(void);
+extern void unplug_bulk_checkin(void);
+
+#endif
diff --git a/bundle.c b/bundle.c
index f48fd7d..8d12816 100644
--- a/bundle.c
+++ b/bundle.c
@@ -23,52 +23,87 @@ static void add_to_ref_list(const unsigned char *sha1, const char *name,
list->nr++;
}
-/* returns an fd */
-int read_bundle_header(const char *path, struct bundle_header *header)
+static int parse_bundle_header(int fd, struct bundle_header *header,
+ const char *report_path)
{
- 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);
+ struct strbuf buf = STRBUF_INIT;
+ int status = 0;
+
+ /* The bundle header begins with the signature */
+ if (strbuf_getwholeline_fd(&buf, fd, '\n') ||
+ strcmp(buf.buf, bundle_signature)) {
+ if (report_path)
+ error(_("'%s' does not look like a v2 bundle file"),
+ report_path);
+ status = -1;
+ goto abort;
}
- while (fgets(buffer, sizeof(buffer), ffd)
- && buffer[0] != '\n') {
- int is_prereq = buffer[0] == '-';
- int offset = is_prereq ? 1 : 0;
- int len = strlen(buffer);
+
+ /* The bundle header ends with an empty line */
+ while (!strbuf_getwholeline_fd(&buf, fd, '\n') &&
+ buf.len && buf.buf[0] != '\n') {
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;
+ int is_prereq = 0;
+
+ if (*buf.buf == '-') {
+ is_prereq = 1;
+ strbuf_remove(&buf, 0, 1);
+ }
+ strbuf_rtrim(&buf);
+
+ /*
+ * Tip lines have object name, SP, and refname.
+ * Prerequisites have object name that is optionally
+ * followed by SP and subject line.
+ */
+ if (get_sha1_hex(buf.buf, sha1) ||
+ (40 <= buf.len && !isspace(buf.buf[40])) ||
+ (!is_prereq && buf.len <= 40)) {
+ if (report_path)
+ error(_("unrecognized header: %s%s (%d)"),
+ (is_prereq ? "-" : ""), buf.buf, (int)buf.len);
+ status = -1;
+ break;
+ } else {
+ if (is_prereq)
+ add_to_ref_list(sha1, "", &header->prerequisites);
+ else
+ add_to_ref_list(sha1, buf.buf + 41, &header->references);
}
- 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);
+
+ abort:
+ if (status) {
+ close(fd);
+ fd = -1;
+ }
+ strbuf_release(&buf);
return fd;
}
+int read_bundle_header(const char *path, struct bundle_header *header)
+{
+ int fd = open(path, O_RDONLY);
+
+ if (fd < 0)
+ return error(_("could not open '%s'"), path);
+ return parse_bundle_header(fd, header, path);
+}
+
+int is_bundle(const char *path, int quiet)
+{
+ struct bundle_header header;
+ int fd = open(path, O_RDONLY);
+
+ if (fd < 0)
+ return 0;
+ memset(&header, 0, sizeof(header));
+ fd = parse_bundle_header(fd, &header, quiet ? NULL : path);
+ if (fd >= 0)
+ close(fd);
+ return (fd >= 0);
+}
+
static int list_refs(struct ref_list *r, int argc, const char **argv)
{
int i;
@@ -102,7 +137,7 @@ int verify_bundle(struct bundle_header *header, int verbose)
struct object_array refs;
struct commit *commit;
int i, ret = 0, req_nr;
- const char *message = "Repository lacks these prerequisite commits:";
+ const char *message = _("Repository lacks these prerequisite commits:");
init_revisions(&revs, NULL);
for (i = 0; i < p->nr; i++) {
@@ -122,14 +157,11 @@ int verify_bundle(struct bundle_header *header, int verbose)
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);
- }
+ refs = revs.pending;
+ revs.leak_pending = 1;
if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
i = req_nr;
while (i && (commit = get_revision(&revs)))
@@ -144,20 +176,28 @@ int verify_bundle(struct bundle_header *header, int verbose)
refs.objects[i].name);
}
- for (i = 0; i < refs.nr; i++)
- clear_commit_marks((struct commit *)refs.objects[i].item, -1);
+ clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
+ free(refs.objects);
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" : "");
+ printf_ln(Q_("The bundle contains %d ref",
+ "The bundle contains %d refs",
+ r->nr),
+ r->nr);
list_refs(r, 0, NULL);
+ if (!r->nr) {
+ printf_ln(_("The bundle records a complete history."));
+ } else {
+ r = &header->prerequisites;
+ printf_ln(Q_("The bundle requires this ref",
+ "The bundle requires these %d refs",
+ r->nr),
+ r->nr);
+ list_refs(r, 0, NULL);
+ }
}
return ret;
}
@@ -202,7 +242,7 @@ int create_bundle(struct bundle_header *header, const char *path,
const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));
const char **argv_pack = xmalloc(6 * sizeof(const char *));
int i, ref_count = 0;
- char buffer[1024];
+ struct strbuf buf = STRBUF_INIT;
struct rev_info revs;
struct child_process rls;
FILE *rls_fout;
@@ -234,29 +274,30 @@ int create_bundle(struct bundle_header *header, const char *path,
if (start_command(&rls))
return -1;
rls_fout = xfdopen(rls.out, "r");
- while (fgets(buffer, sizeof(buffer), rls_fout)) {
+ while (strbuf_getwholeline(&buf, rls_fout, '\n') != EOF) {
unsigned char sha1[20];
- if (buffer[0] == '-') {
- write_or_die(bundle_fd, buffer, strlen(buffer));
- if (!get_sha1_hex(buffer + 1, sha1)) {
+ if (buf.len > 0 && buf.buf[0] == '-') {
+ write_or_die(bundle_fd, buf.buf, buf.len);
+ if (!get_sha1_hex(buf.buf + 1, sha1)) {
struct object *object = parse_object(sha1);
object->flags |= UNINTERESTING;
- add_pending_object(&revs, object, buffer);
+ add_pending_object(&revs, object, xstrdup(buf.buf));
}
- } else if (!get_sha1_hex(buffer, sha1)) {
+ } else if (!get_sha1_hex(buf.buf, sha1)) {
struct object *object = parse_object(sha1);
object->flags |= SHOWN;
}
}
+ strbuf_release(&buf);
fclose(rls_fout);
if (finish_command(&rls))
- return error("rev-list died");
+ return error(_("rev-list died"));
/* write references */
argc = setup_revisions(argc, argv, &revs, NULL);
if (argc > 1)
- return error("unrecognized argument: %s'", argv[1]);
+ return error(_("unrecognized argument: %s"), argv[1]);
object_array_remove_duplicates(&revs.pending);
@@ -271,7 +312,7 @@ int create_bundle(struct bundle_header *header, const char *path,
continue;
if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
continue;
- if (!resolve_ref(e->name, sha1, 1, &flag))
+ if (read_ref_full(e->name, sha1, 1, &flag))
flag = 0;
display_ref = (flag & REF_ISSYMREF) ? e->name : ref;
@@ -291,7 +332,7 @@ int create_bundle(struct bundle_header *header, const char *path,
* constraints.
*/
if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) {
- warning("ref '%s' is excluded by the rev-list options",
+ warning(_("ref '%s' is excluded by the rev-list options"),
e->name);
free(ref);
continue;
@@ -336,7 +377,7 @@ int create_bundle(struct bundle_header *header, const char *path,
free(ref);
}
if (!ref_count)
- die ("Refusing to create empty bundle.");
+ die(_("Refusing to create empty bundle."));
/* end header */
write_or_die(bundle_fd, "\n", 1);
@@ -354,7 +395,7 @@ int create_bundle(struct bundle_header *header, const char *path,
rls.out = bundle_fd;
rls.git_cmd = 1;
if (start_command(&rls))
- return error("Could not spawn pack-objects");
+ return error(_("Could not spawn pack-objects"));
/*
* start_command closed bundle_fd if it was > 1
@@ -372,20 +413,23 @@ int create_bundle(struct bundle_header *header, const char *path,
}
close(rls.in);
if (finish_command(&rls))
- return error ("pack-objects died");
+ return error(_("pack-objects died"));
if (!bundle_to_stdout) {
if (commit_lock_file(&lock))
- die_errno("cannot create '%s'", path);
+ die_errno(_("cannot create '%s'"), path);
}
return 0;
}
-int unbundle(struct bundle_header *header, int bundle_fd)
+int unbundle(struct bundle_header *header, int bundle_fd, int flags)
{
const char *argv_index_pack[] = {"index-pack",
- "--fix-thin", "--stdin", NULL};
+ "--fix-thin", "--stdin", NULL, NULL};
struct child_process ip;
+ if (flags & BUNDLE_VERBOSE)
+ argv_index_pack[3] = "-v";
+
if (verify_bundle(header, 0))
return -1;
memset(&ip, 0, sizeof(ip));
@@ -394,6 +438,6 @@ int unbundle(struct bundle_header *header, int bundle_fd)
ip.no_stdout = 1;
ip.git_cmd = 1;
if (run_command(&ip))
- return error("index-pack died");
+ return error(_("index-pack died"));
return 0;
}
diff --git a/bundle.h b/bundle.h
index e2aedd6..1584e4d 100644
--- a/bundle.h
+++ b/bundle.h
@@ -14,11 +14,13 @@ struct bundle_header {
struct ref_list references;
};
+int is_bundle(const char *path, int quiet);
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);
+#define BUNDLE_VERBOSE 1
+int unbundle(struct bundle_header *header, int bundle_fd, int flags);
int list_bundle_refs(struct bundle_header *header,
int argc, const char **argv);
diff --git a/cache-tree.c b/cache-tree.c
index f755590..28ed657 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -150,15 +150,18 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
}
static int verify_cache(struct cache_entry **cache,
- int entries)
+ int entries, int flags)
{
int i, funny;
+ int silent = flags & WRITE_TREE_SILENT;
/* Verify that the tree is merged */
funny = 0;
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
- if (ce_stage(ce) || (ce->ce_flags & CE_INTENT_TO_ADD)) {
+ if (ce_stage(ce)) {
+ if (silent)
+ return -1;
if (10 < ++funny) {
fprintf(stderr, "...\n");
break;
@@ -239,10 +242,11 @@ static int update_one(struct cache_tree *it,
int entries,
const char *base,
int baselen,
- int missing_ok,
- int dryrun)
+ int flags)
{
struct strbuf buffer;
+ int missing_ok = flags & WRITE_TREE_MISSING_OK;
+ int dryrun = flags & WRITE_TREE_DRY_RUN;
int i;
if (0 <= it->entry_count && has_sha1_file(it->sha1))
@@ -286,8 +290,7 @@ static int update_one(struct cache_tree *it,
cache + i, entries - i,
path,
baselen + sublen + 1,
- missing_ok,
- dryrun);
+ flags);
if (subcnt < 0)
return subcnt;
i += subcnt - 1;
@@ -336,8 +339,8 @@ static int update_one(struct cache_tree *it,
mode, sha1_to_hex(sha1), entlen+baselen, path);
}
- if (ce->ce_flags & CE_REMOVE)
- continue; /* entry being removed */
+ if (ce->ce_flags & (CE_REMOVE | CE_INTENT_TO_ADD))
+ continue; /* entry being removed or placeholder */
strbuf_grow(&buffer, entlen + 100);
strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0');
@@ -369,14 +372,13 @@ static int update_one(struct cache_tree *it,
int cache_tree_update(struct cache_tree *it,
struct cache_entry **cache,
int entries,
- int missing_ok,
- int dryrun)
+ int flags)
{
int i;
- i = verify_cache(cache, entries);
+ i = verify_cache(cache, entries, flags);
if (i)
return i;
- i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
+ i = update_one(it, cache, entries, "", 0, flags);
if (i < 0)
return i;
return 0;
@@ -569,11 +571,9 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
was_valid = cache_tree_fully_valid(active_cache_tree);
if (!was_valid) {
- int missing_ok = flags & WRITE_TREE_MISSING_OK;
-
if (cache_tree_update(active_cache_tree,
active_cache, active_nr,
- missing_ok, 0) < 0)
+ flags) < 0)
return WRITE_TREE_UNMERGED_INDEX;
if (0 <= newfd) {
if (!write_cache(newfd, active_cache, active_nr) &&
@@ -668,3 +668,11 @@ int cache_tree_matches_traversal(struct cache_tree *root,
return it->entry_count;
return 0;
}
+
+int update_main_cache_tree(int flags)
+{
+ if (!the_index.cache_tree)
+ the_index.cache_tree = cache_tree();
+ return cache_tree_update(the_index.cache_tree,
+ the_index.cache, the_index.cache_nr, flags);
+}
diff --git a/cache-tree.h b/cache-tree.h
index 3df641f..d8cb2e9 100644
--- a/cache-tree.h
+++ b/cache-tree.h
@@ -29,11 +29,15 @@ void cache_tree_write(struct strbuf *, struct cache_tree *root);
struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
int cache_tree_fully_valid(struct cache_tree *);
-int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
+int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int);
+
+int update_main_cache_tree(int);
/* bitmasks to write_cache_as_tree flags */
#define WRITE_TREE_MISSING_OK 1
#define WRITE_TREE_IGNORE_CACHE_TREE 2
+#define WRITE_TREE_DRY_RUN 4
+#define WRITE_TREE_SILENT 8
/* error return codes */
#define WRITE_TREE_UNREADABLE_INDEX (-1)
diff --git a/cache.h b/cache.h
index 6b24c25..c22b928 100644
--- a/cache.h
+++ b/cache.h
@@ -6,6 +6,7 @@
#include "hash.h"
#include "advice.h"
#include "gettext.h"
+#include "convert.h"
#include SHA1_HEADER
#ifndef git_SHA_CTX
@@ -34,6 +35,7 @@ int git_inflate(git_zstream *, int flush);
void git_deflate_init(git_zstream *, int level);
void git_deflate_init_gzip(git_zstream *, int level);
void git_deflate_end(git_zstream *);
+int git_deflate_abort(git_zstream *);
int git_deflate_end_gently(git_zstream *);
int git_deflate(git_zstream *, int flush);
unsigned long git_deflate_bound(git_zstream *, unsigned long);
@@ -103,6 +105,9 @@ struct cache_header {
unsigned int hdr_entries;
};
+#define INDEX_FORMAT_LB 2
+#define INDEX_FORMAT_UB 4
+
/*
* The "cache_time" is just the low 32 bits of the
* time. It doesn't matter if it overflows - we only
@@ -113,48 +118,6 @@ struct cache_time {
unsigned int nsec;
};
-/*
- * dev/ino/uid/gid/size are also just tracked to the low 32 bits
- * Again - this is just a (very strong in practice) heuristic that
- * the inode hasn't changed.
- *
- * 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 */
-};
-
-/*
- * This struct is used when CE_EXTENDED bit is 1
- * The struct must match ondisk_cache_entry exactly from
- * ctime till flags
- */
-struct ondisk_cache_entry_extended {
- 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;
- unsigned short flags2;
- char name[FLEX_ARRAY]; /* more */
-};
-
struct cache_entry {
struct cache_time ce_ctime;
struct cache_time ce_mtime;
@@ -167,6 +130,7 @@ struct cache_entry {
unsigned int ce_flags;
unsigned char sha1[20];
struct cache_entry *next;
+ struct cache_entry *dir_next;
char name[FLEX_ARRAY]; /* more */
};
@@ -250,9 +214,6 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
}
#define ce_size(ce) cache_entry_size(ce_namelen(ce))
-#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
- ondisk_cache_entry_extended_size(ce_namelen(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_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
@@ -303,18 +264,15 @@ static inline unsigned int canon_mode(unsigned int mode)
return S_IFGITLINK;
}
-#define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
-#define cache_entry_size(len) flexible_size(cache_entry,len)
-#define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
-#define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
+#define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1)
struct index_state {
struct cache_entry **cache;
+ unsigned int version;
unsigned int cache_nr, cache_alloc, cache_changed;
struct string_list *resolve_undo;
struct cache_tree *cache_tree;
struct cache_time timestamp;
- void *alloc;
unsigned name_hash_initialized : 1,
initialized : 1;
struct hash_table name_hash;
@@ -393,6 +351,7 @@ static inline enum object_type object_type(unsigned int mode)
}
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
@@ -429,12 +388,16 @@ extern char *git_work_tree_cfg;
extern int is_inside_work_tree(void);
extern int have_git_dir(void);
extern const char *get_git_dir(void);
+extern int is_git_directory(const char *path);
extern char *get_object_directory(void);
extern char *get_index_file(void);
extern char *get_graft_file(void);
extern int set_git_dir(const char *path);
+extern const char *get_git_namespace(void);
+extern const char *strip_namespace(const char *namespaced_ref);
extern const char *get_git_work_tree(void);
extern const char *read_gitfile(const char *path);
+extern const char *resolve_gitdir(const char *suspect);
extern void set_git_work_tree(const char *tree);
#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
@@ -446,8 +409,11 @@ extern const char *setup_git_directory(void);
extern char *prefix_path(const char *prefix, int len, const char *path);
extern const char *prefix_filename(const char *prefix, int len, const char *path);
extern int check_filename(const char *prefix, const char *name);
-extern void verify_filename(const char *prefix, const char *name);
+extern void verify_filename(const char *prefix,
+ const char *name,
+ int diagnose_misspelt_rev);
extern void verify_non_filename(const char *prefix, const char *name);
+extern int path_inside_repo(const char *prefix, const char *path);
#define INIT_DB_QUIET 0x0001
@@ -584,6 +550,7 @@ extern int warn_ambiguous_refs;
extern int shared_repository;
extern const char *apply_default_whitespace;
extern const char *apply_default_ignorewhitespace;
+extern const char *git_attributes_file;
extern int zlib_compression_level;
extern int core_compression_level;
extern int core_compression_seen;
@@ -591,40 +558,12 @@ extern size_t packed_git_window_size;
extern size_t packed_git_limit;
extern size_t delta_base_cache_limit;
extern unsigned long big_file_threshold;
+extern unsigned long pack_size_limit_cfg;
extern int read_replace_refs;
extern int fsync_object_files;
extern int core_preload_index;
extern int core_apply_sparse_checkout;
-enum safe_crlf {
- SAFE_CRLF_FALSE = 0,
- SAFE_CRLF_FAIL = 1,
- SAFE_CRLF_WARN = 2
-};
-
-extern enum safe_crlf safe_crlf;
-
-enum auto_crlf {
- AUTO_CRLF_FALSE = 0,
- AUTO_CRLF_TRUE = 1,
- AUTO_CRLF_INPUT = -1
-};
-
-extern enum auto_crlf auto_crlf;
-
-enum eol {
- EOL_UNSET,
- EOL_CRLF,
- EOL_LF,
-#ifdef NATIVE_CRLF
- EOL_NATIVE = EOL_CRLF
-#else
- EOL_NATIVE = EOL_LF
-#endif
-};
-
-extern enum eol core_eol;
-
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0,
@@ -644,8 +583,10 @@ enum rebase_setup_type {
enum push_default_type {
PUSH_DEFAULT_NOTHING = 0,
PUSH_DEFAULT_MATCHING,
+ PUSH_DEFAULT_SIMPLE,
PUSH_DEFAULT_UPSTREAM,
- PUSH_DEFAULT_CURRENT
+ PUSH_DEFAULT_CURRENT,
+ PUSH_DEFAULT_UNSPECIFIED
};
extern enum branch_track git_branch_track;
@@ -681,6 +622,8 @@ extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
__attribute__((format (printf, 3, 4)));
extern char *git_pathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
+extern char *mkpathdup(const char *fmt, ...)
+ __attribute__((format (printf, 1, 2)));
/* Return a statically allocated filename matching the sha1 signature */
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
@@ -728,6 +671,19 @@ static inline void hashclr(unsigned char *hash)
#define EMPTY_TREE_SHA1_BIN \
((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
+#define EMPTY_BLOB_SHA1_HEX \
+ "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
+#define EMPTY_BLOB_SHA1_BIN_LITERAL \
+ "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
+ "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
+#define EMPTY_BLOB_SHA1_BIN \
+ ((const unsigned char *) EMPTY_BLOB_SHA1_BIN_LITERAL)
+
+static inline int is_empty_blob_sha1(const unsigned char *sha1)
+{
+ return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
+}
+
int git_mkstemp(char *path, size_t n, const char *template);
int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
@@ -757,11 +713,12 @@ int set_shared_perm(const char *path, int mode);
int safe_create_leading_directories(char *path);
int safe_create_leading_directories_const(const char *path);
int mkdir_in_gitdir(const char *path);
+extern void home_config_paths(char **global, char **xdg, char *file);
extern char *expand_user_path(const char *path);
-char *enter_repo(char *path, int strict);
+const char *enter_repo(const char *path, int strict);
static inline int is_absolute_path(const char *path)
{
- return path[0] == '/' || has_dos_drive_prefix(path);
+ return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
}
int is_directory(const char *);
const char *real_path(const char *path);
@@ -794,10 +751,16 @@ extern int hash_sha1_file(const void *buf, unsigned long len, const char *type,
extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
extern int force_object_loose(const unsigned char *sha1, time_t mtime);
+extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
+extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
+extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
/* global flag to enable extra checks when accessing packed objects */
extern int do_check_packed_object_crc;
+/* for development: log offset of pack access */
+extern const char *log_pack_access;
+
extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@ -837,10 +800,54 @@ static inline int get_sha1_with_context(const char *str, unsigned char *sha1, st
{
return get_sha1_with_context_1(str, sha1, orc, 0, NULL);
}
+
+/*
+ * Try to read a SHA1 in hexadecimal format from the 40 characters
+ * starting at hex. Write the 20-byte result to sha1 in binary form.
+ * Return 0 on success. Reading stops if a NUL is encountered in the
+ * input, so it is safe to pass this function an arbitrary
+ * null-terminated string.
+ */
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
+
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
-extern int read_ref(const char *filename, unsigned char *sha1);
-extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
+extern int read_ref_full(const char *refname, unsigned char *sha1,
+ int reading, int *flags);
+extern int read_ref(const char *refname, unsigned char *sha1);
+
+/*
+ * Resolve a reference, recursively following symbolic refererences.
+ *
+ * Store the referred-to object's name in sha1 and return the name of
+ * the non-symbolic reference that ultimately pointed at it. The
+ * return value, if not NULL, is a pointer into either a static buffer
+ * or the input ref.
+ *
+ * If the reference cannot be resolved to an object, the behavior
+ * depends on the "reading" argument:
+ *
+ * - If reading is set, return NULL.
+ *
+ * - If reading is not set, clear sha1 and return the name of the last
+ * reference name in the chain, which will either be a non-symbolic
+ * reference or an undefined reference. If this is a prelude to
+ * "writing" to the ref, the return value is the name of the ref
+ * that will actually be created or changed.
+ *
+ * If flag is non-NULL, set the value that it points to the
+ * combination of REF_ISPACKED (if the reference was found among the
+ * packed references) and REF_ISSYMREF (if the initial reference was a
+ * symbolic reference).
+ *
+ * If ref is not a properly-formatted, normalized reference, return
+ * NULL. If more than MAXDEPTH recursive symbolic lookups are needed,
+ * give up and return NULL.
+ *
+ * errno is sometimes set on errors, but not always.
+ */
+extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);
+extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);
+
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 interpret_branch_name(const char *str, struct strbuf *);
@@ -848,7 +855,7 @@ extern int get_sha1_mb(const char *str, unsigned char *sha1);
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[];
+#define ref_fetch_rules ref_rev_parse_rules
extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
extern int validate_headref(const char *ref);
@@ -876,10 +883,8 @@ enum date_mode {
};
const char *show_date(unsigned long time, int timezone, enum date_mode mode);
-const char *show_date_relative(unsigned long time, int tz,
- const struct timeval *now,
- char *timebuf,
- size_t timebuf_size);
+void show_date_relative(unsigned long time, int tz, const struct timeval *now,
+ struct strbuf *timebuf);
int parse_date(const char *date, char *buf, int bufsize);
int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
void datestamp(char *buf, int bufsize);
@@ -888,15 +893,35 @@ unsigned long approxidate_careful(const char *, int *);
unsigned long approxidate_relative(const char *date, const struct timeval *now);
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
+#define IDENT_STRICT 1
+#define IDENT_NO_DATE 2
+#define IDENT_NO_NAME 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);
+extern const char *ident_default_name(void);
+extern const char *ident_default_email(void);
+extern const char *ident_default_date(void);
extern const char *git_editor(void);
extern const char *git_pager(int stdout_is_tty);
+extern int git_ident_config(const char *, const char *, void *);
+
+struct ident_split {
+ const char *name_begin;
+ const char *name_end;
+ const char *mail_begin;
+ const char *mail_end;
+ const char *date_begin;
+ const char *date_end;
+ const char *tz_begin;
+ const char *tz_end;
+};
+/*
+ * Signals an success with 0, but time part of the result may be NULL
+ * if the input lacks timestamp and zone
+ */
+extern int split_ident_line(struct ident_split *, const char *, int);
struct checkout {
const char *base_dir;
@@ -920,7 +945,9 @@ struct cache_def {
extern int has_symlink_leading_path(const char *name, int len);
extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
extern int check_leading_path(const char *name, int len);
+extern int threaded_check_leading_path(struct cache_def *cache, const char *name, int len);
extern int has_dirs_only_path(const char *name, int len, int prefix_len);
+extern int threaded_has_dirs_only_path(struct cache_def *cache, const char *name, int len, int prefix_len);
extern void schedule_dir_for_removal(const char *name, int len);
extern void remove_scheduled_dirs(void);
@@ -930,6 +957,7 @@ extern struct alternate_object_database {
char base[FLEX_ARRAY]; /* more */
} *alt_odb_list;
extern void prepare_alt_odb(void);
+extern void read_info_alternates(const char * relative_base, int depth);
extern void add_to_alternates_file(const char *reference);
typedef int alt_odb_fn(struct alternate_object_database *, void *);
extern void foreach_alt_odb(alt_odb_fn, void*);
@@ -999,17 +1027,16 @@ struct ref {
extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
#define CONNECT_VERBOSE (1u << 0)
-extern char *git_getpass(const char *prompt);
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
extern int finish_connect(struct child_process *conn);
extern int git_connection_is_socket(struct child_process *conn);
-extern int path_match(const char *path, int nr, char **match);
struct extra_have_objects {
int nr, alloc;
unsigned char (*array)[20];
};
-extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *);
+extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
extern int server_supports(const char *feature);
+extern const char *parse_feature_request(const char *features, const char *feature);
extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
@@ -1032,10 +1059,40 @@ extern struct packed_git *add_packed_git(const char *, int, int);
extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t);
extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
+extern int is_pack_valid(struct packed_git *);
extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
-extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+extern int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *);
+
+struct object_info {
+ /* Request */
+ unsigned long *sizep;
+
+ /* Response */
+ enum {
+ OI_CACHED,
+ OI_LOOSE,
+ OI_PACKED,
+ OI_DBCACHED
+ } whence;
+ union {
+ /*
+ * struct {
+ * ... Nothing to expose in this case
+ * } cached;
+ * struct {
+ * ... Nothing to expose in this case
+ * } loose;
+ */
+ struct {
+ struct packed_git *pack;
+ off_t offset;
+ unsigned int is_delta;
+ } packed;
+ } u;
+};
+extern int sha1_object_info_extended(const unsigned char *, struct object_info *);
/* Dumb servers support */
extern int update_server_info(int);
@@ -1056,6 +1113,8 @@ extern int git_config_from_file(config_fn_t fn, const char *, void *);
extern void git_config_push_parameter(const char *text);
extern int git_config_from_parameters(config_fn_t fn, void *data);
extern int git_config(config_fn_t fn, void *);
+extern int git_config_with_options(config_fn_t fn, void *,
+ const char *filename, int respect_includes);
extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
extern int git_parse_ulong(const char *, unsigned long *);
extern int git_config_int(const char *, const char *);
@@ -1065,10 +1124,13 @@ extern int git_config_bool(const char *, const char *);
extern int git_config_maybe_bool(const char *, const char *);
extern int git_config_string(const char **, const char *, const char *);
extern int git_config_pathname(const char **, const char *, const char *);
+extern int git_config_set_in_file(const char *, const char *, const char *);
extern int git_config_set(const char *, const char *);
extern int git_config_parse_key(const char *, char **, int *);
extern int git_config_set_multivar(const char *, const char *, const char *, int);
+extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
extern int git_config_rename_section(const char *, const char *);
+extern int git_config_rename_section_in_file(const char *, const char *, const char *);
extern const char *git_etc_gitconfig(void);
extern int check_repository_format_version(const char *var, const char *value, void *cb);
extern int git_env_bool(const char *, int);
@@ -1077,11 +1139,16 @@ extern int config_error_nonbool(const char *);
extern const char *get_log_output_encoding(void);
extern const char *get_commit_output_encoding(void);
-extern const char *config_exclusive_filename;
+extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
+
+struct config_include_data {
+ int depth;
+ config_fn_t fn;
+ void *data;
+};
+#define CONFIG_INCLUDE_INIT { 0 }
+extern int git_config_include(const char *name, const char *value, void *data);
-#define MAX_GITNAME (1000)
-extern char git_default_email[MAX_GITNAME];
-extern char git_default_name[MAX_GITNAME];
#define IDENT_NAME_GIVEN 01
#define IDENT_MAIL_GIVEN 02
#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN)
@@ -1114,6 +1181,8 @@ extern void setup_pager(void);
extern const char *pager_program;
extern int pager_in_use(void);
extern int pager_use_color;
+extern int term_columns(void);
+extern int decimal_width(int);
extern const char *editor_program;
extern const char *askpass_program;
@@ -1143,13 +1212,6 @@ extern void trace_strbuf(const char *key, const struct strbuf *buf);
void packet_trace_identity(const char *prog);
-/* convert.c */
-/* returns 1 if *dst was used */
-extern int convert_to_git(const char *path, const char *src, size_t len,
- struct strbuf *dst, enum safe_crlf checksafe);
-extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
-extern int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst);
-
/* add */
/*
* return 0 if success, 1 - if addition of a file failed and
@@ -1207,4 +1269,6 @@ extern struct startup_info *startup_info;
/* builtin/merge.c */
int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
+int sane_execvp(const char *file, char *const argv[]);
+
#endif /* CACHE_H */
diff --git a/color.c b/color.c
index 3db214c..e8e2681 100644
--- a/color.c
+++ b/color.c
@@ -1,7 +1,8 @@
#include "cache.h"
#include "color.h"
-int git_use_color_default = 0;
+static int git_use_color_default = 0;
+int color_stdout_is_tty = -1;
/*
* The list of available column colors.
@@ -157,7 +158,7 @@ bad:
die("bad color value '%.*s' for variable '%s'", value_len, value, var);
}
-int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
+int git_config_colorbool(const char *var, const char *value)
{
if (value) {
if (!strcasecmp(value, "never"))
@@ -165,7 +166,7 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
if (!strcasecmp(value, "always"))
return 1;
if (!strcasecmp(value, "auto"))
- goto auto_color;
+ return GIT_COLOR_AUTO;
}
if (!var)
@@ -176,10 +177,14 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
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)) {
+ return GIT_COLOR_AUTO;
+}
+
+static int check_auto_color(void)
+{
+ if (color_stdout_is_tty < 0)
+ color_stdout_is_tty = isatty(1);
+ if (color_stdout_is_tty || (pager_in_use() && pager_use_color)) {
char *term = getenv("TERM");
if (term && strcmp(term, "dumb"))
return 1;
@@ -187,13 +192,36 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
return 0;
}
-int git_color_default_config(const char *var, const char *value, void *cb)
+int want_color(int var)
+{
+ static int want_auto = -1;
+
+ if (var < 0)
+ var = git_use_color_default;
+
+ if (var == GIT_COLOR_AUTO) {
+ if (want_auto < 0)
+ want_auto = check_auto_color();
+ return want_auto;
+ }
+ return var;
+}
+
+int git_color_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "color.ui")) {
- git_use_color_default = git_config_colorbool(var, value, -1);
+ git_use_color_default = git_config_colorbool(var, value);
return 0;
}
+ return 0;
+}
+
+int git_color_default_config(const char *var, const char *value, void *cb)
+{
+ if (git_color_config(var, value, cb) < 0)
+ return -1;
+
return git_default_config(var, value, cb);
}
diff --git a/color.h b/color.h
index 68a926a..9a8495b 100644
--- a/color.h
+++ b/color.h
@@ -49,20 +49,34 @@ struct strbuf;
#define GIT_COLOR_NIL "NIL"
/*
- * This variable stores the value of color.ui
+ * The first three are chosen to match common usage in the code, and what is
+ * returned from git_config_colorbool. The "auto" value can be returned from
+ * config_colorbool, and will be converted by want_color() into either 0 or 1.
*/
-extern int git_use_color_default;
+#define GIT_COLOR_UNKNOWN -1
+#define GIT_COLOR_NEVER 0
+#define GIT_COLOR_ALWAYS 1
+#define GIT_COLOR_AUTO 2
/* A default list of colors to use for commit graphs and show-branch output */
extern const char *column_colors_ansi[];
extern const int column_colors_ansi_max;
/*
- * Use this instead of git_default_config if you need the value of color.ui.
+ * Generally the color code will lazily figure this out itself, but
+ * this provides a mechanism for callers to override autodetection.
*/
+extern int color_stdout_is_tty;
+
+/*
+ * Use the first one if you need only color config; the second is a convenience
+ * if you are just going to change to git_default_config, too.
+ */
+int git_color_config(const char *var, const char *value, void *cb);
int git_color_default_config(const char *var, const char *value, void *cb);
-int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
+int git_config_colorbool(const char *var, const char *value);
+int want_color(int var);
void color_parse(const char *value, const char *var, char *dst);
void color_parse_mem(const char *value, int len, const char *var, char *dst);
__attribute__((format (printf, 3, 4)))
diff --git a/column.c b/column.c
new file mode 100644
index 0000000..9367ba5
--- /dev/null
+++ b/column.c
@@ -0,0 +1,434 @@
+#include "cache.h"
+#include "column.h"
+#include "string-list.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "utf8.h"
+
+#define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \
+ (x) * (d)->rows + (y) : \
+ (y) * (d)->cols + (x))
+
+struct column_data {
+ const struct string_list *list;
+ unsigned int colopts;
+ struct column_options opts;
+
+ int rows, cols;
+ int *len; /* cell length */
+ int *width; /* index to the longest row in column */
+};
+
+/* return length of 's' in letters, ANSI escapes stripped */
+static int item_length(unsigned int colopts, const char *s)
+{
+ int len, i = 0;
+ struct strbuf str = STRBUF_INIT;
+
+ strbuf_addstr(&str, s);
+ while ((s = strstr(str.buf + i, "\033[")) != NULL) {
+ int len = strspn(s + 2, "0123456789;");
+ i = s - str.buf;
+ strbuf_remove(&str, i, len + 3); /* \033[<len><func char> */
+ }
+ len = utf8_strwidth(str.buf);
+ strbuf_release(&str);
+ return len;
+}
+
+/*
+ * Calculate cell width, rows and cols for a table of equal cells, given
+ * table width and how many spaces between cells.
+ */
+static void layout(struct column_data *data, int *width)
+{
+ int i;
+
+ *width = 0;
+ for (i = 0; i < data->list->nr; i++)
+ if (*width < data->len[i])
+ *width = data->len[i];
+
+ *width += data->opts.padding;
+
+ data->cols = (data->opts.width - strlen(data->opts.indent)) / *width;
+ if (data->cols == 0)
+ data->cols = 1;
+
+ data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
+}
+
+static void compute_column_width(struct column_data *data)
+{
+ int i, x, y;
+ for (x = 0; x < data->cols; x++) {
+ data->width[x] = XY2LINEAR(data, x, 0);
+ for (y = 0; y < data->rows; y++) {
+ i = XY2LINEAR(data, x, y);
+ if (i < data->list->nr &&
+ data->len[data->width[x]] < data->len[i])
+ data->width[x] = i;
+ }
+ }
+}
+
+/*
+ * Shrink all columns by shortening them one row each time (and adding
+ * more columns along the way). Hopefully the longest cell will be
+ * moved to the next column, column is shrunk so we have more space
+ * for new columns. The process ends when the whole thing no longer
+ * fits in data->total_width.
+ */
+static void shrink_columns(struct column_data *data)
+{
+ data->width = xrealloc(data->width,
+ sizeof(*data->width) * data->cols);
+ while (data->rows > 1) {
+ int x, total_width, cols, rows;
+ rows = data->rows;
+ cols = data->cols;
+
+ data->rows--;
+ data->cols = DIV_ROUND_UP(data->list->nr, data->rows);
+ if (data->cols != cols)
+ data->width = xrealloc(data->width,
+ sizeof(*data->width) * data->cols);
+ compute_column_width(data);
+
+ total_width = strlen(data->opts.indent);
+ for (x = 0; x < data->cols; x++) {
+ total_width += data->len[data->width[x]];
+ total_width += data->opts.padding;
+ }
+ if (total_width > data->opts.width) {
+ data->rows = rows;
+ data->cols = cols;
+ break;
+ }
+ }
+ compute_column_width(data);
+}
+
+/* Display without layout when not enabled */
+static void display_plain(const struct string_list *list,
+ const char *indent, const char *nl)
+{
+ int i;
+
+ for (i = 0; i < list->nr; i++)
+ printf("%s%s%s", indent, list->items[i].string, nl);
+}
+
+/* Print a cell to stdout with all necessary leading/traling space */
+static int display_cell(struct column_data *data, int initial_width,
+ const char *empty_cell, int x, int y)
+{
+ int i, len, newline;
+
+ i = XY2LINEAR(data, x, y);
+ if (i >= data->list->nr)
+ return -1;
+
+ len = data->len[i];
+ if (data->width && data->len[data->width[x]] < initial_width) {
+ /*
+ * empty_cell has initial_width chars, if real column
+ * is narrower, increase len a bit so we fill less
+ * space.
+ */
+ len += initial_width - data->len[data->width[x]];
+ len -= data->opts.padding;
+ }
+
+ if (COL_LAYOUT(data->colopts) == COL_COLUMN)
+ newline = i + data->rows >= data->list->nr;
+ else
+ newline = x == data->cols - 1 || i == data->list->nr - 1;
+
+ printf("%s%s%s",
+ x == 0 ? data->opts.indent : "",
+ data->list->items[i].string,
+ newline ? data->opts.nl : empty_cell + len);
+ return 0;
+}
+
+/* Display COL_COLUMN or COL_ROW */
+static void display_table(const struct string_list *list,
+ unsigned int colopts,
+ const struct column_options *opts)
+{
+ struct column_data data;
+ int x, y, i, initial_width;
+ char *empty_cell;
+
+ memset(&data, 0, sizeof(data));
+ data.list = list;
+ data.colopts = colopts;
+ data.opts = *opts;
+
+ data.len = xmalloc(sizeof(*data.len) * list->nr);
+ for (i = 0; i < list->nr; i++)
+ data.len[i] = item_length(colopts, list->items[i].string);
+
+ layout(&data, &initial_width);
+
+ if (colopts & COL_DENSE)
+ shrink_columns(&data);
+
+ empty_cell = xmalloc(initial_width + 1);
+ memset(empty_cell, ' ', initial_width);
+ empty_cell[initial_width] = '\0';
+ for (y = 0; y < data.rows; y++) {
+ for (x = 0; x < data.cols; x++)
+ if (display_cell(&data, initial_width, empty_cell, x, y))
+ break;
+ }
+
+ free(data.len);
+ free(data.width);
+ free(empty_cell);
+}
+
+void print_columns(const struct string_list *list, unsigned int colopts,
+ const struct column_options *opts)
+{
+ struct column_options nopts;
+
+ if (!list->nr)
+ return;
+ assert((colopts & COL_ENABLE_MASK) != COL_AUTO);
+
+ memset(&nopts, 0, sizeof(nopts));
+ nopts.indent = opts && opts->indent ? opts->indent : "";
+ nopts.nl = opts && opts->nl ? opts->nl : "\n";
+ nopts.padding = opts ? opts->padding : 1;
+ nopts.width = opts && opts->width ? opts->width : term_columns() - 1;
+ if (!column_active(colopts)) {
+ display_plain(list, "", "\n");
+ return;
+ }
+ switch (COL_LAYOUT(colopts)) {
+ case COL_PLAIN:
+ display_plain(list, nopts.indent, nopts.nl);
+ break;
+ case COL_ROW:
+ case COL_COLUMN:
+ display_table(list, colopts, &nopts);
+ break;
+ default:
+ die("BUG: invalid layout mode %d", COL_LAYOUT(colopts));
+ }
+}
+
+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)
+{
+ if ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {
+ if (stdout_is_tty < 0)
+ stdout_is_tty = isatty(1);
+ *colopts &= ~COL_ENABLE_MASK;
+ if (stdout_is_tty)
+ *colopts |= COL_ENABLED;
+ }
+ return 0;
+}
+
+struct colopt {
+ const char *name;
+ unsigned int value;
+ unsigned int mask;
+};
+
+#define LAYOUT_SET 1
+#define ENABLE_SET 2
+
+static int parse_option(const char *arg, int len, unsigned int *colopts,
+ int *group_set)
+{
+ struct colopt opts[] = {
+ { "always", COL_ENABLED, COL_ENABLE_MASK },
+ { "never", COL_DISABLED, COL_ENABLE_MASK },
+ { "auto", COL_AUTO, COL_ENABLE_MASK },
+ { "plain", COL_PLAIN, COL_LAYOUT_MASK },
+ { "column", COL_COLUMN, COL_LAYOUT_MASK },
+ { "row", COL_ROW, COL_LAYOUT_MASK },
+ { "dense", COL_DENSE, 0 },
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(opts); i++) {
+ int set = 1, arg_len = len, name_len;
+ const char *arg_str = arg;
+
+ if (!opts[i].mask) {
+ if (arg_len > 2 && !strncmp(arg_str, "no", 2)) {
+ arg_str += 2;
+ arg_len -= 2;
+ set = 0;
+ }
+ }
+
+ name_len = strlen(opts[i].name);
+ if (arg_len != name_len ||
+ strncmp(arg_str, opts[i].name, name_len))
+ continue;
+
+ switch (opts[i].mask) {
+ case COL_ENABLE_MASK:
+ *group_set |= ENABLE_SET;
+ break;
+ case COL_LAYOUT_MASK:
+ *group_set |= LAYOUT_SET;
+ break;
+ }
+
+ if (opts[i].mask)
+ *colopts = (*colopts & ~opts[i].mask) | opts[i].value;
+ else {
+ if (set)
+ *colopts |= opts[i].value;
+ else
+ *colopts &= ~opts[i].value;
+ }
+ return 0;
+ }
+
+ return error("unsupported option '%s'", arg);
+}
+
+static int parse_config(unsigned int *colopts, const char *value)
+{
+ const char *sep = " ,";
+ int group_set = 0;
+
+ while (*value) {
+ int len = strcspn(value, sep);
+ if (len) {
+ if (parse_option(value, len, colopts, &group_set))
+ return -1;
+
+ value += len;
+ }
+ value += strspn(value, sep);
+ }
+ /*
+ * Setting layout implies "always" if neither always, never
+ * nor auto is specified.
+ *
+ * Current value in COL_ENABLE_MASK is disregarded. This means if
+ * you set column.ui = auto and pass --column=row, then "auto"
+ * will become "always".
+ */
+ if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))
+ *colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+ return 0;
+}
+
+static int column_config(const char *var, const char *value,
+ const char *key, unsigned int *colopts)
+{
+ if (!value)
+ return config_error_nonbool(var);
+ if (parse_config(colopts, value))
+ return error("invalid column.%s mode %s", key, value);
+ return 0;
+}
+
+int git_column_config(const char *var, const char *value,
+ const char *command, unsigned int *colopts)
+{
+ const char *it = skip_prefix(var, "column.");
+ if (!it)
+ return 0;
+
+ if (!strcmp(it, "ui"))
+ return column_config(var, value, "ui", colopts);
+
+ if (command && !strcmp(it, command))
+ return column_config(var, value, it, colopts);
+
+ return 0;
+}
+
+int parseopt_column_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ unsigned int *colopts = opt->value;
+ *colopts |= COL_PARSEOPT;
+ *colopts &= ~COL_ENABLE_MASK;
+ if (unset) /* --no-column == never */
+ return 0;
+ /* --column == always unless "arg" states otherwise */
+ *colopts |= COL_ENABLED;
+ if (arg)
+ return parse_config(colopts, arg);
+
+ return 0;
+}
+
+static int fd_out = -1;
+static struct child_process column_process;
+
+int run_column_filter(int colopts, const struct column_options *opts)
+{
+ const char *av[10];
+ int ret, ac = 0;
+ struct strbuf sb_colopt = STRBUF_INIT;
+ struct strbuf sb_width = STRBUF_INIT;
+ struct strbuf sb_padding = STRBUF_INIT;
+
+ if (fd_out != -1)
+ return -1;
+
+ av[ac++] = "column";
+ strbuf_addf(&sb_colopt, "--raw-mode=%d", colopts);
+ av[ac++] = sb_colopt.buf;
+ if (opts && opts->width) {
+ strbuf_addf(&sb_width, "--width=%d", opts->width);
+ av[ac++] = sb_width.buf;
+ }
+ if (opts && opts->indent) {
+ av[ac++] = "--indent";
+ av[ac++] = opts->indent;
+ }
+ if (opts && opts->padding) {
+ strbuf_addf(&sb_padding, "--padding=%d", opts->padding);
+ av[ac++] = sb_padding.buf;
+ }
+ av[ac] = NULL;
+
+ fflush(stdout);
+ memset(&column_process, 0, sizeof(column_process));
+ column_process.in = -1;
+ column_process.out = dup(1);
+ column_process.git_cmd = 1;
+ column_process.argv = av;
+
+ ret = start_command(&column_process);
+
+ strbuf_release(&sb_colopt);
+ strbuf_release(&sb_width);
+ strbuf_release(&sb_padding);
+
+ if (ret)
+ return -2;
+
+ fd_out = dup(1);
+ close(1);
+ dup2(column_process.in, 1);
+ close(column_process.in);
+ return 0;
+}
+
+int stop_column_filter(void)
+{
+ if (fd_out == -1)
+ return -1;
+
+ fflush(stdout);
+ close(1);
+ finish_command(&column_process);
+ dup2(fd_out, 1);
+ close(fd_out);
+ fd_out = -1;
+ return 0;
+}
diff --git a/column.h b/column.h
new file mode 100644
index 0000000..0a61917
--- /dev/null
+++ b/column.h
@@ -0,0 +1,45 @@
+#ifndef COLUMN_H
+#define COLUMN_H
+
+#define COL_LAYOUT_MASK 0x000F
+#define COL_ENABLE_MASK 0x0030 /* always, never or auto */
+#define COL_PARSEOPT 0x0040 /* --column is given from cmdline */
+#define COL_DENSE 0x0080 /* Shrink columns when possible,
+ making space for more columns */
+
+#define COL_DISABLED 0x0000 /* must be zero */
+#define COL_ENABLED 0x0010
+#define COL_AUTO 0x0020
+
+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)
+#define COL_COLUMN 0 /* Fill columns before rows */
+#define COL_ROW 1 /* Fill rows before columns */
+#define COL_PLAIN 15 /* one column */
+
+#define explicitly_enable_column(c) \
+ (((c) & COL_PARSEOPT) && column_active(c))
+
+struct column_options {
+ int width;
+ int padding;
+ const char *indent;
+ const char *nl;
+};
+
+struct option;
+extern int parseopt_column_callback(const struct option *, const char *, int);
+extern int git_column_config(const char *var, const char *value,
+ const char *command, unsigned int *colopts);
+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);
+static inline int column_active(unsigned int colopts)
+{
+ return (colopts & COL_ENABLE_MASK) == COL_ENABLED;
+}
+
+extern void print_columns(const struct string_list *list, unsigned int colopts,
+ const struct column_options *opts);
+
+extern int run_column_filter(int colopts, const struct column_options *);
+extern int stop_column_filter(void);
+
+#endif
diff --git a/combine-diff.c b/combine-diff.c
index b11eb71..9786680 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -8,6 +8,7 @@
#include "log-tree.h"
#include "refs.h"
#include "userdiff.h"
+#include "sha1-array.h"
static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
{
@@ -422,7 +423,7 @@ static int make_hunks(struct sline *sline, unsigned long cnt,
hunk_begin, j);
la = (la + context < cnt + 1) ?
(la + context) : cnt + 1;
- while (j <= --la) {
+ while (la && j <= --la) {
if (sline[la].flag & mark) {
contin = 1;
break;
@@ -702,9 +703,8 @@ static void show_combined_header(struct combine_diff_path *elem,
int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
- int use_color = DIFF_OPT_TST(opt, COLOR_DIFF);
- const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
- const char *c_reset = diff_get_color(use_color, DIFF_RESET);
+ const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO);
+ const char *c_reset = diff_get_color_opt(opt, DIFF_RESET);
const char *abb;
int added = 0;
int deleted = 0;
@@ -964,7 +964,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
show_combined_header(elem, num_parent, dense, rev,
mode_differs, 1);
dump_sline(sline, cnt, num_parent,
- DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted);
+ opt->use_color, result_deleted);
}
free(result);
@@ -1050,16 +1050,81 @@ void show_combined_diff(struct combine_diff_path *p,
show_patch_diff(p, num_parent, dense, 1, rev);
}
+static void free_combined_pair(struct diff_filepair *pair)
+{
+ free(pair->two);
+ free(pair);
+}
+
+/*
+ * A combine_diff_path expresses N parents on the LHS against 1 merge
+ * result. Synthesize a diff_filepair that has N entries on the "one"
+ * side and 1 entry on the "two" side.
+ *
+ * In the future, we might want to add more data to combine_diff_path
+ * so that we can fill fields we are ignoring (most notably, size) here,
+ * but currently nobody uses it, so this should suffice for now.
+ */
+static struct diff_filepair *combined_pair(struct combine_diff_path *p,
+ int num_parent)
+{
+ int i;
+ struct diff_filepair *pair;
+ struct diff_filespec *pool;
+
+ pair = xmalloc(sizeof(*pair));
+ pool = xcalloc(num_parent + 1, sizeof(struct diff_filespec));
+ pair->one = pool + 1;
+ pair->two = pool;
+
+ for (i = 0; i < num_parent; i++) {
+ pair->one[i].path = p->path;
+ pair->one[i].mode = p->parent[i].mode;
+ hashcpy(pair->one[i].sha1, p->parent[i].sha1);
+ pair->one[i].sha1_valid = !is_null_sha1(p->parent[i].sha1);
+ pair->one[i].has_more_entries = 1;
+ }
+ pair->one[num_parent - 1].has_more_entries = 0;
+
+ pair->two->path = p->path;
+ pair->two->mode = p->mode;
+ hashcpy(pair->two->sha1, p->sha1);
+ pair->two->sha1_valid = !is_null_sha1(p->sha1);
+ return pair;
+}
+
+static void handle_combined_callback(struct diff_options *opt,
+ struct combine_diff_path *paths,
+ int num_parent,
+ int num_paths)
+{
+ struct combine_diff_path *p;
+ struct diff_queue_struct q;
+ int i;
+
+ q.queue = xcalloc(num_paths, sizeof(struct diff_filepair *));
+ q.alloc = num_paths;
+ q.nr = num_paths;
+ for (i = 0, p = paths; p; p = p->next) {
+ if (!p->len)
+ continue;
+ q.queue[i++] = combined_pair(p, num_parent);
+ }
+ opt->format_callback(&q, opt, opt->format_callback_data);
+ for (i = 0; i < num_paths; i++)
+ free_combined_pair(q.queue[i]);
+ free(q.queue);
+}
+
void diff_tree_combined(const unsigned char *sha1,
- const unsigned char parent[][20],
- int num_parent,
+ const struct sha1_array *parents,
int dense,
struct rev_info *rev)
{
struct diff_options *opt = &rev->diffopt;
struct diff_options diffopts;
struct combine_diff_path *p, *paths = NULL;
- int i, num_paths, needsep, show_log_first;
+ int i, num_paths, needsep, show_log_first, num_parent = parents->nr;
diffopts = *opt;
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
@@ -1079,7 +1144,7 @@ void diff_tree_combined(const unsigned char *sha1,
diffopts.output_format = stat_opt;
else
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diff_tree_sha1(parent[i], sha1, "", &diffopts);
+ diff_tree_sha1(parents->sha1[i], sha1, "", &diffopts);
diffcore_std(&diffopts);
paths = intersect_paths(paths, i, num_parent);
@@ -1109,6 +1174,9 @@ void diff_tree_combined(const unsigned char *sha1,
else if (opt->output_format &
(DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
needsep = 1;
+ else if (opt->output_format & DIFF_FORMAT_CALLBACK)
+ handle_combined_callback(opt, paths, num_parent, num_paths);
+
if (opt->output_format & DIFF_FORMAT_PATCH) {
if (needsep)
putchar(opt->line_termination);
@@ -1128,25 +1196,16 @@ void diff_tree_combined(const unsigned char *sha1,
}
}
-void diff_tree_combined_merge(const unsigned char *sha1,
- int dense, struct rev_info *rev)
+void diff_tree_combined_merge(const struct commit *commit, int dense,
+ struct rev_info *rev)
{
- int num_parent;
- const unsigned char (*parent)[20];
- struct commit *commit = lookup_commit(sha1);
- struct commit_list *parents;
-
- /* count parents */
- for (parents = commit->parents, num_parent = 0;
- parents;
- parents = parents->next, num_parent++)
- ; /* nothing */
-
- parent = xmalloc(num_parent * sizeof(*parent));
- for (parents = commit->parents, num_parent = 0;
- parents;
- parents = parents->next, num_parent++)
- hashcpy((unsigned char *)(parent + num_parent),
- parents->item->object.sha1);
- diff_tree_combined(sha1, parent, num_parent, dense, rev);
+ struct commit_list *parent = commit->parents;
+ struct sha1_array parents = SHA1_ARRAY_INIT;
+
+ while (parent) {
+ sha1_array_append(&parents, parent->item->object.sha1);
+ parent = parent->next;
+ }
+ diff_tree_combined(commit->object.sha1, &parents, dense, rev);
+ sha1_array_clear(&parents);
}
diff --git a/command-list.txt b/command-list.txt
index 95bf18c..14ea67a 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -20,6 +20,7 @@ git-cherry-pick mainporcelain
git-citool mainporcelain
git-clean mainporcelain
git-clone mainporcelain common
+git-column purehelpers
git-commit mainporcelain common
git-commit-tree plumbingmanipulators
git-config ancillarymanipulators
@@ -76,6 +77,7 @@ git-mktree plumbingmanipulators
git-mv mainporcelain common
git-name-rev plumbinginterrogators
git-notes mainporcelain
+git-p4 foreignscminterface
git-pack-objects plumbingmanipulators
git-pack-redundant plumbinginterrogators
git-pack-refs ancillarymanipulators
@@ -130,5 +132,6 @@ git-upload-pack synchelpers
git-var plumbinginterrogators
git-verify-pack plumbinginterrogators
git-verify-tag ancillaryinterrogators
+gitweb ancillaryinterrogators
git-whatchanged ancillaryinterrogators
git-write-tree plumbingmanipulators
diff --git a/commit.c b/commit.c
index 913dbab..8248a99 100644
--- a/commit.c
+++ b/commit.c
@@ -6,6 +6,8 @@
#include "diff.h"
#include "revision.h"
#include "notes.h"
+#include "gpg-interface.h"
+#include "mergesort.h"
int save_commit_buffer = 1;
@@ -39,6 +41,18 @@ struct commit *lookup_commit_reference(const unsigned char *sha1)
return lookup_commit_reference_gently(sha1, 0);
}
+struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name)
+{
+ struct commit *c = lookup_commit_reference(sha1);
+ if (!c)
+ die(_("could not parse %s"), ref_name);
+ if (hashcmp(sha1, c->object.sha1)) {
+ warning(_("%s %s is not a commit!"),
+ ref_name, sha1_to_hex(sha1));
+ }
+ return c;
+}
+
struct commit *lookup_commit(const unsigned char *sha1)
{
struct object *obj = lookup_object(sha1);
@@ -214,22 +228,12 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
return commit_graft[pos];
}
-int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
+int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data)
{
- int i, count = 0;
- for (i = 0; i < commit_graft_nr; i++)
- if (commit_graft[i]->nr_parent < 0) {
- const char *hex =
- sha1_to_hex(commit_graft[i]->sha1);
- count++;
- if (use_pack_protocol)
- packet_buf_write(out, "shallow %s", hex);
- else {
- strbuf_addstr(out, hex);
- strbuf_addch(out, '\n');
- }
- }
- return count;
+ int i, ret;
+ for (i = ret = 0; i < commit_graft_nr && !ret; i++)
+ ret = fn(commit_graft[i], cb_data);
+ return ret;
}
int unregister_shallow(const unsigned char *sha1)
@@ -387,15 +391,31 @@ struct commit_list * commit_list_insert_by_date(struct commit *item, struct comm
return commit_list_insert(item, pp);
}
+static int commit_list_compare_by_date(const void *a, const void *b)
+{
+ unsigned long a_date = ((const struct commit_list *)a)->item->date;
+ unsigned long b_date = ((const struct commit_list *)b)->item->date;
+ if (a_date < b_date)
+ return 1;
+ if (a_date > b_date)
+ return -1;
+ return 0;
+}
+
+static void *commit_list_get_next(const void *a)
+{
+ return ((const struct commit_list *)a)->next;
+}
+
+static void commit_list_set_next(void *a, void *next)
+{
+ ((struct commit_list *)a)->next = next;
+}
void commit_list_sort_by_date(struct commit_list **list)
{
- struct commit_list *ret = NULL;
- while (*list) {
- commit_list_insert_by_date((*list)->item, &ret);
- *list = (*list)->next;
- }
- *list = ret;
+ *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next,
+ commit_list_compare_by_date);
}
struct commit *pop_most_recent_commit(struct commit_list **list,
@@ -419,7 +439,8 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
return ret;
}
-void clear_commit_marks(struct commit *commit, unsigned int mark)
+static void clear_commit_marks_1(struct commit_list **plist,
+ struct commit *commit, unsigned int mark)
{
while (commit) {
struct commit_list *parents;
@@ -434,12 +455,34 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
return;
while ((parents = parents->next))
- clear_commit_marks(parents->item, mark);
+ commit_list_insert(parents->item, plist);
commit = commit->parents->item;
}
}
+void clear_commit_marks(struct commit *commit, unsigned int mark)
+{
+ struct commit_list *list = NULL;
+ commit_list_insert(commit, &list);
+ while (list)
+ clear_commit_marks_1(&list, pop_commit(&list), mark);
+}
+
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
+{
+ struct object *object;
+ struct commit *commit;
+ unsigned int i;
+
+ for (i = 0; i < a->nr; i++) {
+ object = a->objects[i].item;
+ commit = lookup_commit_reference_gently(object->sha1, 1);
+ if (commit)
+ clear_commit_marks(commit, mark);
+ }
+}
+
struct commit *pop_commit(struct commit_list **stack)
{
struct commit_list *top = *stack;
@@ -824,14 +867,260 @@ struct commit_list *reduce_heads(struct commit_list *heads)
return result;
}
+static const char gpg_sig_header[] = "gpgsig";
+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+
+static int do_sign_commit(struct strbuf *buf, const char *keyid)
+{
+ struct strbuf sig = STRBUF_INIT;
+ int inspos, copypos;
+
+ /* find the end of the header */
+ inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+
+ if (!keyid || !*keyid)
+ keyid = get_signing_key();
+ if (sign_buffer(buf, &sig, keyid)) {
+ strbuf_release(&sig);
+ return -1;
+ }
+
+ for (copypos = 0; sig.buf[copypos]; ) {
+ const char *bol = sig.buf + copypos;
+ const char *eol = strchrnul(bol, '\n');
+ int len = (eol - bol) + !!*eol;
+
+ if (!copypos) {
+ strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
+ inspos += gpg_sig_header_len;
+ }
+ strbuf_insert(buf, inspos++, " ", 1);
+ strbuf_insert(buf, inspos, bol, len);
+ inspos += len;
+ copypos += len;
+ }
+ strbuf_release(&sig);
+ return 0;
+}
+
+int parse_signed_commit(const unsigned char *sha1,
+ struct strbuf *payload, struct strbuf *signature)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buffer = read_sha1_file(sha1, &type, &size);
+ int in_signature, saw_signature = -1;
+ char *line, *tail;
+
+ if (!buffer || type != OBJ_COMMIT)
+ goto cleanup;
+
+ line = buffer;
+ tail = buffer + size;
+ in_signature = 0;
+ saw_signature = 0;
+ while (line < tail) {
+ const char *sig = NULL;
+ char *next = memchr(line, '\n', tail - line);
+
+ next = next ? next + 1 : tail;
+ if (in_signature && line[0] == ' ')
+ sig = line + 1;
+ else if (!prefixcmp(line, gpg_sig_header) &&
+ line[gpg_sig_header_len] == ' ')
+ sig = line + gpg_sig_header_len + 1;
+ if (sig) {
+ strbuf_add(signature, sig, next - sig);
+ saw_signature = 1;
+ in_signature = 1;
+ } else {
+ if (*line == '\n')
+ /* dump the whole remainder of the buffer */
+ next = tail;
+ strbuf_add(payload, line, next - line);
+ in_signature = 0;
+ }
+ line = next;
+ }
+ cleanup:
+ free(buffer);
+ return saw_signature;
+}
+
+static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
+{
+ struct merge_remote_desc *desc;
+ struct commit_extra_header *mergetag;
+ char *buf;
+ unsigned long size, len;
+ enum object_type type;
+
+ desc = merge_remote_util(parent);
+ if (!desc || !desc->obj)
+ return;
+ buf = read_sha1_file(desc->obj->sha1, &type, &size);
+ if (!buf || type != OBJ_TAG)
+ goto free_return;
+ len = parse_signature(buf, size);
+ if (size == len)
+ goto free_return;
+ /*
+ * We could verify this signature and either omit the tag when
+ * it does not validate, but the integrator may not have the
+ * public key of the signer of the tag he is merging, while a
+ * later auditor may have it while auditing, so let's not run
+ * verify-signed-buffer here for now...
+ *
+ * if (verify_signed_buffer(buf, len, buf + len, size - len, ...))
+ * warn("warning: signed tag unverified.");
+ */
+ mergetag = xcalloc(1, sizeof(*mergetag));
+ mergetag->key = xstrdup("mergetag");
+ mergetag->value = buf;
+ mergetag->len = size;
+
+ **tail = mergetag;
+ *tail = &mergetag->next;
+ return;
+
+free_return:
+ free(buf);
+}
+
+void append_merge_tag_headers(struct commit_list *parents,
+ struct commit_extra_header ***tail)
+{
+ while (parents) {
+ struct commit *parent = parents->item;
+ handle_signed_tag(parent, tail);
+ parents = parents->next;
+ }
+}
+
+static void add_extra_header(struct strbuf *buffer,
+ struct commit_extra_header *extra)
+{
+ strbuf_addstr(buffer, extra->key);
+ if (extra->len)
+ strbuf_add_lines(buffer, " ", extra->value, extra->len);
+ else
+ strbuf_addch(buffer, '\n');
+}
+
+struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
+ const char **exclude)
+{
+ struct commit_extra_header *extra = NULL;
+ unsigned long size;
+ enum object_type type;
+ char *buffer = read_sha1_file(commit->object.sha1, &type, &size);
+ if (buffer && type == OBJ_COMMIT)
+ extra = read_commit_extra_header_lines(buffer, size, exclude);
+ free(buffer);
+ return extra;
+}
+
+static inline int standard_header_field(const char *field, size_t len)
+{
+ return ((len == 4 && !memcmp(field, "tree ", 5)) ||
+ (len == 6 && !memcmp(field, "parent ", 7)) ||
+ (len == 6 && !memcmp(field, "author ", 7)) ||
+ (len == 9 && !memcmp(field, "committer ", 10)) ||
+ (len == 8 && !memcmp(field, "encoding ", 9)));
+}
+
+static int excluded_header_field(const char *field, size_t len, const char **exclude)
+{
+ if (!exclude)
+ return 0;
+
+ while (*exclude) {
+ size_t xlen = strlen(*exclude);
+ if (len == xlen &&
+ !memcmp(field, *exclude, xlen) && field[xlen] == ' ')
+ return 1;
+ exclude++;
+ }
+ return 0;
+}
+
+struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size,
+ const char **exclude)
+{
+ struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL;
+ const char *line, *next, *eof, *eob;
+ struct strbuf buf = STRBUF_INIT;
+
+ for (line = buffer, eob = line + size;
+ line < eob && *line != '\n';
+ line = next) {
+ next = memchr(line, '\n', eob - line);
+ next = next ? next + 1 : eob;
+ if (*line == ' ') {
+ /* continuation */
+ if (it)
+ strbuf_add(&buf, line + 1, next - (line + 1));
+ continue;
+ }
+ if (it)
+ it->value = strbuf_detach(&buf, &it->len);
+ strbuf_reset(&buf);
+ it = NULL;
+
+ eof = strchr(line, ' ');
+ if (next <= eof)
+ eof = next;
+
+ if (standard_header_field(line, eof - line) ||
+ excluded_header_field(line, eof - line, exclude))
+ continue;
+
+ it = xcalloc(1, sizeof(*it));
+ it->key = xmemdupz(line, eof-line);
+ *tail = it;
+ tail = &it->next;
+ if (eof + 1 < next)
+ strbuf_add(&buf, eof + 1, next - (eof + 1));
+ }
+ if (it)
+ it->value = strbuf_detach(&buf, &it->len);
+ return extra;
+}
+
+void free_commit_extra_headers(struct commit_extra_header *extra)
+{
+ while (extra) {
+ struct commit_extra_header *next = extra->next;
+ free(extra->key);
+ free(extra->value);
+ free(extra);
+ extra = next;
+ }
+}
+
+int commit_tree(const struct strbuf *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author, const char *sign_commit)
+{
+ struct commit_extra_header *extra = NULL, **tail = &extra;
+ int result;
+
+ append_merge_tag_headers(parents, &tail);
+ result = commit_tree_extended(msg, tree, parents, ret,
+ author, sign_commit, extra);
+ free_commit_extra_headers(extra);
+ return result;
+}
+
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";
-int commit_tree(const char *msg, unsigned char *tree,
- struct commit_list *parents, unsigned char *ret,
- const char *author)
+int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author, const char *sign_commit,
+ struct commit_extra_header *extra)
{
int result;
int encoding_is_utf8;
@@ -839,6 +1128,9 @@ int commit_tree(const char *msg, unsigned char *tree,
assert_sha1_type(tree, OBJ_TREE);
+ if (memchr(msg->buf, '\0', msg->len))
+ return error("a NUL byte in commit log message not allowed.");
+
/* Not having i18n.commitencoding is the same as having utf-8 */
encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
@@ -852,29 +1144,85 @@ int commit_tree(const char *msg, unsigned char *tree,
*/
while (parents) {
struct commit_list *next = parents->next;
+ struct commit *parent = parents->item;
+
strbuf_addf(&buffer, "parent %s\n",
- sha1_to_hex(parents->item->object.sha1));
+ sha1_to_hex(parent->object.sha1));
free(parents);
parents = next;
}
/* Person/date information */
if (!author)
- author = git_author_info(IDENT_ERROR_ON_NO_NAME);
+ author = git_author_info(IDENT_STRICT);
strbuf_addf(&buffer, "author %s\n", author);
- strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
+ strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_STRICT));
if (!encoding_is_utf8)
strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
+
+ while (extra) {
+ add_extra_header(&buffer, extra);
+ extra = extra->next;
+ }
strbuf_addch(&buffer, '\n');
/* And add the comment */
- strbuf_addstr(&buffer, msg);
+ strbuf_addbuf(&buffer, msg);
/* And check the encoding */
if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn);
+ if (sign_commit && do_sign_commit(&buffer, sign_commit))
+ return -1;
+
result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
strbuf_release(&buffer);
return result;
}
+
+struct commit *get_merge_parent(const char *name)
+{
+ struct object *obj;
+ struct commit *commit;
+ unsigned char sha1[20];
+ if (get_sha1(name, sha1))
+ return NULL;
+ obj = parse_object(sha1);
+ commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
+ if (commit && !commit->util) {
+ struct merge_remote_desc *desc;
+ desc = xmalloc(sizeof(*desc));
+ desc->obj = obj;
+ desc->name = strdup(name);
+ commit->util = desc;
+ }
+ return commit;
+}
+
+/*
+ * Append a commit to the end of the commit_list.
+ *
+ * next starts by pointing to the variable that holds the head of an
+ * empty commit_list, and is updated to point to the "next" field of
+ * the last item on the list as new commits are appended.
+ *
+ * Usage example:
+ *
+ * struct commit_list *list;
+ * struct commit_list **next = &list;
+ *
+ * next = commit_list_append(c1, next);
+ * next = commit_list_append(c2, next);
+ * assert(commit_list_count(list) == 2);
+ * return list;
+ */
+struct commit_list **commit_list_append(struct commit *commit,
+ struct commit_list **next)
+{
+ struct commit_list *new = xmalloc(sizeof(struct commit_list));
+ new->item = commit;
+ *next = new;
+ new->next = NULL;
+ return &new->next;
+}
diff --git a/commit.h b/commit.h
index a2d571b..d617fa3 100644
--- a/commit.h
+++ b/commit.h
@@ -38,6 +38,13 @@ struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
int quiet);
struct commit *lookup_commit_reference_by_name(const char *name);
+/*
+ * Look up object named by "sha1", dereference tag as necessary,
+ * get a commit and return it. If "sha1" does not dereference to
+ * a commit, use ref_name to report an error and die.
+ */
+struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name);
+
int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
int parse_commit(struct commit *item);
@@ -46,6 +53,8 @@ int find_commit_subject(const char *commit_buffer, const char **subject);
struct commit_list *commit_list_insert(struct commit *item,
struct commit_list **list);
+struct commit_list **commit_list_append(struct commit *commit,
+ struct commit_list **next);
unsigned commit_list_count(const struct commit_list *l);
struct commit_list *commit_list_insert_by_date(struct commit *item,
struct commit_list **list);
@@ -75,6 +84,7 @@ struct pretty_print_context {
const char *after_subject;
int preserve_subject;
enum date_mode date_mode;
+ unsigned date_mode_explicit:1;
int need_8bit_cte;
int show_notes;
struct reflog_walk_info *reflog_info;
@@ -126,6 +136,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
struct commit *pop_commit(struct commit_list **stack);
void clear_commit_marks(struct commit *commit, unsigned int mark);
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
/*
* Performs an in-place topological sort of list supplied.
@@ -142,6 +153,7 @@ struct commit_graft {
int nr_parent; /* < 0 if shallow commit */
unsigned char parent[FLEX_ARRAY][20]; /* more */
};
+typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
struct commit_graft *read_graft_line(char *buf, int len);
int register_commit_graft(struct commit_graft *, int);
@@ -153,7 +165,7 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
extern int register_shallow(const unsigned char *sha1);
extern int unregister_shallow(const unsigned char *sha1);
-extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol);
+extern int for_each_commit_graft(each_commit_graft_fn, void *);
extern int is_repository_shallow(void);
extern struct commit_list *get_shallow_commits(struct object_array *heads,
int depth, int shallow_flag, int not_shallow_flag);
@@ -172,8 +184,43 @@ static inline int single_parent(struct commit *commit)
struct commit_list *reduce_heads(struct commit_list *heads);
-extern int commit_tree(const char *msg, unsigned char *tree,
- struct commit_list *parents, unsigned char *ret,
- const char *author);
+struct commit_extra_header {
+ struct commit_extra_header *next;
+ char *key;
+ char *value;
+ size_t len;
+};
+
+extern void append_merge_tag_headers(struct commit_list *parents,
+ struct commit_extra_header ***tail);
+
+extern int commit_tree(const struct strbuf *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author, const char *sign_commit);
+
+extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author, const char *sign_commit,
+ struct commit_extra_header *);
+
+extern struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);
+extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
+
+extern void free_commit_extra_headers(struct commit_extra_header *extra);
+
+struct merge_remote_desc {
+ struct object *obj; /* the named object, could be a tag */
+ const char *name;
+};
+#define merge_remote_util(commit) ((struct merge_remote_desc *)((commit)->util))
+
+/*
+ * Given "name" from the command line to merge, find the commit object
+ * and return it, while storing merge_remote_desc in its ->util field,
+ * to allow callers to tell if we are told to merge a tag.
+ */
+struct commit *get_merge_parent(const char *name);
+extern int parse_signed_commit(const unsigned char *sha1,
+ struct strbuf *message, struct strbuf *signature);
#endif /* COMMIT_H */
diff --git a/compat/cygwin.c b/compat/cygwin.c
index ba3327f..dfe9b30 100644
--- a/compat/cygwin.c
+++ b/compat/cygwin.c
@@ -114,8 +114,7 @@ static int git_cygwin_config(const char *var, const char *value, void *cb)
static int init_stat(void)
{
- if (have_git_dir()) {
- git_config(git_cygwin_config, NULL);
+ if (have_git_dir() && git_config(git_cygwin_config,NULL)) {
if (!core_filemode && native_stat) {
cygwin_stat_fn = cygwin_stat;
cygwin_lstat_fn = cygwin_lstat;
diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c
index ea249c6..90b7cc4 100644
--- a/compat/inet_ntop.c
+++ b/compat/inet_ntop.c
@@ -15,14 +15,8 @@
* SOFTWARE.
*/
-#include <errno.h>
-#include <sys/types.h>
-
#include "../git-compat-util.h"
-#include <stdio.h>
-#include <string.h>
-
#ifndef NS_INADDRSZ
#define NS_INADDRSZ 4
#endif
@@ -98,7 +92,9 @@ inet_ntop6(const u_char *src, char *dst, size_t size)
for (i = 0; i < NS_IN6ADDRSZ; i++)
words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
best.base = -1;
+ best.len = 0;
cur.base = -1;
+ cur.len = 0;
for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
if (words[i] == 0) {
if (cur.base == -1)
diff --git a/compat/inet_pton.c b/compat/inet_pton.c
index 2ec995e..2b9a0a4 100644
--- a/compat/inet_pton.c
+++ b/compat/inet_pton.c
@@ -15,14 +15,8 @@
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <errno.h>
-#include <sys/types.h>
-
#include "../git-compat-util.h"
-#include <stdio.h>
-#include <string.h>
-
#ifndef NS_INT16SZ
#define NS_INT16SZ 2
#endif
diff --git a/compat/mingw.c b/compat/mingw.c
index f6e9ff7..afc892d 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -178,7 +178,7 @@ static int ask_yes_no_if_possible(const char *format, ...)
vsnprintf(question, sizeof(question), format, args);
va_end(args);
- if ((retry_hook[0] = getenv("GIT_ASK_YESNO"))) {
+ if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
retry_hook[1] = question;
return !run_command_v_opt(retry_hook, 0);
}
@@ -599,19 +599,6 @@ char *mingw_getcwd(char *pointer, int len)
return ret;
}
-#undef getenv
-char *mingw_getenv(const char *name)
-{
- char *result = getenv(name);
- if (!result && !strcmp(name, "TMPDIR")) {
- /* on Windows it is TMP and TEMP */
- result = getenv("TMP");
- if (!result)
- result = getenv("TEMP");
- }
- return result;
-}
-
/*
* See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
* (Parsing C++ Command-Line Arguments)
@@ -711,7 +698,7 @@ static const char *parse_interpreter(const char *cmd)
*/
static char **get_path_split(void)
{
- char *p, **path, *envpath = getenv("PATH");
+ char *p, **path, *envpath = mingw_getenv("PATH");
int i, n = 0;
if (!envpath || !*envpath)
@@ -1016,7 +1003,7 @@ static void mingw_execve(const char *cmd, char *const *argv, char *const *env)
}
}
-void mingw_execvp(const char *cmd, char *const *argv)
+int mingw_execvp(const char *cmd, char *const *argv)
{
char **path = get_path_split();
char *prog = path_lookup(cmd, path, 0);
@@ -1028,11 +1015,13 @@ void mingw_execvp(const char *cmd, char *const *argv)
errno = ENOENT;
free_path_split(path);
+ return -1;
}
-void mingw_execv(const char *cmd, char *const *argv)
+int mingw_execv(const char *cmd, char *const *argv)
{
mingw_execve(cmd, argv, environ);
+ return -1;
}
int mingw_kill(pid_t pid, int sig)
@@ -1128,6 +1117,36 @@ char **make_augmented_environ(const char *const *vars)
return env;
}
+#undef getenv
+
+/*
+ * The system's getenv looks up the name in a case-insensitive manner.
+ * This version tries a case-sensitive lookup and falls back to
+ * case-insensitive if nothing was found. This is necessary because,
+ * as a prominent example, CMD sets 'Path', but not 'PATH'.
+ * Warning: not thread-safe.
+ */
+static char *getenv_cs(const char *name)
+{
+ size_t len = strlen(name);
+ int i = lookup_env(environ, name, len);
+ if (i >= 0)
+ return environ[i] + len + 1; /* skip past name and '=' */
+ return getenv(name);
+}
+
+char *mingw_getenv(const char *name)
+{
+ char *result = getenv_cs(name);
+ if (!result && !strcmp(name, "TMPDIR")) {
+ /* on Windows it is TMP and TEMP */
+ result = getenv_cs("TMP");
+ if (!result)
+ result = getenv_cs("TEMP");
+ }
+ return result;
+}
+
/*
* Note, this isn't a complete replacement for getaddrinfo. It assumes
* that service contains a numerical port, or that it is null. It
@@ -1166,7 +1185,7 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
}
ai->ai_addrlen = sizeof(struct sockaddr_in);
if (hints && (hints->ai_flags & AI_CANONNAME))
- ai->ai_canonname = h ? strdup(h->h_name) : NULL;
+ ai->ai_canonname = h ? xstrdup(h->h_name) : NULL;
else
ai->ai_canonname = NULL;
@@ -1304,6 +1323,13 @@ static void ensure_socket_initialization(void)
initialized = 1;
}
+#undef gethostname
+int mingw_gethostname(char *name, int namelen)
+{
+ ensure_socket_initialization();
+ return gethostname(name, namelen);
+}
+
#undef gethostbyname
struct hostent *mingw_gethostbyname(const char *host)
{
@@ -1688,7 +1714,7 @@ char *getpass(const char *prompt)
return strbuf_detach(&buf, NULL);
}
-pid_t waitpid(pid_t pid, int *status, unsigned options)
+pid_t waitpid(pid_t pid, int *status, int options)
{
HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
FALSE, pid);
diff --git a/compat/mingw.h b/compat/mingw.h
index 547568b..61a6521 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -22,9 +22,10 @@ typedef int socklen_t;
#define S_IWOTH 0
#define S_IXOTH 0
#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
-#define S_ISUID 0
-#define S_ISGID 0
-#define S_ISVTX 0
+
+#define S_ISUID 0004000
+#define S_ISGID 0002000
+#define S_ISVTX 0001000
#define WIFEXITED(x) 1
#define WIFSIGNALED(x) 0
@@ -120,7 +121,7 @@ static inline int mingw_mkdir(const char *path, int mode)
#define mkdir mingw_mkdir
#define WNOHANG 1
-pid_t waitpid(pid_t pid, int *status, unsigned options);
+pid_t waitpid(pid_t pid, int *status, int options);
#define kill mingw_kill
int mingw_kill(pid_t pid, int sig);
@@ -190,6 +191,9 @@ char *mingw_getcwd(char *pointer, int len);
char *mingw_getenv(const char *name);
#define getenv mingw_getenv
+int mingw_gethostname(char *host, int namelen);
+#define gethostname mingw_gethostname
+
struct hostent *mingw_gethostbyname(const char *host);
#define gethostbyname mingw_gethostbyname
@@ -271,9 +275,9 @@ int mingw_utime(const char *file_name, const struct utimbuf *times);
pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
const char *dir,
int fhin, int fhout, int fherr);
-void mingw_execvp(const char *cmd, char *const *argv);
+int mingw_execvp(const char *cmd, char *const *argv);
#define execvp mingw_execvp
-void mingw_execv(const char *cmd, char *const *argv);
+int mingw_execv(const char *cmd, char *const *argv);
#define execv mingw_execv
static inline unsigned int git_ntohl(unsigned int x)
@@ -300,6 +304,15 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format
#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
#define is_dir_sep(c) ((c) == '/' || (c) == '\\')
+static inline char *mingw_find_last_dir_sep(const char *path)
+{
+ char *ret = NULL;
+ for (; *path; ++path)
+ if (is_dir_sep(*path))
+ ret = (char *)path;
+ return ret;
+}
+#define find_last_dir_sep mingw_find_last_dir_sep
#define PATH_SEP ';'
#define PRIuMAX "I64u"
diff --git a/compat/msvc.h b/compat/msvc.h
index a33b01c..aa4b563 100644
--- a/compat/msvc.h
+++ b/compat/msvc.h
@@ -4,6 +4,7 @@
#include <direct.h>
#include <process.h>
#include <malloc.h>
+#include <io.h>
/* porting function */
#define inline __inline
diff --git a/compat/nedmalloc/Readme.txt b/compat/nedmalloc/Readme.txt
index 8763656..e46d8f1 100644
--- a/compat/nedmalloc/Readme.txt
+++ b/compat/nedmalloc/Readme.txt
@@ -100,7 +100,7 @@ v1.04alpha_svn915 7th October 2006:
Thanks to Dmitry Chichkov for reporting this. Futher thanks to Aleksey Sanin.
* Fixed realloc(0, <size>) segfaulting. Thanks to Dmitry Chichkov for
reporting this.
- * Made config defines #ifndef so they can be overriden by the build system.
+ * Made config defines #ifndef so they can be overridden by the build system.
Thanks to Aleksey Sanin for suggesting this.
* Fixed deadlock in nedprealloc() due to unnecessary locking of preferred
thread mspace when mspace_realloc() always uses the original block's mspace
diff --git a/compat/obstack.c b/compat/obstack.c
new file mode 100644
index 0000000..e276ccd
--- /dev/null
+++ b/compat/obstack.c
@@ -0,0 +1,413 @@
+/* obstack.c - subroutines used implicitly by object stack macros
+ Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998,
+ 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA. */
+
+#include "git-compat-util.h"
+#include <gettext.h>
+#include "obstack.h"
+
+/* NOTE BEFORE MODIFYING THIS FILE: This version number must be
+ incremented whenever callers compiled using an old obstack.h can no
+ longer properly call the functions in this obstack.c. */
+#define OBSTACK_INTERFACE_VERSION 1
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself, and the installed library
+ supports the same library interface we do. This code is part of the GNU
+ C Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object
+ files, it is simpler to just do this in the source for each such file. */
+
+#include <stdio.h> /* Random thing to get __GNU_LIBRARY__. */
+#if !defined _LIBC && defined __GNU_LIBRARY__ && __GNU_LIBRARY__ > 1
+# include <gnu-versions.h>
+# if _GNU_OBSTACK_INTERFACE_VERSION == OBSTACK_INTERFACE_VERSION
+# define ELIDE_CODE
+# endif
+#endif
+
+#include <stddef.h>
+
+#ifndef ELIDE_CODE
+
+
+# if HAVE_INTTYPES_H
+# include <inttypes.h>
+# endif
+# if HAVE_STDINT_H || defined _LIBC
+# include <stdint.h>
+# endif
+
+/* Determine default alignment. */
+union fooround
+{
+ uintmax_t i;
+ long double d;
+ void *p;
+};
+struct fooalign
+{
+ char c;
+ union fooround u;
+};
+/* If malloc were really smart, it would round addresses to DEFAULT_ALIGNMENT.
+ But in fact it might be less smart and round addresses to as much as
+ DEFAULT_ROUNDING. So we prepare for it to do that. */
+enum
+ {
+ DEFAULT_ALIGNMENT = offsetof (struct fooalign, u),
+ DEFAULT_ROUNDING = sizeof (union fooround)
+ };
+
+/* When we copy a long block of data, this is the unit to do it with.
+ On some machines, copying successive ints does not work;
+ in such a case, redefine COPYING_UNIT to `long' (if that works)
+ or `char' as a last resort. */
+# ifndef COPYING_UNIT
+# define COPYING_UNIT int
+# endif
+
+
+/* The functions allocating more room by calling `obstack_chunk_alloc'
+ jump to the handler pointed to by `obstack_alloc_failed_handler'.
+ This can be set to a user defined function which should either
+ abort gracefully or use longjump - but shouldn't return. This
+ variable by default points to the internal function
+ `print_and_abort'. */
+static void print_and_abort (void);
+void (*obstack_alloc_failed_handler) (void) = print_and_abort;
+
+# ifdef _LIBC
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4)
+/* A looong time ago (before 1994, anyway; we're not sure) this global variable
+ was used by non-GNU-C macros to avoid multiple evaluation. The GNU C
+ library still exports it because somebody might use it. */
+struct obstack *_obstack_compat;
+compat_symbol (libc, _obstack_compat, _obstack, GLIBC_2_0);
+# endif
+# endif
+
+/* Define a macro that either calls functions with the traditional malloc/free
+ calling interface, or calls functions with the mmalloc/mfree interface
+ (that adds an extra first argument), based on the state of use_extra_arg.
+ For free, do not use ?:, since some compilers, like the MIPS compilers,
+ do not allow (expr) ? void : void. */
+
+# define CALL_CHUNKFUN(h, size) \
+ (((h) -> use_extra_arg) \
+ ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \
+ : (*(struct _obstack_chunk *(*) (long)) (h)->chunkfun) ((size)))
+
+# define CALL_FREEFUN(h, old_chunk) \
+ do { \
+ if ((h) -> use_extra_arg) \
+ (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \
+ else \
+ (*(void (*) (void *)) (h)->freefun) ((old_chunk)); \
+ } while (0)
+
+
+/* Initialize an obstack H for use. Specify chunk size SIZE (0 means default).
+ Objects start on multiples of ALIGNMENT (0 means use default).
+ CHUNKFUN is the function to use to allocate chunks,
+ and FREEFUN the function to free them.
+
+ Return nonzero if successful, calls obstack_alloc_failed_handler if
+ allocation fails. */
+
+int
+_obstack_begin (struct obstack *h,
+ int size, int alignment,
+ void *(*chunkfun) (long),
+ void (*freefun) (void *))
+{
+ register struct _obstack_chunk *chunk; /* points to new chunk */
+
+ if (alignment == 0)
+ alignment = DEFAULT_ALIGNMENT;
+ if (size == 0)
+ /* Default size is what GNU malloc can fit in a 4096-byte block. */
+ {
+ /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc.
+ Use the values for range checking, because if range checking is off,
+ the extra bytes won't be missed terribly, but if range checking is on
+ and we used a larger request, a whole extra 4096 bytes would be
+ allocated.
+
+ These number are irrelevant to the new GNU malloc. I suspect it is
+ less sensitive to the size of the request. */
+ int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1))
+ + 4 + DEFAULT_ROUNDING - 1)
+ & ~(DEFAULT_ROUNDING - 1));
+ size = 4096 - extra;
+ }
+
+ h->chunkfun = (struct _obstack_chunk * (*)(void *, long)) chunkfun;
+ h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun;
+ h->chunk_size = size;
+ h->alignment_mask = alignment - 1;
+ h->use_extra_arg = 0;
+
+ chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size);
+ if (!chunk)
+ (*obstack_alloc_failed_handler) ();
+ h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents,
+ alignment - 1);
+ h->chunk_limit = chunk->limit
+ = (char *) chunk + h->chunk_size;
+ chunk->prev = NULL;
+ /* The initial chunk now contains no empty object. */
+ h->maybe_empty_object = 0;
+ h->alloc_failed = 0;
+ return 1;
+}
+
+int
+_obstack_begin_1 (struct obstack *h, int size, int alignment,
+ void *(*chunkfun) (void *, long),
+ void (*freefun) (void *, void *),
+ void *arg)
+{
+ register struct _obstack_chunk *chunk; /* points to new chunk */
+
+ if (alignment == 0)
+ alignment = DEFAULT_ALIGNMENT;
+ if (size == 0)
+ /* Default size is what GNU malloc can fit in a 4096-byte block. */
+ {
+ /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc.
+ Use the values for range checking, because if range checking is off,
+ the extra bytes won't be missed terribly, but if range checking is on
+ and we used a larger request, a whole extra 4096 bytes would be
+ allocated.
+
+ These number are irrelevant to the new GNU malloc. I suspect it is
+ less sensitive to the size of the request. */
+ int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1))
+ + 4 + DEFAULT_ROUNDING - 1)
+ & ~(DEFAULT_ROUNDING - 1));
+ size = 4096 - extra;
+ }
+
+ h->chunkfun = (struct _obstack_chunk * (*)(void *,long)) chunkfun;
+ h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun;
+ h->chunk_size = size;
+ h->alignment_mask = alignment - 1;
+ h->extra_arg = arg;
+ h->use_extra_arg = 1;
+
+ chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size);
+ if (!chunk)
+ (*obstack_alloc_failed_handler) ();
+ h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents,
+ alignment - 1);
+ h->chunk_limit = chunk->limit
+ = (char *) chunk + h->chunk_size;
+ chunk->prev = NULL;
+ /* The initial chunk now contains no empty object. */
+ h->maybe_empty_object = 0;
+ h->alloc_failed = 0;
+ return 1;
+}
+
+/* Allocate a new current chunk for the obstack *H
+ on the assumption that LENGTH bytes need to be added
+ to the current object, or a new object of length LENGTH allocated.
+ Copies any partial object from the end of the old chunk
+ to the beginning of the new one. */
+
+void
+_obstack_newchunk (struct obstack *h, int length)
+{
+ register struct _obstack_chunk *old_chunk = h->chunk;
+ register struct _obstack_chunk *new_chunk;
+ register long new_size;
+ register long obj_size = h->next_free - h->object_base;
+ register long i;
+ long already;
+ char *object_base;
+
+ /* Compute size for new chunk. */
+ new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100;
+ if (new_size < h->chunk_size)
+ new_size = h->chunk_size;
+
+ /* Allocate and initialize the new chunk. */
+ new_chunk = CALL_CHUNKFUN (h, new_size);
+ if (!new_chunk)
+ (*obstack_alloc_failed_handler) ();
+ h->chunk = new_chunk;
+ new_chunk->prev = old_chunk;
+ new_chunk->limit = h->chunk_limit = (char *) new_chunk + new_size;
+
+ /* Compute an aligned object_base in the new chunk */
+ object_base =
+ __PTR_ALIGN ((char *) new_chunk, new_chunk->contents, h->alignment_mask);
+
+ /* Move the existing object to the new chunk.
+ Word at a time is fast and is safe if the object
+ is sufficiently aligned. */
+ if (h->alignment_mask + 1 >= DEFAULT_ALIGNMENT)
+ {
+ for (i = obj_size / sizeof (COPYING_UNIT) - 1;
+ i >= 0; i--)
+ ((COPYING_UNIT *)object_base)[i]
+ = ((COPYING_UNIT *)h->object_base)[i];
+ /* We used to copy the odd few remaining bytes as one extra COPYING_UNIT,
+ but that can cross a page boundary on a machine
+ which does not do strict alignment for COPYING_UNITS. */
+ already = obj_size / sizeof (COPYING_UNIT) * sizeof (COPYING_UNIT);
+ }
+ else
+ already = 0;
+ /* Copy remaining bytes one by one. */
+ for (i = already; i < obj_size; i++)
+ object_base[i] = h->object_base[i];
+
+ /* If the object just copied was the only data in OLD_CHUNK,
+ free that chunk and remove it from the chain.
+ But not if that chunk might contain an empty object. */
+ if (! h->maybe_empty_object
+ && (h->object_base
+ == __PTR_ALIGN ((char *) old_chunk, old_chunk->contents,
+ h->alignment_mask)))
+ {
+ new_chunk->prev = old_chunk->prev;
+ CALL_FREEFUN (h, old_chunk);
+ }
+
+ h->object_base = object_base;
+ h->next_free = h->object_base + obj_size;
+ /* The new chunk certainly contains no empty object yet. */
+ h->maybe_empty_object = 0;
+}
+# ifdef _LIBC
+libc_hidden_def (_obstack_newchunk)
+# endif
+
+/* Return nonzero if object OBJ has been allocated from obstack H.
+ This is here for debugging.
+ If you use it in a program, you are probably losing. */
+
+/* Suppress -Wmissing-prototypes warning. We don't want to declare this in
+ obstack.h because it is just for debugging. */
+int _obstack_allocated_p (struct obstack *h, void *obj);
+
+int
+_obstack_allocated_p (struct obstack *h, void *obj)
+{
+ register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */
+ register struct _obstack_chunk *plp; /* point to previous chunk if any */
+
+ lp = (h)->chunk;
+ /* We use >= rather than > since the object cannot be exactly at
+ the beginning of the chunk but might be an empty object exactly
+ at the end of an adjacent chunk. */
+ while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+ {
+ plp = lp->prev;
+ lp = plp;
+ }
+ return lp != NULL;
+}
+
+/* Free objects in obstack H, including OBJ and everything allocate
+ more recently than OBJ. If OBJ is zero, free everything in H. */
+
+# undef obstack_free
+
+void
+obstack_free (struct obstack *h, void *obj)
+{
+ register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */
+ register struct _obstack_chunk *plp; /* point to previous chunk if any */
+
+ lp = h->chunk;
+ /* We use >= because there cannot be an object at the beginning of a chunk.
+ But there can be an empty object at that address
+ at the end of another chunk. */
+ while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+ {
+ plp = lp->prev;
+ CALL_FREEFUN (h, lp);
+ lp = plp;
+ /* If we switch chunks, we can't tell whether the new current
+ chunk contains an empty object, so assume that it may. */
+ h->maybe_empty_object = 1;
+ }
+ if (lp)
+ {
+ h->object_base = h->next_free = (char *) (obj);
+ h->chunk_limit = lp->limit;
+ h->chunk = lp;
+ }
+ else if (obj != NULL)
+ /* obj is not in any of the chunks! */
+ abort ();
+}
+
+# ifdef _LIBC
+/* Older versions of libc used a function _obstack_free intended to be
+ called by non-GCC compilers. */
+strong_alias (obstack_free, _obstack_free)
+# endif
+
+int
+_obstack_memory_used (struct obstack *h)
+{
+ register struct _obstack_chunk* lp;
+ register int nbytes = 0;
+
+ for (lp = h->chunk; lp != NULL; lp = lp->prev)
+ {
+ nbytes += lp->limit - (char *) lp;
+ }
+ return nbytes;
+}
+
+# ifdef _LIBC
+# include <libio/iolibio.h>
+# endif
+
+# ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
+# define __attribute__(Spec) /* empty */
+# endif
+# endif
+
+static void
+print_and_abort (void)
+{
+ /* Don't change any of these strings. Yes, it would be possible to add
+ the newline to the string and use fputs or so. But this must not
+ happen because the "memory exhausted" message appears in other places
+ like this and the translation should be reused instead of creating
+ a very similar string which requires a separate translation. */
+# ifdef _LIBC
+ (void) __fxprintf (NULL, "%s\n", _("memory exhausted"));
+# else
+ fprintf (stderr, "%s\n", _("memory exhausted"));
+# endif
+ exit (1);
+}
+
+#endif /* !ELIDE_CODE */
diff --git a/compat/obstack.h b/compat/obstack.h
new file mode 100644
index 0000000..d178bd6
--- /dev/null
+++ b/compat/obstack.h
@@ -0,0 +1,506 @@
+/* obstack.h - object stack macros
+ Copyright (C) 1988-1994,1996-1999,2003,2004,2005,2009
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA. */
+
+/* Summary:
+
+All the apparent functions defined here are macros. The idea
+is that you would use these pre-tested macros to solve a
+very specific set of problems, and they would run fast.
+Caution: no side-effects in arguments please!! They may be
+evaluated MANY times!!
+
+These macros operate a stack of objects. Each object starts life
+small, and may grow to maturity. (Consider building a word syllable
+by syllable.) An object can move while it is growing. Once it has
+been "finished" it never changes address again. So the "top of the
+stack" is typically an immature growing object, while the rest of the
+stack is of mature, fixed size and fixed address objects.
+
+These routines grab large chunks of memory, using a function you
+supply, called `obstack_chunk_alloc'. On occasion, they free chunks,
+by calling `obstack_chunk_free'. You must define them and declare
+them before using any obstack macros.
+
+Each independent stack is represented by a `struct obstack'.
+Each of the obstack macros expects a pointer to such a structure
+as the first argument.
+
+One motivation for this package is the problem of growing char strings
+in symbol tables. Unless you are "fascist pig with a read-only mind"
+--Gosper's immortal quote from HAKMEM item 154, out of context--you
+would not like to put any arbitrary upper limit on the length of your
+symbols.
+
+In practice this often means you will build many short symbols and a
+few long symbols. At the time you are reading a symbol you don't know
+how long it is. One traditional method is to read a symbol into a
+buffer, realloc()ating the buffer every time you try to read a symbol
+that is longer than the buffer. This is beaut, but you still will
+want to copy the symbol from the buffer to a more permanent
+symbol-table entry say about half the time.
+
+With obstacks, you can work differently. Use one obstack for all symbol
+names. As you read a symbol, grow the name in the obstack gradually.
+When the name is complete, finalize it. Then, if the symbol exists already,
+free the newly read name.
+
+The way we do this is to take a large chunk, allocating memory from
+low addresses. When you want to build a symbol in the chunk you just
+add chars above the current "high water mark" in the chunk. When you
+have finished adding chars, because you got to the end of the symbol,
+you know how long the chars are, and you can create a new object.
+Mostly the chars will not burst over the highest address of the chunk,
+because you would typically expect a chunk to be (say) 100 times as
+long as an average object.
+
+In case that isn't clear, when we have enough chars to make up
+the object, THEY ARE ALREADY CONTIGUOUS IN THE CHUNK (guaranteed)
+so we just point to it where it lies. No moving of chars is
+needed and this is the second win: potentially long strings need
+never be explicitly shuffled. Once an object is formed, it does not
+change its address during its lifetime.
+
+When the chars burst over a chunk boundary, we allocate a larger
+chunk, and then copy the partly formed object from the end of the old
+chunk to the beginning of the new larger chunk. We then carry on
+accreting characters to the end of the object as we normally would.
+
+A special macro is provided to add a single char at a time to a
+growing object. This allows the use of register variables, which
+break the ordinary 'growth' macro.
+
+Summary:
+ We allocate large chunks.
+ We carve out one object at a time from the current chunk.
+ Once carved, an object never moves.
+ We are free to append data of any size to the currently
+ growing object.
+ Exactly one object is growing in an obstack at any one time.
+ You can run one obstack per control block.
+ You may have as many control blocks as you dare.
+ Because of the way we do it, you can `unwind' an obstack
+ back to a previous state. (You may remove objects much
+ as you would with a stack.)
+*/
+
+
+/* Don't do the contents of this file more than once. */
+
+#ifndef _OBSTACK_H
+#define _OBSTACK_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* We need the type of a pointer subtraction. If __PTRDIFF_TYPE__ is
+ defined, as with GNU C, use that; that way we don't pollute the
+ namespace with <stddef.h>'s symbols. Otherwise, include <stddef.h>
+ and use ptrdiff_t. */
+
+#ifdef __PTRDIFF_TYPE__
+# define PTR_INT_TYPE __PTRDIFF_TYPE__
+#else
+# include <stddef.h>
+# define PTR_INT_TYPE ptrdiff_t
+#endif
+
+/* If B is the base of an object addressed by P, return the result of
+ aligning P to the next multiple of A + 1. B and P must be of type
+ char *. A + 1 must be a power of 2. */
+
+#define __BPTR_ALIGN(B, P, A) ((B) + (((P) - (B) + (A)) & ~(A)))
+
+/* Similiar to _BPTR_ALIGN (B, P, A), except optimize the common case
+ where pointers can be converted to integers, aligned as integers,
+ and converted back again. If PTR_INT_TYPE is narrower than a
+ pointer (e.g., the AS/400), play it safe and compute the alignment
+ relative to B. Otherwise, use the faster strategy of computing the
+ alignment relative to 0. */
+
+#define __PTR_ALIGN(B, P, A) \
+ __BPTR_ALIGN (sizeof (PTR_INT_TYPE) < sizeof (void *) ? (B) : (char *) 0, \
+ P, A)
+
+#include <string.h>
+
+struct _obstack_chunk /* Lives at front of each chunk. */
+{
+ char *limit; /* 1 past end of this chunk */
+ struct _obstack_chunk *prev; /* address of prior chunk or NULL */
+ char contents[4]; /* objects begin here */
+};
+
+struct obstack /* control current object in current chunk */
+{
+ long chunk_size; /* preferred size to allocate chunks in */
+ struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */
+ char *object_base; /* address of object we are building */
+ char *next_free; /* where to add next char to current object */
+ char *chunk_limit; /* address of char after current chunk */
+ union
+ {
+ PTR_INT_TYPE tempint;
+ void *tempptr;
+ } temp; /* Temporary for some macros. */
+ int alignment_mask; /* Mask of alignment for each object. */
+ /* These prototypes vary based on `use_extra_arg', and we use
+ casts to the prototypeless function type in all assignments,
+ but having prototypes here quiets -Wstrict-prototypes. */
+ struct _obstack_chunk *(*chunkfun) (void *, long);
+ void (*freefun) (void *, struct _obstack_chunk *);
+ void *extra_arg; /* first arg for chunk alloc/dealloc funcs */
+ unsigned use_extra_arg:1; /* chunk alloc/dealloc funcs take extra arg */
+ unsigned maybe_empty_object:1;/* There is a possibility that the current
+ chunk contains a zero-length object. This
+ prevents freeing the chunk if we allocate
+ a bigger chunk to replace it. */
+ unsigned alloc_failed:1; /* No longer used, as we now call the failed
+ handler on error, but retained for binary
+ compatibility. */
+};
+
+/* Declare the external functions we use; they are in obstack.c. */
+
+extern void _obstack_newchunk (struct obstack *, int);
+extern int _obstack_begin (struct obstack *, int, int,
+ void *(*) (long), void (*) (void *));
+extern int _obstack_begin_1 (struct obstack *, int, int,
+ void *(*) (void *, long),
+ void (*) (void *, void *), void *);
+extern int _obstack_memory_used (struct obstack *);
+
+void obstack_free (struct obstack *, void *);
+
+
+/* Error handler called when `obstack_chunk_alloc' failed to allocate
+ more memory. This can be set to a user defined function which
+ should either abort gracefully or use longjump - but shouldn't
+ return. The default action is to print a message and abort. */
+extern void (*obstack_alloc_failed_handler) (void);
+
+/* Pointer to beginning of object being allocated or to be allocated next.
+ Note that this might not be the final address of the object
+ because a new chunk might be needed to hold the final size. */
+
+#define obstack_base(h) ((void *) (h)->object_base)
+
+/* Size for allocating ordinary chunks. */
+
+#define obstack_chunk_size(h) ((h)->chunk_size)
+
+/* Pointer to next byte not yet allocated in current chunk. */
+
+#define obstack_next_free(h) ((h)->next_free)
+
+/* Mask specifying low bits that should be clear in address of an object. */
+
+#define obstack_alignment_mask(h) ((h)->alignment_mask)
+
+/* To prevent prototype warnings provide complete argument list. */
+#define obstack_init(h) \
+ _obstack_begin ((h), 0, 0, \
+ (void *(*) (long)) obstack_chunk_alloc, \
+ (void (*) (void *)) obstack_chunk_free)
+
+#define obstack_begin(h, size) \
+ _obstack_begin ((h), (size), 0, \
+ (void *(*) (long)) obstack_chunk_alloc, \
+ (void (*) (void *)) obstack_chunk_free)
+
+#define obstack_specify_allocation(h, size, alignment, chunkfun, freefun) \
+ _obstack_begin ((h), (size), (alignment), \
+ (void *(*) (long)) (chunkfun), \
+ (void (*) (void *)) (freefun))
+
+#define obstack_specify_allocation_with_arg(h, size, alignment, chunkfun, freefun, arg) \
+ _obstack_begin_1 ((h), (size), (alignment), \
+ (void *(*) (void *, long)) (chunkfun), \
+ (void (*) (void *, void *)) (freefun), (arg))
+
+#define obstack_chunkfun(h, newchunkfun) \
+ ((h) -> chunkfun = (struct _obstack_chunk *(*)(void *, long)) (newchunkfun))
+
+#define obstack_freefun(h, newfreefun) \
+ ((h) -> freefun = (void (*)(void *, struct _obstack_chunk *)) (newfreefun))
+
+#define obstack_1grow_fast(h,achar) (*((h)->next_free)++ = (achar))
+
+#define obstack_blank_fast(h,n) ((h)->next_free += (n))
+
+#define obstack_memory_used(h) _obstack_memory_used (h)
+
+#if defined __GNUC__ && defined __STDC__ && __STDC__
+/* NextStep 2.0 cc is really gcc 1.93 but it defines __GNUC__ = 2 and
+ does not implement __extension__. But that compiler doesn't define
+ __GNUC_MINOR__. */
+# if __GNUC__ < 2 || (__NeXT__ && !__GNUC_MINOR__)
+# define __extension__
+# endif
+
+/* For GNU C, if not -traditional,
+ we can define these macros to compute all args only once
+ without using a global variable.
+ Also, we can avoid using the `temp' slot, to make faster code. */
+
+# define obstack_object_size(OBSTACK) \
+ __extension__ \
+ ({ struct obstack const *__o = (OBSTACK); \
+ (unsigned) (__o->next_free - __o->object_base); })
+
+# define obstack_room(OBSTACK) \
+ __extension__ \
+ ({ struct obstack const *__o = (OBSTACK); \
+ (unsigned) (__o->chunk_limit - __o->next_free); })
+
+# define obstack_make_room(OBSTACK,length) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ int __len = (length); \
+ if (__o->chunk_limit - __o->next_free < __len) \
+ _obstack_newchunk (__o, __len); \
+ (void) 0; })
+
+# define obstack_empty_p(OBSTACK) \
+ __extension__ \
+ ({ struct obstack const *__o = (OBSTACK); \
+ (__o->chunk->prev == 0 \
+ && __o->next_free == __PTR_ALIGN ((char *) __o->chunk, \
+ __o->chunk->contents, \
+ __o->alignment_mask)); })
+
+# define obstack_grow(OBSTACK,where,length) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ int __len = (length); \
+ if (__o->next_free + __len > __o->chunk_limit) \
+ _obstack_newchunk (__o, __len); \
+ memcpy (__o->next_free, where, __len); \
+ __o->next_free += __len; \
+ (void) 0; })
+
+# define obstack_grow0(OBSTACK,where,length) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ int __len = (length); \
+ if (__o->next_free + __len + 1 > __o->chunk_limit) \
+ _obstack_newchunk (__o, __len + 1); \
+ memcpy (__o->next_free, where, __len); \
+ __o->next_free += __len; \
+ *(__o->next_free)++ = 0; \
+ (void) 0; })
+
+# define obstack_1grow(OBSTACK,datum) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ if (__o->next_free + 1 > __o->chunk_limit) \
+ _obstack_newchunk (__o, 1); \
+ obstack_1grow_fast (__o, datum); \
+ (void) 0; })
+
+/* These assume that the obstack alignment is good enough for pointers
+ or ints, and that the data added so far to the current object
+ shares that much alignment. */
+
+# define obstack_ptr_grow(OBSTACK,datum) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ if (__o->next_free + sizeof (void *) > __o->chunk_limit) \
+ _obstack_newchunk (__o, sizeof (void *)); \
+ obstack_ptr_grow_fast (__o, datum); }) \
+
+# define obstack_int_grow(OBSTACK,datum) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ if (__o->next_free + sizeof (int) > __o->chunk_limit) \
+ _obstack_newchunk (__o, sizeof (int)); \
+ obstack_int_grow_fast (__o, datum); })
+
+# define obstack_ptr_grow_fast(OBSTACK,aptr) \
+__extension__ \
+({ struct obstack *__o1 = (OBSTACK); \
+ *(const void **) __o1->next_free = (aptr); \
+ __o1->next_free += sizeof (const void *); \
+ (void) 0; })
+
+# define obstack_int_grow_fast(OBSTACK,aint) \
+__extension__ \
+({ struct obstack *__o1 = (OBSTACK); \
+ *(int *) __o1->next_free = (aint); \
+ __o1->next_free += sizeof (int); \
+ (void) 0; })
+
+# define obstack_blank(OBSTACK,length) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ int __len = (length); \
+ if (__o->chunk_limit - __o->next_free < __len) \
+ _obstack_newchunk (__o, __len); \
+ obstack_blank_fast (__o, __len); \
+ (void) 0; })
+
+# define obstack_alloc(OBSTACK,length) \
+__extension__ \
+({ struct obstack *__h = (OBSTACK); \
+ obstack_blank (__h, (length)); \
+ obstack_finish (__h); })
+
+# define obstack_copy(OBSTACK,where,length) \
+__extension__ \
+({ struct obstack *__h = (OBSTACK); \
+ obstack_grow (__h, (where), (length)); \
+ obstack_finish (__h); })
+
+# define obstack_copy0(OBSTACK,where,length) \
+__extension__ \
+({ struct obstack *__h = (OBSTACK); \
+ obstack_grow0 (__h, (where), (length)); \
+ obstack_finish (__h); })
+
+/* The local variable is named __o1 to avoid a name conflict
+ when obstack_blank is called. */
+# define obstack_finish(OBSTACK) \
+__extension__ \
+({ struct obstack *__o1 = (OBSTACK); \
+ void *__value = (void *) __o1->object_base; \
+ if (__o1->next_free == __value) \
+ __o1->maybe_empty_object = 1; \
+ __o1->next_free \
+ = __PTR_ALIGN (__o1->object_base, __o1->next_free, \
+ __o1->alignment_mask); \
+ if (__o1->next_free - (char *)__o1->chunk \
+ > __o1->chunk_limit - (char *)__o1->chunk) \
+ __o1->next_free = __o1->chunk_limit; \
+ __o1->object_base = __o1->next_free; \
+ __value; })
+
+# define obstack_free(OBSTACK, OBJ) \
+__extension__ \
+({ struct obstack *__o = (OBSTACK); \
+ void *__obj = (OBJ); \
+ if (__obj > (void *)__o->chunk && __obj < (void *)__o->chunk_limit) \
+ __o->next_free = __o->object_base = (char *)__obj; \
+ else (obstack_free) (__o, __obj); })
+
+#else /* not __GNUC__ or not __STDC__ */
+
+# define obstack_object_size(h) \
+ (unsigned) ((h)->next_free - (h)->object_base)
+
+# define obstack_room(h) \
+ (unsigned) ((h)->chunk_limit - (h)->next_free)
+
+# define obstack_empty_p(h) \
+ ((h)->chunk->prev == 0 \
+ && (h)->next_free == __PTR_ALIGN ((char *) (h)->chunk, \
+ (h)->chunk->contents, \
+ (h)->alignment_mask))
+
+/* Note that the call to _obstack_newchunk is enclosed in (..., 0)
+ so that we can avoid having void expressions
+ in the arms of the conditional expression.
+ Casting the third operand to void was tried before,
+ but some compilers won't accept it. */
+
+# define obstack_make_room(h,length) \
+( (h)->temp.tempint = (length), \
+ (((h)->next_free + (h)->temp.tempint > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0))
+
+# define obstack_grow(h,where,length) \
+( (h)->temp.tempint = (length), \
+ (((h)->next_free + (h)->temp.tempint > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0), \
+ memcpy ((h)->next_free, where, (h)->temp.tempint), \
+ (h)->next_free += (h)->temp.tempint)
+
+# define obstack_grow0(h,where,length) \
+( (h)->temp.tempint = (length), \
+ (((h)->next_free + (h)->temp.tempint + 1 > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), (h)->temp.tempint + 1), 0) : 0), \
+ memcpy ((h)->next_free, where, (h)->temp.tempint), \
+ (h)->next_free += (h)->temp.tempint, \
+ *((h)->next_free)++ = 0)
+
+# define obstack_1grow(h,datum) \
+( (((h)->next_free + 1 > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), 1), 0) : 0), \
+ obstack_1grow_fast (h, datum))
+
+# define obstack_ptr_grow(h,datum) \
+( (((h)->next_free + sizeof (char *) > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), sizeof (char *)), 0) : 0), \
+ obstack_ptr_grow_fast (h, datum))
+
+# define obstack_int_grow(h,datum) \
+( (((h)->next_free + sizeof (int) > (h)->chunk_limit) \
+ ? (_obstack_newchunk ((h), sizeof (int)), 0) : 0), \
+ obstack_int_grow_fast (h, datum))
+
+# define obstack_ptr_grow_fast(h,aptr) \
+ (((const void **) ((h)->next_free += sizeof (void *)))[-1] = (aptr))
+
+# define obstack_int_grow_fast(h,aint) \
+ (((int *) ((h)->next_free += sizeof (int)))[-1] = (aint))
+
+# define obstack_blank(h,length) \
+( (h)->temp.tempint = (length), \
+ (((h)->chunk_limit - (h)->next_free < (h)->temp.tempint) \
+ ? (_obstack_newchunk ((h), (h)->temp.tempint), 0) : 0), \
+ obstack_blank_fast (h, (h)->temp.tempint))
+
+# define obstack_alloc(h,length) \
+ (obstack_blank ((h), (length)), obstack_finish ((h)))
+
+# define obstack_copy(h,where,length) \
+ (obstack_grow ((h), (where), (length)), obstack_finish ((h)))
+
+# define obstack_copy0(h,where,length) \
+ (obstack_grow0 ((h), (where), (length)), obstack_finish ((h)))
+
+# define obstack_finish(h) \
+( ((h)->next_free == (h)->object_base \
+ ? (((h)->maybe_empty_object = 1), 0) \
+ : 0), \
+ (h)->temp.tempptr = (h)->object_base, \
+ (h)->next_free \
+ = __PTR_ALIGN ((h)->object_base, (h)->next_free, \
+ (h)->alignment_mask), \
+ (((h)->next_free - (char *) (h)->chunk \
+ > (h)->chunk_limit - (char *) (h)->chunk) \
+ ? ((h)->next_free = (h)->chunk_limit) : 0), \
+ (h)->object_base = (h)->next_free, \
+ (h)->temp.tempptr)
+
+# define obstack_free(h,obj) \
+( (h)->temp.tempint = (char *) (obj) - (char *) (h)->chunk, \
+ ((((h)->temp.tempint > 0 \
+ && (h)->temp.tempint < (h)->chunk_limit - (char *) (h)->chunk)) \
+ ? (int) ((h)->next_free = (h)->object_base \
+ = (h)->temp.tempint + (char *) (h)->chunk) \
+ : (((obstack_free) ((h), (h)->temp.tempint + (char *) (h)->chunk), 0), 0)))
+
+#endif /* not __GNUC__ or not __STDC__ */
+
+#ifdef __cplusplus
+} /* C++ */
+#endif
+
+#endif /* obstack.h */
diff --git a/compat/qsort.c b/compat/qsort.c
index d93dce2..9574d53 100644
--- a/compat/qsort.c
+++ b/compat/qsort.c
@@ -55,7 +55,7 @@ void git_qsort(void *b, size_t n, size_t s,
msort_with_tmp(b, n, s, cmp, buf);
} else {
/* It's somewhat large, so malloc it. */
- char *tmp = malloc(size);
+ char *tmp = xmalloc(size);
msort_with_tmp(b, n, s, cmp, tmp);
free(tmp);
}
diff --git a/compat/setenv.c b/compat/setenv.c
index 3a22ea7..fc1439a 100644
--- a/compat/setenv.c
+++ b/compat/setenv.c
@@ -6,7 +6,10 @@ int gitsetenv(const char *name, const char *value, int replace)
size_t namelen, valuelen;
char *envstr;
- if (!name || !value) return -1;
+ if (!name || strchr(name, '=') || !value) {
+ errno = EINVAL;
+ return -1;
+ }
if (!replace) {
char *oldval = NULL;
oldval = getenv(name);
@@ -16,7 +19,10 @@ int gitsetenv(const char *name, const char *value, int replace)
namelen = strlen(name);
valuelen = strlen(value);
envstr = malloc((namelen + valuelen + 2));
- if (!envstr) return -1;
+ if (!envstr) {
+ errno = ENOMEM;
+ return -1;
+ }
memcpy(envstr, name, namelen);
envstr[namelen] = '=';
diff --git a/compat/snprintf.c b/compat/snprintf.c
index e1e0e75..42ea1ac 100644
--- a/compat/snprintf.c
+++ b/compat/snprintf.c
@@ -19,11 +19,14 @@
#undef vsnprintf
int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
{
+ va_list cp;
char *s;
int ret = -1;
if (maxsize > 0) {
- ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap);
+ va_copy(cp, ap);
+ ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, cp);
+ va_end(cp);
if (ret == maxsize-1)
ret = -1;
/* Windows does not NUL-terminate if result fills buffer */
@@ -42,7 +45,9 @@ int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
if (! str)
break;
s = str;
- ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap);
+ va_copy(cp, ap);
+ ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, cp);
+ va_end(cp);
if (ret == maxsize-1)
ret = -1;
}
diff --git a/compat/strtoimax.c b/compat/strtoimax.c
new file mode 100644
index 0000000..ac09ed8
--- /dev/null
+++ b/compat/strtoimax.c
@@ -0,0 +1,10 @@
+#include "../git-compat-util.h"
+
+intmax_t gitstrtoimax (const char *nptr, char **endptr, int base)
+{
+#if defined(NO_STRTOULL)
+ return strtol(nptr, endptr, base);
+#else
+ return strtoll(nptr, endptr, base);
+#endif
+}
diff --git a/compat/terminal.c b/compat/terminal.c
new file mode 100644
index 0000000..6d16c8f
--- /dev/null
+++ b/compat/terminal.c
@@ -0,0 +1,81 @@
+#include "git-compat-util.h"
+#include "compat/terminal.h"
+#include "sigchain.h"
+#include "strbuf.h"
+
+#ifdef HAVE_DEV_TTY
+
+static int term_fd = -1;
+static struct termios old_term;
+
+static void restore_term(void)
+{
+ if (term_fd < 0)
+ return;
+
+ tcsetattr(term_fd, TCSAFLUSH, &old_term);
+ term_fd = -1;
+}
+
+static void restore_term_on_signal(int sig)
+{
+ restore_term();
+ sigchain_pop(sig);
+ raise(sig);
+}
+
+char *git_terminal_prompt(const char *prompt, int echo)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ int r;
+ FILE *fh;
+
+ fh = fopen("/dev/tty", "w+");
+ if (!fh)
+ return NULL;
+
+ if (!echo) {
+ struct termios t;
+
+ if (tcgetattr(fileno(fh), &t) < 0) {
+ fclose(fh);
+ return NULL;
+ }
+
+ old_term = t;
+ term_fd = fileno(fh);
+ sigchain_push_common(restore_term_on_signal);
+
+ t.c_lflag &= ~ECHO;
+ if (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) {
+ term_fd = -1;
+ fclose(fh);
+ return NULL;
+ }
+ }
+
+ fputs(prompt, fh);
+ fflush(fh);
+
+ r = strbuf_getline(&buf, fh, '\n');
+ if (!echo) {
+ putc('\n', fh);
+ fflush(fh);
+ }
+
+ restore_term();
+ fclose(fh);
+
+ if (r == EOF)
+ return NULL;
+ return buf.buf;
+}
+
+#else
+
+char *git_terminal_prompt(const char *prompt, int echo)
+{
+ return getpass(prompt);
+}
+
+#endif
diff --git a/compat/terminal.h b/compat/terminal.h
new file mode 100644
index 0000000..97db7cd
--- /dev/null
+++ b/compat/terminal.h
@@ -0,0 +1,6 @@
+#ifndef COMPAT_TERMINAL_H
+#define COMPAT_TERMINAL_H
+
+char *git_terminal_prompt(const char *prompt, int echo);
+
+#endif /* COMPAT_TERMINAL_H */
diff --git a/compat/vcbuild/include/arpa/inet.h b/compat/vcbuild/include/arpa/inet.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/arpa/inet.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/grp.h b/compat/vcbuild/include/grp.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/grp.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/inttypes.h b/compat/vcbuild/include/inttypes.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/inttypes.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netdb.h b/compat/vcbuild/include/netdb.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/netdb.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netinet/in.h b/compat/vcbuild/include/netinet/in.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/netinet/in.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netinet/tcp.h b/compat/vcbuild/include/netinet/tcp.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/netinet/tcp.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/pwd.h b/compat/vcbuild/include/pwd.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/pwd.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/ioctl.h b/compat/vcbuild/include/sys/ioctl.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/sys/ioctl.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/select.h b/compat/vcbuild/include/sys/select.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/sys/select.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/socket.h b/compat/vcbuild/include/sys/socket.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/sys/socket.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/wait.h b/compat/vcbuild/include/sys/wait.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/sys/wait.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/termios.h b/compat/vcbuild/include/termios.h
deleted file mode 100644
index 0d8552a..0000000
--- a/compat/vcbuild/include/termios.h
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/win32/sys/poll.c b/compat/win32/poll.c
index 708a6c9..403eaa7 100644
--- a/compat/win32/sys/poll.c
+++ b/compat/win32/poll.c
@@ -1,7 +1,7 @@
/* Emulation for poll(2)
Contributed by Paolo Bonzini.
- Copyright 2001-2003, 2006-2010 Free Software Foundation, Inc.
+ Copyright 2001-2003, 2006-2011 Free Software Foundation, Inc.
This file is part of gnulib.
@@ -27,7 +27,10 @@
#include <malloc.h>
#include <sys/types.h>
-#include "poll.h"
+
+/* Specification. */
+#include <poll.h>
+
#include <errno.h>
#include <limits.h>
#include <assert.h>
@@ -314,10 +317,7 @@ compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
#endif /* !MinGW */
int
-poll (pfd, nfd, timeout)
- struct pollfd *pfd;
- nfds_t nfd;
- int timeout;
+poll (struct pollfd *pfd, nfds_t nfd, int timeout)
{
#ifndef WIN32_NATIVE
fd_set rfds, wfds, efds;
@@ -454,6 +454,7 @@ poll (pfd, nfd, timeout)
if (!hEvent)
hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
+restart:
handle_array[0] = hEvent;
nhandles = 1;
FD_ZERO (&rfds);
@@ -594,6 +595,12 @@ poll (pfd, nfd, timeout)
rc++;
}
+ if (!rc && timeout == INFTIM)
+ {
+ SwitchToThread();
+ goto restart;
+ }
+
return rc;
#endif
}
diff --git a/compat/win32/sys/poll.h b/compat/win32/poll.h
index b7aa59d..b7aa59d 100644
--- a/compat/win32/sys/poll.h
+++ b/compat/win32/poll.h
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h
index 2e20548..8ad1873 100644
--- a/compat/win32/pthread.h
+++ b/compat/win32/pthread.h
@@ -86,6 +86,11 @@ static inline int pthread_key_create(pthread_key_t *keyp, void (*destructor)(voi
return (*keyp = TlsAlloc()) == TLS_OUT_OF_INDEXES ? EAGAIN : 0;
}
+static inline int pthread_key_delete(pthread_key_t key)
+{
+ return TlsFree(key) ? 0 : EINVAL;
+}
+
static inline int pthread_setspecific(pthread_key_t key, const void *value)
{
return TlsSetValue(key, (void *)value) ? 0 : EINVAL;
diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c
index 42b95a9..d015e43 100644
--- a/compat/win32/syslog.c
+++ b/compat/win32/syslog.c
@@ -1,5 +1,4 @@
#include "../../git-compat-util.h"
-#include "../../strbuf.h"
static HANDLE ms_eventlog;
@@ -16,13 +15,8 @@ void openlog(const char *ident, int logopt, int facility)
void syslog(int priority, const char *fmt, ...)
{
- struct strbuf sb = STRBUF_INIT;
- struct strbuf_expand_dict_entry dict[] = {
- {"1", "% 1"},
- {NULL, NULL}
- };
WORD logtype;
- char *str;
+ char *str, *pos;
int str_len;
va_list ap;
@@ -39,11 +33,24 @@ void syslog(int priority, const char *fmt, ...)
}
str = malloc(str_len + 1);
+ if (!str) {
+ warning("malloc failed: '%s'", strerror(errno));
+ return;
+ }
+
va_start(ap, fmt);
vsnprintf(str, str_len + 1, fmt, ap);
va_end(ap);
- strbuf_expand(&sb, str, strbuf_expand_dict_cb, &dict);
- free(str);
+
+ while ((pos = strstr(str, "%1")) != NULL) {
+ str = realloc(str, ++str_len + 1);
+ if (!str) {
+ warning("realloc failed: '%s'", strerror(errno));
+ return;
+ }
+ memmove(pos + 2, pos + 1, strlen(pos));
+ pos[1] = ' ';
+ }
switch (priority) {
case LOG_EMERG:
@@ -66,7 +73,6 @@ void syslog(int priority, const char *fmt, ...)
}
ReportEventA(ms_eventlog, logtype, 0, 0, NULL, 1, 0,
- (const char **)&sb.buf, NULL);
-
- strbuf_release(&sb);
+ (const char **)&str, NULL);
+ free(str);
}
diff --git a/compat/win32mmap.c b/compat/win32mmap.c
index b58aa69..61d2ef8 100644
--- a/compat/win32mmap.c
+++ b/compat/win32mmap.c
@@ -30,7 +30,7 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start);
if (!CloseHandle(hmap))
- warning("unable to close file mapping handle\n");
+ warning("unable to close file mapping handle");
return temp ? temp : MAP_FAILED;
}
diff --git a/config.c b/config.c
index 49e5250..d28a499 100644
--- a/config.c
+++ b/config.c
@@ -12,13 +12,88 @@
#define MAXNAME (256)
-static FILE *config_file;
-static const char *config_file_name;
-static int config_linenr;
-static int config_file_eof;
+typedef struct config_file {
+ struct config_file *prev;
+ FILE *f;
+ const char *name;
+ int linenr;
+ int eof;
+ struct strbuf value;
+ char var[MAXNAME];
+} config_file;
+
+static config_file *cf;
+
static int zlib_compression_seen;
-const char *config_exclusive_filename = NULL;
+#define MAX_INCLUDE_DEPTH 10
+static const char include_depth_advice[] =
+"exceeded maximum include depth (%d) while including\n"
+" %s\n"
+"from\n"
+" %s\n"
+"Do you have circular includes?";
+static int handle_path_include(const char *path, struct config_include_data *inc)
+{
+ int ret = 0;
+ struct strbuf buf = STRBUF_INIT;
+ char *expanded = expand_user_path(path);
+
+ if (!expanded)
+ return error("Could not expand include path '%s'", path);
+ path = expanded;
+
+ /*
+ * Use an absolute path as-is, but interpret relative paths
+ * based on the including config file.
+ */
+ if (!is_absolute_path(path)) {
+ char *slash;
+
+ if (!cf || !cf->name)
+ return error("relative config includes must come from files");
+
+ slash = find_last_dir_sep(cf->name);
+ if (slash)
+ strbuf_add(&buf, cf->name, slash - cf->name + 1);
+ strbuf_addstr(&buf, path);
+ path = buf.buf;
+ }
+
+ if (!access(path, R_OK)) {
+ if (++inc->depth > MAX_INCLUDE_DEPTH)
+ die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
+ cf && cf->name ? cf->name : "the command line");
+ ret = git_config_from_file(git_config_include, path, inc);
+ inc->depth--;
+ }
+ strbuf_release(&buf);
+ free(expanded);
+ return ret;
+}
+
+int git_config_include(const char *var, const char *value, void *data)
+{
+ struct config_include_data *inc = data;
+ const char *type;
+ int ret;
+
+ /*
+ * Pass along all values, including "include" directives; this makes it
+ * possible to query information on the includes themselves.
+ */
+ ret = inc->fn(var, value, inc->data);
+ if (ret < 0)
+ return ret;
+
+ type = skip_prefix(var, "include.");
+ if (!type)
+ return ret;
+
+ if (!strcmp(type, "path"))
+ ret = handle_path_include(value, inc);
+ return ret;
+}
static void lowercase(char *p)
{
@@ -39,8 +114,8 @@ void git_config_push_parameter(const char *text)
strbuf_release(&env);
}
-static int git_config_parse_parameter(const char *text,
- config_fn_t fn, void *data)
+int git_config_parse_parameter(const char *text,
+ config_fn_t fn, void *data)
{
struct strbuf **pair;
pair = strbuf_split_str(text, '=', 2);
@@ -99,7 +174,7 @@ static int get_next_char(void)
FILE *f;
c = '\n';
- if ((f = config_file) != NULL) {
+ if (cf && ((f = cf->f) != NULL)) {
c = fgetc(f);
if (c == '\r') {
/* DOS like systems */
@@ -110,9 +185,9 @@ static int get_next_char(void)
}
}
if (c == '\n')
- config_linenr++;
+ cf->linenr++;
if (c == EOF) {
- config_file_eof = 1;
+ cf->eof = 1;
c = '\n';
}
}
@@ -121,21 +196,22 @@ static int get_next_char(void)
static char *parse_value(void)
{
- static struct strbuf value = STRBUF_INIT;
int quote = 0, comment = 0, space = 0;
- strbuf_reset(&value);
+ strbuf_reset(&cf->value);
for (;;) {
int c = get_next_char();
if (c == '\n') {
- if (quote)
+ if (quote) {
+ cf->linenr--;
return NULL;
- return value.buf;
+ }
+ return cf->value.buf;
}
if (comment)
continue;
if (isspace(c) && !quote) {
- if (value.len)
+ if (cf->value.len)
space++;
continue;
}
@@ -146,7 +222,7 @@ static char *parse_value(void)
}
}
for (; space; space--)
- strbuf_addch(&value, ' ');
+ strbuf_addch(&cf->value, ' ');
if (c == '\\') {
c = get_next_char();
switch (c) {
@@ -168,14 +244,14 @@ static char *parse_value(void)
default:
return NULL;
}
- strbuf_addch(&value, c);
+ strbuf_addch(&cf->value, c);
continue;
}
if (c == '"') {
quote = 1-quote;
continue;
}
- strbuf_addch(&value, c);
+ strbuf_addch(&cf->value, c);
}
}
@@ -192,7 +268,7 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
/* Get the full name */
for (;;) {
c = get_next_char();
- if (config_file_eof)
+ if (cf->eof)
break;
if (!iskeychar(c))
break;
@@ -219,7 +295,7 @@ static int get_extended_base_var(char *name, int baselen, int c)
{
do {
if (c == '\n')
- return -1;
+ goto error_incomplete_line;
c = get_next_char();
} while (isspace(c));
@@ -231,13 +307,13 @@ static int get_extended_base_var(char *name, int baselen, int c)
for (;;) {
int c = get_next_char();
if (c == '\n')
- return -1;
+ goto error_incomplete_line;
if (c == '"')
break;
if (c == '\\') {
c = get_next_char();
if (c == '\n')
- return -1;
+ goto error_incomplete_line;
}
name[baselen++] = c;
if (baselen > MAXNAME / 2)
@@ -248,6 +324,9 @@ static int get_extended_base_var(char *name, int baselen, int c)
if (get_next_char() != ']')
return -1;
return baselen;
+error_incomplete_line:
+ cf->linenr--;
+ return -1;
}
static int get_base_var(char *name)
@@ -256,7 +335,7 @@ static int get_base_var(char *name)
for (;;) {
int c = get_next_char();
- if (config_file_eof)
+ if (cf->eof)
return -1;
if (c == ']')
return baselen;
@@ -274,7 +353,7 @@ static int git_parse_file(config_fn_t fn, void *data)
{
int comment = 0;
int baselen = 0;
- static char var[MAXNAME];
+ char *var = cf->var;
/* U+FEFF Byte Order Mark in UTF8 */
static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
@@ -298,7 +377,7 @@ static int git_parse_file(config_fn_t fn, void *data)
}
}
if (c == '\n') {
- if (config_file_eof)
+ if (cf->eof)
return 0;
comment = 0;
continue;
@@ -323,10 +402,10 @@ static int git_parse_file(config_fn_t fn, void *data)
if (get_value(fn, data, var, baselen+1) < 0)
break;
}
- die("bad config file line %d in %s", config_linenr, config_file_name);
+ die("bad config file line %d in %s", cf->linenr, cf->name);
}
-static int parse_unit_factor(const char *end, unsigned long *val)
+static int parse_unit_factor(const char *end, uintmax_t *val)
{
if (!*end)
return 1;
@@ -349,11 +428,23 @@ static int git_parse_long(const char *value, long *ret)
{
if (value && *value) {
char *end;
- long val = strtol(value, &end, 0);
- unsigned long factor = 1;
+ intmax_t val;
+ uintmax_t uval;
+ uintmax_t factor = 1;
+
+ errno = 0;
+ val = strtoimax(value, &end, 0);
+ if (errno == ERANGE)
+ return 0;
if (!parse_unit_factor(end, &factor))
return 0;
- *ret = val * factor;
+ uval = abs(val);
+ uval *= factor;
+ if ((uval > maximum_signed_value_of_type(long)) ||
+ (abs(val) > uval))
+ return 0;
+ val *= factor;
+ *ret = val;
return 1;
}
return 0;
@@ -363,9 +454,19 @@ int git_parse_ulong(const char *value, unsigned long *ret)
{
if (value && *value) {
char *end;
- unsigned long val = strtoul(value, &end, 0);
+ uintmax_t val;
+ uintmax_t oldval;
+
+ errno = 0;
+ val = strtoumax(value, &end, 0);
+ if (errno == ERANGE)
+ return 0;
+ oldval = val;
if (!parse_unit_factor(end, &val))
return 0;
+ if ((val > maximum_unsigned_value_of_type(long)) ||
+ (oldval > val))
+ return 0;
*ret = val;
return 1;
}
@@ -374,8 +475,8 @@ int git_parse_ulong(const char *value, unsigned long *ret)
static void die_bad_config(const char *name)
{
- if (config_file_name)
- die("bad config value for '%s' in %s", name, config_file_name);
+ if (cf && cf->name)
+ die("bad config value for '%s' in %s", name, cf->name);
die("bad config value for '%s'", name);
}
@@ -484,6 +585,9 @@ static int git_default_core_config(const char *var, const char *value)
return 0;
}
+ if (!strcmp(var, "core.attributesfile"))
+ return git_config_pathname(&git_attributes_file, var, value);
+
if (!strcmp(var, "core.bare")) {
is_bare_repository_cfg = git_config_bool(var, value);
return 0;
@@ -543,7 +647,7 @@ static int git_default_core_config(const char *var, const char *value)
if (!strcmp(var, "core.packedgitwindowsize")) {
int pgsz_x2 = getpagesize() * 2;
- packed_git_window_size = git_config_int(var, value);
+ packed_git_window_size = git_config_ulong(var, value);
/* This value must be multiple of (pagesize * 2) */
packed_git_window_size /= pgsz_x2;
@@ -554,21 +658,23 @@ static int git_default_core_config(const char *var, const char *value)
}
if (!strcmp(var, "core.bigfilethreshold")) {
- long n = git_config_int(var, value);
- big_file_threshold = 0 < n ? n : 0;
+ big_file_threshold = git_config_ulong(var, value);
return 0;
}
if (!strcmp(var, "core.packedgitlimit")) {
- packed_git_limit = git_config_int(var, value);
+ packed_git_limit = git_config_ulong(var, value);
return 0;
}
if (!strcmp(var, "core.deltabasecachelimit")) {
- delta_base_cache_limit = git_config_int(var, value);
+ delta_base_cache_limit = git_config_ulong(var, value);
return 0;
}
+ if (!strcmp(var, "core.logpackaccess"))
+ return git_config_string(&log_pack_access, var, value);
+
if (!strcmp(var, "core.autocrlf")) {
if (value && !strcasecmp(value, "input")) {
if (core_eol == EOL_CRLF)
@@ -656,28 +762,6 @@ static int git_default_core_config(const char *var, const char *value)
return 0;
}
-static int git_default_user_config(const char *var, const char *value)
-{
- if (!strcmp(var, "user.name")) {
- if (!value)
- return config_error_nonbool(var);
- strlcpy(git_default_name, value, sizeof(git_default_name));
- user_ident_explicitly_given |= IDENT_NAME_GIVEN;
- return 0;
- }
-
- if (!strcmp(var, "user.email")) {
- if (!value)
- return config_error_nonbool(var);
- strlcpy(git_default_email, value, sizeof(git_default_email));
- user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
- return 0;
- }
-
- /* Add other config variables here and to Documentation/config.txt. */
- return 0;
-}
-
static int git_default_i18n_config(const char *var, const char *value)
{
if (!strcmp(var, "i18n.commitencoding"))
@@ -729,6 +813,8 @@ static int git_default_push_config(const char *var, const char *value)
push_default = PUSH_DEFAULT_NOTHING;
else if (!strcmp(value, "matching"))
push_default = PUSH_DEFAULT_MATCHING;
+ else if (!strcmp(value, "simple"))
+ push_default = PUSH_DEFAULT_SIMPLE;
else if (!strcmp(value, "upstream"))
push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "tracking")) /* deprecated */
@@ -737,8 +823,8 @@ static int git_default_push_config(const char *var, const char *value)
push_default = PUSH_DEFAULT_CURRENT;
else {
error("Malformed value for %s: %s", var, value);
- return error("Must be one of nothing, matching, "
- "tracking or current.");
+ return error("Must be one of nothing, matching, simple, "
+ "upstream or current.");
}
return 0;
}
@@ -762,7 +848,7 @@ int git_default_config(const char *var, const char *value, void *dummy)
return git_default_core_config(var, value);
if (!prefixcmp(var, "user."))
- return git_default_user_config(var, value);
+ return git_ident_config(var, value, dummy);
if (!prefixcmp(var, "i18n."))
return git_default_i18n_config(var, value);
@@ -784,6 +870,10 @@ int git_default_config(const char *var, const char *value, void *dummy)
return 0;
}
+ if (!strcmp(var, "pack.packsizelimit")) {
+ pack_size_limit_cfg = git_config_ulong(var, value);
+ return 0;
+ }
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
@@ -795,13 +885,24 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
ret = -1;
if (f) {
- config_file = f;
- config_file_name = filename;
- config_linenr = 1;
- config_file_eof = 0;
+ config_file top;
+
+ /* push config-file parsing state stack */
+ top.prev = cf;
+ top.f = f;
+ top.name = filename;
+ top.linenr = 1;
+ top.eof = 0;
+ strbuf_init(&top.value, 1024);
+ cf = &top;
+
ret = git_parse_file(fn, data);
+
+ /* pop config-file parsing state stack */
+ strbuf_release(&top.value);
+ cf = top.prev;
+
fclose(f);
- config_file_name = NULL;
}
return ret;
}
@@ -828,25 +929,25 @@ int git_config_system(void)
int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
- const char *home = NULL;
+ char *xdg_config = NULL;
+ char *user_config = NULL;
+
+ home_config_paths(&user_config, &xdg_config, "config");
- /* Setting $GIT_CONFIG makes git read _only_ the given config file. */
- if (config_exclusive_filename)
- return git_config_from_file(fn, config_exclusive_filename, data);
if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
data);
found += 1;
}
- home = getenv("HOME");
- if (home) {
- char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
- if (!access(user_config, R_OK)) {
- ret += git_config_from_file(fn, user_config, data);
- found += 1;
- }
- free(user_config);
+ if (!access(xdg_config, R_OK)) {
+ ret += git_config_from_file(fn, xdg_config, data);
+ found += 1;
+ }
+
+ if (!access(user_config, R_OK)) {
+ ret += git_config_from_file(fn, user_config, data);
+ found += 1;
}
if (repo_config && !access(repo_config, R_OK)) {
@@ -865,13 +966,31 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
break;
}
+ free(xdg_config);
+ free(user_config);
return ret == 0 ? found : ret;
}
-int git_config(config_fn_t fn, void *data)
+int git_config_with_options(config_fn_t fn, void *data,
+ const char *filename, int respect_includes)
{
char *repo_config = NULL;
int ret;
+ struct config_include_data inc = CONFIG_INCLUDE_INIT;
+
+ if (respect_includes) {
+ inc.fn = fn;
+ inc.data = data;
+ fn = git_config_include;
+ data = &inc;
+ }
+
+ /*
+ * If we have a specific filename, use it. Otherwise, follow the
+ * regular lookup sequence.
+ */
+ if (filename)
+ return git_config_from_file(fn, filename, data);
repo_config = git_pathdup("config");
ret = git_config_early(fn, data, repo_config);
@@ -880,6 +999,11 @@ int git_config(config_fn_t fn, void *data)
return ret;
}
+int git_config(config_fn_t fn, void *data)
+{
+ return git_config_with_options(fn, data, NULL, 1);
+}
+
/*
* Find all the stuff for git_config_set() below.
*/
@@ -909,6 +1033,7 @@ static int store_aux(const char *key, const char *value, void *cb)
{
const char *ep;
size_t section_len;
+ FILE *f = cf->f;
switch (store.state) {
case KEY_SEEN:
@@ -920,7 +1045,7 @@ static int store_aux(const char *key, const char *value, void *cb)
return 1;
}
- store.offset[store.seen] = ftell(config_file);
+ store.offset[store.seen] = ftell(f);
store.seen++;
}
break;
@@ -947,19 +1072,19 @@ static int store_aux(const char *key, const char *value, void *cb)
* Do not increment matches: this is no match, but we
* just made sure we are in the desired section.
*/
- store.offset[store.seen] = ftell(config_file);
+ store.offset[store.seen] = ftell(f);
/* fallthru */
case SECTION_END_SEEN:
case START:
if (matches(key, value)) {
- store.offset[store.seen] = ftell(config_file);
+ store.offset[store.seen] = ftell(f);
store.state = KEY_SEEN;
store.seen++;
} else {
if (strrchr(key, '.') - key == store.baselen &&
!strncmp(key, store.key, store.baselen)) {
store.state = SECTION_SEEN;
- store.offset[store.seen] = ftell(config_file);
+ store.offset[store.seen] = ftell(f);
}
}
}
@@ -1073,6 +1198,12 @@ contline:
return offset;
}
+int git_config_set_in_file(const char *config_filename,
+ const char *key, const char *value)
+{
+ return git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
+}
+
int git_config_set(const char *key, const char *value)
{
return git_config_set_multivar(key, value, NULL, 0);
@@ -1170,18 +1301,14 @@ out_free_ret_1:
* - the config file is removed and the lock file rename()d to it.
*
*/
-int git_config_set_multivar(const char *key, const char *value,
- const char *value_regex, int multi_replace)
+int git_config_set_multivar_in_file(const char *config_filename,
+ const char *key, const char *value,
+ const char *value_regex, int multi_replace)
{
int fd = -1, in_fd;
int ret;
- char *config_filename;
struct lock_file *lock = NULL;
-
- if (config_exclusive_filename)
- config_filename = xstrdup(config_exclusive_filename);
- else
- config_filename = git_pathdup("config");
+ char *filename_buf = NULL;
/* parse-key returns negative; flip the sign to feed exit(3) */
ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
@@ -1190,6 +1317,8 @@ int git_config_set_multivar(const char *key, const char *value,
store.multi_replace = multi_replace;
+ if (!config_filename)
+ config_filename = filename_buf = git_pathdup("config");
/*
* The lock serves a purpose in addition to locking: the new
@@ -1359,7 +1488,7 @@ int git_config_set_multivar(const char *key, const char *value,
out_free:
if (lock)
rollback_lock_file(lock);
- free(config_filename);
+ free(filename_buf);
return ret;
write_err_out:
@@ -1368,6 +1497,13 @@ write_err_out:
}
+int git_config_set_multivar(const char *key, const char *value,
+ const char *value_regex, int multi_replace)
+{
+ return git_config_set_multivar_in_file(NULL, key, value, value_regex,
+ multi_replace);
+}
+
static int section_name_match (const char *buf, const char *name)
{
int i = 0, j = 0, dot = 0;
@@ -1407,19 +1543,42 @@ static int section_name_match (const char *buf, const char *name)
return 0;
}
+static int section_name_is_ok(const char *name)
+{
+ /* Empty section names are bogus. */
+ if (!*name)
+ return 0;
+
+ /*
+ * Before a dot, we must be alphanumeric or dash. After the first dot,
+ * anything goes, so we can stop checking.
+ */
+ for (; *name && *name != '.'; name++)
+ if (*name != '-' && !isalnum(*name))
+ return 0;
+ return 1;
+}
+
/* if new_name == NULL, the section is removed instead */
-int git_config_rename_section(const char *old_name, const char *new_name)
+int git_config_rename_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name)
{
int ret = 0, remove = 0;
- char *config_filename;
- struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
+ char *filename_buf = NULL;
+ struct lock_file *lock;
int out_fd;
char buf[1024];
+ FILE *config_file;
+
+ if (new_name && !section_name_is_ok(new_name)) {
+ ret = error("invalid section name: %s", new_name);
+ goto out;
+ }
- if (config_exclusive_filename)
- config_filename = xstrdup(config_exclusive_filename);
- else
- config_filename = git_pathdup("config");
+ if (!config_filename)
+ config_filename = filename_buf = git_pathdup("config");
+
+ lock = xcalloc(sizeof(struct lock_file), 1);
out_fd = hold_lock_file_for_update(lock, config_filename, 0);
if (out_fd < 0) {
ret = error("could not lock config file %s", config_filename);
@@ -1483,10 +1642,15 @@ unlock_and_out:
if (commit_lock_file(lock) < 0)
ret = error("could not commit config file %s", config_filename);
out:
- free(config_filename);
+ free(filename_buf);
return ret;
}
+int git_config_rename_section(const char *old_name, const char *new_name)
+{
+ return git_config_rename_section_in_file(NULL, old_name, new_name);
+}
+
/*
* Call this to report error for your variable that should not
* get a boolean value (i.e. "[my] var" means "true").
diff --git a/config.mak.in b/config.mak.in
index ab37101..802d342 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -28,13 +28,15 @@ VPATH = @srcdir@
export exec_prefix mandir
export srcdir VPATH
-ASCIIDOC7=@ASCIIDOC7@
NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
NO_OPENSSL=@NO_OPENSSL@
NO_CURL=@NO_CURL@
NO_EXPAT=@NO_EXPAT@
NO_LIBGEN_H=@NO_LIBGEN_H@
HAVE_PATHS_H=@HAVE_PATHS_H@
+HAVE_LIBCHARSET_H=@HAVE_LIBCHARSET_H@
+NO_GETTEXT=@NO_GETTEXT@
+LIBC_CONTAINS_LIBINTL=@LIBC_CONTAINS_LIBINTL@
NEEDS_LIBICONV=@NEEDS_LIBICONV@
NEEDS_SOCKET=@NEEDS_SOCKET@
NEEDS_RESOLV=@NEEDS_RESOLV@
@@ -71,3 +73,4 @@ SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
NO_PTHREADS=@NO_PTHREADS@
PTHREAD_CFLAGS=@PTHREAD_CFLAGS@
PTHREAD_LIBS=@PTHREAD_LIBS@
+CHARSET_LIB=@CHARSET_LIB@
diff --git a/configure.ac b/configure.ac
index 048a1d4..4e9012f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,65 +1,55 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.59)
-AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org])
-
-AC_CONFIG_SRCDIR([git.c])
-
-config_file=config.mak.autogen
-config_append=config.mak.append
-config_in=config.mak.in
+## Definitions of private macros.
-echo "# ${config_append}. Generated by configure." > "${config_append}"
-
-
-## Definitions of macros
# GIT_CONF_APPEND_LINE(LINE)
# --------------------------
# Append LINE to file ${config_append}
AC_DEFUN([GIT_CONF_APPEND_LINE],
-[echo "$1" >> "${config_append}"])# GIT_CONF_APPEND_LINE
-#
+ [echo "$1" >> "${config_append}"])
+
# GIT_ARG_SET_PATH(PROGRAM)
# -------------------------
# Provide --with-PROGRAM=PATH option to set PATH to PROGRAM
# Optional second argument allows setting NO_PROGRAM=YesPlease if
# --without-PROGRAM version used.
AC_DEFUN([GIT_ARG_SET_PATH],
-[AC_ARG_WITH([$1],
- [AS_HELP_STRING([--with-$1=PATH],
- [provide PATH to $1])],
- [GIT_CONF_APPEND_PATH($1,$2)],[])
-])# GIT_ARG_SET_PATH
-#
+ [AC_ARG_WITH([$1],
+ [AS_HELP_STRING([--with-$1=PATH],
+ [provide PATH to $1])],
+ [GIT_CONF_APPEND_PATH([$1], [$2])],
+ [])])
+
# GIT_CONF_APPEND_PATH(PROGRAM)
-# ------------------------------
+# -----------------------------
# Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH
# Used by GIT_ARG_SET_PATH(PROGRAM)
# Optional second argument allows setting NO_PROGRAM=YesPlease if
# --without-PROGRAM is used.
AC_DEFUN([GIT_CONF_APPEND_PATH],
-[PROGRAM=m4_toupper($1); \
-if test "$withval" = "no"; then \
- if test -n "$2"; then \
- m4_toupper($1)_PATH=$withval; \
- AC_MSG_NOTICE([Disabling use of ${PROGRAM}]); \
- GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease); \
- GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=); \
- else \
- AC_MSG_ERROR([You cannot use git without $1]); \
- fi; \
-else \
- if test "$withval" = "yes"; then \
- AC_MSG_WARN([You should provide path for --with-$1=PATH]); \
- else \
- m4_toupper($1)_PATH=$withval; \
- AC_MSG_NOTICE([Setting m4_toupper($1)_PATH to $withval]); \
- GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \
- fi; \
-fi; \
-]) # GIT_CONF_APPEND_PATH
-#
+ [m4_pushdef([GIT_UC_PROGRAM], m4_toupper([$1]))dnl
+ PROGRAM=GIT_UC_PROGRAM
+ if test "$withval" = "no"; then
+ if test -n "$2"; then
+ GIT_UC_PROGRAM[]_PATH=$withval
+ AC_MSG_NOTICE([Disabling use of ${PROGRAM}])
+ GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease)
+ GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=)
+ else
+ AC_MSG_ERROR([You cannot use git without $1])
+ fi
+ else
+ if test "$withval" = "yes"; then
+ AC_MSG_WARN([You should provide path for --with-$1=PATH])
+ else
+ GIT_UC_PROGRAM[]_PATH=$withval
+ AC_MSG_NOTICE([Setting GIT_UC_PROGRAM[]_PATH to $withval])
+ GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval)
+ fi
+ fi
+ m4_popdef([GIT_UC_PROGRAM])])
+
# GIT_PARSE_WITH(PACKAGE)
# -----------------------
# For use in AC_ARG_WITH action-if-found, for packages default ON.
@@ -67,21 +57,22 @@ fi; \
# * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH
# * Unset NO_PACKAGE for --with-PACKAGE without ARG
AC_DEFUN([GIT_PARSE_WITH],
-[PACKAGE=m4_toupper($1); \
-if test "$withval" = "no"; then \
- m4_toupper(NO_$1)=YesPlease; \
-elif test "$withval" = "yes"; then \
- m4_toupper(NO_$1)=; \
-else \
- m4_toupper(NO_$1)=; \
- m4_toupper($1)DIR=$withval; \
- AC_MSG_NOTICE([Setting m4_toupper($1)DIR to $withval]); \
- GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \
-fi \
-])# GIT_PARSE_WITH
-#
+ [m4_pushdef([GIT_UC_PACKAGE], m4_toupper([$1]))dnl
+ PACKAGE=GIT_UC_PACKAGE
+ if test "$withval" = "no"; then
+ NO_[]GIT_UC_PACKAGE=YesPlease
+ elif test "$withval" = "yes"; then
+ NO_[]GIT_UC_PACKAGE=
+ else
+ NO_[]GIT_UC_PACKAGE=
+ GIT_UC_PACKAGE[]DIR=$withval
+ AC_MSG_NOTICE([Setting GIT_UC_PACKAGE[]DIR to $withval])
+ GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval)
+ fi
+ m4_popdef([GIT_UC_PACKAGE])])
+
# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT)
-# ---------------------
+# -----------------------------------------------------
# Set VAR to the value specied by --with-WITHNAME.
# No verification of arguments is performed, but warnings are issued
# if either 'yes' or 'no' is specified.
@@ -90,33 +81,32 @@ fi \
AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR],
[AC_ARG_WITH([$1],
[AS_HELP_STRING([--with-$1=VALUE], $3)],
- if test -n "$withval"; then \
- if test "$withval" = "yes" -o "$withval" = "no"; then \
+ if test -n "$withval"; then
+ if test "$withval" = "yes" -o "$withval" = "no"; then
AC_MSG_WARN([You likely do not want either 'yes' or 'no' as]
- [a value for $1 ($2). Maybe you do...?]); \
- fi; \
- \
- AC_MSG_NOTICE([Setting $2 to $withval]); \
- GIT_CONF_APPEND_LINE($2=$withval); \
+ [a value for $1 ($2). Maybe you do...?])
+ fi
+ AC_MSG_NOTICE([Setting $2 to $withval])
+ GIT_CONF_APPEND_LINE($2=$withval)
fi)])# GIT_PARSE_WITH_SET_MAKE_VAR
-dnl
-dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
-dnl -----------------------------------------
-dnl Similar to AC_CHECK_FUNC, but on systems that do not generate
-dnl warnings for missing prototypes (e.g. FreeBSD when compiling without
-dnl -Wall), it does not work. By looking for function definition in
-dnl libraries, this problem can be worked around.
+#
+# GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
+# -----------------------------------------
+# Similar to AC_CHECK_FUNC, but on systems that do not generate
+# warnings for missing prototypes (e.g. FreeBSD when compiling without
+# -Wall), it does not work. By looking for function definition in
+# libraries, this problem can be worked around.
AC_DEFUN([GIT_CHECK_FUNC],[AC_CHECK_FUNC([$1],[
AC_SEARCH_LIBS([$1],,
[$2],[$3])
],[$3])])
-dnl
-dnl GIT_STASH_FLAGS(BASEPATH_VAR)
-dnl -----------------------------
-dnl Allow for easy stashing of LDFLAGS and CPPFLAGS before running
-dnl tests that may want to take user settings into account.
+#
+# GIT_STASH_FLAGS(BASEPATH_VAR)
+# -----------------------------
+# Allow for easy stashing of LDFLAGS and CPPFLAGS before running
+# tests that may want to take user settings into account.
AC_DEFUN([GIT_STASH_FLAGS],[
if test -n "$1"; then
old_CPPFLAGS="$CPPFLAGS"
@@ -137,6 +127,36 @@ if test -n "$1"; then
fi
])
+## Configure body starts here.
+
+AC_PREREQ(2.59)
+AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org])
+
+AC_CONFIG_SRCDIR([git.c])
+
+config_file=config.mak.autogen
+config_append=config.mak.append
+config_in=config.mak.in
+
+echo "# ${config_append}. Generated by configure." > "${config_append}"
+
+# Directories holding "saner" versions of common or POSIX binaries.
+AC_ARG_WITH([sane-tool-path],
+ [AS_HELP_STRING(
+ [--with-sane-tool-path=DIR-1[[:DIR-2...:DIR-n]]],
+ [Directories to prepend to PATH in build system and generated scripts])],
+ [if test "$withval" = "no"; then
+ withval=''
+ else
+ AC_MSG_NOTICE([Setting SANE_TOOL_PATH to '$withval'])
+ fi
+ GIT_CONF_APPEND_LINE([SANE_TOOL_PATH=$withval])],
+ [# If the "--with-sane-tool-path" option was not given, don't touch
+ # SANE_TOOL_PATH here, but let defaults in Makefile take care of it.
+ # This should minimize spurious differences in the behaviour of the
+ # Git build system when configure is used w.r.t. when it is not.
+ :])
+
## Site configuration related to programs (before tests)
## --with-PACKAGE[=ARG] and --without-PACKAGE
#
@@ -144,14 +164,13 @@ fi
AC_ARG_WITH([lib],
[AS_HELP_STRING([--with-lib=ARG],
[ARG specifies alternative name for lib directory])],
- [if test "$withval" = "no" || test "$withval" = "yes"; then \
- AC_MSG_WARN([You should provide name for --with-lib=ARG]); \
-else \
- lib=$withval; \
- AC_MSG_NOTICE([Setting lib to '$lib']); \
- GIT_CONF_APPEND_LINE(lib=$withval); \
-fi; \
-],[])
+ [if test "$withval" = "no" || test "$withval" = "yes"; then
+ AC_MSG_WARN([You should provide name for --with-lib=ARG])
+ else
+ lib=$withval
+ AC_MSG_NOTICE([Setting lib to '$lib'])
+ GIT_CONF_APPEND_LINE(lib=$withval)
+ fi])
if test -z "$lib"; then
AC_MSG_NOTICE([Setting lib to 'lib' (the default)])
@@ -217,9 +236,9 @@ AC_MSG_NOTICE([CHECKS for site configuration])
# /foo/bar/include and /foo/bar/lib directories.
AC_ARG_WITH(openssl,
AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
-AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\
-GIT_PARSE_WITH(openssl))
-#
+AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),
+GIT_PARSE_WITH([openssl]))
+
# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
# able to use Perl-compatible regular expressions.
#
@@ -229,17 +248,16 @@ GIT_PARSE_WITH(openssl))
AC_ARG_WITH(libpcre,
AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)])
AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]),
-if test "$withval" = "no"; then \
- USE_LIBPCRE=; \
-elif test "$withval" = "yes"; then \
- USE_LIBPCRE=YesPlease; \
-else
- USE_LIBPCRE=YesPlease; \
- LIBPCREDIR=$withval; \
- AC_MSG_NOTICE([Setting LIBPCREDIR to $withval]); \
- GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval); \
-fi \
-)
+ if test "$withval" = "no"; then
+ USE_LIBPCRE=
+ elif test "$withval" = "yes"; then
+ USE_LIBPCRE=YesPlease
+ else
+ USE_LIBPCRE=YesPlease
+ LIBPCREDIR=$withval
+ AC_MSG_NOTICE([Setting LIBPCREDIR to $withval])
+ GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval)
+ fi)
#
# Define NO_CURL if you do not have curl installed. git-http-pull and
# git-http-push are not built, and you cannot use http:// and https://
@@ -347,7 +365,7 @@ AC_ARG_WITH(tcltk,
AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)])
AS_HELP_STRING([],[ARG is the full path to the Tcl/Tk interpreter.])
AS_HELP_STRING([],[Bare --with-tcltk will make the GUI part only if])
-AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),\
+AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),
GIT_PARSE_WITH(tcltk))
#
@@ -419,21 +437,14 @@ if test -n "$ASCIIDOC"; then
AC_MSG_CHECKING([for asciidoc version])
asciidoc_version=`$ASCIIDOC --version 2>/dev/null`
case "${asciidoc_version}" in
- asciidoc' '7*)
- ASCIIDOC7=YesPlease
- AC_MSG_RESULT([${asciidoc_version} > 7])
- ;;
asciidoc' '8*)
- ASCIIDOC7=
AC_MSG_RESULT([${asciidoc_version}])
;;
*)
- ASCIIDOC7=
AC_MSG_RESULT([${asciidoc_version} (unknown)])
;;
esac
fi
-AC_SUBST(ASCIIDOC7)
## Checks for libraries.
@@ -636,6 +647,23 @@ AC_CHECK_LIB([c], [basename],
AC_SUBST(NEEDS_LIBGEN)
test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
+AC_CHECK_LIB([c], [gettext],
+[LIBC_CONTAINS_LIBINTL=YesPlease],
+[LIBC_CONTAINS_LIBINTL=])
+AC_SUBST(LIBC_CONTAINS_LIBINTL)
+
+#
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+
+if test -z "$NO_GETTEXT"; then
+ test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
+fi
+
## Checks for header files.
AC_MSG_NOTICE([CHECKS for header files])
#
@@ -818,6 +846,22 @@ AC_CHECK_HEADER([paths.h],
[HAVE_PATHS_H=])
AC_SUBST(HAVE_PATHS_H)
#
+# Define HAVE_LIBCHARSET_H if have libcharset.h
+AC_CHECK_HEADER([libcharset.h],
+[HAVE_LIBCHARSET_H=YesPlease],
+[HAVE_LIBCHARSET_H=])
+AC_SUBST(HAVE_LIBCHARSET_H)
+# Define CHARSET_LIB if libiconv does not export the locale_charset symbol
+# and libcharset does
+CHARSET_LIB=
+AC_CHECK_LIB([iconv], [locale_charset],
+ [],
+ [AC_CHECK_LIB([charset], [locale_charset],
+ [CHARSET_LIB=-lcharset])
+ ]
+)
+AC_SUBST(CHARSET_LIB)
+#
# Define NO_STRCASESTR if you don't have strcasestr.
GIT_CHECK_FUNC(strcasestr,
[NO_STRCASESTR=],
diff --git a/connect.c b/connect.c
index d2ce57f..55a85ad 100644
--- a/connect.c
+++ b/connect.c
@@ -22,7 +22,7 @@ static int check_ref(const char *name, int len, unsigned int flags)
len -= 5;
/* REF_NORMAL means that we don't want the magic fake tag refs */
- if ((flags & REF_NORMAL) && check_ref_format(name) < 0)
+ if ((flags & REF_NORMAL) && check_refname_format(name, 0))
return 0;
/* REF_HEADS means that we want regular branch heads */
@@ -49,14 +49,25 @@ static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1
extra->nr++;
}
+static void die_initial_contact(int got_at_least_one_head)
+{
+ if (got_at_least_one_head)
+ die("The remote end hung up upon initial contact");
+ else
+ die("Could not read from remote repository.\n\n"
+ "Please make sure you have the correct access rights\n"
+ "and the repository exists.");
+}
+
/*
* Read all the refs from the other end
*/
struct ref **get_remote_heads(int in, struct ref **list,
- int nr_match, char **match,
unsigned int flags,
struct extra_have_objects *extra_have)
{
+ int got_at_least_one_head = 0;
+
*list = NULL;
for (;;) {
struct ref *ref;
@@ -65,7 +76,10 @@ struct ref **get_remote_heads(int in, struct ref **list,
char *name;
int len, name_len;
- len = packet_read_line(in, buffer, sizeof(buffer));
+ len = packet_read(in, buffer, sizeof(buffer));
+ if (len < 0)
+ die_initial_contact(got_at_least_one_head);
+
if (!len)
break;
if (buffer[len-1] == '\n')
@@ -92,41 +106,38 @@ struct ref **get_remote_heads(int in, struct ref **list,
if (!check_ref(name, name_len, flags))
continue;
- if (nr_match && !path_match(name, nr_match, match))
- continue;
ref = alloc_ref(buffer + 41);
hashcpy(ref->old_sha1, old_sha1);
*list = ref;
list = &ref->next;
+ got_at_least_one_head = 1;
}
return list;
}
int server_supports(const char *feature)
{
- return server_capabilities &&
- strstr(server_capabilities, feature) != NULL;
+ return !!parse_feature_request(server_capabilities, feature);
}
-int path_match(const char *path, int nr, char **match)
+const char *parse_feature_request(const char *feature_list, const char *feature)
{
- int i;
- int pathlen = strlen(path);
-
- for (i = 0; i < nr; i++) {
- char *s = match[i];
- int len = strlen(s);
-
- if (!len || len > pathlen)
- continue;
- if (memcmp(path + pathlen - len, s, len))
- continue;
- if (pathlen > len && path[pathlen - len - 1] != '/')
- continue;
- *s = 0;
- return (i + 1);
+ int len;
+
+ if (!feature_list)
+ return NULL;
+
+ len = strlen(feature);
+ while (*feature_list) {
+ const char *found = strstr(feature_list, feature);
+ if (!found)
+ return NULL;
+ if ((feature_list == found || isspace(found[-1])) &&
+ (!found[len] || isspace(found[len]) || found[len] == '='))
+ return found;
+ feature_list = found + 1;
}
- return 0;
+ return NULL;
}
enum protocol {
@@ -175,6 +186,15 @@ static void get_host_and_port(char **host, const char **port)
}
}
+static void enable_keepalive(int sockfd)
+{
+ int ka = 1;
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0)
+ fprintf(stderr, "unable to set SO_KEEPALIVE on socket: %s\n",
+ strerror(errno));
+}
+
#ifndef NO_IPV6
static const char *ai_name(const struct addrinfo *ai)
@@ -239,6 +259,8 @@ static int git_tcp_connect_sock(char *host, int flags)
if (sockfd < 0)
die("unable to connect to %s:\n%s", host, error_message.buf);
+ enable_keepalive(sockfd);
+
if (flags & CONNECT_VERBOSE)
fprintf(stderr, "done.\n");
@@ -254,7 +276,8 @@ static int git_tcp_connect_sock(char *host, int flags)
*/
static int git_tcp_connect_sock(char *host, int flags)
{
- int sockfd = -1, saved_errno = 0;
+ struct strbuf error_message = STRBUF_INIT;
+ int sockfd = -1;
const char *port = STR(DEFAULT_GIT_PORT);
char *ep;
struct hostent *he;
@@ -284,25 +307,21 @@ static int git_tcp_connect_sock(char *host, int flags)
fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
for (cnt = 0, ap = he->h_addr_list; *ap; ap++, cnt++) {
- sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
- if (sockfd < 0) {
- saved_errno = errno;
- continue;
- }
-
memset(&sa, 0, sizeof sa);
sa.sin_family = he->h_addrtype;
sa.sin_port = htons(nport);
memcpy(&sa.sin_addr, *ap, he->h_length);
- if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
- saved_errno = errno;
- fprintf(stderr, "%s[%d: %s]: errno=%s\n",
+ sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+ if ((sockfd < 0) ||
+ connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+ strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
host,
cnt,
inet_ntoa(*(struct in_addr *)&sa.sin_addr),
- strerror(saved_errno));
- close(sockfd);
+ strerror(errno));
+ if (0 <= sockfd)
+ close(sockfd);
sockfd = -1;
continue;
}
@@ -313,7 +332,9 @@ static int git_tcp_connect_sock(char *host, int flags)
}
if (sockfd < 0)
- die("unable to connect a socket (%s)", strerror(saved_errno));
+ die("unable to connect to %s:\n%s", host, error_message.buf);
+
+ enable_keepalive(sockfd);
if (flags & CONNECT_VERBOSE)
fprintf(stderr, "done.\n");
@@ -531,7 +552,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
* Add support for ssh port: ssh://host.xy:<port>/...
*/
if (protocol == PROTO_SSH && host != url)
- port = get_port(host);
+ port = get_port(end);
if (protocol == PROTO_GIT) {
/* These underlying connection commands die() if they
@@ -622,47 +643,3 @@ int finish_connect(struct child_process *conn)
free(conn);
return code;
}
-
-char *git_getpass(const char *prompt)
-{
- const char *askpass;
- struct child_process pass;
- const char *args[3];
- static struct strbuf buffer = STRBUF_INIT;
-
- askpass = getenv("GIT_ASKPASS");
- if (!askpass)
- askpass = askpass_program;
- if (!askpass)
- askpass = getenv("SSH_ASKPASS");
- if (!askpass || !(*askpass)) {
- char *result = getpass(prompt);
- if (!result)
- die_errno("Could not read password");
- return result;
- }
-
- args[0] = askpass;
- args[1] = prompt;
- args[2] = NULL;
-
- memset(&pass, 0, sizeof(pass));
- pass.argv = args;
- pass.out = -1;
-
- if (start_command(&pass))
- exit(1);
-
- strbuf_reset(&buffer);
- if (strbuf_read(&buffer, pass.out, 20) < 0)
- die("failed to read password from %s\n", askpass);
-
- close(pass.out);
-
- if (finish_command(&pass))
- exit(1);
-
- strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
-
- return buffer.buf;
-}
diff --git a/connected.c b/connected.c
new file mode 100644
index 0000000..1e89c1c
--- /dev/null
+++ b/connected.c
@@ -0,0 +1,62 @@
+#include "cache.h"
+#include "run-command.h"
+#include "sigchain.h"
+#include "connected.h"
+
+/*
+ * If we feed all the commits we want to verify to this command
+ *
+ * $ git rev-list --objects --stdin --not --all
+ *
+ * and if it does not error out, that means everything reachable from
+ * these commits locally exists and is connected to our existing refs.
+ * Note that this does _not_ validate the individual objects.
+ *
+ * Returns 0 if everything is connected, non-zero otherwise.
+ */
+int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
+{
+ struct child_process rev_list;
+ const char *argv[] = {"rev-list", "--objects",
+ "--stdin", "--not", "--all", NULL, NULL};
+ char commit[41];
+ unsigned char sha1[20];
+ int err = 0;
+
+ if (fn(cb_data, sha1))
+ return err;
+
+ if (quiet)
+ argv[5] = "--quiet";
+
+ memset(&rev_list, 0, sizeof(rev_list));
+ rev_list.argv = argv;
+ rev_list.git_cmd = 1;
+ rev_list.in = -1;
+ rev_list.no_stdout = 1;
+ rev_list.no_stderr = quiet;
+ if (start_command(&rev_list))
+ return error(_("Could not run 'git rev-list'"));
+
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ commit[40] = '\n';
+ do {
+ memcpy(commit, sha1_to_hex(sha1), 40);
+ if (write_in_full(rev_list.in, commit, 41) < 0) {
+ if (errno != EPIPE && errno != EINVAL)
+ error(_("failed write to rev-list: %s"),
+ strerror(errno));
+ err = -1;
+ break;
+ }
+ } while (!fn(cb_data, sha1));
+
+ if (close(rev_list.in)) {
+ error(_("failed to close rev-list's stdin: %s"), strerror(errno));
+ err = -1;
+ }
+
+ sigchain_pop(SIGPIPE);
+ return finish_command(&rev_list) || err;
+}
diff --git a/connected.h b/connected.h
new file mode 100644
index 0000000..7e4585a
--- /dev/null
+++ b/connected.h
@@ -0,0 +1,20 @@
+#ifndef CONNECTED_H
+#define CONNECTED_H
+
+/*
+ * Take callback data, and return next object name in the buffer.
+ * When called after returning the name for the last object, return -1
+ * to signal EOF, otherwise return 0.
+ */
+typedef int (*sha1_iterate_fn)(void *, unsigned char [20]);
+
+/*
+ * Make sure that our object store has all the commits necessary to
+ * connect the ancestry chain to some of our existing refs, and all
+ * the trees and blobs that these commits use.
+ *
+ * Return 0 if Ok, non zero otherwise (i.e. some missing objects)
+ */
+extern int check_everything_connected(sha1_iterate_fn, int quiet, void *cb_data);
+
+#endif /* CONNECTED_H */
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 5a83090..ffedce7 100755..100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -20,58 +20,8 @@
# 1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
# 2) Add the following line to your .bashrc/.zshrc:
# source ~/.git-completion.sh
-#
-# 3) Consider changing your PS1 to also show the current branch:
-# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
-# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
-#
-# The argument to __git_ps1 will be displayed only if you
-# are currently in a git repository. The %s token will be
-# the name of the current branch.
-#
-# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
-# value, unstaged (*) and staged (+) changes will be shown next
-# to the branch name. You can configure this per-repository
-# with the bash.showDirtyState variable, which defaults to true
-# once GIT_PS1_SHOWDIRTYSTATE is enabled.
-#
-# You can also see if currently something is stashed, by setting
-# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
-# then a '$' will be shown next to the branch name.
-#
-# If you would like to see if there're untracked files, then you can
-# set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're
-# untracked files, then a '%' will be shown next to the branch name.
-#
-# If you would like to see the difference between HEAD and its
-# upstream, set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates
-# you are behind, ">" indicates you are ahead, and "<>"
-# indicates you have diverged. You can further control
-# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
-# list of values:
-# verbose show number of commits ahead/behind (+/-) upstream
-# legacy don't use the '--count' option available in recent
-# versions of git-rev-list
-# git always compare HEAD to @{upstream}
-# svn always compare HEAD to your SVN upstream
-# By default, __git_ps1 will compare HEAD to your SVN upstream
-# if it can find one, or @{upstream} otherwise. Once you have
-# set GIT_PS1_SHOWUPSTREAM, you can override it on a
-# per-repository basis by setting the bash.showUpstream config
-# variable.
-#
-#
-# To submit patches:
-#
-# *) Read Documentation/SubmittingPatches
-# *) Send all patches to the current maintainer:
-#
-# "Shawn O. Pearce" <spearce@spearce.org>
-#
-# *) Always CC the Git mailing list:
-#
-# git@vger.kernel.org
-#
+# 3) Consider changing your PS1 to also show the current branch,
+# see git-prompt.sh for details.
if [[ -n ${ZSH_VERSION-} ]]; then
autoload -U +X bashcompinit && bashcompinit
@@ -86,9 +36,14 @@ esac
# returns location of .git repo
__gitdir ()
{
+ # Note: this function is duplicated in git-prompt.sh
+ # When updating it, make sure you update the other one to match.
if [ -z "${1-}" ]; then
if [ -n "${__git_dir-}" ]; then
echo "$__git_dir"
+ elif [ -n "${GIT_DIR-}" ]; then
+ test -d "${GIT_DIR-}" || return 1
+ echo "$GIT_DIR"
elif [ -d .git ]; then
echo .git
else
@@ -101,231 +56,16 @@ __gitdir ()
fi
}
-# stores the divergence from upstream in $p
-# used by GIT_PS1_SHOWUPSTREAM
-__git_ps1_show_upstream ()
-{
- local key value
- local svn_remote=() svn_url_pattern count n
- local upstream=git legacy="" verbose=""
-
- # get some config options from git-config
- while read key value; do
- case "$key" in
- bash.showupstream)
- GIT_PS1_SHOWUPSTREAM="$value"
- if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
- p=""
- return
- fi
- ;;
- svn-remote.*.url)
- svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
- svn_url_pattern+="\\|$value"
- upstream=svn+git # default upstream is SVN if available, else git
- ;;
- esac
- done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')
-
- # parse configuration values
- for option in ${GIT_PS1_SHOWUPSTREAM}; do
- case "$option" in
- git|svn) upstream="$option" ;;
- verbose) verbose=1 ;;
- legacy) legacy=1 ;;
- esac
- done
-
- # Find our upstream
- case "$upstream" in
- git) upstream="@{upstream}" ;;
- svn*)
- # get the upstream from the "git-svn-id: ..." in a commit message
- # (git-svn uses essentially the same procedure internally)
- local svn_upstream=($(git log --first-parent -1 \
- --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
- if [[ 0 -ne ${#svn_upstream[@]} ]]; then
- svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
- svn_upstream=${svn_upstream%@*}
- local n_stop="${#svn_remote[@]}"
- for ((n=1; n <= n_stop; ++n)); do
- svn_upstream=${svn_upstream#${svn_remote[$n]}}
- done
-
- if [[ -z "$svn_upstream" ]]; then
- # default branch name for checkouts with no layout:
- upstream=${GIT_SVN_ID:-git-svn}
- else
- upstream=${svn_upstream#/}
- fi
- elif [[ "svn+git" = "$upstream" ]]; then
- upstream="@{upstream}"
- fi
- ;;
- esac
-
- # Find how many commits we are ahead/behind our upstream
- if [[ -z "$legacy" ]]; then
- count="$(git rev-list --count --left-right \
- "$upstream"...HEAD 2>/dev/null)"
- else
- # produce equivalent output to --count for older versions of git
- local commits
- if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
- then
- local commit behind=0 ahead=0
- for commit in $commits
- do
- case "$commit" in
- "<"*) let ++behind
- ;;
- *) let ++ahead
- ;;
- esac
- done
- count="$behind $ahead"
- else
- count=""
- fi
- fi
-
- # calculate the result
- if [[ -z "$verbose" ]]; then
- case "$count" in
- "") # no upstream
- p="" ;;
- "0 0") # equal to upstream
- p="=" ;;
- "0 "*) # ahead of upstream
- p=">" ;;
- *" 0") # behind upstream
- p="<" ;;
- *) # diverged from upstream
- p="<>" ;;
- esac
- else
- case "$count" in
- "") # no upstream
- p="" ;;
- "0 0") # equal to upstream
- p=" u=" ;;
- "0 "*) # ahead of upstream
- p=" u+${count#0 }" ;;
- *" 0") # behind upstream
- p=" u-${count% 0}" ;;
- *) # diverged from upstream
- p=" u+${count#* }-${count% *}" ;;
- esac
- fi
-
-}
-
-
-# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
-# returns text to add to bash PS1 prompt (includes branch name)
-__git_ps1 ()
-{
- local g="$(__gitdir)"
- if [ -n "$g" ]; then
- local r=""
- local b=""
- if [ -f "$g/rebase-merge/interactive" ]; then
- r="|REBASE-i"
- b="$(cat "$g/rebase-merge/head-name")"
- elif [ -d "$g/rebase-merge" ]; then
- r="|REBASE-m"
- b="$(cat "$g/rebase-merge/head-name")"
- else
- if [ -d "$g/rebase-apply" ]; then
- if [ -f "$g/rebase-apply/rebasing" ]; then
- r="|REBASE"
- elif [ -f "$g/rebase-apply/applying" ]; then
- r="|AM"
- else
- r="|AM/REBASE"
- fi
- elif [ -f "$g/MERGE_HEAD" ]; then
- r="|MERGING"
- elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
- r="|CHERRY-PICKING"
- elif [ -f "$g/BISECT_LOG" ]; then
- r="|BISECTING"
- fi
-
- b="$(git symbolic-ref HEAD 2>/dev/null)" || {
-
- b="$(
- case "${GIT_PS1_DESCRIBE_STYLE-}" in
- (contains)
- git describe --contains HEAD ;;
- (branch)
- git describe --contains --all HEAD ;;
- (describe)
- git describe HEAD ;;
- (* | default)
- git describe --tags --exact-match HEAD ;;
- esac 2>/dev/null)" ||
-
- b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
- b="unknown"
- b="($b)"
- }
- fi
-
- local w=""
- local i=""
- local s=""
- local u=""
- local c=""
- local p=""
-
- if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
- if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
- c="BARE:"
- else
- b="GIT_DIR!"
- fi
- elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
- if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
- if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
- git diff --no-ext-diff --quiet --exit-code || w="*"
- if git rev-parse --quiet --verify HEAD >/dev/null; then
- git diff-index --cached --quiet HEAD -- || i="+"
- else
- i="#"
- fi
- fi
- fi
- if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
- git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
- fi
-
- if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
- if [ -n "$(git ls-files --others --exclude-standard)" ]; then
- u="%"
- fi
- fi
-
- if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
- __git_ps1_show_upstream
- fi
- fi
-
- local f="$w$i$s$u"
- printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
- fi
-}
-
-# __gitcomp_1 requires 2 arguments
__gitcomp_1 ()
{
- local c IFS=' '$'\t'$'\n'
+ local c IFS=$' \t\n'
for c in $1; do
- case "$c$2" in
- --*=*) printf %s$'\n' "$c$2" ;;
- *.) printf %s$'\n' "$c$2" ;;
- *) printf %s$'\n' "$c$2 " ;;
+ c="$c$2"
+ case $c in
+ --*=*|*.) ;;
+ *) c="$c " ;;
esac
+ printf '%s\n' "$c"
done
}
@@ -485,15 +225,17 @@ _get_comp_words_by_ref ()
fi
fi
-# __gitcomp accepts 1, 2, 3, or 4 arguments
-# generates completion reply with compgen
+# Generates completion reply with compgen, appending a space to possible
+# completion words, if necessary.
+# It accepts 1 to 4 arguments:
+# 1: List of possible completion words.
+# 2: A prefix to be added to each possible completion word (optional).
+# 3: Generate possible completion matches for this word (optional).
+# 4: A suffix to be appended to each possible completion word (optional).
__gitcomp ()
{
- local cur_="$cur"
+ local cur_="${3-$cur}"
- if [ $# -gt 2 ]; then
- cur_="$3"
- fi
case "$cur_" in
--*=)
COMPREPLY=()
@@ -507,42 +249,39 @@ __gitcomp ()
esac
}
-# __git_heads accepts 0 or 1 arguments (to pass to __gitdir)
+# Generates completion reply with compgen from newline-separated possible
+# completion words by appending a space to all of them.
+# It accepts 1 to 4 arguments:
+# 1: List of possible completion words, separated by a single newline.
+# 2: A prefix to be added to each possible completion word (optional).
+# 3: Generate possible completion matches for this word (optional).
+# 4: A suffix to be appended to each possible completion word instead of
+# the default space (optional). If specified but empty, nothing is
+# appended.
+__gitcomp_nl ()
+{
+ local IFS=$'\n'
+ COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}"))
+}
+
__git_heads ()
{
- local cmd i is_hash=y dir="$(__gitdir "${1-}")"
+ local dir="$(__gitdir)"
if [ -d "$dir" ]; then
git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
refs/heads
return
fi
- for i in $(git ls-remote "${1-}" 2>/dev/null); do
- case "$is_hash,$i" in
- y,*) is_hash=n ;;
- n,*^{}) is_hash=y ;;
- n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
- n,*) is_hash=y; echo "$i" ;;
- esac
- done
}
-# __git_tags accepts 0 or 1 arguments (to pass to __gitdir)
__git_tags ()
{
- local cmd i is_hash=y dir="$(__gitdir "${1-}")"
+ local dir="$(__gitdir)"
if [ -d "$dir" ]; then
git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
refs/tags
return
fi
- for i in $(git ls-remote "${1-}" 2>/dev/null); do
- case "$is_hash,$i" in
- y,*) is_hash=n ;;
- n,*^{}) is_hash=y ;;
- n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
- n,*) is_hash=y; echo "$i" ;;
- esac
- done
}
# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments
@@ -550,7 +289,7 @@ __git_tags ()
# by checkout for tracking branches
__git_refs ()
{
- local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}"
+ local i hash dir="$(__gitdir "${1-}")" track="${2-}"
local format refs
if [ -d "$dir" ]; then
case "$cur" in
@@ -576,7 +315,7 @@ __git_refs ()
local ref entry
git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \
"refs/remotes/" | \
- while read entry; do
+ while read -r entry; do
eval "$entry"
ref="${ref#*/}"
if [[ "$ref" == "$cur"* ]]; then
@@ -586,16 +325,27 @@ __git_refs ()
fi
return
fi
- for i in $(git ls-remote "$dir" 2>/dev/null); do
- case "$is_hash,$i" in
- y,*) is_hash=n ;;
- n,*^{}) is_hash=y ;;
- n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
- n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
- n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;;
- n,*) is_hash=y; echo "$i" ;;
- esac
- done
+ case "$cur" in
+ refs|refs/*)
+ git ls-remote "$dir" "$cur*" 2>/dev/null | \
+ while read -r hash i; do
+ case "$i" in
+ *^{}) ;;
+ *) echo "$i" ;;
+ esac
+ done
+ ;;
+ *)
+ git ls-remote "$dir" HEAD ORIG_HEAD 'refs/tags/*' 'refs/heads/*' 'refs/remotes/*' 2>/dev/null | \
+ while read -r hash i; do
+ case "$i" in
+ *^{}) ;;
+ refs/*) echo "${i#refs/*/}" ;;
+ *) echo "$i" ;;
+ esac
+ done
+ ;;
+ esac
}
# __git_refs2 requires 1 argument (to pass to __git_refs)
@@ -610,30 +360,17 @@ __git_refs2 ()
# __git_refs_remotes requires 1 argument (to pass to ls-remote)
__git_refs_remotes ()
{
- local cmd i is_hash=y
- for i in $(git ls-remote "$1" 2>/dev/null); do
- case "$is_hash,$i" in
- n,refs/heads/*)
- is_hash=y
- echo "$i:refs/remotes/$1/${i#refs/heads/}"
- ;;
- y,*) is_hash=n ;;
- n,*^{}) is_hash=y ;;
- n,refs/tags/*) is_hash=y;;
- n,*) is_hash=y; ;;
- esac
+ local i hash
+ git ls-remote "$1" 'refs/heads/*' 2>/dev/null | \
+ while read -r hash i; do
+ echo "$i:refs/remotes/$1/${i#refs/heads/}"
done
}
__git_remotes ()
{
- local i ngoff IFS=$'\n' d="$(__gitdir)"
- __git_shopt -q nullglob || ngoff=1
- __git_shopt -s nullglob
- for i in "$d/remotes"/*; do
- echo ${i#$d/remotes/}
- done
- [ "$ngoff" ] && __git_shopt -u nullglob
+ local i IFS=$'\n' d="$(__gitdir)"
+ test -d "$d/remotes" && ls -1 "$d/remotes"
for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
i="${i#remote.}"
echo "${i/.url*/}"
@@ -660,7 +397,8 @@ __git_merge_strategies=
# is needed.
__git_compute_merge_strategies ()
{
- : ${__git_merge_strategies:=$(__git_list_merge_strategies)}
+ test -n "$__git_merge_strategies" ||
+ __git_merge_strategies=$(__git_list_merge_strategies)
}
__git_complete_revlist_file ()
@@ -690,9 +428,7 @@ __git_complete_revlist_file ()
*) pfx="$ref:$pfx" ;;
esac
- local IFS=$'\n'
- COMPREPLY=($(compgen -P "$pfx" \
- -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
+ __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
| sed '/^100... blob /{
s,^.* ,,
s,$, ,
@@ -706,20 +442,20 @@ __git_complete_revlist_file ()
s,$,/,
}
s/^.* //')" \
- -- "$cur_"))
+ "$pfx" "$cur_" ""
;;
*...*)
pfx="${cur_%...*}..."
cur_="${cur_#*...}"
- __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+ __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
;;
*..*)
pfx="${cur_%..*}.."
cur_="${cur_#*..}"
- __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+ __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
;;
*)
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
;;
esac
}
@@ -739,6 +475,9 @@ __git_complete_remote_or_refspec ()
{
local cur_="$cur" cmd="${words[1]}"
local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
+ if [ "$cmd" = "remote" ]; then
+ ((c++))
+ fi
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
@@ -756,10 +495,10 @@ __git_complete_remote_or_refspec ()
-*) ;;
*) remote="$i"; break ;;
esac
- c=$((++c))
+ ((c++))
done
if [ -z "$remote" ]; then
- __gitcomp "$(__git_remotes)"
+ __gitcomp_nl "$(__git_remotes)"
return
fi
if [ $no_complete_refspec = 1 ]; then
@@ -784,23 +523,23 @@ __git_complete_remote_or_refspec ()
case "$cmd" in
fetch)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_"
+ __gitcomp_nl "$(__git_refs2 "$remote")" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+ __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
fi
;;
- pull)
+ pull|remote)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
+ __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+ __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
fi
;;
push)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs)" "$pfx" "$cur_"
+ __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
+ __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
fi
;;
esac
@@ -838,7 +577,8 @@ __git_list_all_commands ()
__git_all_commands=
__git_compute_all_commands ()
{
- : ${__git_all_commands:=$(__git_list_all_commands)}
+ test -n "$__git_all_commands" ||
+ __git_all_commands=$(__git_list_all_commands)
}
__git_list_porcelain_commands ()
@@ -858,6 +598,8 @@ __git_list_porcelain_commands ()
checkout-index) : plumbing;;
commit-tree) : plumbing;;
count-objects) : infrequent;;
+ credential-cache) : credentials helper;;
+ credential-store) : credentials helper;;
cvsexportcommit) : export;;
cvsimport) : import;;
cvsserver) : daemon;;
@@ -931,7 +673,8 @@ __git_porcelain_commands=
__git_compute_porcelain_commands ()
{
__git_compute_all_commands
- : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
+ test -n "$__git_porcelain_commands" ||
+ __git_porcelain_commands=$(__git_list_porcelain_commands)
}
__git_pretty_aliases ()
@@ -994,7 +737,7 @@ __git_find_on_cmdline ()
return
fi
done
- c=$((++c))
+ ((c++))
done
}
@@ -1005,7 +748,7 @@ __git_has_doubledash ()
if [ "--" = "${words[c]}" ]; then
return 0
fi
- c=$((++c))
+ ((c++))
done
return 1
}
@@ -1079,7 +822,7 @@ _git_archive ()
return
;;
--remote=*)
- __gitcomp "$(__git_remotes)" "" "${cur##--remote=}"
+ __gitcomp_nl "$(__git_remotes)" "" "${cur##--remote=}"
return
;;
--*)
@@ -1110,7 +853,7 @@ _git_bisect ()
case "$subcommand" in
bad|good|reset|skip|start)
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
;;
*)
COMPREPLY=()
@@ -1128,7 +871,7 @@ _git_branch ()
-d|-m) only_local_ref="y" ;;
-r) has_r="y" ;;
esac
- c=$((++c))
+ ((c++))
done
case "$cur" in
@@ -1136,14 +879,14 @@ _git_branch ()
__gitcomp "
--color --no-color --verbose --abbrev= --no-abbrev
--track --no-track --contains --merged --no-merged
- --set-upstream
+ --set-upstream --edit-description --list
"
;;
*)
if [ $only_local_ref = "y" -a $has_r = "n" ]; then
- __gitcomp "$(__git_heads)"
+ __gitcomp_nl "$(__git_heads)"
else
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
fi
;;
esac
@@ -1190,7 +933,7 @@ _git_checkout ()
if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
track=''
fi
- __gitcomp "$(__git_refs '' $track)"
+ __gitcomp_nl "$(__git_refs '' $track)"
;;
esac
}
@@ -1207,7 +950,7 @@ _git_cherry_pick ()
__gitcomp "--edit --no-commit"
;;
*)
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
;;
esac
}
@@ -1259,12 +1002,9 @@ _git_commit ()
" "" "${cur##--cleanup=}"
return
;;
- --reuse-message=*)
- __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
- return
- ;;
- --reedit-message=*)
- __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+ --reuse-message=*|--reedit-message=*|\
+ --fixup=*|--squash=*)
+ __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
return
;;
--untracked-files=*)
@@ -1278,7 +1018,7 @@ _git_commit ()
--dry-run --reuse-message= --reedit-message=
--reset-author --file= --message= --template=
--cleanup= --untracked-files --untracked-files=
- --verbose --quiet
+ --verbose --quiet --fixup= --squash=
"
return
esac
@@ -1295,7 +1035,7 @@ _git_describe ()
"
return
esac
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
}
__git_diff_common_options="--stat --numstat --shortstat --summary
@@ -1432,6 +1172,10 @@ _git_gitk ()
_gitk
}
+__git_match_ctag() {
+ awk "/^${1////\\/}/ { print \$1 }" "$2"
+}
+
_git_grep ()
{
__git_has_doubledash && return
@@ -1454,7 +1198,16 @@ _git_grep ()
;;
esac
- __gitcomp "$(__git_refs)"
+ case "$cword,$prev" in
+ 2,*|*,-*)
+ if test -r tags; then
+ __gitcomp_nl "$(__git_match_ctag "$cur" tags)"
+ return
+ fi
+ ;;
+ esac
+
+ __gitcomp_nl "$(__git_refs)"
}
_git_help ()
@@ -1469,7 +1222,7 @@ _git_help ()
__gitcomp "$__git_all_commands $(__git_aliases)
attributes cli core-tutorial cvs-migration
diffcore gitk glossary hooks ignore modules
- repository-layout tutorial tutorial-2
+ namespaces repository-layout tutorial tutorial-2
workflows
"
}
@@ -1512,7 +1265,7 @@ _git_ls_files ()
_git_ls_remote ()
{
- __gitcomp "$(__git_remotes)"
+ __gitcomp_nl "$(__git_remotes)"
}
_git_ls_tree ()
@@ -1556,14 +1309,9 @@ _git_log ()
merge="--merge"
fi
case "$cur" in
- --pretty=*)
- __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
- " "" "${cur##--pretty=}"
- return
- ;;
- --format=*)
+ --pretty=*|--format=*)
__gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
- " "" "${cur##--format=}"
+ " "" "${cur#*=}"
return
;;
--date=*)
@@ -1601,7 +1349,7 @@ _git_log ()
__git_merge_options="
--no-commit --no-stat --log --no-log --squash --strategy
- --commit --stat --no-squash --ff --no-ff --ff-only
+ --commit --stat --no-squash --ff --no-ff --ff-only --edit --no-edit
"
_git_merge ()
@@ -1613,7 +1361,7 @@ _git_merge ()
__gitcomp "$__git_merge_options"
return
esac
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
}
_git_mergetool ()
@@ -1633,7 +1381,7 @@ _git_mergetool ()
_git_merge_base ()
{
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
}
_git_mv ()
@@ -1662,20 +1410,18 @@ _git_notes ()
__gitcomp '--ref'
;;
,*)
- case "${words[cword-1]}" in
+ case "$prev" in
--ref)
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
;;
*)
__gitcomp "$subcommands --ref"
;;
esac
;;
- add,--reuse-message=*|append,--reuse-message=*)
- __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
- ;;
+ add,--reuse-message=*|append,--reuse-message=*|\
add,--reedit-message=*|append,--reedit-message=*)
- __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+ __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
;;
add,--*|append,--*)
__gitcomp '--file= --message= --reedit-message=
@@ -1690,11 +1436,11 @@ _git_notes ()
prune,*)
;;
*)
- case "${words[cword-1]}" in
+ case "$prev" in
-m|-F)
;;
*)
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
;;
esac
;;
@@ -1722,18 +1468,18 @@ _git_push ()
{
case "$prev" in
--repo)
- __gitcomp "$(__git_remotes)"
+ __gitcomp_nl "$(__git_remotes)"
return
esac
case "$cur" in
--repo=*)
- __gitcomp "$(__git_remotes)" "" "${cur##--repo=}"
+ __gitcomp_nl "$(__git_remotes)" "" "${cur##--repo=}"
return
;;
--*)
__gitcomp "
--all --mirror --tags --dry-run --force --verbose
- --receive-pack= --repo=
+ --receive-pack= --repo= --set-upstream
"
return
;;
@@ -1765,7 +1511,7 @@ _git_rebase ()
return
esac
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
}
_git_reflog ()
@@ -1776,7 +1522,7 @@ _git_reflog ()
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
else
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
fi
}
@@ -1844,7 +1590,7 @@ __git_config_get_set_variables ()
done
git --git-dir="$(__gitdir)" config $config_file --list 2>/dev/null |
- while read line
+ while read -r line
do
case "$line" in
*.*=*)
@@ -1858,23 +1604,27 @@ _git_config ()
{
case "$prev" in
branch.*.remote)
- __gitcomp "$(__git_remotes)"
+ __gitcomp_nl "$(__git_remotes)"
return
;;
branch.*.merge)
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
return
;;
remote.*.fetch)
local remote="${prev#remote.}"
remote="${remote%.fetch}"
- __gitcomp "$(__git_refs_remotes "$remote")"
+ if [ -z "$cur" ]; then
+ COMPREPLY=("refs/heads/")
+ return
+ fi
+ __gitcomp_nl "$(__git_refs_remotes "$remote")"
return
;;
remote.*.push)
local remote="${prev#remote.}"
remote="${remote%.push}"
- __gitcomp "$(git --git-dir="$(__gitdir)" \
+ __gitcomp_nl "$(git --git-dir="$(__gitdir)" \
for-each-ref --format='%(refname):%(refname)' \
refs/heads)"
return
@@ -1921,7 +1671,7 @@ _git_config ()
return
;;
--get|--get-all|--unset|--unset-all)
- __gitcomp "$(__git_config_get_set_variables)"
+ __gitcomp_nl "$(__git_config_get_set_variables)"
return
;;
*.*)
@@ -1947,7 +1697,7 @@ _git_config ()
;;
branch.*)
local pfx="${cur%.*}." cur_="${cur#*.}"
- __gitcomp "$(__git_heads)" "$pfx" "$cur_" "."
+ __gitcomp_nl "$(__git_heads)" "$pfx" "$cur_" "."
return
;;
guitool.*.*)
@@ -1976,7 +1726,7 @@ _git_config ()
pager.*)
local pfx="${cur%.*}." cur_="${cur#*.}"
__git_compute_all_commands
- __gitcomp "$__git_all_commands" "$pfx" "$cur_"
+ __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_"
return
;;
remote.*.*)
@@ -1989,7 +1739,7 @@ _git_config ()
;;
remote.*)
local pfx="${cur%.*}." cur_="${cur#*.}"
- __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "."
+ __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
return
;;
url.*.*)
@@ -2095,6 +1845,7 @@ _git_config ()
core.whitespace
core.worktree
diff.autorefreshindex
+ diff.statGraphWidth
diff.external
diff.ignoreSubmodules
diff.mnemonicprefix
@@ -2281,7 +2032,7 @@ _git_config ()
_git_remote ()
{
- local subcommands="add rename rm show prune update set-head"
+ local subcommands="add rename rm set-head set-branches set-url show prune update"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
@@ -2289,8 +2040,11 @@ _git_remote ()
fi
case "$subcommand" in
- rename|rm|show|prune)
- __gitcomp "$(__git_remotes)"
+ rename|rm|set-url|show|prune)
+ __gitcomp_nl "$(__git_remotes)"
+ ;;
+ set-head|set-branches)
+ __git_complete_remote_or_refspec
;;
update)
local i c='' IFS=$'\n'
@@ -2308,7 +2062,7 @@ _git_remote ()
_git_replace ()
{
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
}
_git_reset ()
@@ -2321,7 +2075,7 @@ _git_reset ()
return
;;
esac
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
}
_git_revert ()
@@ -2332,7 +2086,7 @@ _git_revert ()
return
;;
esac
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
}
_git_rm ()
@@ -2370,14 +2124,9 @@ _git_show ()
__git_has_doubledash && return
case "$cur" in
- --pretty=*)
- __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
- " "" "${cur##--pretty=}"
- return
- ;;
- --format=*)
+ --pretty=*|--format=*)
__gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
- " "" "${cur##--format=}"
+ " "" "${cur#*=}"
return
;;
--*)
@@ -2436,7 +2185,7 @@ _git_stash ()
COMPREPLY=()
;;
show,*|apply,*|drop,*|pop,*|branch,*)
- __gitcomp "$(git --git-dir="$(__gitdir)" stash list \
+ __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
| sed -n -e 's/:.*//p')"
;;
*)
@@ -2509,7 +2258,7 @@ _git_svn ()
__gitcomp "
--merge --strategy= --verbose --dry-run
--fetch-all --no-rebase --commit-url
- --revision $cmt_opts $fc_opts
+ --revision --interactive $cmt_opts $fc_opts
"
;;
set-tree,--*)
@@ -2570,14 +2319,14 @@ _git_tag ()
i="${words[c]}"
case "$i" in
-d|-v)
- __gitcomp "$(__git_tags)"
+ __gitcomp_nl "$(__git_tags)"
return
;;
-f)
f=1
;;
esac
- c=$((++c))
+ ((c++))
done
case "$prev" in
@@ -2586,13 +2335,13 @@ _git_tag ()
;;
-*|tag)
if [ $f = 1 ]; then
- __gitcomp "$(__git_tags)"
+ __gitcomp_nl "$(__git_tags)"
else
COMPREPLY=()
fi
;;
*)
- __gitcomp "$(__git_refs)"
+ __gitcomp_nl "$(__git_refs)"
;;
esac
}
@@ -2602,31 +2351,21 @@ _git_whatchanged ()
_git_log
}
-_git ()
+__git_main ()
{
local i c=1 command __git_dir
- if [[ -n ${ZSH_VERSION-} ]]; then
- emulate -L bash
- setopt KSH_TYPESET
-
- # workaround zsh's bug that leaves 'words' as a special
- # variable in versions < 4.3.12
- typeset -h words
- fi
-
- local cur words cword prev
- _get_comp_words_by_ref -n =: cur words cword prev
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
--git-dir=*) __git_dir="${i#--git-dir=}" ;;
--bare) __git_dir="." ;;
- --version|-p|--paginate) ;;
--help) command="help"; break ;;
+ -c) c=$((++c)) ;;
+ -*) ;;
*) command="$i"; break ;;
esac
- c=$((++c))
+ ((c++))
done
if [ -z "$command" ]; then
@@ -2638,8 +2377,12 @@ _git ()
--bare
--version
--exec-path
+ --exec-path=
--html-path
+ --info-path
--work-tree=
+ --namespace=
+ --no-replace-objects
--help
"
;;
@@ -2659,20 +2402,8 @@ _git ()
fi
}
-_gitk ()
+__gitk_main ()
{
- if [[ -n ${ZSH_VERSION-} ]]; then
- emulate -L bash
- setopt KSH_TYPESET
-
- # workaround zsh's bug that leaves 'words' as a special
- # variable in versions < 4.3.12
- typeset -h words
- fi
-
- local cur words cword prev
- _get_comp_words_by_ref -n =: cur words cword prev
-
__git_has_doubledash && return
local g="$(__gitdir)"
@@ -2693,46 +2424,55 @@ _gitk ()
__git_complete_revlist
}
-complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \
- || complete -o default -o nospace -F _git git
-complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \
- || complete -o default -o nospace -F _gitk gitk
+__git_func_wrap ()
+{
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ emulate -L bash
+ setopt KSH_TYPESET
+
+ # workaround zsh's bug that leaves 'words' as a special
+ # variable in versions < 4.3.12
+ typeset -h words
+
+ # workaround zsh's bug that quotes spaces in the COMPREPLY
+ # array if IFS doesn't contain spaces.
+ typeset -h IFS
+ fi
+ local cur words cword prev
+ _get_comp_words_by_ref -n =: cur words cword prev
+ $1
+}
+
+# Setup completion for certain functions defined above by setting common
+# variables and workarounds.
+# This is NOT a public function; use at your own risk.
+__git_complete ()
+{
+ local wrapper="__git_wrap${2}"
+ eval "$wrapper () { __git_func_wrap $2 ; }"
+ complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \
+ || complete -o default -o nospace -F $wrapper $1
+}
+
+# wrapper for backwards compatibility
+_git ()
+{
+ __git_wrap__git_main
+}
+
+# wrapper for backwards compatibility
+_gitk ()
+{
+ __git_wrap__gitk_main
+}
+
+__git_complete git __git_main
+__git_complete gitk __gitk_main
# The following are necessary only for Cygwin, and only are needed
# when the user has tab-completed the executable name and consequently
# included the '.exe' suffix.
#
if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
-complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
- || complete -o default -o nospace -F _git git.exe
-fi
-
-if [[ -n ${ZSH_VERSION-} ]]; then
- __git_shopt () {
- local option
- if [ $# -ne 2 ]; then
- echo "USAGE: $0 (-q|-s|-u) <option>" >&2
- return 1
- fi
- case "$2" in
- nullglob)
- option="$2"
- ;;
- *)
- echo "$0: invalid option: $2" >&2
- return 1
- esac
- case "$1" in
- -q) setopt | grep -q "$option" ;;
- -u) unsetopt "$option" ;;
- -s) setopt "$option" ;;
- *)
- echo "$0: invalid flag: $1" >&2
- return 1
- esac
- }
-else
- __git_shopt () {
- shopt "$@"
- }
+__git_complete git.exe __git_main
fi
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
new file mode 100644
index 0000000..29b1ec9
--- /dev/null
+++ b/contrib/completion/git-prompt.sh
@@ -0,0 +1,289 @@
+# bash/zsh git prompt support
+#
+# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
+# Distributed under the GNU General Public License, version 2.0.
+#
+# This script allows you to see the current branch in your prompt.
+#
+# To enable:
+#
+# 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
+# 2) Add the following line to your .bashrc/.zshrc:
+# source ~/.git-prompt.sh
+# 3) Change your PS1 to also show the current branch:
+# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
+#
+# The argument to __git_ps1 will be displayed only if you are currently
+# in a git repository. The %s token will be the name of the current
+# branch.
+#
+# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
+# unstaged (*) and staged (+) changes will be shown next to the branch
+# name. You can configure this per-repository with the
+# bash.showDirtyState variable, which defaults to true once
+# GIT_PS1_SHOWDIRTYSTATE is enabled.
+#
+# You can also see if currently something is stashed, by setting
+# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
+# then a '$' will be shown next to the branch name.
+#
+# If you would like to see if there're untracked files, then you can set
+# GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
+# files, then a '%' will be shown next to the branch name.
+#
+# If you would like to see the difference between HEAD and its upstream,
+# set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates you are behind, ">"
+# indicates you are ahead, and "<>" indicates you have diverged. You
+# can further control behaviour by setting GIT_PS1_SHOWUPSTREAM to a
+# space-separated list of values:
+#
+# verbose show number of commits ahead/behind (+/-) upstream
+# legacy don't use the '--count' option available in recent
+# versions of git-rev-list
+# git always compare HEAD to @{upstream}
+# svn always compare HEAD to your SVN upstream
+#
+# By default, __git_ps1 will compare HEAD to your SVN upstream if it can
+# find one, or @{upstream} otherwise. Once you have set
+# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
+# setting the bash.showUpstream config variable.
+
+# __gitdir accepts 0 or 1 arguments (i.e., location)
+# returns location of .git repo
+__gitdir ()
+{
+ # Note: this function is duplicated in git-completion.bash
+ # When updating it, make sure you update the other one to match.
+ if [ -z "${1-}" ]; then
+ if [ -n "${__git_dir-}" ]; then
+ echo "$__git_dir"
+ elif [ -n "${GIT_DIR-}" ]; then
+ test -d "${GIT_DIR-}" || return 1
+ echo "$GIT_DIR"
+ elif [ -d .git ]; then
+ echo .git
+ else
+ git rev-parse --git-dir 2>/dev/null
+ fi
+ elif [ -d "$1/.git" ]; then
+ echo "$1/.git"
+ else
+ echo "$1"
+ fi
+}
+
+# stores the divergence from upstream in $p
+# used by GIT_PS1_SHOWUPSTREAM
+__git_ps1_show_upstream ()
+{
+ local key value
+ local svn_remote svn_url_pattern count n
+ local upstream=git legacy="" verbose=""
+
+ svn_remote=()
+ # get some config options from git-config
+ local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
+ while read -r key value; do
+ case "$key" in
+ bash.showupstream)
+ GIT_PS1_SHOWUPSTREAM="$value"
+ if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+ p=""
+ return
+ fi
+ ;;
+ svn-remote.*.url)
+ svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+ svn_url_pattern+="\\|$value"
+ upstream=svn+git # default upstream is SVN if available, else git
+ ;;
+ esac
+ done <<< "$output"
+
+ # parse configuration values
+ for option in ${GIT_PS1_SHOWUPSTREAM}; do
+ case "$option" in
+ git|svn) upstream="$option" ;;
+ verbose) verbose=1 ;;
+ legacy) legacy=1 ;;
+ esac
+ done
+
+ # Find our upstream
+ case "$upstream" in
+ git) upstream="@{upstream}" ;;
+ svn*)
+ # get the upstream from the "git-svn-id: ..." in a commit message
+ # (git-svn uses essentially the same procedure internally)
+ local svn_upstream=($(git log --first-parent -1 \
+ --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
+ if [[ 0 -ne ${#svn_upstream[@]} ]]; then
+ svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
+ svn_upstream=${svn_upstream%@*}
+ local n_stop="${#svn_remote[@]}"
+ for ((n=1; n <= n_stop; n++)); do
+ svn_upstream=${svn_upstream#${svn_remote[$n]}}
+ done
+
+ if [[ -z "$svn_upstream" ]]; then
+ # default branch name for checkouts with no layout:
+ upstream=${GIT_SVN_ID:-git-svn}
+ else
+ upstream=${svn_upstream#/}
+ fi
+ elif [[ "svn+git" = "$upstream" ]]; then
+ upstream="@{upstream}"
+ fi
+ ;;
+ esac
+
+ # Find how many commits we are ahead/behind our upstream
+ if [[ -z "$legacy" ]]; then
+ count="$(git rev-list --count --left-right \
+ "$upstream"...HEAD 2>/dev/null)"
+ else
+ # produce equivalent output to --count for older versions of git
+ local commits
+ if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
+ then
+ local commit behind=0 ahead=0
+ for commit in $commits
+ do
+ case "$commit" in
+ "<"*) ((behind++)) ;;
+ *) ((ahead++)) ;;
+ esac
+ done
+ count="$behind $ahead"
+ else
+ count=""
+ fi
+ fi
+
+ # calculate the result
+ if [[ -z "$verbose" ]]; then
+ case "$count" in
+ "") # no upstream
+ p="" ;;
+ "0 0") # equal to upstream
+ p="=" ;;
+ "0 "*) # ahead of upstream
+ p=">" ;;
+ *" 0") # behind upstream
+ p="<" ;;
+ *) # diverged from upstream
+ p="<>" ;;
+ esac
+ else
+ case "$count" in
+ "") # no upstream
+ p="" ;;
+ "0 0") # equal to upstream
+ p=" u=" ;;
+ "0 "*) # ahead of upstream
+ p=" u+${count#0 }" ;;
+ *" 0") # behind upstream
+ p=" u-${count% 0}" ;;
+ *) # diverged from upstream
+ p=" u+${count#* }-${count% *}" ;;
+ esac
+ fi
+
+}
+
+
+# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
+# returns text to add to bash PS1 prompt (includes branch name)
+__git_ps1 ()
+{
+ local g="$(__gitdir)"
+ if [ -n "$g" ]; then
+ local r=""
+ local b=""
+ if [ -f "$g/rebase-merge/interactive" ]; then
+ r="|REBASE-i"
+ b="$(cat "$g/rebase-merge/head-name")"
+ elif [ -d "$g/rebase-merge" ]; then
+ r="|REBASE-m"
+ b="$(cat "$g/rebase-merge/head-name")"
+ else
+ if [ -d "$g/rebase-apply" ]; then
+ if [ -f "$g/rebase-apply/rebasing" ]; then
+ r="|REBASE"
+ elif [ -f "$g/rebase-apply/applying" ]; then
+ r="|AM"
+ else
+ r="|AM/REBASE"
+ fi
+ elif [ -f "$g/MERGE_HEAD" ]; then
+ r="|MERGING"
+ elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
+ r="|CHERRY-PICKING"
+ elif [ -f "$g/BISECT_LOG" ]; then
+ r="|BISECTING"
+ fi
+
+ b="$(git symbolic-ref HEAD 2>/dev/null)" || {
+
+ b="$(
+ case "${GIT_PS1_DESCRIBE_STYLE-}" in
+ (contains)
+ git describe --contains HEAD ;;
+ (branch)
+ git describe --contains --all HEAD ;;
+ (describe)
+ git describe HEAD ;;
+ (* | default)
+ git describe --tags --exact-match HEAD ;;
+ esac 2>/dev/null)" ||
+
+ b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
+ b="unknown"
+ b="($b)"
+ }
+ fi
+
+ local w=""
+ local i=""
+ local s=""
+ local u=""
+ local c=""
+ local p=""
+
+ if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
+ if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
+ c="BARE:"
+ else
+ b="GIT_DIR!"
+ fi
+ elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
+ if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
+ if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
+ git diff --no-ext-diff --quiet --exit-code || w="*"
+ if git rev-parse --quiet --verify HEAD >/dev/null; then
+ git diff-index --cached --quiet HEAD -- || i="+"
+ else
+ i="#"
+ fi
+ fi
+ fi
+ if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
+ git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+ fi
+
+ if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
+ if [ -n "$(git ls-files --others --exclude-standard)" ]; then
+ u="%"
+ fi
+ fi
+
+ if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+ __git_ps1_show_upstream
+ fi
+ fi
+
+ local f="$w$i$s$u"
+ printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
+ fi
+}
diff --git a/contrib/credential/osxkeychain/.gitignore b/contrib/credential/osxkeychain/.gitignore
new file mode 100644
index 0000000..6c5b702
--- /dev/null
+++ b/contrib/credential/osxkeychain/.gitignore
@@ -0,0 +1 @@
+git-credential-osxkeychain
diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile
new file mode 100644
index 0000000..4b3a08a
--- /dev/null
+++ b/contrib/credential/osxkeychain/Makefile
@@ -0,0 +1,17 @@
+all:: git-credential-osxkeychain
+
+CC = gcc
+RM = rm -f
+CFLAGS = -g -O2 -Wall
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+git-credential-osxkeychain: git-credential-osxkeychain.o
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -Wl,-framework -Wl,Security
+
+git-credential-osxkeychain.o: git-credential-osxkeychain.c
+ $(CC) -c $(CFLAGS) $<
+
+clean:
+ $(RM) git-credential-osxkeychain git-credential-osxkeychain.o
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
new file mode 100644
index 0000000..6beed12
--- /dev/null
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -0,0 +1,173 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <Security/Security.h>
+
+static SecProtocolType protocol;
+static char *host;
+static char *path;
+static char *username;
+static char *password;
+static UInt16 port;
+
+static void die(const char *err, ...)
+{
+ char msg[4096];
+ va_list params;
+ va_start(params, err);
+ vsnprintf(msg, sizeof(msg), err, params);
+ fprintf(stderr, "%s\n", msg);
+ va_end(params);
+ exit(1);
+}
+
+static void *xstrdup(const char *s1)
+{
+ void *ret = strdup(s1);
+ if (!ret)
+ die("Out of memory");
+ return ret;
+}
+
+#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
+#define KEYCHAIN_ARGS \
+ NULL, /* default keychain */ \
+ KEYCHAIN_ITEM(host), \
+ 0, NULL, /* account domain */ \
+ KEYCHAIN_ITEM(username), \
+ KEYCHAIN_ITEM(path), \
+ port, \
+ protocol, \
+ kSecAuthenticationTypeDefault
+
+static void write_item(const char *what, const char *buf, int len)
+{
+ printf("%s=", what);
+ fwrite(buf, 1, len, stdout);
+ putchar('\n');
+}
+
+static void find_username_in_item(SecKeychainItemRef item)
+{
+ SecKeychainAttributeList list;
+ SecKeychainAttribute attr;
+
+ list.count = 1;
+ list.attr = &attr;
+ attr.tag = kSecAccountItemAttr;
+
+ if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
+ return;
+
+ write_item("username", attr.data, attr.length);
+ SecKeychainItemFreeContent(&list, NULL);
+}
+
+static void find_internet_password(void)
+{
+ void *buf;
+ UInt32 len;
+ SecKeychainItemRef item;
+
+ if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
+ return;
+
+ write_item("password", buf, len);
+ if (!username)
+ find_username_in_item(item);
+
+ SecKeychainItemFreeContent(NULL, buf);
+}
+
+static void delete_internet_password(void)
+{
+ SecKeychainItemRef item;
+
+ /*
+ * Require at least a protocol and host for removal, which is what git
+ * will give us; if you want to do something more fancy, use the
+ * Keychain manager.
+ */
+ if (!protocol || !host)
+ return;
+
+ if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
+ return;
+
+ SecKeychainItemDelete(item);
+}
+
+static void add_internet_password(void)
+{
+ /* Only store complete credentials */
+ if (!protocol || !host || !username || !password)
+ return;
+
+ if (SecKeychainAddInternetPassword(
+ KEYCHAIN_ARGS,
+ KEYCHAIN_ITEM(password),
+ NULL))
+ return;
+}
+
+static void read_credential(void)
+{
+ char buf[1024];
+
+ while (fgets(buf, sizeof(buf), stdin)) {
+ char *v;
+
+ if (!strcmp(buf, "\n"))
+ break;
+ buf[strlen(buf)-1] = '\0';
+
+ v = strchr(buf, '=');
+ if (!v)
+ die("bad input: %s", buf);
+ *v++ = '\0';
+
+ if (!strcmp(buf, "protocol")) {
+ if (!strcmp(v, "https"))
+ protocol = kSecProtocolTypeHTTPS;
+ else if (!strcmp(v, "http"))
+ protocol = kSecProtocolTypeHTTP;
+ else /* we don't yet handle other protocols */
+ exit(0);
+ }
+ else if (!strcmp(buf, "host")) {
+ char *colon = strchr(v, ':');
+ if (colon) {
+ *colon++ = '\0';
+ port = atoi(colon);
+ }
+ host = xstrdup(v);
+ }
+ else if (!strcmp(buf, "path"))
+ path = xstrdup(v);
+ else if (!strcmp(buf, "username"))
+ username = xstrdup(v);
+ else if (!strcmp(buf, "password"))
+ password = xstrdup(v);
+ }
+}
+
+int main(int argc, const char **argv)
+{
+ const char *usage =
+ "Usage: git credential-osxkeychain <get|store|erase>";
+
+ if (!argv[1])
+ die(usage);
+
+ read_credential();
+
+ if (!strcmp(argv[1], "get"))
+ find_internet_password();
+ else if (!strcmp(argv[1], "store"))
+ add_internet_password();
+ else if (!strcmp(argv[1], "erase"))
+ delete_internet_password();
+ /* otherwise, ignore unknown action */
+
+ return 0;
+}
diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README
new file mode 100644
index 0000000..502e03b
--- /dev/null
+++ b/contrib/diff-highlight/README
@@ -0,0 +1,152 @@
+diff-highlight
+==============
+
+Line oriented diffs are great for reviewing code, because for most
+hunks, you want to see the old and the new segments of code next to each
+other. Sometimes, though, when an old line and a new line are very
+similar, it's hard to immediately see the difference.
+
+You can use "--color-words" to highlight only the changed portions of
+lines. However, this can often be hard to read for code, as it loses
+the line structure, and you end up with oddly formatted bits.
+
+Instead, this script post-processes the line-oriented diff, finds pairs
+of lines, and highlights the differing segments. It's currently very
+simple and stupid about doing these tasks. In particular:
+
+ 1. It will only highlight hunks in which the number of removed and
+ added lines is the same, and it will pair lines within the hunk by
+ position (so the first removed line is compared to the first added
+ line, and so forth). This is simple and tends to work well in
+ practice. More complex changes don't highlight well, so we tend to
+ exclude them due to the "same number of removed and added lines"
+ restriction. Or even if we do try to highlight them, they end up
+ not highlighting because of our "don't highlight if the whole line
+ would be highlighted" rule.
+
+ 2. It will find the common prefix and suffix of two lines, and
+ consider everything in the middle to be "different". It could
+ instead do a real diff of the characters between the two lines and
+ find common subsequences. However, the point of the highlight is to
+ call attention to a certain area. Even if some small subset of the
+ highlighted area actually didn't change, that's OK. In practice it
+ ends up being more readable to just have a single blob on the line
+ showing the interesting bit.
+
+The goal of the script is therefore not to be exact about highlighting
+changes, but to call attention to areas of interest without being
+visually distracting. Non-diff lines and existing diff coloration is
+preserved; the intent is that the output should look exactly the same as
+the input, except for the occasional highlight.
+
+Use
+---
+
+You can try out the diff-highlight program with:
+
+---------------------------------------------
+git log -p --color | /path/to/diff-highlight
+---------------------------------------------
+
+If you want to use it all the time, drop it in your $PATH and put the
+following in your git configuration:
+
+---------------------------------------------
+[pager]
+ log = diff-highlight | less
+ show = diff-highlight | less
+ diff = diff-highlight | less
+---------------------------------------------
+
+Bugs
+----
+
+Because diff-highlight relies on heuristics to guess which parts of
+changes are important, there are some cases where the highlighting is
+more distracting than useful. Fortunately, these cases are rare in
+practice, and when they do occur, the worst case is simply a little
+extra highlighting. This section documents some cases known to be
+sub-optimal, in case somebody feels like working on improving the
+heuristics.
+
+1. Two changes on the same line get highlighted in a blob. For example,
+ highlighting:
+
+----------------------------------------------
+-foo(buf, size);
++foo(obj->buf, obj->size);
+----------------------------------------------
+
+ yields (where the inside of "+{}" would be highlighted):
+
+----------------------------------------------
+-foo(buf, size);
++foo(+{obj->buf, obj->}size);
+----------------------------------------------
+
+ whereas a more semantically meaningful output would be:
+
+----------------------------------------------
+-foo(buf, size);
++foo(+{obj->}buf, +{obj->}size);
+----------------------------------------------
+
+ Note that doing this right would probably involve a set of
+ content-specific boundary patterns, similar to word-diff. Otherwise
+ you get junk like:
+
+-----------------------------------------------------
+-this line has some -{i}nt-{ere}sti-{ng} text on it
++this line has some +{fa}nt+{a}sti+{c} text on it
+-----------------------------------------------------
+
+ which is less readable than the current output.
+
+2. The multi-line matching assumes that lines in the pre- and post-image
+ match by position. This is often the case, but can be fooled when a
+ line is removed from the top and a new one added at the bottom (or
+ vice versa). Unless the lines in the middle are also changed, diffs
+ will show this as two hunks, and it will not get highlighted at all
+ (which is good). But if the lines in the middle are changed, the
+ highlighting can be misleading. Here's a pathological case:
+
+-----------------------------------------------------
+-one
+-two
+-three
+-four
++two 2
++three 3
++four 4
++five 5
+-----------------------------------------------------
+
+ which gets highlighted as:
+
+-----------------------------------------------------
+-one
+-t-{wo}
+-three
+-f-{our}
++two 2
++t+{hree 3}
++four 4
++f+{ive 5}
+-----------------------------------------------------
+
+ because it matches "two" to "three 3", and so forth. It would be
+ nicer as:
+
+-----------------------------------------------------
+-one
+-two
+-three
+-four
++two +{2}
++three +{3}
++four +{4}
++five 5
+-----------------------------------------------------
+
+ which would probably involve pre-matching the lines into pairs
+ according to some heuristic.
diff --git a/contrib/diff-highlight/diff-highlight b/contrib/diff-highlight/diff-highlight
new file mode 100755
index 0000000..c4404d4
--- /dev/null
+++ b/contrib/diff-highlight/diff-highlight
@@ -0,0 +1,173 @@
+#!/usr/bin/perl
+
+use warnings FATAL => 'all';
+use strict;
+
+# Highlight by reversing foreground and background. You could do
+# other things like bold or underline if you prefer.
+my $HIGHLIGHT = "\x1b[7m";
+my $UNHIGHLIGHT = "\x1b[27m";
+my $COLOR = qr/\x1b\[[0-9;]*m/;
+my $BORING = qr/$COLOR|\s/;
+
+my @removed;
+my @added;
+my $in_hunk;
+
+while (<>) {
+ if (!$in_hunk) {
+ print;
+ $in_hunk = /^$COLOR*\@/;
+ }
+ elsif (/^$COLOR*-/) {
+ push @removed, $_;
+ }
+ elsif (/^$COLOR*\+/) {
+ push @added, $_;
+ }
+ else {
+ show_hunk(\@removed, \@added);
+ @removed = ();
+ @added = ();
+
+ print;
+ $in_hunk = /^$COLOR*[\@ ]/;
+ }
+
+ # Most of the time there is enough output to keep things streaming,
+ # but for something like "git log -Sfoo", you can get one early
+ # commit and then many seconds of nothing. We want to show
+ # that one commit as soon as possible.
+ #
+ # Since we can receive arbitrary input, there's no optimal
+ # place to flush. Flushing on a blank line is a heuristic that
+ # happens to match git-log output.
+ if (!length) {
+ local $| = 1;
+ }
+}
+
+# Flush any queued hunk (this can happen when there is no trailing context in
+# the final diff of the input).
+show_hunk(\@removed, \@added);
+
+exit 0;
+
+sub show_hunk {
+ my ($a, $b) = @_;
+
+ # If one side is empty, then there is nothing to compare or highlight.
+ if (!@$a || !@$b) {
+ print @$a, @$b;
+ return;
+ }
+
+ # If we have mismatched numbers of lines on each side, we could try to
+ # be clever and match up similar lines. But for now we are simple and
+ # stupid, and only handle multi-line hunks that remove and add the same
+ # number of lines.
+ if (@$a != @$b) {
+ print @$a, @$b;
+ return;
+ }
+
+ my @queue;
+ for (my $i = 0; $i < @$a; $i++) {
+ my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]);
+ print $rm;
+ push @queue, $add;
+ }
+ print @queue;
+}
+
+sub highlight_pair {
+ my @a = split_line(shift);
+ my @b = split_line(shift);
+
+ # Find common prefix, taking care to skip any ansi
+ # color codes.
+ my $seen_plusminus;
+ my ($pa, $pb) = (0, 0);
+ while ($pa < @a && $pb < @b) {
+ if ($a[$pa] =~ /$COLOR/) {
+ $pa++;
+ }
+ elsif ($b[$pb] =~ /$COLOR/) {
+ $pb++;
+ }
+ elsif ($a[$pa] eq $b[$pb]) {
+ $pa++;
+ $pb++;
+ }
+ elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
+ $seen_plusminus = 1;
+ $pa++;
+ $pb++;
+ }
+ else {
+ last;
+ }
+ }
+
+ # Find common suffix, ignoring colors.
+ my ($sa, $sb) = ($#a, $#b);
+ while ($sa >= $pa && $sb >= $pb) {
+ if ($a[$sa] =~ /$COLOR/) {
+ $sa--;
+ }
+ elsif ($b[$sb] =~ /$COLOR/) {
+ $sb--;
+ }
+ elsif ($a[$sa] eq $b[$sb]) {
+ $sa--;
+ $sb--;
+ }
+ else {
+ last;
+ }
+ }
+
+ if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) {
+ return highlight_line(\@a, $pa, $sa),
+ highlight_line(\@b, $pb, $sb);
+ }
+ else {
+ return join('', @a),
+ join('', @b);
+ }
+}
+
+sub split_line {
+ local $_ = shift;
+ return map { /$COLOR/ ? $_ : (split //) }
+ split /($COLOR*)/;
+}
+
+sub highlight_line {
+ my ($line, $prefix, $suffix) = @_;
+
+ return join('',
+ @{$line}[0..($prefix-1)],
+ $HIGHLIGHT,
+ @{$line}[$prefix..$suffix],
+ $UNHIGHLIGHT,
+ @{$line}[($suffix+1)..$#$line]
+ );
+}
+
+# Pairs are interesting to highlight only if we are going to end up
+# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting
+# is just useless noise. We can detect this by finding either a matching prefix
+# or suffix (disregarding boring bits like whitespace and colorization).
+sub is_pair_interesting {
+ my ($a, $pa, $sa, $b, $pb, $sb) = @_;
+ my $prefix_a = join('', @$a[0..($pa-1)]);
+ my $prefix_b = join('', @$b[0..($pb-1)]);
+ my $suffix_a = join('', @$a[($sa+1)..$#$a]);
+ my $suffix_b = join('', @$b[($sb+1)..$#$b]);
+
+ return $prefix_a !~ /^$COLOR*-$BORING*$/ ||
+ $prefix_b !~ /^$COLOR*\+$BORING*$/ ||
+ $suffix_a !~ /^$BORING*$/ ||
+ $suffix_b !~ /^$BORING*$/;
+}
diff --git a/contrib/diffall/README b/contrib/diffall/README
new file mode 100644
index 0000000..507f17d
--- /dev/null
+++ b/contrib/diffall/README
@@ -0,0 +1,31 @@
+The git-diffall script provides a directory based diff mechanism
+for git.
+
+To determine what diff viewer is used, the script requires either
+the 'diff.tool' or 'merge.tool' configuration option to be set.
+
+This script is compatible with most common forms used to specify a
+range of revisions to diff:
+
+ 1. git diffall: shows diff between working tree and staged changes
+ 2. git diffall --cached [<commit>]: shows diff between staged
+ changes and HEAD (or other named commit)
+ 3. git diffall <commit>: shows diff between working tree and named
+ commit
+ 4. git diffall <commit> <commit>: show diff between two named commits
+ 5. git diffall <commit>..<commit>: same as above
+ 6. git diffall <commit>...<commit>: show the changes on the branch
+ containing and up to the second, starting at a common ancestor
+ of both <commit>
+
+Note: all forms take an optional path limiter [-- <path>*]
+
+The '--extcmd=<command>' option allows the user to specify a custom
+command for viewing diffs. When given, configured defaults are
+ignored and the script runs $command $LOCAL $REMOTE. Additionally,
+$BASE is set in the environment.
+
+This script is based on an example provided by Thomas Rast on the
+Git list [1]:
+
+[1] http://thread.gmane.org/gmane.comp.version-control.git/124807
diff --git a/contrib/diffall/git-diffall b/contrib/diffall/git-diffall
new file mode 100755
index 0000000..84f2b65
--- /dev/null
+++ b/contrib/diffall/git-diffall
@@ -0,0 +1,257 @@
+#!/bin/sh
+# Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com>
+#
+# Perform a directory diff between commits in the repository using
+# the external diff or merge tool specified in the user's config.
+
+USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*]
+
+ --cached Compare to the index rather than the working tree.
+
+ --copy-back Copy files back to the working tree when the diff
+ tool exits (in case they were modified by the
+ user). This option is only valid if the diff
+ compared with the working tree.
+
+ -x=<command>
+ --extcmd=<command> Specify a custom command for viewing diffs.
+ git-diffall ignores the configured defaults and
+ runs $command $LOCAL $REMOTE when this option is
+ specified. Additionally, $BASE is set in the
+ environment.
+'
+
+SUBDIRECTORY_OK=1
+. "$(git --exec-path)/git-sh-setup"
+
+TOOL_MODE=diff
+. "$(git --exec-path)/git-mergetool--lib"
+
+merge_tool="$(get_merge_tool)"
+if test -z "$merge_tool"
+then
+ echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set."
+ usage
+fi
+
+start_dir=$(pwd)
+
+# All the file paths returned by the diff command are relative to the root
+# of the working copy. So if the script is called from a subdirectory, it
+# must switch to the root of working copy before trying to use those paths.
+cdup=$(git rev-parse --show-cdup) &&
+cd "$cdup" || {
+ echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
+ exit 1
+}
+
+# set up temp dir
+tmp=$(perl -e 'use File::Temp qw(tempdir);
+ $t=tempdir("/tmp/git-diffall.XXXXX") or exit(1);
+ print $t') || exit 1
+trap 'rm -rf "$tmp"' EXIT
+
+left=
+right=
+paths=
+dashdash_seen=
+compare_staged=
+merge_base=
+left_dir=
+right_dir=
+diff_tool=
+copy_back=
+
+while test $# != 0
+do
+ case "$1" in
+ -h|--h|--he|--hel|--help)
+ usage
+ ;;
+ --cached)
+ compare_staged=1
+ ;;
+ --copy-back)
+ copy_back=1
+ ;;
+ -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
+ if test $# = 1
+ then
+ echo You must specify the tool for use with --extcmd
+ usage
+ else
+ diff_tool=$2
+ shift
+ fi
+ ;;
+ --)
+ dashdash_seen=1
+ ;;
+ -*)
+ echo Invalid option: "$1"
+ usage
+ ;;
+ *)
+ # could be commit, commit range or path limiter
+ case "$1" in
+ *...*)
+ left=${1%...*}
+ right=${1#*...}
+ merge_base=1
+ ;;
+ *..*)
+ left=${1%..*}
+ right=${1#*..}
+ ;;
+ *)
+ if test -n "$dashdash_seen"
+ then
+ paths="$paths$1 "
+ elif test -z "$left"
+ then
+ left=$1
+ elif test -z "$right"
+ then
+ right=$1
+ else
+ paths="$paths$1 "
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ shift
+done
+
+# Determine the set of files which changed
+if test -n "$left" && test -n "$right"
+then
+ left_dir="cmt-$(git rev-parse --short $left)"
+ right_dir="cmt-$(git rev-parse --short $right)"
+
+ if test -n "$compare_staged"
+ then
+ usage
+ elif test -n "$merge_base"
+ then
+ git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
+ else
+ git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
+ fi
+elif test -n "$left"
+then
+ left_dir="cmt-$(git rev-parse --short $left)"
+
+ if test -n "$compare_staged"
+ then
+ right_dir="staged"
+ git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
+ else
+ right_dir="working_tree"
+ git diff --name-only "$left" -- $paths >"$tmp/filelist"
+ fi
+else
+ left_dir="HEAD"
+
+ if test -n "$compare_staged"
+ then
+ right_dir="staged"
+ git diff --name-only --cached -- $paths >"$tmp/filelist"
+ else
+ right_dir="working_tree"
+ git diff --name-only -- $paths >"$tmp/filelist"
+ fi
+fi
+
+# Exit immediately if there are no diffs
+if test ! -s "$tmp/filelist"
+then
+ exit 0
+fi
+
+if test -n "$copy_back" && test "$right_dir" != "working_tree"
+then
+ echo "--copy-back is only valid when diff includes the working tree."
+ exit 1
+fi
+
+# Create the named tmp directories that will hold the files to be compared
+mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
+
+# Populate the tmp/right_dir directory with the files to be compared
+while read name
+do
+ if test -n "$right"
+ then
+ ls_list=$(git ls-tree $right "$name")
+ if test -n "$ls_list"
+ then
+ mkdir -p "$tmp/$right_dir/$(dirname "$name")"
+ git show "$right":"$name" >"$tmp/$right_dir/$name" || true
+ fi
+ elif test -n "$compare_staged"
+ then
+ ls_list=$(git ls-files -- "$name")
+ if test -n "$ls_list"
+ then
+ mkdir -p "$tmp/$right_dir/$(dirname "$name")"
+ git show :"$name" >"$tmp/$right_dir/$name"
+ fi
+ else
+ if test -e "$name"
+ then
+ mkdir -p "$tmp/$right_dir/$(dirname "$name")"
+ cp "$name" "$tmp/$right_dir/$name"
+ fi
+ fi
+done < "$tmp/filelist"
+
+# Populate the tmp/left_dir directory with the files to be compared
+while read name
+do
+ if test -n "$left"
+ then
+ ls_list=$(git ls-tree $left "$name")
+ if test -n "$ls_list"
+ then
+ mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+ git show "$left":"$name" >"$tmp/$left_dir/$name" || true
+ fi
+ else
+ if test -n "$compare_staged"
+ then
+ ls_list=$(git ls-tree HEAD "$name")
+ if test -n "$ls_list"
+ then
+ mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+ git show HEAD:"$name" >"$tmp/$left_dir/$name"
+ fi
+ else
+ mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+ git show :"$name" >"$tmp/$left_dir/$name"
+ fi
+ fi
+done < "$tmp/filelist"
+
+LOCAL="$tmp/$left_dir"
+REMOTE="$tmp/$right_dir"
+
+if test -n "$diff_tool"
+then
+ export BASE
+ eval $diff_tool '"$LOCAL"' '"$REMOTE"'
+else
+ run_merge_tool "$merge_tool" false
+fi
+
+# Copy files back to the working dir, if requested
+if test -n "$copy_back" && test "$right_dir" = "working_tree"
+then
+ cd "$start_dir"
+ git_top_dir=$(git rev-parse --show-toplevel)
+ find "$tmp/$right_dir" -type f |
+ while read file
+ do
+ cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
+ done
+fi
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
index d351cfb..e671f6c 100644
--- a/contrib/emacs/git-blame.el
+++ b/contrib/emacs/git-blame.el
@@ -304,7 +304,7 @@ See also function `git-blame-mode'."
(defun git-blame-cleanup ()
"Remove all blame properties"
- (mapcar 'delete-overlay git-blame-overlays)
+ (mapc 'delete-overlay git-blame-overlays)
(setq git-blame-overlays nil)
(remove-git-blame-text-properties (point-min) (point-max)))
@@ -337,16 +337,16 @@ See also function `git-blame-mode'."
(defvar in-blame-filter nil)
(defun git-blame-filter (proc str)
- (save-excursion
- (set-buffer (process-buffer proc))
- (goto-char (process-mark proc))
- (insert-before-markers str)
- (goto-char 0)
- (unless in-blame-filter
- (let ((more t)
- (in-blame-filter t))
- (while more
- (setq more (git-blame-parse)))))))
+ (with-current-buffer (process-buffer proc)
+ (save-excursion
+ (goto-char (process-mark proc))
+ (insert-before-markers str)
+ (goto-char (point-min))
+ (unless in-blame-filter
+ (let ((more t)
+ (in-blame-filter t))
+ (while more
+ (setq more (git-blame-parse))))))))
(defun git-blame-parse ()
(cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n")
@@ -385,32 +385,33 @@ See also function `git-blame-mode'."
info))))
(defun git-blame-create-overlay (info start-line num-lines)
- (save-excursion
- (set-buffer git-blame-file)
- (let ((inhibit-point-motion-hooks t)
- (inhibit-modification-hooks t))
- (goto-line start-line)
- (let* ((start (point))
- (end (progn (forward-line num-lines) (point)))
- (ovl (make-overlay start end))
- (hash (car info))
- (spec `((?h . ,(substring hash 0 6))
- (?H . ,hash)
- (?a . ,(git-blame-get-info info 'author))
- (?A . ,(git-blame-get-info info 'author-mail))
- (?c . ,(git-blame-get-info info 'committer))
- (?C . ,(git-blame-get-info info 'committer-mail))
- (?s . ,(git-blame-get-info info 'summary)))))
- (push ovl git-blame-overlays)
- (overlay-put ovl 'git-blame info)
- (overlay-put ovl 'help-echo
- (format-spec git-blame-mouseover-format spec))
- (if git-blame-use-colors
- (overlay-put ovl 'face (list :background
- (cdr (assq 'color (cdr info))))))
- (overlay-put ovl 'line-prefix
- (propertize (format-spec git-blame-prefix-format spec)
- 'face 'git-blame-prefix-face))))))
+ (with-current-buffer git-blame-file
+ (save-excursion
+ (let ((inhibit-point-motion-hooks t)
+ (inhibit-modification-hooks t))
+ (goto-char (point-min))
+ (forward-line (1- start-line))
+ (let* ((start (point))
+ (end (progn (forward-line num-lines) (point)))
+ (ovl (make-overlay start end))
+ (hash (car info))
+ (spec `((?h . ,(substring hash 0 6))
+ (?H . ,hash)
+ (?a . ,(git-blame-get-info info 'author))
+ (?A . ,(git-blame-get-info info 'author-mail))
+ (?c . ,(git-blame-get-info info 'committer))
+ (?C . ,(git-blame-get-info info 'committer-mail))
+ (?s . ,(git-blame-get-info info 'summary)))))
+ (push ovl git-blame-overlays)
+ (overlay-put ovl 'git-blame info)
+ (overlay-put ovl 'help-echo
+ (format-spec git-blame-mouseover-format spec))
+ (if git-blame-use-colors
+ (overlay-put ovl 'face (list :background
+ (cdr (assq 'color (cdr info))))))
+ (overlay-put ovl 'line-prefix
+ (propertize (format-spec git-blame-prefix-format spec)
+ 'face 'git-blame-prefix-face)))))))
(defun git-blame-add-info (info key value)
(nconc info (list (cons (intern key) value))))
diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c
index 3140e40..0d54aa7 100644
--- a/contrib/examples/builtin-fetch--tool.c
+++ b/contrib/examples/builtin-fetch--tool.c
@@ -518,7 +518,7 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
filename = git_path("FETCH_HEAD");
fp = fopen(filename, "a");
if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
+ return error("cannot open %s: %s", filename, strerror(errno));
result = append_fetch_head(fp, argv[2], argv[3],
argv[4], argv[5],
argv[6], !!argv[7][0],
@@ -536,7 +536,7 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
filename = git_path("FETCH_HEAD");
fp = fopen(filename, "a");
if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
+ return error("cannot open %s: %s", filename, strerror(errno));
result = fetch_native_store(fp, argv[2], argv[3], argv[4],
verbose, force);
fclose(fp);
diff --git a/contrib/fast-import/git-p4.README b/contrib/fast-import/git-p4.README
new file mode 100644
index 0000000..cec5ecf
--- /dev/null
+++ b/contrib/fast-import/git-p4.README
@@ -0,0 +1,12 @@
+The git-p4 script moved to the top-level of the git source directory.
+
+Invoke it as any other git command, like "git p4 clone", for instance.
+
+Note that the top-level git-p4.py script is now the source. It is
+built using make to git-p4, which will be installed.
+
+Windows users can copy the git-p4.py source script directly, possibly
+invoking it through a batch file called "git-p4.bat" in the same folder.
+It should contain just one line:
+
+ @python "%~d0%~p0git-p4.py" %*
diff --git a/contrib/fast-import/git-p4.bat b/contrib/fast-import/git-p4.bat
deleted file mode 100644
index 9f97e88..0000000
--- a/contrib/fast-import/git-p4.bat
+++ /dev/null
@@ -1 +0,0 @@
-@python "%~d0%~p0git-p4" %*
diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt
deleted file mode 100644
index caa4bb3..0000000
--- a/contrib/fast-import/git-p4.txt
+++ /dev/null
@@ -1,251 +0,0 @@
-git-p4 - Perforce <-> Git converter using git-fast-import
-
-Usage
-=====
-
-git-p4 can be used in two different ways:
-
-1) To import changes from Perforce to a Git repository, using "git-p4 sync".
-
-2) To submit changes from Git back to Perforce, using "git-p4 submit".
-
-Importing
-=========
-
-Simply start with
-
- git-p4 clone //depot/path/project
-
-or
-
- git-p4 clone //depot/path/project myproject
-
-This will:
-
-1) Create an empty git repository in a subdirectory called "project" (or
-"myproject" with the second command)
-
-2) Import the head revision from the given Perforce path into a git branch
-called "p4" (remotes/p4 actually)
-
-3) Create a master branch based on it and check it out.
-
-If you want the entire history (not just the head revision) then you can simply
-append a "@all" to the depot path:
-
- git-p4 clone //depot/project/main@all myproject
-
-
-
-If you want more control you can also use the git-p4 sync command directly:
-
- mkdir repo-git
- cd repo-git
- git init
- git-p4 sync //path/in/your/perforce/depot
-
-This will import the current head revision of the specified depot path into a
-"remotes/p4/master" branch of your git repository. You can use the
---branch=mybranch option to import into a different branch.
-
-If you want to import the entire history of a given depot path simply use:
-
- git-p4 sync //path/in/depot@all
-
-
-Note:
-
-To achieve optimal compression you may want to run 'git repack -a -d -f' after
-a big import. This may take a while.
-
-Incremental Imports
-===================
-
-After an initial import you can continue to synchronize your git repository
-with newer changes from the Perforce depot by just calling
-
- git-p4 sync
-
-in your git repository. By default the "remotes/p4/master" branch is updated.
-
-Advanced Setup
-==============
-
-Suppose you have a periodically updated git repository somewhere, containing a
-complete import of a Perforce project. This repository can be cloned and used
-with git-p4. When updating the cloned repository with the "sync" command,
-git-p4 will try to fetch changes from the original repository first. The git
-protocol used with this is usually faster than importing from Perforce
-directly.
-
-This behaviour can be disabled by setting the "git-p4.syncFromOrigin" git
-configuration variable to "false".
-
-Updating
-========
-
-A common working pattern is to fetch the latest changes from the Perforce depot
-and merge them with local uncommitted changes. The recommended way is to use
-git's rebase mechanism to preserve linear history. git-p4 provides a convenient
-
- git-p4 rebase
-
-command that calls git-p4 sync followed by git rebase to rebase the current
-working branch.
-
-Submitting
-==========
-
-git-p4 has support for submitting changes from a git repository back to the
-Perforce depot. This requires a Perforce checkout separate from your git
-repository. To submit all changes that are in the current git branch but not in
-the "p4" branch (or "origin" if "p4" doesn't exist) simply call
-
- git-p4 submit
-
-in your git repository. If you want to submit changes in a specific branch that
-is not your current git branch you can also pass that as an argument:
-
- git-p4 submit mytopicbranch
-
-You can override the reference branch with the --origin=mysourcebranch option.
-
-The Perforce changelists will be created with the user who ran git-p4. If you
-use --preserve-user then git-p4 will attempt to create Perforce changelists
-with the Perforce user corresponding to the git commit author. You need to
-have sufficient permissions within Perforce, and the git users need to have
-Perforce accounts. Permissions can be granted using 'p4 protect'.
-
-If a submit fails you may have to "p4 resolve" and submit manually. You can
-continue importing the remaining changes with
-
- git-p4 submit --continue
-
-Example
-=======
-
-# Clone a repository
- git-p4 clone //depot/path/project
-# Enter the newly cloned directory
- cd project
-# Do some work...
- vi foo.h
-# ... and commit locally to gi
- git commit foo.h
-# In the meantime somebody submitted changes to the Perforce depot. Rebase your latest
-# changes against the latest changes in Perforce:
- git-p4 rebase
-# Submit your locally committed changes back to Perforce
- git-p4 submit
-# ... and synchronize with Perforce
- git-p4 rebase
-
-
-Configuration parameters
-========================
-
-git-p4.user ($P4USER)
-
-Allows you to specify the username to use to connect to the Perforce repository.
-
- git config [--global] git-p4.user public
-
-git-p4.password ($P4PASS)
-
-Allows you to specify the password to use to connect to the Perforce repository.
-Warning this password will be visible on the command-line invocation of the p4 binary.
-
- git config [--global] git-p4.password public1234
-
-git-p4.port ($P4PORT)
-
-Specify the port to be used to contact the Perforce server. As this will be passed
-directly to the p4 binary, it may be in the format host:port as well.
-
- git config [--global] git-p4.port codes.zimbra.com:2666
-
-git-p4.host ($P4HOST)
-
-Specify the host to contact for a Perforce repository.
-
- git config [--global] git-p4.host perforce.example.com
-
-git-p4.client ($P4CLIENT)
-
-Specify the client name to use
-
- git config [--global] git-p4.client public-view
-
-git-p4.allowSubmit
-
- git config [--global] git-p4.allowSubmit false
-
-git-p4.syncFromOrigin
-
-A useful setup may be that you have a periodically updated git repository
-somewhere that contains a complete import of a Perforce project. That git
-repository can be used to clone the working repository from and one would
-import from Perforce directly after cloning using git-p4. If the connection to
-the Perforce server is slow and the working repository hasn't been synced for a
-while it may be desirable to fetch changes from the origin git repository using
-the efficient git protocol. git-p4 supports this setup by calling "git fetch origin"
-by default if there is an origin branch. You can disable this using:
-
- git config [--global] git-p4.syncFromOrigin false
-
-git-p4.useclientspec
-
- git config [--global] git-p4.useclientspec false
-
-The P4CLIENT environment variable should be correctly set for p4 to be
-able to find the relevant client. This client spec will be used to
-both filter the files cloned by git and set the directory layout as
-specified in the client (this implies --keep-path style semantics).
-
-git-p4.skipSubmitModTimeCheck
-
- git config [--global] git-p4.skipSubmitModTimeCheck false
-
-If true, submit will not check if the p4 change template has been modified.
-
-git-p4.preserveUser
-
- git config [--global] git-p4.preserveUser false
-
-If true, attempt to preserve user names by modifying the p4 changelists. See
-the "--preserve-user" submit option.
-
-git-p4.allowMissingPerforceUsers
-
- git config [--global] git-p4.allowMissingP4Users false
-
-If git-p4 is setting the perforce user for a commit (--preserve-user) then
-if there is no perforce user corresponding to the git author, git-p4 will
-stop. With allowMissingPerforceUsers set to true, git-p4 will use the
-current user (i.e. the behavior without --preserve-user) and carry on with
-the perforce commit.
-
-git-p4.skipUserNameCheck
-
- git config [--global] git-p4.skipUserNameCheck false
-
-When submitting, git-p4 checks that the git commits are authored by the current
-p4 user, and warns if they are not. This disables the check.
-
-Implementation Details...
-=========================
-
-* Changesets from Perforce are imported using git fast-import.
-* The import does not require anything from the Perforce client view as it just uses
- "p4 print //depot/path/file#revision" to get the actual file contents.
-* Every imported changeset has a special [git-p4...] line at the
- end of the log message that gives information about the corresponding
- Perforce change number and is also used by git-p4 itself to find out
- where to continue importing when doing incremental imports.
- Basically when syncing it extracts the perforce change number of the
- latest commit in the "p4" branch and uses "p4 changes //depot/path/...@changenum,#head"
- to find out which changes need to be imported.
-* git-p4 submit uses "git rev-list" to pick the commits between the "p4" branch
- and the current branch.
- The commits themselves are applied using git diff/format-patch ... | git apply
-
diff --git a/contrib/git-jump/README b/contrib/git-jump/README
new file mode 100644
index 0000000..1cebc32
--- /dev/null
+++ b/contrib/git-jump/README
@@ -0,0 +1,92 @@
+git-jump
+========
+
+Git-jump is a script for helping you jump to "interesting" parts of your
+project in your editor. It works by outputting a set of interesting
+spots in the "quickfix" format, which editors like vim can use as a
+queue of places to visit (this feature is usually used to jump to errors
+produced by a compiler). For example, given a diff like this:
+
+------------------------------------
+diff --git a/foo.c b/foo.c
+index a655540..5a59044 100644
+--- a/foo.c
++++ b/foo.c
+@@ -1,3 +1,3 @@
+ int main(void) {
+- printf("hello word!\n");
++ printf("hello world!\n");
+ }
+-----------------------------------
+
+git-jump will feed this to the editor:
+
+-----------------------------------
+foo.c:2: printf("hello word!\n");
+-----------------------------------
+
+Obviously this trivial case isn't that interesting; you could just open
+`foo.c` yourself. But when you have many changes scattered across a
+project, you can use the editor's support to "jump" from point to point.
+
+Git-jump can generate three types of interesting lists:
+
+ 1. The beginning of any diff hunks.
+
+ 2. The beginning of any merge conflict markers.
+
+ 3. Any grep matches.
+
+
+Using git-jump
+--------------
+
+To use it, just drop git-jump in your PATH, and then invoke it like
+this:
+
+--------------------------------------------------
+# jump to changes not yet staged for commit
+git jump diff
+
+# jump to changes that are staged for commit; you can give
+# arbitrary diff options
+git jump diff --cached
+
+# jump to merge conflicts
+git jump merge
+
+# jump to all instances of foo_bar
+git jump grep foo_bar
+
+# same as above, but case-insensitive; you can give
+# arbitrary grep options
+git jump grep -i foo_bar
+--------------------------------------------------
+
+
+Related Programs
+----------------
+
+You can accomplish some of the same things with individual tools. For
+example, you can use `git mergetool` to start vimdiff on each unmerged
+file. `git jump merge` is for the vim-wielding luddite who just wants to
+jump straight to the conflict text with no fanfare.
+
+As of git v1.7.2, `git grep` knows the `--open-files-in-pager` option,
+which does something similar to `git jump grep`. However, it is limited
+to positioning the cursor to the correct line in only the first file,
+leaving you to locate subsequent hits in that file or other files using
+the editor or pager. By contrast, git-jump provides the editor with a
+complete list of files and line numbers for each match.
+
+
+Limitations
+-----------
+
+This scripts was written and tested with vim. Given that the quickfix
+format is the same as what gcc produces, I expect emacs users have a
+similar feature for iterating through the list, but I know nothing about
+how to activate it.
+
+The shell snippets to generate the quickfix lines will almost certainly
+choke on filenames with exotic characters (like newlines).
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
new file mode 100755
index 0000000..a33674e
--- /dev/null
+++ b/contrib/git-jump/git-jump
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+usage() {
+ cat <<\EOF
+usage: git jump <mode> [<args>]
+
+Jump to interesting elements in an editor.
+The <mode> parameter is one of:
+
+diff: elements are diff hunks. Arguments are given to diff.
+
+merge: elements are merge conflicts. Arguments are ignored.
+
+grep: elements are grep hits. Arguments are given to grep.
+EOF
+}
+
+open_editor() {
+ editor=`git var GIT_EDITOR`
+ eval "$editor -q \$1"
+}
+
+mode_diff() {
+ git diff --relative "$@" |
+ perl -ne '
+ if (m{^\+\+\+ b/(.*)}) { $file = $1; next }
+ defined($file) or next;
+ if (m/^@@ .*\+(\d+)/) { $line = $1; next }
+ defined($line) or next;
+ if (/^ /) { $line++; next }
+ if (/^[-+]\s*(.*)/) {
+ print "$file:$line: $1\n";
+ $line = undef;
+ }
+ '
+}
+
+mode_merge() {
+ git ls-files -u |
+ perl -pe 's/^.*?\t//' |
+ sort -u |
+ while IFS= read fn; do
+ grep -Hn '^<<<<<<<' "$fn"
+ done
+}
+
+# Grep -n generates nice quickfix-looking lines by itself,
+# but let's clean up extra whitespace, so they look better if the
+# editor shows them to us in the status bar.
+mode_grep() {
+ git grep -n "$@" |
+ perl -pe '
+ s/[ \t]+/ /g;
+ s/^ *//;
+ '
+}
+
+if test $# -lt 1; then
+ usage >&2
+ exit 1
+fi
+mode=$1; shift
+
+trap 'rm -f "$tmp"' 0 1 2 3 15
+tmp=`mktemp -t git-jump.XXXXXX` || exit 1
+type "mode_$mode" >/dev/null 2>&1 || { usage >&2; exit 1; }
+"mode_$mode" "$@" >"$tmp"
+test -s "$tmp" || exit 0
+open_editor "$tmp"
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index 21989fc..01af9df 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -11,11 +11,11 @@
# 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:
+# /usr/share/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
+# ln -sf /usr/share/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
@@ -60,6 +60,11 @@
# email body. If not specified, there is no limit.
# Lines beyond the limit are suppressed and counted, and a final
# line is added indicating the number of suppressed lines.
+# hooks.diffopts
+# Alternate options for the git diff-tree invocation that shows changes.
+# Default is "--stat --summary --find-copies-harder". Add -p to those
+# options to include a unified diff of changes in addition to the usual
+# summary output.
#
# Notes
# -----
@@ -80,7 +85,6 @@ prep_for_email()
oldrev=$(git rev-parse $1)
newrev=$(git rev-parse $2)
refname="$3"
- maxlines=$4
# --- Interpret
# 0000->1234 (create)
@@ -446,7 +450,7 @@ generate_update_branch_email()
# non-fast-forward updates.
echo ""
echo "Summary of changes:"
- git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev
+ git diff-tree $diffopts $oldrev..$newrev
}
#
@@ -456,7 +460,7 @@ generate_delete_branch_email()
{
echo " was $oldrev"
echo ""
- echo $LOGEND
+ echo $LOGBEGIN
git show -s --pretty=oneline $oldrev
echo $LOGEND
}
@@ -556,7 +560,7 @@ generate_delete_atag_email()
{
echo " was $oldrev"
echo ""
- echo $LOGEND
+ echo $LOGBEGIN
git show -s --pretty=oneline $oldrev
echo $LOGEND
}
@@ -621,7 +625,7 @@ generate_delete_general_email()
{
echo " was $oldrev"
echo ""
- echo $LOGEND
+ echo $LOGBEGIN
git show -s --pretty=oneline $oldrev
echo $LOGEND
}
@@ -723,6 +727,8 @@ envelopesender=$(git config hooks.envelopesender)
emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
custom_showrev=$(git config hooks.showrev)
maxlines=$(git config hooks.emailmaxlines)
+diffopts=$(git config hooks.diffopts)
+: ${diffopts:="--stat --summary --find-copies-harder"}
# --- Main loop
# Allow dual mode: run from the command line just like the update hook, or
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
new file mode 100755
index 0000000..c07b4f0
--- /dev/null
+++ b/contrib/mw-to-git/git-remote-mediawiki
@@ -0,0 +1,904 @@
+#! /usr/bin/perl
+
+# Copyright (C) 2011
+# Jérémie Nikaes <jeremie.nikaes@ensimag.imag.fr>
+# Arnaud Lacurie <arnaud.lacurie@ensimag.imag.fr>
+# Claire Fousse <claire.fousse@ensimag.imag.fr>
+# David Amouyal <david.amouyal@ensimag.imag.fr>
+# Matthieu Moy <matthieu.moy@grenoble-inp.fr>
+# License: GPL v2 or later
+
+# Gateway between Git and MediaWiki.
+# https://github.com/Bibzball/Git-Mediawiki/wiki
+#
+# Known limitations:
+#
+# - Only wiki pages are managed, no support for [[File:...]]
+# attachments.
+#
+# - Poor performance in the best case: it takes forever to check
+# whether we're up-to-date (on fetch or push) or to fetch a few
+# revisions from a large wiki, because we use exclusively a
+# page-based synchronization. We could switch to a wiki-wide
+# synchronization when the synchronization involves few revisions
+# but the wiki is large.
+#
+# - Git renames could be turned into MediaWiki renames (see TODO
+# below)
+#
+# - No way to import "one page, and all pages included in it"
+#
+# - Multiple remote MediaWikis have not been very well tested.
+
+use strict;
+use MediaWiki::API;
+use DateTime::Format::ISO8601;
+use encoding 'utf8';
+
+# use encoding 'utf8' doesn't change STDERROR
+# but we're going to output UTF-8 filenames to STDERR
+binmode STDERR, ":utf8";
+
+use URI::Escape;
+use IPC::Open2;
+
+use warnings;
+
+# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
+use constant SLASH_REPLACEMENT => "%2F";
+
+# It's not always possible to delete pages (may require some
+# priviledges). Deleted pages are replaced with this content.
+use constant DELETED_CONTENT => "[[Category:Deleted]]\n";
+
+# It's not possible to create empty pages. New empty files in Git are
+# sent with this content instead.
+use constant EMPTY_CONTENT => "<!-- empty page -->\n";
+
+# used to reflect file creation or deletion in diff.
+use constant NULL_SHA1 => "0000000000000000000000000000000000000000";
+
+my $remotename = $ARGV[0];
+my $url = $ARGV[1];
+
+# Accept both space-separated and multiple keys in config file.
+# Spaces should be written as _ anyway because we'll use chomp.
+my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".pages"));
+chomp(@tracked_pages);
+
+# Just like @tracked_pages, but for MediaWiki categories.
+my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories"));
+chomp(@tracked_categories);
+
+my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin");
+# Note: mwPassword is discourraged. Use the credential system instead.
+my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword");
+my $wiki_domain = run_git("config --get remote.". $remotename .".mwDomain");
+chomp($wiki_login);
+chomp($wiki_passwd);
+chomp($wiki_domain);
+
+# Import only last revisions (both for clone and fetch)
+my $shallow_import = run_git("config --get --bool remote.". $remotename .".shallow");
+chomp($shallow_import);
+$shallow_import = ($shallow_import eq "true");
+
+# Dumb push: don't update notes and mediawiki ref to reflect the last push.
+#
+# Configurable with mediawiki.dumbPush, or per-remote with
+# remote.<remotename>.dumbPush.
+#
+# This means the user will have to re-import the just-pushed
+# revisions. On the other hand, this means that the Git revisions
+# corresponding to MediaWiki revisions are all imported from the wiki,
+# regardless of whether they were initially created in Git or from the
+# web interface, hence all users will get the same history (i.e. if
+# the push from Git to MediaWiki loses some information, everybody
+# will get the history with information lost). If the import is
+# deterministic, this means everybody gets the same sha1 for each
+# MediaWiki revision.
+my $dumb_push = run_git("config --get --bool remote.$remotename.dumbPush");
+unless ($dumb_push) {
+ $dumb_push = run_git("config --get --bool mediawiki.dumbPush");
+}
+chomp($dumb_push);
+$dumb_push = ($dumb_push eq "true");
+
+my $wiki_name = $url;
+$wiki_name =~ s/[^\/]*:\/\///;
+# If URL is like http://user:password@example.com/, we clearly don't
+# want the password in $wiki_name. While we're there, also remove user
+# and '@' sign, to avoid author like MWUser@HTTPUser@host.com
+$wiki_name =~ s/^.*@//;
+
+# Commands parser
+my $entry;
+my @cmd;
+while (<STDIN>) {
+ chomp;
+ @cmd = split(/ /);
+ if (defined($cmd[0])) {
+ # Line not blank
+ if ($cmd[0] eq "capabilities") {
+ die("Too many arguments for capabilities") unless (!defined($cmd[1]));
+ mw_capabilities();
+ } elsif ($cmd[0] eq "list") {
+ die("Too many arguments for list") unless (!defined($cmd[2]));
+ mw_list($cmd[1]);
+ } elsif ($cmd[0] eq "import") {
+ die("Invalid arguments for import") unless ($cmd[1] ne "" && !defined($cmd[2]));
+ mw_import($cmd[1]);
+ } elsif ($cmd[0] eq "option") {
+ die("Too many arguments for option") unless ($cmd[1] ne "" && $cmd[2] ne "" && !defined($cmd[3]));
+ mw_option($cmd[1],$cmd[2]);
+ } elsif ($cmd[0] eq "push") {
+ mw_push($cmd[1]);
+ } else {
+ print STDERR "Unknown command. Aborting...\n";
+ last;
+ }
+ } else {
+ # blank line: we should terminate
+ last;
+ }
+
+ BEGIN { $| = 1 } # flush STDOUT, to make sure the previous
+ # command is fully processed.
+}
+
+########################## Functions ##############################
+
+## credential API management (generic functions)
+
+sub credential_from_url {
+ my $url = shift;
+ my $parsed = URI->new($url);
+ my %credential;
+
+ if ($parsed->scheme) {
+ $credential{protocol} = $parsed->scheme;
+ }
+ if ($parsed->host) {
+ $credential{host} = $parsed->host;
+ }
+ if ($parsed->path) {
+ $credential{path} = $parsed->path;
+ }
+ if ($parsed->userinfo) {
+ if ($parsed->userinfo =~ /([^:]*):(.*)/) {
+ $credential{username} = $1;
+ $credential{password} = $2;
+ } else {
+ $credential{username} = $parsed->userinfo;
+ }
+ }
+
+ return %credential;
+}
+
+sub credential_read {
+ my %credential;
+ my $reader = shift;
+ my $op = shift;
+ while (<$reader>) {
+ my ($key, $value) = /([^=]*)=(.*)/;
+ if (not defined $key) {
+ die "ERROR receiving response from git credential $op:\n$_\n";
+ }
+ $credential{$key} = $value;
+ }
+ return %credential;
+}
+
+sub credential_write {
+ my $credential = shift;
+ my $writer = shift;
+ while (my ($key, $value) = each(%$credential) ) {
+ if ($value) {
+ print $writer "$key=$value\n";
+ }
+ }
+}
+
+sub credential_run {
+ my $op = shift;
+ my $credential = shift;
+ my $pid = open2(my $reader, my $writer, "git credential $op");
+ credential_write($credential, $writer);
+ print $writer "\n";
+ close($writer);
+
+ if ($op eq "fill") {
+ %$credential = credential_read($reader, $op);
+ } else {
+ if (<$reader>) {
+ die "ERROR while running git credential $op:\n$_";
+ }
+ }
+ close($reader);
+ waitpid($pid, 0);
+ my $child_exit_status = $? >> 8;
+ if ($child_exit_status != 0) {
+ die "'git credential $op' failed with code $child_exit_status.";
+ }
+}
+
+# MediaWiki API instance, created lazily.
+my $mediawiki;
+
+sub mw_connect_maybe {
+ if ($mediawiki) {
+ return;
+ }
+ $mediawiki = MediaWiki::API->new;
+ $mediawiki->{config}->{api_url} = "$url/api.php";
+ if ($wiki_login) {
+ my %credential = credential_from_url($url);
+ $credential{username} = $wiki_login;
+ $credential{password} = $wiki_passwd;
+ credential_run("fill", \%credential);
+ my $request = {lgname => $credential{username},
+ lgpassword => $credential{password},
+ lgdomain => $wiki_domain};
+ if ($mediawiki->login($request)) {
+ credential_run("approve", \%credential);
+ print STDERR "Logged in mediawiki user \"$credential{username}\".\n";
+ } else {
+ print STDERR "Failed to log in mediawiki user \"$credential{username}\" on $url\n";
+ print STDERR " (error " .
+ $mediawiki->{error}->{code} . ': ' .
+ $mediawiki->{error}->{details} . ")\n";
+ credential_run("reject", \%credential);
+ exit 1;
+ }
+ }
+}
+
+sub get_mw_first_pages {
+ my $some_pages = shift;
+ my @some_pages = @{$some_pages};
+
+ my $pages = shift;
+
+ # pattern 'page1|page2|...' required by the API
+ my $titles = join('|', @some_pages);
+
+ my $mw_pages = $mediawiki->api({
+ action => 'query',
+ titles => $titles,
+ });
+ if (!defined($mw_pages)) {
+ print STDERR "fatal: could not query the list of wiki pages.\n";
+ print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+ print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+ exit 1;
+ }
+ while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) {
+ if ($id < 0) {
+ print STDERR "Warning: page $page->{title} not found on wiki\n";
+ } else {
+ $pages->{$page->{title}} = $page;
+ }
+ }
+}
+
+sub get_mw_pages {
+ mw_connect_maybe();
+
+ my %pages; # hash on page titles to avoid duplicates
+ my $user_defined;
+ if (@tracked_pages) {
+ $user_defined = 1;
+ # The user provided a list of pages titles, but we
+ # still need to query the API to get the page IDs.
+
+ my @some_pages = @tracked_pages;
+ while (@some_pages) {
+ my $last = 50;
+ if ($#some_pages < $last) {
+ $last = $#some_pages;
+ }
+ my @slice = @some_pages[0..$last];
+ get_mw_first_pages(\@slice, \%pages);
+ @some_pages = @some_pages[51..$#some_pages];
+ }
+ }
+ if (@tracked_categories) {
+ $user_defined = 1;
+ foreach my $category (@tracked_categories) {
+ if (index($category, ':') < 0) {
+ # Mediawiki requires the Category
+ # prefix, but let's not force the user
+ # to specify it.
+ $category = "Category:" . $category;
+ }
+ my $mw_pages = $mediawiki->list( {
+ action => 'query',
+ list => 'categorymembers',
+ cmtitle => $category,
+ cmlimit => 'max' } )
+ || die $mediawiki->{error}->{code} . ': ' . $mediawiki->{error}->{details};
+ foreach my $page (@{$mw_pages}) {
+ $pages{$page->{title}} = $page;
+ }
+ }
+ }
+ if (!$user_defined) {
+ # No user-provided list, get the list of pages from
+ # the API.
+ my $mw_pages = $mediawiki->list({
+ action => 'query',
+ list => 'allpages',
+ aplimit => 500,
+ });
+ if (!defined($mw_pages)) {
+ print STDERR "fatal: could not get the list of wiki pages.\n";
+ print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+ print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+ exit 1;
+ }
+ foreach my $page (@{$mw_pages}) {
+ $pages{$page->{title}} = $page;
+ }
+ }
+ return values(%pages);
+}
+
+sub run_git {
+ open(my $git, "-|:encoding(UTF-8)", "git " . $_[0]);
+ my $res = do { local $/; <$git> };
+ close($git);
+
+ return $res;
+}
+
+
+sub get_last_local_revision {
+ # Get note regarding last mediawiki revision
+ my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null");
+ my @note_info = split(/ /, $note);
+
+ my $lastrevision_number;
+ if (!(defined($note_info[0]) && $note_info[0] eq "mediawiki_revision:")) {
+ print STDERR "No previous mediawiki revision found";
+ $lastrevision_number = 0;
+ } else {
+ # Notes are formatted : mediawiki_revision: #number
+ $lastrevision_number = $note_info[1];
+ chomp($lastrevision_number);
+ print STDERR "Last local mediawiki revision found is $lastrevision_number";
+ }
+ return $lastrevision_number;
+}
+
+# Remember the timestamp corresponding to a revision id.
+my %basetimestamps;
+
+sub get_last_remote_revision {
+ mw_connect_maybe();
+
+ my @pages = get_mw_pages();
+
+ my $max_rev_num = 0;
+
+ foreach my $page (@pages) {
+ my $id = $page->{pageid};
+
+ my $query = {
+ action => 'query',
+ prop => 'revisions',
+ rvprop => 'ids|timestamp',
+ pageids => $id,
+ };
+
+ my $result = $mediawiki->api($query);
+
+ my $lastrev = pop(@{$result->{query}->{pages}->{$id}->{revisions}});
+
+ $basetimestamps{$lastrev->{revid}} = $lastrev->{timestamp};
+
+ $max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num);
+ }
+
+ print STDERR "Last remote revision found is $max_rev_num.\n";
+ return $max_rev_num;
+}
+
+# Clean content before sending it to MediaWiki
+sub mediawiki_clean {
+ my $string = shift;
+ my $page_created = shift;
+ # Mediawiki does not allow blank space at the end of a page and ends with a single \n.
+ # This function right trims a string and adds a \n at the end to follow this rule
+ $string =~ s/\s+$//;
+ if ($string eq "" && $page_created) {
+ # Creating empty pages is forbidden.
+ $string = EMPTY_CONTENT;
+ }
+ return $string."\n";
+}
+
+# Filter applied on MediaWiki data before adding them to Git
+sub mediawiki_smudge {
+ my $string = shift;
+ if ($string eq EMPTY_CONTENT) {
+ $string = "";
+ }
+ # This \n is important. This is due to mediawiki's way to handle end of files.
+ return $string."\n";
+}
+
+sub mediawiki_clean_filename {
+ my $filename = shift;
+ $filename =~ s/@{[SLASH_REPLACEMENT]}/\//g;
+ # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded.
+ # Do a variant of URL-encoding, i.e. looks like URL-encoding,
+ # but with _ added to prevent MediaWiki from thinking this is
+ # an actual special character.
+ $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge;
+ # If we use the uri escape before
+ # we should unescape here, before anything
+
+ return $filename;
+}
+
+sub mediawiki_smudge_filename {
+ my $filename = shift;
+ $filename =~ s/\//@{[SLASH_REPLACEMENT]}/g;
+ $filename =~ s/ /_/g;
+ # Decode forbidden characters encoded in mediawiki_clean_filename
+ $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf("%c", hex($1))/ge;
+ return $filename;
+}
+
+sub literal_data {
+ my ($content) = @_;
+ print STDOUT "data ", bytes::length($content), "\n", $content;
+}
+
+sub mw_capabilities {
+ # Revisions are imported to the private namespace
+ # refs/mediawiki/$remotename/ by the helper and fetched into
+ # refs/remotes/$remotename later by fetch.
+ print STDOUT "refspec refs/heads/*:refs/mediawiki/$remotename/*\n";
+ print STDOUT "import\n";
+ print STDOUT "list\n";
+ print STDOUT "push\n";
+ print STDOUT "\n";
+}
+
+sub mw_list {
+ # MediaWiki do not have branches, we consider one branch arbitrarily
+ # called master, and HEAD pointing to it.
+ print STDOUT "? refs/heads/master\n";
+ print STDOUT "\@refs/heads/master HEAD\n";
+ print STDOUT "\n";
+}
+
+sub mw_option {
+ print STDERR "remote-helper command 'option $_[0]' not yet implemented\n";
+ print STDOUT "unsupported\n";
+}
+
+sub fetch_mw_revisions_for_page {
+ my $page = shift;
+ my $id = shift;
+ my $fetch_from = shift;
+ my @page_revs = ();
+ my $query = {
+ action => 'query',
+ prop => 'revisions',
+ rvprop => 'ids',
+ rvdir => 'newer',
+ rvstartid => $fetch_from,
+ rvlimit => 500,
+ pageids => $id,
+ };
+
+ my $revnum = 0;
+ # Get 500 revisions at a time due to the mediawiki api limit
+ while (1) {
+ my $result = $mediawiki->api($query);
+
+ # Parse each of those 500 revisions
+ foreach my $revision (@{$result->{query}->{pages}->{$id}->{revisions}}) {
+ my $page_rev_ids;
+ $page_rev_ids->{pageid} = $page->{pageid};
+ $page_rev_ids->{revid} = $revision->{revid};
+ push(@page_revs, $page_rev_ids);
+ $revnum++;
+ }
+ last unless $result->{'query-continue'};
+ $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid};
+ }
+ if ($shallow_import && @page_revs) {
+ print STDERR " Found 1 revision (shallow import).\n";
+ @page_revs = sort {$b->{revid} <=> $a->{revid}} (@page_revs);
+ return $page_revs[0];
+ }
+ print STDERR " Found ", $revnum, " revision(s).\n";
+ return @page_revs;
+}
+
+sub fetch_mw_revisions {
+ my $pages = shift; my @pages = @{$pages};
+ my $fetch_from = shift;
+
+ my @revisions = ();
+ my $n = 1;
+ foreach my $page (@pages) {
+ my $id = $page->{pageid};
+
+ print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n";
+ $n++;
+ my @page_revs = fetch_mw_revisions_for_page($page, $id, $fetch_from);
+ @revisions = (@page_revs, @revisions);
+ }
+
+ return ($n, @revisions);
+}
+
+sub import_file_revision {
+ my $commit = shift;
+ my %commit = %{$commit};
+ my $full_import = shift;
+ my $n = shift;
+
+ my $title = $commit{title};
+ my $comment = $commit{comment};
+ my $content = $commit{content};
+ my $author = $commit{author};
+ my $date = $commit{date};
+
+ print STDOUT "commit refs/mediawiki/$remotename/master\n";
+ print STDOUT "mark :$n\n";
+ print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+ literal_data($comment);
+
+ # If it's not a clone, we need to know where to start from
+ if (!$full_import && $n == 1) {
+ print STDOUT "from refs/mediawiki/$remotename/master^0\n";
+ }
+ if ($content ne DELETED_CONTENT) {
+ print STDOUT "M 644 inline $title.mw\n";
+ literal_data($content);
+ print STDOUT "\n\n";
+ } else {
+ print STDOUT "D $title.mw\n";
+ }
+
+ # mediawiki revision number in the git note
+ if ($full_import && $n == 1) {
+ print STDOUT "reset refs/notes/$remotename/mediawiki\n";
+ }
+ print STDOUT "commit refs/notes/$remotename/mediawiki\n";
+ print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+ literal_data("Note added by git-mediawiki during import");
+ if (!$full_import && $n == 1) {
+ print STDOUT "from refs/notes/$remotename/mediawiki^0\n";
+ }
+ print STDOUT "N inline :$n\n";
+ literal_data("mediawiki_revision: " . $commit{mw_revision});
+ print STDOUT "\n\n";
+}
+
+# parse a sequence of
+# <cmd> <arg1>
+# <cmd> <arg2>
+# \n
+# (like batch sequence of import and sequence of push statements)
+sub get_more_refs {
+ my $cmd = shift;
+ my @refs;
+ while (1) {
+ my $line = <STDIN>;
+ if ($line =~ m/^$cmd (.*)$/) {
+ push(@refs, $1);
+ } elsif ($line eq "\n") {
+ return @refs;
+ } else {
+ die("Invalid command in a '$cmd' batch: ". $_);
+ }
+ }
+}
+
+sub mw_import {
+ # multiple import commands can follow each other.
+ my @refs = (shift, get_more_refs("import"));
+ foreach my $ref (@refs) {
+ mw_import_ref($ref);
+ }
+ print STDOUT "done\n";
+}
+
+sub mw_import_ref {
+ my $ref = shift;
+ # The remote helper will call "import HEAD" and
+ # "import refs/heads/master".
+ # Since HEAD is a symbolic ref to master (by convention,
+ # followed by the output of the command "list" that we gave),
+ # we don't need to do anything in this case.
+ if ($ref eq "HEAD") {
+ return;
+ }
+
+ mw_connect_maybe();
+
+ my @pages = get_mw_pages();
+
+ print STDERR "Searching revisions...\n";
+ my $last_local = get_last_local_revision();
+ my $fetch_from = $last_local + 1;
+ if ($fetch_from == 1) {
+ print STDERR ", fetching from beginning.\n";
+ } else {
+ print STDERR ", fetching from here.\n";
+ }
+ my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from);
+
+ # Creation of the fast-import stream
+ print STDERR "Fetching & writing export data...\n";
+
+ $n = 0;
+ my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
+
+ foreach my $pagerevid (sort {$a->{revid} <=> $b->{revid}} @revisions) {
+ # fetch the content of the pages
+ my $query = {
+ action => 'query',
+ prop => 'revisions',
+ rvprop => 'content|timestamp|comment|user|ids',
+ revids => $pagerevid->{revid},
+ };
+
+ my $result = $mediawiki->api($query);
+
+ my $rev = pop(@{$result->{query}->{pages}->{$pagerevid->{pageid}}->{revisions}});
+
+ $n++;
+
+ my %commit;
+ $commit{author} = $rev->{user} || 'Anonymous';
+ $commit{comment} = $rev->{comment} || '*Empty MediaWiki Message*';
+ $commit{title} = mediawiki_smudge_filename(
+ $result->{query}->{pages}->{$pagerevid->{pageid}}->{title}
+ );
+ $commit{mw_revision} = $pagerevid->{revid};
+ $commit{content} = mediawiki_smudge($rev->{'*'});
+
+ if (!defined($rev->{timestamp})) {
+ $last_timestamp++;
+ } else {
+ $last_timestamp = $rev->{timestamp};
+ }
+ $commit{date} = DateTime::Format::ISO8601->parse_datetime($last_timestamp);
+
+ print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
+
+ import_file_revision(\%commit, ($fetch_from == 1), $n);
+ }
+
+ if ($fetch_from == 1 && $n == 0) {
+ print STDERR "You appear to have cloned an empty MediaWiki.\n";
+ # Something has to be done remote-helper side. If nothing is done, an error is
+ # thrown saying that HEAD is refering to unknown object 0000000000000000000
+ # and the clone fails.
+ }
+}
+
+sub error_non_fast_forward {
+ my $advice = run_git("config --bool advice.pushNonFastForward");
+ chomp($advice);
+ if ($advice ne "false") {
+ # Native git-push would show this after the summary.
+ # We can't ask it to display it cleanly, so print it
+ # ourselves before.
+ print STDERR "To prevent you from losing history, non-fast-forward updates were rejected\n";
+ print STDERR "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n";
+ print STDERR "'Note about fast-forwards' section of 'git push --help' for details.\n";
+ }
+ print STDOUT "error $_[0] \"non-fast-forward\"\n";
+ return 0;
+}
+
+sub mw_push_file {
+ my $diff_info = shift;
+ # $diff_info contains a string in this format:
+ # 100644 100644 <sha1_of_blob_before_commit> <sha1_of_blob_now> <status>
+ my @diff_info_split = split(/[ \t]/, $diff_info);
+
+ # Filename, including .mw extension
+ my $complete_file_name = shift;
+ # Commit message
+ my $summary = shift;
+ # MediaWiki revision number. Keep the previous one by default,
+ # in case there's no edit to perform.
+ my $newrevid = shift;
+
+ my $new_sha1 = $diff_info_split[3];
+ my $old_sha1 = $diff_info_split[2];
+ my $page_created = ($old_sha1 eq NULL_SHA1);
+ my $page_deleted = ($new_sha1 eq NULL_SHA1);
+ $complete_file_name = mediawiki_clean_filename($complete_file_name);
+
+ if (substr($complete_file_name,-3) eq ".mw") {
+ my $title = substr($complete_file_name,0,-3);
+
+ my $file_content;
+ if ($page_deleted) {
+ # Deleting a page usually requires
+ # special priviledges. A common
+ # convention is to replace the page
+ # with this content instead:
+ $file_content = DELETED_CONTENT;
+ } else {
+ $file_content = run_git("cat-file blob $new_sha1");
+ }
+
+ mw_connect_maybe();
+
+ my $result = $mediawiki->edit( {
+ action => 'edit',
+ summary => $summary,
+ title => $title,
+ basetimestamp => $basetimestamps{$newrevid},
+ text => mediawiki_clean($file_content, $page_created),
+ }, {
+ skip_encoding => 1 # Helps with names with accentuated characters
+ });
+ if (!$result) {
+ if ($mediawiki->{error}->{code} == 3) {
+ # edit conflicts, considered as non-fast-forward
+ print STDERR 'Warning: Error ' .
+ $mediawiki->{error}->{code} .
+ ' from mediwiki: ' . $mediawiki->{error}->{details} .
+ ".\n";
+ return ($newrevid, "non-fast-forward");
+ } else {
+ # Other errors. Shouldn't happen => just die()
+ die 'Fatal: Error ' .
+ $mediawiki->{error}->{code} .
+ ' from mediwiki: ' . $mediawiki->{error}->{details};
+ }
+ }
+ $newrevid = $result->{edit}->{newrevid};
+ print STDERR "Pushed file: $new_sha1 - $title\n";
+ } else {
+ print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
+ }
+ return ($newrevid, "ok");
+}
+
+sub mw_push {
+ # multiple push statements can follow each other
+ my @refsspecs = (shift, get_more_refs("push"));
+ my $pushed;
+ for my $refspec (@refsspecs) {
+ my ($force, $local, $remote) = $refspec =~ /^(\+)?([^:]*):([^:]*)$/
+ or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>");
+ if ($force) {
+ print STDERR "Warning: forced push not allowed on a MediaWiki.\n";
+ }
+ if ($local eq "") {
+ print STDERR "Cannot delete remote branch on a MediaWiki\n";
+ print STDOUT "error $remote cannot delete\n";
+ next;
+ }
+ if ($remote ne "refs/heads/master") {
+ print STDERR "Only push to the branch 'master' is supported on a MediaWiki\n";
+ print STDOUT "error $remote only master allowed\n";
+ next;
+ }
+ if (mw_push_revision($local, $remote)) {
+ $pushed = 1;
+ }
+ }
+
+ # Notify Git that the push is done
+ print STDOUT "\n";
+
+ if ($pushed && $dumb_push) {
+ print STDERR "Just pushed some revisions to MediaWiki.\n";
+ print STDERR "The pushed revisions now have to be re-imported, and your current branch\n";
+ print STDERR "needs to be updated with these re-imported commits. You can do this with\n";
+ print STDERR "\n";
+ print STDERR " git pull --rebase\n";
+ print STDERR "\n";
+ }
+}
+
+sub mw_push_revision {
+ my $local = shift;
+ my $remote = shift; # actually, this has to be "refs/heads/master" at this point.
+ my $last_local_revid = get_last_local_revision();
+ print STDERR ".\n"; # Finish sentence started by get_last_local_revision()
+ my $last_remote_revid = get_last_remote_revision();
+ my $mw_revision = $last_remote_revid;
+
+ # Get sha1 of commit pointed by local HEAD
+ my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1);
+ # Get sha1 of commit pointed by remotes/$remotename/master
+ my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/$remotename/master 2>/dev/null");
+ chomp($remoteorigin_sha1);
+
+ if ($last_local_revid > 0 &&
+ $last_local_revid < $last_remote_revid) {
+ return error_non_fast_forward($remote);
+ }
+
+ if ($HEAD_sha1 eq $remoteorigin_sha1) {
+ # nothing to push
+ return 0;
+ }
+
+ # Get every commit in between HEAD and refs/remotes/origin/master,
+ # including HEAD and refs/remotes/origin/master
+ my @commit_pairs = ();
+ if ($last_local_revid > 0) {
+ my $parsed_sha1 = $remoteorigin_sha1;
+ # Find a path from last MediaWiki commit to pushed commit
+ while ($parsed_sha1 ne $HEAD_sha1) {
+ my @commit_info = grep(/^$parsed_sha1/, split(/\n/, run_git("rev-list --children $local")));
+ if (!@commit_info) {
+ return error_non_fast_forward($remote);
+ }
+ my @commit_info_split = split(/ |\n/, $commit_info[0]);
+ # $commit_info_split[1] is the sha1 of the commit to export
+ # $commit_info_split[0] is the sha1 of its direct child
+ push(@commit_pairs, \@commit_info_split);
+ $parsed_sha1 = $commit_info_split[1];
+ }
+ } else {
+ # No remote mediawiki revision. Export the whole
+ # history (linearized with --first-parent)
+ print STDERR "Warning: no common ancestor, pushing complete history\n";
+ my $history = run_git("rev-list --first-parent --children $local");
+ my @history = split('\n', $history);
+ @history = @history[1..$#history];
+ foreach my $line (reverse @history) {
+ my @commit_info_split = split(/ |\n/, $line);
+ push(@commit_pairs, \@commit_info_split);
+ }
+ }
+
+ foreach my $commit_info_split (@commit_pairs) {
+ my $sha1_child = @{$commit_info_split}[0];
+ my $sha1_commit = @{$commit_info_split}[1];
+ my $diff_infos = run_git("diff-tree -r --raw -z $sha1_child $sha1_commit");
+ # TODO: we could detect rename, and encode them with a #redirect on the wiki.
+ # TODO: for now, it's just a delete+add
+ my @diff_info_list = split(/\0/, $diff_infos);
+ # Keep the first line of the commit message as mediawiki comment for the revision
+ my $commit_msg = (split(/\n/, run_git("show --pretty=format:\"%s\" $sha1_commit")))[0];
+ chomp($commit_msg);
+ # Push every blob
+ while (@diff_info_list) {
+ my $status;
+ # git diff-tree -z gives an output like
+ # <metadata>\0<filename1>\0
+ # <metadata>\0<filename2>\0
+ # and we've split on \0.
+ my $info = shift(@diff_info_list);
+ my $file = shift(@diff_info_list);
+ ($mw_revision, $status) = mw_push_file($info, $file, $commit_msg, $mw_revision);
+ if ($status eq "non-fast-forward") {
+ # we may already have sent part of the
+ # commit to MediaWiki, but it's too
+ # late to cancel it. Stop the push in
+ # the middle, but still give an
+ # accurate error message.
+ return error_non_fast_forward($remote);
+ }
+ if ($status ne "ok") {
+ die("Unknown error from mw_push_file()");
+ }
+ }
+ unless ($dumb_push) {
+ run_git("notes --ref=$remotename/mediawiki add -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
+ run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
+ }
+ }
+
+ print STDOUT "ok $remote\n";
+ return 1;
+}
diff --git a/contrib/mw-to-git/git-remote-mediawiki.txt b/contrib/mw-to-git/git-remote-mediawiki.txt
new file mode 100644
index 0000000..4d211f5
--- /dev/null
+++ b/contrib/mw-to-git/git-remote-mediawiki.txt
@@ -0,0 +1,7 @@
+Git-Mediawiki is a project which aims the creation of a gate
+between git and mediawiki, allowing git users to push and pull
+objects from mediawiki just as one would do with a classic git
+repository thanks to remote-helpers.
+
+For more information, visit the wiki at
+https://github.com/Bibzball/Git-Mediawiki/wiki
diff --git a/contrib/persistent-https/LICENSE b/contrib/persistent-https/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/contrib/persistent-https/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/contrib/persistent-https/Makefile b/contrib/persistent-https/Makefile
new file mode 100644
index 0000000..92baa3b
--- /dev/null
+++ b/contrib/persistent-https/Makefile
@@ -0,0 +1,38 @@
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+BUILD_LABEL=$(shell date +"%s")
+TAR_OUT=$(shell go env GOOS)_$(shell go env GOARCH).tar.gz
+
+all: git-remote-persistent-https git-remote-persistent-https--proxy \
+ git-remote-persistent-http
+
+git-remote-persistent-https--proxy: git-remote-persistent-https
+ ln -f -s git-remote-persistent-https git-remote-persistent-https--proxy
+
+git-remote-persistent-http: git-remote-persistent-https
+ ln -f -s git-remote-persistent-https git-remote-persistent-http
+
+git-remote-persistent-https:
+ go build -o git-remote-persistent-https \
+ -ldflags "-X main._BUILD_EMBED_LABEL $(BUILD_LABEL)"
+
+clean:
+ rm -f git-remote-persistent-http* *.tar.gz
+
+tar: clean all
+ @chmod 555 git-remote-persistent-https
+ @tar -czf $(TAR_OUT) git-remote-persistent-http* README LICENSE
+ @echo
+ @echo "Created $(TAR_OUT)"
diff --git a/contrib/persistent-https/README b/contrib/persistent-https/README
new file mode 100644
index 0000000..f784dd2
--- /dev/null
+++ b/contrib/persistent-https/README
@@ -0,0 +1,62 @@
+git-remote-persistent-https
+
+The git-remote-persistent-https binary speeds up SSL operations
+by running a daemon job (git-remote-persistent-https--proxy) that
+keeps a connection open to a server.
+
+
+PRE-BUILT BINARIES
+
+Darwin amd64:
+https://commondatastorage.googleapis.com/git-remote-persistent-https/darwin_amd64.tar.gz
+
+Linux amd64:
+https://commondatastorage.googleapis.com/git-remote-persistent-https/linux_amd64.tar.gz
+
+
+INSTALLING
+
+Move all of the git-remote-persistent-http* binaries to a directory
+in PATH.
+
+
+USAGE
+
+HTTPS requests can be delegated to the proxy by using the
+"persistent-https" scheme, e.g.
+
+git clone persistent-https://kernel.googlesource.com/pub/scm/git/git
+
+Likewise, .gitconfig can be updated as follows to rewrite https urls
+to use persistent-https:
+
+[url "persistent-https"]
+ insteadof = https
+[url "persistent-http"]
+ insteadof = http
+
+
+#####################################################################
+# BUILDING FROM SOURCE
+#####################################################################
+
+LOCATION
+
+The source is available in the contrib/persistent-https directory of
+the Git source repository. The Git source repository is available at
+git://git.kernel.org/pub/scm/git/git.git/
+https://kernel.googlesource.com/pub/scm/git/git
+
+
+PREREQUISITES
+
+The code is written in Go (http://golang.org/) and the Go compiler is
+required. Currently, the compiler must be built and installed from tip
+of source, in order to include a fix in the reverse http proxy:
+http://code.google.com/p/go/source/detail?r=a615b796570a2cd8591884767a7d67ede74f6648
+
+
+BUILDING
+
+Run "make" to build the binaries. See the section on
+INSTALLING above.
diff --git a/contrib/persistent-https/client.go b/contrib/persistent-https/client.go
new file mode 100644
index 0000000..71125b5
--- /dev/null
+++ b/contrib/persistent-https/client.go
@@ -0,0 +1,189 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "net"
+ "net/url"
+ "os"
+ "os/exec"
+ "strings"
+ "syscall"
+ "time"
+)
+
+type Client struct {
+ ProxyBin string
+ Args []string
+
+ insecure bool
+}
+
+func (c *Client) Run() error {
+ if err := c.resolveArgs(); err != nil {
+ return fmt.Errorf("resolveArgs() got error: %v", err)
+ }
+
+ // Connect to the proxy.
+ uconn, hconn, addr, err := c.connect()
+ if err != nil {
+ return fmt.Errorf("connect() got error: %v", err)
+ }
+ // Keep the unix socket connection open for the duration of the request.
+ defer uconn.Close()
+ // Keep a connection to the HTTP server open, so no other user can
+ // bind on the same address so long as the process is running.
+ defer hconn.Close()
+
+ // Start the git-remote-http subprocess.
+ cargs := []string{"-c", fmt.Sprintf("http.proxy=%v", addr), "remote-http"}
+ cargs = append(cargs, c.Args...)
+ cmd := exec.Command("git", cargs...)
+
+ for _, v := range os.Environ() {
+ if !strings.HasPrefix(v, "GIT_PERSISTENT_HTTPS_SECURE=") {
+ cmd.Env = append(cmd.Env, v)
+ }
+ }
+ // Set the GIT_PERSISTENT_HTTPS_SECURE environment variable when
+ // the proxy is using a SSL connection. This allows credential helpers
+ // to identify secure proxy connections, despite being passed an HTTP
+ // scheme.
+ if !c.insecure {
+ cmd.Env = append(cmd.Env, "GIT_PERSISTENT_HTTPS_SECURE=1")
+ }
+
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ if eerr, ok := err.(*exec.ExitError); ok {
+ if stat, ok := eerr.ProcessState.Sys().(syscall.WaitStatus); ok && stat.ExitStatus() != 0 {
+ os.Exit(stat.ExitStatus())
+ }
+ }
+ return fmt.Errorf("git-remote-http subprocess got error: %v", err)
+ }
+ return nil
+}
+
+func (c *Client) connect() (uconn net.Conn, hconn net.Conn, addr string, err error) {
+ uconn, err = DefaultSocket.Dial()
+ if err != nil {
+ if e, ok := err.(*net.OpError); ok && (os.IsNotExist(e.Err) || e.Err == syscall.ECONNREFUSED) {
+ if err = c.startProxy(); err == nil {
+ uconn, err = DefaultSocket.Dial()
+ }
+ }
+ if err != nil {
+ return
+ }
+ }
+
+ if addr, err = c.readAddr(uconn); err != nil {
+ return
+ }
+
+ // Open a tcp connection to the proxy.
+ if hconn, err = net.Dial("tcp", addr); err != nil {
+ return
+ }
+
+ // Verify the address hasn't changed ownership.
+ var addr2 string
+ if addr2, err = c.readAddr(uconn); err != nil {
+ return
+ } else if addr != addr2 {
+ err = fmt.Errorf("address changed after connect. got %q, want %q", addr2, addr)
+ return
+ }
+ return
+}
+
+func (c *Client) readAddr(conn net.Conn) (string, error) {
+ conn.SetDeadline(time.Now().Add(5 * time.Second))
+ data := make([]byte, 100)
+ n, err := conn.Read(data)
+ if err != nil {
+ return "", fmt.Errorf("error reading unix socket: %v", err)
+ } else if n == 0 {
+ return "", errors.New("empty data response")
+ }
+ conn.Write([]byte{1}) // Ack
+
+ var addr string
+ if addrs := strings.Split(string(data[:n]), "\n"); len(addrs) != 2 {
+ return "", fmt.Errorf("got %q, wanted 2 addresses", data[:n])
+ } else if c.insecure {
+ addr = addrs[1]
+ } else {
+ addr = addrs[0]
+ }
+ return addr, nil
+}
+
+func (c *Client) startProxy() error {
+ cmd := exec.Command(c.ProxyBin)
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ result := make(chan error)
+ go func() {
+ bytes, _, err := bufio.NewReader(stdout).ReadLine()
+ if line := string(bytes); err == nil && line != "OK" {
+ err = fmt.Errorf("proxy returned %q, want \"OK\"", line)
+ }
+ result <- err
+ }()
+ select {
+ case err := <-result:
+ return err
+ case <-time.After(5 * time.Second):
+ return errors.New("timeout waiting for proxy to start")
+ }
+ panic("not reachable")
+}
+
+func (c *Client) resolveArgs() error {
+ if nargs := len(c.Args); nargs == 0 {
+ return errors.New("remote needed")
+ } else if nargs > 2 {
+ return fmt.Errorf("want at most 2 args, got %v", c.Args)
+ }
+
+ // Rewrite the url scheme to be http.
+ idx := len(c.Args) - 1
+ rawurl := c.Args[idx]
+ rurl, err := url.Parse(rawurl)
+ if err != nil {
+ return fmt.Errorf("invalid remote: %v", err)
+ }
+ c.insecure = rurl.Scheme == "persistent-http"
+ rurl.Scheme = "http"
+ c.Args[idx] = rurl.String()
+ if idx != 0 && c.Args[0] == rawurl {
+ c.Args[0] = c.Args[idx]
+ }
+ return nil
+}
diff --git a/contrib/persistent-https/main.go b/contrib/persistent-https/main.go
new file mode 100644
index 0000000..fd1b107
--- /dev/null
+++ b/contrib/persistent-https/main.go
@@ -0,0 +1,82 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The git-remote-persistent-https binary speeds up SSL operations by running
+// a daemon job that keeps a connection open to a Git server. This ensures the
+// git-remote-persistent-https--proxy is running and delegating execution
+// to the git-remote-http binary with the http_proxy set to the daemon job.
+// A unix socket is used to authenticate the proxy and discover the
+// HTTP address. Note, both the client and proxy are included in the same
+// binary.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+ "time"
+)
+
+var (
+ forceProxy = flag.Bool("proxy", false, "Whether to start the binary in proxy mode")
+ proxyBin = flag.String("proxy_bin", "git-remote-persistent-https--proxy", "Path to the proxy binary")
+ printLabel = flag.Bool("print_label", false, "Prints the build label for the binary")
+
+ // Variable that should be defined through the -X linker flag.
+ _BUILD_EMBED_LABEL string
+)
+
+const (
+ defaultMaxIdleDuration = 24 * time.Hour
+ defaultPollUpdateInterval = 15 * time.Minute
+)
+
+func main() {
+ flag.Parse()
+ if *printLabel {
+ // Short circuit execution to print the build label
+ fmt.Println(buildLabel())
+ return
+ }
+
+ var err error
+ if *forceProxy || strings.HasSuffix(os.Args[0], "--proxy") {
+ log.SetPrefix("git-remote-persistent-https--proxy: ")
+ proxy := &Proxy{
+ BuildLabel: buildLabel(),
+ MaxIdleDuration: defaultMaxIdleDuration,
+ PollUpdateInterval: defaultPollUpdateInterval,
+ }
+ err = proxy.Run()
+ } else {
+ log.SetPrefix("git-remote-persistent-https: ")
+ client := &Client{
+ ProxyBin: *proxyBin,
+ Args: flag.Args(),
+ }
+ err = client.Run()
+ }
+ if err != nil {
+ log.Fatalln(err)
+ }
+}
+
+func buildLabel() string {
+ if _BUILD_EMBED_LABEL == "" {
+ log.Println(`unlabeled build; build with "make" to label`)
+ }
+ return _BUILD_EMBED_LABEL
+}
diff --git a/contrib/persistent-https/proxy.go b/contrib/persistent-https/proxy.go
new file mode 100644
index 0000000..bb0cdba
--- /dev/null
+++ b/contrib/persistent-https/proxy.go
@@ -0,0 +1,190 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "os/exec"
+ "os/signal"
+ "sync"
+ "syscall"
+ "time"
+)
+
+type Proxy struct {
+ BuildLabel string
+ MaxIdleDuration time.Duration
+ PollUpdateInterval time.Duration
+
+ ul net.Listener
+ httpAddr string
+ httpsAddr string
+}
+
+func (p *Proxy) Run() error {
+ hl, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ return fmt.Errorf("http listen failed: %v", err)
+ }
+ defer hl.Close()
+
+ hsl, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ return fmt.Errorf("https listen failed: %v", err)
+ }
+ defer hsl.Close()
+
+ p.ul, err = DefaultSocket.Listen()
+ if err != nil {
+ c, derr := DefaultSocket.Dial()
+ if derr == nil {
+ c.Close()
+ fmt.Println("OK\nA proxy is already running... exiting")
+ return nil
+ } else if e, ok := derr.(*net.OpError); ok && e.Err == syscall.ECONNREFUSED {
+ // Nothing is listening on the socket, unlink it and try again.
+ syscall.Unlink(DefaultSocket.Path())
+ p.ul, err = DefaultSocket.Listen()
+ }
+ if err != nil {
+ return fmt.Errorf("unix listen failed on %v: %v", DefaultSocket.Path(), err)
+ }
+ }
+ defer p.ul.Close()
+ go p.closeOnSignal()
+ go p.closeOnUpdate()
+
+ p.httpAddr = hl.Addr().String()
+ p.httpsAddr = hsl.Addr().String()
+ fmt.Printf("OK\nListening on unix socket=%v http=%v https=%v\n",
+ p.ul.Addr(), p.httpAddr, p.httpsAddr)
+
+ result := make(chan error, 2)
+ go p.serveUnix(result)
+ go func() {
+ result <- http.Serve(hl, &httputil.ReverseProxy{
+ FlushInterval: 500 * time.Millisecond,
+ Director: func(r *http.Request) {},
+ })
+ }()
+ go func() {
+ result <- http.Serve(hsl, &httputil.ReverseProxy{
+ FlushInterval: 500 * time.Millisecond,
+ Director: func(r *http.Request) {
+ r.URL.Scheme = "https"
+ },
+ })
+ }()
+ return <-result
+}
+
+type socketContext struct {
+ sync.WaitGroup
+ mutex sync.Mutex
+ last time.Time
+}
+
+func (sc *socketContext) Done() {
+ sc.mutex.Lock()
+ defer sc.mutex.Unlock()
+ sc.last = time.Now()
+ sc.WaitGroup.Done()
+}
+
+func (p *Proxy) serveUnix(result chan<- error) {
+ sockCtx := &socketContext{}
+ go p.closeOnIdle(sockCtx)
+
+ var err error
+ for {
+ var uconn net.Conn
+ uconn, err = p.ul.Accept()
+ if err != nil {
+ err = fmt.Errorf("accept failed: %v", err)
+ break
+ }
+ sockCtx.Add(1)
+ go p.handleUnixConn(sockCtx, uconn)
+ }
+ sockCtx.Wait()
+ result <- err
+}
+
+func (p *Proxy) handleUnixConn(sockCtx *socketContext, uconn net.Conn) {
+ defer sockCtx.Done()
+ defer uconn.Close()
+ data := []byte(fmt.Sprintf("%v\n%v", p.httpsAddr, p.httpAddr))
+ uconn.SetDeadline(time.Now().Add(5 * time.Second))
+ for i := 0; i < 2; i++ {
+ if n, err := uconn.Write(data); err != nil {
+ log.Printf("error sending http addresses: %+v\n", err)
+ return
+ } else if n != len(data) {
+ log.Printf("sent %d data bytes, wanted %d\n", n, len(data))
+ return
+ }
+ if _, err := uconn.Read([]byte{0, 0, 0, 0}); err != nil {
+ log.Printf("error waiting for Ack: %+v\n", err)
+ return
+ }
+ }
+ // Wait without a deadline for the client to finish via EOF
+ uconn.SetDeadline(time.Time{})
+ uconn.Read([]byte{0, 0, 0, 0})
+}
+
+func (p *Proxy) closeOnIdle(sockCtx *socketContext) {
+ for d := p.MaxIdleDuration; d > 0; {
+ time.Sleep(d)
+ sockCtx.Wait()
+ sockCtx.mutex.Lock()
+ if d = sockCtx.last.Add(p.MaxIdleDuration).Sub(time.Now()); d <= 0 {
+ log.Println("graceful shutdown from idle timeout")
+ p.ul.Close()
+ }
+ sockCtx.mutex.Unlock()
+ }
+}
+
+func (p *Proxy) closeOnUpdate() {
+ for {
+ time.Sleep(p.PollUpdateInterval)
+ if out, err := exec.Command(os.Args[0], "--print_label").Output(); err != nil {
+ log.Printf("error polling for updated binary: %v\n", err)
+ } else if s := string(out[:len(out)-1]); p.BuildLabel != s {
+ log.Printf("graceful shutdown from updated binary: %q --> %q\n", p.BuildLabel, s)
+ p.ul.Close()
+ break
+ }
+ }
+}
+
+func (p *Proxy) closeOnSignal() {
+ ch := make(chan os.Signal, 10)
+ signal.Notify(ch, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM), os.Signal(syscall.SIGHUP))
+ sig := <-ch
+ p.ul.Close()
+ switch sig {
+ case os.Signal(syscall.SIGHUP):
+ log.Printf("graceful shutdown from signal: %v\n", sig)
+ default:
+ log.Fatalf("exiting from signal: %v\n", sig)
+ }
+}
diff --git a/contrib/persistent-https/socket.go b/contrib/persistent-https/socket.go
new file mode 100644
index 0000000..193b911
--- /dev/null
+++ b/contrib/persistent-https/socket.go
@@ -0,0 +1,97 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "os"
+ "path/filepath"
+ "syscall"
+)
+
+// A Socket is a wrapper around a Unix socket that verifies directory
+// permissions.
+type Socket struct {
+ Dir string
+}
+
+func defaultDir() string {
+ sockPath := ".git-credential-cache"
+ if home := os.Getenv("HOME"); home != "" {
+ return filepath.Join(home, sockPath)
+ }
+ log.Printf("socket: cannot find HOME path. using relative directory %q for socket", sockPath)
+ return sockPath
+}
+
+// DefaultSocket is a Socket in the $HOME/.git-credential-cache directory.
+var DefaultSocket = Socket{Dir: defaultDir()}
+
+// Listen announces the local network address of the unix socket. The
+// permissions on the socket directory are verified before attempting
+// the actual listen.
+func (s Socket) Listen() (net.Listener, error) {
+ network, addr := "unix", s.Path()
+ if err := s.mkdir(); err != nil {
+ return nil, &net.OpError{Op: "listen", Net: network, Addr: &net.UnixAddr{Name: addr, Net: network}, Err: err}
+ }
+ return net.Listen(network, addr)
+}
+
+// Dial connects to the unix socket. The permissions on the socket directory
+// are verified before attempting the actual dial.
+func (s Socket) Dial() (net.Conn, error) {
+ network, addr := "unix", s.Path()
+ if err := s.checkPermissions(); err != nil {
+ return nil, &net.OpError{Op: "dial", Net: network, Addr: &net.UnixAddr{Name: addr, Net: network}, Err: err}
+ }
+ return net.Dial(network, addr)
+}
+
+// Path returns the fully specified file name of the unix socket.
+func (s Socket) Path() string {
+ return filepath.Join(s.Dir, "persistent-https-proxy-socket")
+}
+
+func (s Socket) mkdir() error {
+ if err := s.checkPermissions(); err == nil {
+ return nil
+ } else if !os.IsNotExist(err) {
+ return err
+ }
+ if err := os.MkdirAll(s.Dir, 0700); err != nil {
+ return err
+ }
+ return s.checkPermissions()
+}
+
+func (s Socket) checkPermissions() error {
+ fi, err := os.Stat(s.Dir)
+ if err != nil {
+ return err
+ }
+ if !fi.IsDir() {
+ return fmt.Errorf("socket: got file, want directory for %q", s.Dir)
+ }
+ if fi.Mode().Perm() != 0700 {
+ return fmt.Errorf("socket: got perm %o, want 700 for %q", fi.Mode().Perm(), s.Dir)
+ }
+ if st := fi.Sys().(*syscall.Stat_t); int(st.Uid) != os.Getuid() {
+ return fmt.Errorf("socket: got uid %d, want %d for %q", st.Uid, os.Getuid(), s.Dir)
+ }
+ return nil
+}
diff --git a/contrib/rerere-train.sh b/contrib/rerere-train.sh
index 2cfe1b9..36b6fee 100755
--- a/contrib/rerere-train.sh
+++ b/contrib/rerere-train.sh
@@ -7,7 +7,7 @@ USAGE="$me rev-list-args"
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
-. git-sh-setup
+. $(git --exec-path)/git-sh-setup
require_work_tree
cd_to_toplevel
diff --git a/contrib/subtree/.gitignore b/contrib/subtree/.gitignore
new file mode 100644
index 0000000..7e77c9d
--- /dev/null
+++ b/contrib/subtree/.gitignore
@@ -0,0 +1,5 @@
+*~
+git-subtree.xml
+git-subtree.1
+mainline
+subproj
diff --git a/contrib/subtree/COPYING b/contrib/subtree/COPYING
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/contrib/subtree/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/contrib/subtree/INSTALL b/contrib/subtree/INSTALL
new file mode 100644
index 0000000..7ab0cf4
--- /dev/null
+++ b/contrib/subtree/INSTALL
@@ -0,0 +1,28 @@
+HOW TO INSTALL git-subtree
+==========================
+
+First, build from the top source directory.
+
+Then, in contrib/subtree, run:
+
+ make
+ make install
+ make install-doc
+
+If you used configure to do the main build the git-subtree build will
+pick up those settings. If not, you will likely have to provide a
+value for prefix:
+
+ make prefix=<some dir>
+ make prefix=<some dir> install
+ make prefix=<some dir> install-doc
+
+To run tests first copy git-subtree to the main build area so the
+newly-built git can find it:
+
+ cp git-subtree ../..
+
+Then:
+
+ make test
+
diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile
new file mode 100644
index 0000000..05cdd5c
--- /dev/null
+++ b/contrib/subtree/Makefile
@@ -0,0 +1,52 @@
+-include ../../config.mak.autogen
+-include ../../config.mak
+
+prefix ?= /usr/local
+mandir ?= $(prefix)/share/man
+libexecdir ?= $(prefix)/libexec/git-core
+gitdir ?= $(shell git --exec-path)
+man1dir ?= $(mandir)/man1
+
+gitver ?= $(word 3,$(shell git --version))
+
+# this should be set to a 'standard' bsd-type install program
+INSTALL ?= install
+
+ASCIIDOC_CONF = ../../Documentation/asciidoc.conf
+MANPAGE_NORMAL_XSL = ../../Documentation/manpage-normal.xsl
+
+GIT_SUBTREE_SH := git-subtree.sh
+GIT_SUBTREE := git-subtree
+
+GIT_SUBTREE_DOC := git-subtree.1
+GIT_SUBTREE_XML := git-subtree.xml
+GIT_SUBTREE_TXT := git-subtree.txt
+
+all: $(GIT_SUBTREE)
+
+$(GIT_SUBTREE): $(GIT_SUBTREE_SH)
+ cp $< $@ && chmod +x $@
+
+doc: $(GIT_SUBTREE_DOC)
+
+install: $(GIT_SUBTREE)
+ $(INSTALL) -m 755 $(GIT_SUBTREE) $(libexecdir)
+
+install-doc: install-man
+
+install-man: $(GIT_SUBTREE_DOC)
+ $(INSTALL) -m 644 $^ $(man1dir)
+
+$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
+ xmlto -m $(MANPAGE_NORMAL_XSL) man $^
+
+$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
+ asciidoc -b docbook -d manpage -f $(ASCIIDOC_CONF) \
+ -agit_version=$(gitver) $^
+
+test:
+ $(MAKE) -C t/ test
+
+clean:
+ rm -f *~ *.xml *.html *.1
+ rm -rf subproj mainline
diff --git a/contrib/subtree/README b/contrib/subtree/README
new file mode 100644
index 0000000..c686b4a
--- /dev/null
+++ b/contrib/subtree/README
@@ -0,0 +1,8 @@
+
+Please read git-subtree.txt for documentation.
+
+Please don't contact me using github mail; it's slow, ugly, and worst of
+all, redundant. Email me instead at apenwarr@gmail.com and I'll be happy to
+help.
+
+Avery
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
new file mode 100755
index 0000000..920c664
--- /dev/null
+++ b/contrib/subtree/git-subtree.sh
@@ -0,0 +1,712 @@
+#!/bin/bash
+#
+# git-subtree.sh: split/join git repositories in subdirectories of this one
+#
+# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
+#
+if [ $# -eq 0 ]; then
+ set -- -h
+fi
+OPTS_SPEC="\
+git subtree add --prefix=<prefix> <commit>
+git subtree merge --prefix=<prefix> <commit>
+git subtree pull --prefix=<prefix> <repository> <refspec...>
+git subtree push --prefix=<prefix> <repository> <refspec...>
+git subtree split --prefix=<prefix> <commit...>
+--
+h,help show the help
+q quiet
+d show debug messages
+P,prefix= the name of the subdir to split out
+m,message= use the given message as the commit message for the merge commit
+ options for 'split'
+annotate= add a prefix to commit message of new commits
+b,branch= create a new branch from the split subtree
+ignore-joins ignore prior --rejoin commits
+onto= try connecting new tree to an existing one
+rejoin merge the new branch back into HEAD
+ options for 'add', 'merge', 'pull' and 'push'
+squash merge subtree changes as a single commit
+"
+eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+
+PATH=$PATH:$(git --exec-path)
+. git-sh-setup
+
+require_work_tree
+
+quiet=
+branch=
+debug=
+command=
+onto=
+rejoin=
+ignore_joins=
+annotate=
+squash=
+message=
+
+debug()
+{
+ if [ -n "$debug" ]; then
+ echo "$@" >&2
+ fi
+}
+
+say()
+{
+ if [ -z "$quiet" ]; then
+ echo "$@" >&2
+ fi
+}
+
+assert()
+{
+ if "$@"; then
+ :
+ else
+ die "assertion failed: " "$@"
+ fi
+}
+
+
+#echo "Options: $*"
+
+while [ $# -gt 0 ]; do
+ opt="$1"
+ shift
+ case "$opt" in
+ -q) quiet=1 ;;
+ -d) debug=1 ;;
+ --annotate) annotate="$1"; shift ;;
+ --no-annotate) annotate= ;;
+ -b) branch="$1"; shift ;;
+ -P) prefix="$1"; shift ;;
+ -m) message="$1"; shift ;;
+ --no-prefix) prefix= ;;
+ --onto) onto="$1"; shift ;;
+ --no-onto) onto= ;;
+ --rejoin) rejoin=1 ;;
+ --no-rejoin) rejoin= ;;
+ --ignore-joins) ignore_joins=1 ;;
+ --no-ignore-joins) ignore_joins= ;;
+ --squash) squash=1 ;;
+ --no-squash) squash= ;;
+ --) break ;;
+ *) die "Unexpected option: $opt" ;;
+ esac
+done
+
+command="$1"
+shift
+case "$command" in
+ add|merge|pull) default= ;;
+ split|push) default="--default HEAD" ;;
+ *) die "Unknown command '$command'" ;;
+esac
+
+if [ -z "$prefix" ]; then
+ die "You must provide the --prefix option."
+fi
+
+case "$command" in
+ add) [ -e "$prefix" ] &&
+ die "prefix '$prefix' already exists." ;;
+ *) [ -e "$prefix" ] ||
+ die "'$prefix' does not exist; use 'git subtree add'" ;;
+esac
+
+dir="$(dirname "$prefix/.")"
+
+if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
+ revs=$(git rev-parse $default --revs-only "$@") || exit $?
+ dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
+ if [ -n "$dirs" ]; then
+ die "Error: Use --prefix instead of bare filenames."
+ fi
+fi
+
+debug "command: {$command}"
+debug "quiet: {$quiet}"
+debug "revs: {$revs}"
+debug "dir: {$dir}"
+debug "opts: {$*}"
+debug
+
+cache_setup()
+{
+ cachedir="$GIT_DIR/subtree-cache/$$"
+ rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
+ mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
+ mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
+ debug "Using cachedir: $cachedir" >&2
+}
+
+cache_get()
+{
+ for oldrev in $*; do
+ if [ -r "$cachedir/$oldrev" ]; then
+ read newrev <"$cachedir/$oldrev"
+ echo $newrev
+ fi
+ done
+}
+
+cache_miss()
+{
+ for oldrev in $*; do
+ if [ ! -r "$cachedir/$oldrev" ]; then
+ echo $oldrev
+ fi
+ done
+}
+
+check_parents()
+{
+ missed=$(cache_miss $*)
+ for miss in $missed; do
+ if [ ! -r "$cachedir/notree/$miss" ]; then
+ debug " incorrect order: $miss"
+ fi
+ done
+}
+
+set_notree()
+{
+ echo "1" > "$cachedir/notree/$1"
+}
+
+cache_set()
+{
+ oldrev="$1"
+ newrev="$2"
+ if [ "$oldrev" != "latest_old" \
+ -a "$oldrev" != "latest_new" \
+ -a -e "$cachedir/$oldrev" ]; then
+ die "cache for $oldrev already exists!"
+ fi
+ echo "$newrev" >"$cachedir/$oldrev"
+}
+
+rev_exists()
+{
+ if git rev-parse "$1" >/dev/null 2>&1; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+rev_is_descendant_of_branch()
+{
+ newrev="$1"
+ branch="$2"
+ branch_hash=$(git rev-parse $branch)
+ match=$(git rev-list -1 $branch_hash ^$newrev)
+
+ if [ -z "$match" ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+# if a commit doesn't have a parent, this might not work. But we only want
+# to remove the parent from the rev-list, and since it doesn't exist, it won't
+# be there anyway, so do nothing in that case.
+try_remove_previous()
+{
+ if rev_exists "$1^"; then
+ echo "^$1^"
+ fi
+}
+
+find_latest_squash()
+{
+ debug "Looking for latest squash ($dir)..."
+ dir="$1"
+ sq=
+ main=
+ sub=
+ git log --grep="^git-subtree-dir: $dir/*\$" \
+ --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
+ while read a b junk; do
+ debug "$a $b $junk"
+ debug "{{$sq/$main/$sub}}"
+ case "$a" in
+ START) sq="$b" ;;
+ git-subtree-mainline:) main="$b" ;;
+ git-subtree-split:) sub="$b" ;;
+ END)
+ if [ -n "$sub" ]; then
+ if [ -n "$main" ]; then
+ # a rejoin commit?
+ # Pretend its sub was a squash.
+ sq="$sub"
+ fi
+ debug "Squash found: $sq $sub"
+ echo "$sq" "$sub"
+ break
+ fi
+ sq=
+ main=
+ sub=
+ ;;
+ esac
+ done
+}
+
+find_existing_splits()
+{
+ debug "Looking for prior splits..."
+ dir="$1"
+ revs="$2"
+ main=
+ sub=
+ git log --grep="^git-subtree-dir: $dir/*\$" \
+ --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
+ while read a b junk; do
+ case "$a" in
+ START) sq="$b" ;;
+ git-subtree-mainline:) main="$b" ;;
+ git-subtree-split:) sub="$b" ;;
+ END)
+ debug " Main is: '$main'"
+ if [ -z "$main" -a -n "$sub" ]; then
+ # squash commits refer to a subtree
+ debug " Squash: $sq from $sub"
+ cache_set "$sq" "$sub"
+ fi
+ if [ -n "$main" -a -n "$sub" ]; then
+ debug " Prior: $main -> $sub"
+ cache_set $main $sub
+ cache_set $sub $sub
+ try_remove_previous "$main"
+ try_remove_previous "$sub"
+ fi
+ main=
+ sub=
+ ;;
+ esac
+ done
+}
+
+copy_commit()
+{
+ # We're going to set some environment vars here, so
+ # do it in a subshell to get rid of them safely later
+ debug copy_commit "{$1}" "{$2}" "{$3}"
+ git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
+ (
+ read GIT_AUTHOR_NAME
+ read GIT_AUTHOR_EMAIL
+ read GIT_AUTHOR_DATE
+ read GIT_COMMITTER_NAME
+ read GIT_COMMITTER_EMAIL
+ read GIT_COMMITTER_DATE
+ export GIT_AUTHOR_NAME \
+ GIT_AUTHOR_EMAIL \
+ GIT_AUTHOR_DATE \
+ GIT_COMMITTER_NAME \
+ GIT_COMMITTER_EMAIL \
+ GIT_COMMITTER_DATE
+ (echo -n "$annotate"; cat ) |
+ git commit-tree "$2" $3 # reads the rest of stdin
+ ) || die "Can't copy commit $1"
+}
+
+add_msg()
+{
+ dir="$1"
+ latest_old="$2"
+ latest_new="$3"
+ if [ -n "$message" ]; then
+ commit_message="$message"
+ else
+ commit_message="Add '$dir/' from commit '$latest_new'"
+ fi
+ cat <<-EOF
+ $commit_message
+
+ git-subtree-dir: $dir
+ git-subtree-mainline: $latest_old
+ git-subtree-split: $latest_new
+ EOF
+}
+
+add_squashed_msg()
+{
+ if [ -n "$message" ]; then
+ echo "$message"
+ else
+ echo "Merge commit '$1' as '$2'"
+ fi
+}
+
+rejoin_msg()
+{
+ dir="$1"
+ latest_old="$2"
+ latest_new="$3"
+ if [ -n "$message" ]; then
+ commit_message="$message"
+ else
+ commit_message="Split '$dir/' into commit '$latest_new'"
+ fi
+ cat <<-EOF
+ $commit_message
+
+ git-subtree-dir: $dir
+ git-subtree-mainline: $latest_old
+ git-subtree-split: $latest_new
+ EOF
+}
+
+squash_msg()
+{
+ dir="$1"
+ oldsub="$2"
+ newsub="$3"
+ newsub_short=$(git rev-parse --short "$newsub")
+
+ if [ -n "$oldsub" ]; then
+ oldsub_short=$(git rev-parse --short "$oldsub")
+ echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
+ echo
+ git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
+ git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
+ else
+ echo "Squashed '$dir/' content from commit $newsub_short"
+ fi
+
+ echo
+ echo "git-subtree-dir: $dir"
+ echo "git-subtree-split: $newsub"
+}
+
+toptree_for_commit()
+{
+ commit="$1"
+ git log -1 --pretty=format:'%T' "$commit" -- || exit $?
+}
+
+subtree_for_commit()
+{
+ commit="$1"
+ dir="$2"
+ git ls-tree "$commit" -- "$dir" |
+ while read mode type tree name; do
+ assert [ "$name" = "$dir" ]
+ assert [ "$type" = "tree" -o "$type" = "commit" ]
+ [ "$type" = "commit" ] && continue # ignore submodules
+ echo $tree
+ break
+ done
+}
+
+tree_changed()
+{
+ tree=$1
+ shift
+ if [ $# -ne 1 ]; then
+ return 0 # weird parents, consider it changed
+ else
+ ptree=$(toptree_for_commit $1)
+ if [ "$ptree" != "$tree" ]; then
+ return 0 # changed
+ else
+ return 1 # not changed
+ fi
+ fi
+}
+
+new_squash_commit()
+{
+ old="$1"
+ oldsub="$2"
+ newsub="$3"
+ tree=$(toptree_for_commit $newsub) || exit $?
+ if [ -n "$old" ]; then
+ squash_msg "$dir" "$oldsub" "$newsub" |
+ git commit-tree "$tree" -p "$old" || exit $?
+ else
+ squash_msg "$dir" "" "$newsub" |
+ git commit-tree "$tree" || exit $?
+ fi
+}
+
+copy_or_skip()
+{
+ rev="$1"
+ tree="$2"
+ newparents="$3"
+ assert [ -n "$tree" ]
+
+ identical=
+ nonidentical=
+ p=
+ gotparents=
+ for parent in $newparents; do
+ ptree=$(toptree_for_commit $parent) || exit $?
+ [ -z "$ptree" ] && continue
+ if [ "$ptree" = "$tree" ]; then
+ # an identical parent could be used in place of this rev.
+ identical="$parent"
+ else
+ nonidentical="$parent"
+ fi
+
+ # sometimes both old parents map to the same newparent;
+ # eliminate duplicates
+ is_new=1
+ for gp in $gotparents; do
+ if [ "$gp" = "$parent" ]; then
+ is_new=
+ break
+ fi
+ done
+ if [ -n "$is_new" ]; then
+ gotparents="$gotparents $parent"
+ p="$p -p $parent"
+ fi
+ done
+
+ if [ -n "$identical" ]; then
+ echo $identical
+ else
+ copy_commit $rev $tree "$p" || exit $?
+ fi
+}
+
+ensure_clean()
+{
+ if ! git diff-index HEAD --exit-code --quiet 2>&1; then
+ die "Working tree has modifications. Cannot add."
+ fi
+ if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
+ die "Index has modifications. Cannot add."
+ fi
+}
+
+cmd_add()
+{
+ if [ -e "$dir" ]; then
+ die "'$dir' already exists. Cannot add."
+ fi
+
+ ensure_clean
+
+ if [ $# -eq 1 ]; then
+ "cmd_add_commit" "$@"
+ elif [ $# -eq 2 ]; then
+ "cmd_add_repository" "$@"
+ else
+ say "error: parameters were '$@'"
+ die "Provide either a refspec or a repository and refspec."
+ fi
+}
+
+cmd_add_repository()
+{
+ echo "git fetch" "$@"
+ repository=$1
+ refspec=$2
+ git fetch "$@" || exit $?
+ revs=FETCH_HEAD
+ set -- $revs
+ cmd_add_commit "$@"
+}
+
+cmd_add_commit()
+{
+ revs=$(git rev-parse $default --revs-only "$@") || exit $?
+ set -- $revs
+ rev="$1"
+
+ debug "Adding $dir as '$rev'..."
+ git read-tree --prefix="$dir" $rev || exit $?
+ git checkout -- "$dir" || exit $?
+ tree=$(git write-tree) || exit $?
+
+ headrev=$(git rev-parse HEAD) || exit $?
+ if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
+ headp="-p $headrev"
+ else
+ headp=
+ fi
+
+ if [ -n "$squash" ]; then
+ rev=$(new_squash_commit "" "" "$rev") || exit $?
+ commit=$(add_squashed_msg "$rev" "$dir" |
+ git commit-tree $tree $headp -p "$rev") || exit $?
+ else
+ commit=$(add_msg "$dir" "$headrev" "$rev" |
+ git commit-tree $tree $headp -p "$rev") || exit $?
+ fi
+ git reset "$commit" || exit $?
+
+ say "Added dir '$dir'"
+}
+
+cmd_split()
+{
+ debug "Splitting $dir..."
+ cache_setup || exit $?
+
+ if [ -n "$onto" ]; then
+ debug "Reading history for --onto=$onto..."
+ git rev-list $onto |
+ while read rev; do
+ # the 'onto' history is already just the subdir, so
+ # any parent we find there can be used verbatim
+ debug " cache: $rev"
+ cache_set $rev $rev
+ done
+ fi
+
+ if [ -n "$ignore_joins" ]; then
+ unrevs=
+ else
+ unrevs="$(find_existing_splits "$dir" "$revs")"
+ fi
+
+ # We can't restrict rev-list to only $dir here, because some of our
+ # parents have the $dir contents the root, and those won't match.
+ # (and rev-list --follow doesn't seem to solve this)
+ grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
+ revmax=$(eval "$grl" | wc -l)
+ revcount=0
+ createcount=0
+ eval "$grl" |
+ while read rev parents; do
+ revcount=$(($revcount + 1))
+ say -n "$revcount/$revmax ($createcount) "
+ debug "Processing commit: $rev"
+ exists=$(cache_get $rev)
+ if [ -n "$exists" ]; then
+ debug " prior: $exists"
+ continue
+ fi
+ createcount=$(($createcount + 1))
+ debug " parents: $parents"
+ newparents=$(cache_get $parents)
+ debug " newparents: $newparents"
+
+ tree=$(subtree_for_commit $rev "$dir")
+ debug " tree is: $tree"
+
+ check_parents $parents
+
+ # ugly. is there no better way to tell if this is a subtree
+ # vs. a mainline commit? Does it matter?
+ if [ -z $tree ]; then
+ set_notree $rev
+ if [ -n "$newparents" ]; then
+ cache_set $rev $rev
+ fi
+ continue
+ fi
+
+ newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
+ debug " newrev is: $newrev"
+ cache_set $rev $newrev
+ cache_set latest_new $newrev
+ cache_set latest_old $rev
+ done || exit $?
+ latest_new=$(cache_get latest_new)
+ if [ -z "$latest_new" ]; then
+ die "No new revisions were found"
+ fi
+
+ if [ -n "$rejoin" ]; then
+ debug "Merging split branch into HEAD..."
+ latest_old=$(cache_get latest_old)
+ git merge -s ours \
+ -m "$(rejoin_msg $dir $latest_old $latest_new)" \
+ $latest_new >&2 || exit $?
+ fi
+ if [ -n "$branch" ]; then
+ if rev_exists "refs/heads/$branch"; then
+ if ! rev_is_descendant_of_branch $latest_new $branch; then
+ die "Branch '$branch' is not an ancestor of commit '$latest_new'."
+ fi
+ action='Updated'
+ else
+ action='Created'
+ fi
+ git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
+ say "$action branch '$branch'"
+ fi
+ echo $latest_new
+ exit 0
+}
+
+cmd_merge()
+{
+ revs=$(git rev-parse $default --revs-only "$@") || exit $?
+ ensure_clean
+
+ set -- $revs
+ if [ $# -ne 1 ]; then
+ die "You must provide exactly one revision. Got: '$revs'"
+ fi
+ rev="$1"
+
+ if [ -n "$squash" ]; then
+ first_split="$(find_latest_squash "$dir")"
+ if [ -z "$first_split" ]; then
+ die "Can't squash-merge: '$dir' was never added."
+ fi
+ set $first_split
+ old=$1
+ sub=$2
+ if [ "$sub" = "$rev" ]; then
+ say "Subtree is already at commit $rev."
+ exit 0
+ fi
+ new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
+ debug "New squash commit: $new"
+ rev="$new"
+ fi
+
+ version=$(git version)
+ if [ "$version" \< "git version 1.7" ]; then
+ if [ -n "$message" ]; then
+ git merge -s subtree --message="$message" $rev
+ else
+ git merge -s subtree $rev
+ fi
+ else
+ if [ -n "$message" ]; then
+ git merge -Xsubtree="$prefix" --message="$message" $rev
+ else
+ git merge -Xsubtree="$prefix" $rev
+ fi
+ fi
+}
+
+cmd_pull()
+{
+ ensure_clean
+ git fetch "$@" || exit $?
+ revs=FETCH_HEAD
+ set -- $revs
+ cmd_merge "$@"
+}
+
+cmd_push()
+{
+ if [ $# -ne 2 ]; then
+ die "You must provide <repository> <refspec>"
+ fi
+ if [ -e "$dir" ]; then
+ repository=$1
+ refspec=$2
+ echo "git push using: " $repository $refspec
+ git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
+ else
+ die "'$dir' must already exist. Try 'git subtree add'."
+ fi
+}
+
+"cmd_$command" "$@"
diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt
new file mode 100644
index 0000000..0c44fda
--- /dev/null
+++ b/contrib/subtree/git-subtree.txt
@@ -0,0 +1,366 @@
+git-subtree(1)
+==============
+
+NAME
+----
+git-subtree - Merge subtrees together and split repository into subtrees
+
+
+SYNOPSIS
+--------
+[verse]
+'git subtree' add -P <prefix> <commit>
+'git subtree' pull -P <prefix> <repository> <refspec...>
+'git subtree' push -P <prefix> <repository> <refspec...>
+'git subtree' merge -P <prefix> <commit>
+'git subtree' split -P <prefix> [OPTIONS] [<commit>]
+
+
+DESCRIPTION
+-----------
+Subtrees allow subprojects to be included within a subdirectory
+of the main project, optionally including the subproject's
+entire history.
+
+For example, you could include the source code for a library
+as a subdirectory of your application.
+
+Subtrees are not to be confused with submodules, which are meant for
+the same task. Unlike submodules, subtrees do not need any special
+constructions (like .gitmodule files or gitlinks) be present in
+your repository, and do not force end-users of your
+repository to do anything special or to understand how subtrees
+work. A subtree is just a subdirectory that can be
+committed to, branched, and merged along with your project in
+any way you want.
+
+They are also not to be confused with using the subtree merge
+strategy. The main difference is that, besides merging
+the other project as a subdirectory, you can also extract the
+entire history of a subdirectory from your project and make it
+into a standalone project. Unlike the subtree merge strategy
+you can alternate back and forth between these
+two operations. If the standalone library gets updated, you can
+automatically merge the changes into your project; if you
+update the library inside your project, you can "split" the
+changes back out again and merge them back into the library
+project.
+
+For example, if a library you made for one application ends up being
+useful elsewhere, you can extract its entire history and publish
+that as its own git repository, without accidentally
+intermingling the history of your application project.
+
+[TIP]
+In order to keep your commit messages clean, we recommend that
+people split their commits between the subtrees and the main
+project as much as possible. That is, if you make a change that
+affects both the library and the main application, commit it in
+two pieces. That way, when you split the library commits out
+later, their descriptions will still make sense. But if this
+isn't important to you, it's not *necessary*. git subtree will
+simply leave out the non-library-related parts of the commit
+when it splits it out into the subproject later.
+
+
+COMMANDS
+--------
+add::
+ Create the <prefix> subtree by importing its contents
+ from the given <refspec> or <repository> and remote <refspec>.
+ A new commit is created automatically, joining the imported
+ project's history with your own. With '--squash', imports
+ only a single commit from the subproject, rather than its
+ entire history.
+
+merge::
+ Merge recent changes up to <commit> into the <prefix>
+ subtree. As with normal 'git merge', this doesn't
+ remove your own local changes; it just merges those
+ changes into the latest <commit>. With '--squash',
+ creates only one commit that contains all the changes,
+ rather than merging in the entire history.
+
+ If you use '--squash', the merge direction doesn't
+ always have to be forward; you can use this command to
+ go back in time from v2.5 to v2.4, for example. If your
+ merge introduces a conflict, you can resolve it in the
+ usual ways.
+
+pull::
+ Exactly like 'merge', but parallels 'git pull' in that
+ it fetches the given commit from the specified remote
+ repository.
+
+push::
+ Does a 'split' (see above) using the <prefix> supplied
+ and then does a 'git push' to push the result to the
+ repository and refspec. This can be used to push your
+ subtree to different branches of the remote repository.
+
+split::
+ Extract a new, synthetic project history from the
+ history of the <prefix> subtree. The new history
+ includes only the commits (including merges) that
+ affected <prefix>, and each of those commits now has the
+ contents of <prefix> at the root of the project instead
+ of in a subdirectory. Thus, the newly created history
+ is suitable for export as a separate git repository.
+
+ After splitting successfully, a single commit id is
+ printed to stdout. This corresponds to the HEAD of the
+ newly created tree, which you can manipulate however you
+ want.
+
+ Repeated splits of exactly the same history are
+ guaranteed to be identical (ie. to produce the same
+ commit ids). Because of this, if you add new commits
+ and then re-split, the new commits will be attached as
+ commits on top of the history you generated last time,
+ so 'git merge' and friends will work as expected.
+
+ Note that if you use '--squash' when you merge, you
+ should usually not just '--rejoin' when you split.
+
+
+OPTIONS
+-------
+-q::
+--quiet::
+ Suppress unnecessary output messages on stderr.
+
+-d::
+--debug::
+ Produce even more unnecessary output messages on stderr.
+
+-P <prefix>::
+--prefix=<prefix>::
+ Specify the path in the repository to the subtree you
+ want to manipulate. This option is mandatory
+ for all commands.
+
+-m <message>::
+--message=<message>::
+ This option is only valid for add, merge and pull (unsure).
+ Specify <message> as the commit message for the merge commit.
+
+
+OPTIONS FOR add, merge, push, pull
+----------------------------------
+--squash::
+ This option is only valid for add, merge, push and pull
+ commands.
+
+ Instead of merging the entire history from the subtree
+ project, produce only a single commit that contains all
+ the differences you want to merge, and then merge that
+ new commit into your project.
+
+ Using this option helps to reduce log clutter. People
+ rarely want to see every change that happened between
+ v1.0 and v1.1 of the library they're using, since none of the
+ interim versions were ever included in their application.
+
+ Using '--squash' also helps avoid problems when the same
+ subproject is included multiple times in the same
+ project, or is removed and then re-added. In such a
+ case, it doesn't make sense to combine the histories
+ anyway, since it's unclear which part of the history
+ belongs to which subtree.
+
+ Furthermore, with '--squash', you can switch back and
+ forth between different versions of a subtree, rather
+ than strictly forward. 'git subtree merge --squash'
+ always adjusts the subtree to match the exactly
+ specified commit, even if getting to that commit would
+ require undoing some changes that were added earlier.
+
+ Whether or not you use '--squash', changes made in your
+ local repository remain intact and can be later split
+ and send upstream to the subproject.
+
+
+OPTIONS FOR split
+-----------------
+--annotate=<annotation>::
+ This option is only valid for the split command.
+
+ When generating synthetic history, add <annotation> as a
+ prefix to each commit message. Since we're creating new
+ commits with the same commit message, but possibly
+ different content, from the original commits, this can help
+ to differentiate them and avoid confusion.
+
+ Whenever you split, you need to use the same
+ <annotation>, or else you don't have a guarantee that
+ the new re-created history will be identical to the old
+ one. That will prevent merging from working correctly.
+ git subtree tries to make it work anyway, particularly
+ if you use --rejoin, but it may not always be effective.
+
+-b <branch>::
+--branch=<branch>::
+ This option is only valid for the split command.
+
+ After generating the synthetic history, create a new
+ branch called <branch> that contains the new history.
+ This is suitable for immediate pushing upstream.
+ <branch> must not already exist.
+
+--ignore-joins::
+ This option is only valid for the split command.
+
+ If you use '--rejoin', git subtree attempts to optimize
+ its history reconstruction to generate only the new
+ commits since the last '--rejoin'. '--ignore-join'
+ disables this behaviour, forcing it to regenerate the
+ entire history. In a large project, this can take a
+ long time.
+
+--onto=<onto>::
+ This option is only valid for the split command.
+
+ If your subtree was originally imported using something
+ other than git subtree, its history may not match what
+ git subtree is expecting. In that case, you can specify
+ the commit id <onto> that corresponds to the first
+ revision of the subproject's history that was imported
+ into your project, and git subtree will attempt to build
+ its history from there.
+
+ If you used 'git subtree add', you should never need
+ this option.
+
+--rejoin::
+ This option is only valid for the split command.
+
+ After splitting, merge the newly created synthetic
+ history back into your main project. That way, future
+ splits can search only the part of history that has
+ been added since the most recent --rejoin.
+
+ If your split commits end up merged into the upstream
+ subproject, and then you want to get the latest upstream
+ version, this will allow git's merge algorithm to more
+ intelligently avoid conflicts (since it knows these
+ synthetic commits are already part of the upstream
+ repository).
+
+ Unfortunately, using this option results in 'git log'
+ showing an extra copy of every new commit that was
+ created (the original, and the synthetic one).
+
+ If you do all your merges with '--squash', don't use
+ '--rejoin' when you split, because you don't want the
+ subproject's history to be part of your project anyway.
+
+
+EXAMPLE 1. Add command
+----------------------
+Let's assume that you have a local repository that you would like
+to add an external vendor library to. In this case we will add the
+git-subtree repository as a subdirectory of your already existing
+git-extensions repository in ~/git-extensions/:
+
+ $ git subtree add --prefix=git-subtree --squash \
+ git://github.com/apenwarr/git-subtree.git master
+
+'master' needs to be a valid remote ref and can be a different branch
+name
+
+You can omit the --squash flag, but doing so will increase the number
+of commits that are incldued in your local repository.
+
+We now have a ~/git-extensions/git-subtree directory containing code
+from the master branch of git://github.com/apenwarr/git-subtree.git
+in our git-extensions repository.
+
+EXAMPLE 2. Extract a subtree using commit, merge and pull
+---------------------------------------------------------
+Let's use the repository for the git source code as an example.
+First, get your own copy of the git.git repository:
+
+ $ git clone git://git.kernel.org/pub/scm/git/git.git test-git
+ $ cd test-git
+
+gitweb (commit 1130ef3) was merged into git as of commit
+0a8f4f0, after which it was no longer maintained separately.
+But imagine it had been maintained separately, and we wanted to
+extract git's changes to gitweb since that time, to share with
+the upstream. You could do this:
+
+ $ git subtree split --prefix=gitweb --annotate='(split) ' \
+ 0a8f4f0^.. --onto=1130ef3 --rejoin \
+ --branch gitweb-latest
+ $ gitk gitweb-latest
+ $ git push git@github.com:whatever/gitweb.git gitweb-latest:master
+
+(We use '0a8f4f0^..' because that means "all the changes from
+0a8f4f0 to the current version, including 0a8f4f0 itself.")
+
+If gitweb had originally been merged using 'git subtree add' (or
+a previous split had already been done with --rejoin specified)
+then you can do all your splits without having to remember any
+weird commit ids:
+
+ $ git subtree split --prefix=gitweb --annotate='(split) ' --rejoin \
+ --branch gitweb-latest2
+
+And you can merge changes back in from the upstream project just
+as easily:
+
+ $ git subtree pull --prefix=gitweb \
+ git@github.com:whatever/gitweb.git master
+
+Or, using '--squash', you can actually rewind to an earlier
+version of gitweb:
+
+ $ git subtree merge --prefix=gitweb --squash gitweb-latest~10
+
+Then make some changes:
+
+ $ date >gitweb/myfile
+ $ git add gitweb/myfile
+ $ git commit -m 'created myfile'
+
+And fast forward again:
+
+ $ git subtree merge --prefix=gitweb --squash gitweb-latest
+
+And notice that your change is still intact:
+
+ $ ls -l gitweb/myfile
+
+And you can split it out and look at your changes versus
+the standard gitweb:
+
+ git log gitweb-latest..$(git subtree split --prefix=gitweb)
+
+EXAMPLE 3. Extract a subtree using branch
+-----------------------------------------
+Suppose you have a source directory with many files and
+subdirectories, and you want to extract the lib directory to its own
+git project. Here's a short way to do it:
+
+First, make the new repository wherever you want:
+
+ $ <go to the new location>
+ $ git init --bare
+
+Back in your original directory:
+
+ $ git subtree split --prefix=lib --annotate="(split)" -b split
+
+Then push the new branch onto the new empty repository:
+
+ $ git push <new-repo> split:master
+
+
+AUTHOR
+------
+Written by Avery Pennarun <apenwarr@gmail.com>
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile
new file mode 100644
index 0000000..c864810
--- /dev/null
+++ b/contrib/subtree/t/Makefile
@@ -0,0 +1,69 @@
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+#GIT_TEST_OPTS=--verbose --debug
+SHELL_PATH ?= $(SHELL)
+PERL_PATH ?= /usr/bin/perl
+TAR ?= $(TAR)
+RM ?= rm -f
+PROVE ?= prove
+DEFAULT_TEST_TARGET ?= test
+
+# Shell quote;
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+all: $(DEFAULT_TEST_TARGET)
+
+test: pre-clean $(TEST_LINT)
+ $(MAKE) aggregate-results-and-cleanup
+
+prove: pre-clean $(TEST_LINT)
+ @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
+ $(MAKE) clean
+
+$(T):
+ @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
+
+pre-clean:
+ $(RM) -r test-results
+
+clean:
+ $(RM) -r 'trash directory'.* test-results
+ $(RM) -r valgrind/bin
+ $(RM) .prove
+
+test-lint: test-lint-duplicates test-lint-executable
+
+test-lint-duplicates:
+ @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
+ test -z "$$dups" || { \
+ echo >&2 "duplicate test numbers:" $$dups; exit 1; }
+
+test-lint-executable:
+ @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \
+ test -z "$$bad" || { \
+ echo >&2 "non-executable tests:" $$bad; exit 1; }
+
+aggregate-results-and-cleanup: $(T)
+ $(MAKE) aggregate-results
+ $(MAKE) clean
+
+aggregate-results:
+ for f in ../../../t/test-results/t*-*.counts; do \
+ echo "$$f"; \
+ done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh
+
+valgrind:
+ $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
+
+test-results:
+ mkdir -p test-results
+
+.PHONY: pre-clean $(T) aggregate-results clean valgrind
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
new file mode 100755
index 0000000..bc2eeb0
--- /dev/null
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -0,0 +1,508 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Avery Pennaraum
+#
+test_description='Basic porcelain support for subtrees
+
+This test verifies the basic operation of the merge, pull, add
+and split subcommands of git subtree.
+'
+
+export TEST_DIRECTORY=$(pwd)/../../../t
+
+. ../../../t/test-lib.sh
+
+create()
+{
+ echo "$1" >"$1"
+ git add "$1"
+}
+
+
+check_equal()
+{
+ test_debug 'echo'
+ test_debug "echo \"check a:\" \"{$1}\""
+ test_debug "echo \" b:\" \"{$2}\""
+ if [ "$1" = "$2" ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+fixnl()
+{
+ t=""
+ while read x; do
+ t="$t$x "
+ done
+ echo $t
+}
+
+multiline()
+{
+ while read x; do
+ set -- $x
+ for d in "$@"; do
+ echo "$d"
+ done
+ done
+}
+
+undo()
+{
+ git reset --hard HEAD~
+}
+
+last_commit_message()
+{
+ git log --pretty=format:%s -1
+}
+
+# 1
+test_expect_success 'init subproj' '
+ test_create_repo subproj
+'
+
+# To the subproject!
+cd subproj
+
+# 2
+test_expect_success 'add sub1' '
+ create sub1 &&
+ git commit -m "sub1" &&
+ git branch sub1 &&
+ git branch -m master subproj
+'
+
+# 3
+test_expect_success 'add sub2' '
+ create sub2 &&
+ git commit -m "sub2" &&
+ git branch sub2
+'
+
+# 4
+test_expect_success 'add sub3' '
+ create sub3 &&
+ git commit -m "sub3" &&
+ git branch sub3
+'
+
+# Back to mainline
+cd ..
+
+# 5
+test_expect_success 'add main4' '
+ create main4 &&
+ git commit -m "main4" &&
+ git branch -m master mainline &&
+ git branch subdir
+'
+
+# 6
+test_expect_success 'fetch subproj history' '
+ git fetch ./subproj sub1 &&
+ git branch sub1 FETCH_HEAD
+'
+
+# 7
+test_expect_success 'no subtree exists in main tree' '
+ test_must_fail git subtree merge --prefix=subdir sub1
+'
+
+# 8
+test_expect_success 'no pull from non-existant subtree' '
+ test_must_fail git subtree pull --prefix=subdir ./subproj sub1
+'
+
+# 9
+test_expect_success 'check if --message works for add' '
+ git subtree add --prefix=subdir --message="Added subproject" sub1 &&
+ check_equal ''"$(last_commit_message)"'' "Added subproject" &&
+ undo
+'
+
+# 10
+test_expect_success 'check if --message works as -m and --prefix as -P' '
+ git subtree add -P subdir -m "Added subproject using git subtree" sub1 &&
+ check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
+ undo
+'
+
+# 11
+test_expect_success 'check if --message works with squash too' '
+ git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
+ check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
+ undo
+'
+
+# 12
+test_expect_success 'add subproj to mainline' '
+ git subtree add --prefix=subdir/ FETCH_HEAD &&
+ check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
+'
+
+# 13
+# this shouldn't actually do anything, since FETCH_HEAD is already a parent
+test_expect_success 'merge fetched subproj' '
+ git merge -m "merge -s -ours" -s ours FETCH_HEAD
+'
+
+# 14
+test_expect_success 'add main-sub5' '
+ create subdir/main-sub5 &&
+ git commit -m "main-sub5"
+'
+
+# 15
+test_expect_success 'add main6' '
+ create main6 &&
+ git commit -m "main6 boring"
+'
+
+# 16
+test_expect_success 'add main-sub7' '
+ create subdir/main-sub7 &&
+ git commit -m "main-sub7"
+'
+
+# 17
+test_expect_success 'fetch new subproj history' '
+ git fetch ./subproj sub2 &&
+ git branch sub2 FETCH_HEAD
+'
+
+# 18
+test_expect_success 'check if --message works for merge' '
+ git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
+ check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
+ undo
+'
+
+# 19
+test_expect_success 'check if --message for merge works with squash too' '
+ git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 &&
+ check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
+ undo
+'
+
+# 20
+test_expect_success 'merge new subproj history into subdir' '
+ git subtree merge --prefix=subdir FETCH_HEAD &&
+ git branch pre-split &&
+ check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline"
+'
+
+# 21
+test_expect_success 'Check that prefix argument is required for split' '
+ echo "You must provide the --prefix option." > expected &&
+ test_must_fail git subtree split > actual 2>&1 &&
+ test_debug "echo -n expected: " &&
+ test_debug "cat expected" &&
+ test_debug "echo -n actual: " &&
+ test_debug "cat actual" &&
+ test_cmp expected actual &&
+ rm -f expected actual
+'
+
+# 22
+test_expect_success 'Check that the <prefix> exists for a split' '
+ echo "'"'"'non-existent-directory'"'"'" does not exist\; use "'"'"'git subtree add'"'"'" > expected &&
+ test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 &&
+ test_debug "echo -n expected: " &&
+ test_debug "cat expected" &&
+ test_debug "echo -n actual: " &&
+ test_debug "cat actual" &&
+ test_cmp expected actual
+# rm -f expected actual
+'
+
+# 23
+test_expect_success 'check if --message works for split+rejoin' '
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ git branch spl1 "$spl1" &&
+ check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
+ undo
+'
+
+# 24
+test_expect_success 'check split with --branch' '
+ spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
+ undo &&
+ git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
+ check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
+'
+
+# 25
+test_expect_success 'check split with --branch for an existing branch' '
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ undo &&
+ git branch splitbr2 sub1 &&
+ git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
+ check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
+'
+
+# 26
+test_expect_success 'check split with --branch for an incompatible branch' '
+ test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
+'
+
+
+# 27
+test_expect_success 'check split+rejoin' '
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ undo &&
+ git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin &&
+ check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'"
+'
+
+# 28
+test_expect_success 'add main-sub8' '
+ create subdir/main-sub8 &&
+ git commit -m "main-sub8"
+'
+
+# To the subproject!
+cd ./subproj
+
+# 29
+test_expect_success 'merge split into subproj' '
+ git fetch .. spl1 &&
+ git branch spl1 FETCH_HEAD &&
+ git merge FETCH_HEAD
+'
+
+# 30
+test_expect_success 'add sub9' '
+ create sub9 &&
+ git commit -m "sub9"
+'
+
+# Back to mainline
+cd ..
+
+# 31
+test_expect_success 'split for sub8' '
+ split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"''
+ git branch split2 "$split2"
+'
+
+# 32
+test_expect_success 'add main-sub10' '
+ create subdir/main-sub10 &&
+ git commit -m "main-sub10"
+'
+
+# 33
+test_expect_success 'split for sub10' '
+ spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
+ git branch spl3 "$spl3"
+'
+
+# To the subproject!
+cd ./subproj
+
+# 34
+test_expect_success 'merge split into subproj' '
+ git fetch .. spl3 &&
+ git branch spl3 FETCH_HEAD &&
+ git merge FETCH_HEAD &&
+ git branch subproj-merge-spl3
+'
+
+chkm="main4 main6"
+chkms="main-sub10 main-sub5 main-sub7 main-sub8"
+chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)
+chks="sub1 sub2 sub3 sub9"
+chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)
+
+# 35
+test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
+ subfiles=''"$(git ls-files | fixnl)"'' &&
+ check_equal "$subfiles" "$chkms $chks"
+'
+
+# 36
+test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' '
+ allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
+ check_equal "$allchanges" "$chkms $chks"
+'
+
+# Back to mainline
+cd ..
+
+# 37
+test_expect_success 'pull from subproj' '
+ git fetch ./subproj subproj-merge-spl3 &&
+ git branch subproj-merge-spl3 FETCH_HEAD &&
+ git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
+'
+
+# 38
+test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
+ mainfiles=''"$(git ls-files | fixnl)"'' &&
+ check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub"
+'
+
+# 39
+test_expect_success 'make sure each filename changed exactly once in the entire history' '
+ # main-sub?? and /subdir/main-sub?? both change, because those are the
+ # changes that were split into their own history. And subdir/sub?? never
+ # change, since they were *only* changed in the subtree branch.
+ allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
+ check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"''
+'
+
+# 40
+test_expect_success 'make sure the --rejoin commits never make it into subproj' '
+ check_equal ''"$(git log --pretty=format:'"'%s'"' HEAD^2 | grep -i split)"'' ""
+'
+
+# 41
+test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' '
+ # They are meaningless to subproj since one side of the merge refers to the mainline
+ check_equal ''"$(git log --pretty=format:'"'%s%n%b'"' HEAD^2 | grep "git-subtree.*:")"'' ""
+'
+
+# prepare second pair of repositories
+mkdir test2
+cd test2
+
+# 42
+test_expect_success 'init main' '
+ test_create_repo main
+'
+
+cd main
+
+# 43
+test_expect_success 'add main1' '
+ create main1 &&
+ git commit -m "main1"
+'
+
+cd ..
+
+# 44
+test_expect_success 'init sub' '
+ test_create_repo sub
+'
+
+cd sub
+
+# 45
+test_expect_success 'add sub2' '
+ create sub2 &&
+ git commit -m "sub2"
+'
+
+cd ../main
+
+# check if split can find proper base without --onto
+
+# 46
+test_expect_success 'add sub as subdir in main' '
+ git fetch ../sub master &&
+ git branch sub2 FETCH_HEAD &&
+ git subtree add --prefix subdir sub2
+'
+
+cd ../sub
+
+# 47
+test_expect_success 'add sub3' '
+ create sub3 &&
+ git commit -m "sub3"
+'
+
+cd ../main
+
+# 48
+test_expect_success 'merge from sub' '
+ git fetch ../sub master &&
+ git branch sub3 FETCH_HEAD &&
+ git subtree merge --prefix subdir sub3
+'
+
+# 49
+test_expect_success 'add main-sub4' '
+ create subdir/main-sub4 &&
+ git commit -m "main-sub4"
+'
+
+# 50
+test_expect_success 'split for main-sub4 without --onto' '
+ git subtree split --prefix subdir --branch mainsub4
+'
+
+# at this point, the new commit parent should be sub3 if it is not,
+# something went wrong (the "newparent" of "master~" commit should
+# have been sub3, but it was not, because its cache was not set to
+# itself)
+
+# 51
+test_expect_success 'check that the commit parent is sub3' '
+ check_equal ''"$(git log --pretty=format:%P -1 mainsub4)"'' ''"$(git rev-parse sub3)"''
+'
+
+# 52
+test_expect_success 'add main-sub5' '
+ mkdir subdir2 &&
+ create subdir2/main-sub5 &&
+ git commit -m "main-sub5"
+'
+
+# 53
+test_expect_success 'split for main-sub5 without --onto' '
+ # also test that we still can split out an entirely new subtree
+ # if the parent of the first commit in the tree is not empty,
+ # then the new subtree has accidently been attached to something
+ git subtree split --prefix subdir2 --branch mainsub5 &&
+ check_equal ''"$(git log --pretty=format:%P -1 mainsub5)"'' ""
+'
+
+# make sure no patch changes more than one file. The original set of commits
+# changed only one file each. A multi-file change would imply that we pruned
+# commits too aggressively.
+joincommits()
+{
+ commit=
+ all=
+ while read x y; do
+ #echo "{$x}" >&2
+ if [ -z "$x" ]; then
+ continue
+ elif [ "$x" = "commit:" ]; then
+ if [ -n "$commit" ]; then
+ echo "$commit $all"
+ all=
+ fi
+ commit="$y"
+ else
+ all="$all $y"
+ fi
+ done
+ echo "$commit $all"
+}
+
+# 54
+test_expect_success 'verify one file change per commit' '
+ x= &&
+ list=''"$(git log --pretty=format:'"'commit: %H'"' | joincommits)"'' &&
+# test_debug "echo HERE" &&
+# test_debug "echo ''"$list"''" &&
+ (git log --pretty=format:'"'commit: %H'"' | joincommits |
+ ( while read commit a b; do
+ test_debug "echo Verifying commit "''"$commit"''
+ test_debug "echo a: "''"$a"''
+ test_debug "echo b: "''"$b"''
+ check_equal "$b" ""
+ x=1
+ done
+ check_equal "$x" 1
+ ))
+'
+
+test_done
diff --git a/contrib/subtree/todo b/contrib/subtree/todo
new file mode 100644
index 0000000..7e44b00
--- /dev/null
+++ b/contrib/subtree/todo
@@ -0,0 +1,50 @@
+
+ delete tempdir
+
+ 'git subtree rejoin' option to do the same as --rejoin, eg. after a
+ rebase
+
+ --prefix doesn't force the subtree correctly in merge/pull:
+ "-s subtree" should be given an explicit subtree option?
+ There doesn't seem to be a way to do this. We'd have to
+ patch git-merge-subtree. Ugh.
+ (but we could avoid this problem by generating squashes with
+ exactly the right subtree structure, rather than using
+ subtree merge...)
+
+ add a 'push' subcommand to parallel 'pull'
+
+ add a 'log' subcommand to see what's new in a subtree?
+
+ add to-submodule and from-submodule commands
+
+ automated tests for --squash stuff
+
+ "add" command non-obviously requires a commitid; would be easier if
+ it had a "pull" sort of mode instead
+
+ "pull" and "merge" commands should fail if you've never merged
+ that --prefix before
+
+ docs should provide an example of "add"
+
+ note that the initial split doesn't *have* to have a commitid
+ specified... that's just an optimization
+
+ if you try to add (or maybe merge?) with an invalid commitid, you
+ get a misleading "prefix must end with /" message from
+ one of the other git tools that git-subtree calls. Should
+ detect this situation and print the *real* problem.
+
+ "pull --squash" should do fetch-synthesize-merge, but instead just
+ does "pull" directly, which doesn't work at all.
+
+ make a 'force-update' that does what 'add' does even if the subtree
+ already exists. That way we can help people who imported
+ subtrees "incorrectly" (eg. by just copying in the files) in
+ the past.
+
+ guess --prefix automatically if possible based on pwd
+
+ make a 'git subtree grafts' that automatically expands --squash'd
+ commits so you can see the full history if you want it.
diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt
index 72ffea0..1128ab2 100644
--- a/contrib/svn-fe/svn-fe.txt
+++ b/contrib/svn-fe/svn-fe.txt
@@ -8,7 +8,10 @@ svn-fe - convert an SVN "dumpfile" to a fast-import stream
SYNOPSIS
--------
[verse]
-svnadmin dump --incremental REPO | svn-fe [url] | git fast-import
+mkfifo backchannel &&
+svnadmin dump --deltas REPO |
+ svn-fe [url] 3<backchannel |
+ git fast-import --cat-blob-fd=3 3>backchannel
DESCRIPTION
-----------
@@ -29,9 +32,6 @@ Subversion's repository dump format is documented in full in
Files in this format can be generated using the 'svnadmin dump' or
'svk admin dump' command.
-Dumps produced with 'svnadmin dump --deltas' (dumpfile format v3)
-are not supported.
-
OUTPUT FORMAT
-------------
The fast-import format is documented by the git-fast-import(1)
@@ -51,7 +51,7 @@ as committer, where 'user' is the value of the `svn:author` property
and 'UUID' the repository's identifier.
To support incremental imports, 'svn-fe' puts a `git-svn-id` line at
-the end of each commit log message if passed an url on the command
+the end of each commit log message if passed a URL on the command
line. This line has the form `git-svn-id: URL@REVNO UUID`.
The resulting repository will generally require further processing
diff --git a/convert.c b/convert.c
index efc7e07..6602155 100644
--- a/convert.c
+++ b/convert.c
@@ -2,6 +2,7 @@
#include "attr.h"
#include "run-command.h"
#include "quote.h"
+#include "sigchain.h"
/*
* convert.c - convert a file when checking it out and checking it in.
@@ -195,9 +196,17 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
char *dst;
if (crlf_action == CRLF_BINARY ||
- (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
+ (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) ||
+ (src && !len))
return 0;
+ /*
+ * If we are doing a dry-run and have no source buffer, there is
+ * nothing to analyze; we must assume we would convert.
+ */
+ if (!buf && !src)
+ return 1;
+
gather_stats(src, len, &stats);
if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
@@ -231,6 +240,13 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
if (!stats.cr)
return 0;
+ /*
+ * At this point all of our source analysis is done, and we are sure we
+ * would convert. If we are in dry-run mode, we can give an answer.
+ */
+ if (!buf)
+ return 1;
+
/* only grow if not in place */
if (strbuf_avail(buf) + buf->len < len)
strbuf_grow(buf, len - buf->len);
@@ -360,12 +376,16 @@ static int filter_buffer(int in, int out, void *data)
if (start_command(&child_process))
return error("cannot fork to run external filter %s", params->cmd);
+ sigchain_push(SIGPIPE, SIG_IGN);
+
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", params->cmd);
+ sigchain_pop(SIGPIPE);
+
status = finish_command(&child_process);
if (status)
error("external filter %s failed %d", params->cmd, status);
@@ -391,6 +411,9 @@ static int apply_filter(const char *path, const char *src, size_t len,
if (!cmd)
return 0;
+ if (!dst)
+ return 1;
+
memset(&async, 0, sizeof(async));
async.proc = filter_buffer;
async.data = &params;
@@ -429,6 +452,7 @@ static struct convert_driver {
struct convert_driver *next;
const char *smudge;
const char *clean;
+ int required;
} *user_convert, **user_convert_tail;
static int read_convert_config(const char *var, const char *value, void *cb)
@@ -472,6 +496,11 @@ static int read_convert_config(const char *var, const char *value, void *cb)
if (!strcmp("clean", ep))
return git_config_string(&drv->clean, var, value);
+ if (!strcmp("required", ep)) {
+ drv->required = git_config_bool(var, value);
+ return 0;
+ }
+
return 0;
}
@@ -522,9 +551,12 @@ static int ident_to_git(const char *path, const char *src, size_t len,
{
char *dst, *dollar;
- if (!ident || !count_ident(src, len))
+ if (!ident || (src && !count_ident(src, len)))
return 0;
+ if (!buf)
+ return 1;
+
/* only grow if not in place */
if (strbuf_avail(buf) + buf->len < len)
strbuf_grow(buf, len - buf->len);
@@ -533,7 +565,7 @@ static int ident_to_git(const char *path, const char *src, size_t len,
dollar = memchr(src, '$', len);
if (!dollar)
break;
- memcpy(dst, src, dollar + 1 - src);
+ memmove(dst, src, dollar + 1 - src);
dst += dollar + 1 - src;
len -= dollar + 1 - src;
src = dollar + 1;
@@ -553,7 +585,7 @@ static int ident_to_git(const char *path, const char *src, size_t len,
src = dollar + 1;
}
}
- memcpy(dst, src, len);
+ memmove(dst, src, len);
strbuf_setlen(buf, dst + len - buf->buf);
return 1;
}
@@ -641,7 +673,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
return 1;
}
-static int git_path_check_crlf(const char *path, struct git_attr_check *check)
+static enum crlf_action git_path_check_crlf(const char *path, struct git_attr_check *check)
{
const char *value = check->value;
@@ -658,7 +690,7 @@ static int git_path_check_crlf(const char *path, struct git_attr_check *check)
return CRLF_GUESS;
}
-static int git_path_check_eol(const char *path, struct git_attr_check *check)
+static enum eol git_path_check_eol(const char *path, struct git_attr_check *check)
{
const char *value = check->value;
@@ -727,7 +759,7 @@ static void convert_attrs(struct conv_attrs *ca, const char *path)
git_config(read_convert_config, NULL);
}
- if (!git_checkattr(path, NUM_CONV_ATTRS, ccheck)) {
+ if (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) {
ca->crlf_action = git_path_check_crlf(path, ccheck + 4);
if (ca->crlf_action == CRLF_GUESS)
ca->crlf_action = git_path_check_crlf(path, ccheck + 0);
@@ -747,20 +779,26 @@ int convert_to_git(const char *path, const char *src, size_t len,
{
int ret = 0;
const char *filter = NULL;
+ int required = 0;
struct conv_attrs ca;
convert_attrs(&ca, path);
- if (ca.drv)
+ if (ca.drv) {
filter = ca.drv->clean;
+ required = ca.drv->required;
+ }
ret |= apply_filter(path, src, len, dst, filter);
- if (ret) {
+ if (!ret && required)
+ die("%s: clean filter '%s' failed", path, ca.drv->name);
+
+ if (ret && dst) {
src = dst->buf;
len = dst->len;
}
ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe);
- if (ret) {
+ if (ret && dst) {
src = dst->buf;
len = dst->len;
}
@@ -771,13 +809,16 @@ static int convert_to_working_tree_internal(const char *path, const char *src,
size_t len, struct strbuf *dst,
int normalizing)
{
- int ret = 0;
+ int ret = 0, ret_filter = 0;
const char *filter = NULL;
+ int required = 0;
struct conv_attrs ca;
convert_attrs(&ca, path);
- if (ca.drv)
+ if (ca.drv) {
filter = ca.drv->smudge;
+ required = ca.drv->required;
+ }
ret |= ident_to_worktree(path, src, len, dst, ca.ident);
if (ret) {
@@ -796,7 +837,12 @@ static int convert_to_working_tree_internal(const char *path, const char *src,
len = dst->len;
}
}
- return ret | apply_filter(path, src, len, dst, filter);
+
+ ret_filter = apply_filter(path, src, len, dst, filter);
+ if (!ret_filter && required)
+ die("%s: smudge filter %s failed", path, ca.drv->name);
+
+ return ret | ret_filter;
}
int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
@@ -811,5 +857,468 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str
src = dst->buf;
len = dst->len;
}
- return ret | convert_to_git(path, src, len, dst, 0);
+ return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_FALSE);
+}
+
+/*****************************************************************
+ *
+ * Streaming converison support
+ *
+ *****************************************************************/
+
+typedef int (*filter_fn)(struct stream_filter *,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p);
+typedef void (*free_fn)(struct stream_filter *);
+
+struct stream_filter_vtbl {
+ filter_fn filter;
+ free_fn free;
+};
+
+struct stream_filter {
+ struct stream_filter_vtbl *vtbl;
+};
+
+static int null_filter_fn(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ size_t count;
+
+ if (!input)
+ return 0; /* we do not keep any states */
+ count = *isize_p;
+ if (*osize_p < count)
+ count = *osize_p;
+ if (count) {
+ memmove(output, input, count);
+ *isize_p -= count;
+ *osize_p -= count;
+ }
+ return 0;
+}
+
+static void null_free_fn(struct stream_filter *filter)
+{
+ ; /* nothing -- null instances are shared */
+}
+
+static struct stream_filter_vtbl null_vtbl = {
+ null_filter_fn,
+ null_free_fn,
+};
+
+static struct stream_filter null_filter_singleton = {
+ &null_vtbl,
+};
+
+int is_null_stream_filter(struct stream_filter *filter)
+{
+ return filter == &null_filter_singleton;
+}
+
+
+/*
+ * LF-to-CRLF filter
+ */
+
+struct lf_to_crlf_filter {
+ struct stream_filter filter;
+ unsigned has_held:1;
+ char held;
+};
+
+static int lf_to_crlf_filter_fn(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ size_t count, o = 0;
+ struct lf_to_crlf_filter *lf_to_crlf = (struct lf_to_crlf_filter *)filter;
+
+ /*
+ * We may be holding onto the CR to see if it is followed by a
+ * LF, in which case we would need to go to the main loop.
+ * Otherwise, just emit it to the output stream.
+ */
+ if (lf_to_crlf->has_held && (lf_to_crlf->held != '\r' || !input)) {
+ output[o++] = lf_to_crlf->held;
+ lf_to_crlf->has_held = 0;
+ }
+
+ /* We are told to drain */
+ if (!input) {
+ *osize_p -= o;
+ return 0;
+ }
+
+ count = *isize_p;
+ if (count || lf_to_crlf->has_held) {
+ size_t i;
+ int was_cr = 0;
+
+ if (lf_to_crlf->has_held) {
+ was_cr = 1;
+ lf_to_crlf->has_held = 0;
+ }
+
+ for (i = 0; o < *osize_p && i < count; i++) {
+ char ch = input[i];
+
+ if (ch == '\n') {
+ output[o++] = '\r';
+ } else if (was_cr) {
+ /*
+ * Previous round saw CR and it is not followed
+ * by a LF; emit the CR before processing the
+ * current character.
+ */
+ output[o++] = '\r';
+ }
+
+ /*
+ * We may have consumed the last output slot,
+ * in which case we need to break out of this
+ * loop; hold the current character before
+ * returning.
+ */
+ if (*osize_p <= o) {
+ lf_to_crlf->has_held = 1;
+ lf_to_crlf->held = ch;
+ continue; /* break but increment i */
+ }
+
+ if (ch == '\r') {
+ was_cr = 1;
+ continue;
+ }
+
+ was_cr = 0;
+ output[o++] = ch;
+ }
+
+ *osize_p -= o;
+ *isize_p -= i;
+
+ if (!lf_to_crlf->has_held && was_cr) {
+ lf_to_crlf->has_held = 1;
+ lf_to_crlf->held = '\r';
+ }
+ }
+ return 0;
+}
+
+static void lf_to_crlf_free_fn(struct stream_filter *filter)
+{
+ free(filter);
+}
+
+static struct stream_filter_vtbl lf_to_crlf_vtbl = {
+ lf_to_crlf_filter_fn,
+ lf_to_crlf_free_fn,
+};
+
+static struct stream_filter *lf_to_crlf_filter(void)
+{
+ struct lf_to_crlf_filter *lf_to_crlf = xcalloc(1, sizeof(*lf_to_crlf));
+
+ lf_to_crlf->filter.vtbl = &lf_to_crlf_vtbl;
+ return (struct stream_filter *)lf_to_crlf;
+}
+
+/*
+ * Cascade filter
+ */
+#define FILTER_BUFFER 1024
+struct cascade_filter {
+ struct stream_filter filter;
+ struct stream_filter *one;
+ struct stream_filter *two;
+ char buf[FILTER_BUFFER];
+ int end, ptr;
+};
+
+static int cascade_filter_fn(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ struct cascade_filter *cas = (struct cascade_filter *) filter;
+ size_t filled = 0;
+ size_t sz = *osize_p;
+ size_t to_feed, remaining;
+
+ /*
+ * input -- (one) --> buf -- (two) --> output
+ */
+ while (filled < sz) {
+ remaining = sz - filled;
+
+ /* do we already have something to feed two with? */
+ if (cas->ptr < cas->end) {
+ to_feed = cas->end - cas->ptr;
+ if (stream_filter(cas->two,
+ cas->buf + cas->ptr, &to_feed,
+ output + filled, &remaining))
+ return -1;
+ cas->ptr += (cas->end - cas->ptr) - to_feed;
+ filled = sz - remaining;
+ continue;
+ }
+
+ /* feed one from upstream and have it emit into our buffer */
+ to_feed = input ? *isize_p : 0;
+ if (input && !to_feed)
+ break;
+ remaining = sizeof(cas->buf);
+ if (stream_filter(cas->one,
+ input, &to_feed,
+ cas->buf, &remaining))
+ return -1;
+ cas->end = sizeof(cas->buf) - remaining;
+ cas->ptr = 0;
+ if (input) {
+ size_t fed = *isize_p - to_feed;
+ *isize_p -= fed;
+ input += fed;
+ }
+
+ /* do we know that we drained one completely? */
+ if (input || cas->end)
+ continue;
+
+ /* tell two to drain; we have nothing more to give it */
+ to_feed = 0;
+ remaining = sz - filled;
+ if (stream_filter(cas->two,
+ NULL, &to_feed,
+ output + filled, &remaining))
+ return -1;
+ if (remaining == (sz - filled))
+ break; /* completely drained two */
+ filled = sz - remaining;
+ }
+ *osize_p -= filled;
+ return 0;
+}
+
+static void cascade_free_fn(struct stream_filter *filter)
+{
+ struct cascade_filter *cas = (struct cascade_filter *)filter;
+ free_stream_filter(cas->one);
+ free_stream_filter(cas->two);
+ free(filter);
+}
+
+static struct stream_filter_vtbl cascade_vtbl = {
+ cascade_filter_fn,
+ cascade_free_fn,
+};
+
+static struct stream_filter *cascade_filter(struct stream_filter *one,
+ struct stream_filter *two)
+{
+ struct cascade_filter *cascade;
+
+ if (!one || is_null_stream_filter(one))
+ return two;
+ if (!two || is_null_stream_filter(two))
+ return one;
+
+ cascade = xmalloc(sizeof(*cascade));
+ cascade->one = one;
+ cascade->two = two;
+ cascade->end = cascade->ptr = 0;
+ cascade->filter.vtbl = &cascade_vtbl;
+ return (struct stream_filter *)cascade;
+}
+
+/*
+ * ident filter
+ */
+#define IDENT_DRAINING (-1)
+#define IDENT_SKIPPING (-2)
+struct ident_filter {
+ struct stream_filter filter;
+ struct strbuf left;
+ int state;
+ char ident[45]; /* ": x40 $" */
+};
+
+static int is_foreign_ident(const char *str)
+{
+ int i;
+
+ if (prefixcmp(str, "$Id: "))
+ return 0;
+ for (i = 5; str[i]; i++) {
+ if (isspace(str[i]) && str[i+1] != '$')
+ return 1;
+ }
+ return 0;
+}
+
+static void ident_drain(struct ident_filter *ident, char **output_p, size_t *osize_p)
+{
+ size_t to_drain = ident->left.len;
+
+ if (*osize_p < to_drain)
+ to_drain = *osize_p;
+ if (to_drain) {
+ memcpy(*output_p, ident->left.buf, to_drain);
+ strbuf_remove(&ident->left, 0, to_drain);
+ *output_p += to_drain;
+ *osize_p -= to_drain;
+ }
+ if (!ident->left.len)
+ ident->state = 0;
+}
+
+static int ident_filter_fn(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ struct ident_filter *ident = (struct ident_filter *)filter;
+ static const char head[] = "$Id";
+
+ if (!input) {
+ /* drain upon eof */
+ switch (ident->state) {
+ default:
+ strbuf_add(&ident->left, head, ident->state);
+ case IDENT_SKIPPING:
+ /* fallthru */
+ case IDENT_DRAINING:
+ ident_drain(ident, &output, osize_p);
+ }
+ return 0;
+ }
+
+ while (*isize_p || (ident->state == IDENT_DRAINING)) {
+ int ch;
+
+ if (ident->state == IDENT_DRAINING) {
+ ident_drain(ident, &output, osize_p);
+ if (!*osize_p)
+ break;
+ continue;
+ }
+
+ ch = *(input++);
+ (*isize_p)--;
+
+ if (ident->state == IDENT_SKIPPING) {
+ /*
+ * Skipping until '$' or LF, but keeping them
+ * in case it is a foreign ident.
+ */
+ strbuf_addch(&ident->left, ch);
+ if (ch != '\n' && ch != '$')
+ continue;
+ if (ch == '$' && !is_foreign_ident(ident->left.buf)) {
+ strbuf_setlen(&ident->left, sizeof(head) - 1);
+ strbuf_addstr(&ident->left, ident->ident);
+ }
+ ident->state = IDENT_DRAINING;
+ continue;
+ }
+
+ if (ident->state < sizeof(head) &&
+ head[ident->state] == ch) {
+ ident->state++;
+ continue;
+ }
+
+ if (ident->state)
+ strbuf_add(&ident->left, head, ident->state);
+ if (ident->state == sizeof(head) - 1) {
+ if (ch != ':' && ch != '$') {
+ strbuf_addch(&ident->left, ch);
+ ident->state = 0;
+ continue;
+ }
+
+ if (ch == ':') {
+ strbuf_addch(&ident->left, ch);
+ ident->state = IDENT_SKIPPING;
+ } else {
+ strbuf_addstr(&ident->left, ident->ident);
+ ident->state = IDENT_DRAINING;
+ }
+ continue;
+ }
+
+ strbuf_addch(&ident->left, ch);
+ ident->state = IDENT_DRAINING;
+ }
+ return 0;
+}
+
+static void ident_free_fn(struct stream_filter *filter)
+{
+ struct ident_filter *ident = (struct ident_filter *)filter;
+ strbuf_release(&ident->left);
+ free(filter);
+}
+
+static struct stream_filter_vtbl ident_vtbl = {
+ ident_filter_fn,
+ ident_free_fn,
+};
+
+static struct stream_filter *ident_filter(const unsigned char *sha1)
+{
+ struct ident_filter *ident = xmalloc(sizeof(*ident));
+
+ sprintf(ident->ident, ": %s $", sha1_to_hex(sha1));
+ strbuf_init(&ident->left, 0);
+ ident->filter.vtbl = &ident_vtbl;
+ ident->state = 0;
+ return (struct stream_filter *)ident;
+}
+
+/*
+ * Return an appropriately constructed filter for the path, or NULL if
+ * the contents cannot be filtered without reading the whole thing
+ * in-core.
+ *
+ * Note that you would be crazy to set CRLF, smuge/clean or ident to a
+ * large binary blob you would want us not to slurp into the memory!
+ */
+struct stream_filter *get_stream_filter(const char *path, const unsigned char *sha1)
+{
+ struct conv_attrs ca;
+ enum crlf_action crlf_action;
+ struct stream_filter *filter = NULL;
+
+ convert_attrs(&ca, path);
+
+ if (ca.drv && (ca.drv->smudge || ca.drv->clean))
+ return filter;
+
+ if (ca.ident)
+ filter = ident_filter(sha1);
+
+ crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+
+ if ((crlf_action == CRLF_BINARY) || (crlf_action == CRLF_INPUT) ||
+ (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE))
+ filter = cascade_filter(filter, &null_filter_singleton);
+
+ else if (output_eol(crlf_action) == EOL_CRLF &&
+ !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS))
+ filter = cascade_filter(filter, lf_to_crlf_filter());
+
+ return filter;
+}
+
+void free_stream_filter(struct stream_filter *filter)
+{
+ filter->vtbl->free(filter);
+}
+
+int stream_filter(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ return filter->vtbl->filter(filter, input, isize_p, output, osize_p);
}
diff --git a/convert.h b/convert.h
new file mode 100644
index 0000000..ec5fd69
--- /dev/null
+++ b/convert.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef CONVERT_H
+#define CONVERT_H
+
+enum safe_crlf {
+ SAFE_CRLF_FALSE = 0,
+ SAFE_CRLF_FAIL = 1,
+ SAFE_CRLF_WARN = 2
+};
+
+extern enum safe_crlf safe_crlf;
+
+enum auto_crlf {
+ AUTO_CRLF_FALSE = 0,
+ AUTO_CRLF_TRUE = 1,
+ AUTO_CRLF_INPUT = -1
+};
+
+extern enum auto_crlf auto_crlf;
+
+enum eol {
+ EOL_UNSET,
+ EOL_CRLF,
+ EOL_LF,
+#ifdef NATIVE_CRLF
+ EOL_NATIVE = EOL_CRLF
+#else
+ EOL_NATIVE = EOL_LF
+#endif
+};
+
+extern enum eol core_eol;
+
+/* returns 1 if *dst was used */
+extern int convert_to_git(const char *path, const char *src, size_t len,
+ struct strbuf *dst, enum safe_crlf checksafe);
+extern int convert_to_working_tree(const char *path, const char *src,
+ size_t len, struct strbuf *dst);
+extern int renormalize_buffer(const char *path, const char *src, size_t len,
+ struct strbuf *dst);
+static inline int would_convert_to_git(const char *path, const char *src,
+ size_t len, enum safe_crlf checksafe)
+{
+ return convert_to_git(path, src, len, NULL, checksafe);
+}
+
+/*****************************************************************
+ *
+ * Streaming converison support
+ *
+ *****************************************************************/
+
+struct stream_filter; /* opaque */
+
+extern struct stream_filter *get_stream_filter(const char *path, const unsigned char *);
+extern void free_stream_filter(struct stream_filter *);
+extern int is_null_stream_filter(struct stream_filter *);
+
+/*
+ * Use as much input up to *isize_p and fill output up to *osize_p;
+ * update isize_p and osize_p to indicate how much buffer space was
+ * consumed and filled. Return 0 on success, non-zero on error.
+ *
+ * Some filters may need to buffer the input and look-ahead inside it
+ * to decide what to output, and they may consume more than zero bytes
+ * of input and still not produce any output. After feeding all the
+ * input, pass NULL as input and keep calling this function, to let
+ * such filters know there is no more input coming and it is time for
+ * them to produce the remaining output based on the buffered input.
+ */
+extern int stream_filter(struct stream_filter *,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p);
+
+#endif /* CONVERT_H */
diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c
new file mode 100644
index 0000000..390f194
--- /dev/null
+++ b/credential-cache--daemon.c
@@ -0,0 +1,269 @@
+#include "cache.h"
+#include "credential.h"
+#include "unix-socket.h"
+#include "sigchain.h"
+
+static const char *socket_path;
+
+static void cleanup_socket(void)
+{
+ if (socket_path)
+ unlink(socket_path);
+}
+
+static void cleanup_socket_on_signal(int sig)
+{
+ cleanup_socket();
+ sigchain_pop(sig);
+ raise(sig);
+}
+
+struct credential_cache_entry {
+ struct credential item;
+ unsigned long expiration;
+};
+static struct credential_cache_entry *entries;
+static int entries_nr;
+static int entries_alloc;
+
+static void cache_credential(struct credential *c, int timeout)
+{
+ struct credential_cache_entry *e;
+
+ ALLOC_GROW(entries, entries_nr + 1, entries_alloc);
+ e = &entries[entries_nr++];
+
+ /* take ownership of pointers */
+ memcpy(&e->item, c, sizeof(*c));
+ memset(c, 0, sizeof(*c));
+ e->expiration = time(NULL) + timeout;
+}
+
+static struct credential_cache_entry *lookup_credential(const struct credential *c)
+{
+ int i;
+ for (i = 0; i < entries_nr; i++) {
+ struct credential *e = &entries[i].item;
+ if (credential_match(c, e))
+ return &entries[i];
+ }
+ return NULL;
+}
+
+static void remove_credential(const struct credential *c)
+{
+ struct credential_cache_entry *e;
+
+ e = lookup_credential(c);
+ if (e)
+ e->expiration = 0;
+}
+
+static int check_expirations(void)
+{
+ static unsigned long wait_for_entry_until;
+ int i = 0;
+ unsigned long now = time(NULL);
+ unsigned long next = (unsigned long)-1;
+
+ /*
+ * Initially give the client 30 seconds to actually contact us
+ * and store a credential before we decide there's no point in
+ * keeping the daemon around.
+ */
+ if (!wait_for_entry_until)
+ wait_for_entry_until = now + 30;
+
+ while (i < entries_nr) {
+ if (entries[i].expiration <= now) {
+ entries_nr--;
+ credential_clear(&entries[i].item);
+ if (i != entries_nr)
+ memcpy(&entries[i], &entries[entries_nr], sizeof(*entries));
+ /*
+ * Stick around 30 seconds in case a new credential
+ * shows up (e.g., because we just removed a failed
+ * one, and we will soon get the correct one).
+ */
+ wait_for_entry_until = now + 30;
+ }
+ else {
+ if (entries[i].expiration < next)
+ next = entries[i].expiration;
+ i++;
+ }
+ }
+
+ if (!entries_nr) {
+ if (wait_for_entry_until <= now)
+ return 0;
+ next = wait_for_entry_until;
+ }
+
+ return next - now;
+}
+
+static int read_request(FILE *fh, struct credential *c,
+ struct strbuf *action, int *timeout) {
+ static struct strbuf item = STRBUF_INIT;
+ const char *p;
+
+ strbuf_getline(&item, fh, '\n');
+ p = skip_prefix(item.buf, "action=");
+ if (!p)
+ return error("client sent bogus action line: %s", item.buf);
+ strbuf_addstr(action, p);
+
+ strbuf_getline(&item, fh, '\n');
+ p = skip_prefix(item.buf, "timeout=");
+ if (!p)
+ return error("client sent bogus timeout line: %s", item.buf);
+ *timeout = atoi(p);
+
+ if (credential_read(c, fh) < 0)
+ return -1;
+ return 0;
+}
+
+static void serve_one_client(FILE *in, FILE *out)
+{
+ struct credential c = CREDENTIAL_INIT;
+ struct strbuf action = STRBUF_INIT;
+ int timeout = -1;
+
+ if (read_request(in, &c, &action, &timeout) < 0)
+ /* ignore error */ ;
+ else if (!strcmp(action.buf, "get")) {
+ struct credential_cache_entry *e = lookup_credential(&c);
+ if (e) {
+ fprintf(out, "username=%s\n", e->item.username);
+ fprintf(out, "password=%s\n", e->item.password);
+ }
+ }
+ else if (!strcmp(action.buf, "exit"))
+ exit(0);
+ else if (!strcmp(action.buf, "erase"))
+ remove_credential(&c);
+ else if (!strcmp(action.buf, "store")) {
+ if (timeout < 0)
+ warning("cache client didn't specify a timeout");
+ else if (!c.username || !c.password)
+ warning("cache client gave us a partial credential");
+ else {
+ remove_credential(&c);
+ cache_credential(&c, timeout);
+ }
+ }
+ else
+ warning("cache client sent unknown action: %s", action.buf);
+
+ credential_clear(&c);
+ strbuf_release(&action);
+}
+
+static int serve_cache_loop(int fd)
+{
+ struct pollfd pfd;
+ unsigned long wakeup;
+
+ wakeup = check_expirations();
+ if (!wakeup)
+ return 0;
+
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+ if (poll(&pfd, 1, 1000 * wakeup) < 0) {
+ if (errno != EINTR)
+ die_errno("poll failed");
+ return 1;
+ }
+
+ if (pfd.revents & POLLIN) {
+ int client, client2;
+ FILE *in, *out;
+
+ client = accept(fd, NULL, NULL);
+ if (client < 0) {
+ warning("accept failed: %s", strerror(errno));
+ return 1;
+ }
+ client2 = dup(client);
+ if (client2 < 0) {
+ warning("dup failed: %s", strerror(errno));
+ close(client);
+ return 1;
+ }
+
+ in = xfdopen(client, "r");
+ out = xfdopen(client2, "w");
+ serve_one_client(in, out);
+ fclose(in);
+ fclose(out);
+ }
+ return 1;
+}
+
+static void serve_cache(const char *socket_path)
+{
+ int fd;
+
+ fd = unix_stream_listen(socket_path);
+ if (fd < 0)
+ die_errno("unable to bind to '%s'", socket_path);
+
+ printf("ok\n");
+ fclose(stdout);
+
+ while (serve_cache_loop(fd))
+ ; /* nothing */
+
+ close(fd);
+ unlink(socket_path);
+}
+
+static const char permissions_advice[] =
+"The permissions on your socket directory are too loose; other\n"
+"users may be able to read your cached credentials. Consider running:\n"
+"\n"
+" chmod 0700 %s";
+static void check_socket_directory(const char *path)
+{
+ struct stat st;
+ char *path_copy = xstrdup(path);
+ char *dir = dirname(path_copy);
+
+ if (!stat(dir, &st)) {
+ if (st.st_mode & 077)
+ die(permissions_advice, dir);
+ free(path_copy);
+ return;
+ }
+
+ /*
+ * We must be sure to create the directory with the correct mode,
+ * not just chmod it after the fact; otherwise, there is a race
+ * condition in which somebody can chdir to it, sleep, then try to open
+ * our protected socket.
+ */
+ if (safe_create_leading_directories_const(dir) < 0)
+ die_errno("unable to create directories for '%s'", dir);
+ if (mkdir(dir, 0700) < 0)
+ die_errno("unable to mkdir '%s'", dir);
+ free(path_copy);
+}
+
+int main(int argc, const char **argv)
+{
+ socket_path = argv[1];
+
+ if (!socket_path)
+ die("usage: git-credential-cache--daemon <socket_path>");
+ check_socket_directory(socket_path);
+
+ atexit(cleanup_socket);
+ sigchain_push_common(cleanup_socket_on_signal);
+
+ serve_cache(socket_path);
+
+ return 0;
+}
diff --git a/credential-cache.c b/credential-cache.c
new file mode 100644
index 0000000..9a03792
--- /dev/null
+++ b/credential-cache.c
@@ -0,0 +1,123 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "parse-options.h"
+#include "unix-socket.h"
+#include "run-command.h"
+
+#define FLAG_SPAWN 0x1
+#define FLAG_RELAY 0x2
+
+static int send_request(const char *socket, const struct strbuf *out)
+{
+ int got_data = 0;
+ int fd = unix_stream_connect(socket);
+
+ if (fd < 0)
+ return -1;
+
+ if (write_in_full(fd, out->buf, out->len) < 0)
+ die_errno("unable to write to cache daemon");
+ shutdown(fd, SHUT_WR);
+
+ while (1) {
+ char in[1024];
+ int r;
+
+ r = read_in_full(fd, in, sizeof(in));
+ if (r == 0)
+ break;
+ if (r < 0)
+ die_errno("read error from cache daemon");
+ write_or_die(1, in, r);
+ got_data = 1;
+ }
+ return got_data;
+}
+
+static void spawn_daemon(const char *socket)
+{
+ struct child_process daemon;
+ const char *argv[] = { NULL, NULL, NULL };
+ char buf[128];
+ int r;
+
+ memset(&daemon, 0, sizeof(daemon));
+ argv[0] = "git-credential-cache--daemon";
+ argv[1] = socket;
+ daemon.argv = argv;
+ daemon.no_stdin = 1;
+ daemon.out = -1;
+
+ if (start_command(&daemon))
+ die_errno("unable to start cache daemon");
+ r = read_in_full(daemon.out, buf, sizeof(buf));
+ if (r < 0)
+ die_errno("unable to read result code from cache daemon");
+ if (r != 3 || memcmp(buf, "ok\n", 3))
+ die("cache daemon did not start: %.*s", r, buf);
+ close(daemon.out);
+}
+
+static void do_cache(const char *socket, const char *action, int timeout,
+ int flags)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "action=%s\n", action);
+ strbuf_addf(&buf, "timeout=%d\n", timeout);
+ if (flags & FLAG_RELAY) {
+ if (strbuf_read(&buf, 0, 0) < 0)
+ die_errno("unable to relay credential");
+ }
+
+ if (send_request(socket, &buf) < 0) {
+ if (errno != ENOENT && errno != ECONNREFUSED)
+ die_errno("unable to connect to cache daemon");
+ if (flags & FLAG_SPAWN) {
+ spawn_daemon(socket);
+ if (send_request(socket, &buf) < 0)
+ die_errno("unable to connect to cache daemon");
+ }
+ }
+ strbuf_release(&buf);
+}
+
+int main(int argc, const char **argv)
+{
+ char *socket_path = NULL;
+ int timeout = 900;
+ const char *op;
+ const char * const usage[] = {
+ "git credential-cache [options] <action>",
+ NULL
+ };
+ struct option options[] = {
+ OPT_INTEGER(0, "timeout", &timeout,
+ "number of seconds to cache credentials"),
+ OPT_STRING(0, "socket", &socket_path, "path",
+ "path of cache-daemon socket"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, NULL, options, usage, 0);
+ if (!argc)
+ usage_with_options(usage, options);
+ op = argv[0];
+
+ if (!socket_path)
+ socket_path = expand_user_path("~/.git-credential-cache/socket");
+ if (!socket_path)
+ die("unable to find a suitable socket path; use --socket");
+
+ if (!strcmp(op, "exit"))
+ do_cache(socket_path, op, timeout, 0);
+ else if (!strcmp(op, "get") || !strcmp(op, "erase"))
+ do_cache(socket_path, op, timeout, FLAG_RELAY);
+ else if (!strcmp(op, "store"))
+ do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);
+ else
+ ; /* ignore unknown operation */
+
+ return 0;
+}
diff --git a/credential-store.c b/credential-store.c
new file mode 100644
index 0000000..26f7589
--- /dev/null
+++ b/credential-store.c
@@ -0,0 +1,157 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static struct lock_file credential_lock;
+
+static void parse_credential_file(const char *fn,
+ struct credential *c,
+ void (*match_cb)(struct credential *),
+ void (*other_cb)(struct strbuf *))
+{
+ FILE *fh;
+ struct strbuf line = STRBUF_INIT;
+ struct credential entry = CREDENTIAL_INIT;
+
+ fh = fopen(fn, "r");
+ if (!fh) {
+ if (errno != ENOENT)
+ die_errno("unable to open %s", fn);
+ return;
+ }
+
+ while (strbuf_getline(&line, fh, '\n') != EOF) {
+ credential_from_url(&entry, line.buf);
+ if (entry.username && entry.password &&
+ credential_match(c, &entry)) {
+ if (match_cb) {
+ match_cb(&entry);
+ break;
+ }
+ }
+ else if (other_cb)
+ other_cb(&line);
+ }
+
+ credential_clear(&entry);
+ strbuf_release(&line);
+ fclose(fh);
+}
+
+static void print_entry(struct credential *c)
+{
+ printf("username=%s\n", c->username);
+ printf("password=%s\n", c->password);
+}
+
+static void print_line(struct strbuf *buf)
+{
+ strbuf_addch(buf, '\n');
+ write_or_die(credential_lock.fd, buf->buf, buf->len);
+}
+
+static void rewrite_credential_file(const char *fn, struct credential *c,
+ struct strbuf *extra)
+{
+ if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0)
+ die_errno("unable to get credential storage lock");
+ if (extra)
+ print_line(extra);
+ parse_credential_file(fn, c, NULL, print_line);
+ if (commit_lock_file(&credential_lock) < 0)
+ die_errno("unable to commit credential store");
+}
+
+static void store_credential(const char *fn, struct credential *c)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ /*
+ * Sanity check that what we are storing is actually sensible.
+ * In particular, we can't make a URL without a protocol field.
+ * Without either a host or pathname (depending on the scheme),
+ * we have no primary key. And without a username and password,
+ * we are not actually storing a credential.
+ */
+ if (!c->protocol || !(c->host || c->path) ||
+ !c->username || !c->password)
+ return;
+
+ strbuf_addf(&buf, "%s://", c->protocol);
+ strbuf_addstr_urlencode(&buf, c->username, 1);
+ strbuf_addch(&buf, ':');
+ strbuf_addstr_urlencode(&buf, c->password, 1);
+ strbuf_addch(&buf, '@');
+ if (c->host)
+ strbuf_addstr_urlencode(&buf, c->host, 1);
+ if (c->path) {
+ strbuf_addch(&buf, '/');
+ strbuf_addstr_urlencode(&buf, c->path, 0);
+ }
+
+ rewrite_credential_file(fn, c, &buf);
+ strbuf_release(&buf);
+}
+
+static void remove_credential(const char *fn, struct credential *c)
+{
+ /*
+ * Sanity check that we actually have something to match
+ * against. The input we get is a restrictive pattern,
+ * so technically a blank credential means "erase everything".
+ * But it is too easy to accidentally send this, since it is equivalent
+ * to empty input. So explicitly disallow it, and require that the
+ * pattern have some actual content to match.
+ */
+ if (c->protocol || c->host || c->path || c->username)
+ rewrite_credential_file(fn, c, NULL);
+}
+
+static int lookup_credential(const char *fn, struct credential *c)
+{
+ parse_credential_file(fn, c, print_entry, NULL);
+ return c->username && c->password;
+}
+
+int main(int argc, const char **argv)
+{
+ const char * const usage[] = {
+ "git credential-store [options] <action>",
+ NULL
+ };
+ const char *op;
+ struct credential c = CREDENTIAL_INIT;
+ char *file = NULL;
+ struct option options[] = {
+ OPT_STRING(0, "file", &file, "path",
+ "fetch and store credentials in <path>"),
+ OPT_END()
+ };
+
+ umask(077);
+
+ argc = parse_options(argc, argv, NULL, options, usage, 0);
+ if (argc != 1)
+ usage_with_options(usage, options);
+ op = argv[0];
+
+ if (!file)
+ file = expand_user_path("~/.git-credentials");
+ if (!file)
+ die("unable to set up default path; use --file");
+
+ if (credential_read(&c, stdin) < 0)
+ die("unable to read credential");
+
+ if (!strcmp(op, "get"))
+ lookup_credential(file, &c);
+ else if (!strcmp(op, "erase"))
+ remove_credential(file, &c);
+ else if (!strcmp(op, "store"))
+ store_credential(file, &c);
+ else
+ ; /* Ignore unknown operation. */
+
+ return 0;
+}
diff --git a/credential.c b/credential.c
new file mode 100644
index 0000000..2c40007
--- /dev/null
+++ b/credential.c
@@ -0,0 +1,365 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "url.h"
+#include "prompt.h"
+
+void credential_init(struct credential *c)
+{
+ memset(c, 0, sizeof(*c));
+ c->helpers.strdup_strings = 1;
+}
+
+void credential_clear(struct credential *c)
+{
+ free(c->protocol);
+ free(c->host);
+ free(c->path);
+ free(c->username);
+ free(c->password);
+ string_list_clear(&c->helpers, 0);
+
+ credential_init(c);
+}
+
+int credential_match(const struct credential *want,
+ const struct credential *have)
+{
+#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
+ return CHECK(protocol) &&
+ CHECK(host) &&
+ CHECK(path) &&
+ CHECK(username);
+#undef CHECK
+}
+
+static int credential_config_callback(const char *var, const char *value,
+ void *data)
+{
+ struct credential *c = data;
+ const char *key, *dot;
+
+ key = skip_prefix(var, "credential.");
+ if (!key)
+ return 0;
+
+ if (!value)
+ return config_error_nonbool(var);
+
+ dot = strrchr(key, '.');
+ if (dot) {
+ struct credential want = CREDENTIAL_INIT;
+ char *url = xmemdupz(key, dot - key);
+ int matched;
+
+ credential_from_url(&want, url);
+ matched = credential_match(&want, c);
+
+ credential_clear(&want);
+ free(url);
+
+ if (!matched)
+ return 0;
+ key = dot + 1;
+ }
+
+ if (!strcmp(key, "helper"))
+ string_list_append(&c->helpers, value);
+ else if (!strcmp(key, "username")) {
+ if (!c->username)
+ c->username = xstrdup(value);
+ }
+ else if (!strcmp(key, "usehttppath"))
+ c->use_http_path = git_config_bool(var, value);
+
+ return 0;
+}
+
+static int proto_is_http(const char *s)
+{
+ if (!s)
+ return 0;
+ return !strcmp(s, "https") || !strcmp(s, "http");
+}
+
+static void credential_apply_config(struct credential *c)
+{
+ if (c->configured)
+ return;
+ git_config(credential_config_callback, c);
+ c->configured = 1;
+
+ if (!c->use_http_path && proto_is_http(c->protocol)) {
+ free(c->path);
+ c->path = NULL;
+ }
+}
+
+static void credential_describe(struct credential *c, struct strbuf *out)
+{
+ if (!c->protocol)
+ return;
+ strbuf_addf(out, "%s://", c->protocol);
+ if (c->username && *c->username)
+ strbuf_addf(out, "%s@", c->username);
+ if (c->host)
+ strbuf_addstr(out, c->host);
+ if (c->path)
+ strbuf_addf(out, "/%s", c->path);
+}
+
+static char *credential_ask_one(const char *what, struct credential *c,
+ int flags)
+{
+ struct strbuf desc = STRBUF_INIT;
+ struct strbuf prompt = STRBUF_INIT;
+ char *r;
+
+ credential_describe(c, &desc);
+ if (desc.len)
+ strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
+ else
+ strbuf_addf(&prompt, "%s: ", what);
+
+ r = git_prompt(prompt.buf, flags);
+
+ strbuf_release(&desc);
+ strbuf_release(&prompt);
+ return xstrdup(r);
+}
+
+static void credential_getpass(struct credential *c)
+{
+ if (!c->username)
+ c->username = credential_ask_one("Username", c,
+ PROMPT_ASKPASS|PROMPT_ECHO);
+ if (!c->password)
+ c->password = credential_ask_one("Password", c,
+ PROMPT_ASKPASS);
+}
+
+int credential_read(struct credential *c, FILE *fp)
+{
+ struct strbuf line = STRBUF_INIT;
+
+ while (strbuf_getline(&line, fp, '\n') != EOF) {
+ char *key = line.buf;
+ char *value = strchr(key, '=');
+
+ if (!line.len)
+ break;
+
+ if (!value) {
+ warning("invalid credential line: %s", key);
+ strbuf_release(&line);
+ return -1;
+ }
+ *value++ = '\0';
+
+ if (!strcmp(key, "username")) {
+ free(c->username);
+ c->username = xstrdup(value);
+ } else if (!strcmp(key, "password")) {
+ free(c->password);
+ c->password = xstrdup(value);
+ } else if (!strcmp(key, "protocol")) {
+ free(c->protocol);
+ c->protocol = xstrdup(value);
+ } else if (!strcmp(key, "host")) {
+ free(c->host);
+ c->host = xstrdup(value);
+ } else if (!strcmp(key, "path")) {
+ free(c->path);
+ c->path = xstrdup(value);
+ }
+ /*
+ * Ignore other lines; we don't know what they mean, but
+ * this future-proofs us when later versions of git do
+ * learn new lines, and the helpers are updated to match.
+ */
+ }
+
+ strbuf_release(&line);
+ return 0;
+}
+
+static void credential_write_item(FILE *fp, const char *key, const char *value)
+{
+ if (!value)
+ return;
+ fprintf(fp, "%s=%s\n", key, value);
+}
+
+void credential_write(const struct credential *c, FILE *fp)
+{
+ credential_write_item(fp, "protocol", c->protocol);
+ credential_write_item(fp, "host", c->host);
+ credential_write_item(fp, "path", c->path);
+ credential_write_item(fp, "username", c->username);
+ credential_write_item(fp, "password", c->password);
+}
+
+static int run_credential_helper(struct credential *c,
+ const char *cmd,
+ int want_output)
+{
+ struct child_process helper;
+ const char *argv[] = { NULL, NULL };
+ FILE *fp;
+
+ memset(&helper, 0, sizeof(helper));
+ argv[0] = cmd;
+ helper.argv = argv;
+ helper.use_shell = 1;
+ helper.in = -1;
+ if (want_output)
+ helper.out = -1;
+ else
+ helper.no_stdout = 1;
+
+ if (start_command(&helper) < 0)
+ return -1;
+
+ fp = xfdopen(helper.in, "w");
+ credential_write(c, fp);
+ fclose(fp);
+
+ if (want_output) {
+ int r;
+ fp = xfdopen(helper.out, "r");
+ r = credential_read(c, fp);
+ fclose(fp);
+ if (r < 0) {
+ finish_command(&helper);
+ return -1;
+ }
+ }
+
+ if (finish_command(&helper))
+ return -1;
+ return 0;
+}
+
+static int credential_do(struct credential *c, const char *helper,
+ const char *operation)
+{
+ struct strbuf cmd = STRBUF_INIT;
+ int r;
+
+ if (helper[0] == '!')
+ strbuf_addstr(&cmd, helper + 1);
+ else if (is_absolute_path(helper))
+ strbuf_addstr(&cmd, helper);
+ else
+ strbuf_addf(&cmd, "git credential-%s", helper);
+
+ strbuf_addf(&cmd, " %s", operation);
+ r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
+
+ strbuf_release(&cmd);
+ return r;
+}
+
+void credential_fill(struct credential *c)
+{
+ int i;
+
+ if (c->username && c->password)
+ return;
+
+ credential_apply_config(c);
+
+ for (i = 0; i < c->helpers.nr; i++) {
+ credential_do(c, c->helpers.items[i].string, "get");
+ if (c->username && c->password)
+ return;
+ }
+
+ credential_getpass(c);
+ if (!c->username && !c->password)
+ die("unable to get password from user");
+}
+
+void credential_approve(struct credential *c)
+{
+ int i;
+
+ if (c->approved)
+ return;
+ if (!c->username || !c->password)
+ return;
+
+ credential_apply_config(c);
+
+ for (i = 0; i < c->helpers.nr; i++)
+ credential_do(c, c->helpers.items[i].string, "store");
+ c->approved = 1;
+}
+
+void credential_reject(struct credential *c)
+{
+ int i;
+
+ credential_apply_config(c);
+
+ for (i = 0; i < c->helpers.nr; i++)
+ credential_do(c, c->helpers.items[i].string, "erase");
+
+ free(c->username);
+ c->username = NULL;
+ free(c->password);
+ c->password = NULL;
+ c->approved = 0;
+}
+
+void credential_from_url(struct credential *c, const char *url)
+{
+ const char *at, *colon, *cp, *slash, *host, *proto_end;
+
+ credential_clear(c);
+
+ /*
+ * Match one of:
+ * (1) proto://<host>/...
+ * (2) proto://<user>@<host>/...
+ * (3) proto://<user>:<pass>@<host>/...
+ */
+ proto_end = strstr(url, "://");
+ if (!proto_end)
+ return;
+ cp = proto_end + 3;
+ at = strchr(cp, '@');
+ colon = strchr(cp, ':');
+ slash = strchrnul(cp, '/');
+
+ if (!at || slash <= at) {
+ /* Case (1) */
+ host = cp;
+ }
+ else if (!colon || at <= colon) {
+ /* Case (2) */
+ c->username = url_decode_mem(cp, at - cp);
+ host = at + 1;
+ } else {
+ /* Case (3) */
+ c->username = url_decode_mem(cp, colon - cp);
+ c->password = url_decode_mem(colon + 1, at - (colon + 1));
+ host = at + 1;
+ }
+
+ if (proto_end - url > 0)
+ c->protocol = xmemdupz(url, proto_end - url);
+ if (slash - host > 0)
+ c->host = url_decode_mem(host, slash - host);
+ /* Trim leading and trailing slashes from path */
+ while (*slash == '/')
+ slash++;
+ if (*slash) {
+ char *p;
+ c->path = url_decode(slash);
+ p = c->path + strlen(c->path) - 1;
+ while (p > c->path && *p == '/')
+ *p-- = '\0';
+ }
+}
diff --git a/credential.h b/credential.h
new file mode 100644
index 0000000..0c3e85e
--- /dev/null
+++ b/credential.h
@@ -0,0 +1,34 @@
+#ifndef CREDENTIAL_H
+#define CREDENTIAL_H
+
+#include "string-list.h"
+
+struct credential {
+ struct string_list helpers;
+ unsigned approved:1,
+ configured:1,
+ use_http_path:1;
+
+ char *username;
+ char *password;
+ char *protocol;
+ char *host;
+ char *path;
+};
+
+#define CREDENTIAL_INIT { STRING_LIST_INIT_DUP }
+
+void credential_init(struct credential *);
+void credential_clear(struct credential *);
+
+void credential_fill(struct credential *);
+void credential_approve(struct credential *);
+void credential_reject(struct credential *);
+
+int credential_read(struct credential *, FILE *);
+void credential_write(const struct credential *, FILE *);
+void credential_from_url(struct credential *, const char *url);
+int credential_match(const struct credential *have,
+ const struct credential *want);
+
+#endif /* CREDENTIAL_H */
diff --git a/csum-file.c b/csum-file.c
index be49d5f..53f5375 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -11,8 +11,20 @@
#include "progress.h"
#include "csum-file.h"
-static void flush(struct sha1file *f, void * buf, unsigned int count)
+static void flush(struct sha1file *f, void *buf, unsigned int count)
{
+ if (0 <= f->check_fd && count) {
+ unsigned char check_buffer[8192];
+ ssize_t ret = read_in_full(f->check_fd, check_buffer, count);
+
+ if (ret < 0)
+ die_errno("%s: sha1 file read error", f->name);
+ if (ret < count)
+ die("%s: sha1 file truncated", f->name);
+ if (memcmp(buf, check_buffer, count))
+ die("sha1 file '%s' validation error", f->name);
+ }
+
for (;;) {
int ret = xwrite(f->fd, buf, count);
if (ret > 0) {
@@ -59,6 +71,17 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
fd = 0;
} else
fd = f->fd;
+ if (0 <= f->check_fd) {
+ char discard;
+ int cnt = read_in_full(f->check_fd, &discard, 1);
+ if (cnt < 0)
+ die_errno("%s: error when reading the tail of sha1 file",
+ f->name);
+ if (cnt)
+ die("%s: sha1 file has trailing garbage", f->name);
+ if (close(f->check_fd))
+ die_errno("%s: sha1 file error on close", f->name);
+ }
free(f);
return fd;
}
@@ -101,10 +124,31 @@ struct sha1file *sha1fd(int fd, const char *name)
return sha1fd_throughput(fd, name, NULL);
}
+struct sha1file *sha1fd_check(const char *name)
+{
+ int sink, check;
+ struct sha1file *f;
+
+ sink = open("/dev/null", O_WRONLY);
+ if (sink < 0)
+ return NULL;
+ check = open(name, O_RDONLY);
+ if (check < 0) {
+ int saved_errno = errno;
+ close(sink);
+ errno = saved_errno;
+ return NULL;
+ }
+ f = sha1fd(sink, name);
+ f->check_fd = check;
+ return f;
+}
+
struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp)
{
struct sha1file *f = xmalloc(sizeof(*f));
f->fd = fd;
+ f->check_fd = -1;
f->offset = 0;
f->total = 0;
f->tp = tp;
@@ -114,6 +158,26 @@ struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp
return f;
}
+void sha1file_checkpoint(struct sha1file *f, struct sha1file_checkpoint *checkpoint)
+{
+ sha1flush(f);
+ checkpoint->offset = f->total;
+ checkpoint->ctx = f->ctx;
+}
+
+int sha1file_truncate(struct sha1file *f, struct sha1file_checkpoint *checkpoint)
+{
+ off_t offset = checkpoint->offset;
+
+ if (ftruncate(f->fd, offset) ||
+ lseek(f->fd, offset, SEEK_SET) != offset)
+ return -1;
+ f->total = offset;
+ f->ctx = checkpoint->ctx;
+ f->offset = 0; /* sha1flush() was called in checkpoint */
+ return 0;
+}
+
void crc32_begin(struct sha1file *f)
{
f->crc32 = crc32(0, NULL, 0);
diff --git a/csum-file.h b/csum-file.h
index 294add2..3b540bd 100644
--- a/csum-file.h
+++ b/csum-file.h
@@ -6,6 +6,7 @@ struct progress;
/* A SHA1-protected file */
struct sha1file {
int fd;
+ int check_fd;
unsigned int offset;
git_SHA_CTX ctx;
off_t total;
@@ -16,11 +17,21 @@ struct sha1file {
unsigned char buffer[8192];
};
+/* Checkpoint */
+struct sha1file_checkpoint {
+ off_t offset;
+ git_SHA_CTX ctx;
+};
+
+extern void sha1file_checkpoint(struct sha1file *, struct sha1file_checkpoint *);
+extern int sha1file_truncate(struct sha1file *, struct sha1file_checkpoint *);
+
/* sha1close flags */
#define CSUM_CLOSE 1
#define CSUM_FSYNC 2
extern struct sha1file *sha1fd(int fd, const char *name);
+extern struct sha1file *sha1fd_check(const char *name);
extern struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp);
extern int sha1close(struct sha1file *, unsigned char *, unsigned int);
extern int sha1write(struct sha1file *, void *, unsigned int);
diff --git a/ctype.c b/ctype.c
index b5d856f..9353271 100644
--- a/ctype.c
+++ b/ctype.c
@@ -3,7 +3,7 @@
*
* No surprises, and works with signed and unsigned chars.
*/
-#include "cache.h"
+#include "git-compat-util.h"
enum {
S = GIT_SPACE,
@@ -25,3 +25,39 @@ unsigned char sane_ctype[256] = {
A, A, A, A, A, A, A, A, A, A, A, R, R, 0, P, 0, /* 112..127 */
/* Nothing in the 128.. range */
};
+
+/* For case-insensitive kwset */
+const char tolower_trans_tbl[256] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ ' ', '!', '"', '#', '$', '%', '&', 0x27,
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+ 'x', 'y', 'z', '[', 0x5c, ']', '^', '_',
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+ 'x', 'y', 'z', '{', '|', '}', '~', 0x7f,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+};
diff --git a/daemon.c b/daemon.c
index 4c8346d..ab21e66 100644
--- a/daemon.c
+++ b/daemon.c
@@ -20,6 +20,7 @@
static int log_syslog;
static int verbose;
static int reuseaddr;
+static int informative_errors;
static const char daemon_usage[] =
"git daemon [--verbose] [--syslog] [--export-all]\n"
@@ -108,11 +109,11 @@ static void NORETURN daemon_die(const char *err, va_list params)
exit(1);
}
-static char *path_ok(char *directory)
+static const char *path_ok(char *directory)
{
static char rpath[PATH_MAX];
static char interp_path[PATH_MAX];
- char *path;
+ const char *path;
char *dir;
dir = directory;
@@ -247,6 +248,14 @@ static int git_daemon_config(const char *var, const char *value, void *cb)
return 0;
}
+static int daemon_error(const char *dir, const char *msg)
+{
+ if (!informative_errors)
+ msg = "access denied or repository not exported";
+ packet_write(1, "ERR %s: %s", msg, dir);
+ return -1;
+}
+
static int run_service(char *dir, struct daemon_service *service)
{
const char *path;
@@ -257,11 +266,11 @@ static int run_service(char *dir, struct daemon_service *service)
if (!enabled && !service->overridable) {
logerror("'%s': service not enabled.", service->name);
errno = EACCES;
- return -1;
+ return daemon_error(dir, "service not enabled");
}
if (!(path = path_ok(dir)))
- return -1;
+ return daemon_error(dir, "no such repository");
/*
* Security on the cheap.
@@ -277,7 +286,7 @@ static int run_service(char *dir, struct daemon_service *service)
if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
logerror("'%s': repository not exported.", path);
errno = EACCES;
- return -1;
+ return daemon_error(dir, "repository not exported");
}
if (service->overridable) {
@@ -291,7 +300,7 @@ static int run_service(char *dir, struct daemon_service *service)
logerror("'%s': service not enabled for '%s'",
service->name, path);
errno = EACCES;
- return -1;
+ return daemon_error(dir, "service not enabled");
}
/*
@@ -734,6 +743,29 @@ struct socketlist {
size_t alloc;
};
+static const char *ip2str(int family, struct sockaddr *sin, socklen_t len)
+{
+#ifdef NO_IPV6
+ static char ip[INET_ADDRSTRLEN];
+#else
+ static char ip[INET6_ADDRSTRLEN];
+#endif
+
+ switch (family) {
+#ifndef NO_IPV6
+ case AF_INET6:
+ inet_ntop(family, &((struct sockaddr_in6*)sin)->sin6_addr, ip, len);
+ break;
+#endif
+ case AF_INET:
+ inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len);
+ break;
+ default:
+ strcpy(ip, "<unknown>");
+ }
+ return ip;
+}
+
#ifndef NO_IPV6
static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
@@ -780,15 +812,22 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
#endif
if (set_reuse_addr(sockfd)) {
+ logerror("Could not set SO_REUSEADDR: %s", strerror(errno));
close(sockfd);
continue;
}
if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+ logerror("Could not bind to %s: %s",
+ ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen),
+ strerror(errno));
close(sockfd);
continue; /* not fatal */
}
if (listen(sockfd, 5) < 0) {
+ logerror("Could not listen to %s: %s",
+ ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen),
+ strerror(errno));
close(sockfd);
continue; /* not fatal */
}
@@ -835,16 +874,23 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
return 0;
if (set_reuse_addr(sockfd)) {
+ logerror("Could not set SO_REUSEADDR: %s", strerror(errno));
close(sockfd);
return 0;
}
if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
+ logerror("Could not listen to %s: %s",
+ ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
+ strerror(errno));
close(sockfd);
return 0;
}
if (listen(sockfd, 5) < 0) {
+ logerror("Could not listen to %s: %s",
+ ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
+ strerror(errno));
close(sockfd);
return 0;
}
@@ -1040,6 +1086,8 @@ static int serve(struct string_list *listen_addr, int listen_port,
drop_privileges(cred);
+ loginfo("Ready to rumble");
+
return service_loop(&socklist);
}
@@ -1053,6 +1101,8 @@ int main(int argc, char **argv)
struct credentials *cred = NULL;
int i;
+ git_setup_gettext();
+
git_extract_argv0_path(argv[0]);
for (i = 1; i < argc; i++) {
@@ -1167,6 +1217,14 @@ int main(int argc, char **argv)
make_service_overridable(arg + 18, 0);
continue;
}
+ if (!prefixcmp(arg, "--informative-errors")) {
+ informative_errors = 1;
+ continue;
+ }
+ if (!prefixcmp(arg, "--no-informative-errors")) {
+ informative_errors = 0;
+ continue;
+ }
if (!strcmp(arg, "--")) {
ok_paths = &argv[i+1];
break;
@@ -1214,10 +1272,8 @@ int main(int argc, char **argv)
if (inetd_mode || serve_mode)
return execute();
- if (detach) {
+ if (detach)
daemonize();
- loginfo("Ready to rumble");
- }
else
sanitize_stdfds();
diff --git a/date.c b/date.c
index 353e0a5..1fdcf7c 100644
--- a/date.c
+++ b/date.c
@@ -86,83 +86,98 @@ static int local_tzoffset(unsigned long time)
return offset * eastwest;
}
-const char *show_date_relative(unsigned long time, int tz,
+void show_date_relative(unsigned long time, int tz,
const struct timeval *now,
- char *timebuf,
- size_t timebuf_size)
+ struct strbuf *timebuf)
{
unsigned long diff;
- if (now->tv_sec < time)
- return "in the future";
+ if (now->tv_sec < time) {
+ strbuf_addstr(timebuf, _("in the future"));
+ return;
+ }
diff = now->tv_sec - time;
if (diff < 90) {
- snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu second ago", "%lu seconds ago", diff), diff);
+ return;
}
/* Turn it into minutes */
diff = (diff + 30) / 60;
if (diff < 90) {
- snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu minute ago", "%lu minutes ago", diff), diff);
+ return;
}
/* Turn it into hours */
diff = (diff + 30) / 60;
if (diff < 36) {
- snprintf(timebuf, timebuf_size, "%lu hours ago", diff);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu hour ago", "%lu hours ago", diff), diff);
+ return;
}
/* We deal with number of days from here on */
diff = (diff + 12) / 24;
if (diff < 14) {
- snprintf(timebuf, timebuf_size, "%lu days ago", diff);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu day ago", "%lu days ago", diff), diff);
+ return;
}
/* Say weeks for the past 10 weeks or so */
if (diff < 70) {
- snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu week ago", "%lu weeks ago", (diff + 3) / 7),
+ (diff + 3) / 7);
+ return;
}
/* Say months for the past 12 months or so */
if (diff < 365) {
- snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu month ago", "%lu months ago", (diff + 15) / 30),
+ (diff + 15) / 30);
+ return;
}
/* Give years and months for 5 years or so */
if (diff < 1825) {
unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
unsigned long years = totalmonths / 12;
unsigned long months = totalmonths % 12;
- int n;
- n = snprintf(timebuf, timebuf_size, "%lu year%s",
- years, (years > 1 ? "s" : ""));
- if (months)
- snprintf(timebuf + n, timebuf_size - n,
- ", %lu month%s ago",
- months, (months > 1 ? "s" : ""));
- else
- snprintf(timebuf + n, timebuf_size - n, " ago");
- return timebuf;
+ if (months) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addf(&sb, Q_("%lu year", "%lu years", years), years);
+ /* TRANSLATORS: "%s" is "<n> years" */
+ strbuf_addf(timebuf,
+ Q_("%s, %lu month ago", "%s, %lu months ago", months),
+ sb.buf, months);
+ strbuf_release(&sb);
+ } else
+ strbuf_addf(timebuf,
+ Q_("%lu year ago", "%lu years ago", years), years);
+ return;
}
/* Otherwise, just years. Centuries is probably overkill. */
- snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);
- return timebuf;
+ strbuf_addf(timebuf,
+ Q_("%lu year ago", "%lu years ago", (diff + 183) / 365),
+ (diff + 183) / 365);
}
const char *show_date(unsigned long time, int tz, enum date_mode mode)
{
struct tm *tm;
- static char timebuf[200];
+ static struct strbuf timebuf = STRBUF_INIT;
if (mode == DATE_RAW) {
- snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
- return timebuf;
+ strbuf_reset(&timebuf);
+ strbuf_addf(&timebuf, "%lu %+05d", time, tz);
+ return timebuf.buf;
}
if (mode == DATE_RELATIVE) {
struct timeval now;
+
+ strbuf_reset(&timebuf);
gettimeofday(&now, NULL);
- return show_date_relative(time, tz, &now,
- timebuf, sizeof(timebuf));
+ show_date_relative(time, tz, &now, &timebuf);
+ return timebuf.buf;
}
if (mode == DATE_LOCAL)
@@ -171,23 +186,25 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
tm = time_to_tm(time, tz);
if (!tm)
return NULL;
+
+ strbuf_reset(&timebuf);
if (mode == DATE_SHORT)
- sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
+ strbuf_addf(&timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
tm->tm_mon + 1, tm->tm_mday);
else if (mode == DATE_ISO8601)
- sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
+ strbuf_addf(&timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec,
tz);
else if (mode == DATE_RFC2822)
- sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+ strbuf_addf(&timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
weekday_names[tm->tm_wday], tm->tm_mday,
month_names[tm->tm_mon], tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
else
- sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
+ strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
weekday_names[tm->tm_wday],
month_names[tm->tm_mon],
tm->tm_mday,
@@ -195,7 +212,7 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
tm->tm_year + 1900,
(mode == DATE_LOCAL) ? 0 : ' ',
tz);
- return timebuf;
+ return timebuf.buf;
}
/*
@@ -597,6 +614,33 @@ static int date_string(unsigned long date, int offset, char *buf, int len)
return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
}
+/*
+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+ * only when it appears not as part of any other string.
+ */
+static int match_object_header_date(const char *date, unsigned long *timestamp, int *offset)
+{
+ char *end;
+ unsigned long stamp;
+ int ofs;
+
+ if (*date < '0' || '9' <= *date)
+ return -1;
+ stamp = strtoul(date, &end, 10);
+ if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+ return -1;
+ date = end + 2;
+ ofs = strtol(date, &end, 10);
+ if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+ return -1;
+ ofs = (ofs / 100) * 60 + (ofs % 100);
+ if (date[-1] == '-')
+ ofs = -ofs;
+ *timestamp = stamp;
+ *offset = ofs;
+ return 0;
+}
+
/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
(i.e. English) day/month names, and it doesn't work correctly with %z. */
int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
@@ -622,6 +666,9 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
*offset = -1;
tm_gmt = 0;
+ if (*date == '@' &&
+ !match_object_header_date(date + 1, timestamp, offset))
+ return 0; /* success */
for (;;) {
int match = 0;
unsigned char c = *date;
diff --git a/diff-lib.c b/diff-lib.c
index b379759..fc0dff3 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -289,7 +289,7 @@ static void show_new_file(struct rev_info *revs,
/*
* New file in the index: it might actually be different in
- * the working copy.
+ * the working tree.
*/
if (get_stat_data(new, &sha1, &mode, cached, match_missing,
&dirty_submodule, &revs->diffopt) < 0)
@@ -445,20 +445,19 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
return 0;
}
-int run_diff_index(struct rev_info *revs, int cached)
+static int diff_cache(struct rev_info *revs,
+ const unsigned char *tree_sha1,
+ const char *tree_name,
+ int cached)
{
- struct object *ent;
struct tree *tree;
- const char *tree_name;
- struct unpack_trees_options opts;
struct tree_desc t;
+ struct unpack_trees_options opts;
- ent = revs->pending.objects[0].item;
- tree_name = revs->pending.objects[0].name;
- tree = parse_tree_indirect(ent->sha1);
+ tree = parse_tree_indirect(tree_sha1);
if (!tree)
- return error("bad tree object %s", tree_name);
-
+ return error("bad tree object %s",
+ tree_name ? tree_name : sha1_to_hex(tree_sha1));
memset(&opts, 0, sizeof(opts));
opts.head_idx = 1;
opts.index_only = cached;
@@ -469,9 +468,20 @@ int run_diff_index(struct rev_info *revs, int cached)
opts.unpack_data = revs;
opts.src_index = &the_index;
opts.dst_index = NULL;
+ opts.pathspec = &revs->diffopt.pathspec;
+ opts.pathspec->recursive = 1;
+ opts.pathspec->max_depth = -1;
init_tree_desc(&t, tree->buffer, tree->size);
- if (unpack_trees(1, &t, &opts))
+ return unpack_trees(1, &t, &opts);
+}
+
+int run_diff_index(struct rev_info *revs, int cached)
+{
+ struct object_array_entry *ent;
+
+ ent = revs->pending.objects;
+ if (diff_cache(revs, ent->item->sha1, ent->name, cached))
exit(128);
diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
@@ -483,53 +493,13 @@ int run_diff_index(struct rev_info *revs, int cached)
int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
{
- struct tree *tree;
struct rev_info revs;
- 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;
- * it potentially needs to repeatedly run this, so we will
- * start by removing the higher order entries the last round
- * left behind.
- */
- dst = active_cache;
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (ce_stage(ce)) {
- if (last && !strcmp(ce->name, last->name))
- continue;
- cache_tree_invalidate_path(active_cache_tree,
- ce->name);
- last = ce;
- ce->ce_flags |= CE_REMOVE;
- }
- *dst++ = ce;
- }
- active_nr = dst - active_cache;
init_revisions(&revs, NULL);
init_pathspec(&revs.prune_data, opt->pathspec.raw);
- tree = parse_tree_indirect(tree_sha1);
- if (!tree)
- die("bad tree object %s", sha1_to_hex(tree_sha1));
+ revs.diffopt = *opt;
- memset(&opts, 0, sizeof(opts));
- opts.head_idx = 1;
- opts.index_only = 1;
- opts.diff_index_cached = !DIFF_OPT_TST(opt, FIND_COPIES_HARDER);
- opts.merge = 1;
- opts.fn = oneway_diff;
- opts.unpack_data = &revs;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
-
- init_tree_desc(&t, tree->buffer, tree->size);
- if (unpack_trees(1, &t, &opts))
+ if (diff_cache(&revs, tree_sha1, NULL, 1))
exit(128);
return 0;
}
diff --git a/diff-no-index.c b/diff-no-index.c
index 3a36144..beec49b 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -52,7 +52,7 @@ static int get_mode(const char *path, int *mode)
}
static int queue_diff(struct diff_options *o,
- const char *name1, const char *name2)
+ const char *name1, const char *name2)
{
int mode1 = 0, mode2 = 0;
@@ -63,10 +63,12 @@ static int queue_diff(struct diff_options *o,
return error("file/directory conflict: %s, %s", name1, name2);
if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
- char buffer1[PATH_MAX], buffer2[PATH_MAX];
+ struct strbuf buffer1 = STRBUF_INIT;
+ struct strbuf buffer2 = STRBUF_INIT;
struct string_list p1 = STRING_LIST_INIT_DUP;
struct string_list p2 = STRING_LIST_INIT_DUP;
- int len1 = 0, len2 = 0, i1, i2, ret = 0;
+ int i1, i2, ret = 0;
+ size_t len1 = 0, len2 = 0;
if (name1 && read_directory(name1, &p1))
return -1;
@@ -76,53 +78,53 @@ static int queue_diff(struct diff_options *o,
}
if (name1) {
- len1 = strlen(name1);
- if (len1 > 0 && name1[len1 - 1] == '/')
- len1--;
- memcpy(buffer1, name1, len1);
- buffer1[len1++] = '/';
+ strbuf_addstr(&buffer1, name1);
+ if (buffer1.len && buffer1.buf[buffer1.len - 1] != '/')
+ strbuf_addch(&buffer1, '/');
+ len1 = buffer1.len;
}
if (name2) {
- len2 = strlen(name2);
- if (len2 > 0 && name2[len2 - 1] == '/')
- len2--;
- memcpy(buffer2, name2, len2);
- buffer2[len2++] = '/';
+ strbuf_addstr(&buffer2, name2);
+ if (buffer2.len && buffer2.buf[buffer2.len - 1] != '/')
+ strbuf_addch(&buffer2, '/');
+ len2 = buffer2.len;
}
for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
const char *n1, *n2;
int comp;
+ strbuf_setlen(&buffer1, len1);
+ strbuf_setlen(&buffer2, len2);
+
if (i1 == p1.nr)
comp = 1;
else if (i2 == p2.nr)
comp = -1;
else
- comp = strcmp(p1.items[i1].string,
- p2.items[i2].string);
+ comp = strcmp(p1.items[i1].string, p2.items[i2].string);
if (comp > 0)
n1 = NULL;
else {
- n1 = buffer1;
- strncpy(buffer1 + len1, p1.items[i1++].string,
- PATH_MAX - len1);
+ strbuf_addstr(&buffer1, p1.items[i1++].string);
+ n1 = buffer1.buf;
}
if (comp < 0)
n2 = NULL;
else {
- n2 = buffer2;
- strncpy(buffer2 + len2, p2.items[i2++].string,
- PATH_MAX - len2);
+ strbuf_addstr(&buffer2, p2.items[i2++].string);
+ n2 = buffer2.buf;
}
ret = queue_diff(o, n1, n2);
}
string_list_clear(&p1, 0);
string_list_clear(&p2, 0);
+ strbuf_release(&buffer1);
+ strbuf_release(&buffer2);
return ret;
} else {
@@ -149,23 +151,6 @@ static int queue_diff(struct diff_options *o,
}
}
-static int path_outside_repo(const char *path)
-{
- const char *work_tree;
- size_t len;
-
- if (!is_absolute_path(path))
- return 0;
- work_tree = get_git_work_tree();
- if (!work_tree)
- return 1;
- len = strlen(work_tree);
- if (strncmp(path, work_tree, len) ||
- (path[len] != '\0' && path[len] != '/'))
- return 1;
- return 0;
-}
-
void diff_no_index(struct rev_info *revs,
int argc, const char **argv,
int nongit, const char *prefix)
@@ -195,8 +180,8 @@ void diff_no_index(struct rev_info *revs,
* a colourful "diff" replacement.
*/
if ((argc != i + 2) ||
- (!path_outside_repo(argv[i]) &&
- !path_outside_repo(argv[i+1])))
+ (path_inside_repo(prefix, argv[i]) &&
+ path_inside_repo(prefix, argv[i+1])))
return;
}
if (argc != i + 2)
@@ -222,13 +207,6 @@ void diff_no_index(struct rev_info *revs,
}
}
- /*
- * 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 (!DIFF_OPT_TST(&revs->diffopt, EXIT_WITH_STATUS))
- setup_pager();
-
if (prefix) {
int len = strlen(prefix);
const char *paths[3];
@@ -253,13 +231,15 @@ void diff_no_index(struct rev_info *revs,
if (!revs->diffopt.output_format)
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
- DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
revs->max_count = -2;
if (diff_setup_done(&revs->diffopt) < 0)
die("diff_setup_done failed");
+ setup_diff_pager(&revs->diffopt);
+ DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
+
if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0],
revs->diffopt.pathspec.raw[1]))
exit(1);
@@ -271,5 +251,5 @@ void diff_no_index(struct rev_info *revs,
* The return code for --no-index imitates diff(1):
* 0 = no changes, 1 = changes, else error
*/
- exit(revs->diffopt.found_changes);
+ exit(diff_result_code(&revs->diffopt, 0));
}
diff --git a/diff.c b/diff.c
index 9038f19..1a594df 100644
--- a/diff.c
+++ b/diff.c
@@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
static int diff_mnemonic_prefix;
static int diff_no_prefix;
+static int diff_stat_graph_width;
static int diff_dirstat_permille_default = 30;
static struct diff_options default_diff_options;
@@ -137,7 +138,7 @@ static int git_config_rename(const char *var, const char *value)
int git_diff_ui_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
- diff_use_color_default = git_config_colorbool(var, value, -1);
+ diff_use_color_default = git_config_colorbool(var, value);
return 0;
}
if (!strcmp(var, "diff.renames")) {
@@ -156,6 +157,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
diff_no_prefix = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "diff.statgraphwidth")) {
+ diff_stat_graph_width = git_config_int(var, value);
+ return 0;
+ }
if (!strcmp(var, "diff.external"))
return git_config_string(&external_diff_cmd_cfg, var, value);
if (!strcmp(var, "diff.wordregex"))
@@ -164,6 +169,9 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
if (!strcmp(var, "diff.ignoresubmodules"))
handle_ignore_submodules_arg(&default_diff_options, value);
+ if (git_color_config(var, value, cb) < 0)
+ return -1;
+
return git_diff_basic_config(var, value, cb);
}
@@ -174,11 +182,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
return 0;
}
- switch (userdiff_config(var, value)) {
- case 0: break;
- case -1: return -1;
- default: return 0;
- }
+ if (userdiff_config(var, value) < 0)
+ return -1;
if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
int slot = parse_diff_color_slot(var, 11);
@@ -212,7 +217,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
if (!prefixcmp(var, "submodule."))
return parse_submodule_config_option(var, value);
- return git_color_default_config(var, value, cb);
+ return git_default_config(var, value, cb);
}
static char *quote_two(const char *one, const char *two)
@@ -583,11 +588,10 @@ static void emit_rewrite_diff(const char *name_a,
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 *reset = diff_get_color(color_diff, DIFF_RESET);
+ const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
+ const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
+ const char *reset = diff_get_color(o->use_color, DIFF_RESET);
static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
const char *a_prefix, *b_prefix;
char *data_one, *data_two;
@@ -623,7 +627,7 @@ static void emit_rewrite_diff(const char *name_a,
size_two = fill_textconv(textconv_two, two, &data_two);
memset(&ecbdata, 0, sizeof(ecbdata));
- ecbdata.color_diff = color_diff;
+ ecbdata.color_diff = want_color(o->use_color);
ecbdata.found_changesp = &o->found_changes;
ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
ecbdata.opt = o;
@@ -985,10 +989,74 @@ static void diff_words_flush(struct emit_callback *ecbdata)
diff_words_show(ecbdata->diff_words);
}
+static void diff_filespec_load_driver(struct diff_filespec *one)
+{
+ /* Use already-loaded driver */
+ if (one->driver)
+ return;
+
+ if (S_ISREG(one->mode))
+ one->driver = userdiff_find_by_path(one->path);
+
+ /* Fallback to default settings */
+ if (!one->driver)
+ one->driver = userdiff_find_by_name("default");
+}
+
+static const char *userdiff_word_regex(struct diff_filespec *one)
+{
+ diff_filespec_load_driver(one);
+ return one->driver->word_regex;
+}
+
+static void init_diff_words_data(struct emit_callback *ecbdata,
+ struct diff_options *orig_opts,
+ struct diff_filespec *one,
+ struct diff_filespec *two)
+{
+ int i;
+ struct diff_options *o = xmalloc(sizeof(struct diff_options));
+ memcpy(o, orig_opts, sizeof(struct diff_options));
+
+ ecbdata->diff_words =
+ xcalloc(1, sizeof(struct diff_words_data));
+ ecbdata->diff_words->type = o->word_diff;
+ ecbdata->diff_words->opt = o;
+ if (!o->word_regex)
+ o->word_regex = userdiff_word_regex(one);
+ if (!o->word_regex)
+ o->word_regex = userdiff_word_regex(two);
+ if (!o->word_regex)
+ o->word_regex = diff_word_regex_cfg;
+ if (o->word_regex) {
+ ecbdata->diff_words->word_regex = (regex_t *)
+ xmalloc(sizeof(regex_t));
+ if (regcomp(ecbdata->diff_words->word_regex,
+ o->word_regex,
+ REG_EXTENDED | REG_NEWLINE))
+ die ("Invalid regular expression: %s",
+ o->word_regex);
+ }
+ for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
+ if (o->word_diff == diff_words_styles[i].type) {
+ ecbdata->diff_words->style =
+ &diff_words_styles[i];
+ break;
+ }
+ }
+ if (want_color(o->use_color)) {
+ struct diff_words_style *st = ecbdata->diff_words->style;
+ st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
+ st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
+ st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
+ }
+}
+
static void free_diff_words_data(struct emit_callback *ecbdata)
{
if (ecbdata->diff_words) {
diff_words_flush(ecbdata);
+ free (ecbdata->diff_words->opt);
free (ecbdata->diff_words->minus.text.ptr);
free (ecbdata->diff_words->minus.orig);
free (ecbdata->diff_words->plus.text.ptr);
@@ -1004,7 +1072,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
const char *diff_get_color(int diff_use_color, enum color_diff ix)
{
- if (diff_use_color)
+ if (want_color(diff_use_color))
return diff_colors[ix];
return "";
}
@@ -1111,6 +1179,15 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
diff_words_append(line, len,
&ecbdata->diff_words->plus);
return;
+ } else if (!prefixcmp(line, "\\ ")) {
+ /*
+ * Eat the "no newline at eof" marker as if we
+ * saw a "+" or "-" line with nothing on it,
+ * and return without diff_words_flush() to
+ * defer processing. If this is the end of
+ * preimage, more "+" lines may come after it.
+ */
+ return;
}
diff_words_flush(ecbdata);
if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
@@ -1265,13 +1342,15 @@ const char mime_boundary_leader[] = "------------";
static int scale_linear(int it, int width, int max_change)
{
+ if (!it)
+ return 0;
/*
- * make sure that at least one '-' is printed if there were deletions,
- * and likewise for '+'.
+ * make sure that at least one '-' or '+' is printed if
+ * there is any change to this path. The easiest way is to
+ * scale linearly as if the alloted width is one column shorter
+ * than it is, and then add 1 to the result.
*/
- if (max_change < 2)
- return it;
- return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+ return 1 + (it * (width - 1) / max_change);
}
static void show_name(FILE *file,
@@ -1311,14 +1390,64 @@ static void fill_print_name(struct diffstat_file *file)
file->print_name = pname;
}
+int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
+
+ if (!files) {
+ assert(insertions == 0 && deletions == 0);
+ return fputs(_(" 0 files changed\n"), fp);
+ }
+
+ strbuf_addf(&sb,
+ Q_(" %d file changed", " %d files changed", files),
+ files);
+
+ /*
+ * For binary diff, the caller may want to print "x files
+ * changed" with insertions == 0 && deletions == 0.
+ *
+ * Not omitting "0 insertions(+), 0 deletions(-)" in this case
+ * is probably less confusing (i.e skip over "2 files changed
+ * but nothing about added/removed lines? Is this a bug in Git?").
+ */
+ if (insertions || deletions == 0) {
+ /*
+ * TRANSLATORS: "+" in (+) is a line addition marker;
+ * do not translate it.
+ */
+ strbuf_addf(&sb,
+ Q_(", %d insertion(+)", ", %d insertions(+)",
+ insertions),
+ insertions);
+ }
+
+ if (deletions || insertions == 0) {
+ /*
+ * TRANSLATORS: "-" in (-) is a line removal marker;
+ * do not translate it.
+ */
+ strbuf_addf(&sb,
+ Q_(", %d deletion(-)", ", %d deletions(-)",
+ deletions),
+ deletions);
+ }
+ strbuf_addch(&sb, '\n');
+ ret = fputs(sb.buf, fp);
+ strbuf_release(&sb);
+ return ret;
+}
+
static void show_stats(struct diffstat_t *data, struct diff_options *options)
{
int i, len, add, del, adds = 0, dels = 0;
uintmax_t max_change = 0, max_len = 0;
- int total_files = data->nr;
- int width, name_width;
+ int total_files = data->nr, count;
+ int width, name_width, graph_width, number_width = 0, bin_width = 0;
const char *reset, *add_c, *del_c;
const char *line_prefix = "";
+ int extra_shown = 0;
struct strbuf *msg = NULL;
if (data->nr == 0)
@@ -1329,58 +1458,144 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
line_prefix = msg->buf;
}
- width = options->stat_width ? options->stat_width : 80;
- name_width = options->stat_name_width ? options->stat_name_width : 50;
+ count = options->stat_count ? options->stat_count : data->nr;
- /* Sanity: give at least 5 columns to the graph,
- * but leave at least 10 columns for the name.
- */
- if (width < 25)
- width = 25;
- if (name_width < 10)
- name_width = 10;
- else if (width < name_width + 15)
- name_width = width - 15;
-
- /* Find the longest filename and max number of changes */
reset = diff_get_color_opt(options, DIFF_RESET);
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++) {
+ /*
+ * Find the longest filename and max number of changes
+ */
+ for (i = 0; (i < count) && (i < data->nr); i++) {
struct diffstat_file *file = data->files[i];
uintmax_t change = file->added + file->deleted;
+ if (!data->files[i]->is_renamed &&
+ (change == 0)) {
+ count++; /* not shown == room for one more */
+ continue;
+ }
fill_print_name(file);
len = strlen(file->print_name);
if (max_len < len)
max_len = len;
- if (file->is_binary || file->is_unmerged)
+ if (file->is_unmerged) {
+ /* "Unmerged" is 8 characters */
+ bin_width = bin_width < 8 ? 8 : bin_width;
+ continue;
+ }
+ if (file->is_binary) {
+ /* "Bin XXX -> YYY bytes" */
+ int w = 14 + decimal_width(file->added)
+ + decimal_width(file->deleted);
+ bin_width = bin_width < w ? w : bin_width;
+ /* Display change counts aligned with "Bin" */
+ number_width = 3;
continue;
+ }
+
if (max_change < change)
max_change = change;
}
+ count = i; /* min(count, data->nr) */
- /* Compute the width of the graph part;
- * 10 is for one blank at the beginning of the line plus
- * " | count " between the name and the graph.
+ /*
+ * We have width = stat_width or term_columns() columns total.
+ * We want a maximum of min(max_len, stat_name_width) for the name part.
+ * We want a maximum of min(max_change, stat_graph_width) for the +- part.
+ * We also need 1 for " " and 4 + decimal_width(max_change)
+ * for " | NNNN " and one the empty column at the end, altogether
+ * 6 + decimal_width(max_change).
+ *
+ * If there's not enough space, we will use the smaller of
+ * stat_name_width (if set) and 5/8*width for the filename,
+ * and the rest for constant elements + graph part, but no more
+ * than stat_graph_width for the graph part.
+ * (5/8 gives 50 for filename and 30 for the constant parts + graph
+ * for the standard terminal size).
+ *
+ * In other words: stat_width limits the maximum width, and
+ * stat_name_width fixes the maximum width of the filename,
+ * and is also used to divide available columns if there
+ * aren't enough.
*
- * From here on, name_width is the width of the name area,
- * and width is the width of the graph area.
+ * Binary files are displayed with "Bin XXX -> YYY bytes"
+ * instead of the change count and graph. This part is treated
+ * similarly to the graph part, except that it is not
+ * "scaled". If total width is too small to accomodate the
+ * guaranteed minimum width of the filename part and the
+ * separators and this message, this message will "overflow"
+ * making the line longer than the maximum width.
*/
- name_width = (name_width < max_len) ? name_width : max_len;
- if (width < (name_width + 10) + max_change)
- width = width - (name_width + 10);
+
+ if (options->stat_width == -1)
+ width = term_columns() - options->output_prefix_length;
else
- width = max_change;
+ width = options->stat_width ? options->stat_width : 80;
+ number_width = decimal_width(max_change) > number_width ?
+ decimal_width(max_change) : number_width;
- for (i = 0; i < data->nr; i++) {
+ if (options->stat_graph_width == -1)
+ options->stat_graph_width = diff_stat_graph_width;
+
+ /*
+ * Guarantee 3/8*16==6 for the graph part
+ * and 5/8*16==10 for the filename part
+ */
+ if (width < 16 + 6 + number_width)
+ width = 16 + 6 + number_width;
+
+ /*
+ * First assign sizes that are wanted, ignoring available width.
+ * strlen("Bin XXX -> YYY bytes") == bin_width, and the part
+ * starting from "XXX" should fit in graph_width.
+ */
+ graph_width = max_change + 4 > bin_width ? max_change : bin_width - 4;
+ if (options->stat_graph_width &&
+ options->stat_graph_width < graph_width)
+ graph_width = options->stat_graph_width;
+
+ name_width = (options->stat_name_width > 0 &&
+ options->stat_name_width < max_len) ?
+ options->stat_name_width : max_len;
+
+ /*
+ * Adjust adjustable widths not to exceed maximum width
+ */
+ if (name_width + number_width + 6 + graph_width > width) {
+ if (graph_width > width * 3/8 - number_width - 6) {
+ graph_width = width * 3/8 - number_width - 6;
+ if (graph_width < 6)
+ graph_width = 6;
+ }
+
+ if (options->stat_graph_width &&
+ graph_width > options->stat_graph_width)
+ graph_width = options->stat_graph_width;
+ if (name_width > width - number_width - 6 - graph_width)
+ name_width = width - number_width - 6 - graph_width;
+ else
+ graph_width = width - number_width - 6 - name_width;
+ }
+
+ /*
+ * From here name_width is the width of the name area,
+ * and graph_width is the width of the graph area.
+ * max_change is used to scale graph properly.
+ */
+ for (i = 0; i < count; i++) {
const char *prefix = "";
char *name = data->files[i]->print_name;
uintmax_t added = data->files[i]->added;
uintmax_t deleted = data->files[i]->deleted;
int name_len;
+ if (!data->files[i]->is_renamed &&
+ (added + deleted == 0)) {
+ total_files--;
+ continue;
+ }
/*
* "scale" the filename
*/
@@ -1399,8 +1614,12 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
if (data->files[i]->is_binary) {
fprintf(options->file, "%s", line_prefix);
show_name(options->file, prefix, name, len);
- fprintf(options->file, " Bin ");
- fprintf(options->file, "%s%"PRIuMAX"%s",
+ fprintf(options->file, " %*s", number_width, "Bin");
+ if (!added && !deleted) {
+ putc('\n', options->file);
+ continue;
+ }
+ fprintf(options->file, " %s%"PRIuMAX"%s",
del_c, deleted, reset);
fprintf(options->file, " -> ");
fprintf(options->file, "%s%"PRIuMAX"%s",
@@ -1412,12 +1631,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
else if (data->files[i]->is_unmerged) {
fprintf(options->file, "%s", line_prefix);
show_name(options->file, prefix, name, len);
- fprintf(options->file, " Unmerged\n");
- continue;
- }
- else if (!data->files[i]->is_renamed &&
- (added + deleted == 0)) {
- total_files--;
+ fprintf(options->file, " Unmerged\n");
continue;
}
@@ -1429,22 +1643,46 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
adds += add;
dels += del;
- if (width <= max_change) {
- add = scale_linear(add, width, max_change);
- del = scale_linear(del, width, max_change);
+ if (graph_width <= max_change) {
+ int total = add + del;
+
+ total = scale_linear(add + del, graph_width, max_change);
+ if (total < 2 && add && del)
+ /* width >= 2 due to the sanity check */
+ total = 2;
+ if (add < del) {
+ add = scale_linear(add, graph_width, max_change);
+ del = total - add;
+ } else {
+ del = scale_linear(del, graph_width, max_change);
+ add = total - del;
+ }
}
fprintf(options->file, "%s", line_prefix);
show_name(options->file, prefix, name, len);
- fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
- added + deleted ? " " : "");
+ fprintf(options->file, " %*"PRIuMAX"%s",
+ number_width, added + deleted,
+ added + deleted ? " " : "");
show_graph(options->file, '+', add, add_c, reset);
show_graph(options->file, '-', del, del_c, reset);
fprintf(options->file, "\n");
}
+ for (i = count; i < data->nr; i++) {
+ uintmax_t added = data->files[i]->added;
+ uintmax_t deleted = data->files[i]->deleted;
+ if (!data->files[i]->is_renamed &&
+ (added + deleted == 0)) {
+ total_files--;
+ continue;
+ }
+ adds += added;
+ dels += deleted;
+ if (!extra_shown)
+ fprintf(options->file, "%s ...\n", line_prefix);
+ extra_shown = 1;
+ }
fprintf(options->file, "%s", line_prefix);
- fprintf(options->file,
- " %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ print_stat_summary(options->file, total_files, adds, dels);
}
static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
@@ -1455,17 +1693,16 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
return;
for (i = 0; i < data->nr; i++) {
- if (!data->files[i]->is_binary &&
- !data->files[i]->is_unmerged) {
- int added = data->files[i]->added;
- int deleted= data->files[i]->deleted;
- if (!data->files[i]->is_renamed &&
- (added + deleted == 0)) {
- total_files--;
- } else {
- adds += added;
- dels += deleted;
- }
+ int added = data->files[i]->added;
+ int deleted= data->files[i]->deleted;
+
+ if (data->files[i]->is_unmerged)
+ continue;
+ if (!data->files[i]->is_renamed && (added + deleted == 0)) {
+ total_files--;
+ } else if (!data->files[i]->is_binary) { /* don't count bytes */
+ adds += added;
+ dels += deleted;
}
}
if (options->output_prefix) {
@@ -1474,8 +1711,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
options->output_prefix_data);
fprintf(options->file, "%s", msg->buf);
}
- fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ print_stat_summary(options->file, total_files, adds, dels);
}
static void show_numstat(struct diffstat_t *data, struct diff_options *options)
@@ -1786,11 +2022,10 @@ static int is_conflict_marker(const char *line, int marker_size, unsigned long l
static void checkdiff_consume(void *priv, char *line, unsigned long len)
{
struct checkdiff_t *data = priv;
- int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
int marker_size = data->conflict_marker_size;
- const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
- const char *reset = diff_get_color(color_diff, DIFF_RESET);
- const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
+ const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
+ const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
+ const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
char *err;
char *line_prefix = "";
struct strbuf *msgbuf;
@@ -1925,20 +2160,6 @@ static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *pre
emit_binary_diff_body(file, two, one, prefix);
}
-static void diff_filespec_load_driver(struct diff_filespec *one)
-{
- /* Use already-loaded driver */
- if (one->driver)
- return;
-
- if (S_ISREG(one->mode))
- one->driver = userdiff_find_by_path(one->path);
-
- /* Fallback to default settings */
- if (!one->driver)
- one->driver = userdiff_find_by_name("default");
-}
-
int diff_filespec_is_binary(struct diff_filespec *one)
{
if (one->is_binary == -1) {
@@ -1964,12 +2185,6 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe
return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
}
-static const char *userdiff_word_regex(struct diff_filespec *one)
-{
- diff_filespec_load_driver(one);
- return one->driver->word_regex;
-}
-
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
{
if (!options->a_prefix)
@@ -2119,7 +2334,7 @@ static void builtin_diff(const char *name_a,
struct emit_callback ecbdata;
const struct userdiff_funcname *pe;
- if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
+ if (must_show_header) {
fprintf(o->file, "%s", header.buf);
strbuf_reset(&header);
}
@@ -2135,7 +2350,7 @@ 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 = DIFF_OPT_TST(o, COLOR_DIFF);
+ ecbdata.color_diff = want_color(o->use_color);
ecbdata.found_changesp = &o->found_changes;
ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
@@ -2146,6 +2361,8 @@ static void builtin_diff(const char *name_a,
xecfg.ctxlen = o->context;
xecfg.interhunkctxlen = o->interhunkcontext;
xecfg.flags = XDL_EMIT_FUNCNAMES;
+ if (DIFF_OPT_TST(o, FUNCCONTEXT))
+ xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
if (pe)
xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
if (!diffopts)
@@ -2154,42 +2371,8 @@ static void builtin_diff(const char *name_a,
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
else if (!prefixcmp(diffopts, "-u"))
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
- if (o->word_diff) {
- int i;
-
- ecbdata.diff_words =
- xcalloc(1, sizeof(struct diff_words_data));
- ecbdata.diff_words->type = o->word_diff;
- ecbdata.diff_words->opt = o;
- if (!o->word_regex)
- o->word_regex = userdiff_word_regex(one);
- if (!o->word_regex)
- o->word_regex = userdiff_word_regex(two);
- if (!o->word_regex)
- o->word_regex = diff_word_regex_cfg;
- if (o->word_regex) {
- ecbdata.diff_words->word_regex = (regex_t *)
- xmalloc(sizeof(regex_t));
- if (regcomp(ecbdata.diff_words->word_regex,
- o->word_regex,
- REG_EXTENDED | REG_NEWLINE))
- die ("Invalid regular expression: %s",
- o->word_regex);
- }
- for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
- if (o->word_diff == diff_words_styles[i].type) {
- ecbdata.diff_words->style =
- &diff_words_styles[i];
- break;
- }
- }
- if (DIFF_OPT_TST(o, COLOR_DIFF)) {
- struct diff_words_style *st = ecbdata.diff_words->style;
- st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
- st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
- st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
- }
- }
+ if (o->word_diff)
+ init_diff_words_data(&ecbdata, o, one, two);
xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
&xpp, &xecfg);
if (o->word_diff)
@@ -2219,6 +2402,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
{
mmfile_t mf1, mf2;
struct diffstat_file *data;
+ int same_contents;
data = diffstat_add(diffstat, name_a, name_b);
@@ -2227,10 +2411,17 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
return;
}
+ same_contents = !hashcmp(one->sha1, two->sha1);
+
if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
data->is_binary = 1;
- data->added = diff_filespec_size(two);
- data->deleted = diff_filespec_size(one);
+ if (same_contents) {
+ data->added = 0;
+ data->deleted = 0;
+ } else {
+ data->added = diff_filespec_size(two);
+ data->deleted = diff_filespec_size(one);
+ }
}
else if (complete_rewrite) {
@@ -2240,7 +2431,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
data->added = count_lines(two->data, two->size);
}
- else {
+ else if (!same_contents) {
/* Crazy xdl interfaces.. */
xpparam_t xpp;
xdemitconf_t xecfg;
@@ -2251,6 +2442,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
xpp.flags = o->xdl_opts;
+ xecfg.ctxlen = o->context;
+ xecfg.interhunkctxlen = o->interhunkcontext;
xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
&xpp, &xecfg);
}
@@ -2833,7 +3026,7 @@ static void run_diff_cmd(const char *pgm,
*/
fill_metainfo(msg, name, other, one, two, o, p,
&must_show_header,
- DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
+ want_color(o->use_color) && !pgm);
xfrm_msg = msg->len ? msg->buf : NULL;
}
@@ -2996,11 +3189,11 @@ void diff_setup(struct diff_options *options)
options->rename_limit = -1;
options->dirstat_permille = diff_dirstat_permille_default;
options->context = 3;
+ DIFF_OPT_SET(options, RENAME_EMPTY);
options->change = diff_change;
options->add_remove = diff_addremove;
- if (diff_use_color_default > 0)
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = diff_use_color_default;
options->detect_rename = diff_detect_rename_default;
if (diff_no_prefix) {
@@ -3208,6 +3401,8 @@ static int stat_opt(struct diff_options *options, const char **av)
char *end;
int width = options->stat_width;
int name_width = options->stat_name_width;
+ int graph_width = options->stat_graph_width;
+ int count = options->stat_count;
int argcount = 1;
arg += strlen("--stat");
@@ -3235,12 +3430,34 @@ static int stat_opt(struct diff_options *options, const char **av)
name_width = strtoul(av[1], &end, 10);
argcount = 2;
}
+ } else if (!prefixcmp(arg, "-graph-width")) {
+ arg += strlen("-graph-width");
+ if (*arg == '=')
+ graph_width = strtoul(arg + 1, &end, 10);
+ else if (!*arg && !av[1])
+ die("Option '--stat-graph-width' requires a value");
+ else if (!*arg) {
+ graph_width = strtoul(av[1], &end, 10);
+ argcount = 2;
+ }
+ } else if (!prefixcmp(arg, "-count")) {
+ arg += strlen("-count");
+ if (*arg == '=')
+ count = strtoul(arg + 1, &end, 10);
+ else if (!*arg && !av[1])
+ die("Option '--stat-count' requires a value");
+ else if (!*arg) {
+ count = strtoul(av[1], &end, 10);
+ argcount = 2;
+ }
}
break;
case '=':
width = strtoul(arg+1, &end, 10);
if (*end == ',')
name_width = strtoul(end+1, &end, 10);
+ if (*end == ',')
+ count = strtoul(end+1, &end, 10);
}
/* Important! This checks all the error cases! */
@@ -3248,7 +3465,9 @@ static int stat_opt(struct diff_options *options, const char **av)
return 0;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
options->stat_name_width = name_width;
+ options->stat_graph_width = graph_width;
options->stat_width = width;
+ options->stat_count = count;
return argcount;
}
@@ -3313,7 +3532,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "-s"))
options->output_format |= DIFF_FORMAT_NO_OUTPUT;
else if (!prefixcmp(arg, "--stat"))
- /* --stat, --stat-width, or --stat-name-width */
+ /* --stat, --stat-width, --stat-name-width, or --stat-count */
return stat_opt(options, av);
/* renames options */
@@ -3341,6 +3560,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
}
else if (!strcmp(arg, "--no-renames"))
options->detect_rename = 0;
+ else if (!strcmp(arg, "--rename-empty"))
+ DIFF_OPT_SET(options, RENAME_EMPTY);
+ else if (!strcmp(arg, "--no-rename-empty"))
+ DIFF_OPT_CLR(options, RENAME_EMPTY);
else if (!strcmp(arg, "--relative"))
DIFF_OPT_SET(options, RELATIVE_NAME);
else if (!prefixcmp(arg, "--relative=")) {
@@ -3349,6 +3572,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
}
/* xdiff options */
+ else if (!strcmp(arg, "--minimal"))
+ DIFF_XDL_SET(options, NEED_MINIMAL);
+ else if (!strcmp(arg, "--no-minimal"))
+ DIFF_XDL_CLR(options, NEED_MINIMAL);
else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
DIFF_XDL_SET(options, IGNORE_WHITESPACE);
else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
@@ -3356,7 +3583,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "--ignore-space-at-eol"))
DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
else if (!strcmp(arg, "--patience"))
- DIFF_XDL_SET(options, PATIENCE_DIFF);
+ options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
+ else if (!strcmp(arg, "--histogram"))
+ options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
/* flags options */
else if (!strcmp(arg, "--binary")) {
@@ -3374,24 +3603,21 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "--follow"))
DIFF_OPT_SET(options, FOLLOW_RENAMES);
else if (!strcmp(arg, "--color"))
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = 1;
else if (!prefixcmp(arg, "--color=")) {
- int value = git_config_colorbool(NULL, arg+8, -1);
- if (value == 0)
- DIFF_OPT_CLR(options, COLOR_DIFF);
- else if (value > 0)
- DIFF_OPT_SET(options, COLOR_DIFF);
- else
+ int value = git_config_colorbool(NULL, arg+8);
+ if (value < 0)
return error("option `color' expects \"always\", \"auto\", or \"never\"");
+ options->use_color = value;
}
else if (!strcmp(arg, "--no-color"))
- DIFF_OPT_CLR(options, COLOR_DIFF);
+ options->use_color = 0;
else if (!strcmp(arg, "--color-words")) {
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = 1;
options->word_diff = DIFF_WORDS_COLOR;
}
else if (!prefixcmp(arg, "--color-words=")) {
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = 1;
options->word_diff = DIFF_WORDS_COLOR;
options->word_regex = arg + 14;
}
@@ -3404,7 +3630,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
if (!strcmp(type, "plain"))
options->word_diff = DIFF_WORDS_PLAIN;
else if (!strcmp(type, "color")) {
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = 1;
options->word_diff = DIFF_WORDS_COLOR;
}
else if (!strcmp(type, "porcelain"))
@@ -3495,6 +3721,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (opt_arg(arg, '\0', "inter-hunk-context",
&options->interhunkcontext))
;
+ else if (!strcmp(arg, "-W"))
+ DIFF_OPT_SET(options, FUNCCONTEXT);
+ else if (!strcmp(arg, "--function-context"))
+ DIFF_OPT_SET(options, FUNCCONTEXT);
+ else if (!strcmp(arg, "--no-function-context"))
+ DIFF_OPT_CLR(options, FUNCCONTEXT);
else if ((argcount = parse_long_opt("output", av, &optarg))) {
options->file = fopen(optarg, "w");
if (!options->file)
@@ -4225,6 +4457,12 @@ void diff_flush(struct diff_options *options)
if (output_format & DIFF_FORMAT_PATCH) {
if (separator) {
+ if (options->output_prefix) {
+ struct strbuf *msg = NULL;
+ msg = options->output_prefix(options,
+ options->output_prefix_data);
+ fwrite(msg->buf, msg->len, 1, stdout);
+ }
putc(options->line_termination, options->file);
if (options->stat_sep) {
/* attach patch instead of inline */
diff --git a/diff.h b/diff.h
index 6d303c1..e027650 100644
--- a/diff.h
+++ b/diff.h
@@ -12,6 +12,8 @@ struct diff_queue_struct;
struct strbuf;
struct diff_filespec;
struct userdiff_driver;
+struct sha1_array;
+struct commit;
typedef void (*change_fn_t)(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
@@ -58,7 +60,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#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_RENAME_EMPTY (1 << 8)
/* (1 << 9) unused */
#define DIFF_OPT_HAS_CHANGES (1 << 10)
#define DIFF_OPT_QUICK (1 << 11)
@@ -79,6 +81,8 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
#define DIFF_OPT_DIRSTAT_BY_LINE (1 << 28)
+#define DIFF_OPT_FUNCCONTEXT (1 << 29)
+#define DIFF_OPT_PICKAXE_IGNORE_CASE (1 << 30)
#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)
@@ -87,6 +91,8 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#define DIFF_XDL_SET(opts, flag) ((opts)->xdl_opts |= XDF_##flag)
#define DIFF_XDL_CLR(opts, flag) ((opts)->xdl_opts &= ~XDF_##flag)
+#define DIFF_WITH_ALG(opts, flag) (((opts)->xdl_opts & ~XDF_DIFF_ALGORITHM_MASK) | XDF_##flag)
+
enum diff_words_type {
DIFF_WORDS_NONE = 0,
DIFF_WORDS_PORCELAIN,
@@ -101,6 +107,7 @@ struct diff_options {
const char *single_follow;
const char *a_prefix, *b_prefix;
unsigned flags;
+ int use_color;
int context;
int interhunkcontext;
int break_opt;
@@ -125,6 +132,8 @@ struct diff_options {
int stat_width;
int stat_name_width;
+ int stat_graph_width;
+ int stat_count;
const char *word_regex;
enum diff_words_type word_diff;
@@ -143,6 +152,7 @@ struct diff_options {
diff_format_fn_t format_callback;
void *format_callback_data;
diff_prefix_fn_t output_prefix;
+ int output_prefix_length;
void *output_prefix_data;
};
@@ -159,7 +169,7 @@ enum color_diff {
};
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)
+ diff_get_color((o)->use_color, ix)
extern const char mime_boundary_leader[];
@@ -192,9 +202,9 @@ struct combine_diff_path {
extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
int dense, struct rev_info *);
-extern void diff_tree_combined(const unsigned char *sha1, const unsigned char parent[][20], int num_parent, int dense, struct rev_info *rev);
+extern void diff_tree_combined(const unsigned char *sha1, const struct sha1_array *parents, int dense, struct rev_info *rev);
-extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
+extern void diff_tree_combined_merge(const struct commit *commit, int dense, struct rev_info *rev);
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b);
@@ -319,4 +329,7 @@ extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
extern int parse_rename_score(const char **cp_p);
+extern int print_stat_summary(FILE *fp, int files,
+ int insertions, int deletions);
+
#endif /* DIFF_H */
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c
index ea03b91..ed23eb4 100644
--- a/diffcore-pickaxe.c
+++ b/diffcore-pickaxe.c
@@ -6,6 +6,47 @@
#include "diff.h"
#include "diffcore.h"
#include "xdiff-interface.h"
+#include "kwset.h"
+
+typedef int (*pickaxe_fn)(struct diff_filepair *p, struct diff_options *o, regex_t *regexp, kwset_t kws);
+
+static void pickaxe(struct diff_queue_struct *q, struct diff_options *o,
+ regex_t *regexp, kwset_t kws, pickaxe_fn fn)
+{
+ int i;
+ struct diff_queue_struct outq;
+
+ DIFF_QUEUE_CLEAR(&outq);
+
+ if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
+ /* Showing the whole changeset if needle exists */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (fn(p, o, regexp, kws))
+ return; /* do not munge the queue */
+ }
+
+ /*
+ * Otherwise we will clear the whole queue by copying
+ * the empty outq at the end of this function, but
+ * first clear the current entries in the queue.
+ */
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+ } else {
+ /* Showing only the filepairs that has the needle */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (fn(p, o, regexp, kws))
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+ }
+
+ free(q->queue);
+ *q = outq;
+}
struct diffgrep_cb {
regex_t *regexp;
@@ -44,7 +85,8 @@ static void fill_one(struct diff_filespec *one,
}
}
-static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o)
+static int diff_grep(struct diff_filepair *p, struct diff_options *o,
+ regex_t *regexp, kwset_t kws)
{
regmatch_t regmatch;
struct userdiff_driver *textconv_one = NULL;
@@ -94,14 +136,14 @@ static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_optio
static void diffcore_pickaxe_grep(struct diff_options *o)
{
- struct diff_queue_struct *q = &diff_queued_diff;
- int i, has_changes, err;
+ int err;
regex_t regex;
- struct diff_queue_struct outq;
- outq.queue = NULL;
- outq.nr = outq.alloc = 0;
+ int cflags = REG_EXTENDED | REG_NEWLINE;
+
+ if (DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE))
+ cflags |= REG_ICASE;
- err = regcomp(&regex, o->pickaxe, REG_EXTENDED | REG_NEWLINE);
+ err = regcomp(&regex, o->pickaxe, cflags);
if (err) {
char errbuf[1024];
regerror(err, &regex, errbuf, 1024);
@@ -109,51 +151,21 @@ static void diffcore_pickaxe_grep(struct diff_options *o)
die("invalid log-grep regex: %s", errbuf);
}
- if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
- /* Showing the whole changeset if needle exists */
- for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- if (diff_grep(p, &regex, o))
- has_changes++;
- }
- if (has_changes)
- return; /* do not munge the queue */
-
- /*
- * Otherwise we will clear the whole queue by copying
- * the empty outq at the end of this function, but
- * first clear the current entries in the queue.
- */
- for (i = 0; i < q->nr; i++)
- diff_free_filepair(q->queue[i]);
- } else {
- /* Showing only the filepairs that has the needle */
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- if (diff_grep(p, &regex, o))
- diff_q(&outq, p);
- else
- diff_free_filepair(p);
- }
- }
+ pickaxe(&diff_queued_diff, o, &regex, NULL, diff_grep);
regfree(&regex);
-
- free(q->queue);
- *q = outq;
return;
}
-static unsigned int contains(struct diff_filespec *one,
- const char *needle, unsigned long len,
- regex_t *regexp)
+static unsigned int contains(struct diff_filespec *one, struct diff_options *o,
+ regex_t *regexp, kwset_t kws)
{
unsigned int cnt;
unsigned long sz;
const char *data;
- if (diff_populate_filespec(one, 0))
+ if (!o->pickaxe[0])
return 0;
- if (!len)
+ if (diff_populate_filespec(one, 0))
return 0;
sz = one->size;
@@ -175,11 +187,15 @@ static unsigned int contains(struct diff_filespec *one,
} else { /* Classic exact string match */
while (sz) {
- const char *found = memmem(data, sz, needle, len);
- if (!found)
+ struct kwsmatch kwsm;
+ size_t offset = kwsexec(kws, data, sz, &kwsm);
+ const char *found;
+ if (offset == -1)
break;
- sz -= found - data + len;
- data = found + len;
+ else
+ found = data + offset;
+ sz -= found - data + kwsm.size[0];
+ data = found + kwsm.size[0];
cnt++;
}
}
@@ -187,16 +203,31 @@ static unsigned int contains(struct diff_filespec *one,
return cnt;
}
+static int has_changes(struct diff_filepair *p, struct diff_options *o,
+ regex_t *regexp, kwset_t kws)
+{
+ if (!DIFF_FILE_VALID(p->one)) {
+ if (!DIFF_FILE_VALID(p->two))
+ return 0; /* ignore unmerged */
+ /* created */
+ return contains(p->two, o, regexp, kws) != 0;
+ }
+ if (!DIFF_FILE_VALID(p->two))
+ return contains(p->one, o, regexp, kws) != 0;
+ if (!diff_unmodified_pair(p)) {
+ return contains(p->one, o, regexp, kws) !=
+ contains(p->two, o, regexp, kws);
+ }
+ return 0;
+}
+
static void diffcore_pickaxe_count(struct diff_options *o)
{
const char *needle = o->pickaxe;
int opts = o->pickaxe_opts;
- struct diff_queue_struct *q = &diff_queued_diff;
unsigned long len = strlen(needle);
- int i, has_changes;
regex_t regex, *regexp = NULL;
- struct diff_queue_struct outq;
- DIFF_QUEUE_CLEAR(&outq);
+ kwset_t kws = NULL;
if (opts & DIFF_PICKAXE_REGEX) {
int err;
@@ -209,71 +240,19 @@ static void diffcore_pickaxe_count(struct diff_options *o)
die("invalid pickaxe regex: %s", errbuf);
}
regexp = &regex;
+ } else {
+ kws = kwsalloc(DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE)
+ ? tolower_trans_tbl : NULL);
+ kwsincr(kws, needle, len);
+ kwsprep(kws);
}
- if (opts & DIFF_PICKAXE_ALL) {
- /* Showing the whole changeset if needle exists */
- for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- if (!DIFF_FILE_VALID(p->one)) {
- if (!DIFF_FILE_VALID(p->two))
- continue; /* ignore unmerged */
- /* created */
- if (contains(p->two, needle, len, regexp))
- has_changes++;
- }
- else if (!DIFF_FILE_VALID(p->two)) {
- if (contains(p->one, needle, len, regexp))
- has_changes++;
- }
- else if (!diff_unmodified_pair(p) &&
- contains(p->one, needle, len, regexp) !=
- contains(p->two, needle, len, regexp))
- has_changes++;
- }
- if (has_changes)
- return; /* not munge the queue */
-
- /* otherwise we will clear the whole queue
- * by copying the empty outq at the end of this
- * function, but first clear the current entries
- * in the queue.
- */
- for (i = 0; i < q->nr; i++)
- diff_free_filepair(q->queue[i]);
- }
- else
- /* Showing only the filepairs that has the needle */
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- has_changes = 0;
- if (!DIFF_FILE_VALID(p->one)) {
- if (!DIFF_FILE_VALID(p->two))
- ; /* ignore unmerged */
- /* created */
- else if (contains(p->two, needle, len, regexp))
- has_changes = 1;
- }
- else if (!DIFF_FILE_VALID(p->two)) {
- if (contains(p->one, needle, len, regexp))
- has_changes = 1;
- }
- else if (!diff_unmodified_pair(p) &&
- contains(p->one, needle, len, regexp) !=
- contains(p->two, needle, len, regexp))
- has_changes = 1;
-
- if (has_changes)
- diff_q(&outq, p);
- else
- diff_free_filepair(p);
- }
+ pickaxe(&diff_queued_diff, o, regexp, kws, has_changes);
if (opts & DIFF_PICKAXE_REGEX)
regfree(&regex);
-
- free(q->queue);
- *q = outq;
+ else
+ kwsfree(kws);
return;
}
diff --git a/diffcore-rename.c b/diffcore-rename.c
index f639601..216a7a4 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -512,9 +512,15 @@ void diffcore_rename(struct diff_options *options)
else if (options->single_follow &&
strcmp(options->single_follow, p->two->path))
continue; /* not interested */
+ else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
+ is_empty_blob_sha1(p->two->sha1))
+ continue;
else
locate_rename_dst(p->two, 1);
}
+ else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
+ is_empty_blob_sha1(p->one->sha1))
+ continue;
else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) {
/*
* If the source is a broken "delete", and
diff --git a/diffcore.h b/diffcore.h
index b8f1fde..8f32b82 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -45,7 +45,7 @@ struct diff_filespec {
unsigned dirty_submodule : 2; /* For submodules: its work tree is dirty */
#define DIRTY_SUBMODULE_UNTRACKED 1
#define DIRTY_SUBMODULE_MODIFIED 2
-
+ unsigned has_more_entries : 1; /* only appear in combined diff */
struct userdiff_driver *driver;
/* data should be considered "binary"; -1 means "don't know yet" */
int is_binary;
diff --git a/dir.c b/dir.c
index 08281d2..a772c6d 100644
--- a/dir.c
+++ b/dir.c
@@ -34,56 +34,56 @@ int fnmatch_icase(const char *pattern, const char *string, int flags)
return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
}
-static int common_prefix(const char **pathspec)
+static size_t common_prefix_len(const char **pathspec)
{
- const char *path, *slash, *next;
- int prefix;
+ const char *n, *first;
+ size_t max = 0;
if (!pathspec)
- return 0;
+ return max;
+
+ first = *pathspec;
+ while ((n = *pathspec++)) {
+ size_t i, len = 0;
+ for (i = 0; first == n || i < max; i++) {
+ char c = n[i];
+ if (!c || c != first[i] || is_glob_special(c))
+ break;
+ if (c == '/')
+ len = i + 1;
+ }
+ if (first == n || len < max) {
+ max = len;
+ if (!max)
+ break;
+ }
+ }
+ return max;
+}
- path = *pathspec;
- slash = strrchr(path, '/');
- if (!slash)
- return 0;
+/*
+ * Returns a copy of the longest leading path common among all
+ * pathspecs.
+ */
+char *common_prefix(const char **pathspec)
+{
+ unsigned long len = common_prefix_len(pathspec);
- /*
- * The first 'prefix' characters of 'path' are common leading
- * path components among the pathspecs we have seen so far,
- * including the trailing slash.
- */
- prefix = slash - path + 1;
- while ((next = *++pathspec) != NULL) {
- int len, last_matching_slash = -1;
- for (len = 0; len < prefix && next[len] == path[len]; len++)
- if (next[len] == '/')
- last_matching_slash = len;
- if (len == prefix)
- continue;
- if (last_matching_slash < 0)
- return 0;
- prefix = last_matching_slash + 1;
- }
- return prefix;
+ return len ? xmemdupz(*pathspec, len) : NULL;
}
int fill_directory(struct dir_struct *dir, const char **pathspec)
{
- const char *path;
- int len;
+ size_t len;
/*
* Calculate common prefix for the pathspec, and
* use that to optimize the directory walk
*/
- len = common_prefix(pathspec);
- path = "";
-
- if (len)
- path = xmemdupz(*pathspec, len);
+ len = common_prefix_len(pathspec);
/* Read the directory and prune it */
- read_directory(dir, path, len, pathspec);
+ read_directory(dir, pathspec ? *pathspec : "", len, pathspec);
return len;
}
@@ -288,9 +288,24 @@ int match_pathspec_depth(const struct pathspec *ps,
return retval;
}
+/*
+ * Return the length of the "simple" part of a path match limiter.
+ */
+static int simple_length(const char *match)
+{
+ int len = -1;
+
+ for (;;) {
+ unsigned char c = *match++;
+ len++;
+ if (c == '\0' || is_glob_special(c))
+ return len;
+ }
+}
+
static int no_wildcard(const char *string)
{
- return string[strcspn(string, "*?[{\\")] == '\0';
+ return string[simple_length(string)] == '\0';
}
void add_exclude(const char *string, const char *base,
@@ -326,8 +341,7 @@ void add_exclude(const char *string, const char *base,
x->flags = flags;
if (!strchr(string, '/'))
x->flags |= EXC_FLAG_NODIR;
- if (no_wildcard(string))
- x->flags |= EXC_FLAG_NOWILDCARD;
+ x->nowildcardlen = simple_length(string);
if (*string == '*' && no_wildcard(string+1))
x->flags |= EXC_FLAG_ENDSWITH;
ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
@@ -498,62 +512,74 @@ int excluded_from_list(const char *pathname,
{
int i;
- if (el->nr) {
- for (i = el->nr - 1; 0 <= i; i--) {
- struct exclude *x = el->excludes[i];
- const char *exclude = x->pattern;
- int to_exclude = x->to_exclude;
-
- if (x->flags & EXC_FLAG_MUSTBEDIR) {
- if (*dtype == DT_UNKNOWN)
- *dtype = get_dtype(NULL, pathname, pathlen);
- if (*dtype != DT_DIR)
- continue;
- }
+ if (!el->nr)
+ return -1; /* undefined */
- if (x->flags & EXC_FLAG_NODIR) {
- /* match basename */
- if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp_icase(exclude, basename))
- return to_exclude;
- } else if (x->flags & EXC_FLAG_ENDSWITH) {
- if (x->patternlen - 1 <= pathlen &&
- !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
- return to_exclude;
- } else {
- if (fnmatch_icase(exclude, basename, 0) == 0)
- return to_exclude;
- }
- }
- else {
- /* match with FNM_PATHNAME:
- * exclude has base (baselen long) implicitly
- * in front of it.
- */
- int baselen = x->baselen;
- if (*exclude == '/')
- exclude++;
-
- if (pathlen < baselen ||
- (baselen && pathname[baselen-1] != '/') ||
- strncmp_icase(pathname, x->base, baselen))
- continue;
-
- if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp_icase(exclude, pathname + baselen))
- return to_exclude;
- } else {
- if (fnmatch_icase(exclude, pathname+baselen,
- FNM_PATHNAME) == 0)
- return to_exclude;
- }
+ for (i = el->nr - 1; 0 <= i; i--) {
+ struct exclude *x = el->excludes[i];
+ const char *name, *exclude = x->pattern;
+ int to_exclude = x->to_exclude;
+ int namelen, prefix = x->nowildcardlen;
+
+ if (x->flags & EXC_FLAG_MUSTBEDIR) {
+ if (*dtype == DT_UNKNOWN)
+ *dtype = get_dtype(NULL, pathname, pathlen);
+ if (*dtype != DT_DIR)
+ continue;
+ }
+
+ if (x->flags & EXC_FLAG_NODIR) {
+ /* match basename */
+ if (prefix == x->patternlen) {
+ if (!strcmp_icase(exclude, basename))
+ return to_exclude;
+ } else if (x->flags & EXC_FLAG_ENDSWITH) {
+ if (x->patternlen - 1 <= pathlen &&
+ !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
+ return to_exclude;
+ } else {
+ if (fnmatch_icase(exclude, basename, 0) == 0)
+ return to_exclude;
}
+ continue;
+ }
+
+ /* match with FNM_PATHNAME:
+ * exclude has base (baselen long) implicitly in front of it.
+ */
+ if (*exclude == '/') {
+ exclude++;
+ prefix--;
}
+
+ if (pathlen < x->baselen ||
+ (x->baselen && pathname[x->baselen-1] != '/') ||
+ strncmp_icase(pathname, x->base, x->baselen))
+ continue;
+
+ namelen = x->baselen ? pathlen - x->baselen : pathlen;
+ name = pathname + pathlen - namelen;
+
+ /* if the non-wildcard part is longer than the
+ remaining pathname, surely it cannot match */
+ if (prefix > namelen)
+ continue;
+
+ if (prefix) {
+ if (strncmp_icase(exclude, name, prefix))
+ continue;
+ exclude += prefix;
+ name += prefix;
+ namelen -= prefix;
+ }
+
+ if (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME))
+ return to_exclude;
}
return -1; /* undecided */
}
-int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
{
int pathlen = strlen(pathname);
int st;
@@ -573,6 +599,64 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
return 0;
}
+void path_exclude_check_init(struct path_exclude_check *check,
+ struct dir_struct *dir)
+{
+ check->dir = dir;
+ strbuf_init(&check->path, 256);
+}
+
+void path_exclude_check_clear(struct path_exclude_check *check)
+{
+ strbuf_release(&check->path);
+}
+
+/*
+ * Is this name excluded? This is for a caller like show_files() that
+ * do not honor directory hierarchy and iterate through paths that are
+ * possibly in an ignored directory.
+ *
+ * A path to a directory known to be excluded is left in check->path to
+ * optimize for repeated checks for files in the same excluded directory.
+ */
+int path_excluded(struct path_exclude_check *check,
+ const char *name, int namelen, int *dtype)
+{
+ int i;
+ struct strbuf *path = &check->path;
+
+ /*
+ * we allow the caller to pass namelen as an optimization; it
+ * must match the length of the name, as we eventually call
+ * excluded() on the whole name string.
+ */
+ if (namelen < 0)
+ namelen = strlen(name);
+
+ if (path->len &&
+ path->len <= namelen &&
+ !memcmp(name, path->buf, path->len) &&
+ (!name[path->len] || name[path->len] == '/'))
+ return 1;
+
+ strbuf_setlen(path, 0);
+ for (i = 0; name[i]; i++) {
+ int ch = name[i];
+
+ if (ch == '/') {
+ int dt = DT_DIR;
+ if (excluded(check->dir, path->buf, &dt))
+ return 1;
+ }
+ strbuf_addch(path, ch);
+ }
+
+ /* An entry in the index; cannot be a directory with subentries */
+ strbuf_setlen(path, 0);
+
+ return excluded(check->dir, name, dtype);
+}
+
static struct dir_entry *dir_entry_new(const char *pathname, int len)
{
struct dir_entry *ent;
@@ -866,14 +950,14 @@ enum path_treatment {
};
static enum path_treatment treat_one_path(struct dir_struct *dir,
- char *path, int *len,
+ struct strbuf *path,
const struct path_simplify *simplify,
int dtype, struct dirent *de)
{
- int exclude = excluded(dir, path, &dtype);
+ int exclude = excluded(dir, path->buf, &dtype);
if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
- && exclude_matches_pathspec(path, *len, simplify))
- dir_add_ignored(dir, path, *len);
+ && exclude_matches_pathspec(path->buf, path->len, simplify))
+ dir_add_ignored(dir, path->buf, path->len);
/*
* Excluded? If we don't explicitly want to show
@@ -883,7 +967,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
return path_ignored;
if (dtype == DT_UNKNOWN)
- dtype = get_dtype(de, path, *len);
+ dtype = get_dtype(de, path->buf, path->len);
/*
* Do we want to see just the ignored files?
@@ -900,9 +984,8 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
default:
return path_ignored;
case DT_DIR:
- memcpy(path + *len, "/", 2);
- (*len)++;
- switch (treat_directory(dir, path, *len, simplify)) {
+ strbuf_addch(path, '/');
+ switch (treat_directory(dir, path->buf, path->len, simplify)) {
case show_directory:
if (exclude != !!(dir->flags
& DIR_SHOW_IGNORED))
@@ -923,26 +1006,21 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
static enum path_treatment treat_path(struct dir_struct *dir,
struct dirent *de,
- char *path, int path_max,
+ struct strbuf *path,
int baselen,
- const struct path_simplify *simplify,
- int *len)
+ const struct path_simplify *simplify)
{
int dtype;
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
return path_ignored;
- *len = strlen(de->d_name);
- /* Ignore overly long pathnames! */
- if (*len + baselen + 8 > path_max)
- return path_ignored;
- memcpy(path + baselen, de->d_name, *len + 1);
- *len += baselen;
- if (simplify_away(path, *len, simplify))
+ strbuf_setlen(path, baselen);
+ strbuf_addstr(path, de->d_name);
+ if (simplify_away(path->buf, path->len, simplify))
return path_ignored;
dtype = DTYPE(de);
- return treat_one_path(dir, path, len, simplify, dtype, de);
+ return treat_one_path(dir, path, simplify, dtype, de);
}
/*
@@ -959,36 +1037,37 @@ static int read_directory_recursive(struct dir_struct *dir,
int check_only,
const struct path_simplify *simplify)
{
- DIR *fdir = opendir(*base ? base : ".");
+ DIR *fdir;
int contents = 0;
+ struct dirent *de;
+ struct strbuf path = STRBUF_INIT;
- if (fdir) {
- struct dirent *de;
- char path[PATH_MAX + 1];
- memcpy(path, base, baselen);
-
- while ((de = readdir(fdir)) != NULL) {
- int len;
- switch (treat_path(dir, de, path, sizeof(path),
- baselen, simplify, &len)) {
- case path_recurse:
- contents += read_directory_recursive
- (dir, path, len, 0, simplify);
- continue;
- case path_ignored:
- continue;
- case path_handled:
- break;
- }
- contents++;
- if (check_only)
- goto exit_early;
- else
- dir_add_name(dir, path, len);
+ strbuf_add(&path, base, baselen);
+
+ fdir = opendir(path.len ? path.buf : ".");
+ if (!fdir)
+ goto out;
+
+ while ((de = readdir(fdir)) != NULL) {
+ switch (treat_path(dir, de, &path, baselen, simplify)) {
+ case path_recurse:
+ contents += read_directory_recursive(dir, path.buf,
+ path.len, 0,
+ simplify);
+ continue;
+ case path_ignored:
+ continue;
+ case path_handled:
+ break;
}
-exit_early:
- closedir(fdir);
+ contents++;
+ if (check_only)
+ break;
+ dir_add_name(dir, path.buf, path.len);
}
+ closedir(fdir);
+ out:
+ strbuf_release(&path);
return contents;
}
@@ -1002,21 +1081,6 @@ static int cmp_name(const void *p1, const void *p2)
e2->name, e2->len);
}
-/*
- * Return the length of the "simple" part of a path match limiter.
- */
-static int simple_length(const char *match)
-{
- int len = -1;
-
- for (;;) {
- unsigned char c = *match++;
- len++;
- if (c == '\0' || is_glob_special(c))
- return len;
- }
-}
-
static struct path_simplify *create_simplify(const char **pathspec)
{
int nr, alloc = 0;
@@ -1051,8 +1115,8 @@ static int treat_leading_path(struct dir_struct *dir,
const char *path, int len,
const struct path_simplify *simplify)
{
- char pathbuf[PATH_MAX];
- int baselen, blen;
+ struct strbuf sb = STRBUF_INIT;
+ int baselen, rc = 0;
const char *cp;
while (len && path[len - 1] == '/')
@@ -1067,19 +1131,22 @@ static int treat_leading_path(struct dir_struct *dir,
baselen = len;
else
baselen = cp - path;
- memcpy(pathbuf, path, baselen);
- pathbuf[baselen] = '\0';
- if (!is_directory(pathbuf))
- return 0;
- if (simplify_away(pathbuf, baselen, simplify))
- return 0;
- blen = baselen;
- if (treat_one_path(dir, pathbuf, &blen, simplify,
+ strbuf_setlen(&sb, 0);
+ strbuf_add(&sb, path, baselen);
+ if (!is_directory(sb.buf))
+ break;
+ if (simplify_away(sb.buf, sb.len, simplify))
+ break;
+ if (treat_one_path(dir, &sb, simplify,
DT_DIR, NULL) == path_ignored)
- return 0; /* do not recurse into it */
- if (len <= baselen)
- return 1; /* finished checking */
+ break; /* do not recurse into it */
+ if (len <= baselen) {
+ rc = 1;
+ break; /* finished checking */
+ }
}
+ strbuf_release(&sb);
+ return rc;
}
int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
@@ -1165,22 +1232,32 @@ int is_empty_dir(const char *path)
return ret;
}
-int remove_dir_recursively(struct strbuf *path, int flag)
+static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
{
DIR *dir;
struct dirent *e;
- int ret = 0, original_len = path->len, len;
+ int ret = 0, original_len = path->len, len, kept_down = 0;
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
+ int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
unsigned char submodule_head[20];
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
- !resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
+ !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
/* Do not descend and nuke a nested git work tree. */
+ if (kept_up)
+ *kept_up = 1;
return 0;
+ }
+ flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
dir = opendir(path->buf);
- if (!dir)
- return rmdir(path->buf);
+ if (!dir) {
+ /* an empty dir could be removed even if it is unreadble */
+ if (!keep_toplevel)
+ return rmdir(path->buf);
+ else
+ return -1;
+ }
if (path->buf[original_len - 1] != '/')
strbuf_addch(path, '/');
@@ -1195,7 +1272,7 @@ int remove_dir_recursively(struct strbuf *path, int flag)
if (lstat(path->buf, &st))
; /* fall thru */
else if (S_ISDIR(st.st_mode)) {
- if (!remove_dir_recursively(path, only_empty))
+ if (!remove_dir_recurse(path, flag, &kept_down))
continue; /* happy */
} else if (!only_empty && !unlink(path->buf))
continue; /* happy, too */
@@ -1207,20 +1284,36 @@ int remove_dir_recursively(struct strbuf *path, int flag)
closedir(dir);
strbuf_setlen(path, original_len);
- if (!ret)
+ if (!ret && !keep_toplevel && !kept_down)
ret = rmdir(path->buf);
+ else if (kept_up)
+ /*
+ * report the uplevel that it is not an error that we
+ * did not rmdir() our directory.
+ */
+ *kept_up = !ret;
return ret;
}
+int remove_dir_recursively(struct strbuf *path, int flag)
+{
+ return remove_dir_recurse(path, flag, NULL);
+}
+
void setup_standard_excludes(struct dir_struct *dir)
{
const char *path;
+ char *xdg_path;
dir->exclude_per_dir = ".gitignore";
path = git_path("info/exclude");
+ if (!excludes_file) {
+ home_config_paths(NULL, &xdg_path, "ignore");
+ excludes_file = xdg_path;
+ }
if (!access(path, R_OK))
add_excludes_from_file(dir, path);
- if (excludes_file && !access(excludes_file, R_OK))
+ if (!access(excludes_file, R_OK))
add_excludes_from_file(dir, excludes_file);
}
diff --git a/dir.h b/dir.h
index 433b5b4..893465a 100644
--- a/dir.h
+++ b/dir.h
@@ -1,13 +1,14 @@
#ifndef DIR_H
#define DIR_H
+#include "strbuf.h"
+
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
@@ -17,6 +18,7 @@ struct exclude_list {
struct exclude {
const char *pattern;
int patternlen;
+ int nowildcardlen;
const char *base;
int baselen;
int to_exclude;
@@ -64,6 +66,7 @@ struct dir_struct {
#define MATCHED_RECURSIVELY 1
#define MATCHED_FNMATCH 2
#define MATCHED_EXACTLY 3
+extern char *common_prefix(const char **pathspec);
extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
extern int match_pathspec_depth(const struct pathspec *pathspec,
const char *name, int namelen,
@@ -75,8 +78,22 @@ extern int read_directory(struct dir_struct *, const char *path, int len, const
extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
int *dtype, struct exclude_list *el);
-extern int excluded(struct dir_struct *, const char *, int *);
struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
+
+/*
+ * The excluded() API is meant for callers that check each level of leading
+ * directory hierarchies with excluded() to avoid recursing into excluded
+ * directories. Callers that do not do so should use this API instead.
+ */
+struct path_exclude_check {
+ struct dir_struct *dir;
+ struct strbuf path;
+};
+extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);
+extern void path_exclude_check_clear(struct path_exclude_check *);
+extern int path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
+
+
extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
char **buf_p, struct exclude_list *which, int check_index);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
@@ -101,6 +118,7 @@ extern void setup_standard_excludes(struct dir_struct *dir);
#define REMOVE_DIR_EMPTY_ONLY 01
#define REMOVE_DIR_KEEP_NESTED_GIT 02
+#define REMOVE_DIR_KEEP_TOPLEVEL 04
extern int remove_dir_recursively(struct strbuf *path, int flag);
/* tries to remove the path with empty directories along it, ignores ENOENT */
diff --git a/entry.c b/entry.c
index b017167..17a6bcc 100644
--- a/entry.c
+++ b/entry.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "blob.h"
#include "dir.h"
+#include "streaming.h"
static void create_directories(const char *path, int path_len,
const struct checkout *state)
@@ -91,6 +92,48 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)
return NULL;
}
+static int open_output_fd(char *path, struct cache_entry *ce, int to_tempfile)
+{
+ int symlink = (ce->ce_mode & S_IFMT) != S_IFREG;
+ if (to_tempfile) {
+ strcpy(path, symlink
+ ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
+ return mkstemp(path);
+ } else {
+ return create_file(path, !symlink ? ce->ce_mode : 0666);
+ }
+}
+
+static int fstat_output(int fd, const struct checkout *state, struct stat *st)
+{
+ /* use fstat() only when path == ce->name */
+ if (fstat_is_reliable() &&
+ state->refresh_cache && !state->base_dir_len) {
+ fstat(fd, st);
+ return 1;
+ }
+ return 0;
+}
+
+static int streaming_write_entry(struct cache_entry *ce, char *path,
+ struct stream_filter *filter,
+ const struct checkout *state, int to_tempfile,
+ int *fstat_done, struct stat *statbuf)
+{
+ int result = -1;
+ int fd;
+
+ fd = open_output_fd(path, ce, to_tempfile);
+ if (0 <= fd) {
+ result = stream_blob_to_fd(fd, ce->sha1, filter, 1);
+ *fstat_done = fstat_output(fd, state, statbuf);
+ result = close(fd);
+ }
+ if (result && 0 <= fd)
+ unlink(path);
+ return result;
+}
+
static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
{
unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
@@ -101,6 +144,15 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
size_t wrote, newsize = 0;
struct stat st;
+ if (ce_mode_s_ifmt == S_IFREG) {
+ struct stream_filter *filter = get_stream_filter(path, ce->sha1);
+ if (filter &&
+ !streaming_write_entry(ce, path, filter,
+ state, to_tempfile,
+ &fstat_done, &st))
+ goto finish;
+ }
+
switch (ce_mode_s_ifmt) {
case S_IFREG:
case S_IFLNK:
@@ -128,17 +180,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
size = newsize;
}
- if (to_tempfile) {
- if (ce_mode_s_ifmt == S_IFREG)
- strcpy(path, ".merge_file_XXXXXX");
- else
- strcpy(path, ".merge_link_XXXXXX");
- fd = mkstemp(path);
- } else if (ce_mode_s_ifmt == S_IFREG) {
- fd = create_file(path, ce->ce_mode);
- } else {
- fd = create_file(path, 0666);
- }
+ fd = open_output_fd(path, ce, to_tempfile);
if (fd < 0) {
free(new);
return error("unable to create file %s (%s)",
@@ -146,12 +188,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
}
wrote = write_in_full(fd, new, size);
- /* use fstat() only when path == ce->name */
- if (fstat_is_reliable() &&
- state->refresh_cache && !to_tempfile && !state->base_dir_len) {
- fstat(fd, &st);
- fstat_done = 1;
- }
+ if (!to_tempfile)
+ fstat_done = fstat_output(fd, state, &st);
close(fd);
free(new);
if (wrote != size)
@@ -167,6 +205,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
return error("unknown file mode for %s in index", path);
}
+finish:
if (state->refresh_cache) {
if (!fstat_done)
lstat(ce->name, &st);
diff --git a/environment.c b/environment.c
index 2228c4e..669e498 100644
--- a/environment.c
+++ b/environment.c
@@ -8,10 +8,9 @@
* are.
*/
#include "cache.h"
+#include "refs.h"
+#include "fmt-merge-msg.h"
-char git_default_email[MAX_GITNAME];
-char git_default_name[MAX_GITNAME];
-int user_ident_explicitly_given;
int trust_executable_bit = 1;
int trust_ctime = 1;
int has_symlinks = 1;
@@ -28,6 +27,7 @@ const char *git_log_output_encoding;
int shared_repository = PERM_UMASK;
const char *apply_default_whitespace;
const char *apply_default_ignorewhitespace;
+const char *git_attributes_file;
int zlib_compression_level = Z_BEST_SPEED;
int core_compression_level;
int core_compression_seen;
@@ -36,6 +36,7 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
size_t delta_base_cache_limit = 16 * 1024 * 1024;
unsigned long big_file_threshold = 512 * 1024 * 1024;
+const char *log_pack_access;
const char *pager_program;
int pager_use_color = 1;
const char *editor_program;
@@ -48,7 +49,7 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
-enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
+enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
#ifndef OBJECT_CREATION_MODE
#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
#endif
@@ -56,7 +57,9 @@ enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
char *notes_ref_name;
int grafts_replace_parents = 1;
int core_apply_sparse_checkout;
+int merge_log_config = -1;
struct startup_info *startup_info;
+unsigned long pack_size_limit_cfg;
/* Parallel index stat data preload? */
int core_preload_index = 0;
@@ -65,6 +68,9 @@ int core_preload_index = 0;
char *git_work_tree_cfg;
static char *work_tree;
+static const char *namespace;
+static size_t namespace_len;
+
static const char *git_dir;
static char *git_object_dir, *git_index_file, *git_graft_file;
@@ -86,6 +92,27 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
NULL
};
+static char *expand_namespace(const char *raw_namespace)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf **components, **c;
+
+ if (!raw_namespace || !*raw_namespace)
+ return xstrdup("");
+
+ strbuf_addstr(&buf, raw_namespace);
+ components = strbuf_split(&buf, '/');
+ strbuf_reset(&buf);
+ for (c = components; *c; c++)
+ if (strcmp((*c)->buf, "/") != 0)
+ strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf);
+ strbuf_list_free(components);
+ if (check_refname_format(buf.buf, 0))
+ die("bad git namespace path \"%s\"", raw_namespace);
+ strbuf_addch(&buf, '/');
+ return strbuf_detach(&buf, NULL);
+}
+
static void setup_git_env(void)
{
git_dir = getenv(GIT_DIR_ENVIRONMENT);
@@ -111,6 +138,8 @@ static void setup_git_env(void)
git_graft_file = git_pathdup("info/grafts");
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
read_replace_refs = 0;
+ namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
+ namespace_len = strlen(namespace);
}
int is_bare_repository(void)
@@ -131,6 +160,20 @@ const char *get_git_dir(void)
return git_dir;
}
+const char *get_git_namespace(void)
+{
+ if (!namespace)
+ setup_git_env();
+ return namespace;
+}
+
+const char *strip_namespace(const char *namespaced_ref)
+{
+ if (prefixcmp(namespaced_ref, get_git_namespace()) != 0)
+ return NULL;
+ return namespaced_ref + namespace_len;
+}
+
static int git_work_tree_initialized;
/*
diff --git a/exec_cmd.c b/exec_cmd.c
index 171e841..125fa6f 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -134,7 +134,7 @@ int execv_git_cmd(const char **argv) {
trace_argv_printf(nargv, "trace: exec:");
/* execvp() can only ever return if it fails */
- execvp("git", (char **)nargv);
+ sane_execvp("git", (char **)nargv);
trace_printf("trace: exec failed: %s\n", strerror(errno));
diff --git a/fast-import.c b/fast-import.c
index 1d5e333..eed97c8 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -170,6 +170,11 @@ Format of STDIN stream:
#define DEPTH_BITS 13
#define MAX_DEPTH ((1<<DEPTH_BITS)-1)
+/*
+ * We abuse the setuid bit on directories to mean "do not delta".
+ */
+#define NO_DELTA S_ISUID
+
struct object_entry {
struct pack_idx_entry idx;
struct object_entry *next;
@@ -284,6 +289,7 @@ static uintmax_t marks_set_count;
static uintmax_t object_count_by_type[1 << TYPE_BITS];
static uintmax_t duplicate_count_by_type[1 << TYPE_BITS];
static uintmax_t delta_count_by_type[1 << TYPE_BITS];
+static uintmax_t delta_count_attempts_by_type[1 << TYPE_BITS];
static unsigned long object_count;
static unsigned long branch_count;
static unsigned long branch_load_count;
@@ -304,6 +310,7 @@ static unsigned int atom_cnt;
static struct atom_str **atom_table;
/* The .pack file being generated */
+static struct pack_idx_option pack_idx_opts;
static unsigned int pack_id;
static struct sha1file *pack_file;
static struct packed_git *pack_data;
@@ -354,6 +361,7 @@ static unsigned int cmd_save = 100;
static uintmax_t next_mark;
static struct strbuf new_data = STRBUF_INIT;
static int seen_data_command;
+static int require_explicit_termination;
/* Signal handling */
static volatile sig_atomic_t checkpoint_requested;
@@ -714,13 +722,8 @@ 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 CHECK_REF_FORMAT_ONELEVEL:
- break; /* valid, but too few '/', allow anyway */
- default:
+ if (check_refname_format(name, REFNAME_ALLOW_ONELEVEL))
die("Branch name doesn't conform to GIT standards: %s", name);
- }
b = pool_calloc(1, sizeof(struct branch));
b->name = pool_strdup(name);
@@ -852,15 +855,15 @@ static struct tree_content *dup_tree_content(struct tree_content *s)
static void start_packfile(void)
{
- static char tmpfile[PATH_MAX];
+ static char tmp_file[PATH_MAX];
struct packed_git *p;
struct pack_header hdr;
int pack_fd;
- pack_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+ pack_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
"pack/tmp_pack_XXXXXX");
- p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
- strcpy(p->pack_name, tmpfile);
+ p = xcalloc(1, sizeof(*p) + strlen(tmp_file) + 2);
+ strcpy(p->pack_name, tmp_file);
p->pack_fd = pack_fd;
p->do_not_close = 1;
pack_file = sha1fd(pack_fd, p->pack_name);
@@ -896,7 +899,7 @@ static const char *create_index(void)
if (c != last)
die("internal consistency error creating the index");
- tmpfile = write_idx_file(NULL, idx, object_count, pack_data->sha1);
+ tmpfile = write_idx_file(NULL, idx, object_count, &pack_idx_opts, pack_data->sha1);
free(idx);
return tmpfile;
}
@@ -1043,6 +1046,7 @@ static int store_object(
}
if (last && last->data.buf && last->depth < max_depth && dat->len > 20) {
+ delta_count_attempts_by_type[type]++;
delta = diff_delta(last->data.buf, last->data.len,
dat->buf, dat->len,
&deltalen, dat->len - 20);
@@ -1139,17 +1143,11 @@ static int store_object(
return 0;
}
-static void truncate_pack(off_t to, git_SHA_CTX *ctx)
+static void truncate_pack(struct sha1file_checkpoint *checkpoint)
{
- if (ftruncate(pack_data->pack_fd, to)
- || lseek(pack_data->pack_fd, to, SEEK_SET) != to)
+ if (sha1file_truncate(pack_file, checkpoint))
die_errno("cannot truncate pack to skip duplicate");
- pack_size = to;
-
- /* yes this is a layering violation */
- pack_file->total = to;
- pack_file->offset = 0;
- pack_file->ctx = *ctx;
+ pack_size = checkpoint->offset;
}
static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
@@ -1162,8 +1160,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
unsigned long hdrlen;
off_t offset;
git_SHA_CTX c;
- git_SHA_CTX pack_file_ctx;
git_zstream s;
+ struct sha1file_checkpoint checkpoint;
int status = Z_OK;
/* Determine if we should auto-checkpoint. */
@@ -1171,11 +1169,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
|| (pack_size + 60 + len) < pack_size)
cycle_packfile();
- offset = pack_size;
-
- /* preserve the pack_file SHA1 ctx in case we have to truncate later */
- sha1flush(pack_file);
- pack_file_ctx = pack_file->ctx;
+ sha1file_checkpoint(pack_file, &checkpoint);
+ offset = checkpoint.offset;
hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1;
if (out_sz <= hdrlen)
@@ -1241,14 +1236,14 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
if (e->idx.offset) {
duplicate_count_by_type[OBJ_BLOB]++;
- truncate_pack(offset, &pack_file_ctx);
+ truncate_pack(&checkpoint);
} else if (find_sha1_pack(sha1, packed_git)) {
e->type = OBJ_BLOB;
e->pack_id = MAX_PACK_ID;
e->idx.offset = 1; /* just not zero! */
duplicate_count_by_type[OBJ_BLOB]++;
- truncate_pack(offset, &pack_file_ctx);
+ truncate_pack(&checkpoint);
} else {
e->depth = 0;
@@ -1414,8 +1409,9 @@ static void mktree(struct tree_content *t, int v, struct strbuf *b)
struct tree_entry *e = t->entries[i];
if (!e->versions[v].mode)
continue;
- strbuf_addf(b, "%o %s%c", (unsigned int)e->versions[v].mode,
- e->name->str_dat, '\0');
+ strbuf_addf(b, "%o %s%c",
+ (unsigned int)(e->versions[v].mode & ~NO_DELTA),
+ e->name->str_dat, '\0');
strbuf_add(b, e->versions[v].sha1, 20);
}
}
@@ -1425,7 +1421,7 @@ static void store_tree(struct tree_entry *root)
struct tree_content *t = root->tree;
unsigned int i, j, del;
struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 };
- struct object_entry *le;
+ struct object_entry *le = NULL;
if (!is_null_sha1(root->versions[1].sha1))
return;
@@ -1435,7 +1431,8 @@ static void store_tree(struct tree_entry *root)
store_tree(t->entries[i]);
}
- le = find_object(root->versions[0].sha1);
+ if (!(root->versions[0].mode & NO_DELTA))
+ le = find_object(root->versions[0].sha1);
if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) {
mktree(t, 0, &old_tree);
lo.data = old_tree;
@@ -1469,6 +1466,7 @@ static void tree_content_replace(
{
if (!S_ISDIR(mode))
die("Root cannot be a non-directory");
+ hashclr(root->versions[0].sha1);
hashcpy(root->versions[1].sha1, sha1);
if (root->tree)
release_tree_content_recursive(root->tree);
@@ -1513,6 +1511,23 @@ static int tree_content_set(
if (e->tree)
release_tree_content_recursive(e->tree);
e->tree = subtree;
+
+ /*
+ * We need to leave e->versions[0].sha1 alone
+ * to avoid modifying the preimage tree used
+ * when writing out the parent directory.
+ * But after replacing the subdir with a
+ * completely different one, it's not a good
+ * delta base any more, and besides, we've
+ * thrown away the tree entries needed to
+ * make a delta against it.
+ *
+ * So let's just explicitly disable deltas
+ * for the subtree.
+ */
+ if (S_ISDIR(e->versions[0].mode))
+ e->versions[0].mode |= NO_DELTA;
+
hashclr(root->versions[1].sha1);
return 1;
}
@@ -1626,6 +1641,8 @@ static int tree_content_get(
n = slash1 - p;
else
n = strlen(p);
+ if (!n)
+ die("Empty path component found in input");
if (!root->tree)
load_tree(root);
@@ -1967,32 +1984,41 @@ static int validate_raw_date(const char *src, char *result, int maxlen)
static char *parse_ident(const char *buf)
{
- const char *gt;
+ const char *ltgt;
size_t name_len;
char *ident;
- gt = strrchr(buf, '>');
- if (!gt)
+ /* ensure there is a space delimiter even if there is no name */
+ if (*buf == '<')
+ --buf;
+
+ ltgt = buf + strcspn(buf, "<>");
+ if (*ltgt != '<')
+ die("Missing < in ident string: %s", buf);
+ if (ltgt != buf && ltgt[-1] != ' ')
+ die("Missing space before < in ident string: %s", buf);
+ ltgt = ltgt + 1 + strcspn(ltgt + 1, "<>");
+ if (*ltgt != '>')
die("Missing > in ident string: %s", buf);
- gt++;
- if (*gt != ' ')
+ ltgt++;
+ if (*ltgt != ' ')
die("Missing space after > in ident string: %s", buf);
- gt++;
- name_len = gt - buf;
+ ltgt++;
+ name_len = ltgt - buf;
ident = xmalloc(name_len + 24);
strncpy(ident, buf, name_len);
switch (whenspec) {
case WHENSPEC_RAW:
- if (validate_raw_date(gt, ident + name_len, 24) < 0)
- die("Invalid raw date \"%s\" in ident: %s", gt, buf);
+ if (validate_raw_date(ltgt, ident + name_len, 24) < 0)
+ die("Invalid raw date \"%s\" in ident: %s", ltgt, buf);
break;
case WHENSPEC_RFC2822:
- if (parse_date(gt, ident + name_len, 24) < 0)
- die("Invalid rfc2822 date \"%s\" in ident: %s", gt, buf);
+ if (parse_date(ltgt, ident + name_len, 24) < 0)
+ die("Invalid rfc2822 date \"%s\" in ident: %s", ltgt, buf);
break;
case WHENSPEC_NOW:
- if (strcmp("now", gt))
+ if (strcmp("now", ltgt))
die("Date in ident must be 'now': %s", buf);
datestamp(ident + name_len, 24);
break;
@@ -2140,6 +2166,11 @@ static uintmax_t do_change_note_fanout(
if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
/* This is a note entry */
+ if (fanout == 0xff) {
+ /* Counting mode, no rename */
+ num_notes++;
+ continue;
+ }
construct_path_with_fanout(hex_sha1, fanout, realpath);
if (!strcmp(fullpath, realpath)) {
/* Note entry is in correct location */
@@ -2176,6 +2207,59 @@ static uintmax_t change_note_fanout(struct tree_entry *root,
return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
}
+/*
+ * Given a pointer into a string, parse a mark reference:
+ *
+ * idnum ::= ':' bigint;
+ *
+ * Return the first character after the value in *endptr.
+ *
+ * Complain if the following character is not what is expected,
+ * either a space or end of the string.
+ */
+static uintmax_t parse_mark_ref(const char *p, char **endptr)
+{
+ uintmax_t mark;
+
+ assert(*p == ':');
+ p++;
+ mark = strtoumax(p, endptr, 10);
+ if (*endptr == p)
+ die("No value after ':' in mark: %s", command_buf.buf);
+ return mark;
+}
+
+/*
+ * Parse the mark reference, and complain if this is not the end of
+ * the string.
+ */
+static uintmax_t parse_mark_ref_eol(const char *p)
+{
+ char *end;
+ uintmax_t mark;
+
+ mark = parse_mark_ref(p, &end);
+ if (*end != '\0')
+ die("Garbage after mark: %s", command_buf.buf);
+ return mark;
+}
+
+/*
+ * Parse the mark reference, demanding a trailing space. Return a
+ * pointer to the space.
+ */
+static uintmax_t parse_mark_ref_space(const char **p)
+{
+ uintmax_t mark;
+ char *end;
+
+ mark = parse_mark_ref(*p, &end);
+ if (*end != ' ')
+ die("Missing space after mark: %s", command_buf.buf);
+ *p = end;
+ return mark;
+}
+
static void file_change_m(struct branch *b)
{
const char *p = command_buf.buf + 2;
@@ -2204,21 +2288,21 @@ static void file_change_m(struct branch *b)
}
if (*p == ':') {
- char *x;
- oe = find_mark(strtoumax(p + 1, &x, 10));
+ oe = find_mark(parse_mark_ref_space(&p));
hashcpy(sha1, oe->idx.sha1);
- p = x;
- } else if (!prefixcmp(p, "inline")) {
+ } else if (!prefixcmp(p, "inline ")) {
inline_data = 1;
- p += 6;
+ p += strlen("inline"); /* advance to space */
} else {
if (get_sha1_hex(p, sha1))
- die("Invalid SHA1: %s", command_buf.buf);
+ die("Invalid dataref: %s", command_buf.buf);
oe = find_object(sha1);
p += 40;
+ if (*p != ' ')
+ die("Missing space after SHA1: %s", command_buf.buf);
}
- if (*p++ != ' ')
- die("Missing space after SHA1: %s", command_buf.buf);
+ assert(*p == ' ');
+ p++; /* skip space */
strbuf_reset(&uq);
if (!unquote_c_style(&uq, p, &endp)) {
@@ -2346,7 +2430,7 @@ static void file_change_cr(struct branch *b, int rename)
leaf.tree);
}
-static void note_change_n(struct branch *b, unsigned char old_fanout)
+static void note_change_n(struct branch *b, unsigned char *old_fanout)
{
const char *p = command_buf.buf + 2;
static struct strbuf uq = STRBUF_INIT;
@@ -2357,30 +2441,49 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
uint16_t inline_data = 0;
unsigned char new_fanout;
+ /*
+ * When loading a branch, we don't traverse its tree to count the real
+ * number of notes (too expensive to do this for all non-note refs).
+ * This means that recently loaded notes refs might incorrectly have
+ * b->num_notes == 0, and consequently, old_fanout might be wrong.
+ *
+ * Fix this by traversing the tree and counting the number of notes
+ * when b->num_notes == 0. If the notes tree is truly empty, the
+ * calculation should not take long.
+ */
+ if (b->num_notes == 0 && *old_fanout == 0) {
+ /* Invoke change_note_fanout() in "counting mode". */
+ b->num_notes = change_note_fanout(&b->branch_tree, 0xff);
+ *old_fanout = convert_num_notes_to_fanout(b->num_notes);
+ }
+
+ /* Now parse the notemodify command. */
/* <dataref> or 'inline' */
if (*p == ':') {
- char *x;
- oe = find_mark(strtoumax(p + 1, &x, 10));
+ oe = find_mark(parse_mark_ref_space(&p));
hashcpy(sha1, oe->idx.sha1);
- p = x;
- } else if (!prefixcmp(p, "inline")) {
+ } else if (!prefixcmp(p, "inline ")) {
inline_data = 1;
- p += 6;
+ p += strlen("inline"); /* advance to space */
} else {
if (get_sha1_hex(p, sha1))
- die("Invalid SHA1: %s", command_buf.buf);
+ die("Invalid dataref: %s", command_buf.buf);
oe = find_object(sha1);
p += 40;
+ if (*p != ' ')
+ die("Missing space after SHA1: %s", command_buf.buf);
}
- if (*p++ != ' ')
- die("Missing space after SHA1: %s", command_buf.buf);
+ assert(*p == ' ');
+ p++; /* skip space */
/* <committish> */
s = lookup_branch(p);
if (s) {
+ if (is_null_sha1(s->sha1))
+ die("Can't add a note on empty branch.");
hashcpy(commit_sha1, s->sha1);
} else if (*p == ':') {
- uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
+ uintmax_t commit_mark = parse_mark_ref_eol(p);
struct object_entry *commit_oe = find_mark(commit_mark);
if (commit_oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", commit_mark);
@@ -2415,7 +2518,7 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
typename(type), command_buf.buf);
}
- construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
+ construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path);
if (tree_content_remove(&b->branch_tree, path, NULL))
b->num_notes--;
@@ -2487,7 +2590,7 @@ static int parse_from(struct branch *b)
hashcpy(b->branch_tree.versions[0].sha1, t);
hashcpy(b->branch_tree.versions[1].sha1, t);
} else if (*from == ':') {
- uintmax_t idnum = strtoumax(from + 1, NULL, 10);
+ uintmax_t idnum = parse_mark_ref_eol(from);
struct object_entry *oe = find_mark(idnum);
if (oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", idnum);
@@ -2522,7 +2625,7 @@ static struct hash_list *parse_merge(unsigned int *count)
if (s)
hashcpy(n->sha1, s->sha1);
else if (*from == ':') {
- uintmax_t idnum = strtoumax(from + 1, NULL, 10);
+ uintmax_t idnum = parse_mark_ref_eol(from);
struct object_entry *oe = find_mark(idnum);
if (oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", idnum);
@@ -2602,7 +2705,7 @@ static void parse_new_commit(void)
else if (!prefixcmp(command_buf.buf, "C "))
file_change_cr(b, 0);
else if (!prefixcmp(command_buf.buf, "N "))
- note_change_n(b, prev_fanout);
+ note_change_n(b, &prev_fanout);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
else if (!prefixcmp(command_buf.buf, "ls "))
@@ -2664,7 +2767,7 @@ static void parse_new_tag(void)
/* Obtain the new tag name from the rest of our command */
sp = strchr(command_buf.buf, ' ') + 1;
t = pool_alloc(sizeof(struct tag));
- t->next_tag = NULL;
+ memset(t, 0, sizeof(struct tag));
t->name = pool_strdup(sp);
if (last_tag)
last_tag->next_tag = t;
@@ -2679,22 +2782,24 @@ static void parse_new_tag(void)
from = strchr(command_buf.buf, ' ') + 1;
s = lookup_branch(from);
if (s) {
+ if (is_null_sha1(s->sha1))
+ die("Can't tag an empty branch.");
hashcpy(sha1, s->sha1);
type = OBJ_COMMIT;
} else if (*from == ':') {
struct object_entry *oe;
- from_mark = strtoumax(from + 1, NULL, 10);
+ from_mark = parse_mark_ref_eol(from);
oe = find_mark(from_mark);
type = oe->type;
hashcpy(sha1, oe->idx.sha1);
} else if (!get_sha1(from, sha1)) {
- unsigned long size;
- char *buf;
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf || size < 46)
- die("Not a valid commit: %s", from);
- free(buf);
+ struct object_entry *oe = find_object(sha1);
+ if (!oe) {
+ type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("Not a valid object: %s", from);
+ } else
+ type = oe->type;
} else
die("Invalid ref name or SHA1 expression: %s", from);
read_next_command();
@@ -2798,7 +2903,12 @@ static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
strbuf_release(&line);
cat_blob_write(buf, size);
cat_blob_write("\n", 1);
- free(buf);
+ if (oe && oe->pack_id == pack_id) {
+ last_blob.offset = oe->idx.offset;
+ strbuf_attach(&last_blob.data, buf, size, size);
+ last_blob.depth = oe->depth;
+ } else
+ free(buf);
}
static void parse_cat_blob(void)
@@ -2810,18 +2920,13 @@ static void parse_cat_blob(void)
/* cat-blob SP <object> LF */
p = command_buf.buf + strlen("cat-blob ");
if (*p == ':') {
- char *x;
- oe = find_mark(strtoumax(p + 1, &x, 10));
- if (x == p + 1)
- die("Invalid mark: %s", command_buf.buf);
+ oe = find_mark(parse_mark_ref_eol(p));
if (!oe)
die("Unknown mark: %s", command_buf.buf);
- if (*x)
- die("Garbage after mark: %s", command_buf.buf);
hashcpy(sha1, oe->idx.sha1);
} else {
if (get_sha1_hex(p, sha1))
- die("Invalid SHA1: %s", command_buf.buf);
+ die("Invalid dataref: %s", command_buf.buf);
if (p[40])
die("Garbage after SHA1: %s", command_buf.buf);
oe = find_object(sha1);
@@ -2887,17 +2992,13 @@ static struct object_entry *parse_treeish_dataref(const char **p)
struct object_entry *e;
if (**p == ':') { /* <mark> */
- char *endptr;
- e = find_mark(strtoumax(*p + 1, &endptr, 10));
- if (endptr == *p + 1)
- die("Invalid mark: %s", command_buf.buf);
+ e = find_mark(parse_mark_ref_space(p));
if (!e)
die("Unknown mark: %s", command_buf.buf);
- *p = endptr;
hashcpy(sha1, e->idx.sha1);
} else { /* <sha1> */
if (get_sha1_hex(*p, sha1))
- die("Invalid SHA1: %s", command_buf.buf);
+ die("Invalid dataref: %s", command_buf.buf);
e = find_object(sha1);
*p += 40;
}
@@ -2927,7 +3028,7 @@ static void print_ls(int mode, const unsigned char *sha1, const char *path)
/* mode SP type SP object_name TAB path LF */
strbuf_reset(&line);
strbuf_addf(&line, "%06o %s %s\t",
- mode, type, sha1_to_hex(sha1));
+ mode & ~NO_DELTA, type, sha1_to_hex(sha1));
quote_c_style(path, &line, NULL, 0);
strbuf_addch(&line, '\n');
}
@@ -2973,6 +3074,8 @@ static void parse_ls(struct branch *b)
store_tree(&leaf);
print_ls(leaf.versions[1].mode, leaf.versions[1].sha1, p);
+ if (leaf.tree)
+ release_tree_content_recursive(leaf.tree);
if (!b || root != &b->branch_tree)
release_tree_entry(root);
}
@@ -3139,6 +3242,8 @@ static int parse_one_feature(const char *feature, int from_stream)
relative_marks_paths = 1;
} else if (!strcmp(feature, "no-relative-marks")) {
relative_marks_paths = 0;
+ } else if (!strcmp(feature, "done")) {
+ require_explicit_termination = 1;
} else if (!strcmp(feature, "force")) {
force_update = 1;
} else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
@@ -3195,10 +3300,10 @@ static int git_pack_config(const char *k, const char *v, void *cb)
return 0;
}
if (!strcmp(k, "pack.indexversion")) {
- pack_idx_default_version = git_config_int(k, v);
- if (pack_idx_default_version > 2)
+ pack_idx_opts.version = git_config_int(k, v);
+ if (pack_idx_opts.version > 2)
die("bad pack.indexversion=%"PRIu32,
- pack_idx_default_version);
+ pack_idx_opts.version);
return 0;
}
if (!strcmp(k, "pack.packsizelimit")) {
@@ -3248,10 +3353,13 @@ int main(int argc, const char **argv)
git_extract_argv0_path(argv[0]);
+ git_setup_gettext();
+
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(fast_import_usage);
setup_git_directory();
+ reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
if (!pack_compression_seen && core_compression_seen)
pack_compression_level = core_compression_level;
@@ -3288,6 +3396,8 @@ int main(int argc, const char **argv)
parse_reset_branch();
else if (!strcmp("checkpoint", command_buf.buf))
parse_checkpoint();
+ else if (!strcmp("done", command_buf.buf))
+ break;
else if (!prefixcmp(command_buf.buf, "progress "))
parse_progress();
else if (!prefixcmp(command_buf.buf, "feature "))
@@ -3307,6 +3417,9 @@ int main(int argc, const char **argv)
if (!seen_data_command)
parse_argv();
+ if (require_explicit_termination && feof(stdin))
+ die("stream ends early");
+
end_packfile();
dump_branches();
@@ -3328,10 +3441,10 @@ int main(int argc, const char **argv)
fprintf(stderr, "---------------------------------------------------------------------\n");
fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count);
fprintf(stderr, "Total objects: %10" PRIuMAX " (%10" PRIuMAX " duplicates )\n", total_count, duplicate_count);
- fprintf(stderr, " blobs : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]);
- fprintf(stderr, " trees : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]);
- fprintf(stderr, " commits: %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]);
- fprintf(stderr, " tags : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]);
+ fprintf(stderr, " blobs : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB], delta_count_attempts_by_type[OBJ_BLOB]);
+ fprintf(stderr, " trees : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE], delta_count_attempts_by_type[OBJ_TREE]);
+ fprintf(stderr, " commits: %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT], delta_count_attempts_by_type[OBJ_COMMIT]);
+ fprintf(stderr, " tags : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG], delta_count_attempts_by_type[OBJ_TAG]);
fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count);
fprintf(stderr, " marks: %10" PRIuMAX " (%10" PRIuMAX " unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count);
fprintf(stderr, " atoms: %10u\n", atom_cnt);
diff --git a/fetch-pack.h b/fetch-pack.h
index 0608eda..7c2069c 100644
--- a/fetch-pack.h
+++ b/fetch-pack.h
@@ -10,6 +10,7 @@ struct fetch_pack_args {
lock_pack:1,
use_thin_pack:1,
fetch_all:1,
+ stdin_refs:1,
verbose:1,
no_progress:1,
include_tag:1,
diff --git a/fmt-merge-msg.h b/fmt-merge-msg.h
new file mode 100644
index 0000000..b28d3a6
--- /dev/null
+++ b/fmt-merge-msg.h
@@ -0,0 +1,7 @@
+#ifndef FMT_MERGE_MSG_H
+#define FMT_MERGE_MSG_H
+
+extern int merge_log_config;
+extern int fmt_merge_msg_config(const char *key, const char *value, void *cb);
+
+#endif /* FMT_MERGE_MSG_H */
diff --git a/fsck.c b/fsck.c
index 60bd4bb..4c63b2c 100644
--- a/fsck.c
+++ b/fsck.c
@@ -27,7 +27,7 @@ static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode))
result = walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data);
else {
- result = error("in tree %s: entry %s has bad mode %.6o\n",
+ result = error("in tree %s: entry %s has bad mode %.6o",
sha1_to_hex(tree->object.sha1), entry.path, entry.mode);
}
if (result < 0)
@@ -224,13 +224,15 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
static int fsck_ident(char **ident, struct object *obj, fsck_error error_func)
{
- if (**ident == '<' || **ident == '\n')
- return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
- *ident += strcspn(*ident, "<\n");
- if ((*ident)[-1] != ' ')
+ if (**ident == '<')
return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
+ *ident += strcspn(*ident, "<>\n");
+ if (**ident == '>')
+ return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad name");
if (**ident != '<')
return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email");
+ if ((*ident)[-1] != ' ')
+ return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
(*ident)++;
*ident += strcspn(*ident, "<>\n");
if (**ident != '>')
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index 3ef4861..9a4c9b9 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -15,8 +15,8 @@ do
sed -n '
/^NAME/,/git-'"$cmd"'/H
${
- x
- s/.*git-'"$cmd"' - \(.*\)/ {"'"$cmd"'", "\1"},/
+ x
+ s/.*git-'"$cmd"' - \(.*\)/ {"'"$cmd"'", N_("\1")},/
p
}' "Documentation/git-$cmd.txt"
done
diff --git a/gettext.c b/gettext.c
index ae5394a..f75bca7 100644
--- a/gettext.c
+++ b/gettext.c
@@ -5,6 +5,18 @@
#include "git-compat-util.h"
#include "gettext.h"
+#ifndef NO_GETTEXT
+# include <locale.h>
+# include <libintl.h>
+# ifdef HAVE_LIBCHARSET_H
+# include <libcharset.h>
+# else
+# include <langinfo.h>
+# define locale_charset() nl_langinfo(CODESET)
+# endif
+#endif
+
+#ifdef GETTEXT_POISON
int use_gettext_poison(void)
{
static int poison_requested = -1;
@@ -12,3 +24,108 @@ int use_gettext_poison(void)
poison_requested = getenv("GIT_GETTEXT_POISON") ? 1 : 0;
return poison_requested;
}
+#endif
+
+#ifndef NO_GETTEXT
+static void init_gettext_charset(const char *domain)
+{
+ const char *charset;
+
+ /*
+ This trick arranges for messages to be emitted in the user's
+ requested encoding, but avoids setting LC_CTYPE from the
+ environment for the whole program.
+
+ This primarily done to avoid a bug in vsnprintf in the GNU C
+ Library [1]. which triggered a "your vsnprintf is broken" error
+ on Git's own repository when inspecting v0.99.6~1 under a UTF-8
+ locale.
+
+ That commit contains a ISO-8859-1 encoded author name, which
+ the locale aware vsnprintf(3) won't interpolate in the format
+ argument, due to mismatch between the data encoding and the
+ locale.
+
+ Even if it wasn't for that bug we wouldn't want to use LC_CTYPE at
+ this point, because it'd require auditing all the code that uses C
+ functions whose semantics are modified by LC_CTYPE.
+
+ But only setting LC_MESSAGES as we do creates a problem, since
+ we declare the encoding of our PO files[2] the gettext
+ implementation will try to recode it to the user's locale, but
+ without LC_CTYPE it'll emit something like this on 'git init'
+ under the Icelandic locale:
+
+ Bj? til t?ma Git lind ? /hlagh/.git/
+
+ Gettext knows about the encoding of our PO file, but we haven't
+ told it about the user's encoding, so all the non-US-ASCII
+ characters get encoded to question marks.
+
+ But we're in luck! We can set LC_CTYPE from the environment
+ only while we call nl_langinfo and
+ bind_textdomain_codeset. That suffices to tell gettext what
+ encoding it should emit in, so it'll now say:
+
+ Bjó til tóma Git lind í /hlagh/.git/
+
+ And the equivalent ISO-8859-1 string will be emitted under a
+ ISO-8859-1 locale.
+
+ With this change way we get the advantages of setting LC_CTYPE
+ (talk to the user in his language/encoding), without the major
+ drawbacks (changed semantics for C functions we rely on).
+
+ However foreign functions using other message catalogs that
+ aren't using our neat trick will still have a problem, e.g. if
+ we have to call perror(3):
+
+ #include <stdio.h>
+ #include <locale.h>
+ #include <errno.h>
+
+ int main(void)
+ {
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "C");
+ errno = ENODEV;
+ perror("test");
+ return 0;
+ }
+
+ Running that will give you a message with question marks:
+
+ $ LANGUAGE= LANG=de_DE.utf8 ./test
+ test: Kein passendes Ger?t gefunden
+
+ In the long term we should probably see about getting that
+ vsnprintf bug in glibc fixed, and audit our code so it won't
+ fall apart under a non-C locale.
+
+ Then we could simply set LC_CTYPE from the environment, which would
+ make things like the external perror(3) messages work.
+
+ See t/t0203-gettext-setlocale-sanity.sh's "gettext.c" tests for
+ regression tests.
+
+ 1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530
+ 2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po
+ */
+ setlocale(LC_CTYPE, "");
+ charset = locale_charset();
+ bind_textdomain_codeset(domain, charset);
+ setlocale(LC_CTYPE, "C");
+}
+
+void git_setup_gettext(void)
+{
+ const char *podir = getenv("GIT_TEXTDOMAINDIR");
+
+ if (!podir)
+ podir = GIT_LOCALE_PATH;
+ bindtextdomain("git", podir);
+ setlocale(LC_MESSAGES, "");
+ init_gettext_charset("git");
+ textdomain("git");
+}
+#endif
diff --git a/gettext.h b/gettext.h
index 24d9182..57ba8bb 100644
--- a/gettext.h
+++ b/gettext.h
@@ -13,8 +13,29 @@
#error "namespace conflict: '_' or 'Q_' is pre-defined?"
#endif
+#ifndef NO_GETTEXT
+# include <libintl.h>
+#else
+# ifdef gettext
+# undef gettext
+# endif
+# define gettext(s) (s)
+# ifdef ngettext
+# undef ngettext
+# endif
+# define ngettext(s, p, n) ((n == 1) ? (s) : (p))
+#endif
+
#define FORMAT_PRESERVING(n) __attribute__((format_arg(n)))
+#ifndef NO_GETTEXT
+extern void git_setup_gettext(void);
+#else
+static inline void git_setup_gettext(void)
+{
+}
+#endif
+
#ifdef GETTEXT_POISON
extern int use_gettext_poison(void);
#else
@@ -23,7 +44,7 @@ extern int use_gettext_poison(void);
static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
{
- return use_gettext_poison() ? "# GETTEXT POISON #" : msgid;
+ return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid);
}
static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
@@ -31,7 +52,7 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n)
{
if (use_gettext_poison())
return "# GETTEXT POISON #";
- return n == 1 ? msgid : plu;
+ return ngettext(msgid, plu, n);
}
/* Mark msgid for translation but do not translate it. */
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 8f0839d..710764a 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -268,6 +268,7 @@ sub get_empty_tree {
# FILE: is file different from index?
# INDEX_ADDDEL: is it add/delete between HEAD and index?
# FILE_ADDDEL: is it add/delete between index and file?
+# UNMERGED: is the path unmerged
sub list_modified {
my ($only) = @_;
@@ -318,16 +319,10 @@ sub list_modified {
}
}
- for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) {
+ for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @tracked)) {
if (($add, $del, $file) =
/^([-\d]+) ([-\d]+) (.*)/) {
$file = unquote_path($file);
- if (!exists $data{$file}) {
- $data{$file} = +{
- INDEX => 'unchanged',
- BINARY => 0,
- };
- }
my ($change, $bin);
if ($add eq '-' && $del eq '-') {
$change = 'binary';
@@ -346,6 +341,18 @@ sub list_modified {
$file = unquote_path($file);
$data{$file}{FILE_ADDDEL} = $adddel;
}
+ elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
+ $file = unquote_path($2);
+ if (!exists $data{$file}) {
+ $data{$file} = +{
+ INDEX => 'unchanged',
+ BINARY => 0,
+ };
+ }
+ if ($1 eq 'U') {
+ $data{$file}{UNMERGED} = 1;
+ }
+ }
}
for (sort keys %data) {
@@ -1060,7 +1067,6 @@ EOF
}
sub diff_applies {
- my $fh;
return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
map { @{$_->{TEXT}} } @_);
}
@@ -1190,6 +1196,10 @@ sub apply_patch_for_checkout_commit {
sub patch_update_cmd {
my @all_mods = list_modified($patch_mode_flavour{FILTER});
+ error_msg "ignoring unmerged: $_->{VALUE}\n"
+ for grep { $_->{UNMERGED} } @all_mods;
+ @all_mods = grep { !$_->{UNMERGED} } @all_mods;
+
my @mods = grep { !($_->{BINARY}) } @all_mods;
my @them;
@@ -1503,7 +1513,6 @@ sub patch_update_file {
}
if (@result) {
- my $fh;
my @patch = reassemble_patch($head->{TEXT}, @result);
my $apply_routine = $patch_mode_flavour{APPLY};
&$apply_routine(@patch);
diff --git a/git-am.sh b/git-am.sh
index f5afe15..f8b7a0c 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -15,6 +15,7 @@ q,quiet be quiet
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
+keep-non-patch pass -b flag to git-mailinfo
keep-cr pass --keep-cr flag to git-mailsplit for mbox format
no-keep-cr do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
c,scissors strip everything before a scissors line
@@ -22,6 +23,8 @@ whitespace= pass it through git-apply
ignore-space-change pass it through git-apply
ignore-whitespace pass it through git-apply
directory= pass it through git-apply
+exclude= pass it through git-apply
+include= pass it through git-apply
C= pass it through git-apply
p= pass it through git-apply
patch-format= format the patch(es) are in
@@ -37,13 +40,14 @@ rerere-autoupdate update the index with reused conflict resolution if possible
rebasing* (internal use for git-rebase)"
. git-sh-setup
+. git-sh-i18n
prefix=$(git rev-parse --show-prefix)
set_reflog_action am
require_work_tree
cd_to_toplevel
git var GIT_COMMITTER_IDENT >/dev/null ||
- die "You need to set your committer info first"
+ die "$(gettext "You need to set your committer info first")"
if git rev-parse --verify -q HEAD >/dev/null
then
@@ -88,8 +92,8 @@ safe_to_abort () {
then
return 0
fi
- echo >&2 "You seem to have moved HEAD since the last 'am' failure."
- echo >&2 "Not rewinding to ORIG_HEAD"
+ gettextln "You seem to have moved HEAD since the last 'am' failure.
+Not rewinding to ORIG_HEAD" >&2
return 1
}
@@ -98,9 +102,9 @@ stop_here_user_resolve () {
printf '%s\n' "$resolvemsg"
stop_here $1
fi
- echo "When you have resolved this problem run \"$cmdline --resolved\"."
- echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
- echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
+ eval_gettextln "When you have resolved this problem run \"\$cmdline --resolved\".
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."
stop_here $1
}
@@ -114,7 +118,7 @@ go_next () {
cannot_fallback () {
echo "$1"
- echo "Cannot fall back to three-way merge."
+ gettextln "Cannot fall back to three-way merge."
exit 1
}
@@ -125,21 +129,30 @@ fall_back_3way () {
mkdir "$dotest/patch-merge-tmp-dir"
# First see if the patch records the index info that we can use.
- git apply --build-fake-ancestor "$dotest/patch-merge-tmp-index" \
- "$dotest/patch" &&
+ cmd="git apply $git_apply_opt --build-fake-ancestor" &&
+ cmd="$cmd "'"$dotest/patch-merge-tmp-index" "$dotest/patch"' &&
+ eval "$cmd" &&
GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
git write-tree >"$dotest/patch-merge-base+" ||
- cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
+ cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")"
say Using index info to reconstruct a base tree...
- if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
- git apply --cached <"$dotest/patch"
+
+ cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"'
+
+ if test -z "$GIT_QUIET"
+ then
+ eval "$cmd git diff-index --cached --diff-filter=AM --name-status HEAD"
+ fi
+
+ cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"'
+ if eval "$cmd"
then
mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
else
- cannot_fallback "Did you hand edit your patch?
-It does not apply to blobs recorded in its index."
+ cannot_fallback "$(gettext "Did you hand edit your patch?
+It does not apply to blobs recorded in its index.")"
fi
test -f "$dotest/patch-merge-index" &&
@@ -147,7 +160,7 @@ It does not apply to blobs recorded in its index."
orig_tree=$(cat "$dotest/patch-merge-base") &&
rm -fr "$dotest"/patch-merge-* || exit 1
- say Falling back to patching base and 3-way merge...
+ say "$(gettext "Falling back to patching base and 3-way merge...")"
# This is not so wrong. Depending on which base we picked,
# orig_tree may be wildly different from ours, but his_tree
@@ -192,10 +205,15 @@ check_patch_format () {
return 0
fi
- # otherwise, check the first few lines of the first patch to try
- # to detect its format
+ # otherwise, check the first few non-blank lines of the first
+ # patch to try to detect its format
{
- read l1
+ # Start from first line containing non-whitespace
+ l1=
+ while test -z "$l1"
+ do
+ read l1 || break
+ done
read l2
read l3
case "$l1" in
@@ -254,7 +272,7 @@ split_patches () {
stgit-series)
if test $# -ne 1
then
- clean_abort "Only one StGIT patch series can be applied at once"
+ clean_abort "$(gettext "Only one StGIT patch series can be applied at once")"
fi
series_dir=`dirname "$1"`
series_file="$1"
@@ -288,7 +306,7 @@ split_patches () {
perl -ne 'BEGIN { $subject = 0 }
if ($subject > 1) { print ; }
elsif (/^\s+$/) { next ; }
- elsif (/^Author:/) { print s/Author/From/ ; }
+ elsif (/^Author:/) { s/Author/From/ ; print ;}
elsif (/^(From|Date)/) { print ; }
elsif ($subject) {
$subject = 2 ;
@@ -304,12 +322,46 @@ split_patches () {
this=
msgnum=
;;
+ hg)
+ this=0
+ for hg in "$@"
+ do
+ this=$(( $this + 1 ))
+ msgnum=$(printf "%0${prec}d" $this)
+ # hg stores changeset metadata in #-commented lines preceding
+ # the commit message and diff(s). The only metadata we care about
+ # are the User and Date (Node ID and Parent are hashes which are
+ # only relevant to the hg repository and thus not useful to us)
+ # Since we cannot guarantee that the commit message is in
+ # git-friendly format, we put no Subject: line and just consume
+ # all of the message as the body
+ perl -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 }
+ if ($subject) { print ; }
+ elsif (/^\# User /) { s/\# User/From:/ ; print ; }
+ elsif (/^\# Date /) {
+ my ($hashsign, $str, $time, $tz) = split ;
+ $tz = sprintf "%+05d", (0-$tz)/36;
+ print "Date: " .
+ strftime("%a, %d %b %Y %H:%M:%S ",
+ localtime($time))
+ . "$tz\n";
+ } elsif (/^\# /) { next ; }
+ else {
+ print "\n", $_ ;
+ $subject = 1;
+ }
+ ' <"$hg" >"$dotest/$msgnum" || clean_abort
+ done
+ echo "$this" >"$dotest/last"
+ this=
+ msgnum=
+ ;;
*)
if test -n "$patch_format"
then
- clean_abort "Patch format $patch_format is not supported."
+ clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")"
else
- clean_abort "Patch format detection failed."
+ clean_abort "$(gettext "Patch format detection failed.")"
fi
;;
esac
@@ -335,7 +387,9 @@ do
-i|--interactive)
interactive=t ;;
-b|--binary)
- : ;;
+ echo >&2 "The $1 option has been a no-op for long time, and"
+ echo >&2 "it will be removed. Please do not use it anymore."
+ ;;
-3|--3way)
threeway=t ;;
-s|--signoff)
@@ -346,6 +400,8 @@ do
utf8= ;;
-k|--keep)
keep=t ;;
+ --keep-non-patch)
+ keep=b ;;
-c|--scissors)
scissors=t ;;
--no-scissors)
@@ -359,11 +415,11 @@ do
--rebasing)
rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
-d|--dotest)
- die "-d option is no longer supported. Do not use."
+ die "$(gettext "-d option is no longer supported. Do not use.")"
;;
--resolvemsg)
shift; resolvemsg=$1 ;;
- --whitespace|--directory)
+ --whitespace|--directory|--exclude|--include)
git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
-C|-p)
git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
@@ -422,12 +478,12 @@ then
false
;;
esac ||
- die "previous rebase directory $dotest still exists but mbox given."
+ die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")"
resume=yes
case "$skip,$abort" in
t,t)
- die "Please make up your mind. --skip or --abort?"
+ die "$(gettext "Please make up your mind. --skip or --abort?")"
;;
t,)
git rerere clear
@@ -454,7 +510,7 @@ then
else
# Make sure we are not given --skip, --resolved, nor --abort
test "$skip$resolved$abort" = "" ||
- die "Resolve operation not in progress, we are not resuming."
+ die "$(gettext "Resolve operation not in progress, we are not resuming.")"
# Start afresh.
mkdir -p "$dotest" || exit
@@ -489,7 +545,6 @@ else
echo "$sign" >"$dotest/sign"
echo "$utf8" >"$dotest/utf8"
echo "$keep" >"$dotest/keep"
- echo "$keepcr" >"$dotest/keepcr"
echo "$scissors" >"$dotest/scissors"
echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
echo "$GIT_QUIET" >"$dotest/quiet"
@@ -521,25 +576,28 @@ case "$resolved" in
if test "$files"
then
test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
- die "Dirty index: cannot apply patches (dirty: $files)"
+ die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")"
fi
esac
+# Now, decide what command line options we will give to the git
+# commands we invoke, based on the result of parsing command line
+# options and previous invocation state stored in $dotest/ files.
+
if test "$(cat "$dotest/utf8")" = t
then
utf8=-u
else
utf8=-n
fi
-if test "$(cat "$dotest/keep")" = t
-then
- keep=-k
-fi
-case "$(cat "$dotest/keepcr")" in
+keep=$(cat "$dotest/keep")
+case "$keep" in
t)
- keepcr=--keep-cr ;;
-f)
- keepcr=--no-keep-cr ;;
+ keep=-k ;;
+b)
+ keep=-b ;;
+*)
+ keep= ;;
esac
case "$(cat "$dotest/scissors")" in
t)
@@ -610,9 +668,9 @@ do
go_next && continue
test -s "$dotest/patch" || {
- echo "Patch is empty. Was it split wrong?"
- echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
- echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
+ eval_gettextln "Patch is empty. Was it split wrong?
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."
stop_here $this
}
rm -f "$dotest/original-commit" "$dotest/author-script"
@@ -647,7 +705,7 @@ do
if test -z "$GIT_AUTHOR_EMAIL"
then
- echo "Patch does not have a valid e-mail address."
+ gettextln "Patch does not have a valid e-mail address."
stop_here $this
fi
@@ -694,15 +752,18 @@ do
if test "$interactive" = t
then
test -t 0 ||
- die "cannot be interactive without stdin connected to a terminal."
+ die "$(gettext "cannot be interactive without stdin connected to a terminal.")"
action=again
while test "$action" = again
do
- echo "Commit Body is:"
+ gettextln "Commit Body is:"
echo "--------------------------"
cat "$dotest/final-commit"
echo "--------------------------"
- printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+ # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+ # in your translation. The program will only accept English
+ # input at this point.
+ gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
read reply
case "$reply" in
[yY]*) action=yes ;;
@@ -738,7 +799,7 @@ do
stop_here $this
fi
- say "Applying: $FIRSTLINE"
+ say "$(eval_gettext "Applying: \$FIRSTLINE")"
case "$resolved" in
'')
@@ -759,16 +820,16 @@ do
# working tree.
resolved=
git diff-index --quiet --cached HEAD -- && {
- echo "No changes - did you forget to use 'git add'?"
- echo "If there is nothing left to stage, chances are that something else"
- echo "already introduced the same changes; you might want to skip this patch."
+ gettextln "No changes - did you forget to use 'git add'?
+If there is nothing left to stage, chances are that something else
+already introduced the same changes; you might want to skip this patch."
stop_here_user_resolve $this
}
unmerged=$(git ls-files -u)
if test -n "$unmerged"
then
- echo "You still have unmerged paths in your index"
- echo "did you forget to use 'git add'?"
+ gettextln "You still have unmerged paths in your index
+did you forget to use 'git add'?"
stop_here_user_resolve $this
fi
apply_status=0
@@ -783,7 +844,7 @@ do
# Applying the patch to an earlier tree and merging the
# result may have produced the same tree as ours.
git diff-index --quiet --cached HEAD -- && {
- say No changes -- Patch already applied.
+ say "$(gettext "No changes -- Patch already applied.")"
go_next
continue
}
@@ -793,7 +854,7 @@ do
fi
if test $apply_status != 0
then
- printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE"
+ eval_gettextln 'Patch failed at $msgnum $FIRSTLINE'
stop_here_user_resolve $this
fi
@@ -809,7 +870,7 @@ do
GIT_AUTHOR_DATE=
fi
parent=$(git rev-parse --verify -q HEAD) ||
- say >&2 "applying to an empty history"
+ say >&2 "$(gettext "applying to an empty history")"
if test -n "$committer_date_is_author_date"
then
diff --git a/git-bisect.sh b/git-bisect.sh
index 415a8d0..99efbe8 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -2,43 +2,56 @@
USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'
LONG_USAGE='git bisect help
- print this long help message.
-git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
- reset bisect state and start bisection.
+ print this long help message.
+git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
+ reset bisect state and start bisection.
git bisect bad [<rev>]
- mark <rev> a known-bad revision.
+ mark <rev> a known-bad revision.
git bisect good [<rev>...]
- mark <rev>... known-good revisions.
+ mark <rev>... known-good revisions.
git bisect skip [(<rev>|<range>)...]
- mark <rev>... untestable revisions.
+ mark <rev>... untestable revisions.
git bisect next
- find next bisection to test and check it out.
+ find next bisection to test and check it out.
git bisect reset [<commit>]
- finish bisection search and go back to commit.
+ finish bisection search and go back to commit.
git bisect visualize
- show bisect status in gitk.
+ show bisect status in gitk.
git bisect replay <logfile>
- replay bisection log.
+ replay bisection log.
git bisect log
- show bisect log.
+ show bisect log.
git bisect run <cmd>...
- use <cmd>... to automatically bisect.
+ use <cmd>... to automatically bisect.
Please use "git help bisect" to get the full man page.'
OPTIONS_SPEC=
. git-sh-setup
-require_work_tree
+. git-sh-i18n
_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"
+bisect_head()
+{
+ if test -f "$GIT_DIR/BISECT_HEAD"
+ then
+ echo BISECT_HEAD
+ else
+ echo HEAD
+ fi
+}
+
bisect_autostart() {
test -s "$GIT_DIR/BISECT_START" || {
- echo >&2 'You need to start by "git bisect start"'
+ gettextln "You need to start by \"git bisect start\"" >&2
if test -t 0
then
- echo >&2 -n 'Do you want me to do it for you [Y/n]? '
+ # TRANSLATORS: Make sure to include [Y] and [n] in your
+ # translation. The program will only accept English input
+ # at this point.
+ gettext "Do you want me to do it for you [Y/n]? " >&2
read yesno
case "$yesno" in
[Nn]*)
@@ -53,11 +66,55 @@ bisect_autostart() {
bisect_start() {
#
+ # Check for one bad and then some good revisions.
+ #
+ has_double_dash=0
+ for arg; do
+ case "$arg" in --) has_double_dash=1; break ;; esac
+ done
+ orig_args=$(git rev-parse --sq-quote "$@")
+ bad_seen=0
+ eval=''
+ if test "z$(git rev-parse --is-bare-repository)" != zfalse
+ then
+ mode=--no-checkout
+ else
+ mode=''
+ fi
+ while [ $# -gt 0 ]; do
+ arg="$1"
+ case "$arg" in
+ --)
+ shift
+ break
+ ;;
+ --no-checkout)
+ mode=--no-checkout
+ shift ;;
+ --*)
+ die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
+ *)
+ rev=$(git rev-parse -q --verify "$arg^{commit}") || {
+ test $has_double_dash -eq 1 &&
+ die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
+ break
+ }
+ case $bad_seen in
+ 0) state='bad' ; bad_seen=1 ;;
+ *) state='good' ;;
+ esac
+ eval="$eval bisect_write '$state' '$rev' 'nolog' &&"
+ shift
+ ;;
+ esac
+ done
+
+ #
# Verify HEAD.
#
head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
- die "Bad HEAD - I need a HEAD"
+ die "$(gettext "Bad HEAD - I need a HEAD")"
#
# Check if we are bisecting.
@@ -67,7 +124,11 @@ bisect_start() {
then
# Reset to the rev from where we started.
start_head=$(cat "$GIT_DIR/BISECT_START")
- git checkout "$start_head" -- || exit
+ if test "z$mode" != "z--no-checkout"
+ then
+ git checkout "$start_head" -- ||
+ die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <validbranch>'.")"
+ fi
else
# Get rev from where we start.
case "$head" in
@@ -76,11 +137,11 @@ bisect_start() {
# cogito usage, and cogito users should understand
# it relates to cg-seek.
[ -s "$GIT_DIR/head-name" ] &&
- die "won't bisect on seeked tree"
+ die "$(gettext "won't bisect on seeked tree")"
start_head="${head#refs/heads/}"
;;
*)
- die "Bad HEAD - strange symbolic ref"
+ die "$(gettext "Bad HEAD - strange symbolic ref")"
;;
esac
fi
@@ -91,39 +152,6 @@ bisect_start() {
bisect_clean_state || exit
#
- # Check for one bad and then some good revisions.
- #
- has_double_dash=0
- for arg; do
- case "$arg" in --) has_double_dash=1; break ;; esac
- done
- orig_args=$(git rev-parse --sq-quote "$@")
- bad_seen=0
- eval=''
- while [ $# -gt 0 ]; do
- arg="$1"
- case "$arg" in
- --)
- shift
- break
- ;;
- *)
- rev=$(git rev-parse -q --verify "$arg^{commit}") || {
- test $has_double_dash -eq 1 &&
- die "'$arg' does not appear to be a valid revision"
- break
- }
- case $bad_seen in
- 0) state='bad' ; bad_seen=1 ;;
- *) state='good' ;;
- esac
- eval="$eval bisect_write '$state' '$rev' 'nolog'; "
- shift
- ;;
- esac
- done
-
- #
# Change state.
# In case of mistaken revs or checkout error, or signals received,
# "bisect_auto_next" below may exit or misbehave.
@@ -136,9 +164,12 @@ bisect_start() {
#
# Write new start state.
#
- echo "$start_head" >"$GIT_DIR/BISECT_START" &&
+ echo "$start_head" >"$GIT_DIR/BISECT_START" && {
+ test "z$mode" != "z--no-checkout" ||
+ git update-ref --no-deref BISECT_HEAD "$start_head"
+ } &&
git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
- eval "$eval" &&
+ eval "$eval true" &&
echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
#
# Check if we can proceed to the next bisect state.
@@ -155,7 +186,7 @@ bisect_write() {
case "$state" in
bad) tag="$state" ;;
good|skip) tag="$state"-"$rev" ;;
- *) die "Bad bisect_write argument: $state" ;;
+ *) die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
esac
git update-ref "refs/bisect/$tag" "$rev" || exit
echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
@@ -169,7 +200,8 @@ is_expected_rev() {
check_expected_revs() {
for _rev in "$@"; do
- if ! is_expected_rev "$_rev"; then
+ if ! is_expected_rev "$_rev"
+ then
rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
return
@@ -178,18 +210,18 @@ check_expected_revs() {
}
bisect_skip() {
- all=''
+ all=''
for arg in "$@"
do
- case "$arg" in
- *..*)
- revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
- *)
- revs=$(git rev-parse --sq-quote "$arg") ;;
- esac
- all="$all $revs"
- done
- eval bisect_state 'skip' $all
+ case "$arg" in
+ *..*)
+ revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
+ *)
+ revs=$(git rev-parse --sq-quote "$arg") ;;
+ esac
+ all="$all $revs"
+ done
+ eval bisect_state 'skip' $all
}
bisect_state() {
@@ -197,10 +229,10 @@ bisect_state() {
state=$1
case "$#,$state" in
0,*)
- die "Please call 'bisect_state' with at least one argument." ;;
+ die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
1,bad|1,good|1,skip)
- rev=$(git rev-parse --verify HEAD) ||
- die "Bad rev input: HEAD"
+ rev=$(git rev-parse --verify $(bisect_head)) ||
+ die "$(gettext "Bad rev input: $(bisect_head)")"
bisect_write "$state" "$rev"
check_expected_revs "$rev" ;;
2,bad|*,good|*,skip)
@@ -209,13 +241,13 @@ bisect_state() {
for rev in "$@"
do
sha=$(git rev-parse --verify "$rev^{commit}") ||
- die "Bad rev input: $rev"
+ die "$(eval_gettext "Bad rev input: \$rev")"
eval="$eval bisect_write '$state' '$sha'; "
done
eval "$eval"
check_expected_revs "$@" ;;
*,bad)
- die "'git bisect bad' can take only one argument." ;;
+ die "$(gettext "'git bisect bad' can take only one argument.")" ;;
*)
usage ;;
esac
@@ -238,25 +270,29 @@ bisect_next_check() {
t,,good)
# have bad but not good. we could bisect although
# this is less optimum.
- echo >&2 'Warning: bisecting only with a bad commit.'
+ gettextln "Warning: bisecting only with a bad commit." >&2
if test -t 0
then
- printf >&2 'Are you sure [Y/n]? '
+ # TRANSLATORS: Make sure to include [Y] and [n] in your
+ # translation. The program will only accept English input
+ # at this point.
+ gettext "Are you sure [Y/n]? " >&2
read yesno
case "$yesno" in [Nn]*) exit 1 ;; esac
fi
: bisect without good...
;;
*)
- THEN=''
- test -s "$GIT_DIR/BISECT_START" || {
- echo >&2 'You need to start by "git bisect start".'
- THEN='then '
- }
- echo >&2 'You '$THEN'need to give me at least one good' \
- 'and one bad revisions.'
- echo >&2 '(You can use "git bisect bad" and' \
- '"git bisect good" for that.)'
+
+ if test -s "$GIT_DIR/BISECT_START"
+ then
+ gettextln "You need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
+ else
+ gettextln "You need to start by \"git bisect start\".
+You then need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
+ fi
exit 1 ;;
esac
}
@@ -271,10 +307,10 @@ bisect_next() {
bisect_next_check good
# Perform all bisection computation, display and checkout
- git bisect--helper --next-all
+ git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
res=$?
- # Check if we should exit because bisection is finished
+ # Check if we should exit because bisection is finished
test $res -eq 10 && exit 0
# Check for an error in the bisection process
@@ -289,7 +325,8 @@ bisect_visualize() {
if test $# = 0
then
if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
- type gitk >/dev/null 2>&1; then
+ type gitk >/dev/null 2>&1
+ then
set gitk
else
set git log
@@ -307,23 +344,26 @@ bisect_visualize() {
bisect_reset() {
test -s "$GIT_DIR/BISECT_START" || {
- echo "We are not bisecting."
+ gettextln "We are not bisecting."
return
}
case "$#" in
0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
- 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null ||
- die "'$1' is not a valid commit"
- branch="$1" ;;
+ 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
+ invalid="$1"
+ die "$(eval_gettext "'\$invalid' is not a valid commit")"
+ }
+ branch="$1" ;;
*)
- usage ;;
+ usage ;;
esac
- if git checkout "$branch" -- ; then
- bisect_clean_state
- else
- die "Could not check out original HEAD '$branch'." \
- "Try 'git bisect reset <commit>'."
+
+ if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
+ then
+ die "$(eval_gettext "Could not check out original HEAD '\$branch'.
+Try 'git bisect reset <commit>'.")"
fi
+ bisect_clean_state
}
bisect_clean_state() {
@@ -340,18 +380,21 @@ bisect_clean_state() {
rm -f "$GIT_DIR/BISECT_RUN" &&
# Cleanup head-name if it got left by an old version of git-bisect
rm -f "$GIT_DIR/head-name" &&
-
+ git update-ref -d --no-deref BISECT_HEAD &&
+ # clean up BISECT_START last
rm -f "$GIT_DIR/BISECT_START"
}
bisect_replay () {
- test "$#" -eq 1 || die "No logfile given"
- test -r "$1" || die "cannot read $1 for replaying"
+ file="$1"
+ test "$#" -eq 1 || die "$(gettext "No logfile given")"
+ test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
bisect_reset
while read git bisect command rev
do
test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
- if test "$git" = "git-bisect"; then
+ if test "$git" = "git-bisect"
+ then
rev="$command"
command="$bisect"
fi
@@ -362,98 +405,105 @@ bisect_replay () {
good|bad|skip)
bisect_write "$command" "$rev" ;;
*)
- die "?? what are you talking about?" ;;
+ die "$(gettext "?? what are you talking about?")" ;;
esac
- done <"$1"
+ done <"$file"
bisect_auto_next
}
bisect_run () {
- bisect_next_check fail
-
- while true
- do
- echo "running $@"
- "$@"
- res=$?
-
- # Check for really bad run error.
- if [ $res -lt 0 -o $res -ge 128 ]; then
- echo >&2 "bisect run failed:"
- echo >&2 "exit code $res from '$@' is < 0 or >= 128"
- exit $res
- fi
-
- # 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
- state='good'
- fi
-
- # 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 sane_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 "'bisect_state $state' exited with error code $res"
- exit $res
- fi
-
- if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
- echo "bisect run success"
- exit 0;
- fi
-
- done
+ bisect_next_check fail
+
+ while true
+ do
+ command="$@"
+ eval_gettextln "running \$command"
+ "$@"
+ res=$?
+
+ # Check for really bad run error.
+ if [ $res -lt 0 -o $res -ge 128 ]
+ then
+ eval_gettextln "bisect run failed:
+exit code \$res from '\$command' is < 0 or >= 128" >&2
+ exit $res
+ fi
+
+ # 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
+ state='good'
+ fi
+
+ # 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 sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
+ > /dev/null
+ then
+ gettextln "bisect run cannot continue any more" >&2
+ exit $res
+ fi
+
+ if [ $res -ne 0 ]
+ then
+ eval_gettextln "bisect run failed:
+'bisect_state \$state' exited with error code \$res" >&2
+ exit $res
+ fi
+
+ if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null
+ then
+ gettextln "bisect run success"
+ exit 0;
+ fi
+
+ done
}
bisect_log () {
- test -s "$GIT_DIR/BISECT_LOG" || die "We are not bisecting."
+ test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
cat "$GIT_DIR/BISECT_LOG"
}
case "$#" in
0)
- usage ;;
+ usage ;;
*)
- cmd="$1"
- shift
- case "$cmd" in
- help)
- git bisect -h ;;
- start)
- bisect_start "$@" ;;
- bad|good)
- bisect_state "$cmd" "$@" ;;
- skip)
- bisect_skip "$@" ;;
- next)
- # Not sure we want "next" at the UI level anymore.
- bisect_next "$@" ;;
- visualize|view)
- bisect_visualize "$@" ;;
- reset)
- bisect_reset "$@" ;;
- replay)
- bisect_replay "$@" ;;
- log)
- bisect_log ;;
- run)
- bisect_run "$@" ;;
- *)
- usage ;;
- esac
+ cmd="$1"
+ shift
+ case "$cmd" in
+ help)
+ git bisect -h ;;
+ start)
+ bisect_start "$@" ;;
+ bad|good)
+ bisect_state "$cmd" "$@" ;;
+ skip)
+ bisect_skip "$@" ;;
+ next)
+ # Not sure we want "next" at the UI level anymore.
+ bisect_next "$@" ;;
+ visualize|view)
+ bisect_visualize "$@" ;;
+ reset)
+ bisect_reset "$@" ;;
+ replay)
+ bisect_replay "$@" ;;
+ log)
+ bisect_log ;;
+ run)
+ bisect_run "$@" ;;
+ *)
+ usage ;;
+ esac
esac
diff --git a/git-compat-util.h b/git-compat-util.h
index 041cbdb..5bd9ad7 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -116,7 +116,12 @@
#else
#include <poll.h>
#endif
-#ifndef __MINGW32__
+#if defined(__MINGW32__)
+/* pull in Windows compatibility stuff */
+#include "compat/mingw.h"
+#elif defined(_MSC_VER)
+#include "compat/msvc.h"
+#else
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/socket.h>
@@ -130,6 +135,7 @@
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
+#include <sys/un.h>
#ifndef NO_INTTYPES_H
#include <inttypes.h>
#else
@@ -145,12 +151,6 @@
#include <grp.h>
#define _ALL_SOURCE 1
#endif
-#else /* __MINGW32__ */
-/* pull in Windows compatibility stuff */
-#include "compat/mingw.h"
-#endif /* __MINGW32__ */
-#ifdef _MSC_VER
-#include "compat/msvc.h"
#endif
#ifndef NO_LIBGEN_H
@@ -215,10 +215,14 @@ extern char *gitbasename(char *);
#define is_dir_sep(c) ((c) == '/')
#endif
-#if __HP_cc >= 61000
+#ifndef find_last_dir_sep
+#define find_last_dir_sep(path) strrchr(path, '/')
+#endif
+
+#if defined(__HP_cc) && (__HP_cc >= 61000)
#define NORETURN __attribute__((noreturn))
#define NORETURN_PTR
-#elif defined(__GNUC__)
+#elif defined(__GNUC__) && !defined(NO_NORETURN)
#define NORETURN __attribute__((__noreturn__))
#define NORETURN_PTR __attribute__((__noreturn__))
#elif defined(_MSC_VER)
@@ -347,6 +351,8 @@ extern size_t gitstrlcpy(char *, const char *, size_t);
#ifdef NO_STRTOUMAX
#define strtoumax gitstrtoumax
extern uintmax_t gitstrtoumax(const char *, char **, int);
+#define strtoimax gitstrtoimax
+extern intmax_t gitstrtoimax(const char *, char **, int);
#endif
#ifdef NO_STRTOK_R
@@ -451,12 +457,17 @@ static inline int has_extension(const char *filename, const char *ext)
return len > extlen && !memcmp(filename + len - extlen, ext, extlen);
}
+/* in ctype.c, for kwset users */
+extern const char tolower_trans_tbl[256];
+
/* Sane ctype - no locale, and works with signed chars */
#undef isascii
#undef isspace
#undef isdigit
#undef isalpha
#undef isalnum
+#undef islower
+#undef isupper
#undef tolower
#undef toupper
extern unsigned char sane_ctype[256];
@@ -472,6 +483,8 @@ extern unsigned char sane_ctype[256];
#define isdigit(x) sane_istest(x,GIT_DIGIT)
#define isalpha(x) sane_istest(x,GIT_ALPHA)
#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define islower(x) sane_iscase(x, 1)
+#define isupper(x) sane_iscase(x, 0)
#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
#define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
#define tolower(x) sane_case((unsigned char)(x), 0x20)
@@ -485,6 +498,17 @@ static inline int sane_case(int x, int high)
return x;
}
+static inline int sane_iscase(int x, int is_lower)
+{
+ if (!sane_istest(x, GIT_ALPHA))
+ return 0;
+
+ if (is_lower)
+ return (x & 0x20) != 0;
+ else
+ return (x & 0x20) == 0;
+}
+
static inline int strtoul_ui(char const *s, int base, unsigned int *result)
{
unsigned long ul;
@@ -571,4 +595,7 @@ int rmdir_or_warn(const char *path);
*/
int remove_or_warn(unsigned int mode, const char *path);
+/* Get the passwd entry for the UID of the current process. */
+struct passwd *xgetpwuid_self(void);
+
#endif
diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
index 39a426e..e6bf252 100755
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
@@ -30,6 +30,13 @@ if ($opt_w || $opt_W) {
chomp($gd);
$ENV{GIT_DIR} = $gd;
}
+
+ # On MSYS, convert a Windows-style path to an MSYS-style path
+ # so that rel2abs() below works correctly.
+ if ($^O eq 'msys') {
+ $ENV{GIT_DIR} =~ s#^([[:alpha:]]):/#/$1/#;
+ }
+
# Make sure GIT_DIR is absolute
$ENV{GIT_DIR} = File::Spec->rel2abs($ENV{GIT_DIR});
}
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 1b8bff2..b8eddab 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -109,14 +109,14 @@ my $usage =
" --strict-paths : Don't allow recursing into subdirectories\n".
" --export-all : Don't check for gitcvs.enabled in config\n".
" --version, -V : Print version information and exit\n".
- " --help, -h, -H : Print usage information and exit\n".
+ " -h, -H : Print usage information and exit\n".
"\n".
"<directory> ... is a list of allowed directories. If no directories\n".
"are given, all are allowed. This is an additional restriction, gitcvs\n".
"access still needs to be enabled by the gitcvs.enabled config option.\n".
"Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n";
-my @opts = ( 'help|h|H', 'version|V',
+my @opts = ( 'h|H', 'version|V',
'base-path=s', 'strict-paths', 'export-all' );
GetOptions( $state, @opts )
or die $usage;
diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh
index 0594bf7..3d0fe0c 100755
--- a/git-difftool--helper.sh
+++ b/git-difftool--helper.sh
@@ -13,7 +13,8 @@ TOOL_MODE=diff
should_prompt () {
prompt_merge=$(git config --bool mergetool.prompt || echo true)
prompt=$(git config --bool difftool.prompt || echo $prompt_merge)
- if test "$prompt" = true; then
+ if test "$prompt" = true
+ then
test -z "$GIT_DIFFTOOL_NO_PROMPT"
else
test -n "$GIT_DIFFTOOL_PROMPT"
@@ -37,18 +38,24 @@ launch_merge_tool () {
# $LOCAL and $REMOTE are temporary files so prompt
# the user with the real $MERGED name before launching $merge_tool.
- if should_prompt; then
+ if should_prompt
+ then
printf "\nViewing: '$MERGED'\n"
- if use_ext_cmd; then
- printf "Hit return to launch '%s': " \
+ if use_ext_cmd
+ then
+ printf "Launch '%s' [Y/n]: " \
"$GIT_DIFFTOOL_EXTCMD"
else
- printf "Hit return to launch '%s': " "$merge_tool"
+ printf "Launch '%s' [Y/n]: " "$merge_tool"
+ fi
+ if read ans && test "$ans" = n
+ then
+ return
fi
- read ans
fi
- if use_ext_cmd; then
+ if use_ext_cmd
+ then
export BASE
eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
else
@@ -56,17 +63,26 @@ launch_merge_tool () {
fi
}
-if ! use_ext_cmd; then
- if test -n "$GIT_DIFF_TOOL"; then
+if ! use_ext_cmd
+then
+ if test -n "$GIT_DIFF_TOOL"
+ then
merge_tool="$GIT_DIFF_TOOL"
else
merge_tool="$(get_merge_tool)" || exit
fi
fi
-# Launch the merge tool on each path provided by 'git diff'
-while test $# -gt 6
-do
- launch_merge_tool "$1" "$2" "$5"
- shift 7
-done
+if test -n "$GIT_DIFFTOOL_DIRDIFF"
+then
+ LOCAL="$1"
+ REMOTE="$2"
+ run_merge_tool "$merge_tool" false
+else
+ # Launch the merge tool on each path provided by 'git diff'
+ while test $# -gt 6
+ do
+ launch_merge_tool "$1" "$2" "$5"
+ shift 7
+ done
+fi
diff --git a/git-difftool.perl b/git-difftool.perl
index ced1615..ae1e052 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -1,121 +1,361 @@
-#!/usr/bin/env perl
+#!/usr/bin/perl
# Copyright (c) 2009, 2010 David Aguilar
+# Copyright (c) 2012 Tim Henigan
#
# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
# git-difftool--helper script.
#
# This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
-# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL
-# are exported for use by git-difftool--helper.
+# The GIT_DIFF* variables are exported for use by git-difftool--helper.
#
# Any arguments that are unknown to this script are forwarded to 'git diff'.
use 5.008;
use strict;
use warnings;
-use Cwd qw(abs_path);
use File::Basename qw(dirname);
+use File::Copy;
+use File::Find;
+use File::stat;
+use File::Path qw(mkpath);
+use File::Temp qw(tempdir);
+use Getopt::Long qw(:config pass_through);
+use Git;
-require Git;
-
-my $DIR = abs_path(dirname($0));
-
+my @tools;
+my @working_tree;
+my $rc;
+my $repo = Git->repository();
+my $repo_path = $repo->repo_path();
sub usage
{
+ my $exitcode = shift;
print << 'USAGE';
-usage: git difftool [-t|--tool=<tool>] [-x|--extcmd=<cmd>]
- [-y|--no-prompt] [-g|--gui]
+usage: git difftool [-t|--tool=<tool>] [--tool-help]
+ [-x|--extcmd=<cmd>]
+ [-g|--gui] [--no-gui]
+ [--prompt] [-y|--no-prompt]
+ [-d|--dir-diff]
['git diff' options]
USAGE
- exit 1;
+ exit($exitcode);
}
-sub setup_environment
+sub find_worktree
{
- $ENV{PATH} = "$DIR:$ENV{PATH}";
- $ENV{GIT_PAGER} = '';
- $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
+ # Git->repository->wc_path() does not honor changes to the working
+ # tree location made by $ENV{GIT_WORK_TREE} or the 'core.worktree'
+ # config variable.
+ my $worktree;
+ my $env_worktree = $ENV{GIT_WORK_TREE};
+ my $core_worktree = Git::config('core.worktree');
+
+ if (defined($env_worktree) and (length($env_worktree) > 0)) {
+ $worktree = $env_worktree;
+ } elsif (defined($core_worktree) and (length($core_worktree) > 0)) {
+ $worktree = $core_worktree;
+ } else {
+ $worktree = $repo->wc_path();
+ }
+
+ return $worktree;
}
-sub exe
+my $workdir = find_worktree();
+
+sub filter_tool_scripts
{
- my $exe = shift;
- if ($^O eq 'MSWin32' || $^O eq 'msys') {
- return "$exe.exe";
+ if (-d $_) {
+ if ($_ ne ".") {
+ # Ignore files in subdirectories
+ $File::Find::prune = 1;
+ }
+ } else {
+ if ((-f $_) && ($_ ne "defaults")) {
+ push(@tools, $_);
+ }
}
- return $exe;
}
-sub generate_command
+sub print_tool_help
{
- my @command = (exe('git'), 'diff');
- my $skip_next = 0;
- my $idx = -1;
- my $prompt = '';
- for my $arg (@ARGV) {
- $idx++;
- if ($skip_next) {
- $skip_next = 0;
- next;
+ my ($cmd, @found, @notfound);
+ my $gitpath = Git::exec_path();
+
+ find(\&filter_tool_scripts, "$gitpath/mergetools");
+
+ foreach my $tool (@tools) {
+ $cmd = "TOOL_MODE=diff";
+ $cmd .= ' && . "$(git --exec-path)/git-mergetool--lib"';
+ $cmd .= " && get_merge_tool_path $tool >/dev/null 2>&1";
+ $cmd .= " && can_diff >/dev/null 2>&1";
+ if (system('sh', '-c', $cmd) == 0) {
+ push(@found, $tool);
+ } else {
+ push(@notfound, $tool);
}
- if ($arg eq '-t' || $arg eq '--tool') {
- usage() if $#ARGV <= $idx;
- $ENV{GIT_DIFF_TOOL} = $ARGV[$idx + 1];
- $skip_next = 1;
- next;
+ }
+
+ print "'git difftool --tool=<tool>' may be set to one of the following:\n";
+ print "\t$_\n" for (sort(@found));
+
+ print "\nThe following tools are valid, but not currently available:\n";
+ print "\t$_\n" for (sort(@notfound));
+
+ print "\nNOTE: Some of the tools listed above only work in a windowed\n";
+ print "environment. If run in a terminal-only session, they will fail.\n";
+
+ exit(0);
+}
+
+sub setup_dir_diff
+{
+ # Run the diff; exit immediately if no diff found
+ # 'Repository' and 'WorkingCopy' must be explicitly set to insure that
+ # if $GIT_DIR and $GIT_WORK_TREE are set in ENV, they are actually used
+ # by Git->repository->command*.
+ my $diffrepo = Git->repository(Repository => $repo_path, WorkingCopy => $workdir);
+ my $diffrtn = $diffrepo->command_oneline('diff', '--raw', '--no-abbrev', '-z', @ARGV);
+ exit(0) if (length($diffrtn) == 0);
+
+ # Setup temp directories
+ my $tmpdir = tempdir('git-diffall.XXXXX', CLEANUP => 1, TMPDIR => 1);
+ my $ldir = "$tmpdir/left";
+ my $rdir = "$tmpdir/right";
+ mkpath($ldir) or die $!;
+ mkpath($rdir) or die $!;
+
+ # Build index info for left and right sides of the diff
+ my $submodule_mode = '160000';
+ my $symlink_mode = '120000';
+ my $null_mode = '0' x 6;
+ my $null_sha1 = '0' x 40;
+ my $lindex = '';
+ my $rindex = '';
+ my %submodule;
+ my %symlink;
+ my @rawdiff = split('\0', $diffrtn);
+
+ my $i = 0;
+ while ($i < $#rawdiff) {
+ if ($rawdiff[$i] =~ /^::/) {
+ print "Combined diff formats ('-c' and '--cc') are not supported in directory diff mode.\n";
+ exit(1);
}
- if ($arg =~ /^--tool=/) {
- $ENV{GIT_DIFF_TOOL} = substr($arg, 7);
- next;
+
+ my ($lmode, $rmode, $lsha1, $rsha1, $status) = split(' ', substr($rawdiff[$i], 1));
+ my $src_path = $rawdiff[$i + 1];
+ my $dst_path;
+
+ if ($status =~ /^[CR]/) {
+ $dst_path = $rawdiff[$i + 2];
+ $i += 3;
+ } else {
+ $dst_path = $src_path;
+ $i += 2;
}
- if ($arg eq '-x' || $arg eq '--extcmd') {
- usage() if $#ARGV <= $idx;
- $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1];
- $skip_next = 1;
+
+ if (($lmode eq $submodule_mode) or ($rmode eq $submodule_mode)) {
+ $submodule{$src_path}{left} = $lsha1;
+ if ($lsha1 ne $rsha1) {
+ $submodule{$dst_path}{right} = $rsha1;
+ } else {
+ $submodule{$dst_path}{right} = "$rsha1-dirty";
+ }
next;
}
- if ($arg =~ /^--extcmd=/) {
- $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9);
- next;
+
+ if ($lmode eq $symlink_mode) {
+ $symlink{$src_path}{left} = $diffrepo->command_oneline('show', "$lsha1");
}
- if ($arg eq '-g' || $arg eq '--gui') {
- eval {
- my $tool = Git::command_oneline('config',
- 'diff.guitool');
- if (length($tool)) {
- $ENV{GIT_DIFF_TOOL} = $tool;
- }
- };
- next;
+
+ if ($rmode eq $symlink_mode) {
+ $symlink{$dst_path}{right} = $diffrepo->command_oneline('show', "$rsha1");
}
- if ($arg eq '-y' || $arg eq '--no-prompt') {
- $prompt = 'no';
- next;
+
+ if (($lmode ne $null_mode) and ($status !~ /^C/)) {
+ $lindex .= "$lmode $lsha1\t$src_path\0";
}
- if ($arg eq '--prompt') {
- $prompt = 'yes';
- next;
+
+ if ($rmode ne $null_mode) {
+ if ($rsha1 ne $null_sha1) {
+ $rindex .= "$rmode $rsha1\t$dst_path\0";
+ } else {
+ push(@working_tree, $dst_path);
+ }
}
- if ($arg eq '-h' || $arg eq '--help') {
- usage();
+ }
+
+ # If $GIT_DIR is not set prior to calling 'git update-index' and
+ # 'git checkout-index', then those commands will fail if difftool
+ # is called from a directory other than the repo root.
+ my $must_unset_git_dir = 0;
+ if (not defined($ENV{GIT_DIR})) {
+ $must_unset_git_dir = 1;
+ $ENV{GIT_DIR} = $repo_path;
+ }
+
+ # Populate the left and right directories based on each index file
+ my ($inpipe, $ctx);
+ $ENV{GIT_INDEX_FILE} = "$tmpdir/lindex";
+ ($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);
+ print($inpipe $lindex);
+ $repo->command_close_pipe($inpipe, $ctx);
+ $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
+ exit($rc | ($rc >> 8)) if ($rc != 0);
+
+ $ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
+ ($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);
+ print($inpipe $rindex);
+ $repo->command_close_pipe($inpipe, $ctx);
+ $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
+ exit($rc | ($rc >> 8)) if ($rc != 0);
+
+ # If $GIT_DIR was explicitly set just for the update/checkout
+ # commands, then it should be unset before continuing.
+ delete($ENV{GIT_DIR}) if ($must_unset_git_dir);
+ delete($ENV{GIT_INDEX_FILE});
+
+ # Changes in the working tree need special treatment since they are
+ # not part of the index
+ for my $file (@working_tree) {
+ my $dir = dirname($file);
+ unless (-d "$rdir/$dir") {
+ mkpath("$rdir/$dir") or die $!;
+ }
+ copy("$workdir/$file", "$rdir/$file") or die $!;
+ chmod(stat("$workdir/$file")->mode, "$rdir/$file") or die $!;
+ }
+
+ # Changes to submodules require special treatment. This loop writes a
+ # temporary file to both the left and right directories to show the
+ # change in the recorded SHA1 for the submodule.
+ for my $path (keys %submodule) {
+ if (defined($submodule{$path}{left})) {
+ write_to_file("$ldir/$path", "Subproject commit $submodule{$path}{left}");
+ }
+ if (defined($submodule{$path}{right})) {
+ write_to_file("$rdir/$path", "Subproject commit $submodule{$path}{right}");
+ }
+ }
+
+ # Symbolic links require special treatment. The standard "git diff"
+ # shows only the link itself, not the contents of the link target.
+ # This loop replicates that behavior.
+ for my $path (keys %symlink) {
+ if (defined($symlink{$path}{left})) {
+ write_to_file("$ldir/$path", $symlink{$path}{left});
+ }
+ if (defined($symlink{$path}{right})) {
+ write_to_file("$rdir/$path", $symlink{$path}{right});
}
- push @command, $arg;
}
- if ($prompt eq 'yes') {
- $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
- } elsif ($prompt eq 'no') {
- $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+
+ return ($ldir, $rdir);
+}
+
+sub write_to_file
+{
+ my $path = shift;
+ my $value = shift;
+
+ # Make sure the path to the file exists
+ my $dir = dirname($path);
+ unless (-d "$dir") {
+ mkpath("$dir") or die $!;
}
- return @command
+
+ # If the file already exists in that location, delete it. This
+ # is required in the case of symbolic links.
+ unlink("$path");
+
+ open(my $fh, '>', "$path") or die $!;
+ print($fh $value);
+ close($fh);
}
-setup_environment();
+# parse command-line options. all unrecognized options and arguments
+# are passed through to the 'git diff' command.
+my ($difftool_cmd, $dirdiff, $extcmd, $gui, $help, $prompt, $tool_help);
+GetOptions('g|gui!' => \$gui,
+ 'd|dir-diff' => \$dirdiff,
+ 'h' => \$help,
+ 'prompt!' => \$prompt,
+ 'y' => sub { $prompt = 0; },
+ 't|tool:s' => \$difftool_cmd,
+ 'tool-help' => \$tool_help,
+ 'x|extcmd:s' => \$extcmd);
-# ActiveState Perl for Win32 does not implement POSIX semantics of
-# exec* system call. It just spawns the given executable and finishes
-# the starting program, exiting with code 0.
-# system will at least catch the errors returned by git diff,
-# allowing the caller of git difftool better handling of failures.
-my $rc = system(generate_command());
-exit($rc | ($rc >> 8));
+if (defined($help)) {
+ usage(0);
+}
+if (defined($tool_help)) {
+ print_tool_help();
+}
+if (defined($difftool_cmd)) {
+ if (length($difftool_cmd) > 0) {
+ $ENV{GIT_DIFF_TOOL} = $difftool_cmd;
+ } else {
+ print "No <tool> given for --tool=<tool>\n";
+ usage(1);
+ }
+}
+if (defined($extcmd)) {
+ if (length($extcmd) > 0) {
+ $ENV{GIT_DIFFTOOL_EXTCMD} = $extcmd;
+ } else {
+ print "No <cmd> given for --extcmd=<cmd>\n";
+ usage(1);
+ }
+}
+if ($gui) {
+ my $guitool = '';
+ $guitool = Git::config('diff.guitool');
+ if (length($guitool) > 0) {
+ $ENV{GIT_DIFF_TOOL} = $guitool;
+ }
+}
+
+# In directory diff mode, 'git-difftool--helper' is called once
+# to compare the a/b directories. In file diff mode, 'git diff'
+# will invoke a separate instance of 'git-difftool--helper' for
+# each file that changed.
+if (defined($dirdiff)) {
+ my ($a, $b) = setup_dir_diff();
+ if (defined($extcmd)) {
+ $rc = system($extcmd, $a, $b);
+ } else {
+ $ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';
+ $rc = system('git', 'difftool--helper', $a, $b);
+ }
+
+ exit($rc | ($rc >> 8)) if ($rc != 0);
+
+ # If the diff including working copy files and those
+ # files were modified during the diff, then the changes
+ # should be copied back to the working tree
+ for my $file (@working_tree) {
+ copy("$b/$file", "$workdir/$file") or die $!;
+ chmod(stat("$b/$file")->mode, "$workdir/$file") or die $!;
+ }
+} else {
+ if (defined($prompt)) {
+ if ($prompt) {
+ $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
+ } else {
+ $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+ }
+ }
+
+ $ENV{GIT_PAGER} = '';
+ $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
+
+ # ActiveState Perl for Win32 does not implement POSIX semantics of
+ # exec* system call. It just spawns the given executable and finishes
+ # the starting program, exiting with code 0.
+ # system will at least catch the errors returned by git diff,
+ # allowing the caller of git difftool better handling of failures.
+ my $rc = system('git', 'diff', @ARGV);
+ exit($rc | ($rc >> 8));
+}
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 6b5f225..add2c02 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -12,7 +12,7 @@
functions=$(cat << \EOF
warn () {
- echo "$*" >&2
+ echo "$*" >&2
}
map()
@@ -98,19 +98,17 @@ set_ident () {
}
USAGE="[--env-filter <command>] [--tree-filter <command>]
- [--index-filter <command>] [--parent-filter <command>]
- [--msg-filter <command>] [--commit-filter <command>]
- [--tag-name-filter <command>] [--subdirectory-filter <directory>]
- [--original <namespace>] [-d <directory>] [-f | --force]
- [<rev-list options>...]"
+ [--index-filter <command>] [--parent-filter <command>]
+ [--msg-filter <command>] [--commit-filter <command>]
+ [--tag-name-filter <command>] [--subdirectory-filter <directory>]
+ [--original <namespace>] [-d <directory>] [-f | --force]
+ [<rev-list options>...]"
OPTIONS_SPEC=
. git-sh-setup
if [ "$(is_bare_repository)" = false ]; then
- git diff-files --ignore-submodules --quiet &&
- git diff-index --cached --quiet HEAD -- ||
- die "Cannot rewrite branch(es) with a dirty working directory."
+ require_clean_work_tree 'rewrite branches'
fi
tempdir=.git-rewrite
diff --git a/git-gui/.gitattributes b/git-gui/.gitattributes
index f96112d..33d07c0 100644
--- a/git-gui/.gitattributes
+++ b/git-gui/.gitattributes
@@ -1,3 +1,4 @@
+* whitespace=indent-with-non-tab,trailing-space,space-before-tab,tabwidth=4
* encoding=US-ASCII
git-gui.sh encoding=UTF-8
/po/*.po encoding=UTF-8
diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN
index 1fb4d9b..6570943 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.13.GITGUI
+DEF_VER=0.16.GITGUI
LF='
'
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index fd6a43d..ba4e5c1 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -299,7 +299,9 @@ proc is_config_true {name} {
global repo_config
if {[catch {set v $repo_config($name)}]} {
return 0
- } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
+ }
+ set v [string tolower $v]
+ if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} {
return 1
} else {
return 0
@@ -310,7 +312,9 @@ proc is_config_false {name} {
global repo_config
if {[catch {set v $repo_config($name)}]} {
return 0
- } elseif {$v eq {false} || $v eq {0} || $v eq {no}} {
+ }
+ set v [string tolower $v]
+ if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} {
return 1
} else {
return 0
@@ -460,6 +464,35 @@ proc _which {what args} {
return {}
}
+# Test a file for a hashbang to identify executable scripts on Windows.
+proc is_shellscript {filename} {
+ if {![file exists $filename]} {return 0}
+ set f [open $filename r]
+ fconfigure $f -encoding binary
+ set magic [read $f 2]
+ close $f
+ return [expr {$magic eq "#!"}]
+}
+
+# Run a command connected via pipes on stdout.
+# This is for use with textconv filters and uses sh -c "..." to allow it to
+# contain a command with arguments. On windows we must check for shell
+# scripts specifically otherwise just call the filter command.
+proc open_cmd_pipe {cmd path} {
+ global env
+ if {![file executable [shellpath]]} {
+ set exe [auto_execok [lindex $cmd 0]]
+ if {[is_shellscript [lindex $exe 0]]} {
+ set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
+ } else {
+ set run [concat $exe [lrange $cmd 1 end] $path]
+ }
+ } else {
+ set run [list [shellpath] -c "$cmd \"\$0\"" $path]
+ }
+ return [open |$run r]
+}
+
proc _lappend_nice {cmd_var} {
global _nice
upvar $cmd_var cmd
@@ -725,7 +758,10 @@ if {[is_Windows]} {
gitlogo put gray26 -to 5 15 11 16
gitlogo redither
- wm iconphoto . -default gitlogo
+ image create photo gitlogo32 -width 32 -height 32
+ gitlogo32 copy gitlogo -zoom 2 2
+
+ wm iconphoto . -default gitlogo gitlogo32
}
}
@@ -846,6 +882,7 @@ set default_config(gui.fastcopyblame) false
set default_config(gui.copyblamethreshold) 40
set default_config(gui.blamehistoryctx) 7
set default_config(gui.diffcontext) 5
+set default_config(gui.diffopts) {}
set default_config(gui.commitmsgwidth) 75
set default_config(gui.newbranchtemplate) {}
set default_config(gui.spellingdictionary) {}
@@ -854,10 +891,12 @@ set default_config(gui.fontdiff) [font configure font_diff]
# TODO: this option should be added to the git-config documentation
set default_config(gui.maxfilesdisplayed) 5000
set default_config(gui.usettk) 1
+set default_config(gui.warndetachedcommit) 1
set font_descs {
{fontui font_ui {mc "Main Font"}}
{fontdiff font_diff {mc "Diff/Console Font"}}
}
+set default_config(gui.stageuntracked) ask
######################################################################
##
@@ -1059,6 +1098,10 @@ git-version proc _parse_config {arr_name args} {
} else {
set arr($name) $value
}
+ } elseif {[regexp {^([^\n]+)$} $line line name]} {
+ # no value given, but interpreting them as
+ # boolean will be handled as true
+ set arr($name) {}
}
}
}
@@ -1074,6 +1117,10 @@ git-version proc _parse_config {arr_name args} {
} else {
set arr($name) $value
}
+ } elseif {[regexp {^([^=]+)$} $line line name]} {
+ # no value given, but interpreting them as
+ # boolean will be handled as true
+ set arr($name) {}
}
}
close $fd_rc
@@ -1526,7 +1573,7 @@ proc run_prepare_commit_msg_hook {} {
# prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui
# it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
- # empty file but existant file.
+ # empty file but existent file.
set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
@@ -2473,6 +2520,7 @@ proc toggle_or_diff {w x y} {
[concat $after [list ui_ready]]
}
} else {
+ set selected_paths($path) 1
show_diff $path $w $lno
}
}
@@ -3361,6 +3409,7 @@ foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 gr
$ui_diff tag configure clri3$n -background $c
}
$ui_diff tag configure clr1 -font font_diffbold
+$ui_diff tag configure clr4 -underline 1
$ui_diff tag conf d_info -foreground blue -font font_diffbold
@@ -3877,7 +3926,7 @@ after 1 {
$ui_comm configure -state disabled -background gray
}
}
-if {[is_enabled multicommit]} {
+if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} {
after 1000 hint_gc
}
if {[is_enabled retcode]} {
diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
index 61e358f..324f774 100644
--- a/git-gui/lib/blame.tcl
+++ b/git-gui/lib/blame.tcl
@@ -22,6 +22,7 @@ field w_asim ; # text column: annotations (simple computation)
field w_file ; # text column: actual file data
field w_cviewer ; # pane showing commit message
field finder ; # find mini-dialog frame
+field gotoline ; # line goto mini-dialog frame
field status ; # status mega-widget instance
field old_height ; # last known height of $w.file_pane
@@ -218,7 +219,8 @@ constructor new {i_commit i_path i_jump} {
eval grid $w_columns $w.file_pane.out.sby -sticky nsew
grid conf \
$w.file_pane.out.sbx \
- -column [expr {[llength $w_columns] - 1}] \
+ -column 0 \
+ -columnspan [expr {[llength $w_columns] + 1}] \
-sticky we
grid columnconfigure \
$w.file_pane.out \
@@ -228,7 +230,14 @@ constructor new {i_commit i_path i_jump} {
set finder [::searchbar::new \
$w.file_pane.out.ff $w_file \
- -column [expr {[llength $w_columns] - 1}] \
+ -column 0 \
+ -columnspan [expr {[llength $w_columns] + 1}] \
+ ]
+
+ set gotoline [::linebar::new \
+ $w.file_pane.out.lf $w_file \
+ -column 0 \
+ -columnspan [expr {[llength $w_columns] + 1}] \
]
set w_cviewer $w.file_pane.cm.t
@@ -274,7 +283,11 @@ constructor new {i_commit i_path i_jump} {
$w.ctxm add command \
-label [mc "Find Text..."] \
-accelerator F7 \
- -command [list searchbar::show $finder]
+ -command [cb _show_finder]
+ $w.ctxm add command \
+ -label [mc "Goto Line..."] \
+ -accelerator "Ctrl-G" \
+ -command [cb _show_linebar]
menu $w.ctxm.enc
build_encoding_menu $w.ctxm.enc [cb _setencoding]
$w.ctxm add cascade \
@@ -341,10 +354,13 @@ constructor new {i_commit i_path i_jump} {
bind $w_cviewer <Tab> "[list focus $w_file];break"
bind $w_cviewer <Button-1> [list focus $w_cviewer]
bind $w_file <Visibility> [cb _focus_search $w_file]
- bind $top <F7> [list searchbar::show $finder]
+ bind $top <F7> [cb _show_finder]
+ bind $top <Key-slash> [cb _show_finder]
+ bind $top <Control-Key-s> [cb _show_finder]
bind $top <Escape> [list searchbar::hide $finder]
bind $top <F3> [list searchbar::find_next $finder]
bind $top <Shift-F3> [list searchbar::find_prev $finder]
+ bind $top <Control-Key-g> [cb _show_linebar]
catch { bind $top <Shift-Key-XF86_Switch_VT_3> [list searchbar::find_prev $finder] }
grid configure $w.header -sticky ew
@@ -460,14 +476,7 @@ method _load {jump} {
}
if {$commit eq {}} {
if {$do_textconv ne 0} {
- # Run textconv with sh -c "..." to allow it to
- # contain command + arguments. On windows, just
- # call the filter command.
- if {![file executable [shellpath]]} {
- set fd [open |[linsert $textconv end $path] r]
- } else {
- set fd [open |[list [shellpath] -c "$textconv \"\$0\"" $path] r]
- }
+ set fd [open_cmd_pipe $textconv $path]
} else {
set fd [open $path r]
}
@@ -559,7 +568,11 @@ method _read_file {fd jump} {
foreach i $w_columns {$i conf -state disabled}
if {[eof $fd]} {
- close $fd
+ fconfigure $fd -blocking 1; # enable error reporting on close
+ if {[catch {close $fd} err]} {
+ tk_messageBox -icon error -title [mc Error] \
+ -message $err
+ }
# If we don't force Tk to update the widgets *right now*
# none of our jump commands will cause a change in the UI.
@@ -1049,7 +1062,7 @@ method _gitkcommit {} {
set radius [get_config gui.blamehistoryctx]
set cmdline [list --select-commit=$cmit]
- if {$radius > 0} {
+ if {$radius > 0} {
set author_time {}
set committer_time {}
@@ -1157,7 +1170,7 @@ method _read_diff_load_commit {fd cparent new_path tline} {
}
if {[eof $fd]} {
- close $fd;
+ close $fd
set current_fd {}
_load_new_commit $this \
@@ -1188,6 +1201,7 @@ method _open_tooltip {cur_w} {
_hide_tooltip $this
set tooltip_wm [toplevel $cur_w.tooltip -borderwidth 1]
+ catch {wm attributes $tooltip_wm -type tooltip}
wm overrideredirect $tooltip_wm 1
wm transient $tooltip_wm [winfo toplevel $cur_w]
set tooltip_t $tooltip_wm.label
@@ -1298,9 +1312,9 @@ method _position_tooltip {} {
set pos_y [expr {[winfo pointery .] + 10}]
set g "${req_w}x${req_h}"
- if {$pos_x >= 0} {append g +}
+ if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +}
append g $pos_x
- if {$pos_y >= 0} {append g +}
+ if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +}
append g $pos_y
wm geometry $tooltip_wm $g
@@ -1336,4 +1350,14 @@ method _resize {new_height} {
set old_height $new_height
}
+method _show_finder {} {
+ linebar::hide $gotoline
+ searchbar::show $finder
+}
+
+method _show_linebar {} {
+ searchbar::hide $finder
+ linebar::show $gotoline
+}
+
}
diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl
index a8c6223..0328338 100644
--- a/git-gui/lib/browser.tcl
+++ b/git-gui/lib/browser.tcl
@@ -26,8 +26,14 @@ constructor new {commit {path {}}} {
wm withdraw $top
wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]]
+ if {$path ne {}} {
+ if {[string index $path end] ne {/}} {
+ append path /
+ }
+ }
+
set browser_commit $commit
- set browser_path $browser_commit:$path
+ set browser_path "$browser_commit:[escape_path $path]"
${NS}::label $w.path \
-textvariable @browser_path \
diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl
index c12d5e1..6dae793 100644
--- a/git-gui/lib/choose_rev.tcl
+++ b/git-gui/lib/choose_rev.tcl
@@ -497,6 +497,7 @@ method _open_tooltip {} {
if {$tooltip_wm eq {}} {
set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1]
+ catch {wm attributes $tooltip_wm -type tooltip}
wm overrideredirect $tooltip_wm 1
wm transient $tooltip_wm [winfo toplevel $w_list]
set tooltip_t $tooltip_wm.label
@@ -610,9 +611,9 @@ method _position_tooltip {} {
set pos_y [expr {[winfo pointery .] + 10}]
set g "${req_w}x${req_h}"
- if {$pos_x >= 0} {append g +}
+ if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +}
append g $pos_x
- if {$pos_y >= 0} {append g +}
+ if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +}
append g $pos_y
wm geometry $tooltip_wm $g
diff --git a/git-gui/lib/class.tcl b/git-gui/lib/class.tcl
index c27b714..f08506f 100644
--- a/git-gui/lib/class.tcl
+++ b/git-gui/lib/class.tcl
@@ -138,6 +138,7 @@ proc make_dialog {t w args} {
upvar $t top $w pfx this this
global use_ttk
uplevel [linsert $args 0 make_toplevel $t $w]
+ catch {wm attributes $top -type dialog}
pave_toplevel $pfx
}
diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl
index 5ce4687..0d81432 100644
--- a/git-gui/lib/commit.tcl
+++ b/git-gui/lib/commit.tcl
@@ -260,8 +260,25 @@ proc commit_prehook_wait {fd_ph curHEAD msg_p} {
}
proc commit_commitmsg {curHEAD msg_p} {
+ global is_detached repo_config
global pch_error
+ if {$is_detached
+ && ![file exists [gitdir rebase-merge head-name]]
+ && [is_config_true gui.warndetachedcommit]} {
+ set msg [mc "You are about to commit on a detached head.\
+This is a potentially dangerous thing to do because if you switch\
+to another branch you will loose your changes and it can be difficult\
+to retrieve them later from the reflog. You should probably cancel this\
+commit and create a new branch to continue.\n\
+\n\
+Do you really want to proceed with your Commit?"]
+ if {[ask_popup $msg] ne yes} {
+ unlock_index
+ return
+ }
+ }
+
# -- Run the commit-msg hook.
#
set fd_ph [githook_read commit-msg $msg_p]
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
index cf8a95e..ec44055 100644
--- a/git-gui/lib/diff.tcl
+++ b/git-gui/lib/diff.tcl
@@ -309,6 +309,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
lappend cmd -p
lappend cmd --color
+ set cmd [concat $cmd $repo_config(gui.diffopts)]
if {$repo_config(gui.diffcontext) >= 1} {
lappend cmd "-U$repo_config(gui.diffcontext)"
}
@@ -502,9 +503,9 @@ proc read_diff {fd conflict_size cont_info} {
foreach {posbegin colbegin posend colend} $markup {
set prefix clr
- foreach style [split $colbegin ";"] {
+ foreach style [lsort -integer [split $colbegin ";"]] {
if {$style eq "7"} {append prefix i; continue}
- if {$style < 30 || $style > 47} {continue}
+ if {$style != 4 && ($style < 30 || $style > 47)} {continue}
set a "$mark linestart + $posbegin chars"
set b "$mark linestart + $posend chars"
catch {$ui_diff tag add $prefix$style $a $b}
diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
index 5d7bbf2..8efbbdd 100644
--- a/git-gui/lib/index.tcl
+++ b/git-gui/lib/index.tcl
@@ -356,12 +356,33 @@ proc do_add_all {} {
global file_states
set paths [list]
+ set untracked_paths [list]
foreach path [array names file_states] {
switch -glob -- [lindex $file_states($path) 0] {
U? {continue}
?M -
?T -
?D {lappend paths $path}
+ ?O {lappend untracked_paths $path}
+ }
+ }
+ if {[llength $untracked_paths]} {
+ set reply 0
+ switch -- [get_config gui.stageuntracked] {
+ no {
+ set reply 0
+ }
+ yes {
+ set reply 1
+ }
+ ask -
+ default {
+ set reply [ask_popup [mc "Stage %d untracked files?" \
+ [llength $untracked_paths]]]
+ }
+ }
+ if {$reply} {
+ set paths [concat $paths $untracked_paths]
}
}
add_helper {Adding all changed files} $paths
diff --git a/git-gui/lib/line.tcl b/git-gui/lib/line.tcl
new file mode 100644
index 0000000..a026de9
--- /dev/null
+++ b/git-gui/lib/line.tcl
@@ -0,0 +1,81 @@
+# goto line number
+# based on code from gitk, Copyright (C) Paul Mackerras
+
+class linebar {
+
+field w
+field ctext
+
+field linenum {}
+
+constructor new {i_w i_text args} {
+ global use_ttk NS
+ set w $i_w
+ set ctext $i_text
+
+ ${NS}::frame $w
+ ${NS}::label $w.l -text [mc "Goto Line:"]
+ tentry $w.ent \
+ -textvariable ${__this}::linenum \
+ -background lightgreen \
+ -validate key \
+ -validatecommand [cb _validate %P]
+ ${NS}::button $w.bn -text [mc Go] -command [cb _goto]
+
+ pack $w.l -side left
+ pack $w.bn -side right
+ pack $w.ent -side left -expand 1 -fill x
+
+ eval grid conf $w -sticky we $args
+ grid remove $w
+
+ trace add variable linenum write [cb _goto_cb]
+ bind $w.ent <Return> [cb _goto]
+ bind $w.ent <Escape> [cb hide]
+
+ bind $w <Destroy> [list delete_this $this]
+ return $this
+}
+
+method show {} {
+ if {![visible $this]} {
+ grid $w
+ }
+ focus -force $w.ent
+}
+
+method hide {} {
+ if {[visible $this]} {
+ $w.ent delete 0 end
+ focus $ctext
+ grid remove $w
+ }
+}
+
+method visible {} {
+ return [winfo ismapped $w]
+}
+
+method editor {} {
+ return $w.ent
+}
+
+method _validate {P} {
+ # only accept numbers as input
+ string is integer $P
+}
+
+method _goto_cb {name ix op} {
+ after idle [cb _goto 1]
+}
+
+method _goto {{nohide {0}}} {
+ if {$linenum ne {}} {
+ $ctext see $linenum.0
+ if {!$nohide} {
+ hide $this
+ }
+ }
+}
+
+}
diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl
index 3807c8d..0cf1da1 100644
--- a/git-gui/lib/option.tcl
+++ b/git-gui/lib/option.tcl
@@ -153,9 +153,12 @@ proc do_options {} {
{i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}}
{i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}}
{i-1..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
+ {t gui.diffopts {mc "Additional Diff Parameters"}}
{i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}}
{t gui.newbranchtemplate {mc "New Branch Name Template"}}
{c gui.encoding {mc "Default File Contents Encoding"}}
+ {b gui.warndetachedcommit {mc "Warn before committing to a detached head"}}
+ {s gui.stageuntracked {mc "Staging of untracked files"} {list "yes" "no" "ask"}}
} {
set type [lindex $option 0]
set name [lindex $option 1]
@@ -208,6 +211,23 @@ proc do_options {} {
}
pack $w.$f.$optid -side top -anchor w -fill x
}
+ s {
+ set opts [eval [lindex $option 3]]
+ ${NS}::frame $w.$f.$optid
+ ${NS}::label $w.$f.$optid.l -text "$text:"
+ if {$use_ttk} {
+ ttk::combobox $w.$f.$optid.v \
+ -textvariable ${f}_config_new($name) \
+ -values $opts -state readonly
+ } else {
+ eval tk_optionMenu $w.$f.$optid.v \
+ ${f}_config_new($name) \
+ $opts
+ }
+ 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
+ }
}
}
}
diff --git a/git-gui/lib/search.tcl b/git-gui/lib/search.tcl
index 7fdbf87..ef1e555 100644
--- a/git-gui/lib/search.tcl
+++ b/git-gui/lib/search.tcl
@@ -7,9 +7,16 @@ field w
field ctext
field searchstring {}
-field casesensitive 1
+field regexpsearch
+field default_regexpsearch
+field casesensitive
+field default_casesensitive
+field smartcase
field searchdirn -forwards
+field history
+field history_index
+
field smarktop
field smarkbot
@@ -18,15 +25,37 @@ constructor new {i_w i_text args} {
set w $i_w
set ctext $i_text
+ set default_regexpsearch [is_config_true gui.search.regexp]
+ switch -- [get_config gui.search.case] {
+ no {
+ set default_casesensitive 0
+ set smartcase 0
+ }
+ smart {
+ set default_casesensitive 0
+ set smartcase 1
+ }
+ yes -
+ default {
+ set default_casesensitive 1
+ set smartcase 0
+ }
+ }
+
+ set history [list]
+
${NS}::frame $w
${NS}::label $w.l -text [mc Find:]
- entry $w.ent -textvariable ${__this}::searchstring -background lightgreen
+ tentry $w.ent -textvariable ${__this}::searchstring -background lightgreen
${NS}::button $w.bn -text [mc Next] -command [cb find_next]
${NS}::button $w.bp -text [mc Prev] -command [cb find_prev]
- ${NS}::checkbutton $w.cs -text [mc Case-Sensitive] \
+ ${NS}::checkbutton $w.re -text [mc RegExp] \
+ -variable ${__this}::regexpsearch -command [cb _incrsearch]
+ ${NS}::checkbutton $w.cs -text [mc Case] \
-variable ${__this}::casesensitive -command [cb _incrsearch]
pack $w.l -side left
pack $w.cs -side right
+ pack $w.re -side right
pack $w.bp -side right
pack $w.bn -side right
pack $w.ent -side left -expand 1 -fill x
@@ -35,6 +64,10 @@ constructor new {i_w i_text args} {
grid remove $w
trace add variable searchstring write [cb _incrsearch_cb]
+ bind $w.ent <Return> [cb find_next]
+ bind $w.ent <Shift-Return> [cb find_prev]
+ bind $w.ent <Key-Up> [cb _prev_search]
+ bind $w.ent <Key-Down> [cb _next_search]
bind $w <Destroy> [list delete_this $this]
return $this
@@ -43,6 +76,10 @@ constructor new {i_w i_text args} {
method show {} {
if {![visible $this]} {
grid $w
+ $w.ent delete 0 end
+ set regexpsearch $default_regexpsearch
+ set casesensitive $default_casesensitive
+ set history_index [llength $history]
}
focus -force $w.ent
}
@@ -51,6 +88,7 @@ method hide {} {
if {[visible $this]} {
focus $ctext
grid remove $w
+ _save_search $this
}
}
@@ -96,6 +134,9 @@ method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} {
upvar $mlenvar mlen
lappend cmd -count mlen
}
+ if {$regexpsearch} {
+ lappend cmd -regexp
+ }
if {!$casesensitive} {
lappend cmd -nocase
}
@@ -103,14 +144,16 @@ method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} {
set dir $searchdirn
}
lappend cmd $dir -- $searchstring
- if {$endbound ne {}} {
- set here [eval $cmd [list $start] [list $endbound]]
- } else {
- set here [eval $cmd [list $start]]
- if {$here eq {}} {
- set here [eval $cmd [_get_wrap_anchor $this $dir]]
+ if {[catch {
+ if {$endbound ne {}} {
+ set here [eval $cmd [list $start] [list $endbound]]
+ } else {
+ set here [eval $cmd [list $start]]
+ if {$here eq {}} {
+ set here [eval $cmd [_get_wrap_anchor $this $dir]]
+ }
}
- }
+ } err]} { set here {} }
return $here
}
@@ -124,19 +167,76 @@ method _incrsearch {} {
$ctext mark set anchor [_get_new_anchor $this]
}
if {$searchstring ne {}} {
+ if {$smartcase && [regexp {[[:upper:]]} $searchstring]} {
+ set casesensitive 1
+ }
set here [_do_search $this anchor mlen]
if {$here ne {}} {
$ctext see $here
$ctext tag remove sel 1.0 end
$ctext tag add sel $here "$here + $mlen c"
- $w.ent configure -background lightgreen
+ #$w.ent configure -background lightgreen
+ $w.ent state !pressed
_set_marks $this 1
} else {
- $w.ent configure -background lightpink
+ #$w.ent configure -background lightpink
+ $w.ent state pressed
}
+ } elseif {$smartcase} {
+ # clearing the field resets the smart case detection
+ set casesensitive 0
+ }
+}
+
+method _save_search {} {
+ if {$searchstring eq {}} {
+ return
+ }
+ if {[llength $history] > 0} {
+ foreach {s_regexp s_case s_expr} [lindex $history end] break
+ } else {
+ set s_regexp $regexpsearch
+ set s_case $casesensitive
+ set s_expr ""
+ }
+ if {$searchstring eq $s_expr} {
+ # update modes
+ set history [lreplace $history end end \
+ [list $regexpsearch $casesensitive $searchstring]]
+ } else {
+ lappend history [list $regexpsearch $casesensitive $searchstring]
+ }
+ set history_index [llength $history]
+}
+
+method _prev_search {} {
+ if {$history_index > 0} {
+ incr history_index -1
+ foreach {s_regexp s_case s_expr} [lindex $history $history_index] break
+ $w.ent delete 0 end
+ $w.ent insert 0 $s_expr
+ set regexpsearch $s_regexp
+ set casesensitive $s_case
}
}
+method _next_search {} {
+ if {$history_index < [llength $history]} {
+ incr history_index
+ }
+ if {$history_index < [llength $history]} {
+ foreach {s_regexp s_case s_expr} [lindex $history $history_index] break
+ } else {
+ set s_regexp $default_regexpsearch
+ set s_case $default_casesensitive
+ set s_expr ""
+ }
+ $w.ent delete 0 end
+ $w.ent insert 0 $s_expr
+ set regexpsearch $s_regexp
+ set casesensitive $s_case
+}
+
method find_prev {} {
find_next $this -backwards
}
@@ -147,6 +247,7 @@ method find_next {{dir -forwards}} {
set searchdirn $dir
$ctext mark unset anchor
if {$searchstring ne {}} {
+ _save_search $this
set start [_get_new_anchor $this]
if {$dir eq "-forwards"} {
set start "$start + 1c"
@@ -196,4 +297,4 @@ method scrolled {} {
}
}
-} \ No newline at end of file
+}
diff --git a/git-gui/lib/sshkey.tcl b/git-gui/lib/sshkey.tcl
index 5f75bc9..aa6457b 100644
--- a/git-gui/lib/sshkey.tcl
+++ b/git-gui/lib/sshkey.tcl
@@ -117,7 +117,7 @@ proc read_sshkey_output {fd w} {
} else {
set finfo [find_ssh_key]
if {$finfo eq {}} {
- set sshkey_title [mc "Generation succeded, but no keys found."]
+ set sshkey_title [mc "Generation succeeded, but no keys found."]
$w.contents insert end $sshkey_output
} else {
set sshkey_title [mc "Your key is in: %s" [lindex $finfo 0]]
diff --git a/git-gui/lib/themed.tcl b/git-gui/lib/themed.tcl
index 1da4586..8b88d36 100644
--- a/git-gui/lib/themed.tcl
+++ b/git-gui/lib/themed.tcl
@@ -23,10 +23,59 @@ proc InitTheme {} {
ttk::style configure Gold.TFrame -background gold -relief flat
# listboxes should have a theme border so embed in ttk::frame
ttk::style layout SListbox.TFrame {
- SListbox.Frame.Entry.field -sticky news -border true -children {
- SListbox.Frame.padding -sticky news
- }
- }
+ SListbox.Frame.Entry.field -sticky news -border true -children {
+ SListbox.Frame.padding -sticky news
+ }
+ }
+
+ # Handle either current Tk or older versions of 8.5
+ if {[catch {set theme [ttk::style theme use]}]} {
+ set theme $::ttk::currentTheme
+ }
+
+ if {[lsearch -exact {default alt classic clam} $theme] != -1} {
+ # Simple override of standard ttk::entry to change the field
+ # packground according to a state flag. We should use 'user1'
+ # but not all versions of 8.5 support that so make use of 'pressed'
+ # which is not normally in use for entry widgets.
+ ttk::style layout Edged.Entry [ttk::style layout TEntry]
+ ttk::style map Edged.Entry {*}[ttk::style map TEntry]
+ ttk::style configure Edged.Entry {*}[ttk::style configure TEntry] \
+ -fieldbackground lightgreen
+ ttk::style map Edged.Entry -fieldbackground {
+ {pressed !disabled} lightpink
+ }
+ } else {
+ # For fancier themes, in particular the Windows ones, the field
+ # element may not support changing the background color. So instead
+ # override the fill using the default fill element. If we overrode
+ # the vista theme field element we would loose the themed border
+ # of the widget.
+ catch {
+ ttk::style element create color.fill from default
+ }
+
+ ttk::style layout Edged.Entry {
+ Edged.Entry.field -sticky nswe -border 0 -children {
+ Edged.Entry.border -sticky nswe -border 1 -children {
+ Edged.Entry.padding -sticky nswe -children {
+ Edged.Entry.color.fill -sticky nswe -children {
+ Edged.Entry.textarea -sticky nswe
+ }
+ }
+ }
+ }
+ }
+
+ ttk::style configure Edged.Entry {*}[ttk::style configure TEntry] \
+ -background lightgreen -padding 0 -borderwidth 0
+ ttk::style map Edged.Entry {*}[ttk::style map TEntry] \
+ -background {{pressed !disabled} lightpink}
+ }
+
+ if {[lsearch [bind . <<ThemeChanged>>] InitTheme] == -1} {
+ bind . <<ThemeChanged>> +[namespace code [list InitTheme]]
+ }
}
proc gold_frame {w args} {
@@ -74,6 +123,7 @@ proc paddedlabel {w args} {
# place a themed frame over the surface.
proc Dialog {w args} {
eval [linsert $args 0 toplevel $w -class Dialog]
+ catch {wm attributes $w -type dialog}
pave_toplevel $w
return $w
}
@@ -143,6 +193,47 @@ proc tspinbox {w args} {
}
}
+proc tentry {w args} {
+ global use_ttk
+ if {$use_ttk} {
+ InitTheme
+ ttk::entry $w -style Edged.Entry
+ } else {
+ entry $w
+ }
+
+ rename $w _$w
+ interp alias {} $w {} tentry_widgetproc $w
+ eval [linsert $args 0 tentry_widgetproc $w configure]
+ return $w
+}
+proc tentry_widgetproc {w cmd args} {
+ global use_ttk
+ switch -- $cmd {
+ state {
+ if {$use_ttk} {
+ return [uplevel 1 [list _$w $cmd] $args]
+ } else {
+ if {[lsearch -exact $args pressed] != -1} {
+ _$w configure -background lightpink
+ } else {
+ _$w configure -background lightgreen
+ }
+ }
+ }
+ configure {
+ if {$use_ttk} {
+ if {[set n [lsearch -exact $args -background]] != -1} {
+ set args [lreplace $args $n [incr n]]
+ if {[llength $args] == 0} {return}
+ }
+ }
+ return [uplevel 1 [list _$w $cmd] $args]
+ }
+ default { return [uplevel 1 [list _$w $cmd] $args] }
+ }
+}
+
# Tk 8.6 provides a standard font selection dialog. This uses the native
# dialogs on Windows and MacOSX or a standard Tk dialog on X11.
proc tchoosefont {w title familyvar sizevar} {
diff --git a/git-gui/lib/tools.tcl b/git-gui/lib/tools.tcl
index 95e6e55..6ec9411 100644
--- a/git-gui/lib/tools.tcl
+++ b/git-gui/lib/tools.tcl
@@ -87,8 +87,14 @@ proc tools_exec {fullname} {
return
}
} elseif {[is_config_true "guitool.$fullname.confirm"]} {
- if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} {
- return
+ if {[is_config_true "guitool.$fullname.needsfile"]} {
+ if {[ask_popup [mc "Are you sure you want to run %1\$s on file \"%2\$s\"?" $fullname $current_diff_path]] ne {yes}} {
+ return
+ }
+ } else {
+ if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} {
+ return
+ }
}
}
diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl
index 7fad9b7..e5d211e 100644
--- a/git-gui/lib/transport.tcl
+++ b/git-gui/lib/transport.tcl
@@ -124,6 +124,7 @@ proc do_push_anywhere {} {
set w .push_setup
toplevel $w
+ catch {wm attributes $w -type dialog}
wm withdraw $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
pave_toplevel $w
diff --git a/git-gui/po/README b/git-gui/po/README
index 595bbf5..0f5837d 100644
--- a/git-gui/po/README
+++ b/git-gui/po/README
@@ -18,28 +18,23 @@ 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:
+You would then need to clone the git-gui project repository and create
+a feature branch to begin working:
- $ 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
+ $ git clone git://repo.or.cz/git-gui.git
+ $ cd git-gui.git
+ $ git checkout -b my-translation
-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".
+The "git checkout" command creates a new branch to keep your work
+isolated and to make it simple to post your patch series when
+completed. You will be working on this branch.
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?
+In the git-gui directory is a po/ subdirectory. It has a handful of
+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:
@@ -149,15 +144,18 @@ 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:
+When you are satisfied with your translation, commit your changes then submit
+your patch series to the maintainer and the Git mailing list:
$ 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
+ $ git commit -s -m 'git-gui: added Afrikaans translation.'
+ $ git send-email --to 'git@vger.kernel.org' \
+ --cc 'Pat Thoyts <patthoyts@users.sourceforge.net>' \
+ --subject 'git-gui: Afrikaans translation' \
+ master..
3. Updating your translation.
@@ -169,6 +167,7 @@ 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 checkout master
$ git pull
In the former case, you will edit po/af.po (again, replace "af" with
diff --git a/git-gui/po/sv.po b/git-gui/po/sv.po
index 8bd3c5d..24cc4e3 100644
--- a/git-gui/po/sv.po
+++ b/git-gui/po/sv.po
@@ -1714,7 +1714,7 @@ msgstr ""
#: lib/index.tcl:30
msgid "Continue"
-msgstr "Forstätt"
+msgstr "Fortsätt"
#: lib/index.tcl:33
msgid "Unlock Index"
diff --git a/git-instaweb.sh b/git-instaweb.sh
index 8bfa8a0..01a1b05 100755
--- a/git-instaweb.sh
+++ b/git-instaweb.sh
@@ -27,6 +27,7 @@ httpd="$(git config --get instaweb.httpd)"
root="$(git config --get instaweb.gitwebdir)"
port=$(git config --get instaweb.port)
module_path="$(git config --get instaweb.modulepath)"
+action="browse"
conf="$GIT_DIR/gitweb/httpd.conf"
@@ -98,12 +99,18 @@ start_httpd () {
# here $httpd should have a meaningful value
resolve_full_httpd
+ mkdir -p "$fqgitdir/gitweb/$httpd_only"
+ conf="$fqgitdir/gitweb/$httpd_only.conf"
+
+ # generate correct config file if it doesn't exist
+ test -f "$conf" || configure_httpd
+ test -f "$fqgitdir/gitweb/gitweb_config.perl" || gitweb_conf
# don't quote $full_httpd, there can be arguments to it (-f)
case "$httpd" in
*mongoose*|*plackup*)
#These servers don't have a daemon mode so we'll have to fork it
- $full_httpd "$fqgitdir/gitweb/httpd.conf" &
+ $full_httpd "$conf" &
#Save the pid before doing anything else (we'll print it later)
pid=$!
@@ -117,7 +124,7 @@ $pid
EOF
;;
*)
- $full_httpd "$fqgitdir/gitweb/httpd.conf"
+ $full_httpd "$conf"
if test $? != 0; then
echo "Could not execute http daemon $httpd."
exit 1
@@ -148,17 +155,13 @@ while test $# != 0
do
case "$1" in
--stop|stop)
- stop_httpd
- exit 0
+ action="stop"
;;
--start|start)
- start_httpd
- exit 0
+ action="start"
;;
--restart|restart)
- stop_httpd
- start_httpd
- exit 0
+ action="restart"
;;
-l|--local)
local=true
@@ -587,33 +590,54 @@ our \$projects_list = \$projectroot;
EOF
}
-gitweb_conf
-
-resolve_full_httpd
-mkdir -p "$fqgitdir/gitweb/$httpd_only"
+configure_httpd() {
+ case "$httpd" in
+ *lighttpd*)
+ lighttpd_conf
+ ;;
+ *apache2*|*httpd*)
+ apache2_conf
+ ;;
+ webrick)
+ webrick_conf
+ ;;
+ *mongoose*)
+ mongoose_conf
+ ;;
+ *plackup*)
+ plackup_conf
+ ;;
+ *)
+ echo "Unknown httpd specified: $httpd"
+ exit 1
+ ;;
+ esac
+}
-case "$httpd" in
-*lighttpd*)
- lighttpd_conf
- ;;
-*apache2*|*httpd*)
- apache2_conf
- ;;
-webrick)
- webrick_conf
+case "$action" in
+stop)
+ stop_httpd
+ exit 0
;;
-*mongoose*)
- mongoose_conf
+start)
+ start_httpd
+ exit 0
;;
-*plackup*)
- plackup_conf
- ;;
-*)
- echo "Unknown httpd specified: $httpd"
- exit 1
+restart)
+ stop_httpd
+ start_httpd
+ exit 0
;;
esac
+gitweb_conf
+
+resolve_full_httpd
+mkdir -p "$fqgitdir/gitweb/$httpd_only"
+conf="$fqgitdir/gitweb/$httpd_only.conf"
+
+configure_httpd
+
start_httpd
url=http://127.0.0.1:$port
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
index 7aeb969..f612cb8 100755
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
@@ -117,7 +117,7 @@ case "${1:-.}${2:-.}${3:-.}" in
# If we do not have enough common material, it is not
# worth trying two-file merge using common subsections.
- expr "$sz0" \< "$sz1" \* 2 >/dev/null || : >$orig
+ expr $sz0 \< $sz1 \* 2 >/dev/null || : >$orig
;;
*)
echo "Auto-merging $4"
diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh
index a79a2ec..ed630b2 100644
--- a/git-mergetool--lib.sh
+++ b/git-mergetool--lib.sh
@@ -9,33 +9,16 @@ merge_mode() {
}
translate_merge_tool_path () {
- case "$1" in
- araxis)
- echo compare
- ;;
- bc3)
- echo bcompare
- ;;
- emerge)
- echo emacs
- ;;
- gvimdiff|gvimdiff2)
- echo gvim
- ;;
- vimdiff|vimdiff2)
- echo vim
- ;;
- *)
- echo "$1"
- ;;
- esac
+ echo "$1"
}
check_unchanged () {
- if test "$MERGED" -nt "$BACKUP"; then
+ if test "$MERGED" -nt "$BACKUP"
+ then
status=0
else
- while true; do
+ while true
+ do
echo "$MERGED seems unchanged."
printf "Was the merge successful? [y/n] "
read answer || return 1
@@ -47,312 +30,98 @@ check_unchanged () {
fi
}
+valid_tool_config () {
+ if test -n "$(get_merge_tool_cmd "$1")"
+ then
+ return 0
+ else
+ return 1
+ fi
+}
+
valid_tool () {
+ setup_tool "$1" || valid_tool_config "$1"
+}
+
+setup_tool () {
case "$1" in
- araxis | bc3 | diffuse | ecmerge | emerge | gvimdiff | gvimdiff2 | \
- kdiff3 | meld | opendiff | p4merge | tkdiff | vimdiff | vimdiff2 | xxdiff)
- ;; # happy
- kompare)
- if ! diff_mode; then
- return 1
- fi
- ;;
- tortoisemerge)
- if ! merge_mode; then
- return 1
- fi
+ vim*|gvim*)
+ tool=vim
;;
*)
- if test -z "$(get_merge_tool_cmd "$1")"; then
- return 1
- fi
+ tool="$1"
;;
esac
+ mergetools="$(git --exec-path)/mergetools"
+
+ # Load the default definitions
+ . "$mergetools/defaults"
+ if ! test -f "$mergetools/$tool"
+ then
+ return 1
+ fi
+
+ # Load the redefined functions
+ . "$mergetools/$tool"
+
+ if merge_mode && ! can_merge
+ then
+ echo "error: '$tool' can not be used to resolve merges" >&2
+ exit 1
+ elif diff_mode && ! can_diff
+ then
+ echo "error: '$tool' can only be used to resolve merges" >&2
+ exit 1
+ fi
+ return 0
}
get_merge_tool_cmd () {
# Prints the custom command for a merge tool
- if test -n "$1"; then
- merge_tool="$1"
- else
- merge_tool="$(get_merge_tool)"
- fi
- if diff_mode; then
+ merge_tool="$1"
+ if diff_mode
+ then
echo "$(git config difftool.$merge_tool.cmd ||
- git config mergetool.$merge_tool.cmd)"
+ git config mergetool.$merge_tool.cmd)"
else
echo "$(git config mergetool.$merge_tool.cmd)"
fi
}
+# Entry point for running tools
run_merge_tool () {
+ # If GIT_PREFIX is empty then we cannot use it in tools
+ # that expect to be able to chdir() to its value.
+ GIT_PREFIX=${GIT_PREFIX:-.}
+ export GIT_PREFIX
+
merge_tool_path="$(get_merge_tool_path "$1")" || exit
base_present="$2"
status=0
- case "$1" in
- araxis)
- if merge_mode; then
- touch "$BACKUP"
- if $base_present; then
- "$merge_tool_path" -wait -merge -3 -a1 \
- "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \
- >/dev/null 2>&1
- else
- "$merge_tool_path" -wait -2 \
- "$LOCAL" "$REMOTE" "$MERGED" \
- >/dev/null 2>&1
- fi
- check_unchanged
- else
- "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \
- >/dev/null 2>&1
- fi
- ;;
- bc3)
- if merge_mode; then
- touch "$BACKUP"
- if $base_present; then
- "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \
- -mergeoutput="$MERGED"
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE" \
- -mergeoutput="$MERGED"
- fi
- check_unchanged
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE"
- fi
- ;;
- diffuse)
- if merge_mode; then
- touch "$BACKUP"
- if $base_present; then
- "$merge_tool_path" \
- "$LOCAL" "$MERGED" "$REMOTE" \
- "$BASE" | cat
- else
- "$merge_tool_path" \
- "$LOCAL" "$MERGED" "$REMOTE" | cat
- fi
- check_unchanged
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
- fi
- ;;
- ecmerge)
- if merge_mode; then
- touch "$BACKUP"
- if $base_present; then
- "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
- --default --mode=merge3 --to="$MERGED"
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE" \
- --default --mode=merge2 --to="$MERGED"
- fi
- check_unchanged
- else
- "$merge_tool_path" --default --mode=diff2 \
- "$LOCAL" "$REMOTE"
- fi
- ;;
- emerge)
- if merge_mode; then
- if $base_present; then
- "$merge_tool_path" \
- -f emerge-files-with-ancestor-command \
- "$LOCAL" "$REMOTE" "$BASE" \
- "$(basename "$MERGED")"
- else
- "$merge_tool_path" \
- -f emerge-files-command \
- "$LOCAL" "$REMOTE" \
- "$(basename "$MERGED")"
- fi
- status=$?
- else
- "$merge_tool_path" -f emerge-files-command \
- "$LOCAL" "$REMOTE"
- fi
- ;;
- gvimdiff|vimdiff)
- if merge_mode; then
- touch "$BACKUP"
- if $base_present; then
- "$merge_tool_path" -f -d -c "wincmd J" \
- "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
- else
- "$merge_tool_path" -f -d -c "wincmd l" \
- "$LOCAL" "$MERGED" "$REMOTE"
- fi
- check_unchanged
- else
- "$merge_tool_path" -R -f -d -c "wincmd l" \
- "$LOCAL" "$REMOTE"
- fi
- ;;
- gvimdiff2|vimdiff2)
- if merge_mode; then
- touch "$BACKUP"
- "$merge_tool_path" -f -d -c "wincmd l" \
- "$LOCAL" "$MERGED" "$REMOTE"
- check_unchanged
- else
- "$merge_tool_path" -R -f -d -c "wincmd l" \
- "$LOCAL" "$REMOTE"
- fi
- ;;
- kdiff3)
- if merge_mode; then
- if $base_present; then
- ("$merge_tool_path" --auto \
- --L1 "$MERGED (Base)" \
- --L2 "$MERGED (Local)" \
- --L3 "$MERGED (Remote)" \
- -o "$MERGED" \
- "$BASE" "$LOCAL" "$REMOTE" \
- > /dev/null 2>&1)
- else
- ("$merge_tool_path" --auto \
- --L1 "$MERGED (Local)" \
- --L2 "$MERGED (Remote)" \
- -o "$MERGED" \
- "$LOCAL" "$REMOTE" \
- > /dev/null 2>&1)
- fi
- status=$?
- else
- ("$merge_tool_path" --auto \
- --L1 "$MERGED (A)" \
- --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
- > /dev/null 2>&1)
- fi
- ;;
- kompare)
- "$merge_tool_path" "$LOCAL" "$REMOTE"
- ;;
- meld)
- if merge_mode; then
- touch "$BACKUP"
- "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
- check_unchanged
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE"
- fi
- ;;
- opendiff)
- if merge_mode; then
- touch "$BACKUP"
- if $base_present; then
- "$merge_tool_path" "$LOCAL" "$REMOTE" \
- -ancestor "$BASE" \
- -merge "$MERGED" | cat
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE" \
- -merge "$MERGED" | cat
- fi
- check_unchanged
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
- fi
- ;;
- p4merge)
- if merge_mode; then
- touch "$BACKUP"
- $base_present || >"$BASE"
- "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
- check_unchanged
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE"
- fi
- ;;
- tkdiff)
- if merge_mode; then
- if $base_present; then
- "$merge_tool_path" -a "$BASE" \
- -o "$MERGED" "$LOCAL" "$REMOTE"
- else
- "$merge_tool_path" \
- -o "$MERGED" "$LOCAL" "$REMOTE"
- fi
- status=$?
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE"
- fi
- ;;
- tortoisemerge)
- if $base_present; then
- touch "$BACKUP"
- "$merge_tool_path" \
- -base:"$BASE" -mine:"$LOCAL" \
- -theirs:"$REMOTE" -merged:"$MERGED"
- check_unchanged
- else
- echo "TortoiseMerge cannot be used without a base" 1>&2
- status=1
- fi
- ;;
- xxdiff)
- if merge_mode; then
- touch "$BACKUP"
- if $base_present; then
- "$merge_tool_path" -X --show-merged-pane \
- -R 'Accel.SaveAsMerged: "Ctrl-S"' \
- -R 'Accel.Search: "Ctrl+F"' \
- -R 'Accel.SearchForward: "Ctrl-G"' \
- --merged-file "$MERGED" \
- "$LOCAL" "$BASE" "$REMOTE"
- else
- "$merge_tool_path" -X $extra \
- -R 'Accel.SaveAsMerged: "Ctrl-S"' \
- -R 'Accel.Search: "Ctrl+F"' \
- -R 'Accel.SearchForward: "Ctrl-G"' \
- --merged-file "$MERGED" \
- "$LOCAL" "$REMOTE"
- fi
- check_unchanged
- else
- "$merge_tool_path" \
- -R 'Accel.Search: "Ctrl+F"' \
- -R 'Accel.SearchForward: "Ctrl-G"' \
- "$LOCAL" "$REMOTE"
- fi
- ;;
- *)
- merge_tool_cmd="$(get_merge_tool_cmd "$1")"
- if test -z "$merge_tool_cmd"; then
- if merge_mode; then
- status=1
- fi
- break
- fi
- if merge_mode; then
- trust_exit_code="$(git config --bool \
- mergetool."$1".trustExitCode || echo false)"
- if test "$trust_exit_code" = "false"; then
- touch "$BACKUP"
- ( eval $merge_tool_cmd )
- check_unchanged
- else
- ( eval $merge_tool_cmd )
- status=$?
- fi
- else
- ( eval $merge_tool_cmd )
- fi
- ;;
- esac
+ # Bring tool-specific functions into scope
+ setup_tool "$1"
+
+ if merge_mode
+ then
+ merge_cmd "$1"
+ else
+ diff_cmd "$1"
+ fi
return $status
}
guess_merge_tool () {
- if merge_mode; then
+ if merge_mode
+ then
tools="tortoisemerge"
else
tools="kompare"
fi
- if test -n "$DISPLAY"; then
- if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
+ if test -n "$DISPLAY"
+ then
+ if test -n "$GNOME_DESKTOP_SESSION_ID"
+ then
tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
else
tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
@@ -373,7 +142,8 @@ guess_merge_tool () {
for i in $tools
do
merge_tool_path="$(translate_merge_tool_path "$i")"
- if type "$merge_tool_path" > /dev/null 2>&1; then
+ if type "$merge_tool_path" >/dev/null 2>&1
+ then
echo "$i"
return 0
fi
@@ -386,12 +156,14 @@ guess_merge_tool () {
get_configured_merge_tool () {
# Diff mode first tries diff.tool and falls back to merge.tool.
# Merge mode only checks merge.tool
- if diff_mode; then
+ if diff_mode
+ then
merge_tool=$(git config diff.tool || git config merge.tool)
else
merge_tool=$(git config merge.tool)
fi
- if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
+ if test -n "$merge_tool" && ! valid_tool "$merge_tool"
+ then
echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
return 1
@@ -401,28 +173,28 @@ get_configured_merge_tool () {
get_merge_tool_path () {
# A merge tool has been set, so verify that it's valid.
- if test -n "$1"; then
- merge_tool="$1"
- else
- merge_tool="$(get_merge_tool)"
- fi
- if ! valid_tool "$merge_tool"; then
+ merge_tool="$1"
+ if ! valid_tool "$merge_tool"
+ then
echo >&2 "Unknown merge tool $merge_tool"
exit 1
fi
- if diff_mode; then
+ if diff_mode
+ then
merge_tool_path=$(git config difftool."$merge_tool".path ||
- git config mergetool."$merge_tool".path)
+ git config mergetool."$merge_tool".path)
else
merge_tool_path=$(git config mergetool."$merge_tool".path)
fi
- if test -z "$merge_tool_path"; then
+ if test -z "$merge_tool_path"
+ then
merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
fi
if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
- ! type "$merge_tool_path" > /dev/null 2>&1; then
+ ! type "$merge_tool_path" >/dev/null 2>&1
+ then
echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
- "'$merge_tool_path'"
+ "'$merge_tool_path'"
exit 1
fi
echo "$merge_tool_path"
@@ -430,9 +202,10 @@ get_merge_tool_path () {
get_merge_tool () {
# Check if a merge tool has been configured
- merge_tool=$(get_configured_merge_tool)
+ merge_tool="$(get_configured_merge_tool)"
# Try to guess an appropriate merge tool if no tool has been set.
- if test -z "$merge_tool"; then
+ if test -z "$merge_tool"
+ then
merge_tool="$(guess_merge_tool)" || exit
fi
echo "$merge_tool"
diff --git a/git-mergetool.sh b/git-mergetool.sh
index 085e213..a9f23f7 100755
--- a/git-mergetool.sh
+++ b/git-mergetool.sh
@@ -181,10 +181,14 @@ stage_submodule () {
}
checkout_staged_file () {
- tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ')
+ tmpfile=$(expr \
+ "$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \
+ : '\([^ ]*\) ')
if test $? -eq 0 -a -n "$tmpfile" ; then
mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
+ else
+ >"$3"
fi
}
@@ -224,9 +228,9 @@ merge_file () {
mv -- "$MERGED" "$BACKUP"
cp -- "$BACKUP" "$MERGED"
- base_present && checkout_staged_file 1 "$MERGED" "$BASE"
- local_present && checkout_staged_file 2 "$MERGED" "$LOCAL"
- remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
+ checkout_staged_file 1 "$MERGED" "$BASE"
+ checkout_staged_file 2 "$MERGED" "$LOCAL"
+ checkout_staged_file 3 "$MERGED" "$REMOTE"
if test -z "$local_mode" -o -z "$remote_mode"; then
echo "Deleted merge conflict for '$MERGED':"
diff --git a/contrib/fast-import/git-p4 b/git-p4.py
index 98d2aee..f895a24 100755
--- a/contrib/fast-import/git-p4
+++ b/git-p4.py
@@ -10,10 +10,12 @@
import optparse, sys, os, marshal, subprocess, shelve
import tempfile, getopt, os.path, time, platform
-import re
+import re, shutil
verbose = False
+# Only labels/tags matching this will be imported/exported
+defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
def p4_build_cmd(cmd):
"""Build a suitable p4 command line.
@@ -22,37 +24,41 @@ def p4_build_cmd(cmd):
location. It means that hooking into the environment, or other configuration
can be done more easily.
"""
- real_cmd = "%s " % "p4"
+ real_cmd = ["p4"]
user = gitConfig("git-p4.user")
if len(user) > 0:
- real_cmd += "-u %s " % user
+ real_cmd += ["-u",user]
password = gitConfig("git-p4.password")
if len(password) > 0:
- real_cmd += "-P %s " % password
+ real_cmd += ["-P", password]
port = gitConfig("git-p4.port")
if len(port) > 0:
- real_cmd += "-p %s " % port
+ real_cmd += ["-p", port]
host = gitConfig("git-p4.host")
if len(host) > 0:
- real_cmd += "-h %s " % host
+ real_cmd += ["-H", host]
client = gitConfig("git-p4.client")
if len(client) > 0:
- real_cmd += "-c %s " % client
+ real_cmd += ["-c", client]
- real_cmd += "%s" % (cmd)
- if verbose:
- print real_cmd
+
+ if isinstance(cmd,basestring):
+ real_cmd = ' '.join(real_cmd) + ' ' + cmd
+ else:
+ real_cmd += cmd
return real_cmd
def chdir(dir):
- if os.name == 'nt':
- os.environ['PWD']=dir
+ # P4 uses the PWD environment variable rather than getcwd(). Since we're
+ # not using the shell, we have to set it ourselves. This path could
+ # be relative, so go there first, then figure out where we ended up.
os.chdir(dir)
+ os.environ['PWD'] = os.getcwd()
def die(msg):
if verbose:
@@ -61,29 +67,34 @@ def die(msg):
sys.stderr.write(msg + "\n")
sys.exit(1)
-def write_pipe(c, str):
+def write_pipe(c, stdin):
if verbose:
- sys.stderr.write('Writing pipe: %s\n' % c)
+ sys.stderr.write('Writing pipe: %s\n' % str(c))
- pipe = os.popen(c, 'w')
- val = pipe.write(str)
- if pipe.close():
- die('Command failed: %s' % c)
+ expand = isinstance(c,basestring)
+ p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
+ pipe = p.stdin
+ val = pipe.write(stdin)
+ pipe.close()
+ if p.wait():
+ die('Command failed: %s' % str(c))
return val
-def p4_write_pipe(c, str):
+def p4_write_pipe(c, stdin):
real_cmd = p4_build_cmd(c)
- return write_pipe(real_cmd, str)
+ return write_pipe(real_cmd, stdin)
def read_pipe(c, ignore_error=False):
if verbose:
- sys.stderr.write('Reading pipe: %s\n' % c)
+ sys.stderr.write('Reading pipe: %s\n' % str(c))
- pipe = os.popen(c, 'rb')
+ expand = isinstance(c,basestring)
+ p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+ pipe = p.stdout
val = pipe.read()
- if pipe.close() and not ignore_error:
- die('Command failed: %s' % c)
+ if p.wait() and not ignore_error:
+ die('Command failed: %s' % str(c))
return val
@@ -93,12 +104,14 @@ def p4_read_pipe(c, ignore_error=False):
def read_pipe_lines(c):
if verbose:
- sys.stderr.write('Reading pipe: %s\n' % c)
- ## todo: check return status
- pipe = os.popen(c, 'rb')
+ sys.stderr.write('Reading pipe: %s\n' % str(c))
+
+ expand = isinstance(c, basestring)
+ p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+ pipe = p.stdout
val = pipe.readlines()
- if pipe.close():
- die('Command failed: %s' % c)
+ if pipe.close() or p.wait():
+ die('Command failed: %s' % str(c))
return val
@@ -108,23 +121,118 @@ def p4_read_pipe_lines(c):
return read_pipe_lines(real_cmd)
def system(cmd):
+ expand = isinstance(cmd,basestring)
if verbose:
- sys.stderr.write("executing %s\n" % cmd)
- if os.system(cmd) != 0:
- die("command failed: %s" % cmd)
+ sys.stderr.write("executing %s\n" % str(cmd))
+ subprocess.check_call(cmd, shell=expand)
def p4_system(cmd):
"""Specifically invoke p4 as the system command. """
real_cmd = p4_build_cmd(cmd)
- return system(real_cmd)
+ expand = isinstance(real_cmd, basestring)
+ subprocess.check_call(real_cmd, shell=expand)
+
+def p4_integrate(src, dest):
+ p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
+
+def p4_sync(f, *options):
+ p4_system(["sync"] + list(options) + [wildcard_encode(f)])
+
+def p4_add(f):
+ # forcibly add file names with wildcards
+ if wildcard_present(f):
+ p4_system(["add", "-f", f])
+ else:
+ p4_system(["add", f])
+
+def p4_delete(f):
+ p4_system(["delete", wildcard_encode(f)])
+
+def p4_edit(f):
+ p4_system(["edit", wildcard_encode(f)])
+
+def p4_revert(f):
+ p4_system(["revert", wildcard_encode(f)])
+
+def p4_reopen(type, f):
+ p4_system(["reopen", "-t", type, wildcard_encode(f)])
+
+#
+# Canonicalize the p4 type and return a tuple of the
+# base type, plus any modifiers. See "p4 help filetypes"
+# for a list and explanation.
+#
+def split_p4_type(p4type):
+
+ p4_filetypes_historical = {
+ "ctempobj": "binary+Sw",
+ "ctext": "text+C",
+ "cxtext": "text+Cx",
+ "ktext": "text+k",
+ "kxtext": "text+kx",
+ "ltext": "text+F",
+ "tempobj": "binary+FSw",
+ "ubinary": "binary+F",
+ "uresource": "resource+F",
+ "uxbinary": "binary+Fx",
+ "xbinary": "binary+x",
+ "xltext": "text+Fx",
+ "xtempobj": "binary+Swx",
+ "xtext": "text+x",
+ "xunicode": "unicode+x",
+ "xutf16": "utf16+x",
+ }
+ if p4type in p4_filetypes_historical:
+ p4type = p4_filetypes_historical[p4type]
+ mods = ""
+ s = p4type.split("+")
+ base = s[0]
+ mods = ""
+ if len(s) > 1:
+ mods = s[1]
+ return (base, mods)
+
+#
+# return the raw p4 type of a file (text, text+ko, etc)
+#
+def p4_type(file):
+ results = p4CmdList(["fstat", "-T", "headType", file])
+ return results[0]['headType']
-def isP4Exec(kind):
- """Determine if a Perforce 'kind' should have execute permission
+#
+# Given a type base and modifier, return a regexp matching
+# the keywords that can be expanded in the file
+#
+def p4_keywords_regexp_for_type(base, type_mods):
+ if base in ("text", "unicode", "binary"):
+ kwords = None
+ if "ko" in type_mods:
+ kwords = 'Id|Header'
+ elif "k" in type_mods:
+ kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
+ else:
+ return None
+ pattern = r"""
+ \$ # Starts with a dollar, followed by...
+ (%s) # one of the keywords, followed by...
+ (:[^$]+)? # possibly an old expansion, followed by...
+ \$ # another dollar
+ """ % kwords
+ return pattern
+ else:
+ return None
- 'p4 help filetypes' gives a list of the types. If it starts with 'x',
- or x follows one of a few letters. Otherwise, if there is an 'x' after
- a plus sign, it is also executable"""
- return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
+#
+# Given a file, return a regexp matching the possible
+# RCS keywords that will be expanded, or None for files
+# with kw expansion turned off.
+#
+def p4_keywords_regexp_for_file(file):
+ if not os.path.exists(file):
+ return None
+ else:
+ (type_base, type_mods) = split_p4_type(p4_type(file))
+ return p4_keywords_regexp_for_type(type_base, type_mods)
def setP4ExecBit(file, mode):
# Reopens an already open file and changes the execute bit to match
@@ -139,18 +247,38 @@ def setP4ExecBit(file, mode):
if p4Type[-1] == "+":
p4Type = p4Type[0:-1]
- p4_system("reopen -t %s %s" % (p4Type, file))
+ p4_reopen(p4Type, file)
def getP4OpenedType(file):
# Returns the perforce file type for the given file.
- result = p4_read_pipe("opened %s" % file)
+ result = p4_read_pipe(["opened", wildcard_encode(file)])
match = re.match(".*\((.+)\)\r?$", result)
if match:
return match.group(1)
else:
die("Could not determine file type for %s (result: '%s')" % (file, result))
+# Return the set of all p4 labels
+def getP4Labels(depotPaths):
+ labels = set()
+ if isinstance(depotPaths,basestring):
+ depotPaths = [depotPaths]
+
+ for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
+ label = l['label']
+ labels.add(label)
+
+ return labels
+
+# Return the set of all git tags
+def getGitTags():
+ gitTags = set()
+ for line in read_pipe_lines(["git", "tag"]):
+ tag = line.strip()
+ gitTags.add(tag)
+ return gitTags
+
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.
@@ -200,9 +328,17 @@ def isModeExecChanged(src_mode, dst_mode):
return isModeExec(src_mode) != isModeExec(dst_mode)
def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
- cmd = p4_build_cmd("-G %s" % (cmd))
+
+ if isinstance(cmd,basestring):
+ cmd = "-G " + cmd
+ expand = True
+ else:
+ cmd = ["-G"] + cmd
+ expand = False
+
+ cmd = p4_build_cmd(cmd)
if verbose:
- sys.stderr.write("Opening pipe: %s\n" % cmd)
+ sys.stderr.write("Opening pipe: %s\n" % str(cmd))
# Use a temporary file to avoid deadlocks without
# subprocess.communicate(), which would put another copy
@@ -210,11 +346,16 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
stdin_file = None
if stdin is not None:
stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
- stdin_file.write(stdin)
+ if isinstance(stdin,basestring):
+ stdin_file.write(stdin)
+ else:
+ for i in stdin:
+ stdin_file.write(i + '\n')
stdin_file.flush()
stdin_file.seek(0)
- p4 = subprocess.Popen(cmd, shell=True,
+ p4 = subprocess.Popen(cmd,
+ shell=expand,
stdin=stdin_file,
stdout=subprocess.PIPE)
@@ -247,7 +388,7 @@ def p4Where(depotPath):
if not depotPath.endswith("/"):
depotPath += "/"
depotPath = depotPath + "..."
- outputList = p4CmdList("where %s" % depotPath)
+ outputList = p4CmdList(["where", depotPath])
output = None
for entry in outputList:
if "depotFile" in entry:
@@ -288,6 +429,11 @@ def isValidGitDir(path):
def parseRevision(ref):
return read_pipe("git rev-parse %s" % ref).strip()
+def branchExists(ref):
+ rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
+ ignore_error=True)
+ return len(rev) > 0
+
def extractLogMessageFromGitCommit(commit):
logMessage = ""
@@ -342,6 +488,11 @@ def gitConfig(key, args = None): # set args to "--bool", for instance
_gitConfig[key] = read_pipe(cmd, ignore_error=True).strip()
return _gitConfig[key]
+def gitConfigList(key):
+ if not _gitConfig.has_key(key):
+ _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
+ return _gitConfig[key]
+
def p4BranchesInGit(branchesAreInRemotes = True):
branches = {}
@@ -444,8 +595,10 @@ def originP4BranchesExist():
def p4ChangesForPaths(depotPaths, changeRange):
assert depotPaths
- output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
- for p in depotPaths]))
+ cmd = ['changes']
+ for p in depotPaths:
+ cmd += ["%s...%s" % (p, changeRange)]
+ output = p4_read_pipe_lines(cmd)
changes = {}
for line in output:
@@ -469,14 +622,103 @@ def p4PathStartsWith(path, prefix):
return path.lower().startswith(prefix.lower())
return path.startswith(prefix)
+def getClientSpec():
+ """Look at the p4 client spec, create a View() object that contains
+ all the mappings, and return it."""
+
+ specList = p4CmdList("client -o")
+ if len(specList) != 1:
+ die('Output from "client -o" is %d lines, expecting 1' %
+ len(specList))
+
+ # dictionary of all client parameters
+ entry = specList[0]
+
+ # just the keys that start with "View"
+ view_keys = [ k for k in entry.keys() if k.startswith("View") ]
+
+ # hold this new View
+ view = View()
+
+ # append the lines, in order, to the view
+ for view_num in range(len(view_keys)):
+ k = "View%d" % view_num
+ if k not in view_keys:
+ die("Expected view key %s missing" % k)
+ view.append(entry[k])
+
+ return view
+
+def getClientRoot():
+ """Grab the client directory."""
+
+ output = p4CmdList("client -o")
+ if len(output) != 1:
+ die('Output from "client -o" is %d lines, expecting 1' % len(output))
+
+ entry = output[0]
+ if "Root" not in entry:
+ die('Client has no "Root"')
+
+ return entry["Root"]
+
+#
+# P4 wildcards are not allowed in filenames. P4 complains
+# if you simply add them, but you can force it with "-f", in
+# which case it translates them into %xx encoding internally.
+#
+def wildcard_decode(path):
+ # Search for and fix just these four characters. Do % last so
+ # that fixing it does not inadvertently create new %-escapes.
+ # Cannot have * in a filename in windows; untested as to
+ # what p4 would do in such a case.
+ if not platform.system() == "Windows":
+ path = path.replace("%2A", "*")
+ path = path.replace("%23", "#") \
+ .replace("%40", "@") \
+ .replace("%25", "%")
+ return path
+
+def wildcard_encode(path):
+ # do % first to avoid double-encoding the %s introduced here
+ path = path.replace("%", "%25") \
+ .replace("*", "%2A") \
+ .replace("#", "%23") \
+ .replace("@", "%40")
+ return path
+
+def wildcard_present(path):
+ return path.translate(None, "*#@%") != path
+
class Command:
def __init__(self):
self.usage = "usage: %prog [options]"
self.needsGit = True
+ self.verbose = False
class P4UserMap:
def __init__(self):
self.userMapFromPerforceServer = False
+ self.myP4UserId = None
+
+ def p4UserId(self):
+ if self.myP4UserId:
+ return self.myP4UserId
+
+ results = p4CmdList("user -o")
+ for r in results:
+ if r.has_key('User'):
+ self.myP4UserId = r['User']
+ return r['User']
+ die("Could not find your p4 user id")
+
+ def p4UserIsMe(self, p4User):
+ # return True if the given p4 user is actually me
+ me = self.p4UserId()
+ if not p4User or p4User != me:
+ return False
+ else:
+ return True
def getUserCacheFilename(self):
home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
@@ -518,17 +760,13 @@ class P4UserMap:
class P4Debug(Command):
def __init__(self):
Command.__init__(self)
- self.options = [
- optparse.make_option("--verbose", dest="verbose", action="store_true",
- default=False),
- ]
+ self.options = []
self.description = "A tool to debug the output of p4 -G."
self.needsGit = False
- self.verbose = False
def run(self, args):
j = 0
- for output in p4CmdList(" ".join(args)):
+ for output in p4CmdList(args):
print 'Element: %d' % j
j += 1
print output
@@ -538,11 +776,9 @@ class P4RollBack(Command):
def __init__(self):
Command.__init__(self)
self.options = [
- optparse.make_option("--verbose", dest="verbose", action="store_true"),
optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
]
self.description = "A tool to debug the multi-branch import. Don't use :)"
- self.verbose = False
self.rollbackLocalBranches = False
def run(self, args):
@@ -600,21 +836,20 @@ class P4Submit(Command, P4UserMap):
Command.__init__(self)
P4UserMap.__init__(self)
self.options = [
- optparse.make_option("--verbose", dest="verbose", action="store_true"),
optparse.make_option("--origin", dest="origin"),
optparse.make_option("-M", dest="detectRenames", action="store_true"),
# preserve the user, requires relevant p4 permissions
optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
+ optparse.make_option("--export-labels", dest="exportLabels", action="store_true"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
self.interactive = True
self.origin = ""
self.detectRenames = False
- self.verbose = False
self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
self.isWindows = (platform.system() == "Windows")
- self.myP4UserId = None
+ self.exportLabels = False
def check(self):
if len(p4CmdList("opened ...")) > 0:
@@ -648,6 +883,29 @@ class P4Submit(Command, P4UserMap):
return result
+ def patchRCSKeywords(self, file, pattern):
+ # Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
+ (handle, outFileName) = tempfile.mkstemp(dir='.')
+ try:
+ outFile = os.fdopen(handle, "w+")
+ inFile = open(file, "r")
+ regexp = re.compile(pattern, re.VERBOSE)
+ for line in inFile.readlines():
+ line = regexp.sub(r'$\1$', line)
+ outFile.write(line)
+ inFile.close()
+ outFile.close()
+ # Forcibly overwrite the original file
+ os.unlink(file)
+ shutil.move(outFileName, file)
+ except:
+ # cleanup our temporary file
+ os.unlink(outFileName)
+ print "Failed to strip RCS keywords in %s" % file
+ raise
+
+ print "Patched up RCS keywords in %s" % file
+
def p4UserForCommit(self,id):
# Return the tuple (perforce user,git email) for a given git commit id
self.getUserMapFromPerforceServer()
@@ -682,7 +940,7 @@ class P4Submit(Command, P4UserMap):
break
if not client:
die("could not get client spec")
- results = p4CmdList("changes -c %s -m 1" % client)
+ results = p4CmdList(["changes", "-c", client, "-m", "1"])
for r in results:
if r.has_key('change'):
return r['change']
@@ -713,7 +971,7 @@ class P4Submit(Command, P4UserMap):
def canChangeChangelists(self):
# check to see if we have p4 admin or super-user permissions, either of
# which are required to modify changelists.
- results = p4CmdList("protects %s" % self.depotPath)
+ results = p4CmdList(["protects", self.depotPath])
for r in results:
if r.has_key('perm'):
if r['perm'] == 'admin':
@@ -722,30 +980,11 @@ class P4Submit(Command, P4UserMap):
return 1
return 0
- def p4UserId(self):
- if self.myP4UserId:
- return self.myP4UserId
-
- results = p4CmdList("user -o")
- for r in results:
- if r.has_key('User'):
- self.myP4UserId = r['User']
- return r['User']
- die("Could not find your p4 user id")
-
- def p4UserIsMe(self, p4User):
- # return True if the given p4 user is actually me
- me = self.p4UserId()
- if not p4User or p4User != me:
- return False
- else:
- return True
-
def prepareSubmitTemplate(self):
# remove lines in the Files section that show changes to files outside the depot path we're committing into
template = ""
inFilesSection = False
- for line in p4_read_pipe_lines("change -o"):
+ for line in p4_read_pipe_lines(['change', '-o']):
if line.endswith("\r\n"):
line = line[:-2] + "\n"
if inFilesSection:
@@ -767,6 +1006,41 @@ class P4Submit(Command, P4UserMap):
return template
+ def edit_template(self, template_file):
+ """Invoke the editor to let the user change the submission
+ message. Return true if okay to continue with the submit."""
+
+ # if configured to skip the editing part, just submit
+ if gitConfig("git-p4.skipSubmitEdit") == "true":
+ return True
+
+ # look at the modification time, to check later if the user saved
+ # the file
+ mtime = os.stat(template_file).st_mtime
+
+ # invoke the editor
+ if os.environ.has_key("P4EDITOR") and (os.environ.get("P4EDITOR") != ""):
+ editor = os.environ.get("P4EDITOR")
+ else:
+ editor = read_pipe("git var GIT_EDITOR").strip()
+ system(editor + " " + template_file)
+
+ # If the file was not saved, prompt to see if this patch should
+ # be skipped. But skip this verification step if configured so.
+ if gitConfig("git-p4.skipSubmitEditCheck") == "true":
+ return True
+
+ # modification time updated means user saved the file
+ if os.stat(template_file).st_mtime > mtime:
+ return True
+
+ while True:
+ response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
+ if response == 'y':
+ return True
+ if response == 'n':
+ return False
+
def applyCommit(self, id):
print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
@@ -774,30 +1048,37 @@ class P4Submit(Command, P4UserMap):
if not self.detectRenames:
# If not explicitly set check the config variable
- self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
+ self.detectRenames = gitConfig("git-p4.detectRenames")
- if self.detectRenames:
+ if self.detectRenames.lower() == "false" or self.detectRenames == "":
+ diffOpts = ""
+ elif self.detectRenames.lower() == "true":
diffOpts = "-M"
else:
- diffOpts = ""
+ diffOpts = "-M%s" % self.detectRenames
- if gitConfig("git-p4.detectCopies").lower() == "true":
+ detectCopies = gitConfig("git-p4.detectCopies")
+ if detectCopies.lower() == "true":
diffOpts += " -C"
+ elif detectCopies != "" and detectCopies.lower() != "false":
+ diffOpts += " -C%s" % detectCopies
- if gitConfig("git-p4.detectCopiesHarder").lower() == "true":
+ if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
diffOpts += " --find-copies-harder"
diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
filesToAdd = set()
filesToDelete = set()
editedFiles = set()
+ pureRenameCopy = set()
filesToChangeExecBit = {}
+
for line in diff:
diff = parseDiffTreeEntry(line)
modifier = diff['status']
path = diff['src']
if modifier == "M":
- p4_system("edit \"%s\"" % path)
+ p4_edit(path)
if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
filesToChangeExecBit[path] = diff['dst_mode']
editedFiles.add(path)
@@ -812,21 +1093,26 @@ class P4Submit(Command, P4UserMap):
filesToAdd.remove(path)
elif modifier == "C":
src, dest = diff['src'], diff['dst']
- p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+ p4_integrate(src, dest)
+ pureRenameCopy.add(dest)
if diff['src_sha1'] != diff['dst_sha1']:
- p4_system("edit \"%s\"" % (dest))
+ p4_edit(dest)
+ pureRenameCopy.discard(dest)
if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
- p4_system("edit \"%s\"" % (dest))
+ p4_edit(dest)
+ pureRenameCopy.discard(dest)
filesToChangeExecBit[dest] = diff['dst_mode']
os.unlink(dest)
editedFiles.add(dest)
elif modifier == "R":
src, dest = diff['src'], diff['dst']
- p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+ p4_integrate(src, dest)
if diff['src_sha1'] != diff['dst_sha1']:
- p4_system("edit \"%s\"" % (dest))
+ p4_edit(dest)
+ else:
+ pureRenameCopy.add(dest)
if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
- p4_system("edit \"%s\"" % (dest))
+ p4_edit(dest)
filesToChangeExecBit[dest] = diff['dst_mode']
os.unlink(dest)
editedFiles.add(dest)
@@ -838,9 +1124,45 @@ class P4Submit(Command, P4UserMap):
patchcmd = diffcmd + " | git apply "
tryPatchCmd = patchcmd + "--check -"
applyPatchCmd = patchcmd + "--check --apply -"
+ patch_succeeded = True
if os.system(tryPatchCmd) != 0:
+ fixed_rcs_keywords = False
+ patch_succeeded = False
print "Unfortunately applying the change failed!"
+
+ # Patch failed, maybe it's just RCS keyword woes. Look through
+ # the patch to see if that's possible.
+ if gitConfig("git-p4.attemptRCSCleanup","--bool") == "true":
+ file = None
+ pattern = None
+ kwfiles = {}
+ for file in editedFiles | filesToDelete:
+ # did this file's delta contain RCS keywords?
+ pattern = p4_keywords_regexp_for_file(file)
+
+ if pattern:
+ # this file is a possibility...look for RCS keywords.
+ regexp = re.compile(pattern, re.VERBOSE)
+ for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
+ if regexp.search(line):
+ if verbose:
+ print "got keyword match on %s in %s in %s" % (pattern, line, file)
+ kwfiles[file] = pattern
+ break
+
+ for file in kwfiles:
+ if verbose:
+ print "zapping %s with %s" % (line,pattern)
+ self.patchRCSKeywords(file, kwfiles[file])
+ fixed_rcs_keywords = True
+
+ if fixed_rcs_keywords:
+ print "Retrying the patch with RCS keywords cleaned up"
+ if os.system(tryPatchCmd) == 0:
+ patch_succeeded = True
+
+ if not patch_succeeded:
print "What do you want to do?"
response = "x"
while response != "s" and response != "a" and response != "w":
@@ -849,9 +1171,9 @@ class P4Submit(Command, P4UserMap):
if response == "s":
print "Skipping! Good luck with the next patches..."
for f in editedFiles:
- p4_system("revert \"%s\"" % f);
+ p4_revert(f)
for f in filesToAdd:
- system("rm %s" %f)
+ os.remove(f)
return
elif response == "a":
os.system(applyPatchCmd)
@@ -862,20 +1184,20 @@ class P4Submit(Command, P4UserMap):
print "The following files should be scheduled for deletion with p4 delete:"
print " ".join(filesToDelete)
die("Please resolve and submit the conflict manually and "
- + "continue afterwards with git-p4 submit --continue")
+ + "continue afterwards with git p4 submit --continue")
elif response == "w":
system(diffcmd + " > patch.txt")
print "Patch saved to patch.txt in %s !" % self.clientPath
die("Please resolve and submit the conflict manually and "
- "continue afterwards with git-p4 submit --continue")
+ "continue afterwards with git p4 submit --continue")
system(applyPatchCmd)
for f in filesToAdd:
- p4_system("add \"%s\"" % f)
+ p4_add(f)
for f in filesToDelete:
- p4_system("revert \"%s\"" % f)
- p4_system("delete \"%s\"" % f)
+ p4_revert(f)
+ p4_delete(f)
# Set/clear executable bits
for f in filesToChangeExecBit.keys():
@@ -897,7 +1219,8 @@ class P4Submit(Command, P4UserMap):
del(os.environ["P4DIFF"])
diff = ""
for editedFile in editedFiles:
- diff += p4_read_pipe("diff -du %r" % editedFile)
+ diff += p4_read_pipe(['diff', '-du',
+ wildcard_encode(editedFile)])
newdiff = ""
for newFile in filesToAdd:
@@ -911,12 +1234,12 @@ class P4Submit(Command, P4UserMap):
if self.checkAuthorship and not self.p4UserIsMe(p4User):
submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
- submitTemplate += "######## Use git-p4 option --preserve-user to modify authorship\n"
- submitTemplate += "######## Use git-p4 config git-p4.skipUserNameCheck hides this message.\n"
+ submitTemplate += "######## Use option --preserve-user to modify authorship.\n"
+ submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
separatorLine = "######## everything below this line is just the diff #######\n"
- [handle, fileName] = tempfile.mkstemp()
+ (handle, fileName) = tempfile.mkstemp()
tmpFile = os.fdopen(handle, "w+")
if self.isWindows:
submitTemplate = submitTemplate.replace("\n", "\r\n")
@@ -924,46 +1247,38 @@ class P4Submit(Command, P4UserMap):
newdiff = newdiff.replace("\n", "\r\n")
tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
tmpFile.close()
- mtime = os.stat(fileName).st_mtime
- if os.environ.has_key("P4EDITOR"):
- editor = os.environ.get("P4EDITOR")
- else:
- editor = read_pipe("git var GIT_EDITOR").strip()
- system(editor + " " + fileName)
- if gitConfig("git-p4.skipSubmitEditCheck") == "true":
- checkModTime = False
- else:
- checkModTime = True
-
- response = "y"
- if checkModTime and (os.stat(fileName).st_mtime <= mtime):
- response = "x"
- while response != "y" and response != "n":
- response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
-
- if response == "y":
+ if self.edit_template(fileName):
+ # read the edited message and submit
tmpFile = open(fileName, "rb")
message = tmpFile.read()
tmpFile.close()
submitTemplate = message[:message.index(separatorLine)]
if self.isWindows:
submitTemplate = submitTemplate.replace("\r\n", "\n")
- p4_write_pipe("submit -i", submitTemplate)
+ p4_write_pipe(['submit', '-i'], submitTemplate)
if self.preserveUser:
if p4User:
# Get last changelist number. Cannot easily get it from
- # the submit command output as the output is unmarshalled.
+ # the submit command output as the output is
+ # unmarshalled.
changelist = self.lastP4Changelist()
self.modifyChangelistUser(changelist, p4User)
+ # The rename/copy happened by applying a patch that created a
+ # new file. This leaves it writable, which confuses p4.
+ for f in pureRenameCopy:
+ p4_sync(f, "-f")
+
else:
+ # skip this patch
+ print "Submission cancelled, undoing p4 changes."
for f in editedFiles:
- p4_system("revert \"%s\"" % f);
+ p4_revert(f)
for f in filesToAdd:
- p4_system("revert \"%s\"" % f);
- system("rm %s" %f)
+ p4_revert(f)
+ os.remove(f)
os.remove(fileName)
else:
@@ -975,6 +1290,71 @@ class P4Submit(Command, P4UserMap):
+ "Please review/edit and then use p4 submit -i < %s to submit directly!"
% (fileName, fileName))
+ # Export git tags as p4 labels. Create a p4 label and then tag
+ # with that.
+ def exportGitTags(self, gitTags):
+ validLabelRegexp = gitConfig("git-p4.labelExportRegexp")
+ if len(validLabelRegexp) == 0:
+ validLabelRegexp = defaultLabelRegexp
+ m = re.compile(validLabelRegexp)
+
+ for name in gitTags:
+
+ if not m.match(name):
+ if verbose:
+ print "tag %s does not match regexp %s" % (name, validLabelRegexp)
+ continue
+
+ # Get the p4 commit this corresponds to
+ logMessage = extractLogMessageFromGitCommit(name)
+ values = extractSettingsGitLog(logMessage)
+
+ if not values.has_key('change'):
+ # a tag pointing to something not sent to p4; ignore
+ if verbose:
+ print "git tag %s does not give a p4 commit" % name
+ continue
+ else:
+ changelist = values['change']
+
+ # Get the tag details.
+ inHeader = True
+ isAnnotated = False
+ body = []
+ for l in read_pipe_lines(["git", "cat-file", "-p", name]):
+ l = l.strip()
+ if inHeader:
+ if re.match(r'tag\s+', l):
+ isAnnotated = True
+ elif re.match(r'\s*$', l):
+ inHeader = False
+ continue
+ else:
+ body.append(l)
+
+ if not isAnnotated:
+ body = ["lightweight tag imported by git p4\n"]
+
+ # Create the label - use the same view as the client spec we are using
+ clientSpec = getClientSpec()
+
+ labelTemplate = "Label: %s\n" % name
+ labelTemplate += "Description:\n"
+ for b in body:
+ labelTemplate += "\t" + b + "\n"
+ labelTemplate += "View:\n"
+ for mapping in clientSpec.mappings:
+ labelTemplate += "\t%s\n" % mapping.depot_side.path
+
+ p4_write_pipe(["label", "-i"], labelTemplate)
+
+ # Use the label
+ p4_system(["tag", "-l", name] +
+ ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])
+
+ if verbose:
+ print "created p4 label for tag %s" % name
+
def run(self, args):
if len(args) == 0:
self.master = currentGitBranch()
@@ -982,6 +1362,8 @@ class P4Submit(Command, P4UserMap):
die("Detecting current git branch failed!")
elif len(args) == 1:
self.master = args[0]
+ if not branchExists(self.master):
+ die("Branch %s does not exist" % self.master)
else:
return False
@@ -1005,19 +1387,37 @@ class P4Submit(Command, P4UserMap):
print "Internal error: cannot locate perforce depot path from existing branches"
sys.exit(128)
- self.clientPath = p4Where(self.depotPath)
+ self.useClientSpec = False
+ if gitConfig("git-p4.useclientspec", "--bool") == "true":
+ self.useClientSpec = True
+ if self.useClientSpec:
+ self.clientSpecDirs = getClientSpec()
+
+ if self.useClientSpec:
+ # all files are relative to the client spec
+ self.clientPath = getClientRoot()
+ else:
+ self.clientPath = p4Where(self.depotPath)
- if len(self.clientPath) == 0:
- print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
- sys.exit(128)
+ if self.clientPath == "":
+ die("Error: Cannot locate perforce checkout of %s in client view" % self.depotPath)
print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
self.oldWorkingDirectory = os.getcwd()
+ # ensure the clientPath exists
+ new_client_dir = False
+ if not os.path.exists(self.clientPath):
+ new_client_dir = True
+ os.makedirs(self.clientPath)
+
chdir(self.clientPath)
print "Synchronizing p4 checkout..."
- p4_system("sync ...")
-
+ if new_client_dir:
+ # old one was destroyed, and maybe nobody told p4
+ p4_sync("...", "-f")
+ else:
+ p4_sync("...")
self.check()
commits = []
@@ -1050,8 +1450,231 @@ class P4Submit(Command, P4UserMap):
rebase = P4Rebase()
rebase.rebase()
+ if gitConfig("git-p4.exportLabels", "--bool") == "true":
+ self.exportLabels = True
+
+ if self.exportLabels:
+ p4Labels = getP4Labels(self.depotPath)
+ gitTags = getGitTags()
+
+ missingGitTags = gitTags - p4Labels
+ self.exportGitTags(missingGitTags)
+
return True
+class View(object):
+ """Represent a p4 view ("p4 help views"), and map files in a
+ repo according to the view."""
+
+ class Path(object):
+ """A depot or client path, possibly containing wildcards.
+ The only one supported is ... at the end, currently.
+ Initialize with the full path, with //depot or //client."""
+
+ def __init__(self, path, is_depot):
+ self.path = path
+ self.is_depot = is_depot
+ self.find_wildcards()
+ # remember the prefix bit, useful for relative mappings
+ m = re.match("(//[^/]+/)", self.path)
+ if not m:
+ die("Path %s does not start with //prefix/" % self.path)
+ prefix = m.group(1)
+ if not self.is_depot:
+ # strip //client/ on client paths
+ self.path = self.path[len(prefix):]
+
+ def find_wildcards(self):
+ """Make sure wildcards are valid, and set up internal
+ variables."""
+
+ self.ends_triple_dot = False
+ # There are three wildcards allowed in p4 views
+ # (see "p4 help views"). This code knows how to
+ # handle "..." (only at the end), but cannot deal with
+ # "%%n" or "*". Only check the depot_side, as p4 should
+ # validate that the client_side matches too.
+ if re.search(r'%%[1-9]', self.path):
+ die("Can't handle %%n wildcards in view: %s" % self.path)
+ if self.path.find("*") >= 0:
+ die("Can't handle * wildcards in view: %s" % self.path)
+ triple_dot_index = self.path.find("...")
+ if triple_dot_index >= 0:
+ if triple_dot_index != len(self.path) - 3:
+ die("Can handle only single ... wildcard, at end: %s" %
+ self.path)
+ self.ends_triple_dot = True
+
+ def ensure_compatible(self, other_path):
+ """Make sure the wildcards agree."""
+ if self.ends_triple_dot != other_path.ends_triple_dot:
+ die("Both paths must end with ... if either does;\n" +
+ "paths: %s %s" % (self.path, other_path.path))
+
+ def match_wildcards(self, test_path):
+ """See if this test_path matches us, and fill in the value
+ of the wildcards if so. Returns a tuple of
+ (True|False, wildcards[]). For now, only the ... at end
+ is supported, so at most one wildcard."""
+ if self.ends_triple_dot:
+ dotless = self.path[:-3]
+ if test_path.startswith(dotless):
+ wildcard = test_path[len(dotless):]
+ return (True, [ wildcard ])
+ else:
+ if test_path == self.path:
+ return (True, [])
+ return (False, [])
+
+ def match(self, test_path):
+ """Just return if it matches; don't bother with the wildcards."""
+ b, _ = self.match_wildcards(test_path)
+ return b
+
+ def fill_in_wildcards(self, wildcards):
+ """Return the relative path, with the wildcards filled in
+ if there are any."""
+ if self.ends_triple_dot:
+ return self.path[:-3] + wildcards[0]
+ else:
+ return self.path
+
+ class Mapping(object):
+ def __init__(self, depot_side, client_side, overlay, exclude):
+ # depot_side is without the trailing /... if it had one
+ self.depot_side = View.Path(depot_side, is_depot=True)
+ self.client_side = View.Path(client_side, is_depot=False)
+ self.overlay = overlay # started with "+"
+ self.exclude = exclude # started with "-"
+ assert not (self.overlay and self.exclude)
+ self.depot_side.ensure_compatible(self.client_side)
+
+ def __str__(self):
+ c = " "
+ if self.overlay:
+ c = "+"
+ if self.exclude:
+ c = "-"
+ return "View.Mapping: %s%s -> %s" % \
+ (c, self.depot_side.path, self.client_side.path)
+
+ def map_depot_to_client(self, depot_path):
+ """Calculate the client path if using this mapping on the
+ given depot path; does not consider the effect of other
+ mappings in a view. Even excluded mappings are returned."""
+ matches, wildcards = self.depot_side.match_wildcards(depot_path)
+ if not matches:
+ return ""
+ client_path = self.client_side.fill_in_wildcards(wildcards)
+ return client_path
+
+ #
+ # View methods
+ #
+ def __init__(self):
+ self.mappings = []
+
+ def append(self, view_line):
+ """Parse a view line, splitting it into depot and client
+ sides. Append to self.mappings, preserving order."""
+
+ # Split the view line into exactly two words. P4 enforces
+ # structure on these lines that simplifies this quite a bit.
+ #
+ # Either or both words may be double-quoted.
+ # Single quotes do not matter.
+ # Double-quote marks cannot occur inside the words.
+ # A + or - prefix is also inside the quotes.
+ # There are no quotes unless they contain a space.
+ # The line is already white-space stripped.
+ # The two words are separated by a single space.
+ #
+ if view_line[0] == '"':
+ # First word is double quoted. Find its end.
+ close_quote_index = view_line.find('"', 1)
+ if close_quote_index <= 0:
+ die("No first-word closing quote found: %s" % view_line)
+ depot_side = view_line[1:close_quote_index]
+ # skip closing quote and space
+ rhs_index = close_quote_index + 1 + 1
+ else:
+ space_index = view_line.find(" ")
+ if space_index <= 0:
+ die("No word-splitting space found: %s" % view_line)
+ depot_side = view_line[0:space_index]
+ rhs_index = space_index + 1
+
+ if view_line[rhs_index] == '"':
+ # Second word is double quoted. Make sure there is a
+ # double quote at the end too.
+ if not view_line.endswith('"'):
+ die("View line with rhs quote should end with one: %s" %
+ view_line)
+ # skip the quotes
+ client_side = view_line[rhs_index+1:-1]
+ else:
+ client_side = view_line[rhs_index:]
+
+ # prefix + means overlay on previous mapping
+ overlay = False
+ if depot_side.startswith("+"):
+ overlay = True
+ depot_side = depot_side[1:]
+
+ # prefix - means exclude this path
+ exclude = False
+ if depot_side.startswith("-"):
+ exclude = True
+ depot_side = depot_side[1:]
+
+ m = View.Mapping(depot_side, client_side, overlay, exclude)
+ self.mappings.append(m)
+
+ def map_in_client(self, depot_path):
+ """Return the relative location in the client where this
+ depot file should live. Returns "" if the file should
+ not be mapped in the client."""
+
+ paths_filled = []
+ client_path = ""
+
+ # look at later entries first
+ for m in self.mappings[::-1]:
+
+ # see where will this path end up in the client
+ p = m.map_depot_to_client(depot_path)
+
+ if p == "":
+ # Depot path does not belong in client. Must remember
+ # this, as previous items should not cause files to
+ # exist in this path either. Remember that the list is
+ # being walked from the end, which has higher precedence.
+ # Overlap mappings do not exclude previous mappings.
+ if not m.overlay:
+ paths_filled.append(m.client_side)
+
+ else:
+ # This mapping matched; no need to search any further.
+ # But, the mapping could be rejected if the client path
+ # has already been claimed by an earlier mapping (i.e.
+ # one later in the list, which we are walking backwards).
+ already_mapped_in_client = False
+ for f in paths_filled:
+ # this is View.Path.match
+ if f.match(p):
+ already_mapped_in_client = True
+ break
+ if not already_mapped_in_client:
+ # Include this file, unless it is from a line that
+ # explicitly said to exclude it.
+ if not m.exclude:
+ client_path = p
+
+ # a match, even if rejected, always stops the search
+ break
+
+ return client_path
+
class P4Sync(Command, P4UserMap):
delete_actions = ( "delete", "move/delete", "purge" )
@@ -1064,7 +1687,7 @@ class P4Sync(Command, P4UserMap):
optparse.make_option("--changesfile", dest="changesFile"),
optparse.make_option("--silent", dest="silent", action="store_true"),
optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
- optparse.make_option("--verbose", dest="verbose", action="store_true"),
+ optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
help="Import into refs/heads/ , not refs/remotes"),
optparse.make_option("--max-changes", dest="maxChanges"),
@@ -1088,9 +1711,9 @@ class P4Sync(Command, P4UserMap):
self.branch = ""
self.detectBranches = False
self.detectLabels = False
+ self.importLabels = False
self.changesFile = ""
self.syncWithOrigin = True
- self.verbose = False
self.importIntoRemotes = True
self.maxChanges = ""
self.isWindows = (platform.system() == "Windows")
@@ -1099,27 +1722,21 @@ class P4Sync(Command, P4UserMap):
self.p4BranchesInGit = []
self.cloneExclude = []
self.useClientSpec = False
- self.clientSpecDirs = []
+ self.useClientSpec_from_options = False
+ self.clientSpecDirs = None
+ self.tempBranches = []
+ self.tempBranchLocation = "git-p4-tmp"
if gitConfig("git-p4.syncFromOrigin") == "false":
self.syncWithOrigin = False
- #
- # P4 wildcards are not allowed in filenames. P4 complains
- # if you simply add them, but you can force it with "-f", in
- # which case it translates them into %xx encoding internally.
- # Search for and fix just these four characters. Do % last so
- # that fixing it does not inadvertently create new %-escapes.
- #
- def wildcard_decode(self, path):
- # Cannot have * in a filename in windows; untested as to
- # what p4 would do in such a case.
- if not self.isWindows:
- path = path.replace("%2A", "*")
- path = path.replace("%23", "#") \
- .replace("%40", "@") \
- .replace("%25", "%")
- return path
+ # Force a checkpoint in fast-import and wait for it to finish
+ def checkpoint(self):
+ self.gitStream.write("checkpoint\n\n")
+ self.gitStream.write("progress checkpoint\n\n")
+ out = self.gitOutput.readline()
+ if self.verbose:
+ print "checkpoint finished: " + out
def extractFilesFromCommit(self, commit):
self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
@@ -1150,20 +1767,7 @@ class P4Sync(Command, P4UserMap):
def stripRepoPath(self, path, prefixes):
if self.useClientSpec:
-
- # if using the client spec, we use the output directory
- # specified in the client. For example, a view
- # //depot/foo/branch/... //client/branch/foo/...
- # will end up putting all foo/branch files into
- # branch/foo/
- for val in self.clientSpecDirs:
- if path.startswith(val[0]):
- # replace the depot path with the client path
- path = path.replace(val[0], val[1][1])
- # now strip out the client (//client/...)
- path = re.sub("^(//[^/]+/)", '', path)
- # the rest is all path
- return path
+ return self.clientSpecDirs.map_in_client(path)
if self.keepRepoPath:
prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
@@ -1193,6 +1797,7 @@ class P4Sync(Command, P4UserMap):
fnum = fnum + 1
relPath = self.stripRepoPath(path, self.depotPaths)
+ relPath = wildcard_decode(relPath)
for branch in self.knownBranches.keys():
@@ -1209,38 +1814,63 @@ class P4Sync(Command, P4UserMap):
# - helper for streamP4Files
def streamOneP4File(self, file, contents):
- if file["type"] == "apple":
- print "\nfile %s is a strange apple file that forks. Ignoring" % \
- file['depotFile']
- return
-
relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
- relPath = self.wildcard_decode(relPath)
+ relPath = wildcard_decode(relPath)
if verbose:
sys.stderr.write("%s\n" % relPath)
- mode = "644"
- if isP4Exec(file["type"]):
- mode = "755"
- elif file["type"] == "symlink":
- mode = "120000"
- # p4 print on a symlink contains "target\n", so strip it off
+ (type_base, type_mods) = split_p4_type(file["type"])
+
+ git_mode = "100644"
+ if "x" in type_mods:
+ git_mode = "100755"
+ if type_base == "symlink":
+ git_mode = "120000"
+ # p4 print on a symlink contains "target\n"; remove the newline
data = ''.join(contents)
contents = [data[:-1]]
- if self.isWindows and file["type"].endswith("text"):
+ if type_base == "utf16":
+ # p4 delivers different text in the python output to -G
+ # than it does when using "print -o", or normal p4 client
+ # operations. utf16 is converted to ascii or utf8, perhaps.
+ # But ascii text saved as -t utf16 is completely mangled.
+ # Invoke print -o to get the real contents.
+ text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']])
+ contents = [ text ]
+
+ if type_base == "apple":
+ # Apple filetype files will be streamed as a concatenation of
+ # its appledouble header and the contents. This is useless
+ # on both macs and non-macs. If using "print -q -o xx", it
+ # will create "xx" with the data, and "%xx" with the header.
+ # This is also not very useful.
+ #
+ # Ideally, someday, this script can learn how to generate
+ # appledouble files directly and import those to git, but
+ # non-mac machines can never find a use for apple filetype.
+ print "\nIgnoring apple filetype file %s" % file['depotFile']
+ return
+
+ # Perhaps windows wants unicode, utf16 newlines translated too;
+ # but this is not doing it.
+ if self.isWindows and type_base == "text":
mangled = []
for data in contents:
data = data.replace("\r\n", "\n")
mangled.append(data)
contents = mangled
- if file['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
- contents = map(lambda text: re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text), contents)
- elif file['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
- contents = map(lambda text: re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text), contents)
+ # Note that we do not try to de-mangle keywords on utf16 files,
+ # even though in theory somebody may want that.
+ pattern = p4_keywords_regexp_for_type(type_base, type_mods)
+ if pattern:
+ regexp = re.compile(pattern, re.VERBOSE)
+ text = ''.join(contents)
+ text = regexp.sub(r'$\1$', text)
+ contents = [ text ]
- self.gitStream.write("M %s inline %s\n" % (mode, relPath))
+ self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
# total length...
length = 0
@@ -1254,6 +1884,7 @@ class P4Sync(Command, P4UserMap):
def streamOneP4Deletion(self, file):
relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
+ relPath = wildcard_decode(relPath)
if verbose:
sys.stderr.write("delete %s\n" % relPath)
self.gitStream.write("D %s\n" % relPath)
@@ -1285,19 +1916,17 @@ class P4Sync(Command, P4UserMap):
filesToDelete = []
for f in files:
- includeFile = True
- for val in self.clientSpecDirs:
- if f['path'].startswith(val[0]):
- if val[1][0] <= 0:
- includeFile = False
- break
+ # if using a client spec, only add the files that have
+ # a path in the client
+ if self.clientSpecDirs:
+ if self.clientSpecDirs.map_in_client(f['path']) == "":
+ continue
- if includeFile:
- filesForCommit.append(f)
- if f['action'] in self.delete_actions:
- filesToDelete.append(f)
- else:
- filesToRead.append(f)
+ filesForCommit.append(f)
+ if f['action'] in self.delete_actions:
+ filesToDelete.append(f)
+ else:
+ filesToRead.append(f)
# deleted files...
for f in filesToDelete:
@@ -1312,15 +1941,54 @@ class P4Sync(Command, P4UserMap):
def streamP4FilesCbSelf(entry):
self.streamP4FilesCb(entry)
- p4CmdList("-x - print",
- '\n'.join(['%s#%s' % (f['path'], f['rev'])
- for f in filesToRead]),
- cb=streamP4FilesCbSelf)
+ fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
+
+ p4CmdList(["-x", "-", "print"],
+ stdin=fileArgs,
+ cb=streamP4FilesCbSelf)
# do the last chunk
if self.stream_file.has_key('depotFile'):
self.streamOneP4File(self.stream_file, self.stream_contents)
+ def make_email(self, userid):
+ if userid in self.users:
+ return self.users[userid]
+ else:
+ return "%s <a@b>" % userid
+
+ # Stream a p4 tag
+ def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
+ if verbose:
+ print "writing tag %s for commit %s" % (labelName, commit)
+ gitStream.write("tag %s\n" % labelName)
+ gitStream.write("from %s\n" % commit)
+
+ if labelDetails.has_key('Owner'):
+ owner = labelDetails["Owner"]
+ else:
+ owner = None
+
+ # Try to use the owner of the p4 label, or failing that,
+ # the current p4 user id.
+ if owner:
+ email = self.make_email(owner)
+ else:
+ email = self.make_email(self.p4UserId())
+ tagger = "%s %s %s" % (email, epoch, self.tz)
+
+ gitStream.write("tagger %s\n" % tagger)
+
+ print "labelDetails=",labelDetails
+ if labelDetails.has_key('Description'):
+ description = labelDetails['Description']
+ else:
+ description = 'Label from git p4'
+
+ gitStream.write("data %d\n" % len(description))
+ gitStream.write(description)
+ gitStream.write("\n")
+
def commit(self, details, files, branch, branchPrefixes, parent = ""):
epoch = details["time"]
author = details["user"]
@@ -1344,10 +2012,7 @@ class P4Sync(Command, P4UserMap):
committer = ""
if author not in self.users:
self.getUserMapFromPerforceServer()
- if author in self.users:
- committer = "%s %s %s" % (self.users[author], epoch, self.tz)
- else:
- committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
+ committer = "%s %s %s" % (self.make_email(author), epoch, self.tz)
self.gitStream.write("committer %s\n" % committer)
@@ -1376,8 +2041,8 @@ class P4Sync(Command, P4UserMap):
if self.verbose:
print "Change %s is labelled %s" % (change, labelDetails)
- files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
- for p in branchPrefixes]))
+ files = p4CmdList(["files"] + ["%s...@%s" % (p, change)
+ for p in branchPrefixes])
if len(files) == len(labelRevisions):
@@ -1388,19 +2053,7 @@ class P4Sync(Command, P4UserMap):
cleanedFiles[info["depotFile"]] = info["rev"]
if cleanedFiles == labelRevisions:
- self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
- self.gitStream.write("from %s\n" % branch)
-
- owner = labelDetails["Owner"]
- tagger = ""
- if author in self.users:
- tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
- else:
- tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
- self.gitStream.write("tagger %s\n" % tagger)
- self.gitStream.write("data <<EOT\n")
- self.gitStream.write(labelDetails["Description"])
- self.gitStream.write("EOT\n\n")
+ self.streamTag(self.gitStream, 'tag_%s' % labelDetails['label'], labelDetails, branch, epoch)
else:
if not self.silent:
@@ -1412,10 +2065,11 @@ class P4Sync(Command, P4UserMap):
print ("Tag %s does not match with change %s: file count is different."
% (labelDetails["label"], change))
+ # Build a dictionary of changelists and labels, for "detect-labels" option.
def getLabels(self):
self.labels = {}
- l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
+ l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
if len(l) > 0 and not self.silent:
print "Finding files belonging to labels in %s" % `self.depotPaths`
@@ -1425,9 +2079,9 @@ class P4Sync(Command, P4UserMap):
newestChange = 0
if self.verbose:
print "Querying files for label %s" % label
- for file in p4CmdList("files "
- + ' '.join (["%s...@%s" % (p, label)
- for p in self.depotPaths])):
+ for file in p4CmdList(["files"] +
+ ["%s...@%s" % (p, label)
+ for p in self.depotPaths]):
revisions[file["depotFile"]] = file["rev"]
change = int(file["change"])
if change > newestChange:
@@ -1438,6 +2092,69 @@ class P4Sync(Command, P4UserMap):
if self.verbose:
print "Label changes: %s" % self.labels.keys()
+ # Import p4 labels as git tags. A direct mapping does not
+ # exist, so assume that if all the files are at the same revision
+ # then we can use that, or it's something more complicated we should
+ # just ignore.
+ def importP4Labels(self, stream, p4Labels):
+ if verbose:
+ print "import p4 labels: " + ' '.join(p4Labels)
+
+ ignoredP4Labels = gitConfigList("git-p4.ignoredP4Labels")
+ validLabelRegexp = gitConfig("git-p4.labelImportRegexp")
+ if len(validLabelRegexp) == 0:
+ validLabelRegexp = defaultLabelRegexp
+ m = re.compile(validLabelRegexp)
+
+ for name in p4Labels:
+ commitFound = False
+
+ if not m.match(name):
+ if verbose:
+ print "label %s does not match regexp %s" % (name,validLabelRegexp)
+ continue
+
+ if name in ignoredP4Labels:
+ continue
+
+ labelDetails = p4CmdList(['label', "-o", name])[0]
+
+ # get the most recent changelist for each file in this label
+ change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name)
+ for p in self.depotPaths])
+
+ if change.has_key('change'):
+ # find the corresponding git commit; take the oldest commit
+ changelist = int(change['change'])
+ gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
+ "--reverse", ":/\[git-p4:.*change = %d\]" % changelist])
+ if len(gitCommit) == 0:
+ print "could not find git commit for changelist %d" % changelist
+ else:
+ gitCommit = gitCommit.strip()
+ commitFound = True
+ # Convert from p4 time format
+ try:
+ tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
+ except ValueError:
+ print "Could not convert label time %s" % labelDetail['Update']
+ tmwhen = 1
+
+ when = int(time.mktime(tmwhen))
+ self.streamTag(stream, name, labelDetails, gitCommit, when)
+ if verbose:
+ print "p4 label %s mapped to git commit %s" % (name, gitCommit)
+ else:
+ if verbose:
+ print "Label %s has no changelists - possibly deleted?" % name
+
+ if not commitFound:
+ # We can't import this label; don't try again as it will get very
+ # expensive repeatedly fetching all the files for labels that will
+ # never be imported. If the label is moved in the future, the
+ # ignore will need to be removed manually.
+ system(["git", "config", "--add", "git-p4.ignoredP4Labels", name])
+
def guessProjectName(self):
for p in self.depotPaths:
if p.endswith("/"):
@@ -1450,8 +2167,14 @@ class P4Sync(Command, P4UserMap):
def getBranchMapping(self):
lostAndFoundBranches = set()
- for info in p4CmdList("branches"):
- details = p4Cmd("branch -o %s" % info["branch"])
+ user = gitConfig("git-p4.branchUser")
+ if len(user) > 0:
+ command = "branches -u %s" % user
+ else:
+ command = "branches"
+
+ for info in p4CmdList(command):
+ details = p4Cmd(["branch", "-o", info["branch"]])
viewIdx = 0
while details.has_key("View%s" % viewIdx):
paths = details["View%s" % viewIdx].split(" ")
@@ -1479,6 +2202,25 @@ class P4Sync(Command, P4UserMap):
if source not in self.knownBranches:
lostAndFoundBranches.add(source)
+ # Perforce does not strictly require branches to be defined, so we also
+ # check git config for a branch list.
+ #
+ # Example of branch definition in git config file:
+ # [git-p4]
+ # branchList=main:branchA
+ # branchList=main:branchB
+ # branchList=branchA:branchC
+ configBranches = gitConfigList("git-p4.branchList")
+ for branch in configBranches:
+ if branch:
+ (source, destination) = branch.split(":")
+ self.knownBranches[destination] = source
+
+ lostAndFoundBranches.discard(destination)
+
+ if source not in self.knownBranches:
+ lostAndFoundBranches.add(source)
+
for branch in lostAndFoundBranches:
self.knownBranches[branch] = branch
@@ -1570,7 +2312,7 @@ class P4Sync(Command, P4UserMap):
sourceRef = self.gitRefForBranch(sourceBranch)
#print "source " + sourceBranch
- branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
+ branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
#print "branch parent: %s" % branchParentChange
gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
if len(gitParent) > 0:
@@ -1580,10 +2322,24 @@ class P4Sync(Command, P4UserMap):
self.importChanges(changes)
return True
+ def searchParent(self, parent, branch, target):
+ parentFound = False
+ for blob in read_pipe_lines(["git", "rev-list", "--reverse", "--no-merges", parent]):
+ blob = blob.strip()
+ if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
+ parentFound = True
+ if self.verbose:
+ print "Found parent of %s in commit %s" % (branch, blob)
+ break
+ if parentFound:
+ return blob
+ else:
+ return None
+
def importChanges(self, changes):
cnt = 1
for change in changes:
- description = p4Cmd("describe %s" % change)
+ description = p4Cmd(["describe", str(change)])
self.updateOptionDict(description)
if not self.silent:
@@ -1636,7 +2392,21 @@ class P4Sync(Command, P4UserMap):
parent = self.initialParents[branch]
del self.initialParents[branch]
- self.commit(description, filesForCommit, branch, [branchPrefix], parent)
+ blob = None
+ if len(parent) > 0:
+ tempBranch = os.path.join(self.tempBranchLocation, "%d" % (change))
+ if self.verbose:
+ print "Creating temporary branch: " + tempBranch
+ self.commit(description, filesForCommit, tempBranch, [branchPrefix])
+ self.tempBranches.append(tempBranch)
+ self.checkpoint()
+ blob = self.searchParent(parent, branch, tempBranch)
+ if blob:
+ self.commit(description, filesForCommit, branch, [branchPrefix], blob)
+ else:
+ if self.verbose:
+ print "Parent of %s not found. Committing into head of %s" % (branch, parent)
+ self.commit(description, filesForCommit, branch, [branchPrefix], parent)
else:
files = self.extractFilesFromCommit(description)
self.commit(description, files, self.branch, self.depotPaths,
@@ -1649,17 +2419,17 @@ class P4Sync(Command, P4UserMap):
def importHeadRevision(self, revision):
print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
- details = { "user" : "git perforce import user", "time" : int(time.time()) }
+ details = {}
+ details["user"] = "git perforce import user"
details["desc"] = ("Initial import of %s from the state at revision %s\n"
% (' '.join(self.depotPaths), revision))
details["change"] = revision
newestRevision = 0
fileCnt = 0
- for info in p4CmdList("files "
- + ' '.join(["%s...%s"
- % (p, revision)
- for p in self.depotPaths])):
+ fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
+
+ for info in p4CmdList(["files"] + fileArgs):
if 'code' in info and info['code'] == 'error':
sys.stderr.write("p4 returned an error: %s\n"
@@ -1689,6 +2459,18 @@ class P4Sync(Command, P4UserMap):
fileCnt = fileCnt + 1
details["change"] = newestRevision
+
+ # Use time from top-most change so that all git p4 clones of
+ # the same p4 repo have the same commit SHA1s.
+ res = p4CmdList("describe -s %d" % newestRevision)
+ newestTime = None
+ for r in res:
+ if r.has_key('time'):
+ newestTime = int(r['time'])
+ if newestTime is None:
+ die("\"describe -s\" on newest change %d did not give a time")
+ details["time"] = newestTime
+
self.updateOptionDict(details)
try:
self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
@@ -1697,52 +2479,6 @@ class P4Sync(Command, P4UserMap):
print self.gitError.read()
- def getClientSpec(self):
- specList = p4CmdList( "client -o" )
- temp = {}
- for entry in specList:
- for k,v in entry.iteritems():
- if k.startswith("View"):
-
- # p4 has these %%1 to %%9 arguments in specs to
- # reorder paths; which we can't handle (yet :)
- if re.match('%%\d', v) != None:
- print "Sorry, can't handle %%n arguments in client specs"
- sys.exit(1)
-
- if v.startswith('"'):
- start = 1
- else:
- start = 0
- index = v.find("...")
-
- # save the "client view"; i.e the RHS of the view
- # line that tells the client where to put the
- # files for this view.
- cv = v[index+3:].strip() # +3 to remove previous '...'
-
- # if the client view doesn't end with a
- # ... wildcard, then we're going to mess up the
- # output directory, so fail gracefully.
- if not cv.endswith('...'):
- print 'Sorry, client view in "%s" needs to end with wildcard' % (k)
- sys.exit(1)
- cv=cv[:-3]
-
- # now save the view; +index means included, -index
- # means it should be filtered out.
- v = v[start:index]
- if v.startswith("-"):
- v = v[1:]
- include = -len(v)
- else:
- include = len(v)
-
- temp[v] = (include, cv)
-
- self.clientSpecDirs = temp.items()
- self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) )
-
def run(self, args):
self.depotPaths = []
self.changeRange = ""
@@ -1775,8 +2511,15 @@ class P4Sync(Command, P4UserMap):
if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
- if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
- self.getClientSpec()
+ # accept either the command-line option, or the configuration variable
+ if self.useClientSpec:
+ # will use this after clone to set the variable
+ self.useClientSpec_from_options = True
+ else:
+ if gitConfig("git-p4.useclientspec", "--bool") == "true":
+ self.useClientSpec = True
+ if self.useClientSpec:
+ self.clientSpecDirs = getClientSpec()
# TODO: should always look at previous commits,
# merge with previous imports, if possible.
@@ -1811,12 +2554,14 @@ class P4Sync(Command, P4UserMap):
else:
paths = []
for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
- for i in range(0, min(len(cur), len(prev))):
- if cur[i] <> prev[i]:
+ prev_list = prev.split("/")
+ cur_list = cur.split("/")
+ for i in range(0, min(len(cur_list), len(prev_list))):
+ if cur_list[i] <> prev_list[i]:
i = i - 1
break
- paths.append (cur[:i + 1])
+ paths.append ("/".join(cur_list[:i + 1]))
self.previousDepotPaths = paths
@@ -1846,6 +2591,17 @@ class P4Sync(Command, P4UserMap):
revision = ""
self.users = {}
+ # Make sure no revision specifiers are used when --changesfile
+ # is specified.
+ bad_changesfile = False
+ if len(self.changesFile) > 0:
+ for p in self.depotPaths:
+ if p.find("@") >= 0 or p.find("#") >= 0:
+ bad_changesfile = True
+ break
+ if bad_changesfile:
+ die("Option --changesfile is incompatible with revision specifiers")
+
newPaths = []
for p in self.depotPaths:
if p.find("@") != -1:
@@ -1862,7 +2618,10 @@ class P4Sync(Command, P4UserMap):
revision = p[hashIdx:]
p = p[:hashIdx]
elif self.previousDepotPaths == []:
- revision = "#head"
+ # pay attention to changesfile, if given, else import
+ # the entire p4 tree at the head revision
+ if len(self.changesFile) == 0:
+ revision = "#head"
p = re.sub ("\.\.\.$", "", p)
if not p.endswith("/"):
@@ -1872,7 +2631,6 @@ class P4Sync(Command, P4UserMap):
self.depotPaths = newPaths
-
self.loadUserMapFromCache()
self.labels = {}
if self.detectLabels:
@@ -1921,8 +2679,8 @@ class P4Sync(Command, P4UserMap):
changes.sort()
else:
- # catch "git-p4 sync" with no new branches, in a repo that
- # does not have any existing git-p4 branches
+ # catch "git p4 sync" with no new branches, in a repo that
+ # does not have any existing p4 branches
if len(args) == 0 and not self.p4BranchesInGit:
die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.");
if self.verbose:
@@ -1936,22 +2694,31 @@ class P4Sync(Command, P4UserMap):
if len(changes) == 0:
if not self.silent:
print "No changes to import!"
- return True
+ else:
+ if not self.silent and not self.detectBranches:
+ print "Import destination: %s" % self.branch
+
+ self.updatedBranches = set()
- if not self.silent and not self.detectBranches:
- print "Import destination: %s" % self.branch
+ self.importChanges(changes)
- self.updatedBranches = set()
+ if not self.silent:
+ print ""
+ if len(self.updatedBranches) > 0:
+ sys.stdout.write("Updated branches: ")
+ for b in self.updatedBranches:
+ sys.stdout.write("%s " % b)
+ sys.stdout.write("\n")
- self.importChanges(changes)
+ if gitConfig("git-p4.importLabels", "--bool") == "true":
+ self.importLabels = True
- if not self.silent:
- print ""
- if len(self.updatedBranches) > 0:
- sys.stdout.write("Updated branches: ")
- for b in self.updatedBranches:
- sys.stdout.write("%s " % b)
- sys.stdout.write("\n")
+ if self.importLabels:
+ p4Labels = getP4Labels(self.depotPaths)
+ gitTags = getGitTags()
+
+ missingP4Labels = p4Labels - gitTags
+ self.importP4Labels(self.gitStream, missingP4Labels)
self.gitStream.close()
if importProcess.wait() != 0:
@@ -1959,18 +2726,27 @@ class P4Sync(Command, P4UserMap):
self.gitOutput.close()
self.gitError.close()
+ # Cleanup temporary branches created during import
+ if self.tempBranches != []:
+ for branch in self.tempBranches:
+ read_pipe("git update-ref -d %s" % branch)
+ os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
+
return True
class P4Rebase(Command):
def __init__(self):
Command.__init__(self)
- self.options = [ ]
+ self.options = [
+ optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
+ ]
+ self.importLabels = False
self.description = ("Fetches the latest revision from perforce and "
+ "rebases the current work (branch) against it")
- self.verbose = False
def run(self, args):
sync = P4Sync()
+ sync.importLabels = self.importLabels
sync.run([])
return self.rebase()
@@ -2075,6 +2851,10 @@ class P4Clone(P4Sync):
else:
print "Could not detect main branch. No checkout/master branch created."
+ # auto-set this variable if invoked with --use-client-spec
+ if self.useClientSpec_from_options:
+ system("git config --bool git-p4.useclientspec true")
+
return True
class P4Branches(Command):
@@ -2156,15 +2936,16 @@ def main():
args = sys.argv[2:]
- if len(options) > 0:
+ options.append(optparse.make_option("--verbose", dest="verbose", action="store_true"))
+ if cmd.needsGit:
options.append(optparse.make_option("--git-dir", dest="gitdir"))
- parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
- options,
- description = cmd.description,
- formatter = HelpFormatter())
+ parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
+ options,
+ description = cmd.description,
+ formatter = HelpFormatter())
- (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
+ (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
global verbose
verbose = cmd.verbose
if cmd.needsGit:
@@ -2187,6 +2968,7 @@ def main():
if not cmd.run(args):
parser.print_help()
+ sys.exit(2)
if __name__ == '__main__':
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index b24119d..484b2e6 100644
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -57,34 +57,31 @@ error_on_missing_default_upstream () {
op_prep="$3"
example="$4"
branch_name=$(git symbolic-ref -q HEAD)
+ # If there's only one remote, use that in the suggestion
+ remote="<remote>"
+ if test $(git remote | wc -l) = 1
+ then
+ remote=$(git remote)
+ fi
+
if test -z "$branch_name"
then
- echo "You are not currently on a branch, so I cannot use any
-'branch.<branchname>.merge' in your configuration file.
-Please specify which branch you want to $op_type $op_prep on the command
-line and try again (e.g. '$example').
-See git-${cmd}(1) for details."
+ echo "You are not currently on a branch. Please specify which
+branch you want to $op_type $op_prep. See git-${cmd}(1) for details.
+
+ $example
+"
else
- echo "You asked me to $cmd without telling me which branch you
-want to $op_type $op_prep, and 'branch.${branch_name#refs/heads/}.merge' in
-your configuration file does not tell me, either. Please
-specify which branch you want to use on the command line and
-try again (e.g. '$example').
-See git-${cmd}(1) for details.
+ echo "There is no tracking information for the current branch.
+Please specify which branch you want to $op_type $op_prep.
+See git-${cmd}(1) for details
+
+ $example
-If you often $op_type $op_prep the same branch, you may want to
-use something like the following in your configuration file:
- [branch \"${branch_name#refs/heads/}\"]
- remote = <nickname>
- merge = <remote-ref>"
- test rebase = "$op_type" &&
- echo " rebase = true"
- echo "
- [remote \"<nickname>\"]
- url = <url>
- fetch = <refspec>
+If you wish to set tracking information for this branch you can do so with:
-See git-config(1) for details."
+ git branch --set-upstream ${branch_name#refs/heads/} $remote/<branch>
+"
fi
exit 1
}
diff --git a/git-pull.sh b/git-pull.sh
index 28441ac..2a10047 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -9,28 +9,29 @@ LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEA
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
. git-sh-setup
+. git-sh-i18n
set_reflog_action "pull${1+ $*}"
-require_work_tree
+require_work_tree_exists
cd_to_toplevel
die_conflict () {
git diff-index --cached --name-status -r --ignore-submodules HEAD --
if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
- die "Pull is not possible because you have unmerged files.
+ die "$(gettext "Pull is not possible because you have unmerged files.
Please, fix them up in the work tree, and then use 'git add/rm <file>'
-as appropriate to mark resolution, or use 'git commit -a'."
+as appropriate to mark resolution, or use 'git commit -a'.")"
else
- die "Pull is not possible because you have unmerged files."
+ die "$(gettext "Pull is not possible because you have unmerged files.")"
fi
}
die_merge () {
if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
- die "You have not concluded your merge (MERGE_HEAD exists).
-Please, commit your changes before you can merge."
+ die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge.")"
else
- die "You have not concluded your merge (MERGE_HEAD exists)."
+ die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
fi
}
@@ -39,10 +40,14 @@ test -f "$GIT_DIR/MERGE_HEAD" && die_merge
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
log_arg= verbosity= progress= recurse_submodules=
-merge_args=
+merge_args= edit=
curr_branch=$(git symbolic-ref -q HEAD)
curr_branch_short="${curr_branch#refs/heads/}"
rebase=$(git config --bool branch.$curr_branch_short.rebase)
+if test -z "$rebase"
+then
+ rebase=$(git config --bool pull.rebase)
+fi
dry_run=
while :
do
@@ -65,6 +70,10 @@ do
no_commit=--no-commit ;;
--c|--co|--com|--comm|--commi|--commit)
no_commit=--commit ;;
+ -e|--edit)
+ edit=--edit ;;
+ --no-edit)
+ edit=--no-edit ;;
--sq|--squ|--squa|--squas|--squash)
squash=--squash ;;
--no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
@@ -119,7 +128,7 @@ do
--d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
dry_run=--dry-run
;;
- -h|--h|--he|--hel|--help|--help-|--help-a|--help-al|--help-all)
+ -h|--help-all)
usage
;;
*)
@@ -171,7 +180,7 @@ error_on_no_merge_candidates () {
elif [ -z "$curr_branch" -o -z "$upstream" ]; then
. git-parse-remote
error_on_missing_default_upstream "pull" $op_type $op_prep \
- "git pull <repository> <refspec>"
+ "git pull <remote> <branch>"
else
echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
echo "from the remote, but no such ref was fetched."
@@ -185,7 +194,7 @@ test true = "$rebase" && {
# On an unborn branch
if test -f "$GIT_DIR/index"
then
- die "updating an unborn branch with changes added to the index"
+ die "$(gettext "updating an unborn branch with changes added to the index")"
fi
else
require_clean_work_tree "pull with rebase" "Please commit or stash them."
@@ -216,17 +225,17 @@ then
# $orig_head commit, but we are merging into $curr_head.
# First update the working tree to match $curr_head.
- echo >&2 "Warning: fetch updated the current branch head."
- echo >&2 "Warning: fast-forwarding your working tree from"
- echo >&2 "Warning: commit $orig_head."
+ eval_gettextln "Warning: fetch updated the current branch head.
+Warning: fast-forwarding your working tree from
+Warning: commit \$orig_head." >&2
git update-index -q --refresh
git read-tree -u -m "$orig_head" "$curr_head" ||
- die 'Cannot fast-forward your working tree.
+ die "$(eval_gettext "Cannot fast-forward your working tree.
After making sure that you saved anything precious from
-$ git diff '$orig_head'
+$ git diff \$orig_head
output, run
$ git reset --hard
-to recover.'
+to recover.")"
fi
@@ -241,11 +250,11 @@ case "$merge_head" in
?*' '?*)
if test -z "$orig_head"
then
- die "Cannot merge multiple branches into empty head"
+ die "$(gettext "Cannot merge multiple branches into empty head")"
fi
if test true = "$rebase"
then
- die "Cannot rebase onto multiple branches"
+ die "$(gettext "Cannot rebase onto multiple branches")"
fi
;;
esac
@@ -273,7 +282,7 @@ true)
eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
;;
*)
- eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
+ eval="git-merge $diffstat $no_commit $edit $squash $no_ff $ff_only"
eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
eval="$eval \"\$merge_name\" HEAD $merge_head"
;;
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index c815a24..04d8941 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -20,11 +20,20 @@ esac
test -n "$rebase_root" && root_flag=--root
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
- --src-prefix=a/ --dst-prefix=b/ \
- --no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if test -n "$keep_empty"
+then
+ # we have to do this the hard way. git format-patch completely squashes
+ # empty commits and even if it didn't the format doesn't really lend
+ # itself well to recording empty patches. fortunately, cherry-pick
+ # makes this easy
+ git cherry-pick --allow-empty "$revisions"
+else
+ git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+ --src-prefix=a/ --dst-prefix=b/ \
+ --no-renames $root_flag "$revisions" |
+ git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
ret=$?
test 0 != $ret -a -d "$state_dir" && write_basic_state
exit $ret
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index c6ba7c1..5f56672 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -143,6 +143,21 @@ die_with_patch () {
die "$2"
}
+exit_with_patch () {
+ echo "$1" > "$state_dir"/stopped-sha
+ make_patch $1
+ git rev-parse --verify HEAD > "$amend"
+ warn "You can amend the commit now, with"
+ warn
+ warn " git commit --amend"
+ warn
+ warn "Once you are satisfied with your changes, run"
+ warn
+ warn " git rebase --continue"
+ warn
+ exit $2
+}
+
die_abort () {
rm -rf "$state_dir"
die "$1"
@@ -152,6 +167,14 @@ has_action () {
sane_grep '^[^#]' "$1" >/dev/null
}
+is_empty_commit() {
+ tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null ||
+ die "$1: not a commit that can be picked")
+ ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null ||
+ ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+ test "$tree" = "$ptree"
+}
+
# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
# GIT_AUTHOR_DATE exported from the current environment.
do_with_author () {
@@ -161,14 +184,34 @@ do_with_author () {
)
}
+git_sequence_editor () {
+ if test -z "$GIT_SEQUENCE_EDITOR"
+ then
+ GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
+ if [ -z "$GIT_SEQUENCE_EDITOR" ]
+ then
+ GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
+ fi
+ fi
+
+ eval "$GIT_SEQUENCE_EDITOR" '"$@"'
+}
+
pick_one () {
ff=--ff
+
case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
case "$force_rebase" in '') ;; ?*) ff= ;; esac
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+
+ if is_empty_commit "$sha1"
+ then
+ empty_args="--allow-empty"
+ fi
+
test -d "$rewritten" &&
pick_one_preserving_merges "$@" && return
- output git cherry-pick $ff "$@"
+ output git cherry-pick $empty_args $ff "$@"
}
pick_one_preserving_merges () {
@@ -395,7 +438,13 @@ do_next () {
mark_action_done
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
- git commit --amend --no-post-rewrite
+ git commit --amend --no-post-rewrite || {
+ warn "Could not amend commit after successfully picking $sha1... $rest"
+ warn "This is most likely due to an empty commit message, or the pre-commit hook"
+ warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
+ warn "you are able to reword the commit."
+ exit_with_patch $sha1 1
+ }
record_in_rewritten $sha1
;;
edit|e)
@@ -404,19 +453,8 @@ do_next () {
mark_action_done
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
- echo "$sha1" > "$state_dir"/stopped-sha
- make_patch $sha1
- git rev-parse --verify HEAD > "$amend"
warn "Stopped at $sha1... $rest"
- warn "You can amend the commit now, with"
- warn
- warn " git commit --amend"
- warn
- warn "Once you are satisfied with your changes, run"
- warn
- warn " git rebase --continue"
- warn
- exit 0
+ exit_with_patch $sha1 0
;;
squash|s|fixup|f)
case "$command" in
@@ -472,18 +510,24 @@ do_next () {
git rev-parse --verify HEAD > "$state_dir"/stopped-sha
${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
status=$?
+ # Run in subshell because require_clean_work_tree can die.
+ dirty=f
+ (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t
if test "$status" -ne 0
then
warn "Execution failed: $rest"
+ test "$dirty" = f ||
+ warn "and made changes to the index and/or the working tree"
+
warn "You can fix the problem, and then run"
warn
warn " git rebase --continue"
warn
exit "$status"
- fi
- # Run in subshell because require_clean_work_tree can die.
- if ! (require_clean_work_tree "rebase")
+ elif test "$dirty" = t
then
+ warn "Execution succeeded: $rest"
+ warn "but left changes to the index and/or the working tree"
warn "Commit or stash your changes, and then run"
warn
warn " git rebase --continue"
@@ -640,15 +684,52 @@ rearrange_squash () {
rm -f "$1.sq" "$1.rearranged"
}
+# Add commands after a pick or after a squash/fixup serie
+# in the todo list.
+add_exec_commands () {
+ {
+ first=t
+ while read -r insn rest
+ do
+ case $insn in
+ pick)
+ test -n "$first" ||
+ printf "%s" "$cmd"
+ ;;
+ esac
+ printf "%s %s\n" "$insn" "$rest"
+ first=
+ done
+ printf "%s" "$cmd"
+ } <"$1" >"$1.new" &&
+ mv "$1.new" "$1"
+}
+
case "$action" in
continue)
# do we have anything to commit?
- if git diff-index --cached --quiet --ignore-submodules HEAD --
+ if git diff-index --cached --quiet HEAD --
then
: Nothing to commit -- skip this
else
+ if ! test -f "$author_script"
+ then
+ die "You have staged changes in your working tree. If these changes are meant to be
+squashed into the previous commit, run:
+
+ git commit --amend
+
+If they are meant to go into a new commit, run:
+
+ git commit
+
+In both case, once you're done, continue with:
+
+ git rebase --continue
+"
+ fi
. "$author_script" ||
- die "Cannot find the author identity"
+ die "Error trying to find the author identity to amend commit"
current_head=
if test -f "$amend"
then
@@ -735,9 +816,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
sed -n "s/^>//p" |
while read -r shortsha1 rest
do
+
+ if test -z "$keep_empty" && is_empty_commit $shortsha1
+ then
+ comment_out="# "
+ else
+ comment_out=
+ fi
+
if test t != "$preserve_merges"
then
- printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+ printf '%s\n' "${comment_out}pick $shortsha1 $rest" >>"$todo"
else
sha1=$(git rev-parse $shortsha1)
if test -z "$rebase_root"
@@ -756,7 +845,7 @@ do
if test f = "$preserve"
then
touch "$rewritten"/$sha1
- printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+ printf '%s\n' "${comment_out}pick $shortsha1 $rest" >>"$todo"
fi
fi
done
@@ -789,6 +878,8 @@ fi
test -s "$todo" || echo noop >> "$todo"
test -n "$autosquash" && rearrange_squash "$todo"
+test -n "$cmd" && add_exec_commands "$todo"
+
cat >> "$todo" << EOF
# Rebase $shortrevisions onto $shortonto
@@ -801,16 +892,24 @@ cat >> "$todo" << EOF
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
+# These lines can be re-ordered; they are executed from top to bottom.
+#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
EOF
+if test -z "$keep_empty"
+then
+ echo "# Note that empty commits are commented out" >>"$todo"
+fi
+
+
has_action "$todo" ||
die_abort "Nothing to do"
cp "$todo" "$todo".backup
-git_editor "$todo" ||
+git_sequence_editor "$todo" ||
die_abort "Could not execute editor"
has_action "$todo" ||
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
index 26afc75..dc59907 100644
--- a/git-rebase--merge.sh
+++ b/git-rebase--merge.sh
@@ -90,10 +90,13 @@ call_merge () {
finish_rb_merge () {
move_to_original_branch
- git notes copy --for-rewrite=rebase < "$state_dir"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite &&
- test -s "$state_dir"/rewritten; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$state_dir"/rewritten
+ if test -s "$state_dir"/rewritten
+ then
+ git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
+ if test -x "$GIT_DIR"/hooks/post-rewrite
+ then
+ "$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
+ fi
fi
rm -r "$state_dir"
say All done.
diff --git a/git-rebase.sh b/git-rebase.sh
index 38cbee7..6bd8eae 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,8 @@
# Copyright (c) 2005 Junio C Hamano.
#
-USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
+USAGE='[--interactive | -i] [--exec | -x <cmd>] [-v] [--force-rebase | -f]
+ [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
same name. When the --onto option is provided the new branch starts
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -22,7 +23,7 @@ currently checked out branch is used.
Example: git-rebase master~1 topic
- A---B---C topic A'\''--B'\''--C'\'' topic
+ A---B---C topic A'\''--B'\''--C'\'' topic
/ --> /
D---E---F---G master D---E---F---G master
'
@@ -30,8 +31,8 @@ Example: git-rebase master~1 topic
SUBDIRECTORY_OK=Yes
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
-git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>]
-git rebase [-i] [options] --onto <newbase> --root [<branch>]
+git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
+git rebase [-i] [options] [--exec <cmd>] --onto <newbase> --root [<branch>]
git-rebase [-i] --continue | --abort | --skip
--
Available options are
@@ -43,6 +44,8 @@ s,strategy=! use the given merge strategy
no-ff! cherry-pick all commits, even if unchanged
m,merge! use merging strategies to rebase
i,interactive! let the user edit the list of commits to rebase
+x,exec=! add exec lines after each commit of the editable list
+k,keep-empty preserve empty commits during rebase
f,force-rebase! force rebase even if branch is up to date
X,strategy-option=! pass the argument through to the merge strategy
stat! display a diffstat of what changed upstream
@@ -63,7 +66,7 @@ skip! skip current patch and continue
"
. git-sh-setup
set_reflog_action rebase
-require_work_tree
+require_work_tree_exists
cd_to_toplevel
LF='
@@ -75,6 +78,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\".
To check out the original branch and stop rebasing run \"git rebase --abort\".
"
unset onto
+cmd=
strategy=
strategy_opts=
do_merge=
@@ -97,6 +101,7 @@ state_dir=
action=
preserve_merges=
autosquash=
+keep_empty=
test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
read_basic_state () {
@@ -165,6 +170,7 @@ run_specific_rebase () {
if [ "$interactive_rebase" = implied ]; then
GIT_EDITOR=:
export GIT_EDITOR
+ autosquash=
fi
. git-rebase--$type
}
@@ -217,9 +223,17 @@ do
onto="$2"
shift
;;
+ -x)
+ test 2 -le "$#" || usage
+ cmd="${cmd}exec $2${LF}"
+ shift
+ ;;
-i)
interactive_rebase=explicit
;;
+ -k)
+ keep_empty=yes
+ ;;
-p)
preserve_merges=t
test -z "$interactive_rebase" && interactive_rebase=implied
@@ -299,6 +313,12 @@ do
done
test $# -gt 2 && usage
+if test -n "$cmd" &&
+ test "$interactive_rebase" != explicit
+then
+ die "--exec option must be used with --interactive option"
+fi
+
if test -n "$action"
then
test -z "$in_progress" && die "No rebase in progress?"
@@ -380,7 +400,7 @@ then
then
. git-parse-remote
error_on_missing_default_upstream "rebase" "rebase" \
- "against" "git rebase <upstream branch>"
+ "against" "git rebase <branch>"
fi
;;
*) upstream_name="$1"
@@ -418,7 +438,7 @@ case "$onto_name" in
;;
*)
onto=$(git rev-parse --verify "${onto_name}^0") ||
- die "Does not point to a valid commit: $1"
+ die "Does not point to a valid commit: $onto_name"
;;
esac
@@ -441,8 +461,7 @@ case "$#" in
then
head_name="detached HEAD"
else
- echo >&2 "fatal: no such branch: $1"
- usage
+ die "fatal: no such branch: $1"
fi
;;
*)
diff --git a/git-relink.perl b/git-relink.perl
index e136732..f29285c 100755
--- a/git-relink.perl
+++ b/git-relink.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/env perl
+#!/usr/bin/perl
# Copyright 2005, Ryan Anderson <ryan@michonline.com>
# Distribution permitted under the GPL v2, as distributed
# by the Free Software Foundation.
diff --git a/git-remote-testgit.py b/git-remote-testgit.py
index df9d512..5f3ebd2 100644
--- a/git-remote-testgit.py
+++ b/git-remote-testgit.py
@@ -1,5 +1,18 @@
#!/usr/bin/env python
+# This command is a simple remote-helper, that is used both as a
+# testcase for the remote-helper functionality, and as an example to
+# show remote-helper authors one possible implementation.
+#
+# This is a Git <-> Git importer/exporter, that simply uses git
+# fast-import and git fast-export to consume and produce fast-import
+# streams.
+#
+# To understand better the way things work, one can activate debug
+# traces by setting (to any value) the environment variables
+# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages
+# from the transport-helper side, or from this example remote-helper.
+
# hashlib is only available in python >= 2.5
try:
import hashlib
@@ -9,6 +22,7 @@ except ImportError:
_digest = sha.new
import sys
import os
+import time
sys.path.insert(0, os.getenv("GITPYTHONLIB","."))
from git_remote_helpers.util import die, debug, warn
@@ -35,7 +49,7 @@ def get_repo(alias, url):
prefix = 'refs/testgit/%s/' % alias
debug("prefix: '%s'", prefix)
- repo.gitdir = ""
+ repo.gitdir = os.environ["GIT_DIR"]
repo.alias = alias
repo.prefix = prefix
@@ -70,9 +84,19 @@ def do_capabilities(repo, args):
print "import"
print "export"
- print "gitdir"
print "refspec refs/heads/*:%s*" % repo.prefix
+ dirname = repo.get_base_path(repo.gitdir)
+
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ path = os.path.join(dirname, 'testgit.marks')
+
+ print "*export-marks %s" % path
+ if os.path.exists(path):
+ print "*import-marks %s" % path
+
print # end capabilities
@@ -121,8 +145,24 @@ def do_import(repo, args):
if not repo.gitdir:
die("Need gitdir to import")
+ ref = args[0]
+ refs = [ref]
+
+ while True:
+ line = sys.stdin.readline()
+ if line == '\n':
+ break
+ if not line.startswith('import '):
+ die("Expected import line.")
+
+ # strip of leading 'import '
+ ref = line[7:].strip()
+ refs.append(ref)
+
repo = update_local_repo(repo)
- repo.exporter.export_repo(repo.gitdir)
+ repo.exporter.export_repo(repo.gitdir, refs)
+
+ print "done"
def do_export(repo, args):
@@ -132,32 +172,15 @@ def do_export(repo, args):
if not repo.gitdir:
die("Need gitdir to export")
- dirname = repo.get_base_path(repo.gitdir)
-
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
- path = os.path.join(dirname, 'testgit.marks')
- print path
- if os.path.exists(path):
- print path
- else:
- print ""
- sys.stdout.flush()
-
update_local_repo(repo)
- repo.importer.do_import(repo.gitdir)
- repo.non_local.push(repo.gitdir)
+ changed = repo.importer.do_import(repo.gitdir)
+ if not repo.local:
+ repo.non_local.push(repo.gitdir)
-def do_gitdir(repo, args):
- """Stores the location of the gitdir.
- """
-
- if not args:
- die("gitdir needs an argument")
-
- repo.gitdir = ' '.join(args)
+ for ref in changed:
+ print "ok %s" % ref
+ print
COMMANDS = {
@@ -165,7 +188,6 @@ COMMANDS = {
'list': do_list,
'import': do_import,
'export': do_export,
- 'gitdir': do_gitdir,
}
@@ -183,6 +205,11 @@ def read_one_line(repo):
"""Reads and processes one command.
"""
+ sleepy = os.environ.get("GIT_REMOTE_TESTGIT_SLEEPY")
+ if sleepy:
+ debug("Sleeping %d sec before readline" % int(sleepy))
+ time.sleep(int(sleepy))
+
line = sys.stdin.readline()
cmdline = line
@@ -237,6 +264,7 @@ def main(args):
more = True
+ sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0)
while (more):
more = read_one_line(repo)
diff --git a/git-repack.sh b/git-repack.sh
index 624feec..7579331 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -15,6 +15,7 @@ F pass --no-reuse-object to git-pack-objects
n do not run git-update-server-info
q,quiet be quiet
l pass --local to git-pack-objects
+unpack-unreachable= with -A, do not loosen objects older than this
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
@@ -33,6 +34,8 @@ do
-a) all_into_one=t ;;
-A) all_into_one=t
unpack_unreachable=--unpack-unreachable ;;
+ --unpack-unreachable)
+ unpack_unreachable="--unpack-unreachable=$2"; shift ;;
-d) remove_redundant=t ;;
-q) GIT_QUIET=t ;;
-f) no_reuse=--no-reuse-delta ;;
@@ -76,7 +79,12 @@ case ",$all_into_one," in
if test -n "$existing" -a -n "$unpack_unreachable" -a \
-n "$remove_redundant"
then
- args="$args $unpack_unreachable"
+ # This may have arbitrary user arguments, so we
+ # have to protect it against whitespace splitting
+ # when it gets run as "pack-objects $args" later.
+ # Fortunately, we know it's an approxidate, so we
+ # can just use dots instead.
+ args="$args $(echo "$unpack_unreachable" | tr ' ' .)"
fi
fi
;;
diff --git a/git-request-pull.sh b/git-request-pull.sh
index fc080cc..d566015 100755
--- a/git-request-pull.sh
+++ b/git-request-pull.sh
@@ -35,44 +35,118 @@ do
shift
done
-base=$1
-url=$2
-head=${3-HEAD}
+base=$1 url=$2 head=${3-HEAD} status=0 branch_name=
-[ "$base" ] || usage
-[ "$url" ] || usage
+headref=$(git symbolic-ref -q "$head")
+if git show-ref -q --verify "$headref"
+then
+ branch_name=${headref#refs/heads/}
+ if test "z$branch_name" = "z$headref" ||
+ ! git config "branch.$branch_name.description" >/dev/null
+ then
+ branch_name=
+ fi
+fi
+
+tag_name=$(git describe --exact "$head^0" 2>/dev/null)
-baserev=`git rev-parse --verify "$base"^0` &&
-headrev=`git rev-parse --verify "$head"^0` || exit
+test -n "$base" && test -n "$url" || usage
+baserev=$(git rev-parse --verify "$base"^0) &&
+headrev=$(git rev-parse --verify "$head"^0) || exit
-merge_base=`git merge-base $baserev $headrev` ||
+merge_base=$(git merge-base $baserev $headrev) ||
die "fatal: No commits in common between $base and $head"
-branch=$(git ls-remote "$url" \
- | sed -n -e "/^$headrev refs.heads./{
- s/^.* refs.heads.//
- p
- q
- }")
+# $head is the token given from the command line, and $tag_name, if
+# exists, is the tag we are going to show the commit information for.
+# If that tag exists at the remote and it points at the commit, use it.
+# Otherwise, if a branch with the same name as $head exists at the remote
+# and their values match, use that instead.
+#
+# Otherwise find a random ref that matches $headrev.
+find_matching_ref='
+ sub abbr {
+ my $ref = shift;
+ if ($ref =~ s|^refs/heads/|| || $ref =~ s|^refs/tags/|tags/|) {
+ return $ref;
+ } else {
+ return $ref;
+ }
+ }
+
+ my ($tagged, $branch, $found);
+ while (<STDIN>) {
+ my ($sha1, $ref, $deref) = /^(\S+)\s+(\S+?)(\^\{\})?$/;
+ next unless ($sha1 eq $ARGV[1]);
+ $found = abbr($ref);
+ if ($deref && $ref eq "tags/$ARGV[2]") {
+ $tagged = $found;
+ last;
+ }
+ if ($ref =~ m|/\Q$ARGV[0]\E$|) {
+ $exact = $found;
+ }
+ }
+ if ($tagged) {
+ print "$tagged\n";
+ } elsif ($exact) {
+ print "$exact\n";
+ } elsif ($found) {
+ print "$found\n";
+ }
+'
+
+ref=$(git ls-remote "$url" | perl -e "$find_matching_ref" "$head" "$headrev" "$tag_name")
+
url=$(git ls-remote --get-url "$url")
-if [ -z "$branch" ]; then
- echo "warn: No branch of $url is at:" >&2
- git log --max-count=1 --pretty='tformat:warn: %h: %s' $headrev >&2
- echo "warn: Are you sure you pushed $head there?" >&2
- echo >&2
- echo >&2
- branch=..BRANCH.NOT.VERIFIED..
- status=1
-fi
git show -s --format='The following changes since commit %H:
%s (%ci)
-are available in the git repository at:' $baserev &&
-echo " $url $branch" &&
-echo &&
+are available in the git repository at:
+' $merge_base &&
+echo " $url${ref+ $ref}" &&
+git show -s --format='
+for you to fetch changes up to %H:
+
+ %s (%ci)
+
+----------------------------------------------------------------' $headrev &&
+
+if test -n "$branch_name"
+then
+ echo "(from the branch description for $branch_name local branch)"
+ echo
+ git config "branch.$branch_name.description"
+fi &&
+
+if test -n "$tag_name"
+then
+ if test -z "$ref" || test "$ref" != "tags/$tag_name"
+ then
+ echo >&2 "warn: You locally have $tag_name but it does not (yet)"
+ echo >&2 "warn: appear to be at $url"
+ echo >&2 "warn: Do you want to push it there, perhaps?"
+ fi
+ git cat-file tag "$tag_name" |
+ sed -n -e '1,/^$/d' -e '/^-----BEGIN PGP /q' -e p
+ echo
+fi &&
+
+if test -n "$branch_name" || test -n "$tag_name"
+then
+ echo "----------------------------------------------------------------"
+fi &&
git shortlog ^$baserev $headrev &&
-git diff -M --stat --summary $patch $merge_base..$headrev || exit
+git diff -M --stat --summary $patch $merge_base..$headrev || status=1
+
+if test -z "$ref"
+then
+ echo "warn: No branch of $url is at:" >&2
+ git show -s --format='warn: %h: %s' $headrev >&2
+ echo "warn: Are you sure you pushed '$head' there?" >&2
+ status=1
+fi
exit $status
diff --git a/git-send-email.perl b/git-send-email.perl
index 98ab33a..ef30c55 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -210,6 +210,7 @@ my %config_bool_settings = (
"signedoffbycc" => [\$signed_off_by_cc, undef],
"signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated
"validate" => [\$validate, 1],
+ "multiedit" => [\$multiedit, undef]
);
my %config_settings = (
@@ -225,15 +226,17 @@ my %config_settings = (
"cccmd" => \$cc_cmd,
"aliasfiletype" => \$aliasfiletype,
"bcc" => \@bcclist,
- "aliasesfile" => \@alias_files,
"suppresscc" => \@suppress_cc,
"envelopesender" => \$envelope_sender,
- "multiedit" => \$multiedit,
"confirm" => \$confirm,
"from" => \$sender,
"assume8bitencoding" => \$auto_8bit_encoding,
);
+my %config_path_settings = (
+ "aliasesfile" => \@alias_files,
+);
+
# Help users prepare for 1.7.0
sub chain_reply_to {
if (defined $chain_reply_to &&
@@ -275,7 +278,9 @@ $SIG{INT} = \&signal_handler;
# Begin by accumulating all the variables (defined above), that we will end up
# needing, first, from the command line:
-my $rc = GetOptions("sender|from=s" => \$sender,
+my $help;
+my $rc = GetOptions("h" => \$help,
+ "sender|from=s" => \$sender,
"in-reply-to=s" => \$initial_reply_to,
"subject=s" => \$initial_subject,
"to=s" => \@initial_to,
@@ -313,6 +318,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
"force" => \$force,
);
+usage() if $help;
unless ($rc) {
usage();
}
@@ -330,6 +336,19 @@ sub read_config {
$$target = Git::config_bool(@repo, "$prefix.$setting") unless (defined $$target);
}
+ foreach my $setting (keys %config_path_settings) {
+ my $target = $config_path_settings{$setting};
+ if (ref($target) eq "ARRAY") {
+ unless (@$target) {
+ my @values = Git::config_path(@repo, "$prefix.$setting");
+ @$target = @values if (@values && defined $values[0]);
+ }
+ }
+ else {
+ $$target = Git::config_path(@repo, "$prefix.$setting") unless (defined $$target);
+ }
+ }
+
foreach my $setting (keys %config_settings) {
my $target = $config_settings{$setting};
next if $setting eq "to" and defined $no_to;
@@ -1079,7 +1098,7 @@ X-Mailer: git-send-email $gitversion
$smtp_encryption = '';
# Send EHLO again to receive fresh
# supported commands
- $smtp->hello();
+ $smtp->hello($smtp_domain);
} else {
die "Server does not support STARTTLS! ".$smtp->message;
}
@@ -1095,6 +1114,12 @@ X-Mailer: git-send-email $gitversion
}
if (defined $smtp_authuser) {
+ # Workaround AUTH PLAIN/LOGIN interaction defect
+ # with Authen::SASL::Cyrus
+ eval {
+ require Authen::SASL;
+ Authen::SASL->import(qw(Perl));
+ };
if (!defined $smtp_authpass) {
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
index 32ca59d..6a27f68 100644
--- a/git-sh-i18n.sh
+++ b/git-sh-i18n.sh
@@ -2,22 +2,62 @@
#
# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
#
-# This is a skeleton no-op implementation of gettext for Git. It'll be
-# replaced by something that uses gettext.sh in a future patch series.
+# This is Git's interface to gettext.sh. See po/README for usage
+# instructions.
-if test -z "$GIT_GETTEXT_POISON"
+# Export the TEXTDOMAIN* data that we need for Git
+TEXTDOMAIN=git
+export TEXTDOMAIN
+if test -z "$GIT_TEXTDOMAINDIR"
then
- gettext () {
- printf "%s" "$1"
- }
+ TEXTDOMAINDIR="@@LOCALEDIR@@"
+else
+ TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+fi
+export TEXTDOMAINDIR
+# First decide what scheme to use...
+GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough
+if test -n "@@USE_GETTEXT_SCHEME@@"
+then
+ GIT_INTERNAL_GETTEXT_SH_SCHEME="@@USE_GETTEXT_SCHEME@@"
+elif test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+then
+ : no probing necessary
+elif test -n "$GIT_GETTEXT_POISON"
+then
+ GIT_INTERNAL_GETTEXT_SH_SCHEME=poison
+elif type gettext.sh >/dev/null 2>&1
+then
+ # GNU libintl's gettext.sh
+ GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
+elif test "$(gettext -h 2>&1)" = "-h"
+then
+ # gettext binary exists but no gettext.sh. likely to be a gettext
+ # binary on a Solaris or something that is not GNU libintl and
+ # lack eval_gettext.
+ GIT_INTERNAL_GETTEXT_SH_SCHEME=gettext_without_eval_gettext
+fi
+export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+# ... and then follow that decision.
+case "$GIT_INTERNAL_GETTEXT_SH_SCHEME" in
+gnu)
+ # Use libintl's gettext.sh, or fall back to English if we can't.
+ . gettext.sh
+ ;;
+gettext_without_eval_gettext)
+ # Solaris has a gettext(1) but no eval_gettext(1)
eval_gettext () {
- printf "%s" "$1" | (
+ gettext "$1" | (
export PATH $(git sh-i18n--envsubst --variables "$1");
git sh-i18n--envsubst "$1"
)
}
-else
+ ;;
+poison)
+ # Emit garbage so that tests that incorrectly rely on translatable
+ # strings will fail.
gettext () {
printf "%s" "# GETTEXT POISON #"
}
@@ -25,5 +65,28 @@ else
eval_gettext () {
printf "%s" "# GETTEXT POISON #"
}
-fi
+ ;;
+*)
+ gettext () {
+ printf "%s" "$1"
+ }
+
+ eval_gettext () {
+ printf "%s" "$1" | (
+ export PATH $(git sh-i18n--envsubst --variables "$1");
+ git sh-i18n--envsubst "$1"
+ )
+ }
+ ;;
+esac
+
+# Git-specific wrapper functions
+gettextln () {
+ gettext "$1"
+ echo
+}
+eval_gettextln () {
+ eval_gettext "$1"
+ echo
+}
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 94e26ed..770a86e 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -39,9 +39,15 @@ git_broken_path_fix () {
# @@BROKEN_PATH_FIX@@
-die() {
- echo >&2 "$@"
- exit 1
+die () {
+ die_with_status 1 "$@"
+}
+
+die_with_status () {
+ status=$1
+ shift
+ echo >&2 "$*"
+ exit "$status"
}
GIT_QUIET=
@@ -84,7 +90,7 @@ $LONG_USAGE"
fi
case "$1" in
- -h|--h|--he|--hel|--help)
+ -h)
echo "$LONG_USAGE"
exit
esac
@@ -194,7 +200,7 @@ get_author_ident_from_commit () {
s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
g
- s/^author [^<]* <[^>]*> \(.*\)$/\1/
+ s/^author [^<]* <[^>]*> \(.*\)$/@\1/
s/.*/GIT_AUTHOR_DATE='\''&'\''/p
q
@@ -212,27 +218,8 @@ clear_local_git_env() {
unset $(git rev-parse --local-env-vars)
}
-# 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
- 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
-# Fix some commands on Windows
+# Platform specific tweaks to work around some commands
case $(uname -s) in
*MINGW*)
# Windows has its own (incompatible) sort and find
@@ -242,6 +229,10 @@ case $(uname -s) in
find () {
/usr/bin/find "$@"
}
+ # git sees Windows-style pwd
+ pwd () {
+ builtin pwd -W
+ }
is_absolute_path () {
case "$1" in
[/\\]* | [A-Za-z]:*)
@@ -259,3 +250,23 @@ case $(uname -s) in
return 1
}
esac
+
+# 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
+ 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
diff --git a/git-stash.sh b/git-stash.sh
index 0a94036..4e2c7f8 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -7,13 +7,15 @@ USAGE="list [<options>]
or: $dashless drop [-q|--quiet] [<stash>]
or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
or: $dashless branch <branchname> [<stash>]
- or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
+ or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+ [-u|--include-untracked] [-a|--all] [<message>]]
or: $dashless clear"
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
START_DIR=`pwd`
. git-sh-setup
+. git-sh-i18n
require_work_tree
cd_to_toplevel
@@ -33,13 +35,20 @@ fi
no_changes () {
git diff-index --quiet --cached HEAD --ignore-submodules -- &&
- git diff-files --quiet --ignore-submodules
+ git diff-files --quiet --ignore-submodules &&
+ (test -z "$untracked" || test -z "$(untracked_files)")
+}
+
+untracked_files () {
+ excl_opt=--exclude-standard
+ test "$untracked" = "all" && excl_opt=
+ git ls-files -o -z $excl_opt
}
clear_stash () {
if test $# != 0
then
- die "git stash clear with parameters is unimplemented"
+ die "$(gettext "git stash clear with parameters is unimplemented")"
fi
if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
then
@@ -49,6 +58,7 @@ clear_stash () {
create_stash () {
stash_msg="$1"
+ untracked="$2"
git update-index -q --refresh
if no_changes
@@ -61,7 +71,7 @@ create_stash () {
then
head=$(git rev-list --oneline -n 1 HEAD --)
else
- die "You do not have the initial commit yet"
+ die "$(gettext "You do not have the initial commit yet")"
fi
if branch=$(git symbolic-ref -q HEAD)
@@ -76,7 +86,26 @@ create_stash () {
i_tree=$(git write-tree) &&
i_commit=$(printf 'index on %s\n' "$msg" |
git commit-tree $i_tree -p $b_commit) ||
- die "Cannot save the current index state"
+ die "$(gettext "Cannot save the current index state")"
+
+ if test -n "$untracked"
+ then
+ # Untracked files are stored by themselves in a parentless commit, for
+ # ease of unpacking later.
+ u_commit=$(
+ untracked_files | (
+ export GIT_INDEX_FILE="$TMPindex"
+ rm -f "$TMPindex" &&
+ git update-index -z --add --remove --stdin &&
+ u_tree=$(git write-tree) &&
+ printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree &&
+ rm -f "$TMPindex"
+ ) ) || die "Cannot save the untracked files"
+
+ untracked_commit_option="-p $u_commit";
+ else
+ untracked_commit_option=
+ fi
if test -z "$patch_mode"
then
@@ -86,11 +115,12 @@ create_stash () {
git read-tree --index-output="$TMPindex" -m $i_tree &&
GIT_INDEX_FILE="$TMPindex" &&
export GIT_INDEX_FILE &&
- git diff --name-only -z HEAD | git update-index -z --add --remove --stdin &&
+ git diff --name-only -z HEAD -- >"$TMP-stagenames" &&
+ git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
git write-tree &&
rm -f "$TMPindex"
) ) ||
- die "Cannot save the current worktree state"
+ die "$(gettext "Cannot save the current worktree state")"
else
@@ -103,14 +133,14 @@ create_stash () {
# state of the working tree
w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
- die "Cannot save the current worktree state"
+ die "$(gettext "Cannot save the current worktree state")"
- git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
+ git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
test -s "$TMP-patch" ||
- die "No changes selected"
+ die "$(gettext "No changes selected")"
rm -f "$TMP-index" ||
- die "Cannot remove temporary index (can't happen)"
+ die "$(gettext "Cannot remove temporary index (can't happen)")"
fi
@@ -122,13 +152,14 @@ create_stash () {
stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
fi
w_commit=$(printf '%s\n' "$stash_msg" |
- git commit-tree $w_tree -p $b_commit -p $i_commit) ||
- die "Cannot record working tree state"
+ git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
+ die "$(gettext "Cannot record working tree state")"
}
save_stash () {
keep_index=
patch_mode=
+ untracked=
while test $# != 0
do
case "$1" in
@@ -146,13 +177,30 @@ save_stash () {
-q|--quiet)
GIT_QUIET=t
;;
+ -u|--include-untracked)
+ untracked=untracked
+ ;;
+ -a|--all)
+ untracked=all
+ ;;
--)
shift
break
;;
-*)
- echo "error: unknown option for 'stash save': $1"
- echo " To provide a message, use git stash save -- '$1'"
+ option="$1"
+ # TRANSLATORS: $option is an invalid option, like
+ # `--blah-blah'. The 7 spaces at the beginning of the
+ # second line correspond to "error: ". So you should line
+ # up the second line with however many characters the
+ # translation of "error: " takes in your language. E.g. in
+ # English this is:
+ #
+ # $ git stash save --blah-blah 2>&1 | head -n 2
+ # error: unknown option for 'stash save': --blah-blah
+ # To provide a message, use git stash save -- '--blah-blah'
+ eval_gettextln "error: unknown option for 'stash save': \$option
+ To provide a message, use git stash save -- '\$option'"
usage
;;
*)
@@ -162,29 +210,39 @@ save_stash () {
shift
done
+ if test -n "$patch_mode" && test -n "$untracked"
+ then
+ die "Can't use --patch and --include-untracked or --all at the same time"
+ fi
+
stash_msg="$*"
git update-index -q --refresh
if no_changes
then
- say 'No local changes to save'
+ say "$(gettext "No local changes to save")"
exit 0
fi
test -f "$GIT_DIR/logs/$ref_stash" ||
- clear_stash || die "Cannot initialize stash"
+ clear_stash || die "$(gettext "Cannot initialize stash")"
- create_stash "$stash_msg"
+ create_stash "$stash_msg" $untracked
# 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"
+ die "$(gettext "Cannot save the current status")"
say Saved working directory and index state "$stash_msg"
if test -z "$patch_mode"
then
git reset --hard ${GIT_QUIET:+-q}
+ test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
+ if test -n "$untracked"
+ then
+ git clean --force --quiet -d $CLEAN_X_OPTION
+ fi
if test "$keep_index" = "t" && test -n $i_tree
then
@@ -192,7 +250,7 @@ save_stash () {
fi
else
git apply -R < "$TMP-patch" ||
- die "Cannot remove worktree changes"
+ die "$(gettext "Cannot remove worktree changes")"
if test "$keep_index" != "t"
then
@@ -234,9 +292,11 @@ show_stash () {
# w_commit is set to the commit containing the working tree
# b_commit is set to the base commit
# i_commit is set to the commit containing the index tree
+# u_commit is set to the commit containing the untracked files tree
# w_tree is set to the working tree
# b_tree is set to the base tree
# i_tree is set to the index tree
+# u_tree is set to the untracked files tree
#
# GIT_QUIET is set to t if -q is specified
# INDEX_OPTION is set to --index if --index is specified.
@@ -261,9 +321,11 @@ parse_flags_and_rev()
w_commit=
b_commit=
i_commit=
+ u_commit=
w_tree=
b_tree=
i_tree=
+ u_tree=
REV=$(git rev-parse --no-flags --symbolic "$@") || exit 1
@@ -287,18 +349,21 @@ parse_flags_and_rev()
case $# in
0)
- have_stash || die "No stash found."
+ have_stash || die "$(gettext "No stash found.")"
set -- ${ref_stash}@{0}
;;
1)
:
;;
*)
- die "Too many revisions specified: $REV"
+ die "$(eval_gettext "Too many revisions specified: \$REV")"
;;
esac
- REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || die "$1 is not valid reference"
+ REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || {
+ reference="$1"
+ die "$(eval_gettext "\$reference is not valid reference")"
+ }
i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) &&
set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) &&
@@ -311,6 +376,9 @@ parse_flags_and_rev()
IS_STASH_LIKE=t &&
test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
IS_STASH_REF=t
+
+ u_commit=$(git rev-parse --quiet --verify $REV^3 2>/dev/null) &&
+ u_tree=$(git rev-parse $REV^3: 2>/dev/null)
}
is_stash_like()
@@ -320,7 +388,10 @@ is_stash_like()
}
assert_stash_like() {
- is_stash_like "$@" || die "'$*' is not a stash-like commit"
+ is_stash_like "$@" || {
+ args="$*"
+ die "$(eval_gettext "'\$args' is not a stash-like commit")"
+ }
}
is_stash_ref() {
@@ -328,18 +399,21 @@ is_stash_ref() {
}
assert_stash_ref() {
- is_stash_ref "$@" || die "'$*' is not a stash reference"
+ is_stash_ref "$@" || {
+ args="$*"
+ die "$(eval_gettext "'\$args' is not a stash reference")"
+ }
}
apply_stash () {
assert_stash_like "$@"
- git update-index -q --refresh || die 'unable to refresh index'
+ git update-index -q --refresh || die "$(gettext "unable to refresh index")"
# current index state
c_tree=$(git write-tree) ||
- die 'Cannot apply a stash in the middle of a merge'
+ die "$(gettext "Cannot apply a stash in the middle of a merge")"
unstashed_index_tree=
if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
@@ -347,12 +421,20 @@ apply_stash () {
then
git diff-tree --binary $s^2^..$s^2 | git apply --cached
test $? -ne 0 &&
- die 'Conflicts in index. Try without --index.'
+ die "$(gettext "Conflicts in index. Try without --index.")"
unstashed_index_tree=$(git write-tree) ||
- die 'Could not save index tree'
+ die "$(gettext "Could not save index tree")"
git reset
fi
+ if test -n "$u_tree"
+ then
+ GIT_INDEX_FILE="$TMPindex" git-read-tree "$u_tree" &&
+ GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
+ rm -f "$TMPindex" ||
+ die 'Could not restore untracked files from stash'
+ fi
+
eval "
GITHEAD_$w_tree='Stashed changes' &&
GITHEAD_$c_tree='Updated upstream' &&
@@ -375,7 +457,7 @@ apply_stash () {
git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
git read-tree --reset $c_tree &&
git update-index --add --stdin <"$a" ||
- die "Cannot unstage modified files"
+ die "$(gettext "Cannot unstage modified files")"
rm -f "$a"
fi
squelch=
@@ -389,7 +471,7 @@ apply_stash () {
status=$?
if test -n "$INDEX_OPTION"
then
- echo >&2 'Index was not unstashed.'
+ gettextln "Index was not unstashed." >&2
fi
exit $status
fi
@@ -406,14 +488,15 @@ drop_stash () {
assert_stash_ref "$@"
git reflog delete --updateref --rewrite "${REV}" &&
- say "Dropped ${REV} ($s)" || die "${REV}: Could not drop stash entry"
+ say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
+ die "$(eval_gettext "\${REV}: Could not drop stash entry")"
# clear_stash if we just dropped the last stash entry
- git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
+ git rev-parse --verify "$ref_stash@{0}" >/dev/null 2>&1 || clear_stash
}
apply_to_branch () {
- test -n "$1" || die 'No branch name specified'
+ test -n "$1" || die "$(gettext "No branch name specified")"
branch=$1
shift 1
@@ -484,7 +567,7 @@ branch)
case $# in
0)
save_stash &&
- say '(To restore them type "git stash apply")'
+ say "$(gettext "(To restore them type \"git stash apply\")")"
;;
*)
usage
diff --git a/git-submodule.sh b/git-submodule.sh
index c94218b..5629d87 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# git-submodules.sh: add, init, update or list git submodules
+# git-submodule.sh: add, init, update or list git submodules
#
# Copyright (c) 2007 Lars Hjemli
@@ -14,6 +14,7 @@ USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <r
or: $dashless [--quiet] sync [--] [<path>...]"
OPTIONS_SPEC=
. git-sh-setup
+. git-sh-i18n
. git-parse-remote
require_work_tree
@@ -29,7 +30,22 @@ nofetch=
update=
prefix=
-# Resolve relative url by appending to parent's url
+# The function takes at most 2 arguments. The first argument is the
+# URL that navigates to the submodule origin repo. When relative, this URL
+# is relative to the superproject origin URL repo. The second up_path
+# argument, if specified, is the relative path that navigates
+# from the submodule working tree to the superproject working tree.
+#
+# The output of the function is the origin URL of the submodule.
+#
+# The output will either be an absolute URL or filesystem path (if the
+# superproject origin URL is an absolute URL or filesystem path,
+# respectively) or a relative file system path (if the superproject
+# origin URL is a relative file system path).
+#
+# When the output is a relative file system path, the path is either
+# relative to the submodule working tree, if up_path is specified, or to
+# the superproject working tree otherwise.
resolve_relative_url ()
{
remote=$(get_default_remote)
@@ -38,6 +54,21 @@ resolve_relative_url ()
url="$1"
remoteurl=${remoteurl%/}
sep=/
+ up_path="$2"
+
+ case "$remoteurl" in
+ *:*|/*)
+ is_relative=
+ ;;
+ ./*|../*)
+ is_relative=t
+ ;;
+ *)
+ is_relative=t
+ remoteurl="./$remoteurl"
+ ;;
+ esac
+
while test -n "$url"
do
case "$url" in
@@ -52,7 +83,12 @@ resolve_relative_url ()
sep=:
;;
*)
- die "cannot strip one component off url '$remoteurl'"
+ if test -z "$is_relative" || test "." = "$remoteurl"
+ then
+ die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
+ else
+ remoteurl=.
+ fi
;;
esac
;;
@@ -63,7 +99,8 @@ resolve_relative_url ()
break;;
esac
done
- echo "$remoteurl$sep${url%/}"
+ remoteurl="$remoteurl$sep${url%/}"
+ echo "${is_relative:+${up_path}}${remoteurl#./}"
}
#
@@ -100,12 +137,13 @@ module_list()
module_name()
{
# Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
+ sm_path="$1"
re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
- test -z "$name" &&
- die "No submodule mapping found in .gitmodules for path '$path'"
- echo "$name"
+ test -z "$name" &&
+ die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")"
+ echo "$name"
}
#
@@ -118,7 +156,7 @@ module_name()
#
module_clone()
{
- path=$1
+ sm_path=$1
url=$2
reference="$3"
quiet=
@@ -127,13 +165,51 @@ module_clone()
quiet=-q
fi
- if test -n "$reference"
+ gitdir=
+ gitdir_base=
+ name=$(module_name "$sm_path" 2>/dev/null)
+ test -n "$name" || name="$sm_path"
+ base_name=$(dirname "$name")
+
+ gitdir=$(git rev-parse --git-dir)
+ gitdir_base="$gitdir/modules/$base_name"
+ gitdir="$gitdir/modules/$name"
+
+ if test -d "$gitdir"
then
- git-clone $quiet "$reference" -n "$url" "$path"
+ mkdir -p "$sm_path"
+ rm -f "$gitdir/index"
else
- git-clone $quiet -n "$url" "$path"
- fi ||
- die "Clone of '$url' into submodule path '$path' failed"
+ mkdir -p "$gitdir_base"
+ git clone $quiet -n ${reference:+"$reference"} \
+ --separate-git-dir "$gitdir" "$url" "$sm_path" ||
+ die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
+ fi
+
+ a=$(cd "$gitdir" && pwd)/
+ b=$(cd "$sm_path" && pwd)/
+ # normalize Windows-style absolute paths to POSIX-style absolute paths
+ case $a in [a-zA-Z]:/*) a=/${a%%:*}${a#*:} ;; esac
+ case $b in [a-zA-Z]:/*) b=/${b%%:*}${b#*:} ;; esac
+ # Remove all common leading directories after a sanity check
+ if test "${a#$b}" != "$a" || test "${b#$a}" != "$b"; then
+ die "$(eval_gettext "Gitdir '\$a' is part of the submodule path '\$b' or vice versa")"
+ fi
+ while test "${a%%/*}" = "${b%%/*}"
+ do
+ a=${a#*/}
+ b=${b#*/}
+ done
+ # Now chop off the trailing '/'s that were added in the beginning
+ a=${a%/}
+ b=${b%/}
+
+ # Turn each leading "*/" component into "../"
+ rel=$(echo $b | sed -e 's|[^/][^/]*|..|g')
+ echo "gitdir: $rel/$a" >"$sm_path/.git"
+
+ rel=$(echo $a | sed -e 's|[^/][^/]*|..|g')
+ (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
}
#
@@ -184,14 +260,14 @@ cmd_add()
done
repo=$1
- path=$2
+ sm_path=$2
- if test -z "$path"; then
- path=$(echo "$repo" |
+ if test -z "$sm_path"; then
+ sm_path=$(echo "$repo" |
sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
fi
- if test -z "$repo" -o -z "$path"; then
+ if test -z "$repo" -o -z "$sm_path"; then
usage
fi
@@ -206,13 +282,13 @@ cmd_add()
realrepo=$repo
;;
*)
- die "repo URL: '$repo' must be absolute or begin with ./|../"
+ die "$(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")"
;;
esac
# normalize path:
# multiple //; leading ./; /./; /../; trailing /
- path=$(printf '%s/\n' "$path" |
+ sm_path=$(printf '%s/\n' "$sm_path" |
sed -e '
s|//*|/|g
s|^\(\./\)*||
@@ -222,49 +298,49 @@ cmd_add()
tstart
s|/*$||
')
- git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
- die "'$path' already exists in the index"
+ git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
+ die "$(eval_gettext "'\$sm_path' already exists in the index")"
- if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1
+ if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1
then
- echo >&2 "The following path is ignored by one of your .gitignore files:" &&
- echo >&2 $path &&
- echo >&2 "Use -f if you really want to add it."
+ eval_gettextln "The following path is ignored by one of your .gitignore files:
+\$sm_path
+Use -f if you really want to add it." >&2
exit 1
fi
# perhaps the path exists and is already a git repo, else clone it
- if test -e "$path"
+ if test -e "$sm_path"
then
- if test -d "$path"/.git -o -f "$path"/.git
+ if test -d "$sm_path"/.git -o -f "$sm_path"/.git
then
- echo "Adding existing repo at '$path' to the index"
+ eval_gettextln "Adding existing repo at '\$sm_path' to the index"
else
- die "'$path' already exists and is not a valid git repo"
+ die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")"
fi
else
- module_clone "$path" "$realrepo" "$reference" || exit
+ module_clone "$sm_path" "$realrepo" "$reference" || exit
(
clear_local_git_env
- cd "$path" &&
+ cd "$sm_path" &&
# ash fails to wordsplit ${branch:+-b "$branch"...}
case "$branch" in
'') git checkout -f -q ;;
?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
esac
- ) || die "Unable to checkout submodule '$path'"
+ ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")"
fi
- git config submodule."$path".url "$realrepo"
+ git config submodule."$sm_path".url "$realrepo"
- git add $force "$path" ||
- die "Failed to add submodule '$path'"
+ git add $force "$sm_path" ||
+ die "$(eval_gettext "Failed to add submodule '\$sm_path'")"
- git config -f .gitmodules submodule."$path".path "$path" &&
- git config -f .gitmodules submodule."$path".url "$repo" &&
+ git config -f .gitmodules submodule."$sm_path".path "$sm_path" &&
+ git config -f .gitmodules submodule."$sm_path".url "$repo" &&
git add --force .gitmodules ||
- die "Failed to register submodule '$path'"
+ die "$(eval_gettext "Failed to register submodule '\$sm_path'")"
}
#
@@ -302,23 +378,25 @@ cmd_foreach()
exec 3<&0
module_list |
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
- if test -e "$path"/.git
+ if test -e "$sm_path"/.git
then
- say "Entering '$prefix$path'"
- name=$(module_name "$path")
+ say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
+ name=$(module_name "$sm_path")
(
- prefix="$prefix$path/"
+ prefix="$prefix$sm_path/"
clear_local_git_env
- cd "$path" &&
+ # we make $path available to scripts ...
+ path=$sm_path
+ cd "$sm_path" &&
eval "$@" &&
if test -n "$recursive"
then
cmd_foreach "--recursive" "$@"
fi
) <&3 3<&- ||
- die "Stopping at '$path'; script returned non-zero status."
+ die "$(eval_gettext "Stopping at '\$sm_path'; script returned non-zero status.")"
fi
done
}
@@ -352,15 +430,16 @@ cmd_init()
done
module_list "$@" |
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
- # Skip already registered paths
- name=$(module_name "$path") || exit
+ name=$(module_name "$sm_path") || exit
+
+ # Copy url setting when it is not set yet
if test -z "$(git config "submodule.$name.url")"
then
url=$(git config -f .gitmodules submodule."$name".url)
test -z "$url" &&
- die "No url found for submodule path '$path' in .gitmodules"
+ die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")"
# Possibly a url relative to parent
case "$url" in
@@ -369,7 +448,9 @@ cmd_init()
;;
esac
git config submodule."$name".url "$url" ||
- die "Failed to register url for submodule path '$path'"
+ die "$(eval_gettext "Failed to register url for submodule path '\$sm_path'")"
+
+ say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$sm_path'")"
fi
# Copy "update" setting when it is not set yet
@@ -377,9 +458,7 @@ cmd_init()
test -z "$upd" ||
test -n "$(git config submodule."$name".update)" ||
git config submodule."$name".update "$upd" ||
- die "Failed to register update mode for submodule path '$path'"
-
- say "Submodule '$name' ($url) registered for path '$path'"
+ die "$(eval_gettext "Failed to register update mode for submodule path '\$sm_path'")"
done
}
@@ -425,6 +504,9 @@ cmd_update()
--recursive)
recursive=1
;;
+ --checkout)
+ update="checkout"
+ ;;
--)
shift
break
@@ -446,41 +528,49 @@ cmd_update()
fi
cloned_modules=
- module_list "$@" |
- while read mode sha1 stage path
+ module_list "$@" | {
+ err=
+ while read mode sha1 stage sm_path
do
if test "$stage" = U
then
- echo >&2 "Skipping unmerged submodule $path"
+ echo >&2 "Skipping unmerged submodule $sm_path"
continue
fi
- name=$(module_name "$path") || exit
+ name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url)
- update_module=$(git config submodule."$name".update)
+ if ! test -z "$update"
+ then
+ update_module=$update
+ else
+ update_module=$(git config submodule."$name".update)
+ fi
+
+ if test "$update_module" = "none"
+ then
+ echo "Skipping submodule '$sm_path'"
+ continue
+ fi
+
if test -z "$url"
then
# Only mention uninitialized submodules when its
# path have been specified
test "$#" != "0" &&
- say "Submodule path '$path' not initialized" &&
- say "Maybe you want to use 'update --init'?"
+ say "$(eval_gettext "Submodule path '\$sm_path' not initialized
+Maybe you want to use 'update --init'?")"
continue
fi
- if ! test -d "$path"/.git -o -f "$path"/.git
+ if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
then
- module_clone "$path" "$url" "$reference"|| exit
+ module_clone "$sm_path" "$url" "$reference"|| exit
cloned_modules="$cloned_modules;$name"
subsha1=
else
- subsha1=$(clear_local_git_env; cd "$path" &&
+ subsha1=$(clear_local_git_env; cd "$sm_path" &&
git rev-parse --verify HEAD) ||
- die "Unable to find current revision in submodule path '$path'"
- fi
-
- if ! test -z "$update"
- then
- update_module=$update
+ die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
fi
if test "$subsha1" != "$sha1"
@@ -496,10 +586,10 @@ cmd_update()
then
# Run fetch only if $sha1 isn't present or it
# is not reachable from a ref.
- (clear_local_git_env; cd "$path" &&
+ (clear_local_git_env; cd "$sm_path" &&
( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
test -z "$rev") || git-fetch)) ||
- die "Unable to fetch in submodule path '$path'"
+ die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
fi
# Is this something we just cloned?
@@ -509,35 +599,72 @@ cmd_update()
update_module= ;;
esac
+ must_die_on_failure=
case "$update_module" in
rebase)
command="git rebase"
- action="rebase"
- msg="rebased onto"
+ die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$sm_path'")"
+ say_msg="$(eval_gettext "Submodule path '\$sm_path': rebased into '\$sha1'")"
+ must_die_on_failure=yes
;;
merge)
command="git merge"
- action="merge"
- msg="merged in"
+ die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$sm_path'")"
+ say_msg="$(eval_gettext "Submodule path '\$sm_path': merged in '\$sha1'")"
+ must_die_on_failure=yes
;;
*)
command="git checkout $subforce -q"
- action="checkout"
- msg="checked out"
+ die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$sm_path'")"
+ say_msg="$(eval_gettext "Submodule path '\$sm_path': checked out '\$sha1'")"
;;
esac
- (clear_local_git_env; cd "$path" && $command "$sha1") ||
- die "Unable to $action '$sha1' in submodule path '$path'"
- say "Submodule path '$path': $msg '$sha1'"
+ if (clear_local_git_env; cd "$sm_path" && $command "$sha1")
+ then
+ say "$say_msg"
+ elif test -n "$must_die_on_failure"
+ then
+ die_with_status 2 "$die_msg"
+ else
+ err="${err};$die_msg"
+ continue
+ fi
fi
if test -n "$recursive"
then
- (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") ||
- die "Failed to recurse into submodule path '$path'"
+ (clear_local_git_env; cd "$sm_path" && eval cmd_update "$orig_flags")
+ res=$?
+ if test $res -gt 0
+ then
+ die_msg="$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
+ if test $res -eq 1
+ then
+ err="${err};$die_msg"
+ continue
+ else
+ die_with_status $res "$die_msg"
+ fi
+ fi
fi
done
+
+ if test -n "$err"
+ then
+ OIFS=$IFS
+ IFS=';'
+ for e in $err
+ do
+ if test -n "$e"
+ then
+ echo >&2 "$e"
+ fi
+ done
+ IFS=$OIFS
+ exit 1
+ fi
+ }
}
set_name_rev () {
@@ -619,7 +746,7 @@ cmd_summary() {
if [ -n "$files" ]
then
test -n "$cached" &&
- die "--cached cannot be used with --files"
+ die "$(gettext -- "--cached cannot be used with --files")"
diff_cmd=diff-files
head=
fi
@@ -659,7 +786,7 @@ cmd_summary() {
;; # removed
*)
# unexpected type
- echo >&2 "unexpected mode $mod_dst"
+ eval_gettextln "unexpected mode \$mod_dst" >&2
continue ;;
esac
fi
@@ -677,13 +804,13 @@ cmd_summary() {
total_commits=
case "$missing_src,$missing_dst" in
t,)
- errmsg=" Warn: $name doesn't contain commit $sha1_src"
+ errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_src")"
;;
,t)
- errmsg=" Warn: $name doesn't contain commit $sha1_dst"
+ errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_dst")"
;;
t,t)
- errmsg=" Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+ errmsg="$(eval_gettext " Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")"
;;
*)
errmsg=
@@ -708,11 +835,13 @@ cmd_summary() {
sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
if test $status = T
then
+ blob="$(gettext "blob")"
+ submodule="$(gettext "submodule")"
if test $mod_dst = 160000
then
- echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
+ echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
else
- echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
+ echo "* $name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
fi
else
echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
@@ -744,9 +873,9 @@ cmd_summary() {
done |
if test -n "$for_status"; then
if [ -n "$files" ]; then
- echo "# Submodules changed but not updated:"
+ gettextln "# Submodules changed but not updated:"
else
- echo "# Submodule changes to be committed:"
+ gettextln "# Submodule changes to be committed:"
fi
echo "#"
sed -e 's|^|# |' -e 's|^# $|#|'
@@ -796,30 +925,30 @@ cmd_status()
done
module_list "$@" |
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
- name=$(module_name "$path") || exit
+ name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url)
- displaypath="$prefix$path"
+ displaypath="$prefix$sm_path"
if test "$stage" = U
then
say "U$sha1 $displaypath"
continue
fi
- if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
+ if test -z "$url" || ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
then
say "-$sha1 $displaypath"
continue;
fi
- set_name_rev "$path" "$sha1"
- if git diff-files --ignore-submodules=dirty --quiet -- "$path"
+ set_name_rev "$sm_path" "$sha1"
+ if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path"
then
say " $sha1 $displaypath$revname"
else
if test -z "$cached"
then
- sha1=$(clear_local_git_env; cd "$path" && git rev-parse --verify HEAD)
- set_name_rev "$path" "$sha1"
+ sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
+ set_name_rev "$sm_path" "$sha1"
fi
say "+$sha1 $displaypath$revname"
fi
@@ -829,10 +958,10 @@ cmd_status()
(
prefix="$displaypath/"
clear_local_git_env
- cd "$path" &&
+ cd "$sm_path" &&
eval cmd_status "$orig_args"
) ||
- die "Failed to recurse into submodule path '$path'"
+ die "$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
fi
done
}
@@ -864,30 +993,42 @@ cmd_sync()
done
cd_to_toplevel
module_list "$@" |
- while read mode sha1 stage path
+ while read mode sha1 stage sm_path
do
- name=$(module_name "$path")
+ name=$(module_name "$sm_path")
url=$(git config -f .gitmodules --get submodule."$name".url)
# Possibly a url relative to parent
case "$url" in
./*|../*)
- url=$(resolve_relative_url "$url") || exit
+ # rewrite foo/bar as ../.. to find path from
+ # submodule work tree to superproject work tree
+ up_path="$(echo "$sm_path" | sed "s/[^/][^/]*/../g")" &&
+ # guarantee a trailing /
+ up_path=${up_path%/}/ &&
+ # path from submodule work tree to submodule origin repo
+ sub_origin_url=$(resolve_relative_url "$url" "$up_path") &&
+ # path from superproject work tree to submodule origin repo
+ super_config_url=$(resolve_relative_url "$url") || exit
+ ;;
+ *)
+ sub_origin_url="$url"
+ super_config_url="$url"
;;
esac
if git config "submodule.$name.url" >/dev/null 2>/dev/null
then
- say "Synchronizing submodule url for '$name'"
- git config submodule."$name".url "$url"
+ say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
+ git config submodule."$name".url "$super_config_url"
- if test -e "$path"/.git
+ if test -e "$sm_path"/.git
then
(
clear_local_git_env
- cd "$path"
+ cd "$sm_path"
remote=$(get_default_remote)
- git config remote."$remote".url "$url"
+ git config remote."$remote".url "$sub_origin_url"
)
fi
fi
diff --git a/git-svn.perl b/git-svn.perl
index 89f83fd..0b074c4 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/env perl
+#!/usr/bin/perl
# Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
# License: GPL v2 or later
use 5.008;
@@ -22,14 +22,13 @@ $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
$Git::SVN::Ra::_log_window_size = 100;
$Git::SVN::_minimize_url = 'unset';
-if (! exists $ENV{SVN_SSH}) {
- if (exists $ENV{GIT_SSH}) {
- $ENV{SVN_SSH} = $ENV{GIT_SSH};
- if ($^O eq 'msys') {
- $ENV{SVN_SSH} =~ s/\\/\\\\/g;
- $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
- }
- }
+if (! exists $ENV{SVN_SSH} && exists $ENV{GIT_SSH}) {
+ $ENV{SVN_SSH} = $ENV{GIT_SSH};
+}
+
+if (exists $ENV{SVN_SSH} && $^O eq 'msys') {
+ $ENV{SVN_SSH} =~ s/\\/\\\\/g;
+ $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
}
$Git::SVN::Log::TZ = $ENV{TZ};
@@ -37,18 +36,37 @@ $ENV{TZ} = 'UTC';
$| = 1; # unbuffer STDOUT
sub fatal (@) { print STDERR "@_\n"; exit 1 }
+
+# All SVN commands do it. Otherwise we may die on SIGPIPE when the remote
+# repository decides to close the connection which we expect to be kept alive.
+$SIG{PIPE} = 'IGNORE';
+
+# Given a dot separated version number, "subtract" it from
+# the SVN::Core::VERSION; non-negaitive return means the SVN::Core
+# is at least at the version the caller asked for.
+sub compare_svn_version {
+ my (@ours) = split(/\./, $SVN::Core::VERSION);
+ my (@theirs) = split(/\./, $_[0]);
+ my ($i, $diff);
+
+ for ($i = 0; $i < @ours && $i < @theirs; $i++) {
+ $diff = $ours[$i] - $theirs[$i];
+ return $diff if ($diff);
+ }
+ return 1 if ($i < @ours);
+ return -1 if ($i < @theirs);
+ return 0;
+}
+
sub _req_svn {
require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
require SVN::Ra;
require SVN::Delta;
- if ($SVN::Core::VERSION lt '1.1.0') {
+ if (::compare_svn_version('1.1.0') < 0) {
fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
}
}
my $can_compress = eval { require Compress::Zlib; 1};
-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//;
@@ -59,6 +77,10 @@ use File::Find;
use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
use IPC::Open3;
use Git;
+use Git::SVN::Editor qw//;
+use Git::SVN::Fetcher qw//;
+use Git::SVN::Ra qw//;
+use Git::SVN::Prompt qw//;
use Memoize; # core since 5.8.0, Jul 2002
BEGIN {
@@ -67,8 +89,7 @@ BEGIN {
foreach (qw/command command_oneline command_noisy command_output_pipe
command_input_pipe command_close_pipe
command_bidi_pipe command_close_bidi_pipe/) {
- for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
- Git::SVN::Migration Git::SVN::Log Git::SVN),
+ for my $package ( qw(Git::SVN::Migration Git::SVN::Log Git::SVN),
__PACKAGE__) {
*{"${package}::$_"} = \&{"Git::$_"};
}
@@ -85,15 +106,17 @@ my ($_stdin, $_help, $_edit,
$_message, $_file, $_branch_dest,
$_template, $_shared,
$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
- $_merge, $_strategy, $_dry_run, $_local,
+ $_merge, $_strategy, $_preserve_merges, $_dry_run, $_local,
$_prefix, $_no_checkout, $_url, $_verbose,
- $_git_format, $_commit_url, $_tag, $_merge_info);
+ $_git_format, $_commit_url, $_tag, $_merge_info, $_interactive);
$Git::SVN::_follow_parent = 1;
+$Git::SVN::Fetcher::_placeholder_filename = ".gitignore";
$_q ||= 0;
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
'config-dir=s' => \$Git::SVN::Ra::config_dir,
'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
- 'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex );
+ 'ignore-paths=s' => \$Git::SVN::Fetcher::_ignore_regex,
+ 'ignore-refs=s' => \$Git::SVN::Ra::_ignore_refs_regex );
my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
'authors-file|A=s' => \$_authors,
'authors-prog=s' => \$_authors_prog,
@@ -125,10 +148,10 @@ my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] },
%remote_opts );
my %cmt_opts = ( 'edit|e' => \$_edit,
- 'rmdir' => \$SVN::Git::Editor::_rmdir,
- 'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder,
- 'l=i' => \$SVN::Git::Editor::_rename_limit,
- 'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity
+ 'rmdir' => \$Git::SVN::Editor::_rmdir,
+ 'find-copies-harder' => \$Git::SVN::Editor::_find_copies_harder,
+ 'l=i' => \$Git::SVN::Editor::_rename_limit,
+ 'copy-similarity|C=i'=> \$Git::SVN::Editor::_cp_similarity
);
my %cmd = (
@@ -139,6 +162,10 @@ my %cmd = (
%fc_opts } ],
clone => [ \&cmd_clone, "Initialize and fetch revisions",
{ 'revision|r=s' => \$_revision,
+ 'preserve-empty-dirs' =>
+ \$Git::SVN::Fetcher::_preserve_empty_dirs,
+ 'placeholder-filename=s' =>
+ \$Git::SVN::Fetcher::_placeholder_filename,
%fc_opts, %init_opts } ],
init => [ \&cmd_init, "Initialize a repo for tracking" .
" (requires URL argument)",
@@ -158,6 +185,7 @@ my %cmd = (
'revision|r=i' => \$_revision,
'no-rebase' => \$_no_rebase,
'mergeinfo=s' => \$_merge_info,
+ 'interactive|i' => \$_interactive,
%cmt_opts, %fc_opts } ],
branch => [ \&cmd_branch,
'Create a branch in the SVN repository',
@@ -227,6 +255,7 @@ my %cmd = (
'local|l' => \$_local,
'fetch-all|all' => \$_fetch_all,
'dry-run|n' => \$_dry_run,
+ 'preserve-merges|p' => \$_preserve_merges,
%fc_opts } ],
'commit-diff' => [ \&cmd_commit_diff,
'Commit a diff between two trees',
@@ -251,6 +280,27 @@ my %cmd = (
{} ],
);
+use Term::ReadLine;
+package FakeTerm;
+sub new {
+ my ($class, $reason) = @_;
+ return bless \$reason, shift;
+}
+sub readline {
+ my $self = shift;
+ die "Cannot use readline on FakeTerm: $$self";
+}
+package main;
+
+my $term = eval {
+ $ENV{"GIT_SVN_NOTTY"}
+ ? new Term::ReadLine 'git-svn', \*STDIN, \*STDOUT
+ : new Term::ReadLine 'git-svn';
+};
+if ($@) {
+ $term = new FakeTerm "$@: going non-interactive";
+}
+
my $cmd;
for (my $i = 0; $i < @ARGV; $i++) {
if (defined $cmd{$ARGV[$i]}) {
@@ -294,7 +344,7 @@ read_git_config(\%opts);
if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) {
Getopt::Long::Configure('pass_through');
}
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
+my $rv = GetOptions(%opts, '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 {
@@ -361,6 +411,36 @@ sub version {
exit 0;
}
+sub ask {
+ my ($prompt, %arg) = @_;
+ my $valid_re = $arg{valid_re};
+ my $default = $arg{default};
+ my $resp;
+ my $i = 0;
+
+ if ( !( defined($term->IN)
+ && defined( fileno($term->IN) )
+ && defined( $term->OUT )
+ && defined( fileno($term->OUT) ) ) ){
+ return defined($default) ? $default : undef;
+ }
+
+ while ($i++ < 10) {
+ $resp = $term->readline($prompt);
+ if (!defined $resp) { # EOF
+ print "\n";
+ return defined $default ? $default : undef;
+ }
+ if ($resp eq '' and defined $default) {
+ return $default;
+ }
+ if (!defined $valid_re or $resp =~ /$valid_re/) {
+ return $resp;
+ }
+ }
+ return undef;
+}
+
sub do_git_init_db {
unless (-d $ENV{GIT_DIR}) {
my @init_db = ('init');
@@ -383,9 +463,18 @@ sub do_git_init_db {
command_noisy('config', "$pfx.$i", $icv{$i});
$set = $i;
}
- my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
- command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
- if defined $$ignore_regex;
+ my $ignore_paths_regex = \$Git::SVN::Fetcher::_ignore_regex;
+ command_noisy('config', "$pfx.ignore-paths", $$ignore_paths_regex)
+ if defined $$ignore_paths_regex;
+ my $ignore_refs_regex = \$Git::SVN::Ra::_ignore_refs_regex;
+ command_noisy('config', "$pfx.ignore-refs", $$ignore_refs_regex)
+ if defined $$ignore_refs_regex;
+
+ if (defined $Git::SVN::Fetcher::_preserve_empty_dirs) {
+ my $fname = \$Git::SVN::Fetcher::_placeholder_filename;
+ command_noisy('config', "$pfx.preserve-empty-dirs", 'true');
+ command_noisy('config', "$pfx.placeholder-filename", $$fname);
+ }
}
sub init_subdir {
@@ -497,6 +586,195 @@ sub cmd_set_tree {
unlink $gs->{index};
}
+sub split_merge_info_range {
+ my ($range) = @_;
+ if ($range =~ /(\d+)-(\d+)/) {
+ return (int($1), int($2));
+ } else {
+ return (int($range), int($range));
+ }
+}
+
+sub combine_ranges {
+ my ($in) = @_;
+
+ my @fnums = ();
+ my @arr = split(/,/, $in);
+ for my $element (@arr) {
+ my ($start, $end) = split_merge_info_range($element);
+ push @fnums, $start;
+ }
+
+ my @sorted = @arr [ sort {
+ $fnums[$a] <=> $fnums[$b]
+ } 0..$#arr ];
+
+ my @return = ();
+ my $last = -1;
+ my $first = -1;
+ for my $element (@sorted) {
+ my ($start, $end) = split_merge_info_range($element);
+
+ if ($last == -1) {
+ $first = $start;
+ $last = $end;
+ next;
+ }
+ if ($start <= $last+1) {
+ if ($end > $last) {
+ $last = $end;
+ }
+ next;
+ }
+ if ($first == $last) {
+ push @return, "$first";
+ } else {
+ push @return, "$first-$last";
+ }
+ $first = $start;
+ $last = $end;
+ }
+
+ if ($first != -1) {
+ if ($first == $last) {
+ push @return, "$first";
+ } else {
+ push @return, "$first-$last";
+ }
+ }
+
+ return join(',', @return);
+}
+
+sub merge_revs_into_hash {
+ my ($hash, $minfo) = @_;
+ my @lines = split(' ', $minfo);
+
+ for my $line (@lines) {
+ my ($branchpath, $revs) = split(/:/, $line);
+
+ if (exists($hash->{$branchpath})) {
+ # Merge the two revision sets
+ my $combined = "$hash->{$branchpath},$revs";
+ $hash->{$branchpath} = combine_ranges($combined);
+ } else {
+ # Just do range combining for consolidation
+ $hash->{$branchpath} = combine_ranges($revs);
+ }
+ }
+}
+
+sub merge_merge_info {
+ my ($mergeinfo_one, $mergeinfo_two) = @_;
+ my %result_hash = ();
+
+ merge_revs_into_hash(\%result_hash, $mergeinfo_one);
+ merge_revs_into_hash(\%result_hash, $mergeinfo_two);
+
+ my $result = '';
+ # Sort below is for consistency's sake
+ for my $branchname (sort keys(%result_hash)) {
+ my $revlist = $result_hash{$branchname};
+ $result .= "$branchname:$revlist\n"
+ }
+ return $result;
+}
+
+sub populate_merge_info {
+ my ($d, $gs, $uuid, $linear_refs, $rewritten_parent) = @_;
+
+ my %parentshash;
+ read_commit_parents(\%parentshash, $d);
+ my @parents = @{$parentshash{$d}};
+ if ($#parents > 0) {
+ # Merge commit
+ my $all_parents_ok = 1;
+ my $aggregate_mergeinfo = '';
+ my $rooturl = $gs->repos_root;
+
+ if (defined($rewritten_parent)) {
+ # Replace first parent with newly-rewritten version
+ shift @parents;
+ unshift @parents, $rewritten_parent;
+ }
+
+ foreach my $parent (@parents) {
+ my ($branchurl, $svnrev, $paruuid) =
+ cmt_metadata($parent);
+
+ unless (defined($svnrev)) {
+ # Should have been caught be preflight check
+ fatal "merge commit $d has ancestor $parent, but that change "
+ ."does not have git-svn metadata!";
+ }
+ unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
+ fatal "commit $parent git-svn metadata changed mid-run!";
+ }
+ my $branchpath = $1;
+
+ my $ra = Git::SVN::Ra->new($branchurl);
+ my (undef, undef, $props) =
+ $ra->get_dir(canonicalize_path("."), $svnrev);
+ my $par_mergeinfo = $props->{'svn:mergeinfo'};
+ unless (defined $par_mergeinfo) {
+ $par_mergeinfo = '';
+ }
+ # Merge previous mergeinfo values
+ $aggregate_mergeinfo =
+ merge_merge_info($aggregate_mergeinfo,
+ $par_mergeinfo, 0);
+
+ next if $parent eq $parents[0]; # Skip first parent
+ # Add new changes being placed in tree by merge
+ my @cmd = (qw/rev-list --reverse/,
+ $parent, qw/--not/);
+ foreach my $par (@parents) {
+ unless ($par eq $parent) {
+ push @cmd, $par;
+ }
+ }
+ my @revsin = ();
+ my ($revlist, $ctx) = command_output_pipe(@cmd);
+ while (<$revlist>) {
+ my $irev = $_;
+ chomp $irev;
+ my (undef, $csvnrev, undef) =
+ cmt_metadata($irev);
+ unless (defined $csvnrev) {
+ # A child is missing SVN annotations...
+ # this might be OK, or might not be.
+ warn "W:child $irev is merged into revision "
+ ."$d but does not have git-svn metadata. "
+ ."This means git-svn cannot determine the "
+ ."svn revision numbers to place into the "
+ ."svn:mergeinfo property. You must ensure "
+ ."a branch is entirely committed to "
+ ."SVN before merging it in order for "
+ ."svn:mergeinfo population to function "
+ ."properly";
+ }
+ push @revsin, $csvnrev;
+ }
+ command_close_pipe($revlist, $ctx);
+
+ last unless $all_parents_ok;
+
+ # We now have a list of all SVN revnos which are
+ # merged by this particular parent. Integrate them.
+ next if $#revsin == -1;
+ my $newmergeinfo = "$branchpath:" . join(',', @revsin);
+ $aggregate_mergeinfo =
+ merge_merge_info($aggregate_mergeinfo,
+ $newmergeinfo, 1);
+ }
+ if ($all_parents_ok and $aggregate_mergeinfo) {
+ return $aggregate_mergeinfo;
+ }
+ }
+
+ return undef;
+}
+
sub cmd_dcommit {
my $head = shift;
command_noisy(qw/update-index --refresh/);
@@ -546,8 +824,88 @@ sub cmd_dcommit {
"If these changes depend on each other, re-running ",
"without --no-rebase may be required."
}
+
+ if (defined $_interactive){
+ my $ask_default = "y";
+ foreach my $d (@$linear_refs){
+ my ($fh, $ctx) = command_output_pipe(qw(show --summary), "$d");
+ while (<$fh>){
+ print $_;
+ }
+ command_close_pipe($fh, $ctx);
+ $_ = ask("Commit this patch to SVN? ([y]es (default)|[n]o|[q]uit|[a]ll): ",
+ valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
+ default => $ask_default);
+ die "Commit this patch reply required" unless defined $_;
+ if (/^[nq]/i) {
+ exit(0);
+ } elsif (/^a/i) {
+ last;
+ }
+ }
+ }
+
my $expect_url = $url;
+
+ my $push_merge_info = eval {
+ command_oneline(qw/config --get svn.pushmergeinfo/)
+ };
+ if (not defined($push_merge_info)
+ or $push_merge_info eq "false"
+ or $push_merge_info eq "no"
+ or $push_merge_info eq "never") {
+ $push_merge_info = 0;
+ }
+
+ unless (defined($_merge_info) || ! $push_merge_info) {
+ # Preflight check of changes to ensure no issues with mergeinfo
+ # This includes check for uncommitted-to-SVN parents
+ # (other than the first parent, which we will handle),
+ # information from different SVN repos, and paths
+ # which are not underneath this repository root.
+ my $rooturl = $gs->repos_root;
+ foreach my $d (@$linear_refs) {
+ my %parentshash;
+ read_commit_parents(\%parentshash, $d);
+ my @realparents = @{$parentshash{$d}};
+ if ($#realparents > 0) {
+ # Merge commit
+ shift @realparents; # Remove/ignore first parent
+ foreach my $parent (@realparents) {
+ my ($branchurl, $svnrev, $paruuid) = cmt_metadata($parent);
+ unless (defined $paruuid) {
+ # A parent is missing SVN annotations...
+ # abort the whole operation.
+ fatal "$parent is merged into revision $d, "
+ ."but does not have git-svn metadata. "
+ ."Either dcommit the branch or use a "
+ ."local cherry-pick, FF merge, or rebase "
+ ."instead of an explicit merge commit.";
+ }
+
+ unless ($paruuid eq $uuid) {
+ # Parent has SVN metadata from different repository
+ fatal "merge parent $parent for change $d has "
+ ."git-svn uuid $paruuid, while current change "
+ ."has uuid $uuid!";
+ }
+
+ unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
+ # This branch is very strange indeed.
+ fatal "merge parent $parent for $d is on branch "
+ ."$branchurl, which is not under the "
+ ."git-svn root $rooturl!";
+ }
+ }
+ }
+ }
+ }
+
+ my $rewritten_parent;
Git::SVN::remove_username($expect_url);
+ if (defined($_merge_info)) {
+ $_merge_info =~ tr{ }{\n};
+ }
while (1) {
my $d = shift @$linear_refs or last;
unless (defined $last_rev) {
@@ -561,6 +919,14 @@ sub cmd_dcommit {
print "diff-tree $d~1 $d\n";
} else {
my $cmt_rev;
+
+ unless (defined($_merge_info) || ! $push_merge_info) {
+ $_merge_info = populate_merge_info($d, $gs,
+ $uuid,
+ $linear_refs,
+ $rewritten_parent);
+ }
+
my %ed_opts = ( r => $last_rev,
log => get_commit_entry($d)->{log},
ra => Git::SVN::Ra->new($url),
@@ -575,7 +941,7 @@ sub cmd_dcommit {
},
mergeinfo => $_merge_info,
svn_path => '');
- if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+ if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\n$d~1 == $d\n";
} elsif ($parents->{$d} && @{$parents->{$d}}) {
$gs->{inject_parents_dcommit}->{$cmt_rev} =
@@ -603,6 +969,9 @@ sub cmd_dcommit {
@finish = qw/reset --mixed/;
}
command_noisy(@finish, $gs->refname);
+
+ $rewritten_parent = command_oneline(qw/rev-parse HEAD/);
+
if (@diff) {
@refs = ();
my ($url_, $rev_, $uuid_, $gs_) =
@@ -696,8 +1065,7 @@ sub cmd_branch {
" with the --destination argument.\n";
}
foreach my $g (@{$allglobs}) {
- # SVN::Git::Editor could probably be moved to Git.pm..
- my $re = SVN::Git::Editor::glob2pat($g->{path}->{left});
+ my $re = Git::SVN::Editor::glob2pat($g->{path}->{left});
if ($_branch_dest =~ /$re/) {
$glob = $g;
last;
@@ -1055,7 +1423,7 @@ sub cmd_commit_diff {
tree_b => $tb,
editor_cb => sub { print "Committed r$_[0]\n" },
svn_path => $svn_path );
- if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+ if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\n$ta == $tb\n";
}
}
@@ -1123,7 +1491,7 @@ sub cmd_info {
}
::_req_svn();
$result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
- ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
+ (::compare_svn_version('1.5.4') <= 0 || $file_type ne "dir");
$result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
$result .= "Node Kind: " .
@@ -1224,6 +1592,7 @@ sub rebase_cmd {
push @cmd, '-v' if $_verbose;
push @cmd, qw/--merge/ if $_merge;
push @cmd, "--strategy=$_strategy" if $_strategy;
+ push @cmd, "--preserve-merges" if $_preserve_merges;
@cmd;
}
@@ -1532,8 +1901,7 @@ sub cmt_sha2rev_batch {
sub working_head_info {
my ($head, $refs) = @_;
- my @args = qw/log --no-color --no-decorate --first-parent
- --pretty=medium/;
+ my @args = qw/rev-list --first-parent --pretty=medium/;
my ($fh, $ctx) = command_output_pipe(@args, $head);
my $hash;
my %max;
@@ -1683,8 +2051,14 @@ use Carp qw/croak/;
use File::Path qw/mkpath/;
use File::Copy qw/copy/;
use IPC::Open3;
+use Time::Local;
use Memoize; # core since 5.8.0, Jul 2002
use Memoize::Storable;
+use POSIX qw(:signal_h);
+my $can_use_yaml;
+BEGIN {
+ $can_use_yaml = eval { require Git::SVN::Memoize::YAML; 1};
+}
my ($_gc_nr, $_gc_period);
@@ -1849,6 +2223,8 @@ sub read_all_remotes {
$r->{$1}->{url} = $2;
} elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) {
$r->{$1}->{pushurl} = $2;
+ } elsif (m!^(.+)\.ignore-refs=\s*(.*)\s*$!) {
+ $r->{$1}->{ignore_refs_regex} = $2;
} elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
my ($remote, $t, $local_ref, $remote_ref) =
($1, $2, $3, $4);
@@ -1885,6 +2261,16 @@ sub read_all_remotes {
}
} keys %$r;
+ foreach my $remote (keys %$r) {
+ foreach ( grep { defined $_ }
+ map { $r->{$remote}->{$_} } qw(branches tags) ) {
+ foreach my $rs ( @$_ ) {
+ $rs->{ignore_refs_regex} =
+ $r->{$remote}->{ignore_refs_regex};
+ }
+ }
+ }
+
$r;
}
@@ -2773,7 +3159,7 @@ sub find_parent_branch {
# at the moment), so we can't rely on it
$self->{last_rev} = $r0;
$self->{last_commit} = $parent;
- $ed = SVN::Git::Fetcher->new($self, $gs->{path});
+ $ed = Git::SVN::Fetcher->new($self, $gs->{path});
$gs->ra->gs_do_switch($r0, $rev, $gs,
$self->full_url, $ed)
or die "SVN connection failed somewhere...\n";
@@ -2791,7 +3177,7 @@ sub find_parent_branch {
} else {
print STDERR "Following parent with do_update\n"
unless $::_q > 1;
- $ed = SVN::Git::Fetcher->new($self);
+ $ed = Git::SVN::Fetcher->new($self);
$self->ra->gs_do_update($rev, $rev, $self, $ed)
or die "SVN connection failed somewhere...\n";
}
@@ -2814,7 +3200,7 @@ sub do_fetch {
push @{$log_entry->{parents}}, $lc;
return $log_entry;
}
- $ed = SVN::Git::Fetcher->new($self);
+ $ed = Git::SVN::Fetcher->new($self);
$last_rev = $self->{last_rev};
$ed->{c} = $lc;
@parents = ($lc);
@@ -2823,7 +3209,7 @@ sub do_fetch {
if (my $log_entry = $self->find_parent_branch($paths, $rev)) {
return $log_entry;
}
- $ed = SVN::Git::Fetcher->new($self);
+ $ed = Git::SVN::Fetcher->new($self);
}
unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) {
die "SVN connection failed somewhere...\n";
@@ -2929,6 +3315,14 @@ sub get_untracked {
\@out;
}
+sub get_tz {
+ # some systmes don't handle or mishandle %z, so be creative.
+ my $t = shift || time;
+ my $gm = timelocal(gmtime($t));
+ my $sign = qw( + + - )[ $t <=> $gm ];
+ return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+}
+
# parse_svn_date(DATE)
# --------------------
# Given a date (in UTC) from Subversion, return a string in the format
@@ -2961,8 +3355,7 @@ sub parse_svn_date {
delete $ENV{TZ};
}
- my $our_TZ =
- POSIX::strftime('%Z', $S, $M, $H, $d, $m - 1, $Y - 1900);
+ my $our_TZ = get_tz();
# This converts $epoch_in_UTC into our local timezone.
my ($sec, $min, $hour, $mday, $mon, $year,
@@ -3011,7 +3404,7 @@ sub other_gs {
my (undef, $max_commit) = $gs->rev_map_max(1);
last if (!$max_commit);
my ($url) = ::cmt_metadata($max_commit);
- last if ($url eq $gs->full_url);
+ last if ($url eq $gs->metadata_url);
$ref_id .= '-';
}
print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
@@ -3188,6 +3581,17 @@ sub has_no_changes {
command_oneline("rev-parse", "$commit~1^{tree}"));
}
+sub tie_for_persistent_memoization {
+ my $hash = shift;
+ my $path = shift;
+
+ if ($can_use_yaml) {
+ tie %$hash => 'Git::SVN::Memoize::YAML', "$path.yaml";
+ } else {
+ tie %$hash => 'Memoize::Storable', "$path.db", 'nstore';
+ }
+}
+
# The GIT_DIR environment variable is not always set until after the command
# line arguments are processed, so we can't memoize in a BEGIN block.
{
@@ -3200,22 +3604,26 @@ sub has_no_changes {
my $cache_path = "$ENV{GIT_DIR}/svn/.caches/";
mkpath([$cache_path]) unless -d $cache_path;
- tie my %lookup_svn_merge_cache => 'Memoize::Storable',
- "$cache_path/lookup_svn_merge.db", 'nstore';
+ my %lookup_svn_merge_cache;
+ my %check_cherry_pick_cache;
+ my %has_no_changes_cache;
+
+ tie_for_persistent_memoization(\%lookup_svn_merge_cache,
+ "$cache_path/lookup_svn_merge");
memoize 'lookup_svn_merge',
SCALAR_CACHE => 'FAULT',
LIST_CACHE => ['HASH' => \%lookup_svn_merge_cache],
;
- tie my %check_cherry_pick_cache => 'Memoize::Storable',
- "$cache_path/check_cherry_pick.db", 'nstore';
+ tie_for_persistent_memoization(\%check_cherry_pick_cache,
+ "$cache_path/check_cherry_pick");
memoize 'check_cherry_pick',
SCALAR_CACHE => 'FAULT',
LIST_CACHE => ['HASH' => \%check_cherry_pick_cache],
;
- tie my %has_no_changes_cache => 'Memoize::Storable',
- "$cache_path/has_no_changes.db", 'nstore';
+ tie_for_persistent_memoization(\%has_no_changes_cache,
+ "$cache_path/has_no_changes");
memoize 'has_no_changes',
SCALAR_CACHE => ['HASH' => \%has_no_changes_cache],
LIST_CACHE => 'FAULT',
@@ -3521,7 +3929,7 @@ sub set_tree {
editor_cb => sub {
$self->set_tree_cb($log_entry, $tree, @_) },
svn_path => $self->{path} );
- if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+ if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\nr$self->{last_rev} = $tree\n";
}
}
@@ -3562,7 +3970,7 @@ sub rebuild {
my ($base_rev, $head) = ($partial ? $self->rev_map_max_norebuild(1) :
(undef, undef));
my ($log, $ctx) =
- command_output_pipe(qw/rev-list --pretty=raw --no-color --reverse/,
+ command_output_pipe(qw/rev-list --pretty=raw --reverse/,
($head ? "$head.." : "") . $self->refname,
'--');
my $metadata_url = $self->metadata_url;
@@ -3694,11 +4102,14 @@ sub rev_map_set {
length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
my $db = $self->map_path($uuid);
my $db_lock = "$db.lock";
- my $sig;
+ my $sigmask;
$update_ref ||= 0;
if ($update_ref) {
- $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
- $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
+ $sigmask = POSIX::SigSet->new();
+ my $signew = POSIX::SigSet->new(SIGINT, SIGHUP, SIGTERM,
+ SIGALRM, SIGUSR1, SIGUSR2);
+ sigprocmask(SIG_BLOCK, $signew, $sigmask) or
+ croak "Can't block signals: $!";
}
mkfile($db);
@@ -3737,9 +4148,8 @@ sub rev_map_set {
"$db_lock => $db ($!)\n";
delete $LOCKFILES{$db_lock};
if ($update_ref) {
- $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
- $SIG{USR1} = $SIG{USR2} = 'DEFAULT';
- kill $sig, $$ if defined $sig;
+ sigprocmask(SIG_SETMASK, $sigmask) or
+ croak "Can't restore signal mask: $!";
}
}
@@ -3935,1567 +4345,10 @@ sub remove_username {
$_[0] =~ s{^([^:]*://)[^@]+@}{$1};
}
-package Git::SVN::Prompt;
-use strict;
-use warnings;
-require SVN::Core;
-use vars qw/$_no_auth_cache $_username/;
-
-sub simple {
- my ($cred, $realm, $default_username, $may_save, $pool) = @_;
- $may_save = undef if $_no_auth_cache;
- $default_username = $_username if defined $_username;
- if (defined $default_username && length $default_username) {
- if (defined $realm && length $realm) {
- print STDERR "Authentication realm: $realm\n";
- STDERR->flush;
- }
- $cred->username($default_username);
- } else {
- username($cred, $realm, $may_save, $pool);
- }
- $cred->password(_read_password("Password for '" .
- $cred->username . "': ", $realm));
- $cred->may_save($may_save);
- $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub ssl_server_trust {
- my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
- $may_save = undef if $_no_auth_cache;
- print STDERR "Error validating server certificate for '$realm':\n";
- {
- no warnings 'once';
- # All variables SVN::Auth::SSL::* are used only once,
- # so we're shutting up Perl warnings about this.
- if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
- print STDERR " - The certificate is not issued ",
- "by a trusted authority. Use the\n",
- " fingerprint to validate ",
- "the certificate manually!\n";
- }
- if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
- print STDERR " - The certificate hostname ",
- "does not match.\n";
- }
- if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
- print STDERR " - The certificate is not yet valid.\n";
- }
- if ($failures & $SVN::Auth::SSL::EXPIRED) {
- print STDERR " - The certificate has expired.\n";
- }
- if ($failures & $SVN::Auth::SSL::OTHER) {
- print STDERR " - The certificate has ",
- "an unknown error.\n";
- }
- } # no warnings 'once'
- printf STDERR
- "Certificate information:\n".
- " - Hostname: %s\n".
- " - Valid: from %s until %s\n".
- " - Issuer: %s\n".
- " - Fingerprint: %s\n",
- map $cert_info->$_, qw(hostname valid_from valid_until
- issuer_dname fingerprint);
- my $choice;
-prompt:
- print STDERR $may_save ?
- "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
- "(R)eject or accept (t)emporarily? ";
- STDERR->flush;
- $choice = lc(substr(<STDIN> || 'R', 0, 1));
- if ($choice =~ /^t$/i) {
- $cred->may_save(undef);
- } elsif ($choice =~ /^r$/i) {
- return -1;
- } elsif ($may_save && $choice =~ /^p$/i) {
- $cred->may_save($may_save);
- } else {
- goto prompt;
- }
- $cred->accepted_failures($failures);
- $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub ssl_client_cert {
- my ($cred, $realm, $may_save, $pool) = @_;
- $may_save = undef if $_no_auth_cache;
- print STDERR "Client certificate filename: ";
- STDERR->flush;
- chomp(my $filename = <STDIN>);
- $cred->cert_file($filename);
- $cred->may_save($may_save);
- $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub ssl_client_cert_pw {
- my ($cred, $realm, $may_save, $pool) = @_;
- $may_save = undef if $_no_auth_cache;
- $cred->password(_read_password("Password: ", $realm));
- $cred->may_save($may_save);
- $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub username {
- my ($cred, $realm, $may_save, $pool) = @_;
- $may_save = undef if $_no_auth_cache;
- if (defined $realm && length $realm) {
- print STDERR "Authentication realm: $realm\n";
- }
- my $username;
- if (defined $_username) {
- $username = $_username;
- } else {
- print STDERR "Username: ";
- STDERR->flush;
- chomp($username = <STDIN>);
- }
- $cred->username($username);
- $cred->may_save($may_save);
- $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub _read_password {
- my ($prompt, $realm) = @_;
- my $password = '';
- if (exists $ENV{GIT_ASKPASS}) {
- open(PH, "-|", $ENV{GIT_ASKPASS}, $prompt);
- $password = <PH>;
- $password =~ s/[\012\015]//; # \n\r
- close(PH);
- } else {
- print STDERR $prompt;
- STDERR->flush;
- require Term::ReadKey;
- Term::ReadKey::ReadMode('noecho');
- while (defined(my $key = Term::ReadKey::ReadKey(0))) {
- last if $key =~ /[\012\015]/; # \n\r
- $password .= $key;
- }
- Term::ReadKey::ReadMode('restore');
- print STDERR "\n";
- STDERR->flush;
- }
- $password;
-}
-
-package SVN::Git::Fetcher;
-use vars qw/@ISA/;
-use strict;
-use warnings;
-use Carp qw/croak/;
-use IO::File qw//;
-use vars qw/$_ignore_regex/;
-
-# file baton members: path, mode_a, mode_b, pool, fh, blob, base
-sub new {
- my ($class, $git_svn, $switch_path) = @_;
- my $self = SVN::Delta::Editor->new;
- bless $self, $class;
- if (exists $git_svn->{last_commit}) {
- $self->{c} = $git_svn->{last_commit};
- $self->{empty_symlinks} =
- _mark_empty_symlinks($git_svn, $switch_path);
- }
- $self->{ignore_regex} = eval { command_oneline('config', '--get',
- "svn-remote.$git_svn->{repo_id}.ignore-paths") };
- $self->{empty} = {};
- $self->{dir_prop} = {};
- $self->{file_prop} = {};
- $self->{absent_dir} = {};
- $self->{absent_file} = {};
- $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
- $self->{pathnameencoding} = Git::config('svn.pathnameencoding');
- $self;
-}
-
-# this uses the Ra object, so it must be called before do_{switch,update},
-# not inside them (when the Git::SVN::Fetcher object is passed) to
-# do_{switch,update}
-sub _mark_empty_symlinks {
- my ($git_svn, $switch_path) = @_;
- my $bool = Git::config_bool('svn.brokenSymlinkWorkaround');
- return {} if (!defined($bool)) || (defined($bool) && ! $bool);
-
- my %ret;
- my ($rev, $cmt) = $git_svn->last_rev_commit;
- return {} unless ($rev && $cmt);
-
- # allow the warning to be printed for each revision we fetch to
- # ensure the user sees it. The user can also disable the workaround
- # on the repository even while git svn is running and the next
- # revision fetched will skip this expensive function.
- my $printed_warning;
- chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);
- my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);
- local $/ = "\0";
- my $pfx = defined($switch_path) ? $switch_path : $git_svn->{path};
- $pfx .= '/' if length($pfx);
- while (<$ls>) {
- chomp;
- s/\A100644 blob $empty_blob\t//o or next;
- unless ($printed_warning) {
- print STDERR "Scanning for empty symlinks, ",
- "this may take a while if you have ",
- "many empty files\n",
- "You may disable this with `",
- "git config svn.brokenSymlinkWorkaround ",
- "false'.\n",
- "This may be done in a different ",
- "terminal without restarting ",
- "git svn\n";
- $printed_warning = 1;
- }
- my $path = $_;
- my (undef, $props) =
- $git_svn->ra->get_file($pfx.$path, $rev, undef);
- if ($props->{'svn:special'}) {
- $ret{$path} = 1;
- }
- }
- command_close_pipe($ls, $ctx);
- \%ret;
-}
-
-# returns true if a given path is inside a ".git" directory
-sub in_dot_git {
- $_[0] =~ m{(?:^|/)\.git(?:/|$)};
-}
-
-# return value: 0 -- don't ignore, 1 -- ignore
-sub is_path_ignored {
- my ($self, $path) = @_;
- return 1 if in_dot_git($path);
- return 1 if defined($self->{ignore_regex}) &&
- $path =~ m!$self->{ignore_regex}!;
- return 0 unless defined($_ignore_regex);
- return 1 if $path =~ m!$_ignore_regex!o;
- return 0;
-}
-
-sub set_path_strip {
- my ($self, $path) = @_;
- $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
-}
-
-sub open_root {
- { path => '' };
-}
-
-sub open_directory {
- my ($self, $path, $pb, $rev) = @_;
- { path => $path };
-}
-
-sub git_path {
- my ($self, $path) = @_;
- if (my $enc = $self->{pathnameencoding}) {
- require Encode;
- Encode::from_to($path, 'UTF-8', $enc);
- }
- if ($self->{path_strip}) {
- $path =~ s!$self->{path_strip}!! or
- die "Failed to strip path '$path' ($self->{path_strip})\n";
- }
- $path;
-}
-
-sub delete_entry {
- my ($self, $path, $rev, $pb) = @_;
- return undef if $self->is_path_ignored($path);
-
- my $gpath = $self->git_path($path);
- return undef if ($gpath eq '');
-
- # remove entire directories.
- my ($tree) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
- =~ /\A040000 tree ([a-f\d]{40})\t\Q$gpath\E\0/);
- if ($tree) {
- my ($ls, $ctx) = command_output_pipe(qw/ls-tree
- -r --name-only -z/,
- $tree);
- local $/ = "\0";
- while (<$ls>) {
- chomp;
- my $rmpath = "$gpath/$_";
- $self->{gii}->remove($rmpath);
- print "\tD\t$rmpath\n" unless $::_q;
- }
- print "\tD\t$gpath/\n" unless $::_q;
- command_close_pipe($ls, $ctx);
- } else {
- $self->{gii}->remove($gpath);
- print "\tD\t$gpath\n" unless $::_q;
- }
- $self->{empty}->{$path} = 0;
- undef;
-}
-
-sub open_file {
- my ($self, $path, $pb, $rev) = @_;
- my ($mode, $blob);
-
- goto out if $self->is_path_ignored($path);
-
- my $gpath = $self->git_path($path);
- ($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
- =~ /\A(\d{6}) blob ([a-f\d]{40})\t\Q$gpath\E\0/);
- unless (defined $mode && defined $blob) {
- die "$path was not found in commit $self->{c} (r$rev)\n";
- }
- if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {
- $mode = '120000';
- }
-out:
- { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
- pool => SVN::Pool->new, action => 'M' };
-}
-
-sub add_file {
- my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
- my $mode;
-
- if (!$self->is_path_ignored($path)) {
- my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
- delete $self->{empty}->{$dir};
- $mode = '100644';
- }
- { path => $path, mode_a => $mode, mode_b => $mode,
- pool => SVN::Pool->new, action => 'A' };
-}
-
-sub add_directory {
- my ($self, $path, $cp_path, $cp_rev) = @_;
- goto out if $self->is_path_ignored($path);
- 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;
-out:
- { path => $path };
-}
-
-sub change_dir_prop {
- my ($self, $db, $prop, $value) = @_;
- return undef if $self->is_path_ignored($db->{path});
- $self->{dir_prop}->{$db->{path}} ||= {};
- $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
- undef;
-}
-
-sub absent_directory {
- my ($self, $path, $pb) = @_;
- return undef if $self->is_path_ignored($path);
- $self->{absent_dir}->{$pb->{path}} ||= [];
- push @{$self->{absent_dir}->{$pb->{path}}}, $path;
- undef;
-}
-
-sub absent_file {
- my ($self, $path, $pb) = @_;
- return undef if $self->is_path_ignored($path);
- $self->{absent_file}->{$pb->{path}} ||= [];
- push @{$self->{absent_file}->{$pb->{path}}}, $path;
- undef;
-}
-
-sub change_file_prop {
- my ($self, $fb, $prop, $value) = @_;
- return undef if $self->is_path_ignored($fb->{path});
- if ($prop eq 'svn:executable') {
- if ($fb->{mode_b} != 120000) {
- $fb->{mode_b} = defined $value ? 100755 : 100644;
- }
- } elsif ($prop eq 'svn:special') {
- $fb->{mode_b} = defined $value ? 120000 : 100644;
- } else {
- $self->{file_prop}->{$fb->{path}} ||= {};
- $self->{file_prop}->{$fb->{path}}->{$prop} = $value;
- }
- undef;
-}
-
-sub apply_textdelta {
- my ($self, $fb, $exp) = @_;
- return undef if $self->is_path_ignored($fb->{path});
- my $fh = $::_repository->temp_acquire('svn_delta');
- # $fh gets auto-closed() by SVN::TxDelta::apply(),
- # (but $base does not,) so dup() it for reading in close_file
- open my $dup, '<&', $fh or croak $!;
- my $base = $::_repository->temp_acquire('git_blob');
-
- if ($fb->{blob}) {
- my ($base_is_link, $size);
-
- if ($fb->{mode_a} eq '120000' &&
- ! $self->{empty_symlinks}->{$fb->{path}}) {
- print $base 'link ' or die "print $!\n";
- $base_is_link = 1;
- }
- retry:
- $size = $::_repository->cat_blob($fb->{blob}, $base);
- die "Failed to read object $fb->{blob}" if ($size < 0);
-
- if (defined $exp) {
- seek $base, 0, 0 or croak $!;
- my $got = ::md5sum($base);
- if ($got ne $exp) {
- my $err = "Checksum mismatch: ".
- "$fb->{path} $fb->{blob}\n" .
- "expected: $exp\n" .
- " got: $got\n";
- if ($base_is_link) {
- warn $err,
- "Retrying... (possibly ",
- "a bad symlink from SVN)\n";
- $::_repository->temp_reset($base);
- $base_is_link = 0;
- goto retry;
- }
- die $err;
- }
- }
- }
- seek $base, 0, 0 or croak $!;
- $fb->{fh} = $fh;
- $fb->{base} = $base;
- [ SVN::TxDelta::apply($base, $dup, undef, $fb->{path}, $fb->{pool}) ];
-}
-
-sub close_file {
- my ($self, $fb, $exp) = @_;
- return undef if $self->is_path_ignored($fb->{path});
-
- my $hash;
- my $path = $self->git_path($fb->{path});
- if (my $fh = $fb->{fh}) {
- if (defined $exp) {
- seek($fh, 0, 0) or croak $!;
- my $got = ::md5sum($fh);
- if ($got ne $exp) {
- die "Checksum mismatch: $path\n",
- "expected: $exp\n got: $got\n";
- }
- }
- if ($fb->{mode_b} == 120000) {
- sysseek($fh, 0, 0) or croak $!;
- my $rd = sysread($fh, my $buf, 5);
-
- if (!defined $rd) {
- croak "sysread: $!\n";
- } elsif ($rd == 0) {
- warn "$path has mode 120000",
- " but it points to nothing\n",
- "converting to an empty file with mode",
- " 100644\n";
- $fb->{mode_b} = '100644';
- } elsif ($buf ne 'link ') {
- warn "$path has mode 120000",
- " but is not a link\n";
- } else {
- my $tmp_fh = $::_repository->temp_acquire(
- 'svn_hash');
- my $res;
- while ($res = sysread($fh, my $str, 1024)) {
- my $out = syswrite($tmp_fh, $str, $res);
- defined($out) && $out == $res
- or croak("write ",
- Git::temp_path($tmp_fh),
- ": $!\n");
- }
- defined $res or croak $!;
-
- ($fh, $tmp_fh) = ($tmp_fh, $fh);
- Git::temp_release($tmp_fh, 1);
- }
- }
-
- $hash = $::_repository->hash_and_insert_object(
- Git::temp_path($fh));
- $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
-
- Git::temp_release($fb->{base}, 1);
- Git::temp_release($fh, 1);
- } else {
- $hash = $fb->{blob} or die "no blob information\n";
- }
- $fb->{pool}->clear;
- $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!;
- print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $::_q;
- undef;
-}
-
-sub abort_edit {
- my $self = shift;
- $self->{nr} = $self->{gii}->{nr};
- delete $self->{gii};
- $self->SUPER::abort_edit(@_);
-}
-
-sub close_edit {
- my $self = shift;
- $self->{git_commit_ok} = 1;
- $self->{nr} = $self->{gii}->{nr};
- delete $self->{gii};
- $self->SUPER::close_edit(@_);
-}
-
-package SVN::Git::Editor;
-use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
-use strict;
-use warnings;
-use Carp qw/croak/;
-use IO::File;
-
-sub new {
- my ($class, $opts) = @_;
- foreach (qw/svn_path r ra tree_a tree_b log editor_cb/) {
- die "$_ required!\n" unless (defined $opts->{$_});
- }
-
- my $pool = SVN::Pool->new;
- my $mods = generate_diff($opts->{tree_a}, $opts->{tree_b});
- my $types = check_diff_paths($opts->{ra}, $opts->{svn_path},
- $opts->{r}, $mods);
-
- # $opts->{ra} functions should not be used after this:
- my @ce = $opts->{ra}->get_commit_editor($opts->{log},
- $opts->{editor_cb}, $pool);
- my $self = SVN::Delta::Editor->new(@ce, $pool);
- bless $self, $class;
- foreach (qw/svn_path r tree_a tree_b/) {
- $self->{$_} = $opts->{$_};
- }
- $self->{url} = $opts->{ra}->{url};
- $self->{mods} = $mods;
- $self->{types} = $types;
- $self->{pool} = $pool;
- $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };
- $self->{rm} = { };
- $self->{path_prefix} = length $self->{svn_path} ?
- "$self->{svn_path}/" : '';
- $self->{config} = $opts->{config};
- $self->{mergeinfo} = $opts->{mergeinfo};
- return $self;
-}
-
-sub generate_diff {
- my ($tree_a, $tree_b) = @_;
- my @diff_tree = qw(diff-tree -z -r);
- if ($_cp_similarity) {
- push @diff_tree, "-C$_cp_similarity";
- } else {
- push @diff_tree, '-C';
- }
- push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
- push @diff_tree, "-l$_rename_limit" if defined $_rename_limit;
- push @diff_tree, $tree_a, $tree_b;
- my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
- local $/ = "\0";
- my $state = 'meta';
- my @mods;
- while (<$diff_fh>) {
- chomp $_; # this gets rid of the trailing "\0"
- if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
- ($::sha1)\s($::sha1)\s
- ([MTCRAD])\d*$/xo) {
- push @mods, { mode_a => $1, mode_b => $2,
- sha1_a => $3, sha1_b => $4,
- chg => $5 };
- if ($5 =~ /^(?:C|R)$/) {
- $state = 'file_a';
- } else {
- $state = 'file_b';
- }
- } elsif ($state eq 'file_a') {
- my $x = $mods[$#mods] or croak "Empty array\n";
- if ($x->{chg} !~ /^(?:C|R)$/) {
- croak "Error parsing $_, $x->{chg}\n";
- }
- $x->{file_a} = $_;
- $state = 'file_b';
- } elsif ($state eq 'file_b') {
- my $x = $mods[$#mods] or croak "Empty array\n";
- if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
- croak "Error parsing $_, $x->{chg}\n";
- }
- if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
- croak "Error parsing $_, $x->{chg}\n";
- }
- $x->{file_b} = $_;
- $state = 'meta';
- } else {
- croak "Error parsing $_\n";
- }
- }
- command_close_pipe($diff_fh, $ctx);
- \@mods;
-}
-
-sub check_diff_paths {
- my ($ra, $pfx, $rev, $mods) = @_;
- my %types;
- $pfx .= '/' if length $pfx;
-
- sub type_diff_paths {
- my ($ra, $types, $path, $rev) = @_;
- my @p = split m#/+#, $path;
- my $c = shift @p;
- unless (defined $types->{$c}) {
- $types->{$c} = $ra->check_path($c, $rev);
- }
- while (@p) {
- $c .= '/' . shift @p;
- next if defined $types->{$c};
- $types->{$c} = $ra->check_path($c, $rev);
- }
- }
-
- foreach my $m (@$mods) {
- foreach my $f (qw/file_a file_b/) {
- next unless defined $m->{$f};
- my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#);
- if (length $pfx.$dir && ! defined $types{$dir}) {
- type_diff_paths($ra, \%types, $pfx.$dir, $rev);
- }
- }
- }
- \%types;
-}
-
-sub split_path {
- return ($_[0] =~ m#^(.*?)/?([^/]+)$#);
-}
-
-sub repo_path {
- my ($self, $path) = @_;
- if (my $enc = $self->{pathnameencoding}) {
- require Encode;
- Encode::from_to($path, $enc, 'UTF-8');
- }
- $self->{path_prefix}.(defined $path ? $path : '');
-}
-
-sub url_path {
- my ($self, $path) = @_;
- if ($self->{url} =~ m#^https?://#) {
- $path =~ s!([^~a-zA-Z0-9_./-])!uc sprintf("%%%02x",ord($1))!eg;
- }
- $self->{url} . '/' . $self->repo_path($path);
-}
-
-sub rmdirs {
- my ($self) = @_;
- my $rm = $self->{rm};
- delete $rm->{''}; # we never delete the url we're tracking
- return unless %$rm;
-
- foreach (keys %$rm) {
- my @d = split m#/#, $_;
- my $c = shift @d;
- $rm->{$c} = 1;
- while (@d) {
- $c .= '/' . shift @d;
- $rm->{$c} = 1;
- }
- }
- delete $rm->{$self->{svn_path}};
- delete $rm->{''}; # we never delete the url we're tracking
- return unless %$rm;
-
- my ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/,
- $self->{tree_b});
- local $/ = "\0";
- while (<$fh>) {
- chomp;
- my @dn = split m#/#, $_;
- while (pop @dn) {
- delete $rm->{join '/', @dn};
- }
- unless (%$rm) {
- close $fh;
- return;
- }
- }
- command_close_pipe($fh, $ctx);
-
- my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
- foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
- $self->close_directory($bat->{$d}, $p);
- my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
- print "\tD+\t$d/\n" unless $::_q;
- $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
- delete $bat->{$d};
- }
-}
-
-sub open_or_add_dir {
- my ($self, $full_path, $baton) = @_;
- my $t = $self->{types}->{$full_path};
- if (!defined $t) {
- die "$full_path not known in r$self->{r} or we have a bug!\n";
- }
- {
- no warnings 'once';
- # SVN::Node::none and SVN::Node::file are used only once,
- # so we're shutting up Perl's warnings about them.
- if ($t == $SVN::Node::none) {
- return $self->add_directory($full_path, $baton,
- undef, -1, $self->{pool});
- } elsif ($t == $SVN::Node::dir) {
- return $self->open_directory($full_path, $baton,
- $self->{r}, $self->{pool});
- } # no warnings 'once'
- print STDERR "$full_path already exists in repository at ",
- "r$self->{r} and it is not a directory (",
- ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
- } # no warnings 'once'
- exit 1;
-}
-
-sub ensure_path {
- my ($self, $path) = @_;
- my $bat = $self->{bat};
- my $repo_path = $self->repo_path($path);
- return $bat->{''} unless (length $repo_path);
- my @p = split m#/+#, $repo_path;
- my $c = shift @p;
- $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
- while (@p) {
- my $c0 = $c;
- $c .= '/' . shift @p;
- $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0});
- }
- return $bat->{$c};
-}
-
-# Subroutine to convert a globbing pattern to a regular expression.
-# From perl cookbook.
-sub glob2pat {
- my $globstr = shift;
- my %patmap = ('*' => '.*', '?' => '.', '[' => '[', ']' => ']');
- $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
- return '^' . $globstr . '$';
-}
-
-sub check_autoprop {
- my ($self, $pattern, $properties, $file, $fbat) = @_;
- # Convert the globbing pattern to a regular expression.
- my $regex = glob2pat($pattern);
- # Check if the pattern matches the file name.
- if($file =~ m/($regex)/) {
- # Parse the list of properties to set.
- my @props = split(/;/, $properties);
- foreach my $prop (@props) {
- # Parse 'name=value' syntax and set the property.
- if ($prop =~ /([^=]+)=(.*)/) {
- my ($n,$v) = ($1,$2);
- for ($n, $v) {
- s/^\s+//; s/\s+$//;
- }
- $self->change_file_prop($fbat, $n, $v);
- }
- }
- }
-}
-
-sub apply_autoprops {
- my ($self, $file, $fbat) = @_;
- my $conf_t = ${$self->{config}}{'config'};
- no warnings 'once';
- # Check [miscellany]/enable-auto-props in svn configuration.
- if (SVN::_Core::svn_config_get_bool(
- $conf_t,
- $SVN::_Core::SVN_CONFIG_SECTION_MISCELLANY,
- $SVN::_Core::SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS,
- 0)) {
- # Auto-props are enabled. Enumerate them to look for matches.
- my $callback = sub {
- $self->check_autoprop($_[0], $_[1], $file, $fbat);
- };
- SVN::_Core::svn_config_enumerate(
- $conf_t,
- $SVN::_Core::SVN_CONFIG_SECTION_AUTO_PROPS,
- $callback);
- }
-}
-
-sub A {
- my ($self, $m) = @_;
- my ($dir, $file) = split_path($m->{file_b});
- my $pbat = $self->ensure_path($dir);
- my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
- undef, -1);
- print "\tA\t$m->{file_b}\n" unless $::_q;
- $self->apply_autoprops($file, $fbat);
- $self->chg_file($fbat, $m);
- $self->close_file($fbat,undef,$self->{pool});
-}
-
-sub C {
- my ($self, $m) = @_;
- my ($dir, $file) = split_path($m->{file_b});
- my $pbat = $self->ensure_path($dir);
- my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
- $self->url_path($m->{file_a}), $self->{r});
- print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
- $self->chg_file($fbat, $m);
- $self->close_file($fbat,undef,$self->{pool});
-}
-
-sub delete_entry {
- my ($self, $path, $pbat) = @_;
- my $rpath = $self->repo_path($path);
- my ($dir, $file) = split_path($rpath);
- $self->{rm}->{$dir} = 1;
- $self->SUPER::delete_entry($rpath, $self->{r}, $pbat, $self->{pool});
-}
-
-sub R {
- my ($self, $m) = @_;
- my ($dir, $file) = split_path($m->{file_b});
- my $pbat = $self->ensure_path($dir);
- my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
- $self->url_path($m->{file_a}), $self->{r});
- print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
- $self->apply_autoprops($file, $fbat);
- $self->chg_file($fbat, $m);
- $self->close_file($fbat,undef,$self->{pool});
-
- ($dir, $file) = split_path($m->{file_a});
- $pbat = $self->ensure_path($dir);
- $self->delete_entry($m->{file_a}, $pbat);
-}
-
-sub M {
- my ($self, $m) = @_;
- my ($dir, $file) = split_path($m->{file_b});
- my $pbat = $self->ensure_path($dir);
- my $fbat = $self->open_file($self->repo_path($m->{file_b}),
- $pbat,$self->{r},$self->{pool});
- print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q;
- $self->chg_file($fbat, $m);
- $self->close_file($fbat,undef,$self->{pool});
-}
-
-sub T { shift->M(@_) }
-
-sub change_file_prop {
- my ($self, $fbat, $pname, $pval) = @_;
- $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
-}
-
-sub change_dir_prop {
- my ($self, $pbat, $pname, $pval) = @_;
- $self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool});
-}
-
-sub _chg_file_get_blob ($$$$) {
- my ($self, $fbat, $m, $which) = @_;
- my $fh = $::_repository->temp_acquire("git_blob_$which");
- if ($m->{"mode_$which"} =~ /^120/) {
- print $fh 'link ' or croak $!;
- $self->change_file_prop($fbat,'svn:special','*');
- } elsif ($m->{mode_a} =~ /^120/ && $m->{"mode_$which"} !~ /^120/) {
- $self->change_file_prop($fbat,'svn:special',undef);
- }
- my $blob = $m->{"sha1_$which"};
- return ($fh,) if ($blob =~ /^0{40}$/);
- my $size = $::_repository->cat_blob($blob, $fh);
- croak "Failed to read object $blob" if ($size < 0);
- $fh->flush == 0 or croak $!;
- seek $fh, 0, 0 or croak $!;
-
- my $exp = ::md5sum($fh);
- seek $fh, 0, 0 or croak $!;
- return ($fh, $exp);
-}
-
-sub chg_file {
- my ($self, $fbat, $m) = @_;
- if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
- $self->change_file_prop($fbat,'svn:executable','*');
- } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
- $self->change_file_prop($fbat,'svn:executable',undef);
- }
- my ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';
- my ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';
- my $pool = SVN::Pool->new;
- my $atd = $self->apply_textdelta($fbat, $exp_a, $pool);
- if (-s $fh_a) {
- my $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);
- my $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);
- if (defined $res) {
- die "Unexpected result from send_txstream: $res\n",
- "(SVN::Core::VERSION: $SVN::Core::VERSION)\n";
- }
- } else {
- my $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);
- die "Checksum mismatch\nexpected: $exp_b\ngot: $got\n"
- if ($got ne $exp_b);
- }
- Git::temp_release($fh_b, 1);
- Git::temp_release($fh_a, 1);
- $pool->clear;
-}
-
-sub D {
- my ($self, $m) = @_;
- my ($dir, $file) = split_path($m->{file_b});
- my $pbat = $self->ensure_path($dir);
- print "\tD\t$m->{file_b}\n" unless $::_q;
- $self->delete_entry($m->{file_b}, $pbat);
-}
-
-sub close_edit {
- my ($self) = @_;
- my ($p,$bat) = ($self->{pool}, $self->{bat});
- foreach (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$bat) {
- next if $_ eq '';
- $self->close_directory($bat->{$_}, $p);
- }
- $self->close_directory($bat->{''}, $p);
- $self->SUPER::close_edit($p);
- $p->clear;
-}
-
-sub abort_edit {
- my ($self) = @_;
- $self->SUPER::abort_edit($self->{pool});
-}
-
-sub DESTROY {
- my $self = shift;
- $self->SUPER::DESTROY(@_);
- $self->{pool}->clear;
-}
-
-# this drives the editor
-sub apply_diff {
- my ($self) = @_;
- my $mods = $self->{mods};
- my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
- foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
- my $f = $m->{chg};
- if (defined $o{$f}) {
- $self->$f($m);
- } else {
- fatal("Invalid change type: $f");
- }
- }
-
- if (defined($self->{mergeinfo})) {
- $self->change_dir_prop($self->{bat}{''}, "svn:mergeinfo",
- $self->{mergeinfo});
- }
- $self->rmdirs if $_rmdir;
- if (@$mods == 0) {
- $self->abort_edit;
- } else {
- $self->close_edit;
- }
- return scalar @$mods;
-}
-
-package Git::SVN::Ra;
-use vars qw/@ISA $config_dir $_log_window_size/;
-use strict;
-use warnings;
-my ($ra_invalid, $can_do_switch, %ignored_err, $RA);
-
-BEGIN {
- # enforce temporary pool usage for some simple functions
- no strict 'refs';
- for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root
- get_file/) {
- my $SUPER = "SUPER::$f";
- *$f = sub {
- my $self = shift;
- my $pool = SVN::Pool->new;
- my @ret = $self->$SUPER(@_,$pool);
- $pool->clear;
- wantarray ? @ret : $ret[0];
- };
- }
-}
-
-sub _auth_providers () {
- [
- SVN::Client::get_simple_provider(),
- SVN::Client::get_ssl_server_trust_file_provider(),
- SVN::Client::get_simple_prompt_provider(
- \&Git::SVN::Prompt::simple, 2),
- SVN::Client::get_ssl_client_cert_file_provider(),
- SVN::Client::get_ssl_client_cert_prompt_provider(
- \&Git::SVN::Prompt::ssl_client_cert, 2),
- SVN::Client::get_ssl_client_cert_pw_file_provider(),
- SVN::Client::get_ssl_client_cert_pw_prompt_provider(
- \&Git::SVN::Prompt::ssl_client_cert_pw, 2),
- SVN::Client::get_username_provider(),
- SVN::Client::get_ssl_server_trust_prompt_provider(
- \&Git::SVN::Prompt::ssl_server_trust),
- SVN::Client::get_username_prompt_provider(
- \&Git::SVN::Prompt::username, 2)
- ]
-}
-
-sub escape_uri_only {
- my ($uri) = @_;
- my @tmp;
- foreach (split m{/}, $uri) {
- s/([^~\w.%+-]|%(?![a-fA-F0-9]{2}))/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!/+$!!;
- return $RA if ($RA && $RA->{url} eq $url);
-
- ::_req_svn();
-
- SVN::_Core::svn_config_ensure($config_dir, undef);
- my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);
- my $config = SVN::Core::config_get_config($config_dir);
- $RA = undef;
- my $dont_store_passwords = 1;
- my $conf_t = ${$config}{'config'};
- {
- no warnings 'once';
- # The usage of $SVN::_Core::SVN_CONFIG_* variables
- # produces warnings that variables are used only once.
- # I had not found the better way to shut them up, so
- # the warnings of type 'once' are disabled in this block.
- if (SVN::_Core::svn_config_get_bool($conf_t,
- $SVN::_Core::SVN_CONFIG_SECTION_AUTH,
- $SVN::_Core::SVN_CONFIG_OPTION_STORE_PASSWORDS,
- 1) == 0) {
- SVN::_Core::svn_auth_set_parameter($baton,
- $SVN::_Core::SVN_AUTH_PARAM_DONT_STORE_PASSWORDS,
- bless (\$dont_store_passwords, "_p_void"));
- }
- if (SVN::_Core::svn_config_get_bool($conf_t,
- $SVN::_Core::SVN_CONFIG_SECTION_AUTH,
- $SVN::_Core::SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
- 1) == 0) {
- $Git::SVN::Prompt::_no_auth_cache = 1;
- }
- } # no warnings 'once'
- 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(/|$)##;
- $self->{cache} = { check_path => { r => 0, data => {} },
- get_dir => { r => 0, data => {} } };
- $RA = bless $self, $class;
-}
-
-sub check_path {
- my ($self, $path, $r) = @_;
- my $cache = $self->{cache}->{check_path};
- if ($r == $cache->{r} && exists $cache->{data}->{$path}) {
- return $cache->{data}->{$path};
- }
- my $pool = SVN::Pool->new;
- my $t = $self->SUPER::check_path($path, $r, $pool);
- $pool->clear;
- if ($r != $cache->{r}) {
- %{$cache->{data}} = ();
- $cache->{r} = $r;
- }
- $cache->{data}->{$path} = $t;
-}
-
-sub get_dir {
- my ($self, $dir, $r) = @_;
- my $cache = $self->{cache}->{get_dir};
- if ($r == $cache->{r}) {
- if (my $x = $cache->{data}->{$dir}) {
- return wantarray ? @$x : $x->[0];
- }
- }
- my $pool = SVN::Pool->new;
- my ($d, undef, $props) = $self->SUPER::get_dir($dir, $r, $pool);
- my %dirents = map { $_ => { kind => $d->{$_}->kind } } keys %$d;
- $pool->clear;
- if ($r != $cache->{r}) {
- %{$cache->{data}} = ();
- $cache->{r} = $r;
- }
- $cache->{data}->{$dir} = [ \%dirents, $r, $props ];
- wantarray ? (\%dirents, $r, $props) : \%dirents;
-}
-
-sub DESTROY {
- # do not call the real DESTROY since we store ourselves in $RA
-}
-
-# get_log(paths, start, end, limit,
-# discover_changed_paths, strict_node_history, receiver)
-sub get_log {
- my ($self, @args) = @_;
- my $pool = SVN::Pool->new;
-
- # svn_log_changed_path_t objects passed to get_log are likely to be
- # overwritten even if only the refs are copied to an external variable,
- # so we should dup the structures in their entirety. Using an
- # externally passed pool (instead of our temporary and quickly cleared
- # pool in Git::SVN::Ra) does not help matters at all...
- my $receiver = pop @args;
- my $prefix = "/".$self->{svn_path};
- $prefix =~ s#/+($)##;
- my $prefix_regex = qr#^\Q$prefix\E#;
- push(@args, sub {
- my ($paths) = $_[0];
- return &$receiver(@_) unless $paths;
- $_[0] = ();
- foreach my $p (keys %$paths) {
- my $i = $paths->{$p};
- # Make path relative to our url, not repos_root
- $p =~ s/$prefix_regex//;
- my %s = map { $_ => $i->$_; }
- qw/copyfrom_path copyfrom_rev action/;
- if ($s{'copyfrom_path'}) {
- $s{'copyfrom_path'} =~ s/$prefix_regex//;
- }
- $_[0]{$p} = \%s;
- }
- &$receiver(@_);
- });
-
-
- # the limit parameter was not supported in SVN 1.1.x, so we
- # drop it. Therefore, the receiver callback passed to it
- # is made aware of this limitation by being wrapped if
- # the limit passed to is being wrapped.
- if ($SVN::Core::VERSION le '1.2.0') {
- my $limit = splice(@args, 3, 1);
- if ($limit > 0) {
- my $receiver = pop @args;
- push(@args, sub { &$receiver(@_) if (--$limit >= 0) });
- }
- }
- my $ret = $self->SUPER::get_log(@args, $pool);
- $pool->clear;
- $ret;
-}
-
-sub trees_match {
- my ($self, $url1, $rev1, $url2, $rev2) = @_;
- my $ctx = SVN::Client->new(auth => _auth_providers);
- my $out = IO::File->new_tmpfile;
-
- # older SVN (1.1.x) doesn't take $pool as the last parameter for
- # $ctx->diff(), so we'll create a default one
- my $pool = SVN::Pool->new_default_sub;
-
- $ra_invalid = 1; # this will open a new SVN::Ra connection to $url1
- $ctx->diff([], $url1, $rev1, $url2, $rev2, 1, 1, 0, $out, $out);
- $out->flush;
- my $ret = (($out->stat)[7] == 0);
- close $out or croak $!;
-
- $ret;
-}
-
-sub get_commit_editor {
- my ($self, $log, $cb, $pool) = @_;
- my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
- $self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
-}
-
-sub gs_do_update {
- my ($self, $rev_a, $rev_b, $gs, $editor) = @_;
- my $new = ($rev_a == $rev_b);
- my $path = $gs->{path};
-
- if ($new && -e $gs->{index}) {
- unlink $gs->{index} or die
- "Couldn't unlink index: $gs->{index}: $!\n";
- }
- my $pool = SVN::Pool->new;
- $editor->set_path_strip($path);
- my (@pc) = split m#/#, $path;
- my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),
- 1, $editor, $pool);
- my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
-
- # Since we can't rely on svn_ra_reparent being available, we'll
- # just have to do some magic with set_path to make it so
- # we only want a partial path.
- my $sp = '';
- my $final = join('/', @pc);
- while (@pc) {
- $reporter->set_path($sp, $rev_b, 0, @lock, $pool);
- $sp .= '/' if length $sp;
- $sp .= shift @pc;
- }
- die "BUG: '$sp' != '$final'\n" if ($sp ne $final);
-
- $reporter->set_path($sp, $rev_a, $new, @lock, $pool);
-
- $reporter->finish_report($pool);
- $pool->clear;
- $editor->{git_commit_ok};
-}
-
-# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and
-# svn_ra_reparent didn't work before 1.4)
-sub gs_do_switch {
- my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_;
- my $path = $gs->{path};
- my $pool = SVN::Pool->new;
-
- my $full_url = $self->{url};
- my $old_url = $full_url;
- $full_url .= '/' . $path if length $path;
- my ($ra, $reparented);
-
- if ($old_url =~ m#^svn(\+ssh)?://# ||
- ($full_url =~ m#^https?://# &&
- escape_url($full_url) ne $full_url)) {
- $_[0] = undef;
- $self = undef;
- $RA = undef;
- $ra = Git::SVN::Ra->new($full_url);
- $ra_invalid = 1;
- } elsif ($old_url ne $full_url) {
- SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);
- $self->{url} = $full_url;
- $reparented = 1;
- }
-
- $ra ||= $self;
- $url_b = escape_url($url_b);
- my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
- my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
- $reporter->set_path('', $rev_a, 0, @lock, $pool);
- $reporter->finish_report($pool);
-
- if ($reparented) {
- SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool);
- $self->{url} = $old_url;
- }
-
- $pool->clear;
- $editor->{git_commit_ok};
-}
-
-sub longest_common_path {
- my ($gsv, $globs) = @_;
- my %common;
- my $common_max = scalar @$gsv;
-
- foreach my $gs (@$gsv) {
- my @tmp = split m#/#, $gs->{path};
- my $p = '';
- foreach (@tmp) {
- $p .= length($p) ? "/$_" : $_;
- $common{$p} ||= 0;
- $common{$p}++;
- }
- }
- $globs ||= [];
- $common_max += scalar @$globs;
- foreach my $glob (@$globs) {
- my @tmp = split m#/#, $glob->{path}->{left};
- my $p = '';
- foreach (@tmp) {
- $p .= length($p) ? "/$_" : $_;
- $common{$p} ||= 0;
- $common{$p}++;
- }
- }
-
- my $longest_path = '';
- foreach (sort {length $b <=> length $a} keys %common) {
- if ($common{$_} == $common_max) {
- $longest_path = $_;
- last;
- }
- }
- $longest_path;
-}
-
-sub gs_fetch_loop_common {
- my ($self, $base, $head, $gsv, $globs) = @_;
- return if ($base > $head);
- my $inc = $_log_window_size;
- my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
- my $longest_path = longest_common_path($gsv, $globs);
- my $ra_url = $self->{url};
- my $find_trailing_edge;
- while (1) {
- my %revs;
- my $err;
- my $err_handler = $SVN::Error::handler;
- $SVN::Error::handler = sub {
- ($err) = @_;
- skip_unknown_revs($err);
- };
- sub _cb {
- my ($paths, $r, $author, $date, $log) = @_;
- [ $paths,
- { author => $author, date => $date, log => $log } ];
- }
- $self->get_log([$longest_path], $min, $max, 0, 1, 1,
- sub { $revs{$_[1]} = _cb(@_) });
- if ($err) {
- print "Checked through r$max\r";
- } else {
- $find_trailing_edge = 1;
- }
- if ($err and $find_trailing_edge) {
- print STDERR "Path '$longest_path' ",
- "was probably deleted:\n",
- $err->expanded_message,
- "\nWill attempt to follow ",
- "revisions r$min .. r$max ",
- "committed before the deletion\n";
- my $hi = $max;
- while (--$hi >= $min) {
- my $ok;
- $self->get_log([$longest_path], $min, $hi,
- 0, 1, 1, sub {
- $ok = $_[1];
- $revs{$_[1]} = _cb(@_) });
- if ($ok) {
- print STDERR "r$min .. r$ok OK\n";
- last;
- }
- }
- $find_trailing_edge = 0;
- }
- $SVN::Error::handler = $err_handler;
-
- my %exists = map { $_->{path} => $_ } @$gsv;
- foreach my $r (sort {$a <=> $b} keys %revs) {
- my ($paths, $logged) = @{$revs{$r}};
-
- foreach my $gs ($self->match_globs(\%exists, $paths,
- $globs, $r)) {
- if ($gs->rev_map_max >= $r) {
- next;
- }
- next unless $gs->match_paths($paths, $r);
- $gs->{logged_rev_props} = $logged;
- if (my $last_commit = $gs->last_commit) {
- $gs->assert_index_clean($last_commit);
- }
- my $log_entry = $gs->do_fetch($paths, $r);
- if ($log_entry) {
- $gs->do_git_commit($log_entry);
- }
- $INDEX_FILES{$gs->{index}} = 1;
- }
- foreach my $g (@$globs) {
- my $k = "svn-remote.$g->{remote}." .
- "$g->{t}-maxRev";
- Git::SVN::tmp_config($k, $r);
- }
- if ($ra_invalid) {
- $_[0] = undef;
- $self = undef;
- $RA = undef;
- $self = Git::SVN::Ra->new($ra_url);
- $ra_invalid = undef;
- }
- }
- # 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 $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";
- Git::SVN::tmp_config($k, $max);
- }
- last if $max >= $head;
- $min = $max + 1;
- $max += $inc;
- $max = $head if ($max > $head);
- }
- Git::SVN::gc();
-}
-
-sub get_dir_globbed {
- my ($self, $left, $depth, $r) = @_;
-
- my @x = eval { $self->get_dir($left, $r) };
- return unless scalar @x == 3;
- my $dirents = $x[0];
- my @finalents;
- foreach my $de (keys %$dirents) {
- next if $dirents->{$de}->{kind} != $SVN::Node::dir;
- if ($depth > 1) {
- my @args = ("$left/$de", $depth - 1, $r);
- foreach my $dir ($self->get_dir_globbed(@args)) {
- push @finalents, "$de/$dir";
- }
- } else {
- push @finalents, $de;
- }
- }
- @finalents;
-}
-
-sub match_globs {
- my ($self, $exists, $paths, $globs, $r) = @_;
-
- sub get_dir_check {
- my ($self, $exists, $g, $r) = @_;
-
- my @dirs = $self->get_dir_globbed($g->{path}->{left},
- $g->{path}->{depth},
- $r);
-
- foreach my $de (@dirs) {
- my $p = $g->{path}->full_path($de);
- next if $exists->{$p};
- next if (length $g->{path}->{right} &&
- ($self->check_path($p, $r) !=
- $SVN::Node::dir));
- next unless $p =~ /$g->{path}->{regex}/;
- $exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
- $g->{ref}->full_path($de), 1);
- }
- }
- foreach my $g (@$globs) {
- if (my $path = $paths->{"/$g->{path}->{left}"}) {
- if ($path->{action} =~ /^[AR]$/) {
- get_dir_check($self, $exists, $g, $r);
- }
- }
- foreach (keys %$paths) {
- if (/$g->{path}->{left_regex}/ &&
- !/$g->{path}->{regex}/) {
- next if $paths->{$_}->{action} !~ /^[AR]$/;
- get_dir_check($self, $exists, $g, $r);
- }
- next unless /$g->{path}->{regex}/;
- my $p = $1;
- my $pathname = $g->{path}->full_path($p);
- next if $exists->{$pathname};
- next if ($self->check_path($pathname, $r) !=
- $SVN::Node::dir);
- $exists->{$pathname} = Git::SVN->init(
- $self->{url}, $pathname, undef,
- $g->{ref}->full_path($p), 1);
- }
- my $c = '';
- foreach (split m#/#, $g->{path}->{left}) {
- $c .= "/$_";
- next unless ($paths->{$c} &&
- ($paths->{$c}->{action} =~ /^[AR]$/));
- get_dir_check($self, $exists, $g, $r);
- }
- }
- values %$exists;
-}
-
-sub minimize_url {
- my ($self) = @_;
- return $self->{url} if ($self->{url} eq $self->{repos_root});
- my $url = $self->{repos_root};
- my @components = split(m!/!, $self->{svn_path});
- my $c = '';
- do {
- $url .= "/$c" if length $c;
- eval {
- my $ra = (ref $self)->new($url);
- my $latest = $ra->get_latest_revnum;
- $ra->get_log("", $latest, 0, 1, 0, 1, sub {});
- };
- } while ($@ && ($c = shift @components));
- $url;
-}
-
-sub can_do_switch {
- my $self = shift;
- unless (defined $can_do_switch) {
- my $pool = SVN::Pool->new;
- my $rep = eval {
- $self->do_switch(1, '', 0, $self->{url},
- SVN::Delta::Editor->new, $pool);
- };
- if ($@) {
- $can_do_switch = 0;
- } else {
- $rep->abort_report($pool);
- $can_do_switch = 1;
- }
- $pool->clear;
- }
- $can_do_switch;
-}
-
-sub skip_unknown_revs {
- my ($err) = @_;
- my $errno = $err->apr_err();
- # Maybe the branch we're tracking didn't
- # exist when the repo started, so it's
- # not an error if it doesn't, just continue
- #
- # Wonderfully consistent library, eh?
- # 160013 - svn:// and file://
- # 175002 - http(s)://
- # 175007 - http(s):// (this repo required authorization, too...)
- # More codes may be discovered later...
- if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
- my $err_key = $err->expanded_message;
- # revision numbers change every time, filter them out
- $err_key =~ s/\d+/\0/g;
- $err_key = "$errno\0$err_key";
- unless ($ignored_err{$err_key}) {
- 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;
- }
- die "Error from SVN, ($errno): ", $err->expanded_message,"\n";
-}
-
package Git::SVN::Log;
use strict;
use warnings;
use POSIX qw/strftime/;
-use Time::Local;
use constant commit_log_separator => ('-' x 72) . "\n";
use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
%rusers $show_commit $incremental/;
@@ -5605,11 +4458,8 @@ sub run_pager {
}
sub format_svn_date {
- # some systmes don't handle or mishandle %z, so be creative.
my $t = shift || time;
- my $gm = timelocal(gmtime($t));
- my $sign = qw( + + - )[ $t <=> $gm ];
- my $gmoff = sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+ my $gmoff = Git::SVN::get_tz($t);
return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
}
diff --git a/git-web--browse.sh b/git-web--browse.sh
index e9de241..1e82726 100755
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -156,7 +156,7 @@ firefox|iceweasel|seamonkey|iceape)
;;
google-chrome|chrome|chromium|chromium-browser)
# No need to specify newTab. It's default in chromium
- eval "$browser_path" "$@" &
+ "$browser_path" "$@" &
;;
konqueror)
case "$(basename "$browser_path")" in
@@ -164,10 +164,10 @@ 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 "$@"
+ "$browser_path" newTab "$@" &
;;
kfmclient)
- eval "$browser_path" newTab "$@"
+ "$browser_path" newTab "$@" &
;;
*)
"$browser_path" "$@" &
@@ -175,7 +175,7 @@ konqueror)
esac
;;
w3m|elinks|links|lynx|open)
- eval "$browser_path" "$@"
+ "$browser_path" "$@"
;;
start)
exec "$browser_path" '"web-browse"' "$@"
@@ -185,7 +185,7 @@ opera|dillo)
;;
*)
if test -n "$browser_cmd"; then
- ( eval $browser_cmd "$@" )
+ ( eval "$browser_cmd \"\$@\"" )
fi
;;
esac
diff --git a/git.c b/git.c
index 89721d4..8788b32 100644
--- a/git.c
+++ b/git.c
@@ -7,13 +7,13 @@
const char git_usage_string[] =
"git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
- " [-p|--paginate|--no-pager] [--no-replace-objects]\n"
- " [--bare] [--git-dir=<path>] [--work-tree=<path>]\n"
+ " [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\n"
+ " [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
" [-c name=value] [--help]\n"
" <command> [<args>]";
const char git_more_info_string[] =
- "See 'git help <command>' for more information on a specific command.";
+ N_("See 'git help <command>' for more information on a specific command.");
static struct startup_info git_startup_info;
static int use_pager = -1;
@@ -126,6 +126,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
if (envchanged)
*envchanged = 1;
+ } else if (!strcmp(cmd, "--namespace")) {
+ if (*argc < 2) {
+ fprintf(stderr, "No namespace given for --namespace.\n" );
+ usage(git_usage_string);
+ }
+ setenv(GIT_NAMESPACE_ENVIRONMENT, (*argv)[1], 1);
+ if (envchanged)
+ *envchanged = 1;
+ (*argv)++;
+ (*argc)--;
+ } else if (!prefixcmp(cmd, "--namespace=")) {
+ setenv(GIT_NAMESPACE_ENVIRONMENT, cmd + 12, 1);
+ if (envchanged)
+ *envchanged = 1;
} else if (!strcmp(cmd, "--work-tree")) {
if (*argc < 2) {
fprintf(stderr, "No directory given for --work-tree.\n" );
@@ -183,8 +197,6 @@ static int handle_alias(int *argcp, const char ***argv)
if (alias_string[0] == '!') {
const char **alias_argv;
int argc = *argcp, i;
- struct strbuf sb = STRBUF_INIT;
- const char *env[2];
commit_pager_choice();
@@ -195,13 +207,7 @@ static int handle_alias(int *argcp, const char ***argv)
alias_argv[i] = (*argv)[i];
alias_argv[argc] = NULL;
- strbuf_addstr(&sb, "GIT_PREFIX=");
- if (subdir)
- strbuf_addstr(&sb, subdir);
- env[0] = sb.buf;
- env[1] = NULL;
- ret = run_command_v_opt_cd_env(alias_argv, RUN_USING_SHELL, NULL, env);
- strbuf_release(&sb);
+ ret = run_command_v_opt(alias_argv, RUN_USING_SHELL);
if (ret >= 0) /* normal exit */
exit(ret);
@@ -250,8 +256,6 @@ static int handle_alias(int *argcp, const char ***argv)
return ret;
}
-const char git_version_string[] = GIT_VERSION;
-
#define RUN_SETUP (1<<0)
#define RUN_SETUP_GENTLY (1<<1)
#define USE_PAGER (1<<2)
@@ -328,7 +332,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive },
- { "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE },
+ { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
{ "blame", cmd_blame, RUN_SETUP },
{ "branch", cmd_branch, RUN_SETUP },
{ "bundle", cmd_bundle, RUN_SETUP_GENTLY },
@@ -342,10 +346,12 @@ static void handle_internal_command(int argc, const char **argv)
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
{ "clone", cmd_clone },
+ { "column", cmd_column, RUN_SETUP_GENTLY },
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config, RUN_SETUP_GENTLY },
{ "count-objects", cmd_count_objects, RUN_SETUP },
+ { "credential", cmd_credential, RUN_SETUP_GENTLY },
{ "describe", cmd_describe, RUN_SETUP },
{ "diff", cmd_diff },
{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
@@ -428,6 +434,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "update-ref", cmd_update_ref, RUN_SETUP },
{ "update-server-info", cmd_update_server_info, RUN_SETUP },
{ "upload-archive", cmd_upload_archive },
+ { "upload-archive--writer", cmd_upload_archive_writer },
{ "var", cmd_var, RUN_SETUP_GENTLY },
{ "verify-pack", cmd_verify_pack },
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
@@ -467,6 +474,8 @@ static void execv_dashed_external(const char **argv)
const char *tmp;
int status;
+ if (use_pager == -1)
+ use_pager = check_pager_config(argv[0]);
commit_pager_choice();
strbuf_addf(&cmd, "git-%s", argv[0]);
@@ -486,7 +495,7 @@ static void execv_dashed_external(const char **argv)
* if we fail because the command is not found, it is
* OK to return. Otherwise, we just pass along the status code.
*/
- status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
+ status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE | RUN_CLEAN_ON_EXIT);
if (status >= 0 || errno != ENOENT)
exit(status);
@@ -529,6 +538,8 @@ int main(int argc, const char **argv)
if (!cmd)
cmd = "git-help";
+ git_setup_gettext();
+
/*
* "git-xxxx" is the same as "git xxxx", but we obviously:
*
diff --git a/git.spec.in b/git.spec.in
index 91c8462..d61d537 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -101,6 +101,7 @@ Group: Development/Libraries
Requires: git = %{version}-%{release}
Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
BuildRequires: perl(Error)
+BuildRequires: perl(ExtUtils::MakeMaker)
%description -n perl-Git
Perl interface to Git
@@ -134,6 +135,7 @@ find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
%else
rm -rf $RPM_BUILD_ROOT%{_mandir}
%endif
+rm -rf $RPM_BUILD_ROOT%{_datadir}/locale
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
install -m 644 -T contrib/completion/git-completion.bash $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/git
@@ -199,7 +201,11 @@ rm -rf $RPM_BUILD_ROOT
%files -n gitweb
%defattr(-,root,root)
+%doc gitweb/README gitweb/INSTALL Documentation/*gitweb*.txt
%{_datadir}/gitweb
+%{!?_without_docs: %{_mandir}/man1/*gitweb*.1*}
+%{!?_without_docs: %{_mandir}/man5/*gitweb*.5*}
+%{!?_without_docs: %doc Documentation/*gitweb*.html }
%files -n perl-Git -f perl-files
%defattr(-,root,root)
@@ -208,6 +214,9 @@ rm -rf $RPM_BUILD_ROOT
# No files for you!
%changelog
+* Sun Sep 18 2011 Jakub Narebski <jnareb@gmail.com>
+- Add gitweb manpages to 'gitweb' subpackage
+
* Wed Jun 30 2010 Junio C Hamano <gitster@pobox.com>
- Add 'gitweb' subpackage.
diff --git a/git_remote_helpers/git/exporter.py b/git_remote_helpers/git/exporter.py
index f40f9d6..9ee5f96 100644
--- a/git_remote_helpers/git/exporter.py
+++ b/git_remote_helpers/git/exporter.py
@@ -2,6 +2,8 @@ import os
import subprocess
import sys
+from git_remote_helpers.util import check_call
+
class GitExporter(object):
"""An exporter for testgit repositories.
@@ -15,7 +17,7 @@ class GitExporter(object):
self.repo = repo
- def export_repo(self, base):
+ def export_repo(self, base, refs=None):
"""Exports a fast-export stream for the given directory.
Simply delegates to git fast-epxort and pipes it through sed
@@ -23,8 +25,13 @@ class GitExporter(object):
default refs/heads. This is to demonstrate how the export
data can be stored under it's own ref (using the refspec
capability).
+
+ If None, refs defaults to ["HEAD"].
"""
+ if not refs:
+ refs = ["HEAD"]
+
dirname = self.repo.get_base_path(base)
path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))
@@ -42,12 +49,10 @@ class GitExporter(object):
if os.path.exists(path):
args.append("--import-marks=" + path)
- args.append("HEAD")
+ args.extend(refs)
p1 = subprocess.Popen(args, stdout=subprocess.PIPE)
args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"]
- child = subprocess.Popen(args, stdin=p1.stdout)
- if child.wait() != 0:
- raise CalledProcessError
+ check_call(args, stdin=p1.stdout)
diff --git a/git_remote_helpers/git/git.py b/git_remote_helpers/git/git.py
index a383e6c..007a1bf 100644
--- a/git_remote_helpers/git/git.py
+++ b/git_remote_helpers/git/git.py
@@ -54,7 +54,7 @@ def valid_git_ref (ref_name):
# The following is a reimplementation of the git check-ref-format
# command. The rules were derived from the git check-ref-format(1)
# manual page. This code should be replaced by a call to
- # check_ref_format() in the git library, when such is available.
+ # check_refname_format() in the git library, when such is available.
if ref_name.endswith('/') or \
ref_name.startswith('.') or \
ref_name.count('/.') or \
diff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py
index 70a7127..5c6b595 100644
--- a/git_remote_helpers/git/importer.py
+++ b/git_remote_helpers/git/importer.py
@@ -1,6 +1,8 @@
import os
import subprocess
+from git_remote_helpers.util import check_call, check_output
+
class GitImporter(object):
"""An importer for testgit repositories.
@@ -14,6 +16,18 @@ class GitImporter(object):
self.repo = repo
+ def get_refs(self, gitdir):
+ """Returns a dictionary with refs.
+ """
+ args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"]
+ lines = check_output(args).strip().split('\n')
+ refs = {}
+ for line in lines:
+ value, name = line.split(' ')
+ name = name.strip('commit\t')
+ refs[name] = value
+ return refs
+
def do_import(self, base):
"""Imports a fast-import stream to the given directory.
@@ -30,11 +44,23 @@ class GitImporter(object):
if not os.path.exists(dirname):
os.makedirs(dirname)
+ refs_before = self.get_refs(gitdir)
+
args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path]
if os.path.exists(path):
args.append("--import-marks=" + path)
- child = subprocess.Popen(args)
- if child.wait() != 0:
- raise CalledProcessError
+ check_call(args)
+
+ refs_after = self.get_refs(gitdir)
+
+ changed = {}
+
+ for name, value in refs_after.iteritems():
+ if refs_before.get(name) == value:
+ continue
+
+ changed[name] = value
+
+ return changed
diff --git a/git_remote_helpers/git/non_local.py b/git_remote_helpers/git/non_local.py
index f27389b..e700250 100644
--- a/git_remote_helpers/git/non_local.py
+++ b/git_remote_helpers/git/non_local.py
@@ -1,7 +1,7 @@
import os
import subprocess
-from git_remote_helpers.util import die, warn
+from git_remote_helpers.util import check_call, die, warn
class NonLocalGit(object):
@@ -29,9 +29,7 @@ class NonLocalGit(object):
os.makedirs(path)
args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path]
- child = subprocess.Popen(args)
- if child.wait() != 0:
- raise CalledProcessError
+ check_call(args)
return path
@@ -45,14 +43,10 @@ class NonLocalGit(object):
die("could not find repo at %s", path)
args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath]
- child = subprocess.Popen(args)
- if child.wait() != 0:
- raise CalledProcessError
+ check_call(args)
args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"]
- child = subprocess.Popen(args)
- if child.wait() != 0:
- raise CalledProcessError
+ child = check_call(args)
def push(self, base):
"""Pushes from the non-local repo to base.
@@ -63,7 +57,5 @@ class NonLocalGit(object):
if not os.path.exists(path):
die("could not find repo at %s", path)
- args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath]
- child = subprocess.Popen(args)
- if child.wait() != 0:
- raise CalledProcessError
+ args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath, "--all"]
+ child = check_call(args)
diff --git a/git_remote_helpers/git/repo.py b/git_remote_helpers/git/repo.py
index 58e1cdb..acbf8d7 100644
--- a/git_remote_helpers/git/repo.py
+++ b/git_remote_helpers/git/repo.py
@@ -1,6 +1,9 @@
import os
import subprocess
+from git_remote_helpers.util import check_call
+
+
def sanitize(rev, sep='\t'):
"""Converts a for-each-ref line to a name/value pair.
"""
@@ -53,9 +56,7 @@ class GitRepo(object):
path = ".cached_revs"
ofile = open(path, "w")
- child = subprocess.Popen(args, stdout=ofile)
- if child.wait() != 0:
- raise CalledProcessError
+ check_call(args, stdout=ofile)
output = open(path).readlines()
self.revmap = dict(sanitize(i) for i in output)
if "HEAD" in self.revmap:
diff --git a/git_remote_helpers/util.py b/git_remote_helpers/util.py
index dce83e6..fbbb01b 100644
--- a/git_remote_helpers/util.py
+++ b/git_remote_helpers/util.py
@@ -11,6 +11,21 @@ import sys
import os
import subprocess
+try:
+ from subprocess import CalledProcessError
+except ImportError:
+ # from python2.7:subprocess.py
+ # Exception classes used by this module.
+ class CalledProcessError(Exception):
+ """This exception is raised when a process run by check_call() returns
+ a non-zero exit status. The exit status will be stored in the
+ returncode attribute."""
+ def __init__(self, returncode, cmd):
+ self.returncode = returncode
+ self.cmd = cmd
+ def __str__(self):
+ return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+
# Whether or not to show debug messages
DEBUG = False
@@ -128,6 +143,72 @@ def run_command (args, cwd = None, shell = False, add_env = None,
return (exit_code, output, errors)
+# from python2.7:subprocess.py
+def call(*popenargs, **kwargs):
+ """Run command with arguments. Wait for command to complete, then
+ return the returncode attribute.
+
+ The arguments are the same as for the Popen constructor. Example:
+
+ retcode = call(["ls", "-l"])
+ """
+ return subprocess.Popen(*popenargs, **kwargs).wait()
+
+
+# from python2.7:subprocess.py
+def check_call(*popenargs, **kwargs):
+ """Run command with arguments. Wait for command to complete. If
+ the exit code was zero then return, otherwise raise
+ CalledProcessError. The CalledProcessError object will have the
+ return code in the returncode attribute.
+
+ The arguments are the same as for the Popen constructor. Example:
+
+ check_call(["ls", "-l"])
+ """
+ retcode = call(*popenargs, **kwargs)
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ raise CalledProcessError(retcode, cmd)
+ return 0
+
+
+# from python2.7:subprocess.py
+def check_output(*popenargs, **kwargs):
+ r"""Run command with arguments and return its output as a byte string.
+
+ If the exit code was non-zero it raises a CalledProcessError. The
+ CalledProcessError object will have the return code in the returncode
+ attribute and output in the output attribute.
+
+ The arguments are the same as for the Popen constructor. Example:
+
+ >>> check_output(["ls", "-l", "/dev/null"])
+ 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
+
+ The stdout argument is not allowed as it is used internally.
+ To capture standard error in the result, use stderr=STDOUT.
+
+ >>> check_output(["/bin/sh", "-c",
+ ... "ls -l non_existent_file ; exit 0"],
+ ... stderr=STDOUT)
+ 'ls: non_existent_file: No such file or directory\n'
+ """
+ if 'stdout' in kwargs:
+ raise ValueError('stdout argument not allowed, it will be overridden.')
+ process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
+ output, unused_err = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ raise subprocess.CalledProcessError(retcode, cmd)
+ return output
+
+
def file_reader_method (missing_ok = False):
"""Decorator for simplifying reading of files.
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 4cde0c4..22270ce 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -2,20 +2,45 @@
# Tcl ignores the next line -*- tcl -*- \
exec wish "$0" -- "$@"
-# Copyright © 2005-2009 Paul Mackerras. All rights reserved.
+# Copyright © 2005-2011 Paul Mackerras. All rights reserved.
# This program is free software; it may be used, copied, modified
# and distributed under the terms of the GNU General Public Licence,
# either version 2, or (at your option) any later version.
package require Tk
-proc gitdir {} {
- global env
- if {[info exists env(GIT_DIR)]} {
- return $env(GIT_DIR)
- } else {
- return [exec git rev-parse --git-dir]
+proc hasworktree {} {
+ return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
+ [exec git rev-parse --is-inside-git-dir] == "false"}]
+}
+
+proc reponame {} {
+ global gitdir
+ set n [file normalize $gitdir]
+ if {[string match "*/.git" $n]} {
+ set n [string range $n 0 end-5]
}
+ return [file tail $n]
+}
+
+proc gitworktree {} {
+ variable _gitworktree
+ if {[info exists _gitworktree]} {
+ return $_gitworktree
+ }
+ # v1.7.0 introduced --show-toplevel to return the canonical work-tree
+ if {[catch {set _gitworktree [exec git rev-parse --show-toplevel]}]} {
+ # try to set work tree from environment, core.worktree or use
+ # cdup to obtain a relative path to the top of the worktree. If
+ # run from the top, the ./ prefix ensures normalize expands pwd.
+ if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
+ catch {set _gitworktree [exec git config --get core.worktree]}
+ if {$_gitworktree eq ""} {
+ set _gitworktree [file normalize ./[exec git rev-parse --show-cdup]]
+ }
+ }
+ }
+ return $_gitworktree
}
# A simple scheduler for compute-intensive stuff.
@@ -468,11 +493,11 @@ proc updatecommits {} {
global viewactive viewcomplete tclencoding
global startmsecs showneartags showlocalchanges
global mainheadid viewmainheadid viewmainheadid_orig pending_select
- global isworktree
+ global hasworktree
global varcid vposids vnegids vflags vrevs
global show_notes
- set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
+ set hasworktree [hasworktree]
rereadrefs
set view $curview
if {$mainheadid ne $viewmainheadid_orig($view)} {
@@ -616,12 +641,16 @@ proc varcinit {view} {
proc resetvarcs {view} {
global varcid varccommits parents children vseedcount ordertok
+ global vshortids
foreach vid [array names varcid $view,*] {
unset varcid($vid)
unset children($vid)
unset parents($vid)
}
+ foreach vid [array names vshortids $view,*] {
+ unset vshortids($vid)
+ }
# some commits might have children but haven't been seen yet
foreach vid [array names children $view,*] {
unset children($vid)
@@ -659,7 +688,7 @@ proc newvarc {view id} {
if {![info exists commitinfo($id)]} {
parsecommit $id $commitdata($id) 1
}
- set cdate [lindex $commitinfo($id) 4]
+ set cdate [lindex [lindex $commitinfo($id) 4] 0]
if {![string is integer -strict $cdate]} {
set cdate 0
}
@@ -908,7 +937,7 @@ proc fix_reversal {p a v} {
proc insertrow {id p v} {
global cmitlisted children parents varcid varctok vtokmod
global varccommits ordertok commitidx numcommits curview
- global targetid targetrow
+ global targetid targetrow vshortids
readcommit $id
set vid $v,$id
@@ -917,6 +946,7 @@ proc insertrow {id p v} {
set parents($vid) [list $p]
set a [newvarc $v $id]
set varcid($vid) $a
+ lappend vshortids($v,[string range $id 0 3]) $id
if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
modify_arc $v $a
}
@@ -1372,7 +1402,7 @@ proc getcommitlines {fd inst view updating} {
global commitidx commitdata vdatemode
global parents children curview hlview
global idpending ordertok
- global varccommits varcid varctok vtokmod vfilelimit
+ global varccommits varcid varctok vtokmod vfilelimit vshortids
set stuff [read $fd 500000]
# git log doesn't terminate the last commit with a null...
@@ -1472,6 +1502,8 @@ proc getcommitlines {fd inst view updating} {
set id [lindex $ids 0]
set vid $view,$id
+ lappend vshortids($view,[string range $id 0 3]) $id
+
if {!$listed && $updating && ![info exists varcid($vid)] &&
$vfilelimit($view) ne {}} {
# git log doesn't rewrite parents for unlisted commits
@@ -1621,7 +1653,7 @@ proc readcommit {id} {
}
proc parsecommit {id contents listed} {
- global commitinfo cdate
+ global commitinfo
set inhdr 1
set comment {}
@@ -1641,10 +1673,10 @@ proc parsecommit {id contents listed} {
set line [split $line " "]
set tag [lindex $line 0]
if {$tag == "author"} {
- set audate [lindex $line end-1]
+ set audate [lrange $line end-1 end]
set auname [join [lrange $line 1 end-2] " "]
} elseif {$tag == "committer"} {
- set comdate [lindex $line end-1]
+ set comdate [lrange $line end-1 end]
set comname [join [lrange $line 1 end-2] " "]
}
}
@@ -1671,11 +1703,9 @@ proc parsecommit {id contents listed} {
}
set comment $newcomment
}
- if {$comdate != {}} {
- set cdate($id) $comdate
- }
+ set hasnote [string first "\nNotes:\n" $contents]
set commitinfo($id) [list $headline $auname $audate \
- $comname $comdate $comment]
+ $comname $comdate $comment $hasnote]
}
proc getcommit {id} {
@@ -1696,11 +1726,26 @@ proc getcommit {id} {
# and are present in the current view.
# This is fairly slow...
proc longid {prefix} {
- global varcid curview
+ global varcid curview vshortids
set ids {}
- foreach match [array names varcid "$curview,$prefix*"] {
- lappend ids [lindex [split $match ","] 1]
+ if {[string length $prefix] >= 4} {
+ set vshortid $curview,[string range $prefix 0 3]
+ if {[info exists vshortids($vshortid)]} {
+ foreach id $vshortids($vshortid) {
+ if {[string match "$prefix*" $id]} {
+ if {[lsearch -exact $ids $id] < 0} {
+ lappend ids $id
+ if {[llength $ids] >= 2} break
+ }
+ }
+ }
+ }
+ } else {
+ foreach match [array names varcid "$curview,$prefix*"] {
+ lappend ids [lindex [split $match ","] 1]
+ if {[llength $ids] >= 2} break
+ }
}
return $ids
}
@@ -2437,9 +2482,9 @@ proc makewindow {} {
bindkey n "selnextline 1"
bindkey z "goback"
bindkey x "goforw"
- bindkey i "selnextline -1"
- bindkey k "selnextline 1"
- bindkey j "goback"
+ bindkey k "selnextline -1"
+ bindkey j "selnextline 1"
+ bindkey h "goback"
bindkey l "goforw"
bindkey b prevfile
bindkey d "$ctext yview scroll 18 units"
@@ -2497,6 +2542,8 @@ proc makewindow {} {
{mc "Return to mark" command gotomark}
{mc "Find descendant of this and mark" command find_common_desc}
{mc "Compare with marked commit" command compare_commits}
+ {mc "Diff this -> marked commit" command {diffvsmark 0}}
+ {mc "Diff marked commit -> this" command {diffvsmark 1}}
}
$rowctxmenu configure -tearoff 0
@@ -2505,6 +2552,8 @@ proc makewindow {} {
{mc "Diff this -> selected" command {diffvssel 0}}
{mc "Diff selected -> this" command {diffvssel 1}}
{mc "Make patch" command mkpatch}
+ {mc "Diff this -> marked commit" command {diffvsmark 0}}
+ {mc "Diff marked commit -> this" command {diffvsmark 1}}
}
$fakerowmenu configure -tearoff 0
@@ -2815,7 +2864,7 @@ proc about {} {
message $w.m -text [mc "
Gitk - a commit viewer for git
-Copyright \u00a9 2005-2010 Paul Mackerras
+Copyright \u00a9 2005-2011 Paul Mackerras
Use and redistribute under the terms of the GNU General Public License"] \
-justify center -aspect 400 -border 2 -bg white -relief groove
@@ -2850,9 +2899,9 @@ proc keys {} {
[mc "<%s-W> Close window" $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 "<Up>, p, k Move up one commit"]
+[mc "<Down>, n, j Move down one commit"]
+[mc "<Left>, z, h 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"]
@@ -3333,8 +3382,7 @@ proc gitknewtmpdir {} {
global diffnum gitktmpdir gitdir
if {![info exists gitktmpdir]} {
- set gitktmpdir [file join [file dirname $gitdir] \
- [format ".gitk-tmp.%s" [pid]]]
+ set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
if {[catch {file mkdir $gitktmpdir} err]} {
error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
unset gitktmpdir
@@ -3366,10 +3414,10 @@ proc save_file_from_commit {filename output what} {
proc external_diff_get_one_file {diffid filename diffdir} {
global nullid nullid2 nullfile
- global gitdir
+ global worktree
if {$diffid == $nullid} {
- set difffile [file join [file dirname $gitdir] $filename]
+ set difffile [file join $worktree $filename]
if {[file exists $difffile]} {
return $difffile
}
@@ -3559,7 +3607,7 @@ proc make_relative {f} {
}
proc external_blame {parent_idx {line {}}} {
- global flist_menu_file gitdir
+ global flist_menu_file cdup
global nullid nullid2
global parentlist selectedline currentid
@@ -3578,7 +3626,7 @@ proc external_blame {parent_idx {line {}}} {
if {$line ne {} && $line > 1} {
lappend cmdline "--line=$line"
}
- set f [file join [file dirname $gitdir] $flist_menu_file]
+ set f [file join $cdup $flist_menu_file]
# Unfortunately it seems git gui blame doesn't like
# being given an absolute path...
set f [make_relative $f]
@@ -3591,7 +3639,7 @@ proc external_blame {parent_idx {line {}}} {
proc show_line_source {} {
global cmitmode currentid parents curview blamestuff blameinst
global diff_menu_line diff_menu_filebase flist_menu_file
- global nullid nullid2 gitdir
+ global nullid nullid2 gitdir cdup
set from_index {}
if {$cmitmode eq "tree"} {
@@ -3644,7 +3692,7 @@ proc show_line_source {} {
} else {
lappend blameargs $id
}
- lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file]
+ lappend blameargs -- [file join $cdup $flist_menu_file]
if {[catch {
set f [open $blameargs r]
} err]} {
@@ -4529,12 +4577,22 @@ proc makepatterns {l} {
proc do_file_hl {serial} {
global highlight_files filehighlight highlight_paths gdttype fhl_list
+ global cdup findtype
if {$gdttype eq [mc "touching paths:"]} {
+ # If "exact" match then convert backslashes to forward slashes.
+ # Most useful to support Windows-flavoured file paths.
+ if {$findtype eq [mc "Exact"]} {
+ set highlight_files [string map {"\\" "/"} $highlight_files]
+ }
if {[catch {set paths [shellsplit $highlight_files]}]} return
set highlight_paths [makepatterns $paths]
highlight_filelist
- set gdtargs [concat -- $paths]
+ set relative_paths {}
+ foreach path $paths {
+ lappend relative_paths [file join $cdup $path]
+ }
+ set gdtargs [concat -- $relative_paths]
} elseif {$gdttype eq [mc "adding/removing string:"]} {
set gdtargs [list "-S$highlight_files"]
} else {
@@ -4627,8 +4685,9 @@ proc askfindhighlight {row id} {
}
set info $commitinfo($id)
set isbold 0
- set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
+ set fldtypes [list [mc Headline] [mc Author] "" [mc Committer] "" [mc Comments]]
foreach f $info ty $fldtypes {
+ if {$ty eq ""} continue
if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
[doesmatch $f]} {
if {$ty eq [mc "Author"]} {
@@ -5031,9 +5090,9 @@ proc dohidelocalchanges {} {
# spawn off a process to do git diff-index --cached HEAD
proc dodiffindex {} {
global lserial showlocalchanges vfilelimit curview
- global isworktree
+ global hasworktree
- if {!$showlocalchanges || !$isworktree} return
+ if {!$showlocalchanges || !$hasworktree} return
incr lserial
set cmd "|git diff-index --cached HEAD"
if {$vfilelimit($curview) ne {}} {
@@ -5899,6 +5958,9 @@ proc drawcmittext {id row col} {
|| [info exists idotherrefs($id)]} {
set xt [drawtags $id $x $xt $y]
}
+ if {[lindex $commitinfo($id) 6] > 0} {
+ set xt [drawnotesign $xt $y]
+ }
set headline [lindex $commitinfo($id) 0]
set name [lindex $commitinfo($id) 1]
set date [lindex $commitinfo($id) 2]
@@ -6345,6 +6407,17 @@ proc drawtags {id x xt y1} {
return $xt
}
+proc drawnotesign {xt y} {
+ global linespc canv fgcolor
+
+ set orad [expr {$linespc / 3}]
+ set t [$canv create rectangle [expr {$xt - $orad}] [expr {$y - $orad}] \
+ [expr {$xt + $orad - 1}] [expr {$y + $orad - 1}] \
+ -fill yellow -outline $fgcolor -width 1 -tags circle]
+ set xt [expr {$xt + $orad * 3}]
+ return $xt
+}
+
proc xcoord {i level ln} {
global canvx0 xspc1 xspc2
@@ -6475,7 +6548,7 @@ proc findmore {} {
if {![info exists find_dirn]} {
return 0
}
- set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
+ set fldtypes [list [mc "Headline"] [mc "Author"] "" [mc "Committer"] "" [mc "Comments"]]
set l $findcurline
set moretodo 0
if {$find_dirn > 0} {
@@ -6536,6 +6609,7 @@ proc findmore {} {
}
set info $commitinfo($id)
foreach f $info ty $fldtypes {
+ if {$ty eq ""} continue
if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
[doesmatch $f]} {
set found 1
@@ -6688,7 +6762,7 @@ proc appendwithlinks {text tags} {
set start [$ctext index "end - 1c"]
$ctext insert end $text $tags
- set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
+ set links [regexp -indices -all -inline {(?:\m|-g)[0-9a-f]{6,40}\M} $text]
foreach l $links {
set s [lindex $l 0]
set e [lindex $l 1]
@@ -6704,6 +6778,10 @@ proc appendwithlinks {text tags} {
proc setlink {id lk} {
global curview ctext pendinglinks
+ if {[string range $id 0 1] eq "-g"} {
+ set id [string range $id 2 end]
+ }
+
set known 0
if {[string length $id] < 40} {
set matches [longid $id]
@@ -7376,19 +7454,15 @@ proc startdiff {ids} {
}
}
+# If the filename (name) is under any of the passed filter paths
+# then return true to include the file in the listing.
proc path_filter {filter name} {
+ set worktree [gitworktree]
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
- }
+ set fq_p [file normalize $p]
+ set fq_n [file normalize [file join $worktree $name]]
+ if {[string match [file normalize $fq_p]* $fq_n]} {
+ return 1
}
}
return 0
@@ -7402,7 +7476,7 @@ proc addtocflist {ids} {
}
proc diffcmd {ids flags} {
- global nullid nullid2
+ global log_showroot nullid nullid2
set i [lsearch -exact $ids $nullid]
set j [lsearch -exact $ids $nullid2]
@@ -7436,6 +7510,9 @@ proc diffcmd {ids flags} {
lappend cmd HEAD
}
} else {
+ if {$log_showroot} {
+ lappend flags --root
+ }
set cmd [concat | git diff-tree -r $flags $ids]
}
return $cmd
@@ -8425,6 +8502,11 @@ proc rowmenu {x y id} {
} else {
set state normal
}
+ if {[info exists markedid] && $markedid ne $id} {
+ set mstate normal
+ } else {
+ set mstate disabled
+ }
if {$id ne $nullid && $id ne $nullid2} {
set menu $rowctxmenu
if {$mainhead ne {}} {
@@ -8432,21 +8514,17 @@ proc rowmenu {x y id} {
} else {
$menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
}
- if {[info exists markedid] && $markedid ne $id} {
- $menu entryconfigure 9 -state normal
- $menu entryconfigure 10 -state normal
- $menu entryconfigure 11 -state normal
- } else {
- $menu entryconfigure 9 -state disabled
- $menu entryconfigure 10 -state disabled
- $menu entryconfigure 11 -state disabled
- }
+ $menu entryconfigure 9 -state $mstate
+ $menu entryconfigure 10 -state $mstate
+ $menu entryconfigure 11 -state $mstate
} else {
set menu $fakerowmenu
}
$menu entryconfigure [mca "Diff this -> selected"] -state $state
$menu entryconfigure [mca "Diff selected -> this"] -state $state
$menu entryconfigure [mca "Make patch"] -state $state
+ $menu entryconfigure [mca "Diff this -> marked commit"] -state $mstate
+ $menu entryconfigure [mca "Diff marked commit -> this"] -state $mstate
tk_popup $menu $x $y
}
@@ -8650,6 +8728,21 @@ proc diffvssel {dirn} {
doseldiff $oldid $newid
}
+proc diffvsmark {dirn} {
+ global rowmenuid markedid
+
+ if {![info exists markedid]} return
+ if {$dirn} {
+ set oldid $markedid
+ set newid $rowmenuid
+ } else {
+ set oldid $rowmenuid
+ set newid $markedid
+ }
+ addtohistory [list doseldiff $oldid $newid] savectextpos
+ doseldiff $oldid $newid
+}
+
proc doseldiff {oldid newid} {
global ctext
global commitinfo
@@ -9043,6 +9136,7 @@ proc exec_citool {tool_args {baseid {}}} {
proc cherrypick {} {
global rowmenuid curview
global mainhead mainheadid
+ global gitdir
set oldhead [exec git rev-parse HEAD]
set dheads [descheads $rowmenuid]
@@ -9071,7 +9165,7 @@ proc cherrypick {} {
conflict.\nDo you wish to run git citool to\
resolve it?"]]} {
# Force citool to read MERGE_MSG
- file delete [file join [gitdir] "GITGUI_MSG"]
+ file delete [file join $gitdir "GITGUI_MSG"]
exec_citool {} $rowmenuid
}
} else {
@@ -9437,6 +9531,7 @@ proc refill_reflist {} {
proc getallcommits {} {
global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
global idheads idtags idotherrefs allparents tagobjid
+ global gitdir
if {![info exists allcommits]} {
set nextarc 0
@@ -9444,7 +9539,7 @@ proc getallcommits {} {
set seeds {}
set allcwait 0
set cachedarcs 0
- set allccache [file join [gitdir] "gitk.cache"]
+ set allccache [file join $gitdir "gitk.cache"]
if {![catch {
set f [open $allccache r]
set allcwait 1
@@ -10700,7 +10795,7 @@ proc fontok {} {
if {$fontparam(slant) eq "italic"} {
lappend fontpref($f) "italic"
}
- set w $prefstop.$f
+ set w $prefstop.notebook.fonts.$f
$w conf -text $fontparam(family) -font $fontpref($f)
fontcan
@@ -10754,6 +10849,139 @@ proc chg_fontparam {v sub op} {
font config sample -$sub $fontparam($sub)
}
+# Create a property sheet tab page
+proc create_prefs_page {w} {
+ global NS
+ set parent [join [lrange [split $w .] 0 end-1] .]
+ if {[winfo class $parent] eq "TNotebook"} {
+ ${NS}::frame $w
+ } else {
+ ${NS}::labelframe $w
+ }
+}
+
+proc prefspage_general {notebook} {
+ global NS maxwidth maxgraphpct showneartags showlocalchanges
+ global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
+ global hideremotes want_ttk have_ttk
+
+ set page [create_prefs_page $notebook.general]
+
+ ${NS}::label $page.ldisp -text [mc "Commit list display options"]
+ grid $page.ldisp - -sticky w -pady 10
+ ${NS}::label $page.spacer -text " "
+ ${NS}::label $page.maxwidthl -text [mc "Maximum graph width (lines)"]
+ spinbox $page.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
+ grid $page.spacer $page.maxwidthl $page.maxwidth -sticky w
+ ${NS}::label $page.maxpctl -text [mc "Maximum graph width (% of pane)"]
+ spinbox $page.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
+ grid x $page.maxpctl $page.maxpct -sticky w
+ ${NS}::checkbutton $page.showlocal -text [mc "Show local changes"] \
+ -variable showlocalchanges
+ grid x $page.showlocal -sticky w
+ ${NS}::checkbutton $page.autoselect -text [mc "Auto-select SHA1 (length)"] \
+ -variable autoselect
+ spinbox $page.autosellen -from 1 -to 40 -width 4 -textvariable autosellen
+ grid x $page.autoselect $page.autosellen -sticky w
+ ${NS}::checkbutton $page.hideremotes -text [mc "Hide remote refs"] \
+ -variable hideremotes
+ grid x $page.hideremotes -sticky w
+
+ ${NS}::label $page.ddisp -text [mc "Diff display options"]
+ grid $page.ddisp - -sticky w -pady 10
+ ${NS}::label $page.tabstopl -text [mc "Tab spacing"]
+ spinbox $page.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
+ grid x $page.tabstopl $page.tabstop -sticky w
+ ${NS}::checkbutton $page.ntag -text [mc "Display nearby tags"] \
+ -variable showneartags
+ grid x $page.ntag -sticky w
+ ${NS}::checkbutton $page.ldiff -text [mc "Limit diffs to listed paths"] \
+ -variable limitdiffs
+ grid x $page.ldiff -sticky w
+ ${NS}::checkbutton $page.lattr -text [mc "Support per-file encodings"] \
+ -variable perfile_attrs
+ grid x $page.lattr -sticky w
+
+ ${NS}::entry $page.extdifft -textvariable extdifftool
+ ${NS}::frame $page.extdifff
+ ${NS}::label $page.extdifff.l -text [mc "External diff tool" ]
+ ${NS}::button $page.extdifff.b -text [mc "Choose..."] -command choose_extdiff
+ pack $page.extdifff.l $page.extdifff.b -side left
+ pack configure $page.extdifff.l -padx 10
+ grid x $page.extdifff $page.extdifft -sticky ew
+
+ ${NS}::label $page.lgen -text [mc "General options"]
+ grid $page.lgen - -sticky w -pady 10
+ ${NS}::checkbutton $page.want_ttk -variable want_ttk \
+ -text [mc "Use themed widgets"]
+ if {$have_ttk} {
+ ${NS}::label $page.ttk_note -text [mc "(change requires restart)"]
+ } else {
+ ${NS}::label $page.ttk_note -text [mc "(currently unavailable)"]
+ }
+ grid x $page.want_ttk $page.ttk_note -sticky w
+ return $page
+}
+
+proc prefspage_colors {notebook} {
+ global NS uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
+
+ set page [create_prefs_page $notebook.colors]
+
+ ${NS}::label $page.cdisp -text [mc "Colors: press to choose"]
+ grid $page.cdisp - -sticky w -pady 10
+ label $page.ui -padx 40 -relief sunk -background $uicolor
+ ${NS}::button $page.uibut -text [mc "Interface"] \
+ -command [list choosecolor uicolor {} $page.ui [mc "interface"] setui]
+ grid x $page.uibut $page.ui -sticky w
+ label $page.bg -padx 40 -relief sunk -background $bgcolor
+ ${NS}::button $page.bgbut -text [mc "Background"] \
+ -command [list choosecolor bgcolor {} $page.bg [mc "background"] setbg]
+ grid x $page.bgbut $page.bg -sticky w
+ label $page.fg -padx 40 -relief sunk -background $fgcolor
+ ${NS}::button $page.fgbut -text [mc "Foreground"] \
+ -command [list choosecolor fgcolor {} $page.fg [mc "foreground"] setfg]
+ grid x $page.fgbut $page.fg -sticky w
+ label $page.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
+ ${NS}::button $page.diffoldbut -text [mc "Diff: old lines"] \
+ -command [list choosecolor diffcolors 0 $page.diffold [mc "diff old lines"] \
+ [list $ctext tag conf d0 -foreground]]
+ grid x $page.diffoldbut $page.diffold -sticky w
+ label $page.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
+ ${NS}::button $page.diffnewbut -text [mc "Diff: new lines"] \
+ -command [list choosecolor diffcolors 1 $page.diffnew [mc "diff new lines"] \
+ [list $ctext tag conf dresult -foreground]]
+ grid x $page.diffnewbut $page.diffnew -sticky w
+ label $page.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
+ ${NS}::button $page.hunksepbut -text [mc "Diff: hunk header"] \
+ -command [list choosecolor diffcolors 2 $page.hunksep \
+ [mc "diff hunk header"] \
+ [list $ctext tag conf hunksep -foreground]]
+ grid x $page.hunksepbut $page.hunksep -sticky w
+ label $page.markbgsep -padx 40 -relief sunk -background $markbgcolor
+ ${NS}::button $page.markbgbut -text [mc "Marked line bg"] \
+ -command [list choosecolor markbgcolor {} $page.markbgsep \
+ [mc "marked line background"] \
+ [list $ctext tag conf omark -background]]
+ grid x $page.markbgbut $page.markbgsep -sticky w
+ label $page.selbgsep -padx 40 -relief sunk -background $selectbgcolor
+ ${NS}::button $page.selbgbut -text [mc "Select bg"] \
+ -command [list choosecolor selectbgcolor {} $page.selbgsep [mc "background"] setselbg]
+ grid x $page.selbgbut $page.selbgsep -sticky w
+ return $page
+}
+
+proc prefspage_fonts {notebook} {
+ global NS
+ set page [create_prefs_page $notebook.fonts]
+ ${NS}::label $page.cfont -text [mc "Fonts: press to choose"]
+ grid $page.cfont - -sticky w -pady 10
+ mkfontdisp mainfont $page [mc "Main font"]
+ mkfontdisp textfont $page [mc "Diff display font"]
+ mkfontdisp uifont $page [mc "User interface font"]
+ return $page
+}
+
proc doprefs {} {
global maxwidth maxgraphpct use_ttk NS
global oldprefs prefstop showneartags showlocalchanges
@@ -10774,106 +11002,38 @@ proc doprefs {} {
ttk_toplevel $top
wm title $top [mc "Gitk preferences"]
make_transient $top .
- ${NS}::label $top.ldisp -text [mc "Commit list display options"]
- grid $top.ldisp - -sticky w -pady 10
- ${NS}::label $top.spacer -text " "
- ${NS}::label $top.maxwidthl -text [mc "Maximum graph width (lines)"]
- spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
- grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
- ${NS}::label $top.maxpctl -text [mc "Maximum graph width (% of pane)"]
- spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
- grid x $top.maxpctl $top.maxpct -sticky w
- ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \
- -variable showlocalchanges
- grid x $top.showlocal -sticky w
- ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1 (length)"] \
- -variable autoselect
- spinbox $top.autosellen -from 1 -to 40 -width 4 -textvariable autosellen
- grid x $top.autoselect $top.autosellen -sticky w
- ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \
- -variable hideremotes
- grid x $top.hideremotes -sticky w
-
- ${NS}::label $top.ddisp -text [mc "Diff display options"]
- grid $top.ddisp - -sticky w -pady 10
- ${NS}::label $top.tabstopl -text [mc "Tab spacing"]
- spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
- grid x $top.tabstopl $top.tabstop -sticky w
- ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \
- -variable showneartags
- grid x $top.ntag -sticky w
- ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
- -variable limitdiffs
- grid x $top.ldiff -sticky w
- ${NS}::checkbutton $top.lattr -text [mc "Support per-file encodings"] \
- -variable perfile_attrs
- grid x $top.lattr -sticky w
-
- ${NS}::entry $top.extdifft -textvariable extdifftool
- ${NS}::frame $top.extdifff
- ${NS}::label $top.extdifff.l -text [mc "External diff tool" ]
- ${NS}::button $top.extdifff.b -text [mc "Choose..."] -command choose_extdiff
- pack $top.extdifff.l $top.extdifff.b -side left
- pack configure $top.extdifff.l -padx 10
- grid x $top.extdifff $top.extdifft -sticky ew
-
- ${NS}::label $top.lgen -text [mc "General options"]
- grid $top.lgen - -sticky w -pady 10
- ${NS}::checkbutton $top.want_ttk -variable want_ttk \
- -text [mc "Use themed widgets"]
- if {$have_ttk} {
- ${NS}::label $top.ttk_note -text [mc "(change requires restart)"]
+
+ if {[set use_notebook [expr {$use_ttk && [info command ::ttk::notebook] ne ""}]]} {
+ set notebook [ttk::notebook $top.notebook]
} else {
- ${NS}::label $top.ttk_note -text [mc "(currently unavailable)"]
- }
- grid x $top.want_ttk $top.ttk_note -sticky w
-
- ${NS}::label $top.cdisp -text [mc "Colors: press to choose"]
- grid $top.cdisp - -sticky w -pady 10
- label $top.ui -padx 40 -relief sunk -background $uicolor
- ${NS}::button $top.uibut -text [mc "Interface"] \
- -command [list choosecolor uicolor {} $top.ui [mc "interface"] setui]
- grid x $top.uibut $top.ui -sticky w
- label $top.bg -padx 40 -relief sunk -background $bgcolor
- ${NS}::button $top.bgbut -text [mc "Background"] \
- -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg]
- grid x $top.bgbut $top.bg -sticky w
- label $top.fg -padx 40 -relief sunk -background $fgcolor
- ${NS}::button $top.fgbut -text [mc "Foreground"] \
- -command [list choosecolor fgcolor {} $top.fg [mc "foreground"] setfg]
- grid x $top.fgbut $top.fg -sticky w
- label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
- ${NS}::button $top.diffoldbut -text [mc "Diff: old lines"] \
- -command [list choosecolor diffcolors 0 $top.diffold [mc "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]
- ${NS}::button $top.diffnewbut -text [mc "Diff: new lines"] \
- -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \
- [list $ctext tag conf dresult -foreground]]
- grid x $top.diffnewbut $top.diffnew -sticky w
- label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
- ${NS}::button $top.hunksepbut -text [mc "Diff: hunk header"] \
- -command [list choosecolor diffcolors 2 $top.hunksep \
- [mc "diff hunk header"] \
- [list $ctext tag conf hunksep -foreground]]
- grid x $top.hunksepbut $top.hunksep -sticky w
- label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
- ${NS}::button $top.markbgbut -text [mc "Marked line bg"] \
- -command [list choosecolor markbgcolor {} $top.markbgsep \
- [mc "marked line background"] \
- [list $ctext tag conf omark -background]]
- grid x $top.markbgbut $top.markbgsep -sticky w
- label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
- ${NS}::button $top.selbgbut -text [mc "Select bg"] \
- -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg]
- grid x $top.selbgbut $top.selbgsep -sticky w
-
- ${NS}::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"]
+ set notebook [${NS}::frame $top.notebook -borderwidth 0 -relief flat]
+ }
+
+ lappend pages [prefspage_general $notebook] [mc "General"]
+ lappend pages [prefspage_colors $notebook] [mc "Colors"]
+ lappend pages [prefspage_fonts $notebook] [mc "Fonts"]
+ set col 0
+ foreach {page title} $pages {
+ if {$use_notebook} {
+ $notebook add $page -text $title
+ } else {
+ set btn [${NS}::button $notebook.b_[string map {. X} $page] \
+ -text $title -command [list raise $page]]
+ $page configure -text $title
+ grid $btn -row 0 -column [incr col] -sticky w
+ grid $page -row 1 -column 0 -sticky news -columnspan 100
+ }
+ }
+
+ if {!$use_notebook} {
+ grid columnconfigure $notebook 0 -weight 1
+ grid rowconfigure $notebook 1 -weight 1
+ raise [lindex $pages 0]
+ }
+
+ grid $notebook -sticky news -padx 2 -pady 2
+ grid rowconfigure $top 0 -weight 1
+ grid columnconfigure $top 0 -weight 1
${NS}::frame $top.buts
${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active
@@ -10885,7 +11045,7 @@ proc doprefs {} {
grid columnconfigure $top.buts 1 -weight 1 -uniform a
grid $top.buts - - -pady 10 -sticky ew
grid columnconfigure $top 2 -weight 1
- bind $top <Visibility> "focus $top.buts.ok"
+ bind $top <Visibility> [list focus $top.buts.ok]
}
proc choose_extdiff {} {
@@ -11024,7 +11184,7 @@ proc prefsok {} {
proc formatdate {d} {
global datetimeformat
if {$d ne {}} {
- set d [clock format $d -format $datetimeformat]
+ set d [clock format [lindex $d 0] -format $datetimeformat]
}
return $d
}
@@ -11403,10 +11563,20 @@ catch {
}
}
+set log_showroot true
+catch {
+ set log_showroot [exec git config --bool --get log.showroot]
+}
+
if {[tk windowingsystem] eq "aqua"} {
set mainfont {{Lucida Grande} 9}
set textfont {Monaco 9}
set uifont {{Lucida Grande} 9 bold}
+} elseif {![catch {::tk::pkgconfig get fontsystem} xft] && $xft eq "xft"} {
+ # fontconfig!
+ set mainfont {sans 9}
+ set textfont {monospace 9}
+ set uifont {sans 9 bold}
} else {
set mainfont {Helvetica 9}
set textfont {Courier 9}
@@ -11505,14 +11675,10 @@ setui $uicolor
setoptions
# check that we can find a .git directory somewhere...
-if {[catch {set gitdir [gitdir]}]} {
+if {[catch {set gitdir [exec git rev-parse --git-dir]}]} {
show_error {} . [mc "Cannot find a git repository here."]
exit 1
}
-if {![file isdirectory $gitdir]} {
- show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
- exit 1
-}
set selecthead {}
set selectheadid {}
@@ -11592,6 +11758,8 @@ if {[package vcompare $git_version "1.6.6.2"] >= 0} {
set show_notes "--show-notes"
}
+set appname "gitk"
+
set runq {}
set history {}
set historyindex 0
@@ -11628,7 +11796,12 @@ set stopped 0
set stuffsaved 0
set patchnum 0
set lserial 0
-set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
+set hasworktree [hasworktree]
+set cdup {}
+if {[expr {[exec git rev-parse --is-inside-work-tree] == "true"}]} {
+ set cdup [exec git rev-parse --show-cdup]
+}
+set worktree [exec git rev-parse --show-toplevel]
setcoords
makewindow
catch {
@@ -11656,7 +11829,7 @@ catch {
}
# wait for the window to become visible
tkwait visibility .
-wm title . "[file tail $argv0]: [file tail [pwd]]"
+wm title . "$appname: [reponame]"
update
readrefs
diff --git a/gitweb/INSTALL b/gitweb/INSTALL
index c5236fe..6d45406 100644
--- a/gitweb/INSTALL
+++ b/gitweb/INSTALL
@@ -130,6 +130,8 @@ You can specify the following configuration variables when building GIT:
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_HTML_HEAD_STRING
+ html snippet to include in the <head> section of each page. [No default]
* GITWEB_SITE_HEADER
Filename of html text to include at top of each page. Relative to
gitweb.cgi script. [No default]
@@ -229,9 +231,9 @@ Gitweb config file
------------------
See also "Runtime gitweb configuration" section in README file
-for gitweb (in gitweb/README).
+for gitweb (in gitweb/README), and gitweb.conf(5) manpage.
-- You can configure gitweb further using the gitweb configuration file;
+- You can configure gitweb further using the per-instance gitweb configuration file;
by default this is a file named gitweb_config.perl in the same place as
gitweb.cgi script. You can control the default place for the config file
using the GITWEB_CONFIG build configuration variable, and you can set it
@@ -241,6 +243,17 @@ for gitweb (in gitweb/README).
GITWEB_CONFIG_SYSTEM build configuration variable, and override it
through the GITWEB_CONFIG_SYSTEM environment variable.
+ Note that if per-instance configuration file exists, then system-wide
+ configuration is _not used at all_. This is quite untypical and suprising
+ behavior. On the other hand changing current behavior would break backwards
+ compatibility and can lead to unexpected changes in gitweb behavior.
+ Therefore gitweb also looks for common system-wide configuration file,
+ normally /etc/gitweb-common.conf (set during build time using build time
+ configuration variable GITWEB_CONFIG_COMMON, set it at runtime using
+ environment variable with the same name). Settings from per-instance or
+ system-wide configuration file override those from common system-wide
+ configuration file.
+
- The gitweb config file is a 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) for details.
@@ -276,97 +289,19 @@ adding the following lines to your $GITWEB_CONFIG:
Gitweb repositories
-------------------
-- By default all git repositories under projectroot are visible and
- available to gitweb. The list of projects is generated by default by
- scanning the projectroot directory for git repositories (for object
- databases to be more exact).
-
- You can provide a pre-generated list of [visible] repositories,
- together with information about their owners (the project ownership
- defaults to the owner of the repository directory otherwise), by setting
- the GITWEB_LIST build configuration variable (or the $projects_list
- variable in the gitweb config file) to point to a plain file.
-
- Each line of the projects list file should consist of the url-encoded path
- to the project repository database (relative to projectroot), followed
- by the url-encoded project owner on the same line (separated by a space).
- Spaces in both project path and project owner have to be encoded as either
- '%20' or '+'.
-
- Other characters that have to be url-encoded, i.e. replaced by '%'
- followed by two-digit character number in octal, are: other whitespace
- characters (because they are field separator in a record), plus sign '+'
- (because it can be used as replacement for spaces), and percent sign '%'
- (which is used for encoding / escaping).
-
- You can generate the projects list index file using the project_index
- action (the 'TXT' link on projects list page) directly from gitweb.
-
-- By default, even if a project is not visible on projects list page, you
- can view it nevertheless by hand-crafting a gitweb URL. You can set the
- GITWEB_STRICT_EXPORT build configuration variable (or the $strict_export
- variable in the gitweb config file) to only allow viewing of
- repositories also shown on the overview page.
-
-- Alternatively, you can configure gitweb to only list and allow
- viewing of the explicitly exported repositories, via the
- GITWEB_EXPORT_OK build configuration variable (or the $export_ok
- variable in gitweb config file). If it evaluates to true, gitweb
- shows repositories only if this file exists in its object database
- (if directory has the magic file named $export_ok).
-
-- Finally, it is possible to specify an arbitrary perl subroutine that
- will be called for each project to determine if it can be exported.
- The subroutine receives an absolute path to the project as its only
- parameter.
-
- For example, if you use mod_perl to run the script, and have dumb
- http protocol authentication configured for your repositories, you
- can use the following hook to allow access only if the user is
- authorized to read the files:
-
- $export_auth_hook = sub {
- use Apache2::SubRequest ();
- use Apache2::Const -compile => qw(HTTP_OK);
- my $path = "$_[0]/HEAD";
- my $r = Apache2::RequestUtil->request;
- my $sub = $r->lookup_file($path);
- return $sub->filename eq $path
- && $sub->status == Apache2::Const::HTTP_OK;
- };
-
-
-Generating projects list using gitweb
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We assume that GITWEB_CONFIG has its default Makefile value, namely
-gitweb_config.perl. Put the following in gitweb_make_index.perl file:
-
- $GITWEB_CONFIG = "gitweb_config.perl";
- do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
-
- $projects_list = $projectroot;
-
-Then create the following script to get list of project in the format
-suitable for GITWEB_LIST build configuration variable (or
-$projects_list variable in gitweb config):
-
- #!/bin/sh
-
- export GITWEB_CONFIG="gitweb_make_index.perl"
- export GATEWAY_INTERFACE="CGI/1.1"
- export HTTP_ACCEPT="*/*"
- export REQUEST_METHOD="GET"
- export QUERY_STRING="a=project_index"
-
- perl -- /var/www/cgi-bin/gitweb.cgi
+By default gitweb shows all git repositories under single common repository
+root on a local filesystem; see description of GITWEB_PROJECTROOT build-time
+configuration variable above (and also of GITWEB_LIST).
+
+More advanced usage, like limiting access or visibility of repositories and
+managing multiple roots are described on gitweb manpage.
Example web server configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-See also "Webserver configuration" section in README file for gitweb
-(in gitweb/README).
+See also "Webserver configuration" and "Advanced web server setup" sections
+in gitweb(1) manpage.
- Apache2, gitweb installed as CGI script,
diff --git a/gitweb/Makefile b/gitweb/Makefile
index 5d20515..cd194d0 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -20,6 +20,7 @@ INSTALL ?= install
# default configuration for gitweb
GITWEB_CONFIG = gitweb_config.perl
GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
+GITWEB_CONFIG_COMMON = /etc/gitweb-common.conf
GITWEB_HOME_LINK_STR = projects
GITWEB_SITENAME =
GITWEB_PROJECTROOT = /pub/git
@@ -33,6 +34,7 @@ GITWEB_CSS = static/gitweb.css
GITWEB_LOGO = static/git-logo.png
GITWEB_FAVICON = static/git-favicon.png
GITWEB_JS = static/gitweb.js
+GITWEB_SITE_HTML_HEAD_STRING =
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
HIGHLIGHT_BIN = highlight
@@ -129,6 +131,7 @@ GITWEB_REPLACE = \
-e 's|++GIT_BINDIR++|$(bindir)|g' \
-e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
-e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \
+ -e 's|++GITWEB_CONFIG_COMMON++|$(GITWEB_CONFIG_COMMON)|g' \
-e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
-e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
-e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
@@ -142,6 +145,7 @@ GITWEB_REPLACE = \
-e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
-e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
-e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
+ -e 's|++GITWEB_SITE_HTML_HEAD_STRING++|$(GITWEB_SITE_HTML_HEAD_STRING)|g' \
-e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
-e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
-e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g'
@@ -183,7 +187,9 @@ install: all
### Cleaning rules
clean:
- $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS
+ $(RM) gitweb.cgi static/gitweb.js \
+ static/gitweb.min.js static/gitweb.min.css \
+ GITWEB-BUILD-OPTIONS
.PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE
diff --git a/gitweb/README b/gitweb/README
index a4cfcb4..6da4778 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -7,413 +7,65 @@ The one working on:
From the git version 1.4.0 gitweb is bundled with git.
-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), and
-as a fallback `GITWEB_CONFIG_SYSTEM` (defaults to /etc/gitweb.conf).
-The most notable thing that is not configurable at compile time are the
-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 the "Gitweb config file" (with an example of config file), and
-the "Gitweb repositories" sections in INSTALL file for gitweb.
-
-
-The gitweb config file is a 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 is the list of variables 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
- Core 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> SP
- <URI-encoded repository owner>" line format; actually there can be
- any sequence of whitespace in place of space (SP)). Set to
- $GITWEB_LIST during installation. If empty, $projectroot is used
- to scan for repositories.
- * $my_url, $my_uri
- Full URL and absolute URL of gitweb script;
- in earlier versions of gitweb you might have need to set those
- variables, now there should be no need to do it. See
- $per_request_config if you need to set them still.
- * $base_url
- Base URL for relative URLs in pages generated by gitweb,
- (e.g. $logo, $favicon, @stylesheets if they are relative URLs),
- needed and used only for URLs with nonempty PATH_INFO via
- <base href="$base_url">. Usually gitweb sets its value correctly,
- and there is no need to set this variable, e.g. to $my_uri or "/".
- See $per_request_config if you need to set it anyway.
- * $home_link
- Target of the home link on top of all pages (the first part of view
- "breadcrumbs"). By default set to absolute URI of a page ($my_uri).
- * @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 the 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.
- * $projects_list_group_categories
- Enables the grouping of projects by category on the project list page.
- The category of a project is determined by the $GIT_DIR/category
- file or the 'gitweb.category' variable in its repository configuration.
- Disabled by default.
- * $project_list_default_category
- Default category for projects for which none is specified. If set
- to the empty string, such projects will remain uncategorized and
- listed at the top, above categorized projects.
- * @git_base_url_list
- List of git base URLs used for URL to where fetch project from, shown
- in project summary page. Full URL is "$git_base_url/$project".
- 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 server 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
- relative to the current git repository.
- * $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 must be valid encoding; 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.
- * $prevent_xss
- If true, some gitweb features are disabled to prevent content in
- repositories from launching cross-site scripting (XSS) attacks. Set this
- to true if you don't trust the content of your repositories. The default
- is false.
- * $maxload
- Used to set the maximum load that we will still respond to gitweb queries.
- If server load exceed this value then return "503 Service Unavailable" error.
- Server load is taken to be 0 if gitweb cannot determine its value. Set it to
- undefined value to turn it off. The default is 300.
- * $highlight_bin
- Path to the highlight executable to use (must be the one from
- http://www.andre-simon.de due to assumptions about parameters and output).
- Useful if highlight is not installed on your webserver's PATH.
- [Default: highlight]
- * $per_request_config
- If set to code reference, it would be run once per each request. You can
- set parts of configuration that change per session, e.g. by setting it to
- sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; }
- Otherwise it is treated as boolean value: if true gitweb would process
- config file once per request, if false it would process config file only
- once. Note: $my_url, $my_uri, and $base_url are overwritten with
- their default values before every request, so if you want to change
- them, be sure to set this variable to true or a code reference effecting
- the desired changes. The default is true.
-
-Projects list file format
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Instead of having gitweb find repositories by scanning filesystem starting
-from $projectroot (or $projects_list, if it points to directory), you can
-provide list of projects by setting $projects_list to a text file with list
-of projects (and some additional info). This file uses the following
-format:
-
-One record (for project / repository) per line, whitespace separated fields;
-does not support (at least for now) lines continuation (newline escaping).
-Leading and trailing whitespace are ignored, any run of whitespace can be
-used as field separator (rules for Perl's "split(' ', $line)"). Keyed by
-the first field, which is project name, i.e. path to repository GIT_DIR
-relative to $projectroot. Fields use modified URI encoding, defined in
-RFC 3986, section 2.1 (Percent-Encoding), or rather "Query string encoding"
-(see http://en.wikipedia.org/wiki/Query_string#URL_encoding), the difference
-being that SP (' ') can be encoded as '+' (and therefore '+' has to be also
-percent-encoded). Reserved characters are: '%' (used for encoding), '+'
-(can be used to encode SPACE), all whitespace characters as defined in Perl,
-including SP, TAB and LF, (used to separate fields in a record).
-
-Currently list of fields is
- * <repository path> - path to repository GIT_DIR, relative to $projectroot
- * <repository owner> - displayed as repository owner, preferably full name,
- or email, or both
-
-You can additionally use $projects_list file to limit which repositories
-are visible, and together with $strict_export to limit access to
-repositories (see "Gitweb repositories" section in gitweb/INSTALL).
-
-
-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 project's
- homepage), etc. This is recognized only if XSS prevention is off
- ($prevent_xss is false); a way to include a readme safely when XSS
- prevention is on may be worked out in the future.
- * 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 repository creation. You can use the
- gitweb.description repo configuration variable, but the file takes
- precedence.
- * category (or gitweb.category)
- Singe line category of a project, used to group projects if
- $projects_list_group_categories is enabled. By default (file and
- configuration variable absent), uncategorized projects are put in
- the $project_list_default_category category. You can use the
- gitweb.category repo configuration variable, but the file takes
- precedence.
- * cloneurl (or multiple-valued gitweb.url)
- File with repository URL (used for clone and fetch), one per line.
- Displayed in the project summary page. You can use multiple-valued
- gitweb.url repository configuration variable for that, but the file
- takes precedence.
- * 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
- (via GECOS field / real name field from getpwiud(3)).
- * various gitweb.* config variables (in config)
- Read description of %feature hash for detailed list, and some
- descriptions.
-
-
-Webserver configuration
------------------------
-
-If you want to have one URL for both gitweb and your http://
-repositories, you can configure apache like this:
-
-<VirtualHost *:80>
- ServerName git.example.org
- DocumentRoot /pub/git
- SetEnv GITWEB_CONFIG /etc/gitweb.conf
-
- # turning on mod rewrite
- RewriteEngine on
-
- # make the front page an internal rewrite to the gitweb script
- RewriteRule ^/$ /cgi-bin/gitweb.cgi
-
- # make access for "dumb clients" work
- RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT]
-</VirtualHost>
-
-The above configuration expects your public repositories to live under
-/pub/git and will serve them as http://git.domain.org/dir-under-pub-git,
-both as cloneable GIT URL and as browseable gitweb interface.
-If you then start your git-daemon with --base-path=/pub/git --export-all
-then you can even use the git:// URL with exactly the same path.
-
-Setting the environment variable GITWEB_CONFIG will tell gitweb to use
-the named file (i.e. in this example /etc/gitweb.conf) as a
-configuration for gitweb. Perl variables defined in here will
-override the defaults given at the head of the gitweb.perl (or
-gitweb.cgi). Look at the comments in that file for information on
-which variables and what they mean.
-
-If you use the rewrite rules from the example you'll likely also need
-something like the following in your gitweb.conf (or gitweb_config.perl) file:
-
- @stylesheets = ("/some/absolute/path/gitweb.css");
- $my_uri = "/";
- $home_link = "/";
-
-
-Webserver configuration with multiple projects' root
-----------------------------------------------------
+Build time gitweb configuration
+-------------------------------
+There are many configuration variables which affect building gitweb (among
+others creating gitweb.cgi out of gitweb.perl by replacing placeholders such
+as `++GIT_BINDIR++` by their build-time values).
-If you want to use gitweb with several project roots you can edit your apache
-virtual host and gitweb.conf configuration files like this :
+Building and installing gitweb is described in gitweb's INSTALL file
+(in 'gitweb/INSTALL').
-virtual host configuration :
-<VirtualHost *:80>
- ServerName git.example.org
- DocumentRoot /pub/git
- SetEnv GITWEB_CONFIG /etc/gitweb.conf
-
- # turning on mod rewrite
- RewriteEngine on
-
- # make the front page an internal rewrite to the gitweb script
- RewriteRule ^/$ /cgi-bin/gitweb.cgi [QSA,L,PT]
-
- # look for a public_git folder in unix users' home
- # http://git.example.org/~<user>/
- RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
- # http://git.example.org/+<user>/
- #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
- # http://git.example.org/user/<user>/
- #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
- # defined list of project roots
- RewriteRule ^/scm(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT]
- RewriteRule ^/var(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT]
-
- # make access for "dumb clients" work
- RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT]
-</VirtualHost>
-
-gitweb.conf configuration :
-
-$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git";
-
-These configurations enable two things. First, each unix user (<user>) of the
-server will be able to browse through gitweb git repositories found in
-~/public_git/ with the following url : http://git.example.org/~<user>/
-
-If you do not want this feature on your server just remove the second rewrite rule.
-
-If you already use mod_userdir in your virtual host or you don't want to use
-the '~' as first character just comment or remove the second rewrite rule and
-uncomment one of the following according to what you want.
-
-Second, repositories found in /pub/scm/ and /var/git/ will be accesible
-through http://git.example.org/scm/ and http://git.example.org/var/.
-You can add as many project roots as you want by adding rewrite rules like the
-third and the fourth.
-
-
-PATH_INFO usage
------------------------
-If you enable PATH_INFO usage in gitweb by putting
-
- $feature{'pathinfo'}{'default'} = [1];
-
-in your gitweb.conf, it is possible to set up your server so that it
-consumes and produces URLs in the form
-
-http://git.example.com/project.git/shortlog/sometag
-
-by using a configuration such as the following, that assumes that
-/var/www/gitweb is the DocumentRoot of your webserver, and that it
-contains the gitweb.cgi script and complementary static files
-(stylesheet, favicon):
-
-<VirtualHost *:80>
- ServerAlias git.example.com
-
- DocumentRoot /var/www/gitweb
-
- <Directory /var/www/gitweb>
- Options ExecCGI
- AddHandler cgi-script cgi
-
- DirectoryIndex gitweb.cgi
-
- RewriteEngine On
- RewriteCond %{REQUEST_FILENAME} !-f
- RewriteCond %{REQUEST_FILENAME} !-d
- RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
- </Directory>
-</VirtualHost>
-
-The rewrite rule guarantees that existing static files will be properly
-served, whereas any other URL will be passed to gitweb as PATH_INFO
-parameter.
-
-Notice that in this case you don't need special settings for
-@stylesheets, $my_uri and $home_link, but you lose "dumb client" access
-to your project .git dirs. A possible workaround for the latter is the
-following: in your project root dir (e.g. /pub/git) have the projects
-named without a .git extension (e.g. /pub/git/project instead of
-/pub/git/project.git) and configure Apache as follows:
-
-<VirtualHost *:80>
- ServerAlias git.example.com
-
- DocumentRoot /var/www/gitweb
-
- AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3
- <Directory /var/www/gitweb>
- Options ExecCGI
- AddHandler cgi-script cgi
+Runtime gitweb configuration
+----------------------------
+Gitweb obtains configuration data from the following sources in the
+following order:
- DirectoryIndex gitweb.cgi
+1. built-in values (some set during build stage),
+2. common system-wide configuration file (`GITWEB_CONFIG_COMMON`,
+ defaults to '/etc/gitweb-common.conf'),
+3. either per-instance configuration file (`GITWEB_CONFIG`, defaults to
+ 'gitweb_config.perl' in the same directory as the installed gitweb),
+ or if it does not exists then system-wide configuration file
+ (`GITWEB_CONFIG_SYSTEM`, defaults to '/etc/gitweb.conf').
- RewriteEngine On
- RewriteCond %{REQUEST_FILENAME} !-f
- RewriteCond %{REQUEST_FILENAME} !-d
- RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
- </Directory>
-</VirtualHost>
+Values obtained in later configuration files override values obtained earlier
+in above sequence.
-The additional AliasMatch makes it so that
+You can read defaults in system-wide GITWEB_CONFIG_SYSTEM from GITWEB_CONFIG
+by adding
-http://git.example.com/project.git
+ read_config_file($GITWEB_CONFIG_SYSTEM);
-will give raw access to the project's git dir (so that the project can
-be cloned), while
+at very beginning of per-instance GITWEB_CONFIG file. In this case
+settings in said per-instance file will override settings from
+system-wide configuration file. Note that read_config_file checks
+itself that the $GITWEB_CONFIG_SYSTEM file exists.
-http://git.example.com/project
+The most notable thing that is not configurable at compile time are the
+optional features, stored in the '%features' variable.
-will provide human-friendly gitweb access.
+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'.
-This solution is not 100% bulletproof, in the sense that if some project
-has a named ref (branch, tag) starting with 'git/', then paths such as
+See also gitweb.conf(5) manpage.
-http://git.example.com/project/command/abranch..git/abranch
-will fail with a 404 error.
+Web server configuration
+------------------------
+Gitweb can be run as CGI script, as legacy mod_perl application (using
+ModPerl::Registry), and as FastCGI script. You can find some simple examples
+in "Example web server configuration" section in INSTALL file for gitweb (in
+gitweb/INSTALL).
+See "Webserver configuration" and "Advanced web server setup" sections in
+gitweb(1) manpage.
+AUTHORS
+-------
Originally written by:
Kay Sievers <kay.sievers@vrfy.org>
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 50a835a..55e0e9e 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -52,7 +52,7 @@ sub evaluate_uri {
# as base URL.
# Therefore, if we needed to strip PATH_INFO, then we know that we have
# to build the base URL ourselves:
- our $path_info = $ENV{"PATH_INFO"};
+ our $path_info = decode_utf8($ENV{"PATH_INFO"});
if ($path_info) {
if ($my_url =~ s,\Q$path_info\E$,, &&
$my_uri =~ s,\Q$path_info\E$,, &&
@@ -85,6 +85,8 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++";
our $site_name = "++GITWEB_SITENAME++"
|| ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
+# html snippet to include in the <head> section of each page
+our $site_html_head_string = "++GITWEB_SITE_HTML_HEAD_STRING++";
# filename of html text to include at top of each page
our $site_header = "++GITWEB_SITE_HEADER++";
# html text to include at home page
@@ -131,6 +133,12 @@ our $default_projects_order = "project";
# (only effective if this variable evaluates to true)
our $export_ok = "++GITWEB_EXPORT_OK++";
+# don't generate age column on the projects list page
+our $omit_age_column = 0;
+
+# don't generate information about owners of repositories
+our $omit_owner=0;
+
# show repository only if this subroutine returns true
# when given the path to the project, for example:
# sub { return -e "$_[0]/git-daemon-export-ok"; }
@@ -321,6 +329,10 @@ our %feature = (
# Enable text search, which will list the commits which match author,
# committer or commit text to a given string. Enabled by default.
# Project specific override is not supported.
+ #
+ # Note that this controls all search features, which means that if
+ # it is disabled, then 'grep' and 'pickaxe' search would also be
+ # disabled.
'search' => {
'override' => 0,
'default' => [1]},
@@ -661,13 +673,25 @@ sub read_config_file {
return;
}
-our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
+our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM, $GITWEB_CONFIG_COMMON);
sub evaluate_gitweb_config {
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
+ our $GITWEB_CONFIG_COMMON = $ENV{'GITWEB_CONFIG_COMMON'} || "++GITWEB_CONFIG_COMMON++";
+
+ # Protect agains duplications of file names, to not read config twice.
+ # Only one of $GITWEB_CONFIG and $GITWEB_CONFIG_SYSTEM is used, so
+ # there possibility of duplication of filename there doesn't matter.
+ $GITWEB_CONFIG = "" if ($GITWEB_CONFIG eq $GITWEB_CONFIG_COMMON);
+ $GITWEB_CONFIG_SYSTEM = "" if ($GITWEB_CONFIG_SYSTEM eq $GITWEB_CONFIG_COMMON);
- # use first config file that exists
- read_config_file($GITWEB_CONFIG) or
+ # Common system-wide settings for convenience.
+ # Those settings can be ovverriden by GITWEB_CONFIG or GITWEB_CONFIG_SYSTEM.
+ read_config_file($GITWEB_CONFIG_COMMON);
+
+ # Use first config file that exists. This means use the per-instance
+ # GITWEB_CONFIG if exists, otherwise use GITWEB_SYSTEM_CONFIG.
+ read_config_file($GITWEB_CONFIG) and return;
read_config_file($GITWEB_CONFIG_SYSTEM);
}
@@ -741,6 +765,8 @@ our @cgi_param_mapping = (
extra_options => "opt",
search_use_regexp => "sr",
ctag => "by_tag",
+ diff_style => "ds",
+ project_filter => "pf",
# this must be last entry (for manipulation from JavaScript)
javascript => "js"
);
@@ -797,9 +823,9 @@ sub evaluate_query_params {
while (my ($name, $symbol) = each %cgi_param_mapping) {
if ($symbol eq 'opt') {
- $input_params{$name} = [ $cgi->param($symbol) ];
+ $input_params{$name} = [ map { decode_utf8($_) } $cgi->param($symbol) ];
} else {
- $input_params{$name} = $cgi->param($symbol);
+ $input_params{$name} = decode_utf8($cgi->param($symbol));
}
}
}
@@ -957,7 +983,7 @@ sub evaluate_path_info {
our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
$hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
- $searchtext, $search_regexp);
+ $searchtext, $search_regexp, $project_filter);
sub evaluate_and_validate_params {
our $action = $input_params{'action'};
if (defined $action) {
@@ -975,6 +1001,13 @@ sub evaluate_and_validate_params {
}
}
+ our $project_filter = $input_params{'project_filter'};
+ if (defined $project_filter) {
+ if (!validate_pathname($project_filter)) {
+ die_error(404, "Invalid project_filter parameter");
+ }
+ }
+
our $file_name = $input_params{'file_name'};
if (defined $file_name) {
if (!validate_pathname($file_name)) {
@@ -1054,7 +1087,16 @@ sub evaluate_and_validate_params {
if (length($searchtext) < 2) {
die_error(403, "At least two characters are required for search parameter");
}
- $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
+ if ($search_use_regexp) {
+ $search_regexp = $searchtext;
+ if (!eval { qr/$search_regexp/; 1; }) {
+ (my $error = $@) =~ s/ at \S+ line \d+.*\n?//;
+ die_error(400, "Invalid search regexp '$search_regexp'",
+ esc_html($error));
+ }
+ } else {
+ $search_regexp = quotemeta $searchtext;
+ }
}
}
@@ -1104,8 +1146,10 @@ sub dispatch {
if (!defined $action) {
if (defined $hash) {
$action = git_get_type($hash);
+ $action or die_error(404, "Object does not exist");
} elsif (defined $hash_base && defined $file_name) {
$action = git_get_type("$hash_base:$file_name");
+ $action or die_error(404, "File or directory does not exist");
} elsif (defined $project) {
$action = 'summary';
} else {
@@ -1424,8 +1468,8 @@ sub validate_refname {
sub to_utf8 {
my $str = shift;
return undef unless defined $str;
- if (utf8::valid($str)) {
- utf8::decode($str);
+
+ if (utf8::is_utf8($str) || utf8::decode($str)) {
return $str;
} else {
return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
@@ -1501,6 +1545,17 @@ sub esc_path {
return $str;
}
+# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0)
+sub sanitize {
+ my $str = shift;
+
+ return undef unless defined $str;
+
+ $str = to_utf8($str);
+ $str =~ s|([[:cntrl:]])|($1 =~ /[\t\n\r]/ ? $1 : quot_cec($1))|eg;
+ return $str;
+}
+
# Make control characters "printable", using character escape codes (CEC)
sub quot_cec {
my $cntrl = shift;
@@ -1666,6 +1721,7 @@ sub chop_and_escape_str {
my ($str) = @_;
my $chopped = chop_str(@_);
+ $str = to_utf8($str);
if ($chopped eq $str) {
return esc_html($chopped);
} else {
@@ -1674,6 +1730,97 @@ sub chop_and_escape_str {
}
}
+# Highlight selected fragments of string, using given CSS class,
+# and escape HTML. It is assumed that fragments do not overlap.
+# Regions are passed as list of pairs (array references).
+#
+# Example: esc_html_hl_regions("foobar", "mark", [ 0, 3 ]) returns
+# '<span class="mark">foo</span>bar'
+sub esc_html_hl_regions {
+ my ($str, $css_class, @sel) = @_;
+ my %opts = grep { ref($_) ne 'ARRAY' } @sel;
+ @sel = grep { ref($_) eq 'ARRAY' } @sel;
+ return esc_html($str, %opts) unless @sel;
+
+ my $out = '';
+ my $pos = 0;
+
+ for my $s (@sel) {
+ my ($begin, $end) = @$s;
+
+ # Don't create empty <span> elements.
+ next if $end <= $begin;
+
+ my $escaped = esc_html(substr($str, $begin, $end - $begin),
+ %opts);
+
+ $out .= esc_html(substr($str, $pos, $begin - $pos), %opts)
+ if ($begin - $pos > 0);
+ $out .= $cgi->span({-class => $css_class}, $escaped);
+
+ $pos = $end;
+ }
+ $out .= esc_html(substr($str, $pos), %opts)
+ if ($pos < length($str));
+
+ return $out;
+}
+
+# return positions of beginning and end of each match
+sub matchpos_list {
+ my ($str, $regexp) = @_;
+ return unless (defined $str && defined $regexp);
+
+ my @matches;
+ while ($str =~ /$regexp/g) {
+ push @matches, [$-[0], $+[0]];
+ }
+ return @matches;
+}
+
+# highlight match (if any), and escape HTML
+sub esc_html_match_hl {
+ my ($str, $regexp) = @_;
+ return esc_html($str) unless defined $regexp;
+
+ my @matches = matchpos_list($str, $regexp);
+ return esc_html($str) unless @matches;
+
+ return esc_html_hl_regions($str, 'match', @matches);
+}
+
+
+# highlight match (if any) of shortened string, and escape HTML
+sub esc_html_match_hl_chopped {
+ my ($str, $chopped, $regexp) = @_;
+ return esc_html_match_hl($str, $regexp) unless defined $chopped;
+
+ my @matches = matchpos_list($str, $regexp);
+ return esc_html($chopped) unless @matches;
+
+ # filter matches so that we mark chopped string
+ my $tail = "... "; # see chop_str
+ unless ($chopped =~ s/\Q$tail\E$//) {
+ $tail = '';
+ }
+ my $chop_len = length($chopped);
+ my $tail_len = length($tail);
+ my @filtered;
+
+ for my $m (@matches) {
+ if ($m->[0] > $chop_len) {
+ push @filtered, [ $chop_len, $chop_len + $tail_len ] if ($tail_len > 0);
+ last;
+ } elsif ($m->[1] > $chop_len) {
+ push @filtered, [ $m->[0], $chop_len + $tail_len ];
+ last;
+ }
+ push @filtered, $m;
+ }
+
+ return esc_html_hl_regions($chopped . $tail, 'match', @filtered);
+}
+
## ----------------------------------------------------------------------
## functions returning short strings
@@ -2196,93 +2343,125 @@ sub format_diff_cc_simplified {
return $result;
}
-# format patch (diff) line (not to be used for diff headers)
-sub format_diff_line {
- my $line = shift;
- my ($from, $to) = @_;
- my $diff_class = "";
-
- chomp $line;
+sub diff_line_class {
+ my ($line, $from, $to) = @_;
+ # ordinary diff
+ my $num_sign = 1;
+ # combined diff
if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
- # combined diff
- my $prefix = substr($line, 0, scalar @{$from->{'href'}});
- if ($line =~ m/^\@{3}/) {
- $diff_class = " chunk_header";
- } elsif ($line =~ m/^\\/) {
- $diff_class = " incomplete";
- } elsif ($prefix =~ tr/+/+/) {
- $diff_class = " add";
- } elsif ($prefix =~ tr/-/-/) {
- $diff_class = " rem";
- }
- } else {
- # assume ordinary diff
- my $char = substr($line, 0, 1);
- if ($char eq '+') {
- $diff_class = " add";
- } elsif ($char eq '-') {
- $diff_class = " rem";
- } elsif ($char eq '@') {
- $diff_class = " chunk_header";
- } elsif ($char eq "\\") {
- $diff_class = " incomplete";
- }
- }
- $line = untabify($line);
- if ($from && $to && $line =~ m/^\@{2} /) {
- my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
- $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
-
- $from_lines = 0 unless defined $from_lines;
- $to_lines = 0 unless defined $to_lines;
+ $num_sign = scalar @{$from->{'href'}};
+ }
+
+ my @diff_line_classifier = (
+ { regexp => qr/^\@\@{$num_sign} /, class => "chunk_header"},
+ { regexp => qr/^\\/, class => "incomplete" },
+ { regexp => qr/^ {$num_sign}/, class => "ctx" },
+ # classifier for context must come before classifier add/rem,
+ # or we would have to use more complicated regexp, for example
+ # qr/(?= {0,$m}\+)[+ ]{$num_sign}/, where $m = $num_sign - 1;
+ { regexp => qr/^[+ ]{$num_sign}/, class => "add" },
+ { regexp => qr/^[- ]{$num_sign}/, class => "rem" },
+ );
+ for my $clsfy (@diff_line_classifier) {
+ return $clsfy->{'class'}
+ if ($line =~ $clsfy->{'regexp'});
+ }
- if ($from->{'href'}) {
- $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
- -class=>"list"}, $from_text);
- }
- if ($to->{'href'}) {
- $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
- -class=>"list"}, $to_text);
- }
- $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
- "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
- return "<div class=\"diff$diff_class\">$line</div>\n";
- } elsif ($from && $to && $line =~ m/^\@{3}/) {
- my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
- my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
+ # fallback
+ return "";
+}
- @from_text = split(' ', $ranges);
- for (my $i = 0; $i < @from_text; ++$i) {
- ($from_start[$i], $from_nlines[$i]) =
- (split(',', substr($from_text[$i], 1)), 0);
- }
+# assumes that $from and $to are defined and correctly filled,
+# and that $line holds a line of chunk header for unified diff
+sub format_unidiff_chunk_header {
+ my ($line, $from, $to) = @_;
- $to_text = pop @from_text;
- $to_start = pop @from_start;
- $to_nlines = pop @from_nlines;
+ my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
+ $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
- $line = "<span class=\"chunk_info\">$prefix ";
- for (my $i = 0; $i < @from_text; ++$i) {
- if ($from->{'href'}[$i]) {
- $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
- -class=>"list"}, $from_text[$i]);
- } else {
- $line .= $from_text[$i];
- }
- $line .= " ";
+ $from_lines = 0 unless defined $from_lines;
+ $to_lines = 0 unless defined $to_lines;
+
+ if ($from->{'href'}) {
+ $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
+ -class=>"list"}, $from_text);
+ }
+ if ($to->{'href'}) {
+ $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
+ -class=>"list"}, $to_text);
+ }
+ $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
+ "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+ return $line;
+}
+
+# assumes that $from and $to are defined and correctly filled,
+# and that $line holds a line of chunk header for combined diff
+sub format_cc_diff_chunk_header {
+ my ($line, $from, $to) = @_;
+
+ my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
+ my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
+
+ @from_text = split(' ', $ranges);
+ for (my $i = 0; $i < @from_text; ++$i) {
+ ($from_start[$i], $from_nlines[$i]) =
+ (split(',', substr($from_text[$i], 1)), 0);
+ }
+
+ $to_text = pop @from_text;
+ $to_start = pop @from_start;
+ $to_nlines = pop @from_nlines;
+
+ $line = "<span class=\"chunk_info\">$prefix ";
+ for (my $i = 0; $i < @from_text; ++$i) {
+ if ($from->{'href'}[$i]) {
+ $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
+ -class=>"list"}, $from_text[$i]);
+ } else {
+ $line .= $from_text[$i];
}
- if ($to->{'href'}) {
- $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
- -class=>"list"}, $to_text);
+ $line .= " ";
+ }
+ if ($to->{'href'}) {
+ $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
+ -class=>"list"}, $to_text);
+ } else {
+ $line .= $to_text;
+ }
+ $line .= " $prefix</span>" .
+ "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+ return $line;
+}
+
+# process patch (diff) line (not to be used for diff headers),
+# returning HTML-formatted (but not wrapped) line.
+# If the line is passed as a reference, it is treated as HTML and not
+# esc_html()'ed.
+sub format_diff_line {
+ my ($line, $diff_class, $from, $to) = @_;
+
+ if (ref($line)) {
+ $line = $$line;
+ } else {
+ chomp $line;
+ $line = untabify($line);
+
+ if ($from && $to && $line =~ m/^\@{2} /) {
+ $line = format_unidiff_chunk_header($line, $from, $to);
+ } elsif ($from && $to && $line =~ m/^\@{3}/) {
+ $line = format_cc_diff_chunk_header($line, $from, $to);
} else {
- $line .= $to_text;
+ $line = esc_html($line, -nbsp=>1);
}
- $line .= " $prefix</span>" .
- "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
- return "<div class=\"diff$diff_class\">$line</div>\n";
}
- return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
+
+ my $diff_classes = "diff";
+ $diff_classes .= " $diff_class" if ($diff_class);
+ $line = "<div class=\"$diff_classes\">$line</div>\n";
+
+ return $line;
}
# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -2334,7 +2513,7 @@ sub get_feed_info {
return unless (defined $project);
# some views should link to OPML, or to generic project feed,
# or don't have specific feed yet (so they should use generic)
- return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
+ return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x);
my $branch;
# branches refs uses 'refs/heads/' prefix (fullname) to differentiate
@@ -2510,6 +2689,13 @@ sub git_get_project_config {
# key sanity check
return unless ($key);
+ # only subsection, if exists, is case sensitive,
+ # and not lowercased by 'git config -z -l'
+ if (my ($hi, $mi, $lo) = ($key =~ /^([^.]*)\.(.*)\.([^.]*)$/)) {
+ $key = join(".", lc($hi), $mi, lc($lo));
+ } else {
+ $key = lc($key);
+ }
$key =~ s/^gitweb\.//;
return if ($key =~ m/\W/);
@@ -2701,7 +2887,7 @@ sub git_populate_project_tagcloud {
}
my $cloud;
- my $matched = $cgi->param('by_tag');
+ my $matched = $input_params{'ctag'};
if (eval { require HTML::TagCloud; 1; }) {
$cloud = HTML::TagCloud->new;
foreach my $ctag (sort keys %ctags_lc) {
@@ -2763,19 +2949,18 @@ sub git_get_project_url_list {
sub git_get_projects_list {
my $filter = shift || '';
+ my $paranoid = shift;
my @list;
- $filter =~ s/\.git$//;
-
if (-d $projects_list) {
# search in directory
my $dir = $projects_list;
# remove the trailing "/"
$dir =~ s!/+$!!;
- my $pfxlen = length("$projects_list");
- my $pfxdepth = ($projects_list =~ tr!/!!);
+ my $pfxlen = length("$dir");
+ my $pfxdepth = ($dir =~ tr!/!!);
# when filtering, search only given subdirectory
- if ($filter) {
+ if ($filter && !$paranoid) {
$dir .= "/$filter";
$dir =~ s!/+$!!;
}
@@ -2800,6 +2985,10 @@ sub git_get_projects_list {
}
my $path = substr($File::Find::name, $pfxlen + 1);
+ # paranoidly only filter here
+ if ($paranoid && $filter && $path !~ m!^\Q$filter\E/!) {
+ next;
+ }
# we check related file in $projectroot
if (check_export_ok("$projectroot/$path")) {
push @list, { path => $path };
@@ -2829,9 +3018,11 @@ sub git_get_projects_list {
}
if (check_export_ok("$projectroot/$path")) {
my $pr = {
- path => $path,
- owner => to_utf8($owner),
+ path => $path
};
+ if ($owner) {
+ $pr->{'owner'} = to_utf8($owner);
+ }
push @list, $pr;
}
}
@@ -2852,7 +3043,7 @@ sub filter_forks_from_projects_list {
$path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory
next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git'
next unless ($path); # skip '.git' repository: tests, git-instaweb
- next unless (-d $path); # containing directory exists
+ next unless (-d "$projectroot/$path"); # containing directory exists
$pr->{'forks'} = []; # there can be 0 or more forks of project
# add to trie
@@ -2905,11 +3096,15 @@ sub filter_forks_from_projects_list {
sub search_projects_list {
my ($projlist, %opts) = @_;
my $tagfilter = $opts{'tagfilter'};
- my $searchtext = $opts{'searchtext'};
+ my $search_re = $opts{'search_regexp'};
return @$projlist
- unless ($tagfilter || $searchtext);
+ unless ($tagfilter || $search_re);
+ # searching projects require filling to be run before it;
+ fill_project_list_info($projlist,
+ $tagfilter ? 'ctags' : (),
+ $search_re ? ('path', 'descr') : ());
my @projects;
PROJECT:
foreach my $pr (@$projlist) {
@@ -2920,10 +3115,10 @@ sub search_projects_list {
grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
}
- if ($searchtext) {
+ if ($search_re) {
next unless
- $pr->{'path'} =~ /$searchtext/ ||
- $pr->{'descr_long'} =~ /$searchtext/;
+ $pr->{'path'} =~ /$search_re/ ||
+ $pr->{'descr_long'} =~ /$search_re/;
}
push @projects, $pr;
@@ -3562,12 +3757,9 @@ sub mimetype_guess_file {
open(my $mh, '<', $mimemap) or return undef;
while (<$mh>) {
next if m/^#/; # skip comments
- my ($mimetype, $exts) = split(/\t+/);
- if (defined $exts) {
- my @exts = split(/\s+/, $exts);
- foreach my $ext (@exts) {
- $mimemap{$ext} = $mimetype;
- }
+ my ($mimetype, @exts) = split(/\s+/);
+ foreach my $ext (@exts) {
+ $mimemap{$ext} = $mimetype;
}
}
close($mh);
@@ -3668,7 +3860,12 @@ sub run_highlighter {
sub get_page_title {
my $title = to_utf8($site_name);
- return $title unless (defined $project);
+ unless (defined $project) {
+ if (defined $project_filter) {
+ $title .= " - projects in '" . esc_path($project_filter) . "'";
+ }
+ return $title;
+ }
$title .= " - " . to_utf8($project);
return $title unless (defined $action);
@@ -3683,6 +3880,20 @@ sub get_page_title {
return $title;
}
+sub get_content_type_html {
+ # require explicit support from the UA if we are to send the page as
+ # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
+ # we have to do this because MSIE sometimes globs '*/*', pretending to
+ # support xhtml+xml but choking when it gets what it asked for.
+ if (defined $cgi->http('HTTP_ACCEPT') &&
+ $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
+ $cgi->Accept('application/xhtml+xml') != 0) {
+ return 'application/xhtml+xml';
+ } else {
+ return 'text/html';
+ }
+}
+
sub print_feed_meta {
if (defined $project) {
my %href_params = get_feed_info();
@@ -3698,6 +3909,7 @@ sub print_feed_meta {
'-type' => "application/$type+xml"
);
+ $href_params{'extra_options'} = undef;
$href_params{'action'} = $type;
$link_attr{'-href'} = href(%href_params);
print "<link ".
@@ -3728,24 +3940,107 @@ sub print_feed_meta {
}
}
+sub print_header_links {
+ my $status = shift;
+
+ # print out each stylesheet that exist, providing backwards capability
+ # for those people who defined $stylesheet in a config file
+ if (defined $stylesheet) {
+ print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
+ } else {
+ foreach my $stylesheet (@stylesheets) {
+ next unless $stylesheet;
+ print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
+ }
+ }
+ print_feed_meta()
+ if ($status eq '200 OK');
+ if (defined $favicon) {
+ print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
+ }
+}
+
+sub print_nav_breadcrumbs_path {
+ my $dirprefix = undef;
+ while (my $part = shift) {
+ $dirprefix .= "/" if defined $dirprefix;
+ $dirprefix .= $part;
+ print $cgi->a({-href => href(project => undef,
+ project_filter => $dirprefix,
+ action => "project_list")},
+ esc_html($part)) . " / ";
+ }
+}
+
+sub print_nav_breadcrumbs {
+ my %opts = @_;
+
+ print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
+ if (defined $project) {
+ my @dirname = split '/', $project;
+ my $projectbasename = pop @dirname;
+ print_nav_breadcrumbs_path(@dirname);
+ print $cgi->a({-href => href(action=>"summary")}, esc_html($projectbasename));
+ if (defined $action) {
+ my $action_print = $action ;
+ if (defined $opts{-action_extra}) {
+ $action_print = $cgi->a({-href => href(action=>$action)},
+ $action);
+ }
+ print " / $action_print";
+ }
+ if (defined $opts{-action_extra}) {
+ print " / $opts{-action_extra}";
+ }
+ print "\n";
+ } elsif (defined $project_filter) {
+ print_nav_breadcrumbs_path(split '/', $project_filter);
+ }
+}
+
+sub print_search_form {
+ if (!defined $searchtext) {
+ $searchtext = "";
+ }
+ my $search_hash;
+ if (defined $hash_base) {
+ $search_hash = $hash_base;
+ } elsif (defined $hash) {
+ $search_hash = $hash;
+ } else {
+ $search_hash = "HEAD";
+ }
+ my $action = $my_uri;
+ my $use_pathinfo = gitweb_check_feature('pathinfo');
+ if ($use_pathinfo) {
+ $action .= "/".esc_url($project);
+ }
+ print $cgi->startform(-method => "get", -action => $action) .
+ "<div class=\"search\">\n" .
+ (!$use_pathinfo &&
+ $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
+ $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
+ $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
+ $cgi->popup_menu(-name => 'st', -default => 'commit',
+ -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
+ $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+ " search:\n",
+ $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
+ "<span title=\"Extended regular expression\">" .
+ $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
+ -checked => $search_use_regexp) .
+ "</span>" .
+ "</div>" .
+ $cgi->end_form() . "\n";
+}
+
sub git_header_html {
my $status = shift || "200 OK";
my $expires = shift;
my %opts = @_;
my $title = get_page_title();
- my $content_type;
- # require explicit support from the UA if we are to send the page as
- # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
- # we have to do this because MSIE sometimes globs '*/*', pretending to
- # support xhtml+xml but choking when it gets what it asked for.
- if (defined $cgi->http('HTTP_ACCEPT') &&
- $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
- $cgi->Accept('application/xhtml+xml') != 0) {
- $content_type = 'application/xhtml+xml';
- } else {
- $content_type = 'text/html';
- }
+ my $content_type = get_content_type_html();
print $cgi->header(-type=>$content_type, -charset => 'utf-8',
-status=> $status, -expires => $expires)
unless ($opts{'-no_http_header'});
@@ -3767,20 +4062,10 @@ EOF
if ($ENV{'PATH_INFO'}) {
print "<base href=\"".esc_url($base_url)."\" />\n";
}
- # print out each stylesheet that exist, providing backwards capability
- # for those people who defined $stylesheet in a config file
- if (defined $stylesheet) {
- print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
- } else {
- foreach my $stylesheet (@stylesheets) {
- next unless $stylesheet;
- print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
- }
- }
- print_feed_meta()
- if ($status eq '200 OK');
- if (defined $favicon) {
- print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
+ print_header_links($status);
+
+ if (defined $site_html_head_string) {
+ print to_utf8($site_html_head_string);
}
print "</head>\n" .
@@ -3799,59 +4084,12 @@ EOF
-alt => "git",
-class => "logo"}));
}
- print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
- if (defined $project) {
- print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
- if (defined $action) {
- my $action_print = $action ;
- if (defined $opts{-action_extra}) {
- $action_print = $cgi->a({-href => href(action=>$action)},
- $action);
- }
- print " / $action_print";
- }
- if (defined $opts{-action_extra}) {
- print " / $opts{-action_extra}";
- }
- print "\n";
- }
+ print_nav_breadcrumbs(%opts);
print "</div>\n";
my $have_search = gitweb_check_feature('search');
if (defined $project && $have_search) {
- if (!defined $searchtext) {
- $searchtext = "";
- }
- my $search_hash;
- if (defined $hash_base) {
- $search_hash = $hash_base;
- } elsif (defined $hash) {
- $search_hash = $hash;
- } else {
- $search_hash = "HEAD";
- }
- my $action = $my_uri;
- my $use_pathinfo = gitweb_check_feature('pathinfo');
- if ($use_pathinfo) {
- $action .= "/".esc_url($project);
- }
- print $cgi->startform(-method => "get", -action => $action) .
- "<div class=\"search\">\n" .
- (!$use_pathinfo &&
- $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
- $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
- $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
- $cgi->popup_menu(-name => 'st', -default => 'commit',
- -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
- $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
- " search:\n",
- $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
- "<span title=\"Extended regular expression\">" .
- $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
- -checked => $search_use_regexp) .
- "</span>" .
- "</div>" .
- $cgi->end_form() . "\n";
+ print_search_form();
}
}
@@ -3879,9 +4117,11 @@ sub git_footer_html {
}
} else {
- print $cgi->a({-href => href(project=>undef, action=>"opml"),
+ print $cgi->a({-href => href(project=>undef, action=>"opml",
+ project_filter => $project_filter),
-class => $feed_class}, "OPML") . " ";
- print $cgi->a({-href => href(project=>undef, action=>"project_index"),
+ print $cgi->a({-href => href(project=>undef, action=>"project_index",
+ project_filter => $project_filter),
-class => $feed_class}, "TXT") . "\n";
}
print "</div>\n"; # class="page_footer"
@@ -4777,8 +5017,239 @@ sub git_difftree_body {
print "</table>\n";
}
+# Print context lines and then rem/add lines in a side-by-side manner.
+sub print_sidebyside_diff_lines {
+ my ($ctx, $rem, $add) = @_;
+
+ # print context block before add/rem block
+ if (@$ctx) {
+ print join '',
+ '<div class="chunk_block ctx">',
+ '<div class="old">',
+ @$ctx,
+ '</div>',
+ '<div class="new">',
+ @$ctx,
+ '</div>',
+ '</div>';
+ }
+
+ if (!@$add) {
+ # pure removal
+ print join '',
+ '<div class="chunk_block rem">',
+ '<div class="old">',
+ @$rem,
+ '</div>',
+ '</div>';
+ } elsif (!@$rem) {
+ # pure addition
+ print join '',
+ '<div class="chunk_block add">',
+ '<div class="new">',
+ @$add,
+ '</div>',
+ '</div>';
+ } else {
+ print join '',
+ '<div class="chunk_block chg">',
+ '<div class="old">',
+ @$rem,
+ '</div>',
+ '<div class="new">',
+ @$add,
+ '</div>',
+ '</div>';
+ }
+}
+
+# Print context lines and then rem/add lines in inline manner.
+sub print_inline_diff_lines {
+ my ($ctx, $rem, $add) = @_;
+
+ print @$ctx, @$rem, @$add;
+}
+
+# Format removed and added line, mark changed part and HTML-format them.
+# Implementation is based on contrib/diff-highlight
+sub format_rem_add_lines_pair {
+ my ($rem, $add, $num_parents) = @_;
+
+ # We need to untabify lines before split()'ing them;
+ # otherwise offsets would be invalid.
+ chomp $rem;
+ chomp $add;
+ $rem = untabify($rem);
+ $add = untabify($add);
+
+ my @rem = split(//, $rem);
+ my @add = split(//, $add);
+ my ($esc_rem, $esc_add);
+ # Ignore leading +/- characters for each parent.
+ my ($prefix_len, $suffix_len) = ($num_parents, 0);
+ my ($prefix_has_nonspace, $suffix_has_nonspace);
+
+ my $shorter = (@rem < @add) ? @rem : @add;
+ while ($prefix_len < $shorter) {
+ last if ($rem[$prefix_len] ne $add[$prefix_len]);
+
+ $prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
+ $prefix_len++;
+ }
+
+ while ($prefix_len + $suffix_len < $shorter) {
+ last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
+
+ $suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
+ $suffix_len++;
+ }
+
+ # Mark lines that are different from each other, but have some common
+ # part that isn't whitespace. If lines are completely different, don't
+ # mark them because that would make output unreadable, especially if
+ # diff consists of multiple lines.
+ if ($prefix_has_nonspace || $suffix_has_nonspace) {
+ $esc_rem = esc_html_hl_regions($rem, 'marked',
+ [$prefix_len, @rem - $suffix_len], -nbsp=>1);
+ $esc_add = esc_html_hl_regions($add, 'marked',
+ [$prefix_len, @add - $suffix_len], -nbsp=>1);
+ } else {
+ $esc_rem = esc_html($rem, -nbsp=>1);
+ $esc_add = esc_html($add, -nbsp=>1);
+ }
+
+ return format_diff_line(\$esc_rem, 'rem'),
+ format_diff_line(\$esc_add, 'add');
+}
+
+# HTML-format diff context, removed and added lines.
+sub format_ctx_rem_add_lines {
+ my ($ctx, $rem, $add, $num_parents) = @_;
+ my (@new_ctx, @new_rem, @new_add);
+ my $can_highlight = 0;
+ my $is_combined = ($num_parents > 1);
+
+ # Highlight if every removed line has a corresponding added line.
+ if (@$add > 0 && @$add == @$rem) {
+ $can_highlight = 1;
+
+ # Highlight lines in combined diff only if the chunk contains
+ # diff between the same version, e.g.
+ #
+ # - a
+ # - b
+ # + c
+ # + d
+ #
+ # Otherwise the highlightling would be confusing.
+ if ($is_combined) {
+ for (my $i = 0; $i < @$add; $i++) {
+ my $prefix_rem = substr($rem->[$i], 0, $num_parents);
+ my $prefix_add = substr($add->[$i], 0, $num_parents);
+
+ $prefix_rem =~ s/-/+/g;
+
+ if ($prefix_rem ne $prefix_add) {
+ $can_highlight = 0;
+ last;
+ }
+ }
+ }
+ }
+
+ if ($can_highlight) {
+ for (my $i = 0; $i < @$add; $i++) {
+ my ($line_rem, $line_add) = format_rem_add_lines_pair(
+ $rem->[$i], $add->[$i], $num_parents);
+ push @new_rem, $line_rem;
+ push @new_add, $line_add;
+ }
+ } else {
+ @new_rem = map { format_diff_line($_, 'rem') } @$rem;
+ @new_add = map { format_diff_line($_, 'add') } @$add;
+ }
+
+ @new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
+
+ return (\@new_ctx, \@new_rem, \@new_add);
+}
+
+# Print context lines and then rem/add lines.
+sub print_diff_lines {
+ my ($ctx, $rem, $add, $diff_style, $num_parents) = @_;
+ my $is_combined = $num_parents > 1;
+
+ ($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
+ $num_parents);
+
+ if ($diff_style eq 'sidebyside' && !$is_combined) {
+ print_sidebyside_diff_lines($ctx, $rem, $add);
+ } else {
+ # default 'inline' style and unknown styles
+ print_inline_diff_lines($ctx, $rem, $add);
+ }
+}
+
+sub print_diff_chunk {
+ my ($diff_style, $num_parents, $from, $to, @chunk) = @_;
+ my (@ctx, @rem, @add);
+
+ # The class of the previous line.
+ my $prev_class = '';
+
+ return unless @chunk;
+
+ # incomplete last line might be among removed or added lines,
+ # or both, or among context lines: find which
+ for (my $i = 1; $i < @chunk; $i++) {
+ if ($chunk[$i][0] eq 'incomplete') {
+ $chunk[$i][0] = $chunk[$i-1][0];
+ }
+ }
+
+ # guardian
+ push @chunk, ["", ""];
+
+ foreach my $line_info (@chunk) {
+ my ($class, $line) = @$line_info;
+
+ # print chunk headers
+ if ($class && $class eq 'chunk_header') {
+ print format_diff_line($line, $class, $from, $to);
+ next;
+ }
+
+ ## print from accumulator when have some add/rem lines or end
+ # of chunk (flush context lines), or when have add and rem
+ # lines and new block is reached (otherwise add/rem lines could
+ # be reordered)
+ if (!$class || ((@rem || @add) && $class eq 'ctx') ||
+ (@rem && @add && $class ne $prev_class)) {
+ print_diff_lines(\@ctx, \@rem, \@add,
+ $diff_style, $num_parents);
+ @ctx = @rem = @add = ();
+ }
+
+ ## adding lines to accumulator
+ # guardian value
+ last unless $line;
+ # rem, add or change
+ if ($class eq 'rem') {
+ push @rem, $line;
+ } elsif ($class eq 'add') {
+ push @add, $line;
+ }
+ # context line
+ if ($class eq 'ctx') {
+ push @ctx, $line;
+ }
+
+ $prev_class = $class;
+ }
+}
+
sub git_patchset_body {
- my ($fd, $difftree, $hash, @hash_parents) = @_;
+ my ($fd, $diff_style, $difftree, $hash, @hash_parents) = @_;
my ($hash_parent) = $hash_parents[0];
my $is_combined = (@hash_parents > 1);
@@ -4788,6 +5259,7 @@ sub git_patchset_body {
my $diffinfo;
my $to_name;
my (%from, %to);
+ my @chunk; # for side-by-side diff
print "<div class=\"patchset\">\n";
@@ -4894,10 +5366,21 @@ sub git_patchset_body {
next PATCH if ($patch_line =~ m/^diff /);
- print format_diff_line($patch_line, \%from, \%to);
+ my $class = diff_line_class($patch_line, \%from, \%to);
+
+ if ($class eq 'chunk_header') {
+ print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
+ @chunk = ();
+ }
+
+ push @chunk, [ $class, $patch_line ];
}
} continue {
+ if (@chunk) {
+ print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
+ @chunk = ();
+ }
print "</div>\n"; # class="patch"
}
@@ -4930,35 +5413,98 @@ sub git_patchset_body {
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-# fills project list info (age, description, owner, category, forks)
+sub git_project_search_form {
+ my ($searchtext, $search_use_regexp) = @_;
+
+ my $limit = '';
+ if ($project_filter) {
+ $limit = " in '$project_filter/'";
+ }
+
+ print "<div class=\"projsearch\">\n";
+ print $cgi->startform(-method => 'get', -action => $my_uri) .
+ $cgi->hidden(-name => 'a', -value => 'project_list') . "\n";
+ print $cgi->hidden(-name => 'pf', -value => $project_filter). "\n"
+ if (defined $project_filter);
+ print $cgi->textfield(-name => 's', -value => $searchtext,
+ -title => "Search project by name and description$limit",
+ -size => 60) . "\n" .
+ "<span title=\"Extended regular expression\">" .
+ $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
+ -checked => $search_use_regexp) .
+ "</span>\n" .
+ $cgi->submit(-name => 'btnS', -value => 'Search') .
+ $cgi->end_form() . "\n" .
+ $cgi->a({-href => href(project => undef, searchtext => undef,
+ project_filter => $project_filter)},
+ esc_html("List all projects$limit")) . "<br />\n";
+ print "</div>\n";
+}
+
+# entry for given @keys needs filling if at least one of keys in list
+# is not present in %$project_info
+sub project_info_needs_filling {
+ my ($project_info, @keys) = @_;
+
+ # return List::MoreUtils::any { !exists $project_info->{$_} } @keys;
+ foreach my $key (@keys) {
+ if (!exists $project_info->{$key}) {
+ return 1;
+ }
+ }
+ return;
+}
+
+# fills project list info (age, description, owner, category, forks, etc.)
# for each project in the list, removing invalid projects from
-# returned list
+# returned list, or fill only specified info.
+#
+# Invalid projects are removed from the returned list if and only if you
+# ask 'age' or 'age_string' to be filled, because they are the only fields
+# that run unconditionally git command that requires repository, and
+# therefore do always check if project repository is invalid.
+#
+# USAGE:
+# * fill_project_list_info(\@project_list, 'descr_long', 'ctags')
+# ensures that 'descr_long' and 'ctags' fields are filled
+# * @project_list = fill_project_list_info(\@project_list)
+# ensures that all fields are filled (and invalid projects removed)
+#
# NOTE: modifies $projlist, but does not remove entries from it
sub fill_project_list_info {
- my $projlist = shift;
+ my ($projlist, @wanted_keys) = @_;
my @projects;
+ my $filter_set = sub { return @_; };
+ if (@wanted_keys) {
+ my %wanted_keys = map { $_ => 1 } @wanted_keys;
+ $filter_set = sub { return grep { $wanted_keys{$_} } @_; };
+ }
my $show_ctags = gitweb_check_feature('ctags');
PROJECT:
foreach my $pr (@$projlist) {
- my (@activity) = git_get_last_activity($pr->{'path'});
- unless (@activity) {
- next PROJECT;
+ if (project_info_needs_filling($pr, $filter_set->('age', 'age_string'))) {
+ my (@activity) = git_get_last_activity($pr->{'path'});
+ unless (@activity) {
+ next PROJECT;
+ }
+ ($pr->{'age'}, $pr->{'age_string'}) = @activity;
}
- ($pr->{'age'}, $pr->{'age_string'}) = @activity;
- if (!defined $pr->{'descr'}) {
+ if (project_info_needs_filling($pr, $filter_set->('descr', 'descr_long'))) {
my $descr = git_get_project_description($pr->{'path'}) || "";
$descr = to_utf8($descr);
$pr->{'descr_long'} = $descr;
$pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
}
- if (!defined $pr->{'owner'}) {
+ if (project_info_needs_filling($pr, $filter_set->('owner'))) {
$pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
}
- if ($show_ctags) {
+ if ($show_ctags &&
+ project_info_needs_filling($pr, $filter_set->('ctags'))) {
$pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
}
- if ($projects_list_group_categories && !defined $pr->{'category'}) {
+ if ($projects_list_group_categories &&
+ project_info_needs_filling($pr, $filter_set->('category'))) {
my $cat = git_get_project_category($pr->{'path'}) ||
$project_list_default_category;
$pr->{'category'} = to_utf8($cat);
@@ -5062,14 +5608,25 @@ sub git_project_list_rows {
print "</td>\n";
}
print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
- -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+ -class => "list"},
+ esc_html_match_hl($pr->{'path'}, $search_regexp)) .
+ "</td>\n" .
"<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
- -class => "list", -title => $pr->{'descr_long'}},
- esc_html($pr->{'descr'})) . "</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\">" .
+ -class => "list",
+ -title => $pr->{'descr_long'}},
+ $search_regexp
+ ? esc_html_match_hl_chopped($pr->{'descr_long'},
+ $pr->{'descr'}, $search_regexp)
+ : esc_html($pr->{'descr'})) .
+ "</td>\n";
+ unless ($omit_owner) {
+ print "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
+ }
+ unless ($omit_age_column) {
+ print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+ (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n";
+ }
+ print"<td class=\"link\">" .
$cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
$cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
$cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
@@ -5087,19 +5644,23 @@ sub git_project_list_body {
my $check_forks = gitweb_check_feature('forks');
my $show_ctags = gitweb_check_feature('ctags');
- my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef;
+ my $tagfilter = $show_ctags ? $input_params{'ctag'} : undef;
$check_forks = undef
- if ($tagfilter || $searchtext);
+ if ($tagfilter || $search_regexp);
# filtering out forks before filling info allows to do less work
@projects = filter_forks_from_projects_list(\@projects)
if ($check_forks);
- @projects = fill_project_list_info(\@projects);
- # searching projects require filling to be run before it
+ # search_projects_list pre-fills required info
@projects = search_projects_list(\@projects,
- 'searchtext' => $searchtext,
+ 'search_regexp' => $search_regexp,
'tagfilter' => $tagfilter)
- if ($tagfilter || $searchtext);
+ if ($tagfilter || $search_regexp);
+ # fill the rest
+ my @all_fields = ('descr', 'descr_long', 'ctags', 'category');
+ push @all_fields, ('age', 'age_string') unless($omit_age_column);
+ push @all_fields, 'owner' unless($omit_owner);
+ @projects = fill_project_list_info(\@projects, @all_fields);
$order ||= $default_projects_order;
$from = 0 unless defined $from;
@@ -5130,8 +5691,8 @@ sub git_project_list_body {
}
print_sort_th('project', $order, 'Project');
print_sort_th('descr', $order, 'Description');
- print_sort_th('owner', $order, 'Owner');
- print_sort_th('age', $order, 'Last Change');
+ print_sort_th('owner', $order, 'Owner') unless $omit_owner;
+ print_sort_th('age', $order, 'Last Change') unless $omit_age_column;
print "<th></th>\n" . # for links
"</tr>\n";
}
@@ -5375,7 +5936,7 @@ sub git_tags_body {
sub git_heads_body {
# uses global variable $project
- my ($headlist, $head, $from, $to, $extra) = @_;
+ my ($headlist, $head_at, $from, $to, $extra) = @_;
$from = 0 unless defined $from;
$to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
@@ -5384,7 +5945,7 @@ sub git_heads_body {
for (my $i = $from; $i <= $to; $i++) {
my $entry = $headlist->[$i];
my %ref = %$entry;
- my $curr = $ref{'id'} eq $head;
+ my $curr = defined $head_at && $ref{'id'} eq $head_at;
if ($alternate) {
print "<tr class=\"dark\">\n";
} else {
@@ -5506,6 +6067,217 @@ sub git_remotes_body {
}
}
+sub git_search_message {
+ my %co = @_;
+
+ my $greptype;
+ if ($searchtype eq 'commit') {
+ $greptype = "--grep=";
+ } elsif ($searchtype eq 'author') {
+ $greptype = "--author=";
+ } elsif ($searchtype eq 'committer') {
+ $greptype = "--committer=";
+ }
+ $greptype .= $searchtext;
+ my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
+ $greptype, '--regexp-ignore-case',
+ $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
+
+ my $paging_nav = '';
+ if ($page > 0) {
+ $paging_nav .=
+ $cgi->a({-href => href(-replay=>1, page=>undef)},
+ "first") .
+ " &sdot; " .
+ $cgi->a({-href => href(-replay=>1, page=>$page-1),
+ -accesskey => "p", -title => "Alt-p"}, "prev");
+ } else {
+ $paging_nav .= "first &sdot; prev";
+ }
+ my $next_link = '';
+ if ($#commitlist >= 100) {
+ $next_link =
+ $cgi->a({-href => href(-replay=>1, page=>$page+1),
+ -accesskey => "n", -title => "Alt-n"}, "next");
+ $paging_nav .= " &sdot; $next_link";
+ } else {
+ $paging_nav .= " &sdot; next";
+ }
+
+ git_header_html();
+
+ git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash);
+ if ($page == 0 && !@commitlist) {
+ print "<p>No match.</p>\n";
+ } else {
+ git_search_grep_body(\@commitlist, 0, 99, $next_link);
+ }
+
+ git_footer_html();
+}
+
+sub git_search_changes {
+ my %co = @_;
+
+ local $/ = "\n";
+ open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
+ '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
+ ($search_use_regexp ? '--pickaxe-regex' : ())
+ or die_error(500, "Open git-log failed");
+
+ git_header_html();
+
+ git_print_page_nav('','', $hash,$co{'tree'},$hash);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+ print "<table class=\"pickaxe search\">\n";
+ my $alternate = 1;
+ undef %co;
+ my @files;
+ while (my $line = <$fd>) {
+ chomp $line;
+ next unless $line;
+
+ my %set = parse_difftree_raw_line($line);
+ if (defined $set{'commit'}) {
+ # finish previous commit
+ if (%co) {
+ print "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
+ "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
+ hash_base=>$co{'id'})},
+ "tree") .
+ "</td>\n" .
+ "</tr>\n";
+ }
+
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ %co = parse_commit($set{'commit'});
+ my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
+ print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+ "<td><i>$author</i></td>\n" .
+ "<td>" .
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
+ -class => "list subject"},
+ chop_and_escape_str($co{'title'}, 50) . "<br/>");
+ } elsif (defined $set{'to_id'}) {
+ next if ($set{'to_id'} =~ m/^0{40}$/);
+
+ print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
+ hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
+ -class => "list"},
+ "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
+ "<br/>\n";
+ }
+ }
+ close $fd;
+
+ # finish last commit (warning: repetition!)
+ if (%co) {
+ print "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
+ "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
+ hash_base=>$co{'id'})},
+ "tree") .
+ "</td>\n" .
+ "</tr>\n";
+ }
+
+ print "</table>\n";
+
+ git_footer_html();
+}
+
+sub git_search_files {
+ my %co = @_;
+
+ local $/ = "\n";
+ open my $fd, "-|", git_cmd(), 'grep', '-n', '-z',
+ $search_use_regexp ? ('-E', '-i') : '-F',
+ $searchtext, $co{'tree'}
+ or die_error(500, "Open git-grep failed");
+
+ git_header_html();
+
+ git_print_page_nav('','', $hash,$co{'tree'},$hash);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+ print "<table class=\"grep_search\">\n";
+ my $alternate = 1;
+ my $matches = 0;
+ my $lastfile = '';
+ my $file_href;
+ while (my $line = <$fd>) {
+ chomp $line;
+ my ($file, $lno, $ltext, $binary);
+ last if ($matches++ > 1000);
+ if ($line =~ /^Binary file (.+) matches$/) {
+ $file = $1;
+ $binary = 1;
+ } else {
+ ($file, $lno, $ltext) = split(/\0/, $line, 3);
+ $file =~ s/^$co{'tree'}://;
+ }
+ if ($file ne $lastfile) {
+ $lastfile and print "</td></tr>\n";
+ if ($alternate++) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $file_href = href(action=>"blob", hash_base=>$co{'id'},
+ file_name=>$file);
+ print "<td class=\"list\">".
+ $cgi->a({-href => $file_href, -class => "list"}, esc_path($file));
+ print "</td><td>\n";
+ $lastfile = $file;
+ }
+ if ($binary) {
+ print "<div class=\"binary\">Binary file</div>\n";
+ } else {
+ $ltext = untabify($ltext);
+ if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
+ $ltext = esc_html($1, -nbsp=>1);
+ $ltext .= '<span class="match">';
+ $ltext .= esc_html($2, -nbsp=>1);
+ $ltext .= '</span>';
+ $ltext .= esc_html($3, -nbsp=>1);
+ } else {
+ $ltext = esc_html($ltext, -nbsp=>1);
+ }
+ print "<div class=\"pre\">" .
+ $cgi->a({-href => $file_href.'#l'.$lno,
+ -class => "linenr"}, sprintf('%4i', $lno)) .
+ ' ' . $ltext . "</div>\n";
+ }
+ }
+ if ($lastfile) {
+ print "</td></tr>\n";
+ if ($matches > 1000) {
+ print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
+ }
+ } else {
+ print "<div class=\"diff nodifferences\">No matches found</div>\n";
+ }
+ close $fd;
+
+ print "</table>\n";
+
+ git_footer_html();
+}
+
sub git_search_grep_body {
my ($commitlist, $from, $to, $extra) = @_;
$from = 0 unless defined $from;
@@ -5576,7 +6348,7 @@ sub git_project_list {
die_error(400, "Unknown order parameter");
}
- my @list = git_get_projects_list();
+ my @list = git_get_projects_list($project_filter, $strict_export);
if (!@list) {
die_error(404, "No projects found");
}
@@ -5587,11 +6359,8 @@ sub git_project_list {
insert_file($home_text);
print "</div>\n";
}
- print $cgi->startform(-method => "get") .
- "<p class=\"projsearch\">Search:\n" .
- $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
- "</p>" .
- $cgi->end_form() . "\n";
+
+ git_project_search_form($searchtext, $search_use_regexp);
git_project_list_body(\@list, $order);
git_footer_html();
}
@@ -5602,7 +6371,9 @@ sub git_forks {
die_error(400, "Unknown order parameter");
}
- my @list = git_get_projects_list($project);
+ my $filter = $project;
+ $filter =~ s/\.git$//;
+ my @list = git_get_projects_list($filter);
if (!@list) {
die_error(404, "No forks found");
}
@@ -5615,7 +6386,7 @@ sub git_forks {
}
sub git_project_index {
- my @projects = git_get_projects_list();
+ my @projects = git_get_projects_list($project_filter, $strict_export);
if (!@projects) {
die_error(404, "No projects found");
}
@@ -5661,7 +6432,9 @@ sub git_summary {
if ($check_forks) {
# find forks of a project
- @forklist = git_get_projects_list($project);
+ my $filter = $project;
+ $filter =~ s/\.git$//;
+ @forklist = git_get_projects_list($filter);
# filter out forks of forks
@forklist = filter_forks_from_projects_list(\@forklist)
if (@forklist);
@@ -5672,8 +6445,10 @@ sub git_summary {
print "<div class=\"title\">&nbsp;</div>\n";
print "<table class=\"projects_list\">\n" .
- "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
- "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
+ "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n";
+ unless ($omit_owner) {
+ print "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
+ }
if (defined $cd{'rfc2822'}) {
print "<tr id=\"metadata_lchange\"><td>last change</td>" .
"<td>".format_timestamp_html(\%cd)."</td></tr>\n";
@@ -5792,7 +6567,7 @@ sub git_tag {
sub git_blame_common {
my $format = shift || 'porcelain';
- if ($format eq 'porcelain' && $cgi->param('js')) {
+ if ($format eq 'porcelain' && $input_params{'javascript'}) {
$format = 'incremental';
$action = 'blame_incremental'; # for page title etc
}
@@ -5841,7 +6616,9 @@ sub git_blame_common {
-type=>"text/plain", -charset => "utf-8",
-status=> "200 OK");
local $| = 1; # output autoflush
- print while <$fd>;
+ while (my $line = <$fd>) {
+ print to_utf8($line);
+ }
close $fd
or print "ERROR $!\n";
@@ -6236,7 +7013,8 @@ sub git_blob {
$nr++;
$line = untabify($line);
printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
- $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
+ $nr, esc_attr(href(-replay => 1)), $nr, $nr,
+ $syntax ? sanitize($line) : esc_html($line, -nbsp=>1);
}
}
close $fd
@@ -6392,6 +7170,28 @@ sub snapshot_name {
return wantarray ? ($name, $name) : $name;
}
+sub exit_if_unmodified_since {
+ my ($latest_epoch) = @_;
+ our $cgi;
+
+ my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
+ if (defined $if_modified) {
+ my $since;
+ if (eval { require HTTP::Date; 1; }) {
+ $since = HTTP::Date::str2time($if_modified);
+ } elsif (eval { require Time::ParseDate; 1; }) {
+ $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
+ }
+ if (defined $since && $latest_epoch <= $since) {
+ my %latest_date = parse_date($latest_epoch);
+ print $cgi->header(
+ -last_modified => $latest_date{'rfc2822'},
+ -status => '304 Not Modified');
+ goto DONE_GITWEB;
+ }
+ }
+}
+
sub git_snapshot {
my $format = $input_params{'snapshot_format'};
if (!@snapshot_fmts) {
@@ -6418,6 +7218,10 @@ sub git_snapshot {
my ($name, $prefix) = snapshot_name($project, $hash);
my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
+
+ my %co = parse_commit($hash);
+ exit_if_unmodified_since($co{'committer_epoch'}) if %co;
+
my $cmd = quote_command(
git_cmd(), 'archive',
"--format=$known_snapshot_formats{$format}{'format'}",
@@ -6427,9 +7231,15 @@ sub git_snapshot {
}
$filename =~ s/(["\\])/\\$1/g;
+ my %latest_date;
+ if (%co) {
+ %latest_date = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+ }
+
print $cgi->header(
-type => $known_snapshot_formats{$format}{'type'},
-content_disposition => 'inline; filename="' . $filename . '"',
+ %co ? (-last_modified => $latest_date{'rfc2822'}) : (),
-status => '200 OK');
open my $fd, "-|", $cmd
@@ -6682,6 +7492,7 @@ sub git_object {
sub git_blobdiff {
my $format = shift || 'html';
+ my $diff_style = $input_params{'diff_style'} || 'inline';
my $fd;
my @difftree;
@@ -6760,6 +7571,7 @@ sub git_blobdiff {
my $formats_nav =
$cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
"raw");
+ $formats_nav .= diff_style_nav($diff_style);
git_header_html(undef, $expires);
if (defined $hash_base && (my %co = parse_commit($hash_base))) {
git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
@@ -6791,7 +7603,8 @@ sub git_blobdiff {
if ($format eq 'html') {
print "<div class=\"page_body\">\n";
- git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
+ git_patchset_body($fd, $diff_style,
+ [ \%diffinfo ], $hash_base, $hash_parent_base);
close $fd;
print "</div>\n"; # class="page_body"
@@ -6816,9 +7629,31 @@ sub git_blobdiff_plain {
git_blobdiff('plain');
}
+# assumes that it is added as later part of already existing navigation,
+# so it returns "| foo | bar" rather than just "foo | bar"
+sub diff_style_nav {
+ my ($diff_style, $is_combined) = @_;
+ $diff_style ||= 'inline';
+
+ return "" if ($is_combined);
+
+ my @styles = (inline => 'inline', 'sidebyside' => 'side by side');
+ my %styles = @styles;
+ @styles =
+ @styles[ map { $_ * 2 } 0..$#styles/2 ];
+
+ return join '',
+ map { " | ".$_ }
+ map {
+ $_ eq $diff_style ? $styles{$_} :
+ $cgi->a({-href => href(-replay=>1, diff_style => $_)}, $styles{$_})
+ } @styles;
+}
+
sub git_commitdiff {
my %params = @_;
my $format = $params{-format} || 'html';
+ my $diff_style = $input_params{'diff_style'} || 'inline';
my ($patch_max) = gitweb_get_feature('patches');
if ($format eq 'patch') {
@@ -6844,6 +7679,7 @@ sub git_commitdiff {
$cgi->a({-href => href(action=>"patch", -replay=>1)},
"patch");
}
+ $formats_nav .= diff_style_nav($diff_style, @{$co{'parents'}} > 1);
if (defined $hash_parent &&
$hash_parent ne '-c' && $hash_parent ne '--cc') {
@@ -6861,8 +7697,8 @@ sub git_commitdiff {
}
}
$formats_nav .= ': ' .
- $cgi->a({-href => href(action=>"commitdiff",
- hash=>$hash_parent)},
+ $cgi->a({-href => href(-replay=>1,
+ hash=>$hash_parent, hash_base=>undef)},
esc_html($hash_parent_short)) .
')';
} elsif (!$co{'parent'}) {
@@ -6872,28 +7708,28 @@ sub git_commitdiff {
# single parent commit
$formats_nav .=
' (parent: ' .
- $cgi->a({-href => href(action=>"commitdiff",
- hash=>$co{'parent'})},
+ $cgi->a({-href => href(-replay=>1,
+ hash=>$co{'parent'}, hash_base=>undef)},
esc_html(substr($co{'parent'}, 0, 7))) .
')';
} else {
# merge commit
if ($hash_parent eq '--cc') {
$formats_nav .= ' | ' .
- $cgi->a({-href => href(action=>"commitdiff",
+ $cgi->a({-href => href(-replay=>1,
hash=>$hash, hash_parent=>'-c')},
'combined');
} else { # $hash_parent eq '-c'
$formats_nav .= ' | ' .
- $cgi->a({-href => href(action=>"commitdiff",
+ $cgi->a({-href => href(-replay=>1,
hash=>$hash, hash_parent=>'--cc')},
'compact');
}
$formats_nav .=
' (merge: ' .
join(' ', map {
- $cgi->a({-href => href(action=>"commitdiff",
- hash=>$_)},
+ $cgi->a({-href => href(-replay=>1,
+ hash=>$_, hash_base=>undef)},
esc_html(substr($_, 0, 7)));
} @{$co{'parents'}} ) .
')';
@@ -7022,7 +7858,8 @@ sub git_commitdiff {
$use_parents ? @{$co{'parents'}} : $hash_parent);
print "<br/>\n";
- git_patchset_body($fd, \@difftree, $hash,
+ git_patchset_body($fd, $diff_style,
+ \@difftree, $hash,
$use_parents ? @{$co{'parents'}} : $hash_parent);
close $fd;
print "</div>\n"; # class="page_body"
@@ -7061,7 +7898,23 @@ sub git_history {
}
sub git_search {
- gitweb_check_feature('search') or die_error(403, "Search is disabled");
+ $searchtype ||= 'commit';
+
+ # check if appropriate features are enabled
+ gitweb_check_feature('search')
+ or die_error(403, "Search is disabled");
+ if ($searchtype eq 'pickaxe') {
+ # pickaxe may take all resources of your box and run for several minutes
+ # with every query - so decide by yourself how public you make this feature
+ gitweb_check_feature('pickaxe')
+ or die_error(403, "Pickaxe search is disabled");
+ }
+ if ($searchtype eq 'grep') {
+ # grep search might be potentially CPU-intensive, too
+ gitweb_check_feature('grep')
+ or die_error(403, "Grep search is disabled");
+ }
+
if (!defined $searchtext) {
die_error(400, "Text field is empty");
}
@@ -7076,205 +7929,17 @@ sub git_search {
$page = 0;
}
- $searchtype ||= 'commit';
- if ($searchtype eq 'pickaxe') {
- # pickaxe may take all resources of your box and run for several minutes
- # with every query - so decide by yourself how public you make this feature
- gitweb_check_feature('pickaxe')
- or die_error(403, "Pickaxe is disabled");
- }
- if ($searchtype eq 'grep') {
- gitweb_check_feature('grep')
- or die_error(403, "Grep is disabled");
- }
-
- git_header_html();
-
- if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
- my $greptype;
- if ($searchtype eq 'commit') {
- $greptype = "--grep=";
- } elsif ($searchtype eq 'author') {
- $greptype = "--author=";
- } elsif ($searchtype eq 'committer') {
- $greptype = "--committer=";
- }
- $greptype .= $searchtext;
- my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
- $greptype, '--regexp-ignore-case',
- $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
-
- my $paging_nav = '';
- if ($page > 0) {
- $paging_nav .=
- $cgi->a({-href => href(action=>"search", hash=>$hash,
- searchtext=>$searchtext,
- searchtype=>$searchtype)},
- "first");
- $paging_nav .= " &sdot; " .
- $cgi->a({-href => href(-replay=>1, page=>$page-1),
- -accesskey => "p", -title => "Alt-p"}, "prev");
- } else {
- $paging_nav .= "first";
- $paging_nav .= " &sdot; prev";
- }
- my $next_link = '';
- if ($#commitlist >= 100) {
- $next_link =
- $cgi->a({-href => href(-replay=>1, page=>$page+1),
- -accesskey => "n", -title => "Alt-n"}, "next");
- $paging_nav .= " &sdot; $next_link";
- } else {
- $paging_nav .= " &sdot; next";
- }
-
- git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
- git_print_header_div('commit', esc_html($co{'title'}), $hash);
- if ($page == 0 && !@commitlist) {
- print "<p>No match.</p>\n";
- } else {
- git_search_grep_body(\@commitlist, 0, 99, $next_link);
- }
- }
-
- if ($searchtype eq 'pickaxe') {
- git_print_page_nav('','', $hash,$co{'tree'},$hash);
- git_print_header_div('commit', esc_html($co{'title'}), $hash);
-
- print "<table class=\"pickaxe search\">\n";
- my $alternate = 1;
- local $/ = "\n";
- open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
- '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
- ($search_use_regexp ? '--pickaxe-regex' : ());
- undef %co;
- my @files;
- while (my $line = <$fd>) {
- chomp $line;
- next unless $line;
-
- my %set = parse_difftree_raw_line($line);
- if (defined $set{'commit'}) {
- # finish previous commit
- if (%co) {
- print "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
- " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
- print "</td>\n" .
- "</tr>\n";
- }
-
- if ($alternate) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- $alternate ^= 1;
- %co = parse_commit($set{'commit'});
- my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
- print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
- "<td><i>$author</i></td>\n" .
- "<td>" .
- $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
- -class => "list subject"},
- chop_and_escape_str($co{'title'}, 50) . "<br/>");
- } elsif (defined $set{'to_id'}) {
- next if ($set{'to_id'} =~ m/^0{40}$/);
-
- print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
- hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
- -class => "list"},
- "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
- "<br/>\n";
- }
- }
- close $fd;
-
- # finish last commit (warning: repetition!)
- if (%co) {
- print "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
- " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
- print "</td>\n" .
- "</tr>\n";
- }
-
- print "</table>\n";
- }
-
- if ($searchtype eq 'grep') {
- git_print_page_nav('','', $hash,$co{'tree'},$hash);
- git_print_header_div('commit', esc_html($co{'title'}), $hash);
-
- print "<table class=\"grep_search\">\n";
- my $alternate = 1;
- my $matches = 0;
- local $/ = "\n";
- open my $fd, "-|", git_cmd(), 'grep', '-n',
- $search_use_regexp ? ('-E', '-i') : '-F',
- $searchtext, $co{'tree'};
- my $lastfile = '';
- while (my $line = <$fd>) {
- chomp $line;
- my ($file, $lno, $ltext, $binary);
- last if ($matches++ > 1000);
- if ($line =~ /^Binary file (.+) matches$/) {
- $file = $1;
- $binary = 1;
- } else {
- (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
- }
- if ($file ne $lastfile) {
- $lastfile and print "</td></tr>\n";
- if ($alternate++) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- print "<td class=\"list\">".
- $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
- file_name=>"$file"),
- -class => "list"}, esc_path($file));
- print "</td><td>\n";
- $lastfile = $file;
- }
- if ($binary) {
- print "<div class=\"binary\">Binary file</div>\n";
- } else {
- $ltext = untabify($ltext);
- if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
- $ltext = esc_html($1, -nbsp=>1);
- $ltext .= '<span class="match">';
- $ltext .= esc_html($2, -nbsp=>1);
- $ltext .= '</span>';
- $ltext .= esc_html($3, -nbsp=>1);
- } else {
- $ltext = esc_html($ltext, -nbsp=>1);
- }
- print "<div class=\"pre\">" .
- $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
- file_name=>"$file").'#l'.$lno,
- -class => "linenr"}, sprintf('%4i', $lno))
- . ' ' . $ltext . "</div>\n";
- }
- }
- if ($lastfile) {
- print "</td></tr>\n";
- if ($matches > 1000) {
- print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
- }
- } else {
- print "<div class=\"diff nodifferences\">No matches found</div>\n";
- }
- close $fd;
-
- print "</table>\n";
+ if ($searchtype eq 'commit' ||
+ $searchtype eq 'author' ||
+ $searchtype eq 'committer') {
+ git_search_message(%co);
+ } elsif ($searchtype eq 'pickaxe') {
+ git_search_changes(%co);
+ } elsif ($searchtype eq 'grep') {
+ git_search_files(%co);
+ } else {
+ die_error(400, "Unknown search type");
}
- git_footer_html();
}
sub git_search_help {
@@ -7354,33 +8019,14 @@ sub git_feed {
if (defined($commitlist[0])) {
%latest_commit = %{$commitlist[0]};
my $latest_epoch = $latest_commit{'committer_epoch'};
- %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
- my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
- if (defined $if_modified) {
- my $since;
- if (eval { require HTTP::Date; 1; }) {
- $since = HTTP::Date::str2time($if_modified);
- } elsif (eval { require Time::ParseDate; 1; }) {
- $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
- }
- if (defined $since && $latest_epoch <= $since) {
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8',
- -last_modified => $latest_date{'rfc2822'},
- -status => '304 Not Modified');
- return;
- }
- }
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8',
- -last_modified => $latest_date{'rfc2822'});
- } else {
- print $cgi->header(
- -type => $content_type,
- -charset => 'utf-8');
+ exit_if_unmodified_since($latest_epoch);
+ %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
}
+ print $cgi->header(
+ -type => $content_type,
+ -charset => 'utf-8',
+ %latest_date ? (-last_modified => $latest_date{'rfc2822'}) : (),
+ -status => '200 OK');
# Optimization: skip generating the body if client asks only
# for Last-Modified date.
@@ -7594,7 +8240,7 @@ sub git_atom {
}
sub git_opml {
- my @list = git_get_projects_list();
+ my @list = git_get_projects_list($project_filter, $strict_export);
if (!@list) {
die_error(404, "No projects found");
}
@@ -7604,11 +8250,18 @@ sub git_opml {
-charset => 'utf-8',
-content_disposition => 'inline; filename="opml.xml"');
+ my $title = esc_html($site_name);
+ my $filter = " within subdirectory ";
+ if (defined $project_filter) {
+ $filter .= esc_html($project_filter);
+ } else {
+ $filter = "";
+ }
print <<XML;
<?xml version="1.0" encoding="utf-8"?>
<opml version="1.0">
<head>
- <title>$site_name OPML Export</title>
+ <title>$title OPML Export$filter</title>
</head>
<body>
<outline text="git RSS feeds">
diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css
index 7d88509..cb86d2d 100644
--- a/gitweb/static/gitweb.css
+++ b/gitweb/static/gitweb.css
@@ -438,6 +438,10 @@ div.diff.add {
color: #008800;
}
+div.diff.add span.marked {
+ background-color: #aaffaa;
+}
+
div.diff.from_file a.path,
div.diff.from_file {
color: #aa0000;
@@ -447,6 +451,10 @@ div.diff.rem {
color: #cc0000;
}
+div.diff.rem span.marked {
+ background-color: #ffaaaa;
+}
+
div.diff.chunk_header a,
div.diff.chunk_header {
color: #990099;
@@ -475,6 +483,36 @@ div.diff.nodifferences {
color: #600000;
}
+/* side-by-side diff */
+div.chunk_block {
+ overflow: hidden;
+}
+
+div.chunk_block div.old {
+ float: left;
+ width: 50%;
+ overflow: hidden;
+}
+
+div.chunk_block div.new {
+ margin-left: 50%;
+ width: 50%;
+}
+
+div.chunk_block.rem div.old div.diff.rem {
+ background-color: #fff5f5;
+}
+div.chunk_block.add div.new div.diff.add {
+ background-color: #f8fff8;
+}
+div.chunk_block.chg div div.diff {
+ background-color: #fffff0;
+}
+div.chunk_block.ctx div div.diff.ctx {
+ color: #404040;
+}
+
+
div.index_include {
border: solid #d9d8d1;
border-width: 0px 0px 1px;
@@ -490,8 +528,13 @@ div.search {
right: 12px
}
-p.projsearch {
+div.projsearch {
text-align: center;
+ margin: 20px 0px;
+}
+
+div.projsearch form {
+ margin-bottom: 2px;
}
td.linenr {
diff --git a/gitweb/static/js/blame_incremental.js b/gitweb/static/js/blame_incremental.js
index 676da6b..db6eb50 100644
--- a/gitweb/static/js/blame_incremental.js
+++ b/gitweb/static/js/blame_incremental.js
@@ -29,7 +29,6 @@
/* ............................................................ */
/* utility/helper functions (and variables) */
-var xhr; // XMLHttpRequest object
var projectUrl; // partial query + separator ('?' or ';')
// 'commits' is an associative map. It maps SHA1s to Commit objects.
@@ -420,8 +419,6 @@ function handleLine(commit, group) {
// ----------------------------------------------------------------------
-var inProgress = false; // are we processing response
-
/**#@+
* @constant
*/
@@ -433,8 +430,6 @@ var endRe = /^END ?([^ ]*) ?(.*)/;
var curCommit = new Commit();
var curGroup = {};
-var pollTimer = null;
-
/**
* Parse output from 'git blame --incremental [...]', received via
* XMLHttpRequest from server (blamedataUrl), and call handleLine
@@ -535,43 +530,51 @@ function processData(unprocessed, nextReadPos) {
* Handle XMLHttpRequest errors
*
* @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} [xhr.pollTimer] ID of the timeout to clear
*
- * @globals pollTimer, commits, inProgress
+ * @globals commits
*/
function handleError(xhr) {
errorInfo('Server error: ' +
xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
- clearInterval(pollTimer);
+ if (typeof xhr.pollTimer === "number") {
+ clearTimeout(xhr.pollTimer);
+ delete xhr.pollTimer;
+ }
commits = {}; // free memory
-
- inProgress = false;
}
/**
* Called after XMLHttpRequest finishes (loads)
*
- * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} [xhr.pollTimer] ID of the timeout to clear
*
- * @globals pollTimer, commits, inProgress
+ * @globals commits
*/
function responseLoaded(xhr) {
- clearInterval(pollTimer);
+ if (typeof xhr.pollTimer === "number") {
+ clearTimeout(xhr.pollTimer);
+ delete xhr.pollTimer;
+ }
fixColorsAndGroups();
writeTimeInterval();
commits = {}; // free memory
-
- inProgress = false;
}
/**
* handler for XMLHttpRequest onreadystatechange event
* @see startBlame
*
- * @globals xhr, inProgress
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} xhr.prevDataLength: previous value of xhr.responseText.length
+ * @param {Number} xhr.nextReadPos: start of unread part of xhr.responseText
+ * @param {Number} [xhr.pollTimer] ID of the timeout (to reset or cancel)
+ * @param {Boolean} fromTimer: if handler was called from timer
*/
-function handleResponse() {
+function handleResponse(xhr, fromTimer) {
/*
* xhr.readyState
@@ -609,32 +612,31 @@ function handleResponse() {
return;
}
- // in case we were called before finished processing
- if (inProgress) {
- return;
- } else {
- inProgress = true;
- }
// extract new whole (complete) lines, and process them
- while (xhr.prevDataLength !== xhr.responseText.length) {
- if (xhr.readyState === 4 &&
- xhr.prevDataLength === xhr.responseText.length) {
- break;
- }
-
+ if (xhr.prevDataLength !== xhr.responseText.length) {
xhr.prevDataLength = xhr.responseText.length;
var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
- } // end while
+ }
// did we finish work?
- if (xhr.readyState === 4 &&
- xhr.prevDataLength === xhr.responseText.length) {
+ if (xhr.readyState === 4) {
responseLoaded(xhr);
+ return;
}
- inProgress = false;
+ // if we get from timer, we have to restart it
+ // otherwise onreadystatechange gives us partial response, timer not needed
+ if (fromTimer) {
+ setTimeout(function () {
+ handleResponse(xhr, true);
+ }, 1000);
+
+ } else if (typeof xhr.pollTimer === "number") {
+ clearTimeout(xhr.pollTimer);
+ delete xhr.pollTimer;
+ }
}
// ============================================================
@@ -649,11 +651,11 @@ function handleResponse() {
* Called from 'blame_incremental' view after loading table with
* file contents, a base for blame view.
*
- * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
+ * @globals t0, projectUrl, div_progress_bar, totalLines
*/
function startBlame(blamedataUrl, bUrl) {
- xhr = createRequestObject();
+ var xhr = createRequestObject();
if (!xhr) {
errorInfo('ERROR: XMLHttpRequest not supported');
return;
@@ -672,8 +674,9 @@ function startBlame(blamedataUrl, bUrl) {
xhr.prevDataLength = -1; // used to detect if we have new data
xhr.nextReadPos = 0; // where unread part of response starts
- xhr.onreadystatechange = handleResponse;
- //xhr.onreadystatechange = function () { handleResponse(xhr); };
+ xhr.onreadystatechange = function () {
+ handleResponse(xhr, false);
+ };
xhr.open('GET', blamedataUrl);
xhr.setRequestHeader('Accept', 'text/plain');
@@ -681,7 +684,9 @@ function startBlame(blamedataUrl, bUrl) {
// not all browsers call onreadystatechange event on each server flush
// poll response using timer every second to handle this issue
- pollTimer = setInterval(xhr.onreadystatechange, 1000);
+ xhr.pollTimer = setTimeout(function () {
+ handleResponse(xhr, true);
+ }, 1000);
}
/* end of blame_incremental.js */
diff --git a/gitweb/static/js/lib/cookies.js b/gitweb/static/js/lib/cookies.js
index 72b51cd..66b9a07 100644
--- a/gitweb/static/js/lib/cookies.js
+++ b/gitweb/static/js/lib/cookies.js
@@ -30,7 +30,7 @@
* If a negative value is specified or a date in the past),
* the cookie will be deleted.
* If set to null or omitted, the cookie will be a session cookie
- * and will not be retained when the the browser exits.
+ * and will not be retained when the browser exits.
* @param {String} [options.path] Restrict access of a cookie to particular directory
* (default: path of page that created the cookie).
* @param {String} [options.domain] Override what web sites are allowed to access cookie
diff --git a/gpg-interface.c b/gpg-interface.c
new file mode 100644
index 0000000..0863c61
--- /dev/null
+++ b/gpg-interface.c
@@ -0,0 +1,140 @@
+#include "cache.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "gpg-interface.h"
+#include "sigchain.h"
+
+static char *configured_signing_key;
+static const char *gpg_program = "gpg";
+
+void set_signing_key(const char *key)
+{
+ free(configured_signing_key);
+ configured_signing_key = xstrdup(key);
+}
+
+int git_gpg_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "user.signingkey")) {
+ set_signing_key(value);
+ }
+ if (!strcmp(var, "gpg.program")) {
+ if (!value)
+ return config_error_nonbool(var);
+ gpg_program = xstrdup(value);
+ }
+ return 0;
+}
+
+const char *get_signing_key(void)
+{
+ if (configured_signing_key)
+ return configured_signing_key;
+ return git_committer_info(IDENT_STRICT|IDENT_NO_DATE);
+}
+
+/*
+ * Create a detached signature for the contents of "buffer" and append
+ * it after "signature"; "buffer" and "signature" can be the same
+ * strbuf instance, which would cause the detached signature appended
+ * at the end.
+ */
+int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)
+{
+ struct child_process gpg;
+ const char *args[4];
+ ssize_t len;
+ size_t i, j, bottom;
+
+ memset(&gpg, 0, sizeof(gpg));
+ gpg.argv = args;
+ gpg.in = -1;
+ gpg.out = -1;
+ args[0] = gpg_program;
+ args[1] = "-bsau";
+ args[2] = signing_key;
+ args[3] = NULL;
+
+ if (start_command(&gpg))
+ return error(_("could not run gpg."));
+
+ /*
+ * When the username signingkey is bad, program could be terminated
+ * because gpg exits without reading and then write gets SIGPIPE.
+ */
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
+ close(gpg.in);
+ close(gpg.out);
+ finish_command(&gpg);
+ return error(_("gpg did not accept the data"));
+ }
+ close(gpg.in);
+
+ bottom = signature->len;
+ len = strbuf_read(signature, gpg.out, 1024);
+ close(gpg.out);
+
+ sigchain_pop(SIGPIPE);
+
+ if (finish_command(&gpg) || !len || len < 0)
+ return error(_("gpg failed to sign the data"));
+
+ /* Strip CR from the line endings, in case we are on Windows. */
+ for (i = j = bottom; i < signature->len; i++)
+ if (signature->buf[i] != '\r') {
+ if (i != j)
+ signature->buf[j] = signature->buf[i];
+ j++;
+ }
+ strbuf_setlen(signature, j);
+
+ return 0;
+}
+
+/*
+ * Run "gpg" to see if the payload matches the detached signature.
+ * gpg_output, when set, receives the diagnostic output from GPG.
+ */
+int verify_signed_buffer(const char *payload, size_t payload_size,
+ const char *signature, size_t signature_size,
+ struct strbuf *gpg_output)
+{
+ struct child_process gpg;
+ const char *args_gpg[] = {NULL, "--verify", "FILE", "-", NULL};
+ char path[PATH_MAX];
+ int fd, ret;
+
+ args_gpg[0] = gpg_program;
+ fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
+ if (fd < 0)
+ return error("could not create temporary file '%s': %s",
+ path, strerror(errno));
+ if (write_in_full(fd, signature, signature_size) < 0)
+ return error("failed writing detached signature to '%s': %s",
+ path, strerror(errno));
+ close(fd);
+
+ memset(&gpg, 0, sizeof(gpg));
+ gpg.argv = args_gpg;
+ gpg.in = -1;
+ if (gpg_output)
+ gpg.err = -1;
+ args_gpg[2] = path;
+ if (start_command(&gpg)) {
+ unlink(path);
+ return error("could not run gpg.");
+ }
+
+ write_in_full(gpg.in, payload, payload_size);
+ close(gpg.in);
+
+ if (gpg_output)
+ strbuf_read(gpg_output, gpg.err, 0);
+ ret = finish_command(&gpg);
+
+ unlink_or_warn(path);
+
+ return ret;
+}
diff --git a/gpg-interface.h b/gpg-interface.h
new file mode 100644
index 0000000..b9c3608
--- /dev/null
+++ b/gpg-interface.h
@@ -0,0 +1,10 @@
+#ifndef GPG_INTERFACE_H
+#define GPG_INTERFACE_H
+
+extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
+extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output);
+extern int git_gpg_config(const char *, const char *, void *);
+extern void set_signing_key(const char *);
+extern const char *get_signing_key(void);
+
+#endif
diff --git a/graph.c b/graph.c
index 2f6893d..7e0a099 100644
--- a/graph.c
+++ b/graph.c
@@ -194,8 +194,10 @@ static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void
struct git_graph *graph = data;
static struct strbuf msgbuf = STRBUF_INIT;
+ assert(opt);
assert(graph);
+ opt->output_prefix_length = graph->width;
strbuf_reset(&msgbuf);
graph_padding_line(graph, &msgbuf);
return &msgbuf;
@@ -245,6 +247,7 @@ struct git_graph *graph_init(struct rev_info *opt)
*/
opt->diffopt.output_prefix = diff_output_prefix_callback;
opt->diffopt.output_prefix_data = graph;
+ opt->diffopt.output_prefix_length = 0;
return graph;
}
@@ -347,7 +350,7 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)
static unsigned short graph_get_current_column_color(const struct git_graph *graph)
{
- if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
+ if (!want_color(graph->revs->diffopt.use_color))
return column_colors_max;
return graph->default_column_color;
}
diff --git a/grep.c b/grep.c
index d03d9e2..04e3ec6 100644
--- a/grep.c
+++ b/grep.c
@@ -3,18 +3,64 @@
#include "userdiff.h"
#include "xdiff-interface.h"
-void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field field, const char *pat)
+static struct grep_pat *create_grep_pat(const char *pat, size_t patlen,
+ const char *origin, int no,
+ enum grep_pat_token t,
+ enum grep_header_field field)
{
struct grep_pat *p = xcalloc(1, sizeof(*p));
- p->pattern = pat;
- p->patternlen = strlen(pat);
- p->origin = "header";
- p->no = 0;
- p->token = GREP_PATTERN_HEAD;
+ p->pattern = xmemdupz(pat, patlen);
+ p->patternlen = patlen;
+ p->origin = origin;
+ p->no = no;
+ p->token = t;
p->field = field;
- *opt->header_tail = p;
- opt->header_tail = &p->next;
+ return p;
+}
+
+static void do_append_grep_pat(struct grep_pat ***tail, struct grep_pat *p)
+{
+ **tail = p;
+ *tail = &p->next;
p->next = NULL;
+
+ switch (p->token) {
+ case GREP_PATTERN: /* atom */
+ case GREP_PATTERN_HEAD:
+ case GREP_PATTERN_BODY:
+ for (;;) {
+ struct grep_pat *new_pat;
+ size_t len = 0;
+ char *cp = p->pattern + p->patternlen, *nl = NULL;
+ while (++len <= p->patternlen) {
+ if (*(--cp) == '\n') {
+ nl = cp;
+ break;
+ }
+ }
+ if (!nl)
+ break;
+ new_pat = create_grep_pat(nl + 1, len - 1, p->origin,
+ p->no, p->token, p->field);
+ new_pat->next = p->next;
+ if (!p->next)
+ *tail = &new_pat->next;
+ p->next = new_pat;
+ *nl = '\0';
+ p->patternlen -= len;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void append_header_grep_pattern(struct grep_opt *opt,
+ enum grep_header_field field, const char *pat)
+{
+ struct grep_pat *p = create_grep_pat(pat, strlen(pat), "header", 0,
+ GREP_PATTERN_HEAD, field);
+ do_append_grep_pat(&opt->header_tail, p);
}
void append_grep_pattern(struct grep_opt *opt, const char *pat,
@@ -26,15 +72,8 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat,
void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen,
const char *origin, int no, enum grep_pat_token t)
{
- struct grep_pat *p = xcalloc(1, sizeof(*p));
- p->pattern = pat;
- p->patternlen = patlen;
- p->origin = origin;
- p->no = no;
- p->token = t;
- *opt->pattern_tail = p;
- opt->pattern_tail = &p->next;
- p->next = NULL;
+ struct grep_pat *p = create_grep_pat(pat, patlen, origin, no, t, 0);
+ do_append_grep_pat(&opt->pattern_tail, p);
}
struct grep_opt *grep_opt_dup(const struct grep_opt *opt)
@@ -79,7 +118,7 @@ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
{
const char *error;
int erroffset;
- int options = 0;
+ int options = PCRE_MULTILINE;
if (opt->ignore_case)
options |= PCRE_CASELESS;
@@ -137,16 +176,45 @@ static void free_pcre_regexp(struct grep_pat *p)
}
#endif /* !USE_LIBPCRE */
+static int is_fixed(const char *s, size_t len)
+{
+ size_t i;
+
+ /* regcomp cannot accept patterns with NULs so we
+ * consider any pattern containing a NUL fixed.
+ */
+ if (memchr(s, 0, len))
+ return 1;
+
+ for (i = 0; i < len; i++) {
+ if (is_regex_special(s[i]))
+ return 0;
+ }
+
+ return 1;
+}
+
static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
{
int err;
p->word_regexp = opt->word_regexp;
p->ignore_case = opt->ignore_case;
- p->fixed = opt->fixed;
- if (p->fixed)
+ if (opt->fixed || is_fixed(p->pattern, p->patternlen))
+ p->fixed = 1;
+ else
+ p->fixed = 0;
+
+ if (p->fixed) {
+ if (opt->regflags & REG_ICASE || p->ignore_case)
+ p->kws = kwsalloc(tolower_trans_tbl);
+ else
+ p->kws = kwsalloc(NULL);
+ kwsincr(p->kws, p->pattern, p->patternlen);
+ kwsprep(p->kws);
return;
+ }
if (opt->pcre) {
compile_pcre_regexp(p, opt);
@@ -289,7 +357,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
if (!opt->header_list)
return NULL;
- p = opt->header_list;
+
for (p = opt->header_list; p; p = p->next) {
if (p->token != GREP_PATTERN_HEAD)
die("bug: a non-header pattern in grep header list.");
@@ -395,10 +463,13 @@ void free_grep_patterns(struct grep_opt *opt)
case GREP_PATTERN: /* atom */
case GREP_PATTERN_HEAD:
case GREP_PATTERN_BODY:
- if (p->pcre_regexp)
+ if (p->kws)
+ kwsfree(p->kws);
+ else if (p->pcre_regexp)
free_pcre_regexp(p);
else
regfree(&p->regexp);
+ free(p->pattern);
break;
default:
break;
@@ -430,7 +501,7 @@ static int word_char(char ch)
static void output_color(struct grep_opt *opt, const void *data, size_t size,
const char *color)
{
- if (opt->color && color && color[0]) {
+ if (want_color(opt->color) && color && color[0]) {
opt->output(opt, color, strlen(color));
opt->output(opt, data, size);
opt->output(opt, GIT_COLOR_RESET, strlen(GIT_COLOR_RESET));
@@ -455,26 +526,14 @@ static void show_name(struct grep_opt *opt, const char *name)
static int fixmatch(struct grep_pat *p, char *line, char *eol,
regmatch_t *match)
{
- char *hit;
-
- if (p->ignore_case) {
- char *s = line;
- do {
- hit = strcasestr(s, p->pattern);
- if (hit)
- break;
- s += strlen(s) + 1;
- } while (s < eol);
- } else
- hit = memmem(line, eol - line, p->pattern, p->patternlen);
-
- if (!hit) {
+ struct kwsmatch kwsm;
+ size_t offset = kwsexec(p->kws, line, eol - line, &kwsm);
+ if (offset == -1) {
match->rm_so = match->rm_eo = -1;
return REG_NOMATCH;
- }
- else {
- match->rm_so = hit - line;
- match->rm_eo = match->rm_so + p->patternlen;
+ } else {
+ match->rm_so = offset;
+ match->rm_eo = match->rm_so + kwsm.size[0];
return 0;
}
}
@@ -721,7 +780,10 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
int rest = eol - bol;
char *line_color = NULL;
- if (opt->pre_context || opt->post_context) {
+ if (opt->file_break && opt->last_shown == 0) {
+ if (opt->show_hunk_mark)
+ opt->output(opt, "\n", 1);
+ } else if (opt->pre_context || opt->post_context || opt->funcbody) {
if (opt->last_shown == 0) {
if (opt->show_hunk_mark) {
output_color(opt, "--", 2, opt->color_sep);
@@ -732,9 +794,13 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
opt->output(opt, "\n", 1);
}
}
+ if (opt->heading && opt->last_shown == 0) {
+ output_color(opt, name, strlen(name), opt->color_filename);
+ opt->output(opt, "\n", 1);
+ }
opt->last_shown = lno;
- if (opt->pathname) {
+ if (!opt->heading && opt->pathname) {
output_color(opt, name, strlen(name), opt->color_filename);
output_sep(opt, sign);
}
@@ -775,10 +841,51 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
opt->output(opt, "\n", 1);
}
-static int match_funcname(struct grep_opt *opt, char *bol, char *eol)
+#ifndef NO_PTHREADS
+int grep_use_locks;
+
+/*
+ * This lock protects access to the gitattributes machinery, which is
+ * not thread-safe.
+ */
+pthread_mutex_t grep_attr_mutex;
+
+static inline void grep_attr_lock(void)
+{
+ if (grep_use_locks)
+ pthread_mutex_lock(&grep_attr_mutex);
+}
+
+static inline void grep_attr_unlock(void)
+{
+ if (grep_use_locks)
+ pthread_mutex_unlock(&grep_attr_mutex);
+}
+
+/*
+ * Same as git_attr_mutex, but protecting the thread-unsafe object db access.
+ */
+pthread_mutex_t grep_read_mutex;
+
+#else
+#define grep_attr_lock()
+#define grep_attr_unlock()
+#endif
+
+static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol)
{
xdemitconf_t *xecfg = opt->priv;
- if (xecfg && xecfg->find_func) {
+ if (xecfg && !xecfg->find_func) {
+ grep_source_load_driver(gs);
+ if (gs->driver->funcname.pattern) {
+ const struct userdiff_funcname *pe = &gs->driver->funcname;
+ xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
+ } else {
+ xecfg = opt->priv = NULL;
+ }
+ }
+
+ if (xecfg) {
char buf[1];
return xecfg->find_func(bol, eol - bol, buf, 1,
xecfg->find_func_priv) >= 0;
@@ -791,31 +898,34 @@ static int match_funcname(struct grep_opt *opt, char *bol, char *eol)
return 0;
}
-static void show_funcname_line(struct grep_opt *opt, const char *name,
- char *buf, char *bol, unsigned lno)
+static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
+ char *bol, unsigned lno)
{
- while (bol > buf) {
+ while (bol > gs->buf) {
char *eol = --bol;
- while (bol > buf && bol[-1] != '\n')
+ while (bol > gs->buf && bol[-1] != '\n')
bol--;
lno--;
if (lno <= opt->last_shown)
break;
- if (match_funcname(opt, bol, eol)) {
- show_line(opt, bol, eol, name, lno, '=');
+ if (match_funcname(opt, gs, bol, eol)) {
+ show_line(opt, bol, eol, gs->name, lno, '=');
break;
}
}
}
-static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
- char *bol, unsigned lno)
+static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
+ char *bol, char *end, unsigned lno)
{
unsigned cur = lno, from = 1, funcname_lno = 0;
- int funcname_needed = opt->funcname;
+ int funcname_needed = !!opt->funcname;
+
+ if (opt->funcbody && !match_funcname(opt, gs, bol, end))
+ funcname_needed = 2;
if (opt->pre_context < lno)
from = lno - opt->pre_context;
@@ -823,13 +933,14 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
from = opt->last_shown + 1;
/* Rewind. */
- while (bol > buf && cur > from) {
+ while (bol > gs->buf &&
+ cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
char *eol = --bol;
- while (bol > buf && bol[-1] != '\n')
+ while (bol > gs->buf && bol[-1] != '\n')
bol--;
cur--;
- if (funcname_needed && match_funcname(opt, bol, eol)) {
+ if (funcname_needed && match_funcname(opt, gs, bol, eol)) {
funcname_lno = cur;
funcname_needed = 0;
}
@@ -837,7 +948,7 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
/* We need to look even further back to find a function signature. */
if (opt->funcname && funcname_needed)
- show_funcname_line(opt, name, buf, bol, cur);
+ show_funcname_line(opt, gs, bol, cur);
/* Back forward. */
while (cur < lno) {
@@ -845,7 +956,7 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
while (*eol != '\n')
eol++;
- show_line(opt, bol, eol, name, cur, sign);
+ show_line(opt, bol, eol, gs->name, cur, sign);
bol = eol + 1;
cur++;
}
@@ -907,52 +1018,49 @@ static int look_ahead(struct grep_opt *opt,
return 0;
}
-int grep_threads_ok(const struct grep_opt *opt)
-{
- /* If this condition is true, then we may use the attribute
- * machinery in grep_buffer_1. The attribute code is not
- * thread safe, so we disable the use of threads.
- */
- if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
- !opt->name_only)
- return 0;
-
- return 1;
-}
-
static void std_output(struct grep_opt *opt, const void *buf, size_t size)
{
fwrite(buf, size, 1, stdout);
}
-static int grep_buffer_1(struct grep_opt *opt, const char *name,
- char *buf, unsigned long size, int collect_hits)
+static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits)
{
- char *bol = buf;
- unsigned long left = size;
+ char *bol;
+ unsigned long left;
unsigned lno = 1;
unsigned last_hit = 0;
int binary_match_only = 0;
unsigned count = 0;
int try_lookahead = 0;
+ int show_function = 0;
enum grep_context ctx = GREP_CONTEXT_HEAD;
xdemitconf_t xecfg;
if (!opt->output)
opt->output = std_output;
- if (opt->last_shown && (opt->pre_context || opt->post_context) &&
- opt->output == std_output)
- opt->show_hunk_mark = 1;
+ if (opt->pre_context || opt->post_context || opt->file_break ||
+ opt->funcbody) {
+ /* Show hunk marks, except for the first file. */
+ if (opt->last_shown)
+ opt->show_hunk_mark = 1;
+ /*
+ * If we're using threads then we can't easily identify
+ * the first file. Always put hunk marks in that case
+ * and skip the very first one later in work_done().
+ */
+ if (opt->output != std_output)
+ opt->show_hunk_mark = 1;
+ }
opt->last_shown = 0;
switch (opt->binary) {
case GREP_BINARY_DEFAULT:
- if (buffer_is_binary(buf, size))
+ if (grep_source_is_binary(gs))
binary_match_only = 1;
break;
case GREP_BINARY_NOMATCH:
- if (buffer_is_binary(buf, size))
+ if (grep_source_is_binary(gs))
return 0; /* Assume unmatch */
break;
case GREP_BINARY_TEXT:
@@ -962,17 +1070,15 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
}
memset(&xecfg, 0, sizeof(xecfg));
- if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
- !opt->name_only && !binary_match_only && !collect_hits) {
- struct userdiff_driver *drv = userdiff_find_by_path(name);
- if (drv && drv->funcname.pattern) {
- const struct userdiff_funcname *pe = &drv->funcname;
- xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
- opt->priv = &xecfg;
- }
- }
+ opt->priv = &xecfg;
+
try_lookahead = should_lookahead(opt);
+ if (grep_source_load(gs) < 0)
+ return 0;
+
+ bol = gs->buf;
+ left = gs->size;
while (left) {
char *eol, ch;
int hit;
@@ -988,7 +1094,8 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
*/
if (try_lookahead
&& !(last_hit
- && lno <= last_hit + opt->post_context)
+ && (show_function ||
+ lno <= last_hit + opt->post_context))
&& look_ahead(opt, &left, &lno, &bol))
break;
eol = end_of_line(bol, &left);
@@ -1020,14 +1127,14 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
if (opt->status_only)
return 1;
if (opt->name_only) {
- show_name(opt, name);
+ show_name(opt, gs->name);
return 1;
}
if (opt->count)
goto next_line;
if (binary_match_only) {
opt->output(opt, "Binary file ", 12);
- output_color(opt, name, strlen(name),
+ output_color(opt, gs->name, strlen(gs->name),
opt->color_filename);
opt->output(opt, " matches\n", 9);
return 1;
@@ -1035,19 +1142,24 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
/* Hit at this line. If we haven't shown the
* pre-context lines, we would need to show them.
*/
- if (opt->pre_context)
- show_pre_context(opt, name, buf, bol, lno);
+ if (opt->pre_context || opt->funcbody)
+ show_pre_context(opt, gs, bol, eol, lno);
else if (opt->funcname)
- show_funcname_line(opt, name, buf, bol, lno);
- show_line(opt, bol, eol, name, lno, ':');
+ show_funcname_line(opt, gs, bol, lno);
+ show_line(opt, bol, eol, gs->name, lno, ':');
last_hit = lno;
+ if (opt->funcbody)
+ show_function = 1;
+ goto next_line;
}
- else if (last_hit &&
- lno <= last_hit + opt->post_context) {
+ if (show_function && match_funcname(opt, gs, bol, eol))
+ show_function = 0;
+ if (show_function ||
+ (last_hit && lno <= last_hit + opt->post_context)) {
/* If the last hit is within the post context,
* we need to show this line.
*/
- show_line(opt, bol, eol, name, lno, '-');
+ show_line(opt, bol, eol, gs->name, lno, '-');
}
next_line:
@@ -1065,7 +1177,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
return 0;
if (opt->unmatch_name_only) {
/* We did not see any hit, so we want to show this */
- show_name(opt, name);
+ show_name(opt, gs->name);
return 1;
}
@@ -1079,7 +1191,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
*/
if (opt->count && count) {
char buf[32];
- output_color(opt, name, strlen(name), opt->color_filename);
+ output_color(opt, gs->name, strlen(gs->name), opt->color_filename);
output_sep(opt, ':');
snprintf(buf, sizeof(buf), "%u\n", count);
opt->output(opt, buf, strlen(buf));
@@ -1114,23 +1226,174 @@ static int chk_hit_marker(struct grep_expr *x)
}
}
-int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+int grep_source(struct grep_opt *opt, struct grep_source *gs)
{
/*
* we do not have to do the two-pass grep when we do not check
* buffer-wide "all-match".
*/
if (!opt->all_match)
- return grep_buffer_1(opt, name, buf, size, 0);
+ return grep_source_1(opt, gs, 0);
/* Otherwise the toplevel "or" terms hit a bit differently.
* We first clear hit markers from them.
*/
clr_hit_marker(opt->pattern_expression);
- grep_buffer_1(opt, name, buf, size, 1);
+ grep_source_1(opt, gs, 1);
if (!chk_hit_marker(opt->pattern_expression))
return 0;
- return grep_buffer_1(opt, name, buf, size, 0);
+ return grep_source_1(opt, gs, 0);
+}
+
+int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
+{
+ struct grep_source gs;
+ int r;
+
+ grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL);
+ gs.buf = buf;
+ gs.size = size;
+
+ r = grep_source(opt, &gs);
+
+ grep_source_clear(&gs);
+ return r;
+}
+
+void grep_source_init(struct grep_source *gs, enum grep_source_type type,
+ const char *name, const void *identifier)
+{
+ gs->type = type;
+ gs->name = name ? xstrdup(name) : NULL;
+ gs->buf = NULL;
+ gs->size = 0;
+ gs->driver = NULL;
+
+ switch (type) {
+ case GREP_SOURCE_FILE:
+ gs->identifier = xstrdup(identifier);
+ break;
+ case GREP_SOURCE_SHA1:
+ gs->identifier = xmalloc(20);
+ memcpy(gs->identifier, identifier, 20);
+ break;
+ case GREP_SOURCE_BUF:
+ gs->identifier = NULL;
+ }
+}
+
+void grep_source_clear(struct grep_source *gs)
+{
+ free(gs->name);
+ gs->name = NULL;
+ free(gs->identifier);
+ gs->identifier = NULL;
+ grep_source_clear_data(gs);
+}
+
+void grep_source_clear_data(struct grep_source *gs)
+{
+ switch (gs->type) {
+ case GREP_SOURCE_FILE:
+ case GREP_SOURCE_SHA1:
+ free(gs->buf);
+ gs->buf = NULL;
+ gs->size = 0;
+ break;
+ case GREP_SOURCE_BUF:
+ /* leave user-provided buf intact */
+ break;
+ }
+}
+
+static int grep_source_load_sha1(struct grep_source *gs)
+{
+ enum object_type type;
+
+ grep_read_lock();
+ gs->buf = read_sha1_file(gs->identifier, &type, &gs->size);
+ grep_read_unlock();
+
+ if (!gs->buf)
+ return error(_("'%s': unable to read %s"),
+ gs->name,
+ sha1_to_hex(gs->identifier));
+ return 0;
+}
+
+static int grep_source_load_file(struct grep_source *gs)
+{
+ const char *filename = gs->identifier;
+ struct stat st;
+ char *data;
+ size_t size;
+ int i;
+
+ if (lstat(filename, &st) < 0) {
+ err_ret:
+ if (errno != ENOENT)
+ error(_("'%s': %s"), filename, strerror(errno));
+ return -1;
+ }
+ if (!S_ISREG(st.st_mode))
+ return -1;
+ size = xsize_t(st.st_size);
+ i = open(filename, O_RDONLY);
+ if (i < 0)
+ goto err_ret;
+ data = xmalloc(size + 1);
+ if (st.st_size != read_in_full(i, data, size)) {
+ error(_("'%s': short read %s"), filename, strerror(errno));
+ close(i);
+ free(data);
+ return -1;
+ }
+ close(i);
+ data[size] = 0;
+
+ gs->buf = data;
+ gs->size = size;
+ return 0;
+}
+
+int grep_source_load(struct grep_source *gs)
+{
+ if (gs->buf)
+ return 0;
+
+ switch (gs->type) {
+ case GREP_SOURCE_FILE:
+ return grep_source_load_file(gs);
+ case GREP_SOURCE_SHA1:
+ return grep_source_load_sha1(gs);
+ case GREP_SOURCE_BUF:
+ return gs->buf ? 0 : -1;
+ }
+ die("BUG: invalid grep_source type");
+}
+
+void grep_source_load_driver(struct grep_source *gs)
+{
+ if (gs->driver)
+ return;
+
+ grep_attr_lock();
+ gs->driver = userdiff_find_by_path(gs->name);
+ if (!gs->driver)
+ gs->driver = userdiff_find_by_name("default");
+ grep_attr_unlock();
+}
+
+int grep_source_is_binary(struct grep_source *gs)
+{
+ grep_source_load_driver(gs);
+ if (gs->driver->binary != -1)
+ return gs->driver->binary;
+
+ if (!grep_source_load(gs))
+ return buffer_is_binary(gs->buf, gs->size);
+
+ return 0;
}
diff --git a/grep.h b/grep.h
index cd055cd..ed7de6b 100644
--- a/grep.h
+++ b/grep.h
@@ -7,6 +7,9 @@
typedef int pcre;
typedef int pcre_extra;
#endif
+#include "kwset.h"
+#include "thread-utils.h"
+#include "userdiff.h"
enum grep_pat_token {
GREP_PATTERN,
@@ -35,12 +38,13 @@ struct grep_pat {
const char *origin;
int no;
enum grep_pat_token token;
- const char *pattern;
+ char *pattern;
size_t patternlen;
enum grep_header_field field;
regex_t regexp;
pcre *pcre_regexp;
pcre_extra *pcre_extra_info;
+ kwset_t kws;
unsigned fixed:1;
unsigned ignore_case:1;
unsigned word_regexp:1;
@@ -98,6 +102,7 @@ struct grep_opt {
int color;
int max_depth;
int funcname;
+ int funcbody;
char color_context[COLOR_MAXLEN];
char color_filename[COLOR_MAXLEN];
char color_function[COLOR_MAXLEN];
@@ -110,6 +115,8 @@ struct grep_opt {
unsigned post_context;
unsigned last_shown;
int show_hunk_mark;
+ int file_break;
+ int heading;
void *priv;
void (*output)(struct grep_opt *opt, const void *data, size_t size);
@@ -121,9 +128,61 @@ extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const cha
extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
extern void compile_grep_patterns(struct grep_opt *opt);
extern void free_grep_patterns(struct grep_opt *opt);
-extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
+extern int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size);
+
+struct grep_source {
+ char *name;
+
+ enum grep_source_type {
+ GREP_SOURCE_SHA1,
+ GREP_SOURCE_FILE,
+ GREP_SOURCE_BUF,
+ } type;
+ void *identifier;
+
+ char *buf;
+ unsigned long size;
+
+ struct userdiff_driver *driver;
+};
+
+void grep_source_init(struct grep_source *gs, enum grep_source_type type,
+ const char *name, const void *identifier);
+int grep_source_load(struct grep_source *gs);
+void grep_source_clear_data(struct grep_source *gs);
+void grep_source_clear(struct grep_source *gs);
+void grep_source_load_driver(struct grep_source *gs);
+int grep_source_is_binary(struct grep_source *gs);
+
+int grep_source(struct grep_opt *opt, struct grep_source *gs);
extern struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
extern int grep_threads_ok(const struct grep_opt *opt);
+#ifndef NO_PTHREADS
+/*
+ * Mutex used around access to the attributes machinery if
+ * opt->use_threads. Must be initialized/destroyed by callers!
+ */
+extern int grep_use_locks;
+extern pthread_mutex_t grep_attr_mutex;
+extern pthread_mutex_t grep_read_mutex;
+
+static inline void grep_read_lock(void)
+{
+ if (grep_use_locks)
+ pthread_mutex_lock(&grep_read_mutex);
+}
+
+static inline void grep_read_unlock(void)
+{
+ if (grep_use_locks)
+ pthread_mutex_unlock(&grep_read_mutex);
+}
+
+#else
+#define grep_read_lock()
+#define grep_read_unlock()
+#endif
+
#endif
diff --git a/help.c b/help.c
index 4219355..662349d 100644
--- a/help.c
+++ b/help.c
@@ -4,28 +4,9 @@
#include "levenshtein.h"
#include "help.h"
#include "common-cmds.h"
-
-/* most GUI terminals set COLUMNS (although some don't export it) */
-static int term_columns(void)
-{
- char *col_string = getenv("COLUMNS");
- int n_cols;
-
- if (col_string && (n_cols = atoi(col_string)) > 0)
- return n_cols;
-
-#ifdef TIOCGWINSZ
- {
- struct winsize ws;
- if (!ioctl(1, TIOCGWINSZ, &ws)) {
- if (ws.ws_col)
- return ws.ws_col;
- }
- }
-#endif
-
- return 80;
-}
+#include "string-list.h"
+#include "column.h"
+#include "version.h"
void add_cmdname(struct cmdnames *cmds, const char *name, int len)
{
@@ -92,31 +73,25 @@ void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
cmds->cnt = cj;
}
-static void pretty_print_string_list(struct cmdnames *cmds, int longest)
+static void pretty_print_string_list(struct cmdnames *cmds,
+ unsigned int colopts)
{
- int cols = 1, rows;
- int space = longest + 1; /* min 1 SP between words */
- int max_cols = term_columns() - 1; /* don't print *on* the edge */
- int i, j;
-
- if (space < max_cols)
- cols = max_cols / space;
- rows = DIV_ROUND_UP(cmds->cnt, cols);
-
- for (i = 0; i < rows; i++) {
- printf(" ");
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ struct column_options copts;
+ int i;
- for (j = 0; j < cols; j++) {
- int n = j * rows + i;
- int size = space;
- if (n >= cmds->cnt)
- break;
- if (j == cols-1 || n + rows >= cmds->cnt)
- size = 1;
- printf("%-*s", size, cmds->names[n]->name);
- }
- putchar('\n');
- }
+ for (i = 0; i < cmds->cnt; i++)
+ string_list_append(&list, cmds->names[i]->name);
+ /*
+ * always enable column display, we only consult column.*
+ * about layout strategy and stuff
+ */
+ colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+ memset(&copts, 0, sizeof(copts));
+ copts.indent = " ";
+ copts.padding = 2;
+ print_columns(&list, colopts, &copts);
+ string_list_clear(&list, 0);
}
static int is_executable(const char *name)
@@ -127,7 +102,10 @@ static int is_executable(const char *name)
!S_ISREG(st.st_mode))
return 0;
-#ifdef WIN32
+#if defined(WIN32) || defined(__CYGWIN__)
+#if defined(__CYGWIN__)
+if ((st.st_mode & S_IXUSR) == 0)
+#endif
{ /* cannot trust the executable bit, peek into the file instead */
char buf[3] = { 0 };
int n;
@@ -222,34 +200,21 @@ void load_command_list(const char *prefix,
exclude_cmds(other_cmds, main_cmds);
}
-void list_commands(const char *title, struct cmdnames *main_cmds,
- struct cmdnames *other_cmds)
+void list_commands(unsigned int colopts,
+ struct cmdnames *main_cmds, struct cmdnames *other_cmds)
{
- int i, longest = 0;
-
- for (i = 0; i < main_cmds->cnt; i++)
- if (longest < main_cmds->names[i]->len)
- longest = main_cmds->names[i]->len;
- for (i = 0; i < other_cmds->cnt; i++)
- if (longest < other_cmds->names[i]->len)
- longest = other_cmds->names[i]->len;
-
if (main_cmds->cnt) {
const char *exec_path = git_exec_path();
- printf("available %s in '%s'\n", title, exec_path);
- printf("----------------");
- mput_char('-', strlen(title) + strlen(exec_path));
+ printf_ln(_("available git commands in '%s'"), exec_path);
putchar('\n');
- pretty_print_string_list(main_cmds, longest);
+ pretty_print_string_list(main_cmds, colopts);
putchar('\n');
}
if (other_cmds->cnt) {
- printf("%s available from elsewhere on your $PATH\n", title);
- printf("---------------------------------------");
- mput_char('-', strlen(title));
+ printf_ln(_("git commands available from elsewhere on your $PATH"));
putchar('\n');
- pretty_print_string_list(other_cmds, longest);
+ pretty_print_string_list(other_cmds, colopts);
putchar('\n');
}
}
@@ -353,14 +318,14 @@ const char *help_unknown_cmd(const char *cmd)
}
main_cmds.names[i]->len =
- levenshtein(cmd, candidate, 0, 2, 1, 4) + 1;
+ levenshtein(cmd, candidate, 0, 2, 1, 3) + 1;
}
qsort(main_cmds.names, main_cmds.cnt,
sizeof(*main_cmds.names), levenshtein_compare);
if (!main_cmds.cnt)
- die ("Uh oh. Your system reports no Git commands at all.");
+ die(_("Uh oh. Your system reports no Git commands at all."));
/* skip and count prefix matches */
for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
@@ -381,23 +346,26 @@ const char *help_unknown_cmd(const char *cmd)
const char *assumed = main_cmds.names[0]->name;
main_cmds.names[0] = NULL;
clean_cmdnames(&main_cmds);
- fprintf(stderr, "WARNING: You called a Git command named '%s', "
- "which does not exist.\n"
- "Continuing under the assumption that you meant '%s'\n",
+ fprintf_ln(stderr,
+ _("WARNING: You called a Git command named '%s', "
+ "which does not exist.\n"
+ "Continuing under the assumption that you meant '%s'"),
cmd, assumed);
if (autocorrect > 0) {
- fprintf(stderr, "in %0.1f seconds automatically...\n",
+ fprintf_ln(stderr, _("in %0.1f seconds automatically..."),
(float)autocorrect/10.0);
poll(NULL, 0, autocorrect * 100);
}
return assumed;
}
- fprintf(stderr, "git: '%s' is not a git command. See 'git --help'.\n", cmd);
+ fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
if (SIMILAR_ENOUGH(best_similarity)) {
- fprintf(stderr, "\nDid you mean %s?\n",
- n < 2 ? "this": "one of these");
+ fprintf_ln(stderr,
+ Q_("\nDid you mean this?",
+ "\nDid you mean one of these?",
+ n));
for (i = 0; i < n; i++)
fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
diff --git a/help.h b/help.h
index b6b12d5..0ae5a12 100644
--- a/help.h
+++ b/help.h
@@ -25,8 +25,6 @@ extern void add_cmdname(struct cmdnames *cmds, const char *name, int len);
/* Here we require that excludes is a sorted list. */
extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);
-extern void list_commands(const char *title,
- struct cmdnames *main_cmds,
- struct cmdnames *other_cmds);
+extern void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
#endif /* HELP_H */
diff --git a/hex.c b/hex.c
index bb402fb..9ebc050 100644
--- a/hex.c
+++ b/hex.c
@@ -39,7 +39,15 @@ int get_sha1_hex(const char *hex, unsigned char *sha1)
{
int i;
for (i = 0; i < 20; i++) {
- unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+ unsigned int val;
+ /*
+ * hex[1]=='\0' is caught when val is checked below,
+ * but if hex[0] is NUL we have to avoid reading
+ * past the end of the string:
+ */
+ if (!hex[0])
+ return -1;
+ val = (hexval(hex[0]) << 4) | hexval(hex[1]);
if (val & ~0xff)
return -1;
*sha1++ = val;
diff --git a/http-backend.c b/http-backend.c
index 59ad7da..f50e77f 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -7,6 +7,7 @@
#include "run-command.h"
#include "string-list.h"
#include "url.h"
+#include "argv-array.h"
static const char content_type[] = "Content-Type";
static const char content_length[] = "Content-Length";
@@ -317,8 +318,7 @@ static void run_service(const char **argv)
const char *encoding = getenv("HTTP_CONTENT_ENCODING");
const char *user = getenv("REMOTE_USER");
const char *host = getenv("REMOTE_ADDR");
- char *env[3];
- struct strbuf buf = STRBUF_INIT;
+ struct argv_array env = ARGV_ARRAY_INIT;
int gzipped_request = 0;
struct child_process cld;
@@ -332,17 +332,15 @@ static void run_service(const char **argv)
if (!host || !*host)
host = "(none)";
- memset(&env, 0, sizeof(env));
- strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user);
- env[0] = strbuf_detach(&buf, NULL);
-
- strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
- env[1] = strbuf_detach(&buf, NULL);
- env[2] = NULL;
+ if (!getenv("GIT_COMMITTER_NAME"))
+ argv_array_pushf(&env, "GIT_COMMITTER_NAME=%s", user);
+ if (!getenv("GIT_COMMITTER_EMAIL"))
+ argv_array_pushf(&env, "GIT_COMMITTER_EMAIL=%s@http.%s",
+ user, host);
memset(&cld, 0, sizeof(cld));
cld.argv = argv;
- cld.env = (const char *const *)env;
+ cld.env = env.argv;
if (gzipped_request)
cld.in = -1;
cld.git_cmd = 1;
@@ -357,9 +355,7 @@ static void run_service(const char **argv)
if (finish_command(&cld))
exit(1);
- free(env[0]);
- free(env[1]);
- strbuf_release(&buf);
+ argv_array_clear(&env);
}
static int show_text_ref(const char *name, const unsigned char *sha1,
@@ -545,6 +541,8 @@ int main(int argc, char **argv)
char *cmd_arg = NULL;
int i;
+ git_setup_gettext();
+
git_extract_argv0_path(argv[0]);
set_die_routine(die_webcgi);
diff --git a/http-fetch.c b/http-fetch.c
index 3af4c71b..ba3ea10 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -22,6 +22,8 @@ int main(int argc, const char **argv)
int get_verbosely = 0;
int get_recover = 0;
+ git_setup_gettext();
+
git_extract_argv0_path(argv[0]);
while (arg < argc && argv[arg][0] == '-') {
@@ -56,6 +58,10 @@ int main(int argc, const char **argv)
commits = 1;
}
+ if (get_all == 0)
+ warning("http-fetch: use without -a is deprecated.\n"
+ "In a future release, -a will become the default.");
+
if (argv[arg])
str_end_url_with_slash(argv[arg], &url);
@@ -63,7 +69,7 @@ int main(int argc, const char **argv)
git_config(git_default_config, NULL);
- http_init(NULL);
+ http_init(NULL, url, 0);
walker = get_http_walker(url);
walker->get_tree = get_tree;
walker->get_history = get_history;
diff --git a/http-push.c b/http-push.c
index 376331a..a832ca7 100644
--- a/http-push.c
+++ b/http-push.c
@@ -904,7 +904,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
ep = strchr(ep + 1, '/');
}
- escaped = xml_entities(git_default_email);
+ escaped = xml_entities(ident_default_email());
strbuf_addf(&out_buffer.buf, LOCK_REQUEST, escaped);
free(escaped);
@@ -1108,7 +1108,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
if (repo->path)
url = repo->path;
if (strncmp(path, url, repo->path_len))
- error("Parsed path '%s' does not match url: '%s'\n",
+ error("Parsed path '%s' does not match url: '%s'",
path, url);
else {
path += repo->path_len;
@@ -1606,10 +1606,10 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
strbuf_release(&buffer);
}
-static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
+static int verify_merge_base(unsigned char *head_sha1, struct ref *remote)
{
- struct commit *head = lookup_commit(head_sha1);
- struct commit *branch = lookup_commit(branch_sha1);
+ struct commit *head = lookup_commit_or_die(head_sha1, "HEAD");
+ struct commit *branch = lookup_commit_or_die(remote->old_sha1, remote->name);
struct commit_list *merge_bases = get_merge_bases(head, branch, 1);
return (merge_bases && !merge_bases->next && merge_bases->item == branch);
@@ -1680,7 +1680,7 @@ static int delete_remote_branch(const char *pattern, int force)
return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1));
/* Remote branch must be an ancestor of remote HEAD */
- if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
+ if (!verify_merge_base(head_sha1, remote_ref)) {
return error("The branch '%s' is not an ancestor "
"of your current HEAD.\n"
"If you are sure you want to delete it,"
@@ -1702,7 +1702,7 @@ static int delete_remote_branch(const char *pattern, int force)
run_active_slot(slot);
free(url);
if (results.curl_result != CURLE_OK)
- return error("DELETE request failed (%d/%ld)\n",
+ return error("DELETE request failed (%d/%ld)",
results.curl_result, results.http_code);
} else {
free(url);
@@ -1747,7 +1747,8 @@ int main(int argc, char **argv)
int i;
int new_refs;
struct ref *ref, *local_refs;
- struct remote *remote;
+
+ git_setup_gettext();
git_extract_argv0_path(argv[0]);
@@ -1821,14 +1822,7 @@ int main(int argc, char **argv)
memset(remote_dir_exists, -1, 256);
- /*
- * Create a minimum remote by hand to give to http_init(),
- * primarily to allow it to look at the URL.
- */
- remote = xcalloc(sizeof(*remote), 1);
- ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
- remote->url[remote->url_nr++] = repo->url;
- http_init(remote);
+ http_init(NULL, repo->url, 1);
#ifdef USE_CURL_MULTI
is_running_queue = 0;
@@ -1877,8 +1871,8 @@ int main(int argc, char **argv)
}
/* match them up */
- if (match_refs(local_refs, &remote_refs,
- nr_refspec, (const char **) refspec, push_all)) {
+ if (match_push_refs(local_refs, &remote_refs,
+ nr_refspec, (const char **) refspec, push_all)) {
rc = -1;
goto cleanup;
}
diff --git a/http.c b/http.c
index b2ae8de..b61ac85 100644
--- a/http.c
+++ b/http.c
@@ -3,8 +3,9 @@
#include "sideband.h"
#include "run-command.h"
#include "url.h"
+#include "credential.h"
+#include "version.h"
-int data_received;
int active_requests;
int http_is_verbose;
size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
@@ -41,7 +42,9 @@ static long curl_low_speed_limit = -1;
static long curl_low_speed_time = -1;
static int curl_ftp_no_epsv;
static const char *curl_http_proxy;
-static char *user_name, *user_pass;
+static const char *curl_cookie_file;
+static struct credential http_auth = CREDENTIAL_INIT;
+static int http_proactive_auth;
static const char *user_agent;
#if LIBCURL_VERSION_NUM >= 0x071700
@@ -52,7 +55,7 @@ static const char *user_agent;
#define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD
#endif
-static char *ssl_cert_password;
+static struct credential cert_auth = CREDENTIAL_INIT;
static int ssl_cert_password_required;
static struct curl_slist *pragma_header;
@@ -98,13 +101,11 @@ size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
struct strbuf *buffer = buffer_;
strbuf_add(buffer, ptr, size);
- data_received++;
return size;
}
size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf)
{
- data_received++;
return eltsize * nmemb;
}
@@ -191,6 +192,9 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.proxy", var))
return git_config_string(&curl_http_proxy, var, value);
+ if (!strcmp("http.cookiefile", var))
+ return git_config_string(&curl_cookie_file, var, value);
+
if (!strcmp("http.postbuffer", var)) {
http_post_buffer = git_config_int(var, value);
if (http_post_buffer < LARGE_PACKET_MAX)
@@ -207,30 +211,35 @@ static int http_options(const char *var, const char *value, void *cb)
static void init_curl_http_auth(CURL *result)
{
- if (user_name) {
- struct strbuf up = STRBUF_INIT;
- if (!user_pass)
- user_pass = xstrdup(git_getpass("Password: "));
- strbuf_addf(&up, "%s:%s", user_name, user_pass);
- curl_easy_setopt(result, CURLOPT_USERPWD,
- strbuf_detach(&up, NULL));
+ if (!http_auth.username)
+ return;
+
+ credential_fill(&http_auth);
+
+#if LIBCURL_VERSION_NUM >= 0x071301
+ curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
+ curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
+#else
+ {
+ static struct strbuf up = STRBUF_INIT;
+ strbuf_reset(&up);
+ strbuf_addf(&up, "%s:%s",
+ http_auth.username, http_auth.password);
+ curl_easy_setopt(result, CURLOPT_USERPWD, up.buf);
}
+#endif
}
static int has_cert_password(void)
{
- if (ssl_cert_password != NULL)
- return 1;
if (ssl_cert == NULL || ssl_cert_password_required != 1)
return 0;
- /* Only prompt the user once. */
- ssl_cert_password_required = -1;
- ssl_cert_password = git_getpass("Certificate Password: ");
- if (ssl_cert_password != NULL) {
- ssl_cert_password = xstrdup(ssl_cert_password);
- return 1;
- } else
- return 0;
+ if (!cert_auth.password) {
+ cert_auth.protocol = xstrdup("cert");
+ cert_auth.path = xstrdup(ssl_cert);
+ credential_fill(&cert_auth);
+ }
+ return 1;
}
static CURL *get_curl_handle(void)
@@ -254,12 +263,13 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
#endif
- init_curl_http_auth(result);
+ if (http_proactive_auth)
+ init_curl_http_auth(result);
if (ssl_cert != NULL)
curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
if (has_cert_password())
- curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password);
+ curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password);
#if LIBCURL_VERSION_NUM >= 0x070903
if (ssl_key != NULL)
curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
@@ -290,66 +300,19 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
curl_easy_setopt(result, CURLOPT_USERAGENT,
- user_agent ? user_agent : GIT_HTTP_USER_AGENT);
+ user_agent ? user_agent : git_user_agent());
if (curl_ftp_no_epsv)
curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
- if (curl_http_proxy)
+ if (curl_http_proxy) {
curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
+ curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
+ }
return result;
}
-static void http_auth_init(const char *url)
-{
- char *at, *colon, *cp, *slash, *decoded;
- int len;
-
- cp = strstr(url, "://");
- if (!cp)
- return;
-
- /*
- * Ok, the URL looks like "proto://something". Which one?
- * "proto://<user>:<pass>@<host>/...",
- * "proto://<user>@<host>/...", or just
- * "proto://<host>/..."?
- */
- cp += 3;
- at = strchr(cp, '@');
- colon = strchr(cp, ':');
- slash = strchrnul(cp, '/');
- if (!at || slash <= at)
- return; /* No credentials */
- if (!colon || at <= colon) {
- /* Only username */
- len = at - cp;
- user_name = xmalloc(len + 1);
- memcpy(user_name, cp, len);
- user_name[len] = '\0';
- decoded = url_decode(user_name);
- free(user_name);
- user_name = decoded;
- user_pass = NULL;
- } else {
- len = colon - cp;
- user_name = xmalloc(len + 1);
- memcpy(user_name, cp, len);
- user_name[len] = '\0';
- decoded = url_decode(user_name);
- free(user_name);
- user_name = decoded;
- len = at - (colon + 1);
- user_pass = xmalloc(len + 1);
- memcpy(user_pass, colon + 1, len);
- user_pass[len] = '\0';
- decoded = url_decode(user_pass);
- free(user_pass);
- user_pass = decoded;
- }
-}
-
static void set_from_env(const char **var, const char *envname)
{
const char *val = getenv(envname);
@@ -357,7 +320,7 @@ static void set_from_env(const char **var, const char *envname)
*var = val;
}
-void http_init(struct remote *remote)
+void http_init(struct remote *remote, const char *url, int proactive_auth)
{
char *low_speed_limit;
char *low_speed_time;
@@ -368,6 +331,8 @@ void http_init(struct remote *remote)
curl_global_init(CURL_GLOBAL_ALL);
+ http_proactive_auth = proactive_auth;
+
if (remote && remote->http_proxy)
curl_http_proxy = xstrdup(remote->http_proxy);
@@ -421,11 +386,11 @@ void http_init(struct remote *remote)
if (getenv("GIT_CURL_FTP_NO_EPSV"))
curl_ftp_no_epsv = 1;
- if (remote && remote->url && remote->url[0]) {
- http_auth_init(remote->url[0]);
+ if (url) {
+ credential_from_url(&http_auth, url);
if (!ssl_cert_password_required &&
getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
- !prefixcmp(remote->url[0], "https://"))
+ !prefixcmp(url, "https://"))
ssl_cert_password_required = 1;
}
@@ -471,10 +436,10 @@ void http_cleanup(void)
curl_http_proxy = NULL;
}
- if (ssl_cert_password != NULL) {
- memset(ssl_cert_password, 0, strlen(ssl_cert_password));
- free(ssl_cert_password);
- ssl_cert_password = NULL;
+ if (cert_auth.password != NULL) {
+ memset(cert_auth.password, 0, strlen(cert_auth.password));
+ free(cert_auth.password);
+ cert_auth.password = NULL;
}
ssl_cert_password_required = 0;
}
@@ -526,11 +491,11 @@ struct active_request_slot *get_active_slot(void)
active_requests++;
slot->in_use = 1;
- slot->local = NULL;
slot->results = NULL;
slot->finished = NULL;
slot->callback_data = NULL;
slot->callback_func = NULL;
+ curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
@@ -539,6 +504,8 @@ struct active_request_slot *get_active_slot(void)
curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL);
curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+ if (http_auth.password)
+ init_curl_http_auth(slot->curl);
return slot;
}
@@ -629,8 +596,6 @@ void step_active_slots(void)
void run_active_slot(struct active_request_slot *slot)
{
#ifdef USE_CURL_MULTI
- long last_pos = 0;
- long current_pos;
fd_set readfds;
fd_set writefds;
fd_set excfds;
@@ -640,25 +605,33 @@ void run_active_slot(struct active_request_slot *slot)
slot->finished = &finished;
while (!finished) {
- data_received = 0;
step_active_slots();
- if (!data_received && slot->local != NULL) {
- current_pos = ftell(slot->local);
- if (current_pos > last_pos)
- data_received++;
- last_pos = current_pos;
- }
+ if (slot->in_use) {
+#if LIBCURL_VERSION_NUM >= 0x070f04
+ long curl_timeout;
+ curl_multi_timeout(curlm, &curl_timeout);
+ if (curl_timeout == 0) {
+ continue;
+ } else if (curl_timeout == -1) {
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 50000;
+ } else {
+ select_timeout.tv_sec = curl_timeout / 1000;
+ select_timeout.tv_usec = (curl_timeout % 1000) * 1000;
+ }
+#else
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 50000;
+#endif
- if (slot->in_use && !data_received) {
- max_fd = 0;
+ max_fd = -1;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&excfds);
- select_timeout.tv_sec = 0;
- select_timeout.tv_usec = 50000;
- select(max_fd, &readfds, &writefds,
- &excfds, &select_timeout);
+ curl_multi_fdset(curlm, &readfds, &writefds, &excfds, &max_fd);
+
+ select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout);
}
}
#else
@@ -736,14 +709,6 @@ static inline int needs_quote(int ch)
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)
{
struct strbuf buf = STRBUF_INIT;
@@ -811,7 +776,6 @@ static int http_request(const char *url, void *result, int target, int options)
headers = curl_slist_append(headers, buf.buf);
strbuf_reset(&buf);
}
- slot->local = result;
} else
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
fwrite_buffer);
@@ -833,44 +797,51 @@ static int http_request(const char *url, void *result, int target, int options)
else if (missing_target(&results))
ret = HTTP_MISSING_TARGET;
else if (results.http_code == 401) {
- if (user_name) {
+ if (http_auth.username && http_auth.password) {
+ credential_reject(&http_auth);
ret = HTTP_NOAUTH;
} else {
- /*
- * git_getpass is needed here because its very likely stdin/stdout are
- * pipes to our parent process. So we instead need to use /dev/tty,
- * but that is non-portable. Using git_getpass() can at least be stubbed
- * on other platforms with a different implementation if/when necessary.
- */
- user_name = xstrdup(git_getpass("Username: "));
+ credential_fill(&http_auth);
init_curl_http_auth(slot->curl);
ret = HTTP_REAUTH;
}
- } else
+ } else {
+ if (!curl_errorstr[0])
+ strlcpy(curl_errorstr,
+ curl_easy_strerror(results.curl_result),
+ sizeof(curl_errorstr));
ret = HTTP_ERROR;
+ }
} else {
error("Unable to start HTTP request for %s", url);
ret = HTTP_START_FAILED;
}
- slot->local = NULL;
curl_slist_free_all(headers);
strbuf_release(&buf);
+ if (ret == HTTP_OK)
+ credential_approve(&http_auth);
+
return ret;
}
+static int http_request_reauth(const char *url, void *result, int target,
+ int options)
+{
+ int ret = http_request(url, result, target, options);
+ if (ret != HTTP_REAUTH)
+ return ret;
+ return http_request(url, result, target, options);
+}
+
int http_get_strbuf(const char *url, struct strbuf *result, int options)
{
- int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
- if (http_ret == HTTP_REAUTH) {
- http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
- }
- return http_ret;
+ return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);
}
/*
- * Downloads an url and stores the result in the given file.
+ * Downloads a URL and stores the result in the given file.
*
* If a previous interrupted download is detected (i.e. a previous temporary
* file is still around) the download is resumed.
@@ -889,7 +860,7 @@ static int http_get_file(const char *url, const char *filename, int options)
goto cleanup;
}
- ret = http_request(url, result, HTTP_REQUEST_FILE, options);
+ ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options);
fclose(result);
if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename))
@@ -903,7 +874,7 @@ int http_error(const char *url, int ret)
{
/* http_request has already handled HTTP_START_FAILED. */
if (ret != HTTP_START_FAILED)
- error("%s while accessing %s\n", curl_errorstr, url);
+ error("%s while accessing %s", curl_errorstr, url);
return ret;
}
@@ -947,7 +918,7 @@ static char *fetch_pack_index(unsigned char *sha1, const char *base_url)
tmp = strbuf_detach(&buf, NULL);
if (http_get_file(url, tmp, 0) != HTTP_OK) {
- error("Unable to get pack index %s\n", url);
+ error("Unable to get pack index %s", url);
free(tmp);
tmp = NULL;
}
@@ -1043,7 +1014,6 @@ void release_http_pack_request(struct http_pack_request *preq)
if (preq->packfile != NULL) {
fclose(preq->packfile);
preq->packfile = NULL;
- preq->slot->local = NULL;
}
if (preq->range_header != NULL) {
curl_slist_free_all(preq->range_header);
@@ -1065,7 +1035,6 @@ int finish_http_pack_request(struct http_pack_request *preq)
fclose(preq->packfile);
preq->packfile = NULL;
- preq->slot->local = NULL;
lst = preq->lst;
while (*lst != p)
@@ -1116,9 +1085,8 @@ struct http_pack_request *new_http_pack_request(
struct strbuf buf = STRBUF_INIT;
struct http_pack_request *preq;
- preq = xmalloc(sizeof(*preq));
+ preq = xcalloc(1, sizeof(*preq));
preq->target = target;
- preq->range_header = NULL;
end_url_with_slash(&buf, base_url);
strbuf_addf(&buf, "objects/pack/pack-%s.pack",
@@ -1135,7 +1103,6 @@ struct http_pack_request *new_http_pack_request(
}
preq->slot = get_active_slot();
- preq->slot->local = preq->packfile;
curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile);
curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
@@ -1192,7 +1159,6 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb,
git_SHA1_Update(&freq->c, expn,
sizeof(expn) - freq->stream.avail_out);
} while (freq->stream.avail_in && freq->zret == Z_OK);
- data_received++;
return size;
}
@@ -1210,7 +1176,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
struct curl_slist *range_header = NULL;
struct http_object_request *freq;
- freq = xmalloc(sizeof(*freq));
+ freq = xcalloc(1, sizeof(*freq));
hashcpy(freq->sha1, sha1);
freq->localfile = -1;
@@ -1248,8 +1214,6 @@ struct http_object_request *new_http_object_request(const char *base_url,
goto abort;
}
- memset(&freq->stream, 0, sizeof(freq->stream));
-
git_inflate_init(&freq->stream);
git_SHA1_Init(&freq->c);
@@ -1324,7 +1288,6 @@ struct http_object_request *new_http_object_request(const char *base_url,
return freq;
abort:
- free(filename);
free(freq->url);
free(freq);
return NULL;
diff --git a/http.h b/http.h
index 0bf8592..915c286 100644
--- a/http.h
+++ b/http.h
@@ -49,7 +49,6 @@ struct slot_results {
struct active_request_slot {
CURL *curl;
- FILE *local;
int in_use;
CURLcode curl_result;
long http_code;
@@ -86,10 +85,10 @@ extern void add_fill_function(void *data, int (*fill)(void *));
extern void step_active_slots(void);
#endif
-extern void http_init(struct remote *remote);
+extern void http_init(struct remote *remote, const char *url,
+ int proactive_auth);
extern void http_cleanup(void);
-extern int data_received;
extern int active_requests;
extern int http_is_verbose;
extern size_t http_post_buffer;
@@ -128,7 +127,7 @@ extern char *get_remote_object_url(const char *url, const char *hex,
#define HTTP_NOAUTH 5
/*
- * Requests an url and stores the result in a strbuf.
+ * Requests a URL and stores the result in a strbuf.
*
* If the result pointer is NULL, a HTTP HEAD request is made instead of GET.
*/
diff --git a/ident.c b/ident.c
index 35a6f26..443c075 100644
--- a/ident.c
+++ b/ident.c
@@ -7,7 +7,10 @@
*/
#include "cache.h"
+static struct strbuf git_default_name = STRBUF_INIT;
+static struct strbuf git_default_email = STRBUF_INIT;
static char git_default_date[50];
+int user_ident_explicitly_given;
#ifdef NO_GECOS_IN_PWENT
#define get_gecos(ignored) "&"
@@ -15,108 +18,110 @@ static char git_default_date[50];
#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos)
#endif
-static void copy_gecos(const struct passwd *w, char *name, size_t sz)
+static void copy_gecos(const struct passwd *w, struct strbuf *name)
{
- char *src, *dst;
- size_t len, nlen;
-
- nlen = strlen(w->pw_name);
+ char *src;
/* Traditionally GECOS field had office phone numbers etc, separated
* with commas. Also & stands for capitalized form of the login name.
*/
- for (len = 0, dst = name, src = get_gecos(w); len < sz; src++) {
+ for (src = get_gecos(w); *src && *src != ','; src++) {
int ch = *src;
- if (ch != '&') {
- *dst++ = ch;
- if (ch == 0 || ch == ',')
- break;
- len++;
- continue;
- }
- if (len + nlen < sz) {
+ if (ch != '&')
+ strbuf_addch(name, ch);
+ else {
/* Sorry, Mr. McDonald... */
- *dst++ = toupper(*w->pw_name);
- memcpy(dst, w->pw_name + 1, nlen - 1);
- dst += nlen - 1;
- len += nlen;
+ strbuf_addch(name, toupper(*w->pw_name));
+ strbuf_addstr(name, w->pw_name + 1);
}
}
- if (len < sz)
- name[len] = 0;
- else
- die("Your parents must have hated you!");
+}
+
+static int add_mailname_host(struct strbuf *buf)
+{
+ FILE *mailname;
+ mailname = fopen("/etc/mailname", "r");
+ if (!mailname) {
+ if (errno != ENOENT)
+ warning("cannot open /etc/mailname: %s",
+ strerror(errno));
+ return -1;
+ }
+ if (strbuf_getline(buf, mailname, '\n') == EOF) {
+ if (ferror(mailname))
+ warning("cannot read /etc/mailname: %s",
+ strerror(errno));
+ fclose(mailname);
+ return -1;
+ }
+ /* success! */
+ fclose(mailname);
+ return 0;
+}
+
+static void add_domainname(struct strbuf *out)
+{
+ char buf[1024];
+ struct hostent *he;
+
+ if (gethostname(buf, sizeof(buf))) {
+ warning("cannot get host name: %s", strerror(errno));
+ strbuf_addstr(out, "(none)");
+ return;
+ }
+ if (strchr(buf, '.'))
+ strbuf_addstr(out, buf);
+ else if ((he = gethostbyname(buf)) && strchr(he->h_name, '.'))
+ strbuf_addstr(out, he->h_name);
+ else
+ strbuf_addf(out, "%s.(none)", buf);
}
-static void copy_email(const struct passwd *pw)
+static void copy_email(const struct passwd *pw, struct strbuf *email)
{
/*
* Make up a fake email address
* (name + '@' + hostname [+ '.' + domainname])
*/
- size_t len = strlen(pw->pw_name);
- if (len > sizeof(git_default_email)/2)
- die("Your sysadmin must hate you!");
- memcpy(git_default_email, pw->pw_name, len);
- git_default_email[len++] = '@';
- gethostname(git_default_email + len, sizeof(git_default_email) - len);
- if (!strchr(git_default_email+len, '.')) {
- struct hostent *he = gethostbyname(git_default_email + len);
- char *domainname;
-
- len = strlen(git_default_email);
- git_default_email[len++] = '.';
- if (he && (domainname = strchr(he->h_name, '.')))
- strlcpy(git_default_email + len, domainname + 1,
- sizeof(git_default_email) - len);
- else
- strlcpy(git_default_email + len, "(none)",
- sizeof(git_default_email) - len);
- }
+ strbuf_addstr(email, pw->pw_name);
+ strbuf_addch(email, '@');
+
+ if (!add_mailname_host(email))
+ return; /* read from "/etc/mailname" (Debian) */
+ add_domainname(email);
}
-static void setup_ident(void)
+const char *ident_default_name(void)
{
- struct passwd *pw = NULL;
-
- /* Get the name ("gecos") */
- if (!git_default_name[0]) {
- pw = getpwuid(getuid());
- if (!pw)
- die("You don't exist. Go away!");
- copy_gecos(pw, git_default_name, sizeof(git_default_name));
+ if (!git_default_name.len) {
+ copy_gecos(xgetpwuid_self(), &git_default_name);
+ strbuf_trim(&git_default_name);
}
+ return git_default_name.buf;
+}
- if (!git_default_email[0]) {
+const char *ident_default_email(void)
+{
+ if (!git_default_email.len) {
const char *email = getenv("EMAIL");
if (email && email[0]) {
- strlcpy(git_default_email, email,
- sizeof(git_default_email));
+ strbuf_addstr(&git_default_email, email);
user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
- } else {
- if (!pw)
- pw = getpwuid(getuid());
- if (!pw)
- die("You don't exist. Go away!");
- copy_email(pw);
- }
+ } else
+ copy_email(xgetpwuid_self(), &git_default_email);
+ strbuf_trim(&git_default_email);
}
-
- /* And set the default date */
- if (!git_default_date[0])
- datestamp(git_default_date, sizeof(git_default_date));
+ return git_default_email.buf;
}
-static int add_raw(char *buf, size_t size, int offset, const char *str)
+const char *ident_default_date(void)
{
- size_t len = strlen(str);
- if (offset + len > size)
- return size;
- memcpy(buf + offset, str, len);
- return offset + len;
+ if (!git_default_date[0])
+ datestamp(git_default_date, sizeof(git_default_date));
+ return git_default_date;
}
static int crud(unsigned char c)
@@ -137,7 +142,7 @@ static int crud(unsigned char c)
* Copy over a string to the destination, but avoid special
* characters ('\n', '<' and '>') and remove crud at the end
*/
-static int copy(char *buf, size_t size, int offset, const char *src)
+static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src)
{
size_t i, len;
unsigned char c;
@@ -161,19 +166,87 @@ 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
- * an identification line
+ * an identification line. We can only remove crud, never add it,
+ * so 'len' is our maximum.
*/
+ strbuf_grow(sb, len);
for (i = 0; i < len; i++) {
c = *src++;
switch (c) {
case '\n': case '<': case '>':
continue;
}
- if (offset >= size)
- return size;
- buf[offset++] = c;
+ sb->buf[sb->len++] = c;
}
- return offset;
+ sb->buf[sb->len] = '\0';
+}
+
+/*
+ * Reverse of fmt_ident(); given an ident line, split the fields
+ * to allow the caller to parse it.
+ * Signal a success by returning 0, but date/tz fields of the result
+ * can still be NULL if the input line only has the name/email part
+ * (e.g. reading from a reflog entry).
+ */
+int split_ident_line(struct ident_split *split, const char *line, int len)
+{
+ const char *cp;
+ size_t span;
+ int status = -1;
+
+ memset(split, 0, sizeof(*split));
+
+ split->name_begin = line;
+ for (cp = line; *cp && cp < line + len; cp++)
+ if (*cp == '<') {
+ split->mail_begin = cp + 1;
+ break;
+ }
+ if (!split->mail_begin)
+ return status;
+
+ for (cp = split->mail_begin - 2; line <= cp; cp--)
+ if (!isspace(*cp)) {
+ split->name_end = cp + 1;
+ break;
+ }
+ if (!split->name_end)
+ return status;
+
+ for (cp = split->mail_begin; cp < line + len; cp++)
+ if (*cp == '>') {
+ split->mail_end = cp;
+ break;
+ }
+ if (!split->mail_end)
+ return status;
+
+ for (cp = split->mail_end + 1; cp < line + len && isspace(*cp); cp++)
+ ;
+ if (line + len <= cp)
+ goto person_only;
+ split->date_begin = cp;
+ span = strspn(cp, "0123456789");
+ if (!span)
+ goto person_only;
+ split->date_end = split->date_begin + span;
+ for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)
+ ;
+ if (line + len <= cp || (*cp != '+' && *cp != '-'))
+ goto person_only;
+ split->tz_begin = cp;
+ span = strspn(cp + 1, "0123456789");
+ if (!span)
+ goto person_only;
+ split->tz_end = split->tz_begin + 1 + span;
+ return 0;
+
+person_only:
+ split->date_begin = NULL;
+ split->date_end = NULL;
+ split->tz_begin = NULL;
+ split->tz_end = NULL;
+ return 0;
}
static const char *env_hint =
@@ -192,61 +265,62 @@ static const char *env_hint =
const char *fmt_ident(const char *name, const char *email,
const char *date_str, int flag)
{
- static char buffer[1000];
+ static struct strbuf ident = STRBUF_INIT;
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)
- name = git_default_name;
+ int strict = (flag & IDENT_STRICT);
+ int want_date = !(flag & IDENT_NO_DATE);
+ int want_name = !(flag & IDENT_NO_NAME);
+
+ if (want_name && !name)
+ name = ident_default_name();
if (!email)
- email = git_default_email;
+ email = ident_default_email();
- if (!*name) {
+ if (want_name && !*name) {
struct passwd *pw;
- if ((warn_on_no_name || error_on_no_name) &&
- name == git_default_name && env_hint) {
- fputs(env_hint, stderr);
- env_hint = NULL; /* warn only once */
+ if (strict) {
+ if (name == git_default_name.buf)
+ fputs(env_hint, stderr);
+ die("empty ident name (for <%s>) not allowed", email);
}
- if (error_on_no_name)
- die("empty ident %s <%s> not allowed", name, email);
- pw = getpwuid(getuid());
- if (!pw)
- die("You don't exist. Go away!");
- strlcpy(git_default_name, pw->pw_name,
- sizeof(git_default_name));
- name = git_default_name;
+ pw = xgetpwuid_self();
+ name = pw->pw_name;
}
- strcpy(date, git_default_date);
- if (!name_addr_only && date_str && date_str[0]) {
- if (parse_date(date_str, date, sizeof(date)) < 0)
- die("invalid date format: %s", date_str);
+ if (strict && email == git_default_email.buf &&
+ strstr(email, "(none)")) {
+ fputs(env_hint, stderr);
+ die("unable to auto-detect email address (got '%s')", email);
}
- i = copy(buffer, sizeof(buffer), 0, name);
- i = add_raw(buffer, sizeof(buffer), i, " <");
- i = copy(buffer, sizeof(buffer), i, email);
- 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 (want_date) {
+ if (date_str && date_str[0]) {
+ if (parse_date(date_str, date, sizeof(date)) < 0)
+ die("invalid date format: %s", date_str);
+ }
+ else
+ strcpy(date, ident_default_date());
+ }
+
+ strbuf_reset(&ident);
+ if (want_name) {
+ strbuf_addstr_without_crud(&ident, name);
+ strbuf_addstr(&ident, " <");
}
- if (i >= sizeof(buffer))
- die("Impossibly long personal identifier");
- buffer[i] = 0;
- return buffer;
+ strbuf_addstr_without_crud(&ident, email);
+ if (want_name)
+ strbuf_addch(&ident, '>');
+ if (want_date) {
+ strbuf_addch(&ident, ' ');
+ strbuf_addstr_without_crud(&ident, date);
+ }
+ return ident.buf;
}
const char *fmt_name(const char *name, const char *email)
{
- return fmt_ident(name, email, NULL, IDENT_ERROR_ON_NO_NAME | IDENT_NO_DATE);
+ return fmt_ident(name, email, NULL, IDENT_STRICT | IDENT_NO_DATE);
}
const char *git_author_info(int flag)
@@ -277,3 +351,26 @@ int user_ident_sufficiently_given(void)
return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
#endif
}
+
+int git_ident_config(const char *var, const char *value, void *data)
+{
+ if (!strcmp(var, "user.name")) {
+ if (!value)
+ return config_error_nonbool(var);
+ strbuf_reset(&git_default_name);
+ strbuf_addstr(&git_default_name, value);
+ user_ident_explicitly_given |= IDENT_NAME_GIVEN;
+ return 0;
+ }
+
+ if (!strcmp(var, "user.email")) {
+ if (!value)
+ return config_error_nonbool(var);
+ strbuf_reset(&git_default_email);
+ strbuf_addstr(&git_default_email, value);
+ user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/imap-send.c b/imap-send.c
index e1ad1a4..d42e471 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -25,6 +25,7 @@
#include "cache.h"
#include "exec_cmd.h"
#include "run-command.h"
+#include "prompt.h"
#ifdef NO_OPENSSL
typedef void *SSL;
#else
@@ -41,28 +42,6 @@ struct store_conf {
unsigned trash_remote_new:1, trash_only_new:1;
};
-struct string_list {
- struct string_list *next;
- char string[1];
-};
-
-struct channel_conf {
- struct channel_conf *next;
- char *name;
- struct store_conf *master, *slave;
- char *master_name, *slave_name;
- char *sync_state;
- struct string_list *patterns;
- int mops, sops;
- unsigned max_messages; /* for slave only */
-};
-
-struct group_conf {
- struct group_conf *next;
- char *name;
- struct string_list *channels;
-};
-
/* For message->status */
#define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
#define M_DEAD (1<<1) /* expunged */
@@ -70,7 +49,6 @@ struct group_conf {
struct message {
struct message *next;
- /* struct string_list *keywords; */
size_t size; /* zero implies "not fetched" */
int uid;
unsigned char flags, status;
@@ -161,7 +139,6 @@ static struct imap_server_conf server = {
struct imap_store_conf {
struct store_conf gen;
struct imap_server_conf *server;
- unsigned use_namespace:1;
};
#define NIL (void *)0x1
@@ -1045,7 +1022,7 @@ static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const cha
ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
if (ret != strlen(response))
- return error("IMAP error: sending response failed\n");
+ return error("IMAP error: sending response failed");
free(response);
@@ -1209,13 +1186,10 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
goto bail;
}
if (!srvc->pass) {
- char prompt[80];
- sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
- arg = git_getpass(prompt);
- if (!arg) {
- perror("getpass");
- exit(1);
- }
+ struct strbuf prompt = STRBUF_INIT;
+ strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host);
+ arg = git_getpass(prompt.buf);
+ strbuf_release(&prompt);
if (!*arg) {
fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
goto bail;
@@ -1539,6 +1513,8 @@ int main(int argc, char **argv)
git_extract_argv0_path(argv[0]);
+ git_setup_gettext();
+
if (argc != 1)
usage(imap_send_usage);
diff --git a/kwset.c b/kwset.c
new file mode 100644
index 0000000..51b2ab6
--- /dev/null
+++ b/kwset.c
@@ -0,0 +1,771 @@
+/*
+ * This file has been copied from commit e7ac713d^ in the GNU grep git
+ * repository. A few small changes have been made to adapt the code to
+ * Git.
+ */
+
+/* kwset.c - search for any of a set of keywords.
+ Copyright 1989, 1998, 2000, 2005 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
+ 02110-1301, USA. */
+
+/* Written August 1989 by Mike Haertel.
+ The author may be reached (Email) at the address mike@ai.mit.edu,
+ or (US mail) as Mike Haertel c/o Free Software Foundation. */
+
+/* The algorithm implemented by these routines bears a startling resemblence
+ to one discovered by Beate Commentz-Walter, although it is not identical.
+ See "A String Matching Algorithm Fast on the Average," Technical Report,
+ IBM-Germany, Scientific Center Heidelberg, Tiergartenstrasse 15, D-6900
+ Heidelberg, Germany. See also Aho, A.V., and M. Corasick, "Efficient
+ String Matching: An Aid to Bibliographic Search," CACM June 1975,
+ Vol. 18, No. 6, which describes the failure function used below. */
+
+#include "cache.h"
+
+#include "kwset.h"
+#include "compat/obstack.h"
+
+#define NCHAR (UCHAR_MAX + 1)
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
+
+#define U(c) ((unsigned char) (c))
+
+/* Balanced tree of edges and labels leaving a given trie node. */
+struct tree
+{
+ struct tree *llink; /* Left link; MUST be first field. */
+ struct tree *rlink; /* Right link (to larger labels). */
+ struct trie *trie; /* Trie node pointed to by this edge. */
+ unsigned char label; /* Label on this edge. */
+ char balance; /* Difference in depths of subtrees. */
+};
+
+/* Node of a trie representing a set of reversed keywords. */
+struct trie
+{
+ unsigned int accepting; /* Word index of accepted word, or zero. */
+ struct tree *links; /* Tree of edges leaving this node. */
+ struct trie *parent; /* Parent of this node. */
+ struct trie *next; /* List of all trie nodes in level order. */
+ struct trie *fail; /* Aho-Corasick failure function. */
+ int depth; /* Depth of this node from the root. */
+ int shift; /* Shift function for search failures. */
+ int maxshift; /* Max shift of self and descendents. */
+};
+
+/* Structure returned opaquely to the caller, containing everything. */
+struct kwset
+{
+ struct obstack obstack; /* Obstack for node allocation. */
+ int words; /* Number of words in the trie. */
+ struct trie *trie; /* The trie itself. */
+ int mind; /* Minimum depth of an accepting node. */
+ int maxd; /* Maximum depth of any node. */
+ unsigned char delta[NCHAR]; /* Delta table for rapid search. */
+ struct trie *next[NCHAR]; /* Table of children of the root. */
+ char *target; /* Target string if there's only one. */
+ int mind2; /* Used in Boyer-Moore search for one string. */
+ char const *trans; /* Character translation table. */
+};
+
+/* Allocate and initialize a keyword set object, returning an opaque
+ pointer to it. Return NULL if memory is not available. */
+kwset_t
+kwsalloc (char const *trans)
+{
+ struct kwset *kwset;
+
+ kwset = (struct kwset *) xmalloc(sizeof (struct kwset));
+
+ obstack_init(&kwset->obstack);
+ kwset->words = 0;
+ kwset->trie
+ = (struct trie *) obstack_alloc(&kwset->obstack, sizeof (struct trie));
+ if (!kwset->trie)
+ {
+ kwsfree((kwset_t) kwset);
+ return NULL;
+ }
+ kwset->trie->accepting = 0;
+ kwset->trie->links = NULL;
+ kwset->trie->parent = NULL;
+ kwset->trie->next = NULL;
+ kwset->trie->fail = NULL;
+ kwset->trie->depth = 0;
+ kwset->trie->shift = 0;
+ kwset->mind = INT_MAX;
+ kwset->maxd = -1;
+ kwset->target = NULL;
+ kwset->trans = trans;
+
+ return (kwset_t) kwset;
+}
+
+/* This upper bound is valid for CHAR_BIT >= 4 and
+ exact for CHAR_BIT in { 4..11, 13, 15, 17, 19 }. */
+#define DEPTH_SIZE (CHAR_BIT + CHAR_BIT/2)
+
+/* Add the given string to the contents of the keyword set. Return NULL
+ for success, an error message otherwise. */
+const char *
+kwsincr (kwset_t kws, char const *text, size_t len)
+{
+ struct kwset *kwset;
+ register struct trie *trie;
+ register unsigned char label;
+ register struct tree *link;
+ register int depth;
+ struct tree *links[DEPTH_SIZE];
+ enum { L, R } dirs[DEPTH_SIZE];
+ struct tree *t, *r, *l, *rl, *lr;
+
+ kwset = (struct kwset *) kws;
+ trie = kwset->trie;
+ text += len;
+
+ /* Descend the trie (built of reversed keywords) character-by-character,
+ installing new nodes when necessary. */
+ while (len--)
+ {
+ label = kwset->trans ? kwset->trans[U(*--text)] : *--text;
+
+ /* Descend the tree of outgoing links for this trie node,
+ looking for the current character and keeping track
+ of the path followed. */
+ link = trie->links;
+ links[0] = (struct tree *) &trie->links;
+ dirs[0] = L;
+ depth = 1;
+
+ while (link && label != link->label)
+ {
+ links[depth] = link;
+ if (label < link->label)
+ dirs[depth++] = L, link = link->llink;
+ else
+ dirs[depth++] = R, link = link->rlink;
+ }
+
+ /* The current character doesn't have an outgoing link at
+ this trie node, so build a new trie node and install
+ a link in the current trie node's tree. */
+ if (!link)
+ {
+ link = (struct tree *) obstack_alloc(&kwset->obstack,
+ sizeof (struct tree));
+ if (!link)
+ return "memory exhausted";
+ link->llink = NULL;
+ link->rlink = NULL;
+ link->trie = (struct trie *) obstack_alloc(&kwset->obstack,
+ sizeof (struct trie));
+ if (!link->trie)
+ {
+ obstack_free(&kwset->obstack, link);
+ return "memory exhausted";
+ }
+ link->trie->accepting = 0;
+ link->trie->links = NULL;
+ link->trie->parent = trie;
+ link->trie->next = NULL;
+ link->trie->fail = NULL;
+ link->trie->depth = trie->depth + 1;
+ link->trie->shift = 0;
+ link->label = label;
+ link->balance = 0;
+
+ /* Install the new tree node in its parent. */
+ if (dirs[--depth] == L)
+ links[depth]->llink = link;
+ else
+ links[depth]->rlink = link;
+
+ /* Back up the tree fixing the balance flags. */
+ while (depth && !links[depth]->balance)
+ {
+ if (dirs[depth] == L)
+ --links[depth]->balance;
+ else
+ ++links[depth]->balance;
+ --depth;
+ }
+
+ /* Rebalance the tree by pointer rotations if necessary. */
+ if (depth && ((dirs[depth] == L && --links[depth]->balance)
+ || (dirs[depth] == R && ++links[depth]->balance)))
+ {
+ switch (links[depth]->balance)
+ {
+ case (char) -2:
+ switch (dirs[depth + 1])
+ {
+ case L:
+ r = links[depth], t = r->llink, rl = t->rlink;
+ t->rlink = r, r->llink = rl;
+ t->balance = r->balance = 0;
+ break;
+ case R:
+ r = links[depth], l = r->llink, t = l->rlink;
+ rl = t->rlink, lr = t->llink;
+ t->llink = l, l->rlink = lr, t->rlink = r, r->llink = rl;
+ l->balance = t->balance != 1 ? 0 : -1;
+ r->balance = t->balance != (char) -1 ? 0 : 1;
+ t->balance = 0;
+ break;
+ default:
+ abort ();
+ }
+ break;
+ case 2:
+ switch (dirs[depth + 1])
+ {
+ case R:
+ l = links[depth], t = l->rlink, lr = t->llink;
+ t->llink = l, l->rlink = lr;
+ t->balance = l->balance = 0;
+ break;
+ case L:
+ l = links[depth], r = l->rlink, t = r->llink;
+ lr = t->llink, rl = t->rlink;
+ t->llink = l, l->rlink = lr, t->rlink = r, r->llink = rl;
+ l->balance = t->balance != 1 ? 0 : -1;
+ r->balance = t->balance != (char) -1 ? 0 : 1;
+ t->balance = 0;
+ break;
+ default:
+ abort ();
+ }
+ break;
+ default:
+ abort ();
+ }
+
+ if (dirs[depth - 1] == L)
+ links[depth - 1]->llink = t;
+ else
+ links[depth - 1]->rlink = t;
+ }
+ }
+
+ trie = link->trie;
+ }
+
+ /* Mark the node we finally reached as accepting, encoding the
+ index number of this word in the keyword set so far. */
+ if (!trie->accepting)
+ trie->accepting = 1 + 2 * kwset->words;
+ ++kwset->words;
+
+ /* Keep track of the longest and shortest string of the keyword set. */
+ if (trie->depth < kwset->mind)
+ kwset->mind = trie->depth;
+ if (trie->depth > kwset->maxd)
+ kwset->maxd = trie->depth;
+
+ return NULL;
+}
+
+/* Enqueue the trie nodes referenced from the given tree in the
+ given queue. */
+static void
+enqueue (struct tree *tree, struct trie **last)
+{
+ if (!tree)
+ return;
+ enqueue(tree->llink, last);
+ enqueue(tree->rlink, last);
+ (*last) = (*last)->next = tree->trie;
+}
+
+/* Compute the Aho-Corasick failure function for the trie nodes referenced
+ from the given tree, given the failure function for their parent as
+ well as a last resort failure node. */
+static void
+treefails (register struct tree const *tree, struct trie const *fail,
+ struct trie *recourse)
+{
+ register struct tree *link;
+
+ if (!tree)
+ return;
+
+ treefails(tree->llink, fail, recourse);
+ treefails(tree->rlink, fail, recourse);
+
+ /* Find, in the chain of fails going back to the root, the first
+ node that has a descendent on the current label. */
+ while (fail)
+ {
+ link = fail->links;
+ while (link && tree->label != link->label)
+ if (tree->label < link->label)
+ link = link->llink;
+ else
+ link = link->rlink;
+ if (link)
+ {
+ tree->trie->fail = link->trie;
+ return;
+ }
+ fail = fail->fail;
+ }
+
+ tree->trie->fail = recourse;
+}
+
+/* Set delta entries for the links of the given tree such that
+ the preexisting delta value is larger than the current depth. */
+static void
+treedelta (register struct tree const *tree,
+ register unsigned int depth,
+ unsigned char delta[])
+{
+ if (!tree)
+ return;
+ treedelta(tree->llink, depth, delta);
+ treedelta(tree->rlink, depth, delta);
+ if (depth < delta[tree->label])
+ delta[tree->label] = depth;
+}
+
+/* Return true if A has every label in B. */
+static int
+hasevery (register struct tree const *a, register struct tree const *b)
+{
+ if (!b)
+ return 1;
+ if (!hasevery(a, b->llink))
+ return 0;
+ if (!hasevery(a, b->rlink))
+ return 0;
+ while (a && b->label != a->label)
+ if (b->label < a->label)
+ a = a->llink;
+ else
+ a = a->rlink;
+ return !!a;
+}
+
+/* Compute a vector, indexed by character code, of the trie nodes
+ referenced from the given tree. */
+static void
+treenext (struct tree const *tree, struct trie *next[])
+{
+ if (!tree)
+ return;
+ treenext(tree->llink, next);
+ treenext(tree->rlink, next);
+ next[tree->label] = tree->trie;
+}
+
+/* Compute the shift for each trie node, as well as the delta
+ table and next cache for the given keyword set. */
+const char *
+kwsprep (kwset_t kws)
+{
+ register struct kwset *kwset;
+ register int i;
+ register struct trie *curr;
+ register char const *trans;
+ unsigned char delta[NCHAR];
+
+ kwset = (struct kwset *) kws;
+
+ /* Initial values for the delta table; will be changed later. The
+ delta entry for a given character is the smallest depth of any
+ node at which an outgoing edge is labeled by that character. */
+ memset(delta, kwset->mind < UCHAR_MAX ? kwset->mind : UCHAR_MAX, NCHAR);
+
+ /* Check if we can use the simple boyer-moore algorithm, instead
+ of the hairy commentz-walter algorithm. */
+ if (kwset->words == 1 && kwset->trans == NULL)
+ {
+ char c;
+
+ /* Looking for just one string. Extract it from the trie. */
+ kwset->target = obstack_alloc(&kwset->obstack, kwset->mind);
+ if (!kwset->target)
+ return "memory exhausted";
+ for (i = kwset->mind - 1, curr = kwset->trie; i >= 0; --i)
+ {
+ kwset->target[i] = curr->links->label;
+ curr = curr->links->trie;
+ }
+ /* Build the Boyer Moore delta. Boy that's easy compared to CW. */
+ for (i = 0; i < kwset->mind; ++i)
+ delta[U(kwset->target[i])] = kwset->mind - (i + 1);
+ /* Find the minimal delta2 shift that we might make after
+ a backwards match has failed. */
+ c = kwset->target[kwset->mind - 1];
+ for (i = kwset->mind - 2; i >= 0; --i)
+ if (kwset->target[i] == c)
+ break;
+ kwset->mind2 = kwset->mind - (i + 1);
+ }
+ else
+ {
+ register struct trie *fail;
+ struct trie *last, *next[NCHAR];
+
+ /* Traverse the nodes of the trie in level order, simultaneously
+ computing the delta table, failure function, and shift function. */
+ for (curr = last = kwset->trie; curr; curr = curr->next)
+ {
+ /* Enqueue the immediate descendents in the level order queue. */
+ enqueue(curr->links, &last);
+
+ curr->shift = kwset->mind;
+ curr->maxshift = kwset->mind;
+
+ /* Update the delta table for the descendents of this node. */
+ treedelta(curr->links, curr->depth, delta);
+
+ /* Compute the failure function for the decendents of this node. */
+ treefails(curr->links, curr->fail, kwset->trie);
+
+ /* Update the shifts at each node in the current node's chain
+ of fails back to the root. */
+ for (fail = curr->fail; fail; fail = fail->fail)
+ {
+ /* If the current node has some outgoing edge that the fail
+ doesn't, then the shift at the fail should be no larger
+ than the difference of their depths. */
+ if (!hasevery(fail->links, curr->links))
+ if (curr->depth - fail->depth < fail->shift)
+ fail->shift = curr->depth - fail->depth;
+
+ /* If the current node is accepting then the shift at the
+ fail and its descendents should be no larger than the
+ difference of their depths. */
+ if (curr->accepting && fail->maxshift > curr->depth - fail->depth)
+ fail->maxshift = curr->depth - fail->depth;
+ }
+ }
+
+ /* Traverse the trie in level order again, fixing up all nodes whose
+ shift exceeds their inherited maxshift. */
+ for (curr = kwset->trie->next; curr; curr = curr->next)
+ {
+ if (curr->maxshift > curr->parent->maxshift)
+ curr->maxshift = curr->parent->maxshift;
+ if (curr->shift > curr->maxshift)
+ curr->shift = curr->maxshift;
+ }
+
+ /* Create a vector, indexed by character code, of the outgoing links
+ from the root node. */
+ for (i = 0; i < NCHAR; ++i)
+ next[i] = NULL;
+ treenext(kwset->trie->links, next);
+
+ if ((trans = kwset->trans) != NULL)
+ for (i = 0; i < NCHAR; ++i)
+ kwset->next[i] = next[U(trans[i])];
+ else
+ memcpy(kwset->next, next, NCHAR * sizeof(struct trie *));
+ }
+
+ /* Fix things up for any translation table. */
+ if ((trans = kwset->trans) != NULL)
+ for (i = 0; i < NCHAR; ++i)
+ kwset->delta[i] = delta[U(trans[i])];
+ else
+ memcpy(kwset->delta, delta, NCHAR);
+
+ return NULL;
+}
+
+/* Fast boyer-moore search. */
+static size_t
+bmexec (kwset_t kws, char const *text, size_t size)
+{
+ struct kwset const *kwset;
+ register unsigned char const *d1;
+ register char const *ep, *sp, *tp;
+ register int d, gc, i, len, md2;
+
+ kwset = (struct kwset const *) kws;
+ len = kwset->mind;
+
+ if (len == 0)
+ return 0;
+ if (len > size)
+ return -1;
+ if (len == 1)
+ {
+ tp = memchr (text, kwset->target[0], size);
+ return tp ? tp - text : -1;
+ }
+
+ d1 = kwset->delta;
+ sp = kwset->target + len;
+ gc = U(sp[-2]);
+ md2 = kwset->mind2;
+ tp = text + len;
+
+ /* Significance of 12: 1 (initial offset) + 10 (skip loop) + 1 (md2). */
+ if (size > 12 * len)
+ /* 11 is not a bug, the initial offset happens only once. */
+ for (ep = text + size - 11 * len;;)
+ {
+ while (tp <= ep)
+ {
+ d = d1[U(tp[-1])], tp += d;
+ d = d1[U(tp[-1])], tp += d;
+ if (d == 0)
+ goto found;
+ d = d1[U(tp[-1])], tp += d;
+ d = d1[U(tp[-1])], tp += d;
+ d = d1[U(tp[-1])], tp += d;
+ if (d == 0)
+ goto found;
+ d = d1[U(tp[-1])], tp += d;
+ d = d1[U(tp[-1])], tp += d;
+ d = d1[U(tp[-1])], tp += d;
+ if (d == 0)
+ goto found;
+ d = d1[U(tp[-1])], tp += d;
+ d = d1[U(tp[-1])], tp += d;
+ }
+ break;
+ found:
+ if (U(tp[-2]) == gc)
+ {
+ for (i = 3; i <= len && U(tp[-i]) == U(sp[-i]); ++i)
+ ;
+ if (i > len)
+ return tp - len - text;
+ }
+ tp += md2;
+ }
+
+ /* Now we have only a few characters left to search. We
+ carefully avoid ever producing an out-of-bounds pointer. */
+ ep = text + size;
+ d = d1[U(tp[-1])];
+ while (d <= ep - tp)
+ {
+ d = d1[U((tp += d)[-1])];
+ if (d != 0)
+ continue;
+ if (U(tp[-2]) == gc)
+ {
+ for (i = 3; i <= len && U(tp[-i]) == U(sp[-i]); ++i)
+ ;
+ if (i > len)
+ return tp - len - text;
+ }
+ d = md2;
+ }
+
+ return -1;
+}
+
+/* Hairy multiple string search. */
+static size_t
+cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch)
+{
+ struct kwset const *kwset;
+ struct trie * const *next;
+ struct trie const *trie;
+ struct trie const *accept;
+ char const *beg, *lim, *mch, *lmch;
+ register unsigned char c;
+ register unsigned char const *delta;
+ register int d;
+ register char const *end, *qlim;
+ register struct tree const *tree;
+ register char const *trans;
+
+ accept = NULL;
+
+ /* Initialize register copies and look for easy ways out. */
+ kwset = (struct kwset *) kws;
+ if (len < kwset->mind)
+ return -1;
+ next = kwset->next;
+ delta = kwset->delta;
+ trans = kwset->trans;
+ lim = text + len;
+ end = text;
+ if ((d = kwset->mind) != 0)
+ mch = NULL;
+ else
+ {
+ mch = text, accept = kwset->trie;
+ goto match;
+ }
+
+ if (len >= 4 * kwset->mind)
+ qlim = lim - 4 * kwset->mind;
+ else
+ qlim = NULL;
+
+ while (lim - end >= d)
+ {
+ if (qlim && end <= qlim)
+ {
+ end += d - 1;
+ while ((d = delta[c = *end]) && end < qlim)
+ {
+ end += d;
+ end += delta[U(*end)];
+ end += delta[U(*end)];
+ }
+ ++end;
+ }
+ else
+ d = delta[c = (end += d)[-1]];
+ if (d)
+ continue;
+ beg = end - 1;
+ trie = next[c];
+ if (trie->accepting)
+ {
+ mch = beg;
+ accept = trie;
+ }
+ d = trie->shift;
+ while (beg > text)
+ {
+ c = trans ? trans[U(*--beg)] : *--beg;
+ tree = trie->links;
+ while (tree && c != tree->label)
+ if (c < tree->label)
+ tree = tree->llink;
+ else
+ tree = tree->rlink;
+ if (tree)
+ {
+ trie = tree->trie;
+ if (trie->accepting)
+ {
+ mch = beg;
+ accept = trie;
+ }
+ }
+ else
+ break;
+ d = trie->shift;
+ }
+ if (mch)
+ goto match;
+ }
+ return -1;
+
+ match:
+ /* Given a known match, find the longest possible match anchored
+ at or before its starting point. This is nearly a verbatim
+ copy of the preceding main search loops. */
+ if (lim - mch > kwset->maxd)
+ lim = mch + kwset->maxd;
+ lmch = NULL;
+ d = 1;
+ while (lim - end >= d)
+ {
+ if ((d = delta[c = (end += d)[-1]]) != 0)
+ continue;
+ beg = end - 1;
+ if (!(trie = next[c]))
+ {
+ d = 1;
+ continue;
+ }
+ if (trie->accepting && beg <= mch)
+ {
+ lmch = beg;
+ accept = trie;
+ }
+ d = trie->shift;
+ while (beg > text)
+ {
+ c = trans ? trans[U(*--beg)] : *--beg;
+ tree = trie->links;
+ while (tree && c != tree->label)
+ if (c < tree->label)
+ tree = tree->llink;
+ else
+ tree = tree->rlink;
+ if (tree)
+ {
+ trie = tree->trie;
+ if (trie->accepting && beg <= mch)
+ {
+ lmch = beg;
+ accept = trie;
+ }
+ }
+ else
+ break;
+ d = trie->shift;
+ }
+ if (lmch)
+ {
+ mch = lmch;
+ goto match;
+ }
+ if (!d)
+ d = 1;
+ }
+
+ if (kwsmatch)
+ {
+ kwsmatch->index = accept->accepting / 2;
+ kwsmatch->offset[0] = mch - text;
+ kwsmatch->size[0] = accept->depth;
+ }
+ return mch - text;
+}
+
+/* Search through the given text for a match of any member of the
+ given keyword set. Return a pointer to the first character of
+ the matching substring, or NULL if no match is found. If FOUNDLEN
+ is non-NULL store in the referenced location the length of the
+ matching substring. Similarly, if FOUNDIDX is non-NULL, store
+ in the referenced location the index number of the particular
+ keyword matched. */
+size_t
+kwsexec (kwset_t kws, char const *text, size_t size,
+ struct kwsmatch *kwsmatch)
+{
+ struct kwset const *kwset = (struct kwset *) kws;
+ if (kwset->words == 1 && kwset->trans == NULL)
+ {
+ size_t ret = bmexec (kws, text, size);
+ if (kwsmatch != NULL && ret != (size_t) -1)
+ {
+ kwsmatch->index = 0;
+ kwsmatch->offset[0] = ret;
+ kwsmatch->size[0] = kwset->mind;
+ }
+ return ret;
+ }
+ else
+ return cwexec(kws, text, size, kwsmatch);
+}
+
+/* Free the components of the given keyword set. */
+void
+kwsfree (kwset_t kws)
+{
+ struct kwset *kwset;
+
+ kwset = (struct kwset *) kws;
+ obstack_free(&kwset->obstack, NULL);
+ free(kws);
+}
diff --git a/kwset.h b/kwset.h
new file mode 100644
index 0000000..a21b2ea
--- /dev/null
+++ b/kwset.h
@@ -0,0 +1,63 @@
+/* This file has been copied from commit e7ac713d^ in the GNU grep git
+ * repository. A few small changes have been made to adapt the code to
+ * Git.
+ */
+
+/* kwset.h - header declaring the keyword set library.
+ Copyright (C) 1989, 1998, 2005 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
+ 02110-1301, USA. */
+
+/* Written August 1989 by Mike Haertel.
+ The author may be reached (Email) at the address mike@ai.mit.edu,
+ or (US mail) as Mike Haertel c/o Free Software Foundation. */
+
+struct kwsmatch
+{
+ int index; /* Index number of matching keyword. */
+ size_t offset[1]; /* Offset of each submatch. */
+ size_t size[1]; /* Length of each submatch. */
+};
+
+struct kwset_t;
+typedef struct kwset_t* kwset_t;
+
+/* Return an opaque pointer to a newly allocated keyword set, or NULL
+ if enough memory cannot be obtained. The argument if non-NULL
+ specifies a table of character translations to be applied to all
+ pattern and search text. */
+extern kwset_t kwsalloc(char const *);
+
+/* Incrementally extend the keyword set to include the given string.
+ Return NULL for success, or an error message. Remember an index
+ number for each keyword included in the set. */
+extern const char *kwsincr(kwset_t, char const *, size_t);
+
+/* When the keyword set has been completely built, prepare it for
+ use. Return NULL for success, or an error message. */
+extern const char *kwsprep(kwset_t);
+
+/* Search through the given buffer for a member of the keyword set.
+ Return a pointer to the leftmost longest match found, or NULL if
+ no match is found. If foundlen is non-NULL, store the length of
+ the matching substring in the integer it points to. Similarly,
+ if foundindex is non-NULL, store the index of the particular
+ keyword found therein. */
+extern size_t kwsexec(kwset_t, char const *, size_t, struct kwsmatch *);
+
+/* Deallocate the given keyword set and all its associated storage. */
+extern void kwsfree(kwset_t);
+
diff --git a/list-objects.c b/list-objects.c
index 0fb44e7..3dd4a96 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -12,7 +12,8 @@ static void process_blob(struct rev_info *revs,
struct blob *blob,
show_object_fn show,
struct name_path *path,
- const char *name)
+ const char *name,
+ void *cb_data)
{
struct object *obj = &blob->object;
@@ -23,7 +24,7 @@ static void process_blob(struct rev_info *revs,
if (obj->flags & (UNINTERESTING | SEEN))
return;
obj->flags |= SEEN;
- show(obj, path, name);
+ show(obj, path, name, cb_data);
}
/*
@@ -52,7 +53,8 @@ static void process_gitlink(struct rev_info *revs,
const unsigned char *sha1,
show_object_fn show,
struct name_path *path,
- const char *name)
+ const char *name,
+ void *cb_data)
{
/* Nothing to do */
}
@@ -62,13 +64,15 @@ static void process_tree(struct rev_info *revs,
show_object_fn show,
struct name_path *path,
struct strbuf *base,
- const char *name)
+ const char *name,
+ void *cb_data)
{
struct object *obj = &tree->object;
struct tree_desc desc;
struct name_entry entry;
struct name_path me;
- int match = revs->diffopt.pathspec.nr == 0 ? 2 : 0;
+ enum interesting match = revs->diffopt.pathspec.nr == 0 ?
+ all_entries_interesting: entry_not_interesting;
int baselen = base->len;
if (!revs->tree_objects)
@@ -80,7 +84,7 @@ static void process_tree(struct rev_info *revs,
if (parse_tree(tree) < 0)
die("bad tree object %s", sha1_to_hex(obj->sha1));
obj->flags |= SEEN;
- show(obj, path, name);
+ show(obj, path, name, cb_data);
me.up = path;
me.elem = name;
me.elem_len = strlen(name);
@@ -94,26 +98,29 @@ static void process_tree(struct rev_info *revs,
init_tree_desc(&desc, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
- if (match != 2) {
+ if (match != all_entries_interesting) {
match = tree_entry_interesting(&entry, base, 0,
&revs->diffopt.pathspec);
- if (match < 0)
+ if (match == all_entries_not_interesting)
break;
- if (match == 0)
+ if (match == entry_not_interesting)
continue;
}
if (S_ISDIR(entry.mode))
process_tree(revs,
lookup_tree(entry.sha1),
- show, &me, base, entry.path);
+ show, &me, base, entry.path,
+ cb_data);
else if (S_ISGITLINK(entry.mode))
process_gitlink(revs, entry.sha1,
- show, &me, entry.path);
+ show, &me, entry.path,
+ cb_data);
else
process_blob(revs,
lookup_blob(entry.sha1),
- show, &me, entry.path);
+ show, &me, entry.path,
+ cb_data);
}
strbuf_setlen(base, baselen);
free(tree->buffer);
@@ -185,17 +192,17 @@ void traverse_commit_list(struct rev_info *revs,
continue;
if (obj->type == OBJ_TAG) {
obj->flags |= SEEN;
- show_object(obj, NULL, name);
+ show_object(obj, NULL, name, data);
continue;
}
if (obj->type == OBJ_TREE) {
process_tree(revs, (struct tree *)obj, show_object,
- NULL, &base, name);
+ NULL, &base, name, data);
continue;
}
if (obj->type == OBJ_BLOB) {
process_blob(revs, (struct blob *)obj, show_object,
- NULL, name);
+ NULL, name, data);
continue;
}
die("unknown pending object %s (%s)",
diff --git a/list-objects.h b/list-objects.h
index d65dbf0..3db7bb6 100644
--- a/list-objects.h
+++ b/list-objects.h
@@ -2,11 +2,10 @@
#define LIST_OBJECTS_H
typedef void (*show_commit_fn)(struct commit *, void *);
-typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *);
-typedef void (*show_edge_fn)(struct commit *);
-
+typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *, void *);
void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *);
+typedef void (*show_edge_fn)(struct commit *);
void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn);
#endif
diff --git a/ll-merge.c b/ll-merge.c
index 6ce512e..f3f7692 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -73,7 +73,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
if (buffer_is_binary(orig->ptr, orig->size) ||
buffer_is_binary(src1->ptr, src1->size) ||
buffer_is_binary(src2->ptr, src2->size)) {
- warning("Cannot merge binary files: %s (%s vs. %s)\n",
+ warning("Cannot merge binary files: %s (%s vs. %s)",
path, name1, name2);
return ll_binary_merge(drv_unused, result,
path,
@@ -330,7 +330,7 @@ static int git_path_check_merge(const char *path, struct git_attr_check check[2]
check[0].attr = git_attr("merge");
check[1].attr = git_attr("conflict-marker-size");
}
- return git_checkattr(path, 2, check);
+ return git_check_attr(path, 2, check);
}
static void normalize_file(mmfile_t *mm, const char *path)
@@ -387,7 +387,7 @@ int ll_merge_marker_size(const char *path)
if (!check.attr)
check.attr = git_attr("conflict-marker-size");
- if (!git_checkattr(path, 1, &check) && check.value) {
+ if (!git_check_attr(path, 1, &check) && check.value) {
marker_size = atoi(check.value);
if (marker_size <= 0)
marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
diff --git a/log-tree.c b/log-tree.c
index e945701..c894930 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -8,6 +8,7 @@
#include "refs.h"
#include "string-list.h"
#include "color.h"
+#include "gpg-interface.h"
struct decoration name_decoration = { "object names" };
@@ -18,6 +19,7 @@ enum decoration_type {
DECORATION_REF_TAG,
DECORATION_REF_STASH,
DECORATION_REF_HEAD,
+ DECORATION_GRAFTED,
};
static char decoration_colors[][COLOR_MAXLEN] = {
@@ -27,11 +29,12 @@ static char decoration_colors[][COLOR_MAXLEN] = {
GIT_COLOR_BOLD_YELLOW, /* REF_TAG */
GIT_COLOR_BOLD_MAGENTA, /* REF_STASH */
GIT_COLOR_BOLD_CYAN, /* REF_HEAD */
+ GIT_COLOR_BOLD_BLUE, /* GRAFTED */
};
static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix)
{
- if (decorate_use_color)
+ if (want_color(decorate_use_color))
return decoration_colors[ix];
return "";
}
@@ -77,7 +80,7 @@ int parse_decorate_color_config(const char *var, const int ofs, const char *valu
* for showing the commit sha1, use the same check for --decorate
*/
#define decorate_get_color_opt(o, ix) \
- decorate_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
+ decorate_get_color((o)->use_color, ix)
static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
{
@@ -90,20 +93,36 @@ static void add_name_decoration(enum decoration_type type, const char *name, str
static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
{
- struct object *obj = parse_object(sha1);
+ struct object *obj;
enum decoration_type type = DECORATION_NONE;
+
+ if (!prefixcmp(refname, "refs/replace/")) {
+ unsigned char original_sha1[20];
+ if (!read_replace_refs)
+ return 0;
+ if (get_sha1_hex(refname + 13, original_sha1)) {
+ warning("invalid replace ref %s", refname);
+ return 0;
+ }
+ obj = parse_object(original_sha1);
+ if (obj)
+ add_name_decoration(DECORATION_GRAFTED, "replaced", obj);
+ return 0;
+ }
+
+ obj = parse_object(sha1);
if (!obj)
return 0;
- if (!prefixcmp(refname, "refs/heads"))
+ if (!prefixcmp(refname, "refs/heads/"))
type = DECORATION_REF_LOCAL;
- else if (!prefixcmp(refname, "refs/remotes"))
+ else if (!prefixcmp(refname, "refs/remotes/"))
type = DECORATION_REF_REMOTE;
- else if (!prefixcmp(refname, "refs/tags"))
+ else if (!prefixcmp(refname, "refs/tags/"))
type = DECORATION_REF_TAG;
- else if (!prefixcmp(refname, "refs/stash"))
+ else if (!strcmp(refname, "refs/stash"))
type = DECORATION_REF_STASH;
- else if (!prefixcmp(refname, "HEAD"))
+ else if (!strcmp(refname, "HEAD"))
type = DECORATION_REF_HEAD;
if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS)
@@ -118,6 +137,15 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in
return 0;
}
+static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
+{
+ struct commit *commit = lookup_commit(graft->sha1);
+ if (!commit)
+ return 0;
+ add_name_decoration(DECORATION_GRAFTED, "grafted", &commit->object);
+ return 0;
+}
+
void load_ref_decorations(int flags)
{
static int loaded;
@@ -125,6 +153,7 @@ void load_ref_decorations(int flags)
loaded = 1;
for_each_ref(add_ref_decoration, &flags);
head_ref(add_ref_decoration, &flags);
+ for_each_commit_graft(add_graft_decoration, NULL);
}
}
@@ -137,6 +166,14 @@ static void show_parents(struct commit *commit, int abbrev)
}
}
+static void show_children(struct rev_info *opt, struct commit *commit, int abbrev)
+{
+ struct commit_list *p = lookup_decoration(&opt->children, &commit->object);
+ for ( ; p; p = p->next) {
+ printf(" %s", find_unique_abbrev(p->item->object.sha1, abbrev));
+ }
+}
+
void show_decorations(struct rev_info *opt, struct commit *commit)
{
const char *prefix;
@@ -262,19 +299,22 @@ static unsigned int digits_in_number(unsigned int number)
return result;
}
-void get_patch_filename(struct commit *commit, int nr, const char *suffix,
- struct strbuf *buf)
+void get_patch_filename(struct commit *commit, const char *subject, int nr,
+ const char *suffix, struct strbuf *buf)
{
int suffix_len = strlen(suffix) + 1;
int start_len = buf->len;
- strbuf_addf(buf, commit ? "%04d-" : "%d", nr);
- if (commit) {
+ strbuf_addf(buf, commit || subject ? "%04d-" : "%d", nr);
+ if (commit || subject) {
int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len;
struct pretty_print_context ctx = {0};
- ctx.date_mode = DATE_NORMAL;
- format_commit_message(commit, "%f", buf, &ctx);
+ if (subject)
+ strbuf_addstr(buf, subject);
+ else if (commit)
+ format_commit_message(commit, "%f", buf, &ctx);
+
if (max_len < buf->len)
strbuf_setlen(buf, max_len);
strbuf_addstr(buf, suffix);
@@ -347,8 +387,8 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
mime_boundary_leader, opt->mime_boundary);
extra_headers = subject_buffer;
- get_patch_filename(opt->numbered_files ? NULL : commit, opt->nr,
- opt->patch_suffix, &filename);
+ get_patch_filename(opt->numbered_files ? NULL : commit, NULL,
+ opt->nr, opt->patch_suffix, &filename);
snprintf(buffer, sizeof(buffer) - 1,
"\n--%s%s\n"
"Content-Type: text/x-patch;"
@@ -367,6 +407,129 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
*extra_headers_p = extra_headers;
}
+static void show_sig_lines(struct rev_info *opt, int status, const char *bol)
+{
+ const char *color, *reset, *eol;
+
+ color = diff_get_color_opt(&opt->diffopt,
+ status ? DIFF_WHITESPACE : DIFF_FRAGINFO);
+ reset = diff_get_color_opt(&opt->diffopt, DIFF_RESET);
+ while (*bol) {
+ eol = strchrnul(bol, '\n');
+ printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset,
+ *eol ? "\n" : "");
+ bol = (*eol) ? (eol + 1) : eol;
+ }
+}
+
+static void show_signature(struct rev_info *opt, struct commit *commit)
+{
+ struct strbuf payload = STRBUF_INIT;
+ struct strbuf signature = STRBUF_INIT;
+ struct strbuf gpg_output = STRBUF_INIT;
+ int status;
+
+ if (parse_signed_commit(commit->object.sha1, &payload, &signature) <= 0)
+ goto out;
+
+ status = verify_signed_buffer(payload.buf, payload.len,
+ signature.buf, signature.len,
+ &gpg_output);
+ if (status && !gpg_output.len)
+ strbuf_addstr(&gpg_output, "No signature\n");
+
+ show_sig_lines(opt, status, gpg_output.buf);
+
+ out:
+ strbuf_release(&gpg_output);
+ strbuf_release(&payload);
+ strbuf_release(&signature);
+}
+
+static int which_parent(const unsigned char *sha1, const struct commit *commit)
+{
+ int nth;
+ const struct commit_list *parent;
+
+ for (nth = 0, parent = commit->parents; parent; parent = parent->next) {
+ if (!hashcmp(parent->item->object.sha1, sha1))
+ return nth;
+ nth++;
+ }
+ return -1;
+}
+
+static int is_common_merge(const struct commit *commit)
+{
+ return (commit->parents
+ && commit->parents->next
+ && !commit->parents->next->next);
+}
+
+static void show_one_mergetag(struct rev_info *opt,
+ struct commit_extra_header *extra,
+ struct commit *commit)
+{
+ unsigned char sha1[20];
+ struct tag *tag;
+ struct strbuf verify_message;
+ int status, nth;
+ size_t payload_size, gpg_message_offset;
+
+ hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), sha1);
+ tag = lookup_tag(sha1);
+ if (!tag)
+ return; /* error message already given */
+
+ strbuf_init(&verify_message, 256);
+ if (parse_tag_buffer(tag, extra->value, extra->len))
+ strbuf_addstr(&verify_message, "malformed mergetag\n");
+ else if (is_common_merge(commit) &&
+ !hashcmp(tag->tagged->sha1,
+ commit->parents->next->item->object.sha1))
+ strbuf_addf(&verify_message,
+ "merged tag '%s'\n", tag->tag);
+ else if ((nth = which_parent(tag->tagged->sha1, commit)) < 0)
+ strbuf_addf(&verify_message, "tag %s names a non-parent %s\n",
+ tag->tag, tag->tagged->sha1);
+ else
+ strbuf_addf(&verify_message,
+ "parent #%d, tagged '%s'\n", nth + 1, tag->tag);
+ gpg_message_offset = verify_message.len;
+
+ payload_size = parse_signature(extra->value, extra->len);
+ if ((extra->len <= payload_size) ||
+ (verify_signed_buffer(extra->value, payload_size,
+ extra->value + payload_size,
+ extra->len - payload_size,
+ &verify_message) &&
+ verify_message.len <= gpg_message_offset)) {
+ strbuf_addstr(&verify_message, "No signature\n");
+ status = -1;
+ }
+ else if (strstr(verify_message.buf + gpg_message_offset,
+ ": Good signature from "))
+ status = 0;
+ else
+ status = -1;
+
+ show_sig_lines(opt, status, verify_message.buf);
+ strbuf_release(&verify_message);
+}
+
+static void show_mergetag(struct rev_info *opt, struct commit *commit)
+{
+ struct commit_extra_header *extra, *to_free;
+
+ to_free = read_commit_extra_headers(commit, NULL);
+ for (extra = to_free; extra; extra = extra->next) {
+ if (strcmp(extra->key, "mergetag"))
+ continue; /* not a merge tag */
+ show_one_mergetag(opt, extra, commit);
+ }
+ free_commit_extra_headers(to_free);
+}
+
void show_log(struct rev_info *opt)
{
struct strbuf msgbuf = STRBUF_INIT;
@@ -386,6 +549,8 @@ void show_log(struct rev_info *opt)
fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
if (opt->print_parents)
show_parents(commit, abbrev_commit);
+ if (opt->children.name)
+ show_children(opt, commit, abbrev_commit);
show_decorations(opt, commit);
if (opt->graph && !graph_is_commit_finished(opt->graph)) {
putchar('\n');
@@ -445,6 +610,8 @@ void show_log(struct rev_info *opt)
stdout);
if (opt->print_parents)
show_parents(commit, abbrev_commit);
+ if (opt->children.name)
+ show_children(opt, commit, abbrev_commit);
if (parent)
printf(" (from %s)",
find_unique_abbrev(parent->object.sha1,
@@ -465,15 +632,19 @@ void show_log(struct rev_info *opt)
* graph info here.
*/
show_reflog_message(opt->reflog_info,
- opt->commit_format == CMIT_FMT_ONELINE,
- opt->date_mode_explicit ?
- opt->date_mode :
- DATE_NORMAL);
+ opt->commit_format == CMIT_FMT_ONELINE,
+ opt->date_mode,
+ opt->date_mode_explicit);
if (opt->commit_format == CMIT_FMT_ONELINE)
return;
}
}
+ if (opt->show_signature) {
+ show_signature(opt, commit);
+ show_mergetag(opt, commit);
+ }
+
if (!commit->buffer)
return;
@@ -483,6 +654,7 @@ void show_log(struct rev_info *opt)
if (ctx.need_8bit_cte >= 0)
ctx.need_8bit_cte = has_non_ascii(opt->add_signoff);
ctx.date_mode = opt->date_mode;
+ ctx.date_mode_explicit = opt->date_mode_explicit;
ctx.abbrev = opt->diffopt.abbrev;
ctx.after_subject = extra_headers;
ctx.preserve_subject = opt->preserve_subject;
@@ -513,7 +685,7 @@ void show_log(struct rev_info *opt)
if (opt->use_terminator) {
if (!opt->missing_newline)
graph_show_padding(opt->graph);
- putchar('\n');
+ putchar(opt->diffopt.line_termination);
}
strbuf_release(&msgbuf);
@@ -542,14 +714,15 @@ int log_tree_diff_flush(struct rev_info *opt)
opt->verbose_header &&
opt->commit_format != CMIT_FMT_ONELINE) {
int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
- if ((pch & opt->diffopt.output_format) == pch)
- printf("---");
if (opt->diffopt.output_prefix) {
struct strbuf *msg = NULL;
msg = opt->diffopt.output_prefix(&opt->diffopt,
opt->diffopt.output_prefix_data);
fwrite(msg->buf, msg->len, 1, stdout);
}
+ if ((pch & opt->diffopt.output_format) == pch) {
+ printf("---");
+ }
putchar('\n');
}
}
@@ -559,9 +732,7 @@ int log_tree_diff_flush(struct rev_info *opt)
static int do_diff_combined(struct rev_info *opt, struct commit *commit)
{
- unsigned const char *sha1 = commit->object.sha1;
-
- diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
+ diff_tree_combined_merge(commit, opt->dense_combined_merges, opt);
return !opt->loginfo;
}
diff --git a/log-tree.h b/log-tree.h
index 5c4cf7c..f5ac238 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -21,7 +21,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
void load_ref_decorations(int flags);
#define FORMAT_PATCH_NAME_MAX 64
-void get_patch_filename(struct commit *commit, int nr, const char *suffix,
- struct strbuf *buf);
+void get_patch_filename(struct commit *commit, const char *subject, int nr,
+ const char *suffix, struct strbuf *buf);
#endif
diff --git a/mailmap.c b/mailmap.c
index 8c3196c..47aa419 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -190,27 +190,27 @@ void clear_mailmap(struct string_list *map)
int map_user(struct string_list *map,
char *email, int maxlen_email, char *name, int maxlen_name)
{
- char *p;
+ char *end_of_email;
struct string_list_item *item;
struct mailmap_entry *me;
char buf[1024], *mailbuf;
int i;
/* figure out space requirement for email */
- p = strchr(email, '>');
- if (!p) {
+ end_of_email = strchr(email, '>');
+ if (!end_of_email) {
/* email passed in might not be wrapped in <>, but end with a \0 */
- p = memchr(email, '\0', maxlen_email);
- if (!p)
+ end_of_email = memchr(email, '\0', maxlen_email);
+ if (!end_of_email)
return 0;
}
- if (p - email + 1 < sizeof(buf))
+ if (end_of_email - email + 1 < sizeof(buf))
mailbuf = buf;
else
- mailbuf = xmalloc(p - email + 1);
+ mailbuf = xmalloc(end_of_email - email + 1);
/* downcase the email address */
- for (i = 0; i < p - email; i++)
+ for (i = 0; i < end_of_email - email; i++)
mailbuf[i] = tolower(email[i]);
mailbuf[i] = 0;
@@ -236,6 +236,8 @@ int map_user(struct string_list *map,
}
if (maxlen_email && mi->email)
strlcpy(email, mi->email, maxlen_email);
+ else
+ *end_of_email = '\0';
if (maxlen_name && mi->name)
strlcpy(name, mi->name, maxlen_name);
debug_mm("map_user: to '%s' <%s>\n", name, mi->email ? mi->email : "");
diff --git a/merge-recursive.c b/merge-recursive.c
index db9ba19..680937c 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -38,16 +38,15 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two,
return lookup_tree(shifted);
}
-/*
- * A virtual commit has (const char *)commit->util set to the name.
- */
-
static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
{
struct commit *commit = xcalloc(1, sizeof(struct commit));
+ struct merge_remote_desc *desc = xmalloc(sizeof(*desc));
+
+ desc->name = comment;
+ desc->obj = (struct object *)commit;
commit->tree = tree;
- commit->util = (void*)comment;
- /* avoid warnings */
+ commit->util = desc;
commit->object.parsed = 1;
return commit;
}
@@ -66,10 +65,12 @@ static int sha_eq(const unsigned char *a, const unsigned char *b)
enum rename_type {
RENAME_NORMAL = 0,
RENAME_DELETE,
- RENAME_ONE_FILE_TO_TWO
+ RENAME_ONE_FILE_TO_ONE,
+ RENAME_ONE_FILE_TO_TWO,
+ RENAME_TWO_FILES_TO_ONE
};
-struct rename_df_conflict_info {
+struct rename_conflict_info {
enum rename_type rename_type;
struct diff_filepair *pair1;
struct diff_filepair *pair2;
@@ -77,6 +78,8 @@ struct rename_df_conflict_info {
const char *branch2;
struct stage_data *dst_entry1;
struct stage_data *dst_entry2;
+ struct diff_filespec ren1_other;
+ struct diff_filespec ren2_other;
};
/*
@@ -88,34 +91,54 @@ struct stage_data {
unsigned mode;
unsigned char sha[20];
} stages[4];
- struct rename_df_conflict_info *rename_df_conflict_info;
+ struct rename_conflict_info *rename_conflict_info;
unsigned processed:1;
};
-static inline void setup_rename_df_conflict_info(enum rename_type rename_type,
- struct diff_filepair *pair1,
- struct diff_filepair *pair2,
- const char *branch1,
- const char *branch2,
- struct stage_data *dst_entry1,
- struct stage_data *dst_entry2)
+static inline void setup_rename_conflict_info(enum rename_type rename_type,
+ struct diff_filepair *pair1,
+ struct diff_filepair *pair2,
+ const char *branch1,
+ const char *branch2,
+ struct stage_data *dst_entry1,
+ struct stage_data *dst_entry2,
+ struct merge_options *o,
+ struct stage_data *src_entry1,
+ struct stage_data *src_entry2)
{
- struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info));
+ struct rename_conflict_info *ci = xcalloc(1, sizeof(struct rename_conflict_info));
ci->rename_type = rename_type;
ci->pair1 = pair1;
ci->branch1 = branch1;
ci->branch2 = branch2;
ci->dst_entry1 = dst_entry1;
- dst_entry1->rename_df_conflict_info = ci;
+ dst_entry1->rename_conflict_info = ci;
dst_entry1->processed = 0;
assert(!pair2 == !dst_entry2);
if (dst_entry2) {
ci->dst_entry2 = dst_entry2;
ci->pair2 = pair2;
- dst_entry2->rename_df_conflict_info = ci;
- dst_entry2->processed = 0;
+ dst_entry2->rename_conflict_info = ci;
+ }
+
+ if (rename_type == RENAME_TWO_FILES_TO_ONE) {
+ /*
+ * For each rename, there could have been
+ * modifications on the side of history where that
+ * file was not renamed.
+ */
+ int ostage1 = o->branch1 == branch1 ? 3 : 2;
+ int ostage2 = ostage1 ^ 1;
+
+ ci->ren1_other.path = pair1->one->path;
+ hashcpy(ci->ren1_other.sha1, src_entry1->stages[ostage1].sha);
+ ci->ren1_other.mode = src_entry1->stages[ostage1].mode;
+
+ ci->ren2_other.path = pair2->one->path;
+ hashcpy(ci->ren2_other.sha1, src_entry2->stages[ostage2].sha);
+ ci->ren2_other.mode = src_entry2->stages[ostage2].mode;
}
}
@@ -160,7 +183,7 @@ static void output_commit_title(struct merge_options *o, struct commit *commit)
for (i = o->call_depth; i--;)
fputs(" ", stdout);
if (commit->util)
- printf("virtual %s\n", (char *)commit->util);
+ printf("virtual %s\n", merge_remote_util(commit)->name);
else {
printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
if (parse_commit(commit) != 0)
@@ -230,7 +253,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce))
- fprintf(stderr, "BUG: %d %.*s", ce_stage(ce),
+ fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
(int)ce_namelen(ce), ce->name);
}
die("Bug in merge-recursive.c");
@@ -241,7 +264,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
if (!cache_tree_fully_valid(active_cache_tree) &&
cache_tree_update(active_cache_tree,
- active_cache, active_nr, 0, 0) < 0)
+ active_cache, active_nr, 0) < 0)
die("error building trees");
result = lookup_tree(active_cache_tree->sha1);
@@ -333,44 +356,90 @@ static struct string_list *get_unmerged(void)
return unmerged;
}
-static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
- struct string_list *entries)
+static int string_list_df_name_compare(const void *a, const void *b)
{
- /* If there are D/F conflicts, and the paths currently exist
- * in the working copy as a file, we want to remove them to
- * make room for the corresponding directory. Such paths will
- * later be processed in process_df_entry() at the end. If
- * the corresponding directory ends up being removed by the
- * merge, then the file will be reinstated at that time;
- * otherwise, if the file is not supposed to be removed by the
- * merge, the contents of the file will be placed in another
- * unique filename.
+ const struct string_list_item *one = a;
+ const struct string_list_item *two = b;
+ int onelen = strlen(one->string);
+ int twolen = strlen(two->string);
+ /*
+ * Here we only care that entries for D/F conflicts are
+ * adjacent, in particular with the file of the D/F conflict
+ * appearing before files below the corresponding directory.
+ * The order of the rest of the list is irrelevant for us.
*
- * NOTE: This function relies on the fact that entries for a
- * D/F conflict will appear adjacent in the index, with the
- * entries for the file appearing before entries for paths
- * below the corresponding directory.
+ * To achieve this, we sort with df_name_compare and provide
+ * the mode S_IFDIR so that D/F conflicts will sort correctly.
+ * We use the mode S_IFDIR for everything else for simplicity,
+ * since in other cases any changes in their order due to
+ * sorting cause no problems for us.
*/
+ int cmp = df_name_compare(one->string, onelen, S_IFDIR,
+ two->string, twolen, S_IFDIR);
+ /*
+ * Now that 'foo' and 'foo/bar' compare equal, we have to make sure
+ * that 'foo' comes before 'foo/bar'.
+ */
+ if (cmp)
+ return cmp;
+ return onelen - twolen;
+}
+
+static void record_df_conflict_files(struct merge_options *o,
+ struct string_list *entries)
+{
+ /* If there is a D/F conflict and the file for such a conflict
+ * currently exist in the working tree, we want to allow it to be
+ * removed to make room for the corresponding directory if needed.
+ * The files underneath the directories of such D/F conflicts will
+ * be processed before the corresponding file involved in the D/F
+ * conflict. If the D/F directory ends up being removed by the
+ * merge, then we won't have to touch the D/F file. If the D/F
+ * directory needs to be written to the working copy, then the D/F
+ * file will simply be removed (in make_room_for_path()) to make
+ * room for the necessary paths. Note that if both the directory
+ * and the file need to be present, then the D/F file will be
+ * reinstated with a new unique name at the time it is processed.
+ */
+ struct string_list df_sorted_entries;
const char *last_file = NULL;
int last_len = 0;
int i;
+ /*
+ * If we're merging merge-bases, we don't want to bother with
+ * any working directory changes.
+ */
+ if (o->call_depth)
+ return;
+
+ /* Ensure D/F conflicts are adjacent in the entries list. */
+ memset(&df_sorted_entries, 0, sizeof(struct string_list));
for (i = 0; i < entries->nr; i++) {
- const char *path = entries->items[i].string;
+ struct string_list_item *next = &entries->items[i];
+ string_list_append(&df_sorted_entries, next->string)->util =
+ next->util;
+ }
+ qsort(df_sorted_entries.items, entries->nr, sizeof(*entries->items),
+ string_list_df_name_compare);
+
+ string_list_clear(&o->df_conflict_file_set, 1);
+ for (i = 0; i < df_sorted_entries.nr; i++) {
+ const char *path = df_sorted_entries.items[i].string;
int len = strlen(path);
- struct stage_data *e = entries->items[i].util;
+ struct stage_data *e = df_sorted_entries.items[i].util;
/*
* Check if last_file & path correspond to a D/F conflict;
* i.e. whether path is last_file+'/'+<something>.
- * If so, remove last_file to make room for path and friends.
+ * If so, record that it's okay to remove last_file to make
+ * room for path and friends if needed.
*/
if (last_file &&
len > last_len &&
memcmp(path, last_file, last_len) == 0 &&
path[last_len] == '/') {
- output(o, 3, "Removing %s to make room for subdirectory; may re-add later.", last_file);
- unlink(last_file);
+ string_list_insert(&o->df_conflict_file_set, last_file);
}
/*
@@ -386,6 +455,7 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
last_file = NULL;
}
}
+ string_list_clear(&df_sorted_entries, 0);
}
struct rename {
@@ -415,6 +485,7 @@ static struct string_list *get_renames(struct merge_options *o,
renames = xcalloc(1, sizeof(struct string_list));
diff_setup(&opts);
DIFF_OPT_SET(&opts, RECURSIVE);
+ DIFF_OPT_CLR(&opts, RENAME_EMPTY);
opts.detect_rename = DIFF_DETECT_RENAME;
opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
o->diff_rename_limit >= 0 ? o->diff_rename_limit :
@@ -461,10 +532,21 @@ static struct string_list *get_renames(struct merge_options *o,
return renames;
}
-static int update_stages_options(const char *path, struct diff_filespec *o,
- struct diff_filespec *a, struct diff_filespec *b,
- int clear, int options)
+static int update_stages(const char *path, const struct diff_filespec *o,
+ const struct diff_filespec *a,
+ const struct diff_filespec *b)
{
+
+ /*
+ * NOTE: It is usually a bad idea to call update_stages on a path
+ * before calling update_file on that same path, since it can
+ * sometimes lead to spurious "refusing to lose untracked file..."
+ * messages from update_file (via make_room_for path via
+ * would_lose_untracked). Instead, reverse the order of the calls
+ * (executing update_file first and then update_stages).
+ */
+ int clear = 1;
+ int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
if (clear)
if (remove_file_from_cache(path))
return -1;
@@ -480,23 +562,11 @@ static int update_stages_options(const char *path, struct diff_filespec *o,
return 0;
}
-static int update_stages(const char *path, struct diff_filespec *o,
- struct diff_filespec *a, struct diff_filespec *b,
- int clear)
+static void update_entry(struct stage_data *entry,
+ struct diff_filespec *o,
+ struct diff_filespec *a,
+ struct diff_filespec *b)
{
- int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
- return update_stages_options(path, o, a, b, clear, options);
-}
-
-static int update_stages_and_entry(const char *path,
- struct stage_data *entry,
- struct diff_filespec *o,
- struct diff_filespec *a,
- struct diff_filespec *b,
- int clear)
-{
- int options;
-
entry->processed = 0;
entry->stages[1].mode = o->mode;
entry->stages[2].mode = a->mode;
@@ -504,8 +574,6 @@ static int update_stages_and_entry(const char *path,
hashcpy(entry->stages[1].sha, o->sha1);
hashcpy(entry->stages[2].sha, a->sha1);
hashcpy(entry->stages[3].sha, b->sha1);
- options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
- return update_stages_options(path, o, a, b, clear, options);
}
static int remove_file(struct merge_options *o, int clean,
@@ -563,7 +631,31 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
}
}
-static int would_lose_untracked(const char *path)
+static int dir_in_way(const char *path, int check_working_copy)
+{
+ int pos, pathlen = strlen(path);
+ char *dirpath = xmalloc(pathlen + 2);
+ struct stat st;
+
+ strcpy(dirpath, path);
+ dirpath[pathlen] = '/';
+ dirpath[pathlen+1] = '\0';
+
+ pos = cache_name_pos(dirpath, pathlen+1);
+
+ if (pos < 0)
+ pos = -1 - pos;
+ if (pos < active_nr &&
+ !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) {
+ free(dirpath);
+ return 1;
+ }
+
+ free(dirpath);
+ return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);
+}
+
+static int was_tracked(const char *path)
{
int pos = cache_name_pos(path, strlen(path));
@@ -580,18 +672,42 @@ static int would_lose_untracked(const char *path)
switch (ce_stage(active_cache[pos])) {
case 0:
case 2:
- return 0;
+ return 1;
}
pos++;
}
- return file_exists(path);
+ return 0;
+}
+
+static int would_lose_untracked(const char *path)
+{
+ return !was_tracked(path) && file_exists(path);
}
-static int make_room_for_path(const char *path)
+static int make_room_for_path(struct merge_options *o, const char *path)
{
- int status;
+ int status, i;
const char *msg = "failed to create path '%s'%s";
+ /* Unlink any D/F conflict files that are in the way */
+ for (i = 0; i < o->df_conflict_file_set.nr; i++) {
+ const char *df_path = o->df_conflict_file_set.items[i].string;
+ size_t pathlen = strlen(path);
+ size_t df_pathlen = strlen(df_path);
+ if (df_pathlen < pathlen &&
+ path[df_pathlen] == '/' &&
+ strncmp(path, df_path, df_pathlen) == 0) {
+ output(o, 3,
+ "Removing %s to make room for subdirectory\n",
+ df_path);
+ unlink(df_path);
+ unsorted_string_list_delete_item(&o->df_conflict_file_set,
+ i, 0);
+ break;
+ }
+ }
+
+ /* Make sure leading directories are created */
status = safe_create_leading_directories_const(path);
if (status) {
if (status == -3) {
@@ -659,7 +775,7 @@ static void update_file_flags(struct merge_options *o,
}
}
- if (make_room_for_path(path) < 0) {
+ if (make_room_for_path(o, path) < 0) {
update_wd = 0;
free(buf);
goto update_index;
@@ -712,9 +828,9 @@ struct merge_file_info {
static int merge_3way(struct merge_options *o,
mmbuffer_t *result_buf,
- struct diff_filespec *one,
- struct diff_filespec *a,
- struct diff_filespec *b,
+ const struct diff_filespec *one,
+ const struct diff_filespec *a,
+ const struct diff_filespec *b,
const char *branch1,
const char *branch2)
{
@@ -771,12 +887,12 @@ static int merge_3way(struct merge_options *o,
return merge_status;
}
-static struct merge_file_info merge_file(struct merge_options *o,
- struct diff_filespec *one,
- struct diff_filespec *a,
- struct diff_filespec *b,
- const char *branch1,
- const char *branch2)
+static struct merge_file_info merge_file_1(struct merge_options *o,
+ const struct diff_filespec *one,
+ const struct diff_filespec *a,
+ const struct diff_filespec *b,
+ const char *branch1,
+ const char *branch2)
{
struct merge_file_info result;
result.merge = 0;
@@ -830,8 +946,10 @@ static struct merge_file_info merge_file(struct merge_options *o,
free(result_buf.ptr);
result.clean = (merge_status == 0);
} else if (S_ISGITLINK(a->mode)) {
- result.clean = merge_submodule(result.sha, one->path, one->sha1,
- a->sha1, b->sha1);
+ result.clean = merge_submodule(result.sha,
+ one->path, one->sha1,
+ a->sha1, b->sha1,
+ !o->call_depth);
} else if (S_ISLNK(a->mode)) {
hashcpy(result.sha, a->sha1);
@@ -845,94 +963,303 @@ static struct merge_file_info merge_file(struct merge_options *o,
return result;
}
+static struct merge_file_info
+merge_file_special_markers(struct merge_options *o,
+ const struct diff_filespec *one,
+ const struct diff_filespec *a,
+ const struct diff_filespec *b,
+ const char *branch1,
+ const char *filename1,
+ const char *branch2,
+ const char *filename2)
+{
+ char *side1 = NULL;
+ char *side2 = NULL;
+ struct merge_file_info mfi;
+
+ if (filename1) {
+ side1 = xmalloc(strlen(branch1) + strlen(filename1) + 2);
+ sprintf(side1, "%s:%s", branch1, filename1);
+ }
+ if (filename2) {
+ side2 = xmalloc(strlen(branch2) + strlen(filename2) + 2);
+ sprintf(side2, "%s:%s", branch2, filename2);
+ }
+
+ mfi = merge_file_1(o, one, a, b,
+ side1 ? side1 : branch1, side2 ? side2 : branch2);
+ free(side1);
+ free(side2);
+ return mfi;
+}
+
+static struct merge_file_info merge_file(struct merge_options *o,
+ const char *path,
+ const unsigned char *o_sha, int o_mode,
+ const unsigned char *a_sha, int a_mode,
+ const unsigned char *b_sha, int b_mode,
+ const char *branch1,
+ const char *branch2)
+{
+ struct diff_filespec one, a, b;
+
+ one.path = a.path = b.path = (char *)path;
+ hashcpy(one.sha1, o_sha);
+ one.mode = o_mode;
+ hashcpy(a.sha1, a_sha);
+ a.mode = a_mode;
+ hashcpy(b.sha1, b_sha);
+ b.mode = b_mode;
+ return merge_file_1(o, &one, &a, &b, branch1, branch2);
+}
+
+static void handle_change_delete(struct merge_options *o,
+ const char *path,
+ const unsigned char *o_sha, int o_mode,
+ const unsigned char *a_sha, int a_mode,
+ const unsigned char *b_sha, int b_mode,
+ const char *change, const char *change_past)
+{
+ char *renamed = NULL;
+ if (dir_in_way(path, !o->call_depth)) {
+ renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
+ }
+
+ if (o->call_depth) {
+ /*
+ * We cannot arbitrarily accept either a_sha or b_sha as
+ * correct; since there is no true "middle point" between
+ * them, simply reuse the base version for virtual merge base.
+ */
+ remove_file_from_cache(path);
+ update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
+ } else if (!a_sha) {
+ output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
+ "and %s in %s. Version %s of %s left in tree%s%s.",
+ change, path, o->branch1,
+ change_past, o->branch2, o->branch2, path,
+ NULL == renamed ? "" : " at ",
+ NULL == renamed ? "" : renamed);
+ update_file(o, 0, b_sha, b_mode, renamed ? renamed : path);
+ } else {
+ output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
+ "and %s in %s. Version %s of %s left in tree%s%s.",
+ change, path, o->branch2,
+ change_past, o->branch1, o->branch1, path,
+ NULL == renamed ? "" : " at ",
+ NULL == renamed ? "" : renamed);
+ if (renamed)
+ update_file(o, 0, a_sha, a_mode, renamed);
+ /*
+ * No need to call update_file() on path when !renamed, since
+ * that would needlessly touch path. We could call
+ * update_file_flags() with update_cache=0 and update_wd=0,
+ * but that's a no-op.
+ */
+ }
+ free(renamed);
+}
+
static void conflict_rename_delete(struct merge_options *o,
struct diff_filepair *pair,
const char *rename_branch,
const char *other_branch)
{
- char *dest_name = pair->two->path;
- int df_conflict = 0;
- struct stat st;
+ const struct diff_filespec *orig = pair->one;
+ const struct diff_filespec *dest = pair->two;
+ const unsigned char *a_sha = NULL;
+ const unsigned char *b_sha = NULL;
+ int a_mode = 0;
+ int b_mode = 0;
+
+ if (rename_branch == o->branch1) {
+ a_sha = dest->sha1;
+ a_mode = dest->mode;
+ } else {
+ b_sha = dest->sha1;
+ b_mode = dest->mode;
+ }
- output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
- "and deleted in %s",
- pair->one->path, pair->two->path, rename_branch,
- other_branch);
- if (!o->call_depth)
- update_stages(dest_name, NULL,
- rename_branch == o->branch1 ? pair->two : NULL,
- rename_branch == o->branch1 ? NULL : pair->two,
- 1);
- if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {
- dest_name = unique_path(o, dest_name, rename_branch);
- df_conflict = 1;
+ handle_change_delete(o,
+ o->call_depth ? orig->path : dest->path,
+ orig->sha1, orig->mode,
+ a_sha, a_mode,
+ b_sha, b_mode,
+ "rename", "renamed");
+
+ if (o->call_depth) {
+ remove_file_from_cache(dest->path);
+ } else {
+ update_stages(dest->path, NULL,
+ rename_branch == o->branch1 ? dest : NULL,
+ rename_branch == o->branch1 ? NULL : dest);
}
- update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);
- if (df_conflict)
- free(dest_name);
+
}
-static void conflict_rename_rename_1to2(struct merge_options *o,
- struct diff_filepair *pair1,
- const char *branch1,
- struct diff_filepair *pair2,
- const char *branch2)
+static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
+ struct stage_data *entry,
+ int stage)
{
- /* One file was renamed in both branches, but to different names. */
- char *del[2];
- int delp = 0;
- const char *ren1_dst = pair1->two->path;
- const char *ren2_dst = pair2->two->path;
- const char *dst_name1 = ren1_dst;
- const char *dst_name2 = ren2_dst;
- struct stat st;
- if (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
- dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
- output(o, 1, "%s is a directory in %s adding as %s instead",
- ren1_dst, branch2, dst_name1);
+ unsigned char *sha = entry->stages[stage].sha;
+ unsigned mode = entry->stages[stage].mode;
+ if (mode == 0 || is_null_sha1(sha))
+ return NULL;
+ hashcpy(target->sha1, sha);
+ target->mode = mode;
+ return target;
+}
+
+static void handle_file(struct merge_options *o,
+ struct diff_filespec *rename,
+ int stage,
+ struct rename_conflict_info *ci)
+{
+ char *dst_name = rename->path;
+ struct stage_data *dst_entry;
+ const char *cur_branch, *other_branch;
+ struct diff_filespec other;
+ struct diff_filespec *add;
+
+ if (stage == 2) {
+ dst_entry = ci->dst_entry1;
+ cur_branch = ci->branch1;
+ other_branch = ci->branch2;
+ } else {
+ dst_entry = ci->dst_entry2;
+ cur_branch = ci->branch2;
+ other_branch = ci->branch1;
}
- if (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
- dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
- output(o, 1, "%s is a directory in %s adding as %s instead",
- ren2_dst, branch1, dst_name2);
+
+ add = filespec_from_entry(&other, dst_entry, stage ^ 1);
+ if (add) {
+ char *add_name = unique_path(o, rename->path, other_branch);
+ update_file(o, 0, add->sha1, add->mode, add_name);
+
+ remove_file(o, 0, rename->path, 0);
+ dst_name = unique_path(o, rename->path, cur_branch);
+ } else {
+ if (dir_in_way(rename->path, !o->call_depth)) {
+ dst_name = unique_path(o, rename->path, cur_branch);
+ output(o, 1, "%s is a directory in %s adding as %s instead",
+ rename->path, other_branch, dst_name);
+ }
}
+ update_file(o, 0, rename->sha1, rename->mode, dst_name);
+ if (stage == 2)
+ update_stages(rename->path, NULL, rename, add);
+ else
+ update_stages(rename->path, NULL, add, rename);
+
+ if (dst_name != rename->path)
+ free(dst_name);
+}
+
+static void conflict_rename_rename_1to2(struct merge_options *o,
+ struct rename_conflict_info *ci)
+{
+ /* One file was renamed in both branches, but to different names. */
+ struct diff_filespec *one = ci->pair1->one;
+ struct diff_filespec *a = ci->pair1->two;
+ struct diff_filespec *b = ci->pair2->two;
+
+ output(o, 1, "CONFLICT (rename/rename): "
+ "Rename \"%s\"->\"%s\" in branch \"%s\" "
+ "rename \"%s\"->\"%s\" in \"%s\"%s",
+ one->path, a->path, ci->branch1,
+ one->path, b->path, ci->branch2,
+ o->call_depth ? " (left unresolved)" : "");
if (o->call_depth) {
- remove_file_from_cache(dst_name1);
- remove_file_from_cache(dst_name2);
+ struct merge_file_info mfi;
+ struct diff_filespec other;
+ struct diff_filespec *add;
+ mfi = merge_file(o, one->path,
+ one->sha1, one->mode,
+ a->sha1, a->mode,
+ b->sha1, b->mode,
+ ci->branch1, ci->branch2);
/*
- * Uncomment to leave the conflicting names in the resulting tree
- *
- * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
- * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
+ * FIXME: For rename/add-source conflicts (if we could detect
+ * such), this is wrong. We should instead find a unique
+ * pathname and then either rename the add-source file to that
+ * unique path, or use that unique path instead of src here.
*/
- } else {
- update_stages(ren1_dst, NULL, pair1->two, NULL, 1);
- update_stages(ren2_dst, NULL, NULL, pair2->two, 1);
+ update_file(o, 0, mfi.sha, mfi.mode, one->path);
- update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
- update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
+ /*
+ * Above, we put the merged content at the merge-base's
+ * path. Now we usually need to delete both a->path and
+ * b->path. However, the rename on each side of the merge
+ * could also be involved in a rename/add conflict. In
+ * such cases, we should keep the added file around,
+ * resolving the conflict at that path in its favor.
+ */
+ add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
+ if (add)
+ update_file(o, 0, add->sha1, add->mode, a->path);
+ else
+ remove_file_from_cache(a->path);
+ add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
+ if (add)
+ update_file(o, 0, add->sha1, add->mode, b->path);
+ else
+ remove_file_from_cache(b->path);
+ } else {
+ handle_file(o, a, 2, ci);
+ handle_file(o, b, 3, ci);
}
- while (delp--)
- free(del[delp]);
}
static void conflict_rename_rename_2to1(struct merge_options *o,
- struct rename *ren1,
- const char *branch1,
- struct rename *ren2,
- const char *branch2)
+ struct rename_conflict_info *ci)
{
- /* Two files were renamed to the same thing. */
- char *new_path1 = unique_path(o, ren1->pair->two->path, branch1);
- char *new_path2 = unique_path(o, ren2->pair->two->path, branch2);
- output(o, 1, "Renaming %s to %s and %s to %s instead",
- ren1->pair->one->path, new_path1,
- ren2->pair->one->path, new_path2);
- remove_file(o, 0, ren1->pair->two->path, 0);
- update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
- update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
- free(new_path2);
- free(new_path1);
+ /* Two files, a & b, were renamed to the same thing, c. */
+ struct diff_filespec *a = ci->pair1->one;
+ struct diff_filespec *b = ci->pair2->one;
+ struct diff_filespec *c1 = ci->pair1->two;
+ struct diff_filespec *c2 = ci->pair2->two;
+ char *path = c1->path; /* == c2->path */
+ struct merge_file_info mfi_c1;
+ struct merge_file_info mfi_c2;
+
+ output(o, 1, "CONFLICT (rename/rename): "
+ "Rename %s->%s in %s. "
+ "Rename %s->%s in %s",
+ a->path, c1->path, ci->branch1,
+ b->path, c2->path, ci->branch2);
+
+ remove_file(o, 1, a->path, would_lose_untracked(a->path));
+ remove_file(o, 1, b->path, would_lose_untracked(b->path));
+
+ mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
+ o->branch1, c1->path,
+ o->branch2, ci->ren1_other.path);
+ mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
+ o->branch1, ci->ren2_other.path,
+ o->branch2, c2->path);
+
+ if (o->call_depth) {
+ /*
+ * If mfi_c1.clean && mfi_c2.clean, then it might make
+ * sense to do a two-way merge of those results. But, I
+ * think in all cases, it makes sense to have the virtual
+ * merge base just undo the renames; they can be detected
+ * again later for the non-recursive merge.
+ */
+ remove_file(o, 0, path, 0);
+ update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
+ update_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);
+ } else {
+ char *new_path1 = unique_path(o, path, ci->branch1);
+ char *new_path2 = unique_path(o, path, ci->branch2);
+ output(o, 1, "Renaming %s to %s and %s to %s instead",
+ a->path, new_path1, b->path, new_path2);
+ remove_file(o, 0, path, 0);
+ update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
+ update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);
+ free(new_path2);
+ free(new_path1);
+ }
}
static int process_renames(struct merge_options *o,
@@ -947,12 +1274,12 @@ static int process_renames(struct merge_options *o,
for (i = 0; i < a_renames->nr; i++) {
sre = a_renames->items[i].util;
string_list_insert(&a_by_dst, sre->pair->two->path)->util
- = sre->dst_entry;
+ = (void *)sre;
}
for (i = 0; i < b_renames->nr; i++) {
sre = b_renames->items[i].util;
string_list_insert(&b_by_dst, sre->pair->two->path)->util
- = sre->dst_entry;
+ = (void *)sre;
}
for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
@@ -960,6 +1287,7 @@ static int process_renames(struct merge_options *o,
struct rename *ren1 = NULL, *ren2 = NULL;
const char *branch1, *branch2;
const char *ren1_src, *ren1_dst;
+ struct string_list_item *lookup;
if (i >= a_renames->nr) {
ren2 = b_renames->items[j++].util;
@@ -991,44 +1319,82 @@ static int process_renames(struct merge_options *o,
ren1 = tmp;
}
- ren1->dst_entry->processed = 1;
- ren1->src_entry->processed = 1;
-
if (ren1->processed)
continue;
ren1->processed = 1;
+ ren1->dst_entry->processed = 1;
+ /* BUG: We should only mark src_entry as processed if we
+ * are not dealing with a rename + add-source case.
+ */
+ ren1->src_entry->processed = 1;
ren1_src = ren1->pair->one->path;
ren1_dst = ren1->pair->two->path;
if (ren2) {
+ /* One file renamed on both sides */
const char *ren2_src = ren2->pair->one->path;
const char *ren2_dst = ren2->pair->two->path;
- /* Renamed in 1 and renamed in 2 */
+ enum rename_type rename_type;
if (strcmp(ren1_src, ren2_src) != 0)
- die("ren1.src != ren2.src");
+ die("ren1_src != ren2_src");
ren2->dst_entry->processed = 1;
ren2->processed = 1;
if (strcmp(ren1_dst, ren2_dst) != 0) {
- setup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO,
- ren1->pair,
- ren2->pair,
- branch1,
- branch2,
- ren1->dst_entry,
- ren2->dst_entry);
+ rename_type = RENAME_ONE_FILE_TO_TWO;
+ clean_merge = 0;
} else {
+ rename_type = RENAME_ONE_FILE_TO_ONE;
+ /* BUG: We should only remove ren1_src in
+ * the base stage (think of rename +
+ * add-source cases).
+ */
remove_file(o, 1, ren1_src, 1);
- update_stages_and_entry(ren1_dst,
- ren1->dst_entry,
- ren1->pair->one,
- ren1->pair->two,
- ren2->pair->two,
- 1 /* clear */);
+ update_entry(ren1->dst_entry,
+ ren1->pair->one,
+ ren1->pair->two,
+ ren2->pair->two);
}
+ setup_rename_conflict_info(rename_type,
+ ren1->pair,
+ ren2->pair,
+ branch1,
+ branch2,
+ ren1->dst_entry,
+ ren2->dst_entry,
+ o,
+ NULL,
+ NULL);
+ } else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) {
+ /* Two different files renamed to the same thing */
+ char *ren2_dst;
+ ren2 = lookup->util;
+ ren2_dst = ren2->pair->two->path;
+ if (strcmp(ren1_dst, ren2_dst) != 0)
+ die("ren1_dst != ren2_dst");
+
+ clean_merge = 0;
+ ren2->processed = 1;
+ /*
+ * BUG: We should only mark src_entry as processed
+ * if we are not dealing with a rename + add-source
+ * case.
+ */
+ ren2->src_entry->processed = 1;
+
+ setup_rename_conflict_info(RENAME_TWO_FILES_TO_ONE,
+ ren1->pair,
+ ren2->pair,
+ branch1,
+ branch2,
+ ren1->dst_entry,
+ ren2->dst_entry,
+ o,
+ ren1->src_entry,
+ ren2->src_entry);
+
} else {
/* Renamed in 1, maybe changed in 2 */
- struct string_list_item *item;
/* we only use sha1 and mode of these */
struct diff_filespec src_other, dst_other;
int try_merge;
@@ -1042,7 +1408,12 @@ static int process_renames(struct merge_options *o,
int renamed_stage = a_renames == renames1 ? 2 : 3;
int other_stage = a_renames == renames1 ? 3 : 2;
- remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
+ /* BUG: We should only remove ren1_src in the base
+ * stage and in other_stage (think of rename +
+ * add-source case).
+ */
+ remove_file(o, 1, ren1_src,
+ renamed_stage == 2 || !was_tracked(ren1_src));
hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
src_other.mode = ren1->src_entry->stages[other_stage].mode;
@@ -1051,27 +1422,33 @@ static int process_renames(struct merge_options *o,
try_merge = 0;
if (sha_eq(src_other.sha1, null_sha1)) {
- if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
- ren1->dst_entry->processed = 0;
- setup_rename_df_conflict_info(RENAME_DELETE,
- ren1->pair,
- NULL,
- branch1,
- branch2,
- ren1->dst_entry,
- NULL);
- } else {
- clean_merge = 0;
- conflict_rename_delete(o, ren1->pair, branch1, branch2);
- }
+ setup_rename_conflict_info(RENAME_DELETE,
+ ren1->pair,
+ NULL,
+ branch1,
+ branch2,
+ ren1->dst_entry,
+ NULL,
+ o,
+ NULL,
+ NULL);
} else if ((dst_other.mode == ren1->pair->two->mode) &&
sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
- /* Added file on the other side
- identical to the file being
- renamed: clean merge */
- update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
+ /*
+ * Added file on the other side identical to
+ * the file being renamed: clean merge.
+ * Also, there is no need to overwrite the
+ * file already in the working copy, so call
+ * update_file_flags() instead of
+ * update_file().
+ */
+ update_file_flags(o,
+ ren1->pair->two->sha1,
+ ren1->pair->two->mode,
+ ren1_dst,
+ 1, /* update_cache */
+ 0 /* update_wd */);
} else if (!sha_eq(dst_other.sha1, null_sha1)) {
- const char *new_path;
clean_merge = 0;
try_merge = 1;
output(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. "
@@ -1080,40 +1457,19 @@ static int process_renames(struct merge_options *o,
ren1_dst, branch2);
if (o->call_depth) {
struct merge_file_info mfi;
- struct diff_filespec one, a, b;
-
- one.path = a.path = b.path =
- (char *)ren1_dst;
- hashcpy(one.sha1, null_sha1);
- one.mode = 0;
- hashcpy(a.sha1, ren1->pair->two->sha1);
- a.mode = ren1->pair->two->mode;
- hashcpy(b.sha1, dst_other.sha1);
- b.mode = dst_other.mode;
- mfi = merge_file(o, &one, &a, &b,
- branch1,
- branch2);
+ mfi = merge_file(o, ren1_dst, null_sha1, 0,
+ ren1->pair->two->sha1, ren1->pair->two->mode,
+ dst_other.sha1, dst_other.mode,
+ branch1, branch2);
output(o, 1, "Adding merged %s", ren1_dst);
- update_file(o, 0,
- mfi.sha,
- mfi.mode,
- ren1_dst);
+ update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
try_merge = 0;
} else {
- new_path = unique_path(o, ren1_dst, branch2);
+ char *new_path = unique_path(o, ren1_dst, branch2);
output(o, 1, "Adding as %s instead", new_path);
update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+ free(new_path);
}
- } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
- ren2 = item->util;
- clean_merge = 0;
- ren2->processed = 1;
- output(o, 1, "CONFLICT (rename/rename): "
- "Rename %s->%s in %s. "
- "Rename %s->%s in %s",
- ren1_src, ren1_dst, branch1,
- ren2->pair->one->path, ren2->pair->two->path, branch2);
- conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
} else
try_merge = 1;
@@ -1129,16 +1485,17 @@ static int process_renames(struct merge_options *o,
b = ren1->pair->two;
a = &src_other;
}
- update_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);
- if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
- setup_rename_df_conflict_info(RENAME_NORMAL,
- ren1->pair,
- NULL,
- branch1,
- NULL,
- ren1->dst_entry,
- NULL);
- }
+ update_entry(ren1->dst_entry, one, a, b);
+ setup_rename_conflict_info(RENAME_NORMAL,
+ ren1->pair,
+ NULL,
+ branch1,
+ NULL,
+ ren1->dst_entry,
+ NULL,
+ o,
+ NULL,
+ NULL);
}
}
}
@@ -1200,29 +1557,18 @@ error_return:
return ret;
}
-static void handle_delete_modify(struct merge_options *o,
+static void handle_modify_delete(struct merge_options *o,
const char *path,
- const char *new_path,
+ unsigned char *o_sha, int o_mode,
unsigned char *a_sha, int a_mode,
unsigned char *b_sha, int b_mode)
{
- if (!a_sha) {
- output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
- "and modified in %s. Version %s of %s left in tree%s%s.",
- path, o->branch1,
- o->branch2, o->branch2, path,
- path == new_path ? "" : " at ",
- path == new_path ? "" : new_path);
- update_file(o, 0, b_sha, b_mode, new_path);
- } else {
- output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
- "and modified in %s. Version %s of %s left in tree%s%s.",
- path, o->branch2,
- o->branch1, o->branch1, path,
- path == new_path ? "" : " at ",
- path == new_path ? "" : new_path);
- update_file(o, 0, a_sha, a_mode, new_path);
- }
+ handle_change_delete(o,
+ path,
+ o_sha, o_mode,
+ a_sha, a_mode,
+ b_sha, b_mode,
+ "modify", "modified");
}
static int merge_content(struct merge_options *o,
@@ -1230,12 +1576,12 @@ static int merge_content(struct merge_options *o,
unsigned char *o_sha, int o_mode,
unsigned char *a_sha, int a_mode,
unsigned char *b_sha, int b_mode,
- const char *df_rename_conflict_branch)
+ struct rename_conflict_info *rename_conflict_info)
{
const char *reason = "content";
+ const char *path1 = NULL, *path2 = NULL;
struct merge_file_info mfi;
struct diff_filespec one, a, b;
- struct stat st;
unsigned df_conflict_remains = 0;
if (!o_sha) {
@@ -1250,16 +1596,43 @@ static int merge_content(struct merge_options *o,
hashcpy(b.sha1, b_sha);
b.mode = b_mode;
- mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);
- if (df_rename_conflict_branch &&
- lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
- df_conflict_remains = 1;
+ if (rename_conflict_info) {
+ struct diff_filepair *pair1 = rename_conflict_info->pair1;
+
+ path1 = (o->branch1 == rename_conflict_info->branch1) ?
+ pair1->two->path : pair1->one->path;
+ /* If rename_conflict_info->pair2 != NULL, we are in
+ * RENAME_ONE_FILE_TO_ONE case. Otherwise, we have a
+ * normal rename.
+ */
+ path2 = (rename_conflict_info->pair2 ||
+ o->branch2 == rename_conflict_info->branch1) ?
+ pair1->two->path : pair1->one->path;
+
+ if (dir_in_way(path, !o->call_depth))
+ df_conflict_remains = 1;
}
+ mfi = merge_file_special_markers(o, &one, &a, &b,
+ o->branch1, path1,
+ o->branch2, path2);
if (mfi.clean && !df_conflict_remains &&
- sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
+ sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
+ int path_renamed_outside_HEAD;
output(o, 3, "Skipped %s (merged same as existing)", path);
- else
+ /*
+ * The content merge resulted in the same file contents we
+ * already had. We can return early if those file contents
+ * are recorded at the correct path (which may not be true
+ * if the merge involves a rename).
+ */
+ path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
+ if (!path_renamed_outside_HEAD) {
+ add_cacheinfo(mfi.mode, mfi.sha, path,
+ 0, (!o->call_depth), 0);
+ return mfi.clean;
+ }
+ } else
output(o, 2, "Auto-merging %s", path);
if (!mfi.clean) {
@@ -1267,16 +1640,34 @@ static int merge_content(struct merge_options *o,
reason = "submodule";
output(o, 1, "CONFLICT (%s): Merge conflict in %s",
reason, path);
+ if (rename_conflict_info && !df_conflict_remains)
+ update_stages(path, &one, &a, &b);
}
if (df_conflict_remains) {
- const char *new_path;
- update_file_flags(o, mfi.sha, mfi.mode, path,
- o->call_depth || mfi.clean, 0);
- new_path = unique_path(o, path, df_rename_conflict_branch);
- mfi.clean = 0;
+ char *new_path;
+ if (o->call_depth) {
+ remove_file_from_cache(path);
+ } else {
+ if (!mfi.clean)
+ update_stages(path, &one, &a, &b);
+ else {
+ int file_from_stage2 = was_tracked(path);
+ struct diff_filespec merged;
+ hashcpy(merged.sha1, mfi.sha);
+ merged.mode = mfi.mode;
+
+ update_stages(path, NULL,
+ file_from_stage2 ? &merged : NULL,
+ file_from_stage2 ? NULL : &merged);
+ }
+
+ }
+ new_path = unique_path(o, path, rename_conflict_info->branch1);
output(o, 1, "Adding as %s instead", new_path);
- update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);
+ update_file(o, 0, mfi.sha, mfi.mode, new_path);
+ free(new_path);
+ mfi.clean = 0;
} else {
update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
}
@@ -1301,104 +1692,15 @@ static int process_entry(struct merge_options *o,
unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
- if (entry->rename_df_conflict_info)
- return 1; /* Such cases are handled elsewhere. */
-
- entry->processed = 1;
- if (o_sha && (!a_sha || !b_sha)) {
- /* Case A: Deleted in one */
- if ((!a_sha && !b_sha) ||
- (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
- (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
- /* Deleted in both or deleted in one and
- * unchanged in the other */
- if (a_sha)
- output(o, 2, "Removing %s", path);
- /* do not touch working file if it did not exist */
- remove_file(o, 1, path, !a_sha);
- } else if (string_list_has_string(&o->current_directory_set,
- path)) {
- entry->processed = 0;
- return 1; /* Assume clean until processed */
- } else {
- /* Deleted in one and changed in the other */
- clean_merge = 0;
- handle_delete_modify(o, path, path,
- a_sha, a_mode, b_sha, b_mode);
- }
-
- } else if ((!o_sha && a_sha && !b_sha) ||
- (!o_sha && !a_sha && b_sha)) {
- /* Case B: Added in one. */
- unsigned mode;
- const unsigned char *sha;
-
- if (a_sha) {
- mode = a_mode;
- sha = a_sha;
- } else {
- mode = b_mode;
- sha = b_sha;
- }
- if (string_list_has_string(&o->current_directory_set, path)) {
- /* Handle D->F conflicts after all subfiles */
- entry->processed = 0;
- return 1; /* Assume clean until processed */
- } else {
- output(o, 2, "Adding %s", path);
- update_file(o, 1, sha, mode, path);
- }
- } else if (a_sha && b_sha) {
- /* Case C: Added in both (check for same permissions) and */
- /* case D: Modified in both, but differently. */
- clean_merge = merge_content(o, path,
- o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
- NULL);
- } else if (!o_sha && !a_sha && !b_sha) {
- /*
- * this entry was deleted altogether. a_mode == 0 means
- * we had that path and want to actively remove it.
- */
- remove_file(o, 1, path, !a_mode);
- } else
- die("Fatal merge failure, shouldn't happen.");
-
- return clean_merge;
-}
-
-/*
- * Per entry merge function for D/F (and/or rename) conflicts. In the
- * cases we can cleanly resolve D/F conflicts, process_entry() can
- * clean out all the files below the directory for us. All D/F
- * conflict cases must be handled here at the end to make sure any
- * directories that can be cleaned out, are.
- *
- * Some rename conflicts may also be handled here that don't necessarily
- * involve D/F conflicts, since the code to handle them is generic enough
- * to handle those rename conflicts with or without D/F conflicts also
- * being involved.
- */
-static int process_df_entry(struct merge_options *o,
- const char *path, struct stage_data *entry)
-{
- int clean_merge = 1;
- unsigned o_mode = entry->stages[1].mode;
- unsigned a_mode = entry->stages[2].mode;
- unsigned b_mode = entry->stages[3].mode;
- unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
- unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
- unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
- struct stat st;
-
entry->processed = 1;
- if (entry->rename_df_conflict_info) {
- struct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;
- char *src;
+ if (entry->rename_conflict_info) {
+ struct rename_conflict_info *conflict_info = entry->rename_conflict_info;
switch (conflict_info->rename_type) {
case RENAME_NORMAL:
+ case RENAME_ONE_FILE_TO_ONE:
clean_merge = merge_content(o, path,
o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
- conflict_info->branch1);
+ conflict_info);
break;
case RENAME_DELETE:
clean_merge = 0;
@@ -1407,39 +1709,39 @@ static int process_df_entry(struct merge_options *o,
conflict_info->branch2);
break;
case RENAME_ONE_FILE_TO_TWO:
- src = conflict_info->pair1->one->path;
clean_merge = 0;
- output(o, 1, "CONFLICT (rename/rename): "
- "Rename \"%s\"->\"%s\" in branch \"%s\" "
- "rename \"%s\"->\"%s\" in \"%s\"%s",
- src, conflict_info->pair1->two->path, conflict_info->branch1,
- src, conflict_info->pair2->two->path, conflict_info->branch2,
- o->call_depth ? " (left unresolved)" : "");
- if (o->call_depth) {
- remove_file_from_cache(src);
- update_file(o, 0, conflict_info->pair1->one->sha1,
- conflict_info->pair1->one->mode, src);
- }
- conflict_rename_rename_1to2(o, conflict_info->pair1,
- conflict_info->branch1,
- conflict_info->pair2,
- conflict_info->branch2);
- conflict_info->dst_entry2->processed = 1;
+ conflict_rename_rename_1to2(o, conflict_info);
+ break;
+ case RENAME_TWO_FILES_TO_ONE:
+ clean_merge = 0;
+ conflict_rename_rename_2to1(o, conflict_info);
break;
default:
entry->processed = 0;
break;
}
} else if (o_sha && (!a_sha || !b_sha)) {
- /* Modify/delete; deleted side may have put a directory in the way */
- const char *new_path = path;
- if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
- new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
- clean_merge = 0;
- handle_delete_modify(o, path, new_path,
- a_sha, a_mode, b_sha, b_mode);
- } else if (!o_sha && !!a_sha != !!b_sha) {
- /* directory -> (directory, file) */
+ /* Case A: Deleted in one */
+ if ((!a_sha && !b_sha) ||
+ (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
+ (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
+ /* Deleted in both or deleted in one and
+ * unchanged in the other */
+ if (a_sha)
+ output(o, 2, "Removing %s", path);
+ /* do not touch working file if it did not exist */
+ remove_file(o, 1, path, !a_sha);
+ } else {
+ /* Modify/delete; deleted side may have put a directory in the way */
+ clean_merge = 0;
+ handle_modify_delete(o, path, o_sha, o_mode,
+ a_sha, a_mode, b_sha, b_mode);
+ }
+ } else if ((!o_sha && a_sha && !b_sha) ||
+ (!o_sha && !a_sha && b_sha)) {
+ /* Case B: Added in one. */
+ /* [nothing|directory] -> ([nothing|directory], file) */
+
const char *add_branch;
const char *other_branch;
unsigned mode;
@@ -1459,21 +1761,37 @@ static int process_df_entry(struct merge_options *o,
sha = b_sha;
conf = "directory/file";
}
- if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
- const char *new_path = unique_path(o, path, add_branch);
+ if (dir_in_way(path, !o->call_depth)) {
+ char *new_path = unique_path(o, path, add_branch);
clean_merge = 0;
output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
"Adding %s as %s",
conf, path, other_branch, path, new_path);
+ if (o->call_depth)
+ remove_file_from_cache(path);
update_file(o, 0, sha, mode, new_path);
+ if (o->call_depth)
+ remove_file_from_cache(path);
+ free(new_path);
} else {
output(o, 2, "Adding %s", path);
- update_file(o, 1, sha, mode, path);
+ /* do not overwrite file if already present */
+ update_file_flags(o, sha, mode, path, 1, !a_sha);
}
- } else {
- entry->processed = 0;
- return 1; /* not handled; assume clean until processed */
- }
+ } else if (a_sha && b_sha) {
+ /* Case C: Added in both (check for same permissions) and */
+ /* case D: Modified in both, but differently. */
+ clean_merge = merge_content(o, path,
+ o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+ NULL);
+ } else if (!o_sha && !a_sha && !b_sha) {
+ /*
+ * this entry was deleted altogether. a_mode == 0 means
+ * we had that path and want to actively remove it.
+ */
+ remove_file(o, 1, path, !a_mode);
+ } else
+ die("Fatal merge failure, shouldn't happen.");
return clean_merge;
}
@@ -1517,11 +1835,11 @@ int merge_trees(struct merge_options *o,
get_files_dirs(o, merge);
entries = get_unmerged();
- make_room_for_directories_of_df_conflicts(o, entries);
+ record_df_conflict_files(o, entries);
re_head = get_renames(o, head, common, head, merge, entries);
re_merge = get_renames(o, merge, common, head, merge, entries);
clean = process_renames(o, re_head, re_merge);
- for (i = 0; i < entries->nr; i++) {
+ for (i = entries->nr-1; 0 <= i; i--) {
const char *path = entries->items[i].string;
struct stage_data *e = entries->items[i].util;
if (!e->processed
@@ -1529,13 +1847,6 @@ int merge_trees(struct merge_options *o,
clean = 0;
}
for (i = 0; i < entries->nr; i++) {
- const char *path = entries->items[i].string;
- struct stage_data *e = entries->items[i].util;
- if (!e->processed
- && !process_df_entry(o, path, e))
- clean = 0;
- }
- for (i = 0; i < entries->nr; i++) {
struct stage_data *e = entries->items[i].util;
if (!e->processed)
die("Unprocessed path??? %s",
@@ -1601,12 +1912,10 @@ int merge_recursive(struct merge_options *o,
merged_common_ancestors = pop_commit(&ca);
if (merged_common_ancestors == NULL) {
- /* if there is no common ancestor, make an empty tree */
- struct tree *tree = xcalloc(1, sizeof(struct tree));
+ /* if there is no common ancestor, use an empty tree */
+ struct tree *tree;
- tree->object.parsed = 1;
- tree->object.type = OBJ_TREE;
- pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+ tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
merged_common_ancestors = make_virtual_commit(tree, "ancestor");
}
@@ -1743,6 +2052,8 @@ void init_merge_options(struct merge_options *o)
o->current_file_set.strdup_strings = 1;
memset(&o->current_directory_set, 0, sizeof(struct string_list));
o->current_directory_set.strdup_strings = 1;
+ memset(&o->df_conflict_file_set, 0, sizeof(struct string_list));
+ o->df_conflict_file_set.strdup_strings = 1;
}
int parse_merge_opt(struct merge_options *o, const char *s)
@@ -1758,7 +2069,9 @@ int parse_merge_opt(struct merge_options *o, const char *s)
else if (!prefixcmp(s, "subtree="))
o->subtree_shift = s + strlen("subtree=");
else if (!strcmp(s, "patience"))
- o->xdl_opts |= XDF_PATIENCE_DIFF;
+ o->xdl_opts = DIFF_WITH_ALG(o, PATIENCE_DIFF);
+ else if (!strcmp(s, "histogram"))
+ o->xdl_opts = DIFF_WITH_ALG(o, HISTOGRAM_DIFF);
else if (!strcmp(s, "ignore-space-change"))
o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(s, "ignore-all-space"))
diff --git a/merge-recursive.h b/merge-recursive.h
index 7e1e972..58f3435 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -26,6 +26,7 @@ struct merge_options {
struct strbuf obuf;
struct string_list current_file_set;
struct string_list current_directory_set;
+ struct string_list df_conflict_file_set;
};
/* merge_trees() but with recursive ancestor consolidation */
diff --git a/mergesort.c b/mergesort.c
new file mode 100644
index 0000000..e5fdf2e
--- /dev/null
+++ b/mergesort.c
@@ -0,0 +1,73 @@
+#include "cache.h"
+#include "mergesort.h"
+
+struct mergesort_sublist {
+ void *ptr;
+ unsigned long len;
+};
+
+static void *get_nth_next(void *list, unsigned long n,
+ void *(*get_next_fn)(const void *))
+{
+ while (n-- && list)
+ list = get_next_fn(list);
+ return list;
+}
+
+static void *pop_item(struct mergesort_sublist *l,
+ void *(*get_next_fn)(const void *))
+{
+ void *p = l->ptr;
+ l->ptr = get_next_fn(l->ptr);
+ l->len = l->ptr ? (l->len - 1) : 0;
+ return p;
+}
+
+void *llist_mergesort(void *list,
+ void *(*get_next_fn)(const void *),
+ void (*set_next_fn)(void *, void *),
+ int (*compare_fn)(const void *, const void *))
+{
+ unsigned long l;
+
+ if (!list)
+ return NULL;
+ for (l = 1; ; l *= 2) {
+ void *curr;
+ struct mergesort_sublist p, q;
+
+ p.ptr = list;
+ q.ptr = get_nth_next(p.ptr, l, get_next_fn);
+ if (!q.ptr)
+ break;
+ p.len = q.len = l;
+
+ if (compare_fn(p.ptr, q.ptr) > 0)
+ list = curr = pop_item(&q, get_next_fn);
+ else
+ list = curr = pop_item(&p, get_next_fn);
+
+ while (p.ptr) {
+ while (p.len || q.len) {
+ void *prev = curr;
+
+ if (!p.len)
+ curr = pop_item(&q, get_next_fn);
+ else if (!q.len)
+ curr = pop_item(&p, get_next_fn);
+ else if (compare_fn(p.ptr, q.ptr) > 0)
+ curr = pop_item(&q, get_next_fn);
+ else
+ curr = pop_item(&p, get_next_fn);
+ set_next_fn(prev, curr);
+ }
+ p.ptr = q.ptr;
+ p.len = l;
+ q.ptr = get_nth_next(p.ptr, l, get_next_fn);
+ q.len = q.ptr ? l : 0;
+
+ }
+ set_next_fn(curr, NULL);
+ }
+ return list;
+}
diff --git a/mergesort.h b/mergesort.h
new file mode 100644
index 0000000..644cff1
--- /dev/null
+++ b/mergesort.h
@@ -0,0 +1,17 @@
+#ifndef MERGESORT_H
+#define MERGESORT_H
+
+/*
+ * Sort linked list in place.
+ * - get_next_fn() returns the next element given an element of a linked list.
+ * - set_next_fn() takes two elements A and B, and makes B the "next" element
+ * of A on the list.
+ * - compare_fn() takes two elements A and B, and returns negative, 0, positive
+ * as the same sign as "subtracting" B from A.
+ */
+void *llist_mergesort(void *list,
+ void *(*get_next_fn)(const void *),
+ void (*set_next_fn)(void *, void *),
+ int (*compare_fn)(const void *, const void *));
+
+#endif
diff --git a/mergetools/araxis b/mergetools/araxis
new file mode 100644
index 0000000..64f97c5
--- /dev/null
+++ b/mergetools/araxis
@@ -0,0 +1,20 @@
+diff_cmd () {
+ "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" >/dev/null 2>&1
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ if $base_present
+ then
+ "$merge_tool_path" -wait -merge -3 -a1 \
+ "$BASE" "$LOCAL" "$REMOTE" "$MERGED" >/dev/null 2>&1
+ else
+ "$merge_tool_path" -wait -2 \
+ "$LOCAL" "$REMOTE" "$MERGED" >/dev/null 2>&1
+ fi
+ check_unchanged
+}
+
+translate_merge_tool_path() {
+ echo compare
+}
diff --git a/mergetools/bc3 b/mergetools/bc3
new file mode 100644
index 0000000..b6319d2
--- /dev/null
+++ b/mergetools/bc3
@@ -0,0 +1,25 @@
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ if $base_present
+ then
+ "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \
+ -mergeoutput="$MERGED"
+ else
+ "$merge_tool_path" "$LOCAL" "$REMOTE" \
+ -mergeoutput="$MERGED"
+ fi
+ check_unchanged
+}
+
+translate_merge_tool_path() {
+ if type bcomp >/dev/null 2>/dev/null
+ then
+ echo bcomp
+ else
+ echo bcompare
+ fi
+}
diff --git a/mergetools/defaults b/mergetools/defaults
new file mode 100644
index 0000000..1d8f2a3
--- /dev/null
+++ b/mergetools/defaults
@@ -0,0 +1,46 @@
+# Redefined by builtin tools
+can_merge () {
+ return 0
+}
+
+can_diff () {
+ return 0
+}
+
+diff_cmd () {
+ merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+ if test -z "$merge_tool_cmd"
+ then
+ status=1
+ break
+ fi
+ ( eval $merge_tool_cmd )
+ status=$?
+ return $status
+}
+
+merge_cmd () {
+ merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+ if test -z "$merge_tool_cmd"
+ then
+ status=1
+ break
+ fi
+ trust_exit_code="$(git config --bool \
+ mergetool."$1".trustExitCode || echo false)"
+ if test "$trust_exit_code" = "false"
+ then
+ touch "$BACKUP"
+ ( eval $merge_tool_cmd )
+ status=$?
+ check_unchanged
+ else
+ ( eval $merge_tool_cmd )
+ status=$?
+ fi
+ return $status
+}
+
+translate_merge_tool_path () {
+ echo "$1"
+}
diff --git a/mergetools/deltawalker b/mergetools/deltawalker
new file mode 100644
index 0000000..b3c71b6
--- /dev/null
+++ b/mergetools/deltawalker
@@ -0,0 +1,21 @@
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE" >/dev/null 2>&1
+}
+
+merge_cmd () {
+ # Adding $(pwd)/ in front of $MERGED should not be necessary.
+ # However without it, DeltaWalker (at least v1.9.8 on Windows)
+ # crashes with a JRE exception. The DeltaWalker user manual,
+ # shows $(pwd)/ whenever the '-merged' options is given.
+ # Adding it here seems to work around the problem.
+ if $base_present
+ then
+ "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" -merged="$(pwd)/$MERGED"
+ else
+ "$merge_tool_path" "$LOCAL" "$REMOTE" -merged="$(pwd)/$MERGED"
+ fi >/dev/null 2>&1
+}
+
+translate_merge_tool_path() {
+ echo DeltaWalker
+}
diff --git a/mergetools/diffuse b/mergetools/diffuse
new file mode 100644
index 0000000..02e0843
--- /dev/null
+++ b/mergetools/diffuse
@@ -0,0 +1,17 @@
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ if $base_present
+ then
+ "$merge_tool_path" \
+ "$LOCAL" "$MERGED" "$REMOTE" \
+ "$BASE" | cat
+ else
+ "$merge_tool_path" \
+ "$LOCAL" "$MERGED" "$REMOTE" | cat
+ fi
+ check_unchanged
+}
diff --git a/mergetools/ecmerge b/mergetools/ecmerge
new file mode 100644
index 0000000..13c2e43
--- /dev/null
+++ b/mergetools/ecmerge
@@ -0,0 +1,16 @@
+diff_cmd () {
+ "$merge_tool_path" --default --mode=diff2 "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ if $base_present
+ then
+ "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
+ --default --mode=merge3 --to="$MERGED"
+ else
+ "$merge_tool_path" "$LOCAL" "$REMOTE" \
+ --default --mode=merge2 --to="$MERGED"
+ fi
+ check_unchanged
+}
diff --git a/mergetools/emerge b/mergetools/emerge
new file mode 100644
index 0000000..f96d9e5
--- /dev/null
+++ b/mergetools/emerge
@@ -0,0 +1,23 @@
+diff_cmd () {
+ "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+ if $base_present
+ then
+ "$merge_tool_path" \
+ -f emerge-files-with-ancestor-command \
+ "$LOCAL" "$REMOTE" "$BASE" \
+ "$(basename "$MERGED")"
+ else
+ "$merge_tool_path" \
+ -f emerge-files-command \
+ "$LOCAL" "$REMOTE" \
+ "$(basename "$MERGED")"
+ fi
+ status=$?
+}
+
+translate_merge_tool_path() {
+ echo emacs
+}
diff --git a/mergetools/kdiff3 b/mergetools/kdiff3
new file mode 100644
index 0000000..28fead4
--- /dev/null
+++ b/mergetools/kdiff3
@@ -0,0 +1,24 @@
+diff_cmd () {
+ "$merge_tool_path" --auto \
+ --L1 "$MERGED (A)" --L2 "$MERGED (B)" \
+ "$LOCAL" "$REMOTE" >/dev/null 2>&1
+}
+
+merge_cmd () {
+ if $base_present
+ then
+ "$merge_tool_path" --auto \
+ --L1 "$MERGED (Base)" \
+ --L2 "$MERGED (Local)" \
+ --L3 "$MERGED (Remote)" \
+ -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" \
+ >/dev/null 2>&1
+ else
+ "$merge_tool_path" --auto \
+ --L1 "$MERGED (Local)" \
+ --L2 "$MERGED (Remote)" \
+ -o "$MERGED" "$LOCAL" "$REMOTE" \
+ >/dev/null 2>&1
+ fi
+ status=$?
+}
diff --git a/mergetools/kompare b/mergetools/kompare
new file mode 100644
index 0000000..433686c
--- /dev/null
+++ b/mergetools/kompare
@@ -0,0 +1,7 @@
+can_merge () {
+ return 1
+}
+
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
diff --git a/mergetools/meld b/mergetools/meld
new file mode 100644
index 0000000..cb672a5
--- /dev/null
+++ b/mergetools/meld
@@ -0,0 +1,32 @@
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+ if test -z "${meld_has_output_option:+set}"
+ then
+ check_meld_for_output_version
+ fi
+ touch "$BACKUP"
+ if test "$meld_has_output_option" = true
+ then
+ "$merge_tool_path" --output "$MERGED" \
+ "$LOCAL" "$BASE" "$REMOTE"
+ else
+ "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
+ fi
+ check_unchanged
+}
+
+# Check whether 'meld --output <file>' is supported
+check_meld_for_output_version () {
+ meld_path="$(git config mergetool.meld.path)"
+ meld_path="${meld_path:-meld}"
+
+ if "$meld_path" --help 2>&1 | grep -e --output >/dev/null
+ then
+ meld_has_output_option=true
+ else
+ meld_has_output_option=false
+ fi
+}
diff --git a/mergetools/opendiff b/mergetools/opendiff
new file mode 100644
index 0000000..0942b2a
--- /dev/null
+++ b/mergetools/opendiff
@@ -0,0 +1,16 @@
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ if $base_present
+ then
+ "$merge_tool_path" "$LOCAL" "$REMOTE" \
+ -ancestor "$BASE" -merge "$MERGED" | cat
+ else
+ "$merge_tool_path" "$LOCAL" "$REMOTE" \
+ -merge "$MERGED" | cat
+ fi
+ check_unchanged
+}
diff --git a/mergetools/p4merge b/mergetools/p4merge
new file mode 100644
index 0000000..1a45c1b
--- /dev/null
+++ b/mergetools/p4merge
@@ -0,0 +1,10 @@
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ $base_present || >"$BASE"
+ "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
+ check_unchanged
+}
diff --git a/mergetools/tkdiff b/mergetools/tkdiff
new file mode 100644
index 0000000..618c438
--- /dev/null
+++ b/mergetools/tkdiff
@@ -0,0 +1,12 @@
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+ if $base_present
+ then
+ "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"
+ else
+ "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
+ fi
+}
diff --git a/mergetools/tortoisemerge b/mergetools/tortoisemerge
new file mode 100644
index 0000000..ed7db49
--- /dev/null
+++ b/mergetools/tortoisemerge
@@ -0,0 +1,17 @@
+can_diff () {
+ return 1
+}
+
+merge_cmd () {
+ if $base_present
+ then
+ touch "$BACKUP"
+ "$merge_tool_path" \
+ -base:"$BASE" -mine:"$LOCAL" \
+ -theirs:"$REMOTE" -merged:"$MERGED"
+ check_unchanged
+ else
+ echo "TortoiseMerge cannot be used without a base" 1>&2
+ return 1
+ fi
+}
diff --git a/mergetools/vim b/mergetools/vim
new file mode 100644
index 0000000..619594a
--- /dev/null
+++ b/mergetools/vim
@@ -0,0 +1,44 @@
+diff_cmd () {
+ case "$1" in
+ gvimdiff|vimdiff)
+ "$merge_tool_path" -R -f -d \
+ -c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
+ ;;
+ gvimdiff2|vimdiff2)
+ "$merge_tool_path" -R -f -d \
+ -c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
+ ;;
+ esac
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ case "$1" in
+ gvimdiff|vimdiff)
+ if $base_present
+ then
+ "$merge_tool_path" -f -d -c 'wincmd J' \
+ "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
+ else
+ "$merge_tool_path" -f -d -c 'wincmd l' \
+ "$LOCAL" "$MERGED" "$REMOTE"
+ fi
+ ;;
+ gvimdiff2|vimdiff2)
+ "$merge_tool_path" -f -d -c 'wincmd l' \
+ "$LOCAL" "$MERGED" "$REMOTE"
+ ;;
+ esac
+ check_unchanged
+}
+
+translate_merge_tool_path() {
+ case "$1" in
+ gvimdiff|gvimdiff2)
+ echo gvim
+ ;;
+ vimdiff|vimdiff2)
+ echo vim
+ ;;
+ esac
+}
diff --git a/mergetools/xxdiff b/mergetools/xxdiff
new file mode 100644
index 0000000..05b4433
--- /dev/null
+++ b/mergetools/xxdiff
@@ -0,0 +1,25 @@
+diff_cmd () {
+ "$merge_tool_path" \
+ -R 'Accel.Search: "Ctrl+F"' \
+ -R 'Accel.SearchForward: "Ctrl-G"' \
+ "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ if $base_present
+ then
+ "$merge_tool_path" -X --show-merged-pane \
+ -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+ -R 'Accel.Search: "Ctrl+F"' \
+ -R 'Accel.SearchForward: "Ctrl-G"' \
+ --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
+ else
+ "$merge_tool_path" -X $extra \
+ -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+ -R 'Accel.Search: "Ctrl+F"' \
+ -R 'Accel.SearchForward: "Ctrl-G"' \
+ --merged-file "$MERGED" "$LOCAL" "$REMOTE"
+ fi
+ check_unchanged
+}
diff --git a/name-hash.c b/name-hash.c
index c6b6a3f..d8d25c2 100644
--- a/name-hash.c
+++ b/name-hash.c
@@ -57,12 +57,10 @@ static void hash_index_entry_directories(struct index_state *istate, struct cach
if (*ptr == '/') {
++ptr;
hash = hash_name(ce->name, ptr - ce->name);
- if (!lookup_hash(hash, &istate->name_hash)) {
- pos = insert_hash(hash, ce, &istate->name_hash);
- if (pos) {
- ce->next = *pos;
- *pos = ce;
- }
+ pos = insert_hash(hash, ce, &istate->name_hash);
+ if (pos) {
+ ce->dir_next = *pos;
+ *pos = ce;
}
}
}
@@ -76,7 +74,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
if (ce->ce_flags & CE_HASHED)
return;
ce->ce_flags |= CE_HASHED;
- ce->next = NULL;
+ ce->next = ce->dir_next = NULL;
hash = hash_name(ce->name, ce_namelen(ce));
pos = insert_hash(hash, ce, &istate->name_hash);
if (pos) {
@@ -166,7 +164,10 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na
if (same_name(ce, name, namelen, icase))
return ce;
}
- ce = ce->next;
+ if (icase && name[namelen - 1] == '/')
+ ce = ce->dir_next;
+ else
+ ce = ce->next;
}
/*
diff --git a/notes-cache.c b/notes-cache.c
index 4c8984e..eabe4a0 100644
--- a/notes-cache.c
+++ b/notes-cache.c
@@ -48,6 +48,7 @@ int notes_cache_write(struct notes_cache *c)
{
unsigned char tree_sha1[20];
unsigned char commit_sha1[20];
+ struct strbuf msg = STRBUF_INIT;
if (!c || !c->tree.initialized || !c->tree.ref || !*c->tree.ref)
return -1;
@@ -56,7 +57,9 @@ int notes_cache_write(struct notes_cache *c)
if (write_notes_tree(&c->tree, tree_sha1))
return -1;
- if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
+ strbuf_attach(&msg, c->validity,
+ strlen(c->validity), strlen(c->validity) + 1);
+ if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
return -1;
if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
0, QUIET_ON_ERR) < 0)
diff --git a/notes-merge.c b/notes-merge.c
index baaf31f..29c6411 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -21,14 +21,6 @@ void init_notes_merge_options(struct notes_merge_options *o)
o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT;
}
-#define OUTPUT(o, v, ...) \
- do { \
- if ((o)->verbosity >= (v)) { \
- printf(__VA_ARGS__); \
- puts(""); \
- } \
- } while (0)
-
static int path_to_sha1(const char *path, unsigned char *sha1)
{
char hex_sha1[40];
@@ -275,7 +267,8 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
* Must establish NOTES_MERGE_WORKTREE.
* Abort if NOTES_MERGE_WORKTREE already exists
*/
- if (file_exists(git_path(NOTES_MERGE_WORKTREE))) {
+ if (file_exists(git_path(NOTES_MERGE_WORKTREE)) &&
+ !is_empty_dir(git_path(NOTES_MERGE_WORKTREE))) {
if (advice_resolve_conflict)
die("You have not concluded your previous "
"notes merge (%s exists).\nPlease, use "
@@ -392,21 +385,26 @@ static int merge_one_change_manual(struct notes_merge_options *o,
strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj));
- OUTPUT(o, 2, "Auto-merging notes for %s", sha1_to_hex(p->obj));
+ if (o->verbosity >= 2)
+ printf("Auto-merging notes for %s\n", sha1_to_hex(p->obj));
check_notes_merge_worktree(o);
if (is_null_sha1(p->local)) {
/* D/F conflict, checkout p->remote */
assert(!is_null_sha1(p->remote));
- OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
- "deleted in %s and modified in %s. Version from %s "
- "left in tree.", sha1_to_hex(p->obj), lref, rref, rref);
+ if (o->verbosity >= 1)
+ printf("CONFLICT (delete/modify): Notes for object %s "
+ "deleted in %s and modified in %s. Version from %s "
+ "left in tree.\n",
+ sha1_to_hex(p->obj), lref, rref, rref);
write_note_to_worktree(p->obj, p->remote);
} else if (is_null_sha1(p->remote)) {
/* D/F conflict, checkout p->local */
assert(!is_null_sha1(p->local));
- OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
- "deleted in %s and modified in %s. Version from %s "
- "left in tree.", sha1_to_hex(p->obj), rref, lref, lref);
+ if (o->verbosity >= 1)
+ printf("CONFLICT (delete/modify): Notes for object %s "
+ "deleted in %s and modified in %s. Version from %s "
+ "left in tree.\n",
+ sha1_to_hex(p->obj), rref, lref, lref);
write_note_to_worktree(p->obj, p->local);
} else {
/* "regular" conflict, checkout result of ll_merge() */
@@ -415,8 +413,9 @@ static int merge_one_change_manual(struct notes_merge_options *o,
reason = "add/add";
assert(!is_null_sha1(p->local));
assert(!is_null_sha1(p->remote));
- OUTPUT(o, 1, "CONFLICT (%s): Merge conflict in notes for "
- "object %s", reason, sha1_to_hex(p->obj));
+ if (o->verbosity >= 1)
+ printf("CONFLICT (%s): Merge conflict in notes for "
+ "object %s\n", reason, sha1_to_hex(p->obj));
ll_merge_in_worktree(o, p);
}
@@ -438,24 +437,30 @@ static int merge_one_change(struct notes_merge_options *o,
case NOTES_MERGE_RESOLVE_MANUAL:
return merge_one_change_manual(o, p, t);
case NOTES_MERGE_RESOLVE_OURS:
- OUTPUT(o, 2, "Using local notes for %s", sha1_to_hex(p->obj));
+ if (o->verbosity >= 2)
+ printf("Using local notes for %s\n",
+ sha1_to_hex(p->obj));
/* nothing to do */
return 0;
case NOTES_MERGE_RESOLVE_THEIRS:
- OUTPUT(o, 2, "Using remote notes for %s", sha1_to_hex(p->obj));
+ if (o->verbosity >= 2)
+ printf("Using remote notes for %s\n",
+ sha1_to_hex(p->obj));
if (add_note(t, p->obj, p->remote, combine_notes_overwrite))
die("BUG: combine_notes_overwrite failed");
return 0;
case NOTES_MERGE_RESOLVE_UNION:
- OUTPUT(o, 2, "Concatenating local and remote notes for %s",
- sha1_to_hex(p->obj));
+ if (o->verbosity >= 2)
+ printf("Concatenating local and remote notes for %s\n",
+ sha1_to_hex(p->obj));
if (add_note(t, p->obj, p->remote, combine_notes_concatenate))
die("failed to concatenate notes "
"(combine_notes_concatenate)");
return 0;
case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ:
- OUTPUT(o, 2, "Concatenating unique lines in local and remote "
- "notes for %s", sha1_to_hex(p->obj));
+ if (o->verbosity >= 2)
+ printf("Concatenating unique lines in local and remote "
+ "notes for %s\n", sha1_to_hex(p->obj));
if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq))
die("failed to concatenate notes "
"(combine_notes_cat_sort_uniq)");
@@ -518,14 +523,17 @@ static int merge_from_diffs(struct notes_merge_options *o,
conflicts = merge_changes(o, changes, &num_changes, t);
free(changes);
- OUTPUT(o, 4, "Merge result: %i unmerged notes and a %s notes tree",
- conflicts, t->dirty ? "dirty" : "clean");
+ if (o->verbosity >= 4)
+ printf(t->dirty ?
+ "Merge result: %i unmerged notes and a dirty notes tree\n" :
+ "Merge result: %i unmerged notes and a clean notes tree\n",
+ conflicts);
return conflicts ? -1 : 1;
}
void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
- const char *msg, unsigned char *result_sha1)
+ const struct strbuf *msg, unsigned char *result_sha1)
{
unsigned char tree_sha1[20];
@@ -546,7 +554,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
/* else: t->ref points to nothing, assume root/orphan commit */
}
- if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+ if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
die("Failed to commit notes tree to database");
}
@@ -568,9 +576,10 @@ int notes_merge(struct notes_merge_options *o,
o->local_ref, o->remote_ref);
/* Dereference o->local_ref into local_sha1 */
- if (!resolve_ref(o->local_ref, local_sha1, 0, NULL))
+ if (read_ref_full(o->local_ref, local_sha1, 0, NULL))
die("Failed to resolve local notes ref '%s'", o->local_ref);
- else if (!check_ref_format(o->local_ref) && is_null_sha1(local_sha1))
+ else if (!check_refname_format(o->local_ref, 0) &&
+ is_null_sha1(local_sha1))
local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */
else if (!(local = lookup_commit_reference(local_sha1)))
die("Could not parse local commit %s (%s)",
@@ -583,7 +592,7 @@ int notes_merge(struct notes_merge_options *o,
* Failed to get remote_sha1. If o->remote_ref looks like an
* unborn ref, perform the merge using an empty notes tree.
*/
- if (!check_ref_format(o->remote_ref)) {
+ if (!check_refname_format(o->remote_ref, 0)) {
hashclr(remote_sha1);
remote = NULL;
} else {
@@ -616,33 +625,40 @@ int notes_merge(struct notes_merge_options *o,
if (!bases) {
base_sha1 = null_sha1;
base_tree_sha1 = EMPTY_TREE_SHA1_BIN;
- OUTPUT(o, 4, "No merge base found; doing history-less merge");
+ if (o->verbosity >= 4)
+ printf("No merge base found; doing history-less merge\n");
} else if (!bases->next) {
base_sha1 = bases->item->object.sha1;
base_tree_sha1 = bases->item->tree->object.sha1;
- OUTPUT(o, 4, "One merge base found (%.7s)",
- sha1_to_hex(base_sha1));
+ if (o->verbosity >= 4)
+ printf("One merge base found (%.7s)\n",
+ sha1_to_hex(base_sha1));
} else {
/* TODO: How to handle multiple merge-bases? */
base_sha1 = bases->item->object.sha1;
base_tree_sha1 = bases->item->tree->object.sha1;
- OUTPUT(o, 3, "Multiple merge bases found. Using the first "
- "(%.7s)", sha1_to_hex(base_sha1));
+ if (o->verbosity >= 3)
+ printf("Multiple merge bases found. Using the first "
+ "(%.7s)\n", sha1_to_hex(base_sha1));
}
- OUTPUT(o, 4, "Merging remote commit %.7s into local commit %.7s with "
- "merge-base %.7s", sha1_to_hex(remote->object.sha1),
- sha1_to_hex(local->object.sha1), sha1_to_hex(base_sha1));
+ if (o->verbosity >= 4)
+ printf("Merging remote commit %.7s into local commit %.7s with "
+ "merge-base %.7s\n", sha1_to_hex(remote->object.sha1),
+ sha1_to_hex(local->object.sha1),
+ sha1_to_hex(base_sha1));
if (!hashcmp(remote->object.sha1, base_sha1)) {
/* Already merged; result == local commit */
- OUTPUT(o, 2, "Already up-to-date!");
+ if (o->verbosity >= 2)
+ printf("Already up-to-date!\n");
hashcpy(result_sha1, local->object.sha1);
goto found_result;
}
if (!hashcmp(local->object.sha1, base_sha1)) {
/* Fast-forward; result == remote commit */
- OUTPUT(o, 2, "Fast-forward");
+ if (o->verbosity >= 2)
+ printf("Fast-forward\n");
hashcpy(result_sha1, remote->object.sha1);
goto found_result;
}
@@ -655,7 +671,7 @@ int notes_merge(struct notes_merge_options *o,
struct commit_list *parents = NULL;
commit_list_insert(remote, &parents); /* LIFO order */
commit_list_insert(local, &parents);
- create_notes_commit(local_tree, parents, o->commit_msg.buf,
+ create_notes_commit(local_tree, parents, &o->commit_msg,
result_sha1);
}
@@ -674,65 +690,87 @@ int notes_merge_commit(struct notes_merge_options *o,
{
/*
* Iterate through files in .git/NOTES_MERGE_WORKTREE and add all
- * found notes to 'partial_tree'. Write the updates notes tree to
+ * found notes to 'partial_tree'. Write the updated notes tree to
* the DB, and commit the resulting tree object while reusing the
* commit message and parents from 'partial_commit'.
* Finally store the new commit object SHA1 into 'result_sha1'.
*/
- struct dir_struct dir;
- char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/"));
- int path_len = strlen(path), i;
- const char *msg = strstr(partial_commit->buffer, "\n\n");
-
- OUTPUT(o, 3, "Committing notes in notes merge worktree at %.*s",
- path_len - 1, path);
+ DIR *dir;
+ struct dirent *e;
+ struct strbuf path = STRBUF_INIT;
+ char *msg = strstr(partial_commit->buffer, "\n\n");
+ struct strbuf sb_msg = STRBUF_INIT;
+ int baselen;
+
+ strbuf_addstr(&path, git_path(NOTES_MERGE_WORKTREE));
+ if (o->verbosity >= 3)
+ printf("Committing notes in notes merge worktree at %s\n",
+ path.buf);
if (!msg || msg[2] == '\0')
die("partial notes commit has empty message");
msg += 2;
- memset(&dir, 0, sizeof(dir));
- read_directory(&dir, path, path_len, NULL);
- for (i = 0; i < dir.nr; i++) {
- struct dir_entry *ent = dir.entries[i];
+ dir = opendir(path.buf);
+ if (!dir)
+ die_errno("could not open %s", path.buf);
+
+ strbuf_addch(&path, '/');
+ baselen = path.len;
+ while ((e = readdir(dir)) != NULL) {
struct stat st;
- const char *relpath = ent->name + path_len;
unsigned char obj_sha1[20], blob_sha1[20];
- if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) {
- OUTPUT(o, 3, "Skipping non-SHA1 entry '%s'", ent->name);
+ if (is_dot_or_dotdot(e->d_name))
+ continue;
+
+ if (strlen(e->d_name) != 40 || get_sha1_hex(e->d_name, obj_sha1)) {
+ if (o->verbosity >= 3)
+ printf("Skipping non-SHA1 entry '%s%s'\n",
+ path.buf, e->d_name);
continue;
}
+ strbuf_addstr(&path, e->d_name);
/* write file as blob, and add to partial_tree */
- if (stat(ent->name, &st))
- die_errno("Failed to stat '%s'", ent->name);
- if (index_path(blob_sha1, ent->name, &st, HASH_WRITE_OBJECT))
- die("Failed to write blob object from '%s'", ent->name);
+ if (stat(path.buf, &st))
+ die_errno("Failed to stat '%s'", path.buf);
+ if (index_path(blob_sha1, path.buf, &st, HASH_WRITE_OBJECT))
+ die("Failed to write blob object from '%s'", path.buf);
if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
die("Failed to add resolved note '%s' to notes tree",
- ent->name);
- OUTPUT(o, 4, "Added resolved note for object %s: %s",
- sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
+ path.buf);
+ if (o->verbosity >= 4)
+ printf("Added resolved note for object %s: %s\n",
+ sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
+ strbuf_setlen(&path, baselen);
}
- create_notes_commit(partial_tree, partial_commit->parents, msg,
+ strbuf_attach(&sb_msg, msg, strlen(msg), strlen(msg) + 1);
+ create_notes_commit(partial_tree, partial_commit->parents, &sb_msg,
result_sha1);
- OUTPUT(o, 4, "Finalized notes merge commit: %s",
- sha1_to_hex(result_sha1));
- free(path);
+ if (o->verbosity >= 4)
+ printf("Finalized notes merge commit: %s\n",
+ sha1_to_hex(result_sha1));
+ strbuf_release(&path);
+ closedir(dir);
return 0;
}
int notes_merge_abort(struct notes_merge_options *o)
{
- /* Remove .git/NOTES_MERGE_WORKTREE directory and all files within */
+ /*
+ * Remove all files within .git/NOTES_MERGE_WORKTREE. We do not remove
+ * the .git/NOTES_MERGE_WORKTREE directory itself, since it might be
+ * the current working directory of the user.
+ */
struct strbuf buf = STRBUF_INIT;
int ret;
strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
- OUTPUT(o, 3, "Removing notes merge worktree at %s", buf.buf);
- ret = remove_dir_recursively(&buf, 0);
+ if (o->verbosity >= 3)
+ printf("Removing notes merge worktree at %s/*\n", buf.buf);
+ ret = remove_dir_recursively(&buf, REMOVE_DIR_KEEP_TOPLEVEL);
strbuf_release(&buf);
return ret;
}
diff --git a/notes-merge.h b/notes-merge.h
index 168a672..0c11b17 100644
--- a/notes-merge.h
+++ b/notes-merge.h
@@ -37,7 +37,7 @@ void init_notes_merge_options(struct notes_merge_options *o);
* The resulting commit SHA1 is stored in result_sha1.
*/
void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
- const char *msg, unsigned char *result_sha1);
+ const struct strbuf *msg, unsigned char *result_sha1);
/*
* Merge notes from o->remote_ref into o->local_ref
diff --git a/object.c b/object.c
index 31976b5..4af3451 100644
--- a/object.c
+++ b/object.c
@@ -149,6 +149,8 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
struct tree *tree = lookup_tree(sha1);
if (tree) {
obj = &tree->object;
+ if (!tree->buffer)
+ tree->object.parsed = 0;
if (!tree->object.parsed) {
if (parse_tree_buffer(tree, buffer, size))
return NULL;
@@ -174,7 +176,7 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
obj = &tag->object;
}
} else {
- warning("object %s has unknown type id %d\n", sha1_to_hex(sha1), type);
+ warning("object %s has unknown type id %d", sha1_to_hex(sha1), type);
obj = NULL;
}
if (obj && obj->type == OBJ_NONE)
@@ -189,13 +191,29 @@ struct object *parse_object(const unsigned char *sha1)
enum object_type type;
int eaten;
const unsigned char *repl = lookup_replace_object(sha1);
- void *buffer = read_sha1_file(sha1, &type, &size);
+ void *buffer;
+ struct object *obj;
+
+ obj = lookup_object(sha1);
+ if (obj && obj->parsed)
+ return obj;
+
+ if ((obj && obj->type == OBJ_BLOB) ||
+ (!obj && has_sha1_file(sha1) &&
+ sha1_object_info(sha1, NULL) == OBJ_BLOB)) {
+ if (check_sha1_signature(repl, NULL, 0, NULL) < 0) {
+ error("sha1 mismatch %s", sha1_to_hex(repl));
+ return NULL;
+ }
+ parse_blob_buffer(lookup_blob(sha1), NULL, 0);
+ return lookup_object(sha1);
+ }
+ buffer = read_sha1_file(sha1, &type, &size);
if (buffer) {
- struct object *obj;
if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) {
free(buffer);
- error("sha1 mismatch %s\n", sha1_to_hex(repl));
+ error("sha1 mismatch %s", sha1_to_hex(repl));
return NULL;
}
@@ -268,3 +286,14 @@ void object_array_remove_duplicates(struct object_array *array)
array->nr = dst;
}
}
+
+void clear_object_flags(unsigned flags)
+{
+ int i;
+
+ for (i=0; i < obj_hash_size; i++) {
+ struct object *obj = obj_hash[i];
+ if (obj)
+ obj->flags &= ~flags;
+ }
+}
diff --git a/object.h b/object.h
index b6618d9..6a97b6b 100644
--- a/object.h
+++ b/object.h
@@ -76,4 +76,6 @@ void add_object_array(struct object *obj, const char *name, struct object_array
void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
void object_array_remove_duplicates(struct object_array *);
+void clear_object_flags(unsigned flags);
+
#endif /* OBJECT_H */
diff --git a/pack-check.c b/pack-check.c
index 0c19b6e..63a595c 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "pack.h"
#include "pack-revindex.h"
+#include "progress.h"
struct idx_entry {
off_t offset;
@@ -42,7 +43,10 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs,
}
static int verify_packfile(struct packed_git *p,
- struct pack_window **w_curs)
+ struct pack_window **w_curs,
+ verify_fn fn,
+ struct progress *progress, uint32_t base_count)
+
{
off_t index_size = p->index_size;
const unsigned char *index_base = p->index_data;
@@ -113,20 +117,25 @@ static int verify_packfile(struct packed_git *p,
p->pack_name, (uintmax_t)offset);
}
data = unpack_entry(p, entries[i].offset, &type, &size);
- if (!data) {
+ if (!data)
err = error("cannot unpack %s from %s at offset %"PRIuMAX"",
sha1_to_hex(entries[i].sha1), p->pack_name,
(uintmax_t)entries[i].offset);
- break;
- }
- if (check_sha1_signature(entries[i].sha1, data, size, typename(type))) {
+ else if (check_sha1_signature(entries[i].sha1, data, size, typename(type)))
err = error("packed %s from %s is corrupt",
sha1_to_hex(entries[i].sha1), p->pack_name);
- free(data);
- break;
+ else if (fn) {
+ int eaten = 0;
+ fn(entries[i].sha1, type, size, data, &eaten);
+ if (eaten)
+ data = NULL;
}
+ if (((base_count + i) & 1023) == 0)
+ display_progress(progress, base_count + i);
free(data);
+
}
+ display_progress(progress, base_count + i);
free(entries);
return err;
@@ -155,7 +164,8 @@ int verify_pack_index(struct packed_git *p)
return err;
}
-int verify_pack(struct packed_git *p)
+int verify_pack(struct packed_git *p, verify_fn fn,
+ struct progress *progress, uint32_t base_count)
{
int err = 0;
struct pack_window *w_curs = NULL;
@@ -164,7 +174,7 @@ int verify_pack(struct packed_git *p)
if (!p->index_data)
return -1;
- err |= verify_packfile(p, &w_curs);
+ err |= verify_packfile(p, &w_curs, fn, progress, base_count);
unuse_pack(&w_curs);
return err;
diff --git a/pack-refs.c b/pack-refs.c
index 1290570..f09a054 100644
--- a/pack-refs.c
+++ b/pack-refs.c
@@ -72,7 +72,7 @@ static void try_remove_empty_parents(char *name)
for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
while (*p && *p != '/')
p++;
- /* tolerate duplicate slashes; see check_ref_format() */
+ /* tolerate duplicate slashes; see check_refname_format() */
while (*p == '/')
p++;
}
@@ -143,7 +143,6 @@ int pack_refs(unsigned int flags)
packed.fd = -1;
if (commit_lock_file(&packed) < 0)
die_errno("unable to overwrite old ref-pack file");
- if (cbdata.flags & PACK_REFS_PRUNE)
- prune_refs(cbdata.ref_to_prune);
+ prune_refs(cbdata.ref_to_prune);
return 0;
}
diff --git a/pack-write.c b/pack-write.c
index a905ca4..ca9e63b 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -2,8 +2,12 @@
#include "pack.h"
#include "csum-file.h"
-uint32_t pack_idx_default_version = 2;
-uint32_t pack_idx_off32_limit = 0x7fffffff;
+void reset_pack_idx_option(struct pack_idx_option *opts)
+{
+ memset(opts, 0, sizeof(*opts));
+ opts->version = 2;
+ opts->off32_limit = 0x7fffffff;
+}
static int sha1_compare(const void *_a, const void *_b)
{
@@ -12,13 +16,35 @@ static int sha1_compare(const void *_a, const void *_b)
return hashcmp(a->sha1, b->sha1);
}
+static int cmp_uint32(const void *a_, const void *b_)
+{
+ uint32_t a = *((uint32_t *)a_);
+ uint32_t b = *((uint32_t *)b_);
+
+ return (a < b) ? -1 : (a != b);
+}
+
+static int need_large_offset(off_t offset, const struct pack_idx_option *opts)
+{
+ uint32_t ofsval;
+
+ if ((offset >> 31) || (opts->off32_limit < offset))
+ return 1;
+ if (!opts->anomaly_nr)
+ return 0;
+ ofsval = offset;
+ return !!bsearch(&ofsval, opts->anomaly, opts->anomaly_nr,
+ sizeof(ofsval), cmp_uint32);
+}
+
/*
* On entry *sha1 contains the pack content SHA1 hash, on exit it is
* the SHA1 hash of sorted object names. The objects array passed in
* will be sorted by SHA1 on exit.
*/
const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects,
- int nr_objects, unsigned char *sha1)
+ int nr_objects, const struct pack_idx_option *opts,
+ unsigned char *sha1)
{
struct sha1file *f;
struct pack_idx_entry **sorted_by_sha, **list, **last;
@@ -42,20 +68,25 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
else
sorted_by_sha = list = last = NULL;
- if (!index_name) {
- static char tmpfile[PATH_MAX];
- fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
- index_name = xstrdup(tmpfile);
+ if (opts->flags & WRITE_IDX_VERIFY) {
+ assert(index_name);
+ f = sha1fd_check(index_name);
} else {
- unlink(index_name);
- fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ if (!index_name) {
+ static char tmp_file[PATH_MAX];
+ fd = odb_mkstemp(tmp_file, sizeof(tmp_file), "pack/tmp_idx_XXXXXX");
+ index_name = xstrdup(tmp_file);
+ } else {
+ unlink(index_name);
+ fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ }
+ if (fd < 0)
+ die_errno("unable to create '%s'", index_name);
+ f = sha1fd(fd, index_name);
}
- if (fd < 0)
- die_errno("unable to create '%s'", index_name);
- f = sha1fd(fd, index_name);
/* if last object's offset is >= 2^31 we should use index V2 */
- index_version = (last_obj_offset >> 31) ? 2 : pack_idx_default_version;
+ index_version = need_large_offset(last_obj_offset, opts) ? 2 : opts->version;
/* index versions 2 and above need a header */
if (index_version >= 2) {
@@ -98,6 +129,10 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
}
sha1write(f, obj->sha1, 20);
git_SHA1_Update(&ctx, obj->sha1, 20);
+ if ((opts->flags & WRITE_IDX_STRICT) &&
+ (i && !hashcmp(list[-2]->sha1, obj->sha1)))
+ die("The same object %s appears twice in the pack",
+ sha1_to_hex(obj->sha1));
}
if (index_version >= 2) {
@@ -115,8 +150,11 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
list = sorted_by_sha;
for (i = 0; i < nr_objects; i++) {
struct pack_idx_entry *obj = *list++;
- uint32_t offset = (obj->offset <= pack_idx_off32_limit) ?
- obj->offset : (0x80000000 | nr_large_offset++);
+ uint32_t offset;
+
+ offset = (need_large_offset(obj->offset, opts)
+ ? (0x80000000 | nr_large_offset++)
+ : obj->offset);
offset = htonl(offset);
sha1write(f, &offset, 4);
}
@@ -126,22 +164,36 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
while (nr_large_offset) {
struct pack_idx_entry *obj = *list++;
uint64_t offset = obj->offset;
- if (offset > pack_idx_off32_limit) {
- uint32_t split[2];
- split[0] = htonl(offset >> 32);
- split[1] = htonl(offset & 0xffffffff);
- sha1write(f, split, 8);
- nr_large_offset--;
- }
+ uint32_t split[2];
+
+ if (!need_large_offset(offset, opts))
+ continue;
+ split[0] = htonl(offset >> 32);
+ split[1] = htonl(offset & 0xffffffff);
+ sha1write(f, split, 8);
+ nr_large_offset--;
}
}
sha1write(f, sha1, 20);
- sha1close(f, NULL, CSUM_FSYNC);
+ sha1close(f, NULL, ((opts->flags & WRITE_IDX_VERIFY)
+ ? CSUM_CLOSE : CSUM_FSYNC));
git_SHA1_Final(sha1, &ctx);
return index_name;
}
+off_t write_pack_header(struct sha1file *f, uint32_t nr_entries)
+{
+ struct pack_header hdr;
+
+ hdr.hdr_signature = htonl(PACK_SIGNATURE);
+ hdr.hdr_version = htonl(PACK_VERSION);
+ hdr.hdr_entries = htonl(nr_entries);
+ if (sha1write(f, &hdr, sizeof(hdr)))
+ return 0;
+ return sizeof(hdr);
+}
+
/*
* Update pack header with object_count and compute new SHA1 for pack data
* associated to pack_fd, and write that SHA1 at the end. That new SHA1
@@ -280,3 +332,44 @@ int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned
*hdr = c;
return n;
}
+
+struct sha1file *create_tmp_packfile(char **pack_tmp_name)
+{
+ char tmpname[PATH_MAX];
+ int fd;
+
+ fd = odb_mkstemp(tmpname, sizeof(tmpname), "pack/tmp_pack_XXXXXX");
+ *pack_tmp_name = xstrdup(tmpname);
+ return sha1fd(fd, *pack_tmp_name);
+}
+
+void finish_tmp_packfile(char *name_buffer,
+ const char *pack_tmp_name,
+ struct pack_idx_entry **written_list,
+ uint32_t nr_written,
+ struct pack_idx_option *pack_idx_opts,
+ unsigned char sha1[])
+{
+ const char *idx_tmp_name;
+ char *end_of_name_prefix = strrchr(name_buffer, 0);
+
+ if (adjust_shared_perm(pack_tmp_name))
+ die_errno("unable to make temporary pack file readable");
+
+ idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
+ pack_idx_opts, sha1);
+ if (adjust_shared_perm(idx_tmp_name))
+ die_errno("unable to make temporary index file readable");
+
+ sprintf(end_of_name_prefix, "%s.pack", sha1_to_hex(sha1));
+ free_pack_by_name(name_buffer);
+
+ if (rename(pack_tmp_name, name_buffer))
+ die_errno("unable to rename temporary pack file");
+
+ sprintf(end_of_name_prefix, "%s.idx", sha1_to_hex(sha1));
+ if (rename(idx_tmp_name, name_buffer))
+ die_errno("unable to rename temporary index file");
+
+ free((void *)idx_tmp_name);
+}
diff --git a/pack.h b/pack.h
index bb27576..aa6ee7d 100644
--- a/pack.h
+++ b/pack.h
@@ -2,6 +2,7 @@
#define PACK_H
#include "object.h"
+#include "csum-file.h"
/*
* Packed object header
@@ -34,9 +35,25 @@ struct pack_header {
*/
#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */
-/* These may be overridden by command-line parameters */
-extern uint32_t pack_idx_default_version;
-extern uint32_t pack_idx_off32_limit;
+struct pack_idx_option {
+ unsigned flags;
+ /* flag bits */
+#define WRITE_IDX_VERIFY 01 /* verify only, do not write the idx file */
+#define WRITE_IDX_STRICT 02
+
+ uint32_t version;
+ uint32_t off32_limit;
+
+ /*
+ * List of offsets that would fit within off32_limit but
+ * need to be written out as 64-bit entity for byte-for-byte
+ * verification.
+ */
+ int anomaly_alloc, anomaly_nr;
+ uint32_t *anomaly;
+};
+
+extern void reset_pack_idx_option(struct pack_idx_option *);
/*
* Packed object index header
@@ -55,10 +72,15 @@ 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);
+
+struct progress;
+typedef int (*verify_fn)(const unsigned char*, enum object_type, unsigned long, void*, int*);
+
+extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, unsigned char *sha1);
extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
extern int verify_pack_index(struct packed_git *);
-extern int verify_pack(struct packed_git *);
+extern int verify_pack(struct packed_git *, verify_fn fn, struct progress *, uint32_t);
+extern off_t write_pack_header(struct sha1file *f, uint32_t);
extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
extern char *index_pack_lockfile(int fd);
extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned char *);
@@ -67,4 +89,8 @@ extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned ch
#define PH_ERROR_PACK_SIGNATURE (-2)
#define PH_ERROR_PROTOCOL (-3)
extern int read_pack_header(int fd, struct pack_header *);
+
+extern struct sha1file *create_tmp_packfile(char **pack_tmp_name);
+extern void finish_tmp_packfile(char *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, uint32_t nr_written, struct pack_idx_option *pack_idx_opts, unsigned char sha1[]);
+
#endif
diff --git a/pager.c b/pager.c
index dac358f..4dcb08d 100644
--- a/pager.c
+++ b/pager.c
@@ -11,8 +11,6 @@
* something different on Windows.
*/
-static int spawned_pager;
-
#ifndef WIN32
static void pager_preexec(void)
{
@@ -75,10 +73,16 @@ void setup_pager(void)
{
const char *pager = git_pager(isatty(1));
- if (!pager)
+ if (!pager || pager_in_use())
return;
- spawned_pager = 1; /* means we are emitting to terminal */
+ /*
+ * force computing the width of the terminal before we redirect
+ * the standard output to the pager.
+ */
+ (void) term_columns();
+
+ setenv("GIT_PAGER_IN_USE", "true", 1);
/* spawn the pager */
pager_argv[0] = pager;
@@ -109,10 +113,49 @@ void setup_pager(void)
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;
}
+
+/*
+ * Return cached value (if set) or $COLUMNS environment variable (if
+ * set and positive) or ioctl(1, TIOCGWINSZ).ws_col (if positive),
+ * and default to 80 if all else fails.
+ */
+int term_columns(void)
+{
+ static int term_columns_at_startup;
+
+ char *col_string;
+ int n_cols;
+
+ if (term_columns_at_startup)
+ return term_columns_at_startup;
+
+ term_columns_at_startup = 80;
+
+ col_string = getenv("COLUMNS");
+ if (col_string && (n_cols = atoi(col_string)) > 0)
+ term_columns_at_startup = n_cols;
+#ifdef TIOCGWINSZ
+ else {
+ struct winsize ws;
+ if (!ioctl(1, TIOCGWINSZ, &ws) && ws.ws_col)
+ term_columns_at_startup = ws.ws_col;
+ }
+#endif
+
+ return term_columns_at_startup;
+}
+
+/*
+ * How many columns do we need to show this number in decimal?
+ */
+int decimal_width(int number)
+{
+ int i, width;
+
+ for (width = 1, i = 10; i <= number; width++)
+ i *= 10;
+ return width;
+}
diff --git a/parse-options-cb.c b/parse-options-cb.c
new file mode 100644
index 0000000..0de5fb1
--- /dev/null
+++ b/parse-options-cb.c
@@ -0,0 +1,130 @@
+#include "git-compat-util.h"
+#include "parse-options.h"
+#include "cache.h"
+#include "commit.h"
+#include "color.h"
+#include "string-list.h"
+
+/*----- some often used options -----*/
+
+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;
+}
+
+int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
+ int unset)
+{
+ *(unsigned long *)(opt->value) = approxidate(arg);
+ return 0;
+}
+
+int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
+ int unset)
+{
+ int value;
+
+ if (!arg)
+ arg = unset ? "never" : (const char *)opt->defval;
+ value = git_config_colorbool(NULL, arg);
+ if (value < 0)
+ return opterror(opt,
+ "expects \"always\", \"auto\", or \"never\"", 0);
+ *(int *)opt->value = value;
+ return 0;
+}
+
+int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
+ int unset)
+{
+ int *target = opt->value;
+
+ if (unset)
+ /* --no-quiet, --no-verbose */
+ *target = 0;
+ else if (opt->short_name == 'v') {
+ if (*target >= 0)
+ (*target)++;
+ else
+ *target = 1;
+ } else {
+ if (*target <= 0)
+ (*target)--;
+ else
+ *target = -1;
+ }
+ return 0;
+}
+
+int parse_opt_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))
+ return error("malformed object name %s", arg);
+ commit = lookup_commit_reference(sha1);
+ if (!commit)
+ return error("no such commit %s", arg);
+ commit_list_insert(commit, opt->value);
+ return 0;
+}
+
+int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
+{
+ int *target = opt->value;
+ *target = unset ? 2 : 1;
+ return 0;
+}
+
+int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
+{
+ int i, j;
+
+ for (i = 0; i < dst_size; i++)
+ if (dst[i].type == OPTION_END)
+ break;
+ for (j = 0; i < dst_size; i++, j++) {
+ dst[i] = src[j];
+ if (src[j].type == OPTION_END)
+ return 0;
+ }
+ return -1;
+}
+
+int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
+{
+ struct string_list *v = opt->value;
+
+ if (unset) {
+ string_list_clear(v, 0);
+ return 0;
+ }
+
+ if (!arg)
+ return -1;
+
+ string_list_append(v, xstrdup(arg));
+ return 0;
+}
+
+int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset)
+{
+ return 0;
+}
diff --git a/parse-options.c b/parse-options.c
index 73bd28a..ab70c29 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -11,14 +11,14 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx,
#define OPT_SHORT 1
#define OPT_UNSET 2
-static int optbug(const struct option *opt, const char *reason)
+int optbug(const struct option *opt, const char *reason)
{
if (opt->long_name)
return error("BUG: option '%s' %s", opt->long_name, reason);
return error("BUG: switch '%c' %s", opt->short_name, reason);
}
-static int opterror(const struct option *opt, const char *reason, int flags)
+int opterror(const struct option *opt, const char *reason, int flags)
{
if (flags & OPT_SHORT)
return error("switch `%c' %s", opt->short_name, reason);
@@ -83,7 +83,7 @@ static int get_value(struct parse_opt_ctx_t *p,
*(int *)opt->value &= ~opt->defval;
return 0;
- case OPTION_BOOLEAN:
+ case OPTION_COUNTUP:
*(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
return 0;
@@ -193,13 +193,14 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
arg_end = arg + strlen(arg);
for (; options->type != OPTION_END; options++) {
- const char *rest;
- int flags = 0;
+ const char *rest, *long_name = options->long_name;
+ int flags = 0, opt_flags = 0;
- if (!options->long_name)
+ if (!long_name)
continue;
- rest = skip_prefix(arg, options->long_name);
+again:
+ rest = skip_prefix(arg, long_name);
if (options->type == OPTION_ARGUMENT) {
if (!rest)
continue;
@@ -212,7 +213,7 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
}
if (!rest) {
/* abbreviated? */
- if (!strncmp(options->long_name, arg, arg_end - arg)) {
+ if (!strncmp(long_name, arg, arg_end - arg)) {
is_abbreviated:
if (abbrev_option) {
/*
@@ -227,7 +228,7 @@ is_abbreviated:
if (!(flags & OPT_UNSET) && *arg_end)
p->opt = arg_end + 1;
abbrev_option = options;
- abbrev_flags = flags;
+ abbrev_flags = flags ^ opt_flags;
continue;
}
/* negation allowed? */
@@ -239,12 +240,18 @@ is_abbreviated:
goto is_abbreviated;
}
/* negated? */
- if (strncmp(arg, "no-", 3))
+ if (prefixcmp(arg, "no-")) {
+ if (!prefixcmp(long_name, "no-")) {
+ long_name += 3;
+ opt_flags |= OPT_UNSET;
+ goto again;
+ }
continue;
+ }
flags |= OPT_UNSET;
- rest = skip_prefix(arg + 3, options->long_name);
+ rest = skip_prefix(arg + 3, long_name);
/* abbreviated and negated? */
- if (!rest && !prefixcmp(options->long_name, arg + 3))
+ if (!rest && !prefixcmp(long_name, arg + 3))
goto is_abbreviated;
if (!rest)
continue;
@@ -254,7 +261,7 @@ is_abbreviated:
continue;
p->opt = rest + 1;
}
- return get_value(p, options, flags);
+ return get_value(p, options, flags ^ opt_flags);
}
if (ambiguous_option)
@@ -319,7 +326,7 @@ static void parse_options_check(const struct option *opts)
err |= optbug(opts, "uses feature "
"not supported for dashless options");
switch (opts->type) {
- case OPTION_BOOLEAN:
+ case OPTION_COUNTUP:
case OPTION_BIT:
case OPTION_NEGBIT:
case OPTION_SET_INT:
@@ -386,6 +393,8 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
case -1:
return parse_options_usage(ctx, usagestr, options, 1);
case -2:
+ if (ctx->opt)
+ check_typos(arg + 1, options);
goto unknown;
}
if (ctx->opt)
@@ -481,7 +490,7 @@ static int usage_argh(const struct option *opts, FILE *outfile)
s = literal ? "[%s]" : "[<%s>]";
else
s = literal ? " %s" : " <%s>";
- return fprintf(outfile, s, opts->argh ? opts->argh : "...");
+ return fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
}
#define USAGE_OPTS_WIDTH 24
@@ -499,13 +508,16 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
fprintf(outfile, "cat <<\\EOF\n");
- fprintf(outfile, "usage: %s\n", *usagestr++);
+ fprintf_ln(outfile, _("usage: %s"), _(*usagestr++));
while (*usagestr && **usagestr)
- fprintf(outfile, " or: %s\n", *usagestr++);
+ /* TRANSLATORS: the colon here should align with the
+ one in "usage: %s" translation */
+ fprintf_ln(outfile, _(" or: %s"), _(*usagestr++));
while (*usagestr) {
- fprintf(outfile, "%s%s\n",
- **usagestr ? " " : "",
- *usagestr);
+ if (**usagestr)
+ fprintf_ln(outfile, _(" %s"), _(*usagestr));
+ else
+ putchar('\n');
usagestr++;
}
@@ -519,14 +531,14 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
if (*opts->help)
- fprintf(outfile, "%s\n", opts->help);
+ fprintf(outfile, "%s\n", _(opts->help));
continue;
}
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
continue;
pos = fprintf(outfile, " ");
- if (opts->short_name && !(opts->flags & PARSE_OPT_NEGHELP)) {
+ if (opts->short_name) {
if (opts->flags & PARSE_OPT_NODASH)
pos += fprintf(outfile, "%c", opts->short_name);
else
@@ -535,9 +547,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
if (opts->long_name && opts->short_name)
pos += fprintf(outfile, ", ");
if (opts->long_name)
- pos += fprintf(outfile, "--%s%s",
- (opts->flags & PARSE_OPT_NEGHELP) ? "no-" : "",
- opts->long_name);
+ pos += fprintf(outfile, "--%s", opts->long_name);
if (opts->type == OPTION_NUMBER)
pos += fprintf(outfile, "-NUM");
@@ -551,7 +561,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
fputc('\n', outfile);
pad = USAGE_OPTS_WIDTH;
}
- fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
+ fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", _(opts->help));
}
fputc('\n', outfile);
@@ -583,107 +593,3 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx,
return usage_with_options_internal(ctx, usagestr, opts, 0, err);
}
-
-/*----- 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;
-}
-
-int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
- int unset)
-{
- *(unsigned long *)(opt->value) = approxidate(arg);
- return 0;
-}
-
-int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
- int unset)
-{
- int value;
-
- if (!arg)
- arg = unset ? "never" : (const char *)opt->defval;
- value = git_config_colorbool(NULL, arg, -1);
- if (value < 0)
- return opterror(opt,
- "expects \"always\", \"auto\", or \"never\"", 0);
- *(int *)opt->value = value;
- return 0;
-}
-
-int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
- int unset)
-{
- int *target = opt->value;
-
- if (unset)
- /* --no-quiet, --no-verbose */
- *target = 0;
- else if (opt->short_name == 'v') {
- if (*target >= 0)
- (*target)++;
- else
- *target = 1;
- } else {
- if (*target <= 0)
- (*target)--;
- else
- *target = -1;
- }
- return 0;
-}
-
-int parse_opt_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))
- return error("malformed object name %s", arg);
- commit = lookup_commit_reference(sha1);
- if (!commit)
- return error("no such commit %s", arg);
- commit_list_insert(commit, opt->value);
- return 0;
-}
-
-int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
-{
- int *target = opt->value;
- *target = unset ? 2 : 1;
- return 0;
-}
-
-int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
-{
- int i, j;
-
- for (i = 0; i < dst_size; i++)
- if (dst[i].type == OPTION_END)
- break;
- for (j = 0; i < dst_size; i++, j++) {
- dst[i] = src[j];
- if (src[j].type == OPTION_END)
- return 0;
- }
- return -1;
-}
diff --git a/parse-options.h b/parse-options.h
index d1b12fe..77a4a8b 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -10,7 +10,7 @@ enum parse_opt_type {
/* options with no arguments */
OPTION_BIT,
OPTION_NEGBIT,
- OPTION_BOOLEAN, /* _INCR would have been a better name */
+ OPTION_COUNTUP,
OPTION_SET_INT,
OPTION_SET_PTR,
/* options with arguments (usually) */
@@ -21,6 +21,9 @@ enum parse_opt_type {
OPTION_FILENAME
};
+/* Deprecated synonym */
+#define OPTION_BOOLEAN OPTION_COUNTUP
+
enum parse_opt_flags {
PARSE_OPT_KEEP_DASHDASH = 1,
PARSE_OPT_STOP_AT_NON_OPTION = 2,
@@ -37,7 +40,6 @@ enum parse_opt_option_flags {
PARSE_OPT_LASTARG_DEFAULT = 16,
PARSE_OPT_NODASH = 32,
PARSE_OPT_LITERAL_ARGHELP = 64,
- PARSE_OPT_NEGHELP = 128,
PARSE_OPT_SHELL_EVAL = 256
};
@@ -64,12 +66,14 @@ typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
*
* `argh`::
* token to explain the kind of argument this option wants. Keep it
- * homogeneous across the repository.
+ * homogeneous across the repository. Should be wrapped by N_() for
+ * translation.
*
* `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.
+ * Should be wrapped by N_() for translation.
*
* `flags`::
* mask of parse_opt_option_flags.
@@ -87,9 +91,6 @@ typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
* PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
* (i.e. '<argh>') in the help message.
* Useful for options with multiple parameters.
- * PARSE_OPT_NEGHELP: says that the long option should always be shown with
- * the --no prefix in the usage message. Sometimes
- * useful for users of OPTION_NEGBIT.
*
* `callback`::
* pointer to the callback to use for OPTION_CALLBACK or
@@ -122,18 +123,22 @@ struct option {
PARSE_OPT_NOARG, NULL, (b) }
#define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG, NULL, (b) }
-#define OPT_BOOLEAN(s, l, v, h) { OPTION_BOOLEAN, (s), (l), (v), NULL, \
+#define OPT_COUNTUP(s, l, v, h) { OPTION_COUNTUP, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG }
#define OPT_SET_INT(s, l, v, h, i) { OPTION_SET_INT, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG, NULL, (i) }
+#define OPT_BOOL(s, l, v, h) OPT_SET_INT(s, l, v, h, 1)
#define OPT_SET_PTR(s, l, v, h, p) { OPTION_SET_PTR, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG, NULL, (p) }
-#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) }
+#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), N_("n"), (h) }
#define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) }
+#define OPT_STRING_LIST(s, l, v, a, h) \
+ { OPTION_CALLBACK, (s), (l), (v), (a), \
+ (h), 0, &parse_opt_string_list }
#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG, &parse_opt_tertiary }
#define OPT_DATE(s, l, v, h) \
- { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \
+ { OPTION_CALLBACK, (s), (l), (v), N_("time"),(h), 0, \
parse_opt_approxidate_cb }
#define OPT_CALLBACK(s, l, v, a, h, f) \
{ OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
@@ -141,14 +146,22 @@ struct option {
{ OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
#define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \
- "file", (h) }
+ N_("file"), (h) }
#define OPT_COLOR_FLAG(s, l, v, h) \
- { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
+ { OPTION_CALLBACK, (s), (l), (v), N_("when"), (h), PARSE_OPT_OPTARG, \
parse_opt_color_flag_cb, (intptr_t)"always" }
+#define OPT_NOOP_NOARG(s, l) \
+ { OPTION_CALLBACK, (s), (l), NULL, NULL, \
+ N_("no-op (backward compatibility)"), \
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb }
+
+/* Deprecated synonym */
+#define OPT_BOOLEAN OPT_COUNTUP
/* parse_options() will filter out the processed options and leave the
- * non-option arguments in argv[].
+ * non-option arguments in argv[]. usagestr strings should be marked
+ * for translation with N_().
* Returns the number of arguments left in argv[].
*/
extern int parse_options(int argc, const char **argv, const char *prefix,
@@ -162,6 +175,8 @@ extern NORETURN void usage_msg_opt(const char *msg,
const char * const *usagestr,
const struct option *options);
+extern int optbug(const struct option *opt, const char *reason);
+extern int opterror(const struct option *opt, const char *reason, int flags);
/*----- incremental advanced APIs -----*/
enum {
@@ -204,21 +219,25 @@ extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
extern int parse_opt_with_commit(const struct option *, const char *, int);
extern int parse_opt_tertiary(const struct option *, const char *, int);
+extern int parse_opt_string_list(const struct option *, const char *, int);
+extern int parse_opt_noop_cb(const struct option *, const char *, int);
#define OPT__VERBOSE(var, h) OPT_BOOLEAN('v', "verbose", (var), (h))
#define OPT__QUIET(var, h) OPT_BOOLEAN('q', "quiet", (var), (h))
#define OPT__VERBOSITY(var) \
- { OPTION_CALLBACK, 'v', "verbose", (var), NULL, "be more verbose", \
+ { OPTION_CALLBACK, 'v', "verbose", (var), NULL, N_("be more verbose"), \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
- { OPTION_CALLBACK, 'q', "quiet", (var), NULL, "be more quiet", \
+ { OPTION_CALLBACK, 'q', "quiet", (var), NULL, N_("be more quiet"), \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
#define OPT__DRY_RUN(var, h) OPT_BOOLEAN('n', "dry-run", (var), (h))
#define OPT__FORCE(var, h) OPT_BOOLEAN('f', "force", (var), (h))
#define OPT__ABBREV(var) \
- { OPTION_CALLBACK, 0, "abbrev", (var), "n", \
- "use <n> digits to display SHA-1s", \
+ { OPTION_CALLBACK, 0, "abbrev", (var), N_("n"), \
+ N_("use <n> digits to display SHA-1s"), \
PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
#define OPT__COLOR(var, h) \
OPT_COLOR_FLAG(0, "color", (var), (h))
+#define OPT_COLUMN(s, l, v, h) \
+ { OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback }
#endif
diff --git a/path.c b/path.c
index 6f3f5d5..66acd24 100644
--- a/path.c
+++ b/path.c
@@ -87,6 +87,21 @@ char *git_pathdup(const char *fmt, ...)
return xstrdup(path);
}
+char *mkpathdup(const char *fmt, ...)
+{
+ char *path;
+ struct strbuf sb = STRBUF_INIT;
+ va_list args;
+
+ va_start(args, fmt);
+ strbuf_vaddf(&sb, fmt, args);
+ va_end(args);
+ path = xstrdup(cleanup_path(sb.buf));
+
+ strbuf_release(&sb);
+ return path;
+}
+
char *mkpath(const char *fmt, ...)
{
va_list args;
@@ -122,6 +137,32 @@ char *git_path(const char *fmt, ...)
return cleanup_path(pathname);
}
+void home_config_paths(char **global, char **xdg, char *file)
+{
+ char *xdg_home = getenv("XDG_CONFIG_HOME");
+ char *home = getenv("HOME");
+ char *to_free = NULL;
+
+ if (!home) {
+ if (global)
+ *global = NULL;
+ } else {
+ if (!xdg_home) {
+ to_free = mkpathdup("%s/.config", home);
+ xdg_home = to_free;
+ }
+ if (global)
+ *global = mkpathdup("%s/.gitconfig", home);
+ }
+
+ if (!xdg_home)
+ *xdg = NULL;
+ else
+ *xdg = mkpathdup("%s/git/%s", xdg_home, file);
+
+ free(to_free);
+}
+
char *git_path_submodule(const char *path, const char *fmt, ...)
{
char *pathname = get_pathname();
@@ -283,7 +324,7 @@ return_null:
* links. User relative paths are also returned as they are given,
* except DWIM suffixing.
*/
-char *enter_repo(char *path, int strict)
+const char *enter_repo(const char *path, int strict)
{
static char used_path[PATH_MAX];
static char validated_path[PATH_MAX];
@@ -293,18 +334,21 @@ char *enter_repo(char *path, int strict)
if (!strict) {
static const char *suffix[] = {
- ".git/.git", "/.git", ".git", "", NULL,
+ "/.git", "", ".git/.git", ".git", NULL,
};
+ const char *gitfile;
int len = strlen(path);
int i;
- while ((1 < len) && (path[len-1] == '/')) {
- path[len-1] = 0;
+ while ((1 < len) && (path[len-1] == '/'))
len--;
- }
+
if (PATH_MAX <= len)
return NULL;
- if (path[0] == '~') {
- char *newpath = expand_user_path(path);
+ strncpy(used_path, path, len); used_path[len] = 0 ;
+ strcpy(validated_path, used_path);
+
+ if (used_path[0] == '~') {
+ char *newpath = expand_user_path(used_path);
if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
free(newpath);
return NULL;
@@ -316,24 +360,26 @@ char *enter_repo(char *path, int strict)
* anyway.
*/
strcpy(used_path, newpath); free(newpath);
- strcpy(validated_path, path);
- path = used_path;
}
else if (PATH_MAX - 10 < len)
return NULL;
- else {
- path = strcpy(used_path, path);
- strcpy(validated_path, path);
- }
- len = strlen(path);
+ len = strlen(used_path);
for (i = 0; suffix[i]; i++) {
- strcpy(path + len, suffix[i]);
- if (!access(path, F_OK)) {
+ struct stat st;
+ strcpy(used_path + len, suffix[i]);
+ if (!stat(used_path, &st) &&
+ (S_ISREG(st.st_mode) ||
+ (S_ISDIR(st.st_mode) && is_git_directory(used_path)))) {
strcat(validated_path, suffix[i]);
break;
}
}
- if (!suffix[i] || chdir(path))
+ if (!suffix[i])
+ return NULL;
+ gitfile = read_gitfile(used_path) ;
+ if (gitfile)
+ strcpy(used_path, gitfile);
+ if (chdir(used_path))
return NULL;
path = validated_path;
}
diff --git a/perl/Git.pm b/perl/Git.pm
index a86ab70..497f420 100644
--- a/perl/Git.pm
+++ b/perl/Git.pm
@@ -570,30 +570,10 @@ does. In scalar context requires the variable to be set only one time
(exception is thrown otherwise), in array context returns allows the
variable to be set multiple times and returns all the values.
-This currently wraps command('config') so it is not so fast.
-
=cut
sub config {
- my ($self, $var) = _maybe_self(@_);
-
- try {
- my @cmd = ('config');
- unshift @cmd, $self if $self;
- if (wantarray) {
- return command(@cmd, '--get-all', $var);
- } else {
- return command_oneline(@cmd, '--get', $var);
- }
- } catch Git::Error::Command with {
- my $E = shift;
- if ($E->value() == 1) {
- # Key not found.
- return;
- } else {
- throw $E;
- }
- };
+ return _config_common({}, @_);
}
@@ -603,30 +583,33 @@ Retrieve the bool configuration C<VARIABLE>. The return value
is usable as a boolean in perl (and C<undef> if it's not defined,
of course).
-This currently wraps command('config') so it is not so fast.
-
=cut
sub config_bool {
- my ($self, $var) = _maybe_self(@_);
+ my $val = scalar _config_common({'kind' => '--bool'}, @_);
- try {
- my @cmd = ('config', '--bool', '--get', $var);
- unshift @cmd, $self if $self;
- my $val = command_oneline(@cmd);
- return undef unless defined $val;
+ # Do not rewrite this as return (defined $val && $val eq 'true')
+ # as some callers do care what kind of falsehood they receive.
+ if (!defined $val) {
+ return undef;
+ } else {
return $val eq 'true';
- } catch Git::Error::Command with {
- my $E = shift;
- if ($E->value() == 1) {
- # Key not found.
- return undef;
- } else {
- throw $E;
- }
- };
+ }
}
+
+=item config_path ( VARIABLE )
+
+Retrieve the path configuration C<VARIABLE>. The return value
+is an expanded path or C<undef> if it's not defined.
+
+=cut
+
+sub config_path {
+ return _config_common({'kind' => '--path'}, @_);
+}
+
+
=item config_int ( VARIABLE )
Retrieve the integer configuration C<VARIABLE>. The return value
@@ -635,22 +618,31 @@ 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,
-This currently wraps command('config') so it is not so fast.
-
=cut
sub config_int {
+ return scalar _config_common({'kind' => '--int'}, @_);
+}
+
+# Common subroutine to implement bulk of what the config* family of methods
+# do. This curently wraps command('config') so it is not so fast.
+sub _config_common {
+ my ($opts) = shift @_;
my ($self, $var) = _maybe_self(@_);
try {
- my @cmd = ('config', '--int', '--get', $var);
+ my @cmd = ('config', $opts->{'kind'} ? $opts->{'kind'} : ());
unshift @cmd, $self if $self;
- return command_oneline(@cmd);
+ if (wantarray) {
+ return command(@cmd, '--get-all', $var);
+ } else {
+ return command_oneline(@cmd, '--get', $var);
+ }
} catch Git::Error::Command with {
my $E = shift;
if ($E->value() == 1) {
# Key not found.
- return undef;
+ return;
} else {
throw $E;
}
@@ -699,7 +691,7 @@ The hash is in the format C<refname =\> hash>. For tags, the C<refname> entry
contains the tag object while a C<refname^{}> entry gives the tagged objects.
C<REPOSITORY> has the same meaning as the appropriate C<git-ls-remote>
-argument; either an URL or a remote name (if called on a repository instance).
+argument; either a URL or a remote name (if called on a repository instance).
C<GROUPS> is an optional arrayref that can contain 'tags' to return all the
tags and/or 'heads' to return all the heads. C<REFGLOB> is an optional array
of strings containing a shell-like glob to further limit the refs returned in
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
new file mode 100644
index 0000000..40dd897
--- /dev/null
+++ b/perl/Git/I18N.pm
@@ -0,0 +1,98 @@
+package Git::I18N;
+use 5.008;
+use strict;
+use warnings;
+BEGIN {
+ require Exporter;
+ if ($] < 5.008003) {
+ *import = \&Exporter::import;
+ } else {
+ # Exporter 5.57 which supports this invocation was
+ # released with perl 5.8.3
+ Exporter->import('import');
+ }
+}
+
+our @EXPORT = qw(__);
+our @EXPORT_OK = @EXPORT;
+
+sub __bootstrap_locale_messages {
+ our $TEXTDOMAIN = 'git';
+ our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';
+
+ require POSIX;
+ POSIX->import(qw(setlocale));
+ # Non-core prerequisite module
+ require Locale::Messages;
+ Locale::Messages->import(qw(:locale_h :libintl_h));
+
+ setlocale(LC_MESSAGES(), '');
+ setlocale(LC_CTYPE(), '');
+ textdomain($TEXTDOMAIN);
+ bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);
+
+ return;
+}
+
+BEGIN
+{
+ # Used by our test script to see if it should test fallbacks or
+ # not.
+ our $__HAS_LIBRARY = 1;
+
+ local $@;
+ eval {
+ __bootstrap_locale_messages();
+ *__ = \&Locale::Messages::gettext;
+ 1;
+ } or do {
+ # Tell test.pl that we couldn't load the gettext library.
+ $Git::I18N::__HAS_LIBRARY = 0;
+
+ # Just a fall-through no-op
+ *__ = sub ($) { $_[0] };
+ };
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Git::I18N - Perl interface to Git's Gettext localizations
+
+=head1 SYNOPSIS
+
+ use Git::I18N;
+
+ print __("Welcome to Git!\n");
+
+ printf __("The following error occured: %s\n"), $error;
+
+=head1 DESCRIPTION
+
+Git's internal Perl interface to gettext via L<Locale::Messages>. If
+L<Locale::Messages> can't be loaded (it's not a core module) we
+provide stub passthrough fallbacks.
+
+This is a distilled interface to gettext, see C<info '(gettext)Perl'>
+for the full interface. This module implements only a small part of
+it.
+
+=head1 FUNCTIONS
+
+=head2 __($)
+
+L<Locale::Messages>'s gettext function if all goes well, otherwise our
+passthrough fallback function.
+
+=head1 AUTHOR
+
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=cut
diff --git a/perl/Git/SVN/Editor.pm b/perl/Git/SVN/Editor.pm
new file mode 100644
index 0000000..755092f
--- /dev/null
+++ b/perl/Git/SVN/Editor.pm
@@ -0,0 +1,536 @@
+package Git::SVN::Editor;
+use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
+use strict;
+use warnings;
+use SVN::Core;
+use SVN::Delta;
+use Carp qw/croak/;
+use IO::File;
+use Git qw/command command_oneline command_noisy command_output_pipe
+ command_input_pipe command_close_pipe
+ command_bidi_pipe command_close_bidi_pipe/;
+BEGIN {
+ @ISA = qw(SVN::Delta::Editor);
+}
+
+sub new {
+ my ($class, $opts) = @_;
+ foreach (qw/svn_path r ra tree_a tree_b log editor_cb/) {
+ die "$_ required!\n" unless (defined $opts->{$_});
+ }
+
+ my $pool = SVN::Pool->new;
+ my $mods = generate_diff($opts->{tree_a}, $opts->{tree_b});
+ my $types = check_diff_paths($opts->{ra}, $opts->{svn_path},
+ $opts->{r}, $mods);
+
+ # $opts->{ra} functions should not be used after this:
+ my @ce = $opts->{ra}->get_commit_editor($opts->{log},
+ $opts->{editor_cb}, $pool);
+ my $self = SVN::Delta::Editor->new(@ce, $pool);
+ bless $self, $class;
+ foreach (qw/svn_path r tree_a tree_b/) {
+ $self->{$_} = $opts->{$_};
+ }
+ $self->{url} = $opts->{ra}->{url};
+ $self->{mods} = $mods;
+ $self->{types} = $types;
+ $self->{pool} = $pool;
+ $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };
+ $self->{rm} = { };
+ $self->{path_prefix} = length $self->{svn_path} ?
+ "$self->{svn_path}/" : '';
+ $self->{config} = $opts->{config};
+ $self->{mergeinfo} = $opts->{mergeinfo};
+ return $self;
+}
+
+sub generate_diff {
+ my ($tree_a, $tree_b) = @_;
+ my @diff_tree = qw(diff-tree -z -r);
+ if ($_cp_similarity) {
+ push @diff_tree, "-C$_cp_similarity";
+ } else {
+ push @diff_tree, '-C';
+ }
+ push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+ push @diff_tree, "-l$_rename_limit" if defined $_rename_limit;
+ push @diff_tree, $tree_a, $tree_b;
+ my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
+ local $/ = "\0";
+ my $state = 'meta';
+ my @mods;
+ while (<$diff_fh>) {
+ chomp $_; # this gets rid of the trailing "\0"
+ if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
+ ($::sha1)\s($::sha1)\s
+ ([MTCRAD])\d*$/xo) {
+ push @mods, { mode_a => $1, mode_b => $2,
+ sha1_a => $3, sha1_b => $4,
+ chg => $5 };
+ if ($5 =~ /^(?:C|R)$/) {
+ $state = 'file_a';
+ } else {
+ $state = 'file_b';
+ }
+ } elsif ($state eq 'file_a') {
+ my $x = $mods[$#mods] or croak "Empty array\n";
+ if ($x->{chg} !~ /^(?:C|R)$/) {
+ croak "Error parsing $_, $x->{chg}\n";
+ }
+ $x->{file_a} = $_;
+ $state = 'file_b';
+ } elsif ($state eq 'file_b') {
+ my $x = $mods[$#mods] or croak "Empty array\n";
+ if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
+ croak "Error parsing $_, $x->{chg}\n";
+ }
+ if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
+ croak "Error parsing $_, $x->{chg}\n";
+ }
+ $x->{file_b} = $_;
+ $state = 'meta';
+ } else {
+ croak "Error parsing $_\n";
+ }
+ }
+ command_close_pipe($diff_fh, $ctx);
+ \@mods;
+}
+
+sub check_diff_paths {
+ my ($ra, $pfx, $rev, $mods) = @_;
+ my %types;
+ $pfx .= '/' if length $pfx;
+
+ sub type_diff_paths {
+ my ($ra, $types, $path, $rev) = @_;
+ my @p = split m#/+#, $path;
+ my $c = shift @p;
+ unless (defined $types->{$c}) {
+ $types->{$c} = $ra->check_path($c, $rev);
+ }
+ while (@p) {
+ $c .= '/' . shift @p;
+ next if defined $types->{$c};
+ $types->{$c} = $ra->check_path($c, $rev);
+ }
+ }
+
+ foreach my $m (@$mods) {
+ foreach my $f (qw/file_a file_b/) {
+ next unless defined $m->{$f};
+ my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#);
+ if (length $pfx.$dir && ! defined $types{$dir}) {
+ type_diff_paths($ra, \%types, $pfx.$dir, $rev);
+ }
+ }
+ }
+ \%types;
+}
+
+sub split_path {
+ return ($_[0] =~ m#^(.*?)/?([^/]+)$#);
+}
+
+sub repo_path {
+ my ($self, $path) = @_;
+ if (my $enc = $self->{pathnameencoding}) {
+ require Encode;
+ Encode::from_to($path, $enc, 'UTF-8');
+ }
+ $self->{path_prefix}.(defined $path ? $path : '');
+}
+
+sub url_path {
+ my ($self, $path) = @_;
+ if ($self->{url} =~ m#^https?://#) {
+ $path =~ s!([^~a-zA-Z0-9_./-])!uc sprintf("%%%02x",ord($1))!eg;
+ }
+ $self->{url} . '/' . $self->repo_path($path);
+}
+
+sub rmdirs {
+ my ($self) = @_;
+ my $rm = $self->{rm};
+ delete $rm->{''}; # we never delete the url we're tracking
+ return unless %$rm;
+
+ foreach (keys %$rm) {
+ my @d = split m#/#, $_;
+ my $c = shift @d;
+ $rm->{$c} = 1;
+ while (@d) {
+ $c .= '/' . shift @d;
+ $rm->{$c} = 1;
+ }
+ }
+ delete $rm->{$self->{svn_path}};
+ delete $rm->{''}; # we never delete the url we're tracking
+ return unless %$rm;
+
+ my ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/,
+ $self->{tree_b});
+ local $/ = "\0";
+ while (<$fh>) {
+ chomp;
+ my @dn = split m#/#, $_;
+ while (pop @dn) {
+ delete $rm->{join '/', @dn};
+ }
+ unless (%$rm) {
+ close $fh;
+ return;
+ }
+ }
+ command_close_pipe($fh, $ctx);
+
+ my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
+ foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
+ $self->close_directory($bat->{$d}, $p);
+ my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
+ print "\tD+\t$d/\n" unless $::_q;
+ $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
+ delete $bat->{$d};
+ }
+}
+
+sub open_or_add_dir {
+ my ($self, $full_path, $baton, $deletions) = @_;
+ my $t = $self->{types}->{$full_path};
+ if (!defined $t) {
+ die "$full_path not known in r$self->{r} or we have a bug!\n";
+ }
+ {
+ no warnings 'once';
+ # SVN::Node::none and SVN::Node::file are used only once,
+ # so we're shutting up Perl's warnings about them.
+ if ($t == $SVN::Node::none || defined($deletions->{$full_path})) {
+ return $self->add_directory($full_path, $baton,
+ undef, -1, $self->{pool});
+ } elsif ($t == $SVN::Node::dir) {
+ return $self->open_directory($full_path, $baton,
+ $self->{r}, $self->{pool});
+ } # no warnings 'once'
+ print STDERR "$full_path already exists in repository at ",
+ "r$self->{r} and it is not a directory (",
+ ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
+ } # no warnings 'once'
+ exit 1;
+}
+
+sub ensure_path {
+ my ($self, $path, $deletions) = @_;
+ my $bat = $self->{bat};
+ my $repo_path = $self->repo_path($path);
+ return $bat->{''} unless (length $repo_path);
+
+ my @p = split m#/+#, $repo_path;
+ my $c = shift @p;
+ $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''}, $deletions);
+ while (@p) {
+ my $c0 = $c;
+ $c .= '/' . shift @p;
+ $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0}, $deletions);
+ }
+ return $bat->{$c};
+}
+
+# Subroutine to convert a globbing pattern to a regular expression.
+# From perl cookbook.
+sub glob2pat {
+ my $globstr = shift;
+ my %patmap = ('*' => '.*', '?' => '.', '[' => '[', ']' => ']');
+ $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
+ return '^' . $globstr . '$';
+}
+
+sub check_autoprop {
+ my ($self, $pattern, $properties, $file, $fbat) = @_;
+ # Convert the globbing pattern to a regular expression.
+ my $regex = glob2pat($pattern);
+ # Check if the pattern matches the file name.
+ if($file =~ m/($regex)/) {
+ # Parse the list of properties to set.
+ my @props = split(/;/, $properties);
+ foreach my $prop (@props) {
+ # Parse 'name=value' syntax and set the property.
+ if ($prop =~ /([^=]+)=(.*)/) {
+ my ($n,$v) = ($1,$2);
+ for ($n, $v) {
+ s/^\s+//; s/\s+$//;
+ }
+ $self->change_file_prop($fbat, $n, $v);
+ }
+ }
+ }
+}
+
+sub apply_autoprops {
+ my ($self, $file, $fbat) = @_;
+ my $conf_t = ${$self->{config}}{'config'};
+ no warnings 'once';
+ # Check [miscellany]/enable-auto-props in svn configuration.
+ if (SVN::_Core::svn_config_get_bool(
+ $conf_t,
+ $SVN::_Core::SVN_CONFIG_SECTION_MISCELLANY,
+ $SVN::_Core::SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS,
+ 0)) {
+ # Auto-props are enabled. Enumerate them to look for matches.
+ my $callback = sub {
+ $self->check_autoprop($_[0], $_[1], $file, $fbat);
+ };
+ SVN::_Core::svn_config_enumerate(
+ $conf_t,
+ $SVN::_Core::SVN_CONFIG_SECTION_AUTO_PROPS,
+ $callback);
+ }
+}
+
+sub A {
+ my ($self, $m, $deletions) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir, $deletions);
+ my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+ undef, -1);
+ print "\tA\t$m->{file_b}\n" unless $::_q;
+ $self->apply_autoprops($file, $fbat);
+ $self->chg_file($fbat, $m);
+ $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub C {
+ my ($self, $m, $deletions) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir, $deletions);
+ my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+ $self->url_path($m->{file_a}), $self->{r});
+ print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
+ $self->chg_file($fbat, $m);
+ $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub delete_entry {
+ my ($self, $path, $pbat) = @_;
+ my $rpath = $self->repo_path($path);
+ my ($dir, $file) = split_path($rpath);
+ $self->{rm}->{$dir} = 1;
+ $self->SUPER::delete_entry($rpath, $self->{r}, $pbat, $self->{pool});
+}
+
+sub R {
+ my ($self, $m, $deletions) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir, $deletions);
+ my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+ $self->url_path($m->{file_a}), $self->{r});
+ print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
+ $self->apply_autoprops($file, $fbat);
+ $self->chg_file($fbat, $m);
+ $self->close_file($fbat,undef,$self->{pool});
+
+ ($dir, $file) = split_path($m->{file_a});
+ $pbat = $self->ensure_path($dir, $deletions);
+ $self->delete_entry($m->{file_a}, $pbat);
+}
+
+sub M {
+ my ($self, $m, $deletions) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir, $deletions);
+ my $fbat = $self->open_file($self->repo_path($m->{file_b}),
+ $pbat,$self->{r},$self->{pool});
+ print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q;
+ $self->chg_file($fbat, $m);
+ $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub T { shift->M(@_) }
+
+sub change_file_prop {
+ my ($self, $fbat, $pname, $pval) = @_;
+ $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
+}
+
+sub change_dir_prop {
+ my ($self, $pbat, $pname, $pval) = @_;
+ $self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool});
+}
+
+sub _chg_file_get_blob ($$$$) {
+ my ($self, $fbat, $m, $which) = @_;
+ my $fh = $::_repository->temp_acquire("git_blob_$which");
+ if ($m->{"mode_$which"} =~ /^120/) {
+ print $fh 'link ' or croak $!;
+ $self->change_file_prop($fbat,'svn:special','*');
+ } elsif ($m->{mode_a} =~ /^120/ && $m->{"mode_$which"} !~ /^120/) {
+ $self->change_file_prop($fbat,'svn:special',undef);
+ }
+ my $blob = $m->{"sha1_$which"};
+ return ($fh,) if ($blob =~ /^0{40}$/);
+ my $size = $::_repository->cat_blob($blob, $fh);
+ croak "Failed to read object $blob" if ($size < 0);
+ $fh->flush == 0 or croak $!;
+ seek $fh, 0, 0 or croak $!;
+
+ my $exp = ::md5sum($fh);
+ seek $fh, 0, 0 or croak $!;
+ return ($fh, $exp);
+}
+
+sub chg_file {
+ my ($self, $fbat, $m) = @_;
+ if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
+ $self->change_file_prop($fbat,'svn:executable','*');
+ } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
+ $self->change_file_prop($fbat,'svn:executable',undef);
+ }
+ my ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';
+ my ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';
+ my $pool = SVN::Pool->new;
+ my $atd = $self->apply_textdelta($fbat, $exp_a, $pool);
+ if (-s $fh_a) {
+ my $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);
+ my $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);
+ if (defined $res) {
+ die "Unexpected result from send_txstream: $res\n",
+ "(SVN::Core::VERSION: $SVN::Core::VERSION)\n";
+ }
+ } else {
+ my $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);
+ die "Checksum mismatch\nexpected: $exp_b\ngot: $got\n"
+ if ($got ne $exp_b);
+ }
+ Git::temp_release($fh_b, 1);
+ Git::temp_release($fh_a, 1);
+ $pool->clear;
+}
+
+sub D {
+ my ($self, $m, $deletions) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir, $deletions);
+ print "\tD\t$m->{file_b}\n" unless $::_q;
+ $self->delete_entry($m->{file_b}, $pbat);
+}
+
+sub close_edit {
+ my ($self) = @_;
+ my ($p,$bat) = ($self->{pool}, $self->{bat});
+ foreach (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$bat) {
+ next if $_ eq '';
+ $self->close_directory($bat->{$_}, $p);
+ }
+ $self->close_directory($bat->{''}, $p);
+ $self->SUPER::close_edit($p);
+ $p->clear;
+}
+
+sub abort_edit {
+ my ($self) = @_;
+ $self->SUPER::abort_edit($self->{pool});
+}
+
+sub DESTROY {
+ my $self = shift;
+ $self->SUPER::DESTROY(@_);
+ $self->{pool}->clear;
+}
+
+# this drives the editor
+sub apply_diff {
+ my ($self) = @_;
+ my $mods = $self->{mods};
+ my %o = ( D => 0, C => 1, R => 2, A => 3, M => 4, T => 5 );
+ my %deletions;
+
+ foreach my $m (@$mods) {
+ if ($m->{chg} eq "D") {
+ $deletions{$m->{file_b}} = 1;
+ }
+ }
+
+ foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+ my $f = $m->{chg};
+ if (defined $o{$f}) {
+ $self->$f($m, \%deletions);
+ } else {
+ fatal("Invalid change type: $f");
+ }
+ }
+
+ if (defined($self->{mergeinfo})) {
+ $self->change_dir_prop($self->{bat}{''}, "svn:mergeinfo",
+ $self->{mergeinfo});
+ }
+ $self->rmdirs if $_rmdir;
+ if (@$mods == 0 && !defined($self->{mergeinfo})) {
+ $self->abort_edit;
+ } else {
+ $self->close_edit;
+ }
+ return scalar @$mods;
+}
+
+1;
+__END__
+
+Git::SVN::Editor - commit driver for "git svn set-tree" and dcommit
+
+=head1 SYNOPSIS
+
+ use Git::SVN::Editor;
+ use Git::SVN::Ra;
+
+ my $ra = Git::SVN::Ra->new($url);
+ my %opts = (
+ r => 19,
+ log => "log message",
+ ra => $ra,
+ config => SVN::Core::config_get_config($svn_config_dir),
+ tree_a => "$commit^",
+ tree_b => "$commit",
+ editor_cb => sub { print "Committed r$_[0]\n"; },
+ mergeinfo => "/branches/foo:1-10",
+ svn_path => "trunk"
+ );
+ Git::SVN::Editor->new(\%opts)->apply_diff or print "No changes\n";
+
+ my $re = Git::SVN::Editor::glob2pat("trunk/*");
+ if ($branchname =~ /$re/) {
+ print "matched!\n";
+ }
+
+=head1 DESCRIPTION
+
+This module is an implementation detail of the "git svn" command.
+Do not use it unless you are developing git-svn.
+
+This module adapts the C<SVN::Delta::Editor> object returned by
+C<SVN::Delta::get_commit_editor> and drives it to convey the
+difference between two git tree objects to a remote Subversion
+repository.
+
+The interface will change as git-svn evolves.
+
+=head1 DEPENDENCIES
+
+Subversion perl bindings,
+the core L<Carp> and L<IO::File> modules,
+and git's L<Git> helper module.
+
+C<Git::SVN::Editor> has not been tested using callers other than
+B<git-svn> itself.
+
+=head1 SEE ALSO
+
+L<SVN::Delta>,
+L<Git::SVN::Fetcher>.
+
+=head1 INCOMPATIBILITIES
+
+None reported.
+
+=head1 BUGS
+
+None.
diff --git a/perl/Git/SVN/Fetcher.pm b/perl/Git/SVN/Fetcher.pm
new file mode 100644
index 0000000..ef8e9ed
--- /dev/null
+++ b/perl/Git/SVN/Fetcher.pm
@@ -0,0 +1,603 @@
+package Git::SVN::Fetcher;
+use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename
+ @deleted_gpath %added_placeholder $repo_id/;
+use strict;
+use warnings;
+use SVN::Delta;
+use Carp qw/croak/;
+use File::Basename qw/dirname/;
+use IO::File qw//;
+use Git qw/command command_oneline command_noisy command_output_pipe
+ command_input_pipe command_close_pipe
+ command_bidi_pipe command_close_bidi_pipe/;
+BEGIN {
+ @ISA = qw(SVN::Delta::Editor);
+}
+
+# file baton members: path, mode_a, mode_b, pool, fh, blob, base
+sub new {
+ my ($class, $git_svn, $switch_path) = @_;
+ my $self = SVN::Delta::Editor->new;
+ bless $self, $class;
+ if (exists $git_svn->{last_commit}) {
+ $self->{c} = $git_svn->{last_commit};
+ $self->{empty_symlinks} =
+ _mark_empty_symlinks($git_svn, $switch_path);
+ }
+
+ # some options are read globally, but can be overridden locally
+ # per [svn-remote "..."] section. Command-line options will *NOT*
+ # override options set in an [svn-remote "..."] section
+ $repo_id = $git_svn->{repo_id};
+ my $k = "svn-remote.$repo_id.ignore-paths";
+ my $v = eval { command_oneline('config', '--get', $k) };
+ $self->{ignore_regex} = $v;
+
+ $k = "svn-remote.$repo_id.preserve-empty-dirs";
+ $v = eval { command_oneline('config', '--get', '--bool', $k) };
+ if ($v && $v eq 'true') {
+ $_preserve_empty_dirs = 1;
+ $k = "svn-remote.$repo_id.placeholder-filename";
+ $v = eval { command_oneline('config', '--get', $k) };
+ $_placeholder_filename = $v;
+ }
+
+ # Load the list of placeholder files added during previous invocations.
+ $k = "svn-remote.$repo_id.added-placeholder";
+ $v = eval { command_oneline('config', '--get-all', $k) };
+ if ($_preserve_empty_dirs && $v) {
+ # command() prints errors to stderr, so we only call it if
+ # command_oneline() succeeded.
+ my @v = command('config', '--get-all', $k);
+ $added_placeholder{ dirname($_) } = $_ foreach @v;
+ }
+
+ $self->{empty} = {};
+ $self->{dir_prop} = {};
+ $self->{file_prop} = {};
+ $self->{absent_dir} = {};
+ $self->{absent_file} = {};
+ $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
+ $self->{pathnameencoding} = Git::config('svn.pathnameencoding');
+ $self;
+}
+
+# this uses the Ra object, so it must be called before do_{switch,update},
+# not inside them (when the Git::SVN::Fetcher object is passed) to
+# do_{switch,update}
+sub _mark_empty_symlinks {
+ my ($git_svn, $switch_path) = @_;
+ my $bool = Git::config_bool('svn.brokenSymlinkWorkaround');
+ return {} if (!defined($bool)) || (defined($bool) && ! $bool);
+
+ my %ret;
+ my ($rev, $cmt) = $git_svn->last_rev_commit;
+ return {} unless ($rev && $cmt);
+
+ # allow the warning to be printed for each revision we fetch to
+ # ensure the user sees it. The user can also disable the workaround
+ # on the repository even while git svn is running and the next
+ # revision fetched will skip this expensive function.
+ my $printed_warning;
+ chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);
+ my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);
+ local $/ = "\0";
+ my $pfx = defined($switch_path) ? $switch_path : $git_svn->{path};
+ $pfx .= '/' if length($pfx);
+ while (<$ls>) {
+ chomp;
+ s/\A100644 blob $empty_blob\t//o or next;
+ unless ($printed_warning) {
+ print STDERR "Scanning for empty symlinks, ",
+ "this may take a while if you have ",
+ "many empty files\n",
+ "You may disable this with `",
+ "git config svn.brokenSymlinkWorkaround ",
+ "false'.\n",
+ "This may be done in a different ",
+ "terminal without restarting ",
+ "git svn\n";
+ $printed_warning = 1;
+ }
+ my $path = $_;
+ my (undef, $props) =
+ $git_svn->ra->get_file($pfx.$path, $rev, undef);
+ if ($props->{'svn:special'}) {
+ $ret{$path} = 1;
+ }
+ }
+ command_close_pipe($ls, $ctx);
+ \%ret;
+}
+
+# returns true if a given path is inside a ".git" directory
+sub in_dot_git {
+ $_[0] =~ m{(?:^|/)\.git(?:/|$)};
+}
+
+# return value: 0 -- don't ignore, 1 -- ignore
+sub is_path_ignored {
+ my ($self, $path) = @_;
+ return 1 if in_dot_git($path);
+ return 1 if defined($self->{ignore_regex}) &&
+ $path =~ m!$self->{ignore_regex}!;
+ return 0 unless defined($_ignore_regex);
+ return 1 if $path =~ m!$_ignore_regex!o;
+ return 0;
+}
+
+sub set_path_strip {
+ my ($self, $path) = @_;
+ $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
+}
+
+sub open_root {
+ { path => '' };
+}
+
+sub open_directory {
+ my ($self, $path, $pb, $rev) = @_;
+ { path => $path };
+}
+
+sub git_path {
+ my ($self, $path) = @_;
+ if (my $enc = $self->{pathnameencoding}) {
+ require Encode;
+ Encode::from_to($path, 'UTF-8', $enc);
+ }
+ if ($self->{path_strip}) {
+ $path =~ s!$self->{path_strip}!! or
+ die "Failed to strip path '$path' ($self->{path_strip})\n";
+ }
+ $path;
+}
+
+sub delete_entry {
+ my ($self, $path, $rev, $pb) = @_;
+ return undef if $self->is_path_ignored($path);
+
+ my $gpath = $self->git_path($path);
+ return undef if ($gpath eq '');
+
+ # remove entire directories.
+ my ($tree) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
+ =~ /\A040000 tree ([a-f\d]{40})\t\Q$gpath\E\0/);
+ if ($tree) {
+ my ($ls, $ctx) = command_output_pipe(qw/ls-tree
+ -r --name-only -z/,
+ $tree);
+ local $/ = "\0";
+ while (<$ls>) {
+ chomp;
+ my $rmpath = "$gpath/$_";
+ $self->{gii}->remove($rmpath);
+ print "\tD\t$rmpath\n" unless $::_q;
+ }
+ print "\tD\t$gpath/\n" unless $::_q;
+ command_close_pipe($ls, $ctx);
+ } else {
+ $self->{gii}->remove($gpath);
+ print "\tD\t$gpath\n" unless $::_q;
+ }
+ # Don't add to @deleted_gpath if we're deleting a placeholder file.
+ push @deleted_gpath, $gpath unless $added_placeholder{dirname($path)};
+ $self->{empty}->{$path} = 0;
+ undef;
+}
+
+sub open_file {
+ my ($self, $path, $pb, $rev) = @_;
+ my ($mode, $blob);
+
+ goto out if $self->is_path_ignored($path);
+
+ my $gpath = $self->git_path($path);
+ ($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
+ =~ /\A(\d{6}) blob ([a-f\d]{40})\t\Q$gpath\E\0/);
+ unless (defined $mode && defined $blob) {
+ die "$path was not found in commit $self->{c} (r$rev)\n";
+ }
+ if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {
+ $mode = '120000';
+ }
+out:
+ { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
+ pool => SVN::Pool->new, action => 'M' };
+}
+
+sub add_file {
+ my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
+ my $mode;
+
+ if (!$self->is_path_ignored($path)) {
+ my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
+ delete $self->{empty}->{$dir};
+ $mode = '100644';
+
+ if ($added_placeholder{$dir}) {
+ # Remove our placeholder file, if we created one.
+ delete_entry($self, $added_placeholder{$dir})
+ unless $path eq $added_placeholder{$dir};
+ delete $added_placeholder{$dir}
+ }
+ }
+
+ { path => $path, mode_a => $mode, mode_b => $mode,
+ pool => SVN::Pool->new, action => 'A' };
+}
+
+sub add_directory {
+ my ($self, $path, $cp_path, $cp_rev) = @_;
+ goto out if $self->is_path_ignored($path);
+ 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;
+ push @deleted_gpath, $gpath;
+ }
+ command_close_pipe($ls, $ctx);
+ $self->{empty}->{$path} = 0;
+ }
+ my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
+ delete $self->{empty}->{$dir};
+ $self->{empty}->{$path} = 1;
+
+ if ($added_placeholder{$dir}) {
+ # Remove our placeholder file, if we created one.
+ delete_entry($self, $added_placeholder{$dir});
+ delete $added_placeholder{$dir}
+ }
+
+out:
+ { path => $path };
+}
+
+sub change_dir_prop {
+ my ($self, $db, $prop, $value) = @_;
+ return undef if $self->is_path_ignored($db->{path});
+ $self->{dir_prop}->{$db->{path}} ||= {};
+ $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
+ undef;
+}
+
+sub absent_directory {
+ my ($self, $path, $pb) = @_;
+ return undef if $self->is_path_ignored($path);
+ $self->{absent_dir}->{$pb->{path}} ||= [];
+ push @{$self->{absent_dir}->{$pb->{path}}}, $path;
+ undef;
+}
+
+sub absent_file {
+ my ($self, $path, $pb) = @_;
+ return undef if $self->is_path_ignored($path);
+ $self->{absent_file}->{$pb->{path}} ||= [];
+ push @{$self->{absent_file}->{$pb->{path}}}, $path;
+ undef;
+}
+
+sub change_file_prop {
+ my ($self, $fb, $prop, $value) = @_;
+ return undef if $self->is_path_ignored($fb->{path});
+ if ($prop eq 'svn:executable') {
+ if ($fb->{mode_b} != 120000) {
+ $fb->{mode_b} = defined $value ? 100755 : 100644;
+ }
+ } elsif ($prop eq 'svn:special') {
+ $fb->{mode_b} = defined $value ? 120000 : 100644;
+ } else {
+ $self->{file_prop}->{$fb->{path}} ||= {};
+ $self->{file_prop}->{$fb->{path}}->{$prop} = $value;
+ }
+ undef;
+}
+
+sub apply_textdelta {
+ my ($self, $fb, $exp) = @_;
+ return undef if $self->is_path_ignored($fb->{path});
+ my $fh = $::_repository->temp_acquire('svn_delta');
+ # $fh gets auto-closed() by SVN::TxDelta::apply(),
+ # (but $base does not,) so dup() it for reading in close_file
+ open my $dup, '<&', $fh or croak $!;
+ my $base = $::_repository->temp_acquire('git_blob');
+
+ if ($fb->{blob}) {
+ my ($base_is_link, $size);
+
+ if ($fb->{mode_a} eq '120000' &&
+ ! $self->{empty_symlinks}->{$fb->{path}}) {
+ print $base 'link ' or die "print $!\n";
+ $base_is_link = 1;
+ }
+ retry:
+ $size = $::_repository->cat_blob($fb->{blob}, $base);
+ die "Failed to read object $fb->{blob}" if ($size < 0);
+
+ if (defined $exp) {
+ seek $base, 0, 0 or croak $!;
+ my $got = ::md5sum($base);
+ if ($got ne $exp) {
+ my $err = "Checksum mismatch: ".
+ "$fb->{path} $fb->{blob}\n" .
+ "expected: $exp\n" .
+ " got: $got\n";
+ if ($base_is_link) {
+ warn $err,
+ "Retrying... (possibly ",
+ "a bad symlink from SVN)\n";
+ $::_repository->temp_reset($base);
+ $base_is_link = 0;
+ goto retry;
+ }
+ die $err;
+ }
+ }
+ }
+ seek $base, 0, 0 or croak $!;
+ $fb->{fh} = $fh;
+ $fb->{base} = $base;
+ [ SVN::TxDelta::apply($base, $dup, undef, $fb->{path}, $fb->{pool}) ];
+}
+
+sub close_file {
+ my ($self, $fb, $exp) = @_;
+ return undef if $self->is_path_ignored($fb->{path});
+
+ my $hash;
+ my $path = $self->git_path($fb->{path});
+ if (my $fh = $fb->{fh}) {
+ if (defined $exp) {
+ seek($fh, 0, 0) or croak $!;
+ my $got = ::md5sum($fh);
+ if ($got ne $exp) {
+ die "Checksum mismatch: $path\n",
+ "expected: $exp\n got: $got\n";
+ }
+ }
+ if ($fb->{mode_b} == 120000) {
+ sysseek($fh, 0, 0) or croak $!;
+ my $rd = sysread($fh, my $buf, 5);
+
+ if (!defined $rd) {
+ croak "sysread: $!\n";
+ } elsif ($rd == 0) {
+ warn "$path has mode 120000",
+ " but it points to nothing\n",
+ "converting to an empty file with mode",
+ " 100644\n";
+ $fb->{mode_b} = '100644';
+ } elsif ($buf ne 'link ') {
+ warn "$path has mode 120000",
+ " but is not a link\n";
+ } else {
+ my $tmp_fh = $::_repository->temp_acquire(
+ 'svn_hash');
+ my $res;
+ while ($res = sysread($fh, my $str, 1024)) {
+ my $out = syswrite($tmp_fh, $str, $res);
+ defined($out) && $out == $res
+ or croak("write ",
+ Git::temp_path($tmp_fh),
+ ": $!\n");
+ }
+ defined $res or croak $!;
+
+ ($fh, $tmp_fh) = ($tmp_fh, $fh);
+ Git::temp_release($tmp_fh, 1);
+ }
+ }
+
+ $hash = $::_repository->hash_and_insert_object(
+ Git::temp_path($fh));
+ $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
+
+ Git::temp_release($fb->{base}, 1);
+ Git::temp_release($fh, 1);
+ } else {
+ $hash = $fb->{blob} or die "no blob information\n";
+ }
+ $fb->{pool}->clear;
+ $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!;
+ print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $::_q;
+ undef;
+}
+
+sub abort_edit {
+ my $self = shift;
+ $self->{nr} = $self->{gii}->{nr};
+ delete $self->{gii};
+ $self->SUPER::abort_edit(@_);
+}
+
+sub close_edit {
+ my $self = shift;
+
+ if ($_preserve_empty_dirs) {
+ my @empty_dirs;
+
+ # Any entry flagged as empty that also has an associated
+ # dir_prop represents a newly created empty directory.
+ foreach my $i (keys %{$self->{empty}}) {
+ push @empty_dirs, $i if exists $self->{dir_prop}->{$i};
+ }
+
+ # Search for directories that have become empty due subsequent
+ # file deletes.
+ push @empty_dirs, $self->find_empty_directories();
+
+ # Finally, add a placeholder file to each empty directory.
+ $self->add_placeholder_file($_) foreach (@empty_dirs);
+
+ $self->stash_placeholder_list();
+ }
+
+ $self->{git_commit_ok} = 1;
+ $self->{nr} = $self->{gii}->{nr};
+ delete $self->{gii};
+ $self->SUPER::close_edit(@_);
+}
+
+sub find_empty_directories {
+ my ($self) = @_;
+ my @empty_dirs;
+ my %dirs = map { dirname($_) => 1 } @deleted_gpath;
+
+ foreach my $dir (sort keys %dirs) {
+ next if $dir eq ".";
+
+ # If there have been any additions to this directory, there is
+ # no reason to check if it is empty.
+ my $skip_added = 0;
+ foreach my $t (qw/dir_prop file_prop/) {
+ foreach my $path (keys %{ $self->{$t} }) {
+ if (exists $self->{$t}->{dirname($path)}) {
+ $skip_added = 1;
+ last;
+ }
+ }
+ last if $skip_added;
+ }
+ next if $skip_added;
+
+ # Use `git ls-tree` to get the filenames of this directory
+ # that existed prior to this particular commit.
+ my $ls = command('ls-tree', '-z', '--name-only',
+ $self->{c}, "$dir/");
+ my %files = map { $_ => 1 } split(/\0/, $ls);
+
+ # Remove the filenames that were deleted during this commit.
+ delete $files{$_} foreach (@deleted_gpath);
+
+ # Report the directory if there are no filenames left.
+ push @empty_dirs, $dir unless (scalar %files);
+ }
+ @empty_dirs;
+}
+
+sub add_placeholder_file {
+ my ($self, $dir) = @_;
+ my $path = "$dir/$_placeholder_filename";
+ my $gpath = $self->git_path($path);
+
+ my $fh = $::_repository->temp_acquire($gpath);
+ my $hash = $::_repository->hash_and_insert_object(Git::temp_path($fh));
+ Git::temp_release($fh, 1);
+ $self->{gii}->update('100644', $hash, $gpath) or croak $!;
+
+ # The directory should no longer be considered empty.
+ delete $self->{empty}->{$dir} if exists $self->{empty}->{$dir};
+
+ # Keep track of any placeholder files we create.
+ $added_placeholder{$dir} = $path;
+}
+
+sub stash_placeholder_list {
+ my ($self) = @_;
+ my $k = "svn-remote.$repo_id.added-placeholder";
+ my $v = eval { command_oneline('config', '--get-all', $k) };
+ command_noisy('config', '--unset-all', $k) if $v;
+ foreach (values %added_placeholder) {
+ command_noisy('config', '--add', $k, $_);
+ }
+}
+
+1;
+__END__
+
+Git::SVN::Fetcher - tree delta consumer for "git svn fetch"
+
+=head1 SYNOPSIS
+
+ use SVN::Core;
+ use SVN::Ra;
+ use Git::SVN;
+ use Git::SVN::Fetcher;
+ use Git;
+
+ my $gs = Git::SVN->find_by_url($url);
+ my $ra = SVN::Ra->new(url => $url);
+ my $editor = Git::SVN::Fetcher->new($gs);
+ my $reporter = $ra->do_update($SVN::Core::INVALID_REVNUM, '',
+ 1, $editor);
+ $reporter->set_path('', $old_rev, 0);
+ $reporter->finish_report;
+ my $tree = $gs->tmp_index_do(sub { command_oneline('write-tree') });
+
+ foreach my $path (keys %{$editor->{dir_prop}) {
+ my $props = $editor->{dir_prop}{$path};
+ foreach my $prop (keys %$props) {
+ print "property $prop at $path changed to $props->{$prop}\n";
+ }
+ }
+ foreach my $path (keys %{$editor->{empty}) {
+ my $action = $editor->{empty}{$path} ? 'added' : 'removed';
+ print "empty directory $path $action\n";
+ }
+ foreach my $path (keys %{$editor->{file_prop}) { ... }
+ foreach my $parent (keys %{$editor->{absent_dir}}) {
+ my @children = @{$editor->{abstent_dir}{$parent}};
+ print "cannot fetch directory $parent/$_: not authorized?\n"
+ foreach @children;
+ }
+ foreach my $parent (keys %{$editor->{absent_file}) { ... }
+
+=head1 DESCRIPTION
+
+This is a subclass of C<SVN::Delta::Editor>, which means it implements
+callbacks to act as a consumer of Subversion tree deltas. This
+particular implementation of those callbacks is meant to store
+information about the resulting content which B<git svn fetch> could
+use to populate new commits and new entries for F<unhandled.log>.
+More specifically:
+
+=over
+
+=item * Additions, removals, and modifications of files are propagated
+to git-svn's index file F<$GIT_DIR/svn/$refname/index> using
+B<git update-index>.
+
+=item * Changes in Subversion path properties are recorded in the
+C<dir_prop> and C<file_prop> fields (which are hashes).
+
+=item * Addition and removal of empty directories are indicated by
+entries with value 1 and 0 respectively in the C<empty> hash.
+
+=item * Paths that are present but cannot be conveyed (presumably due
+to permissions) are recorded in the C<absent_file> and
+C<absent_dirs> hashes. For each key, the corresponding value is
+a list of paths under that directory that were present but
+could not be conveyed.
+
+=back
+
+The interface is unstable. Do not use this module unless you are
+developing git-svn.
+
+=head1 DEPENDENCIES
+
+L<SVN::Delta> from the Subversion perl bindings,
+the core L<Carp>, L<File::Basename>, and L<IO::File> modules,
+and git's L<Git> helper module.
+
+C<Git::SVN::Fetcher> has not been tested using callers other than
+B<git-svn> itself.
+
+=head1 SEE ALSO
+
+L<SVN::Delta>,
+L<Git::SVN::Editor>.
+
+=head1 INCOMPATIBILITIES
+
+None reported.
+
+=head1 BUGS
+
+None.
diff --git a/perl/Git/SVN/Memoize/YAML.pm b/perl/Git/SVN/Memoize/YAML.pm
new file mode 100644
index 0000000..9676b8f
--- /dev/null
+++ b/perl/Git/SVN/Memoize/YAML.pm
@@ -0,0 +1,93 @@
+package Git::SVN::Memoize::YAML;
+use warnings;
+use strict;
+use YAML::Any ();
+
+# based on Memoize::Storable.
+
+sub TIEHASH {
+ my $package = shift;
+ my $filename = shift;
+ my $truehash = (-e $filename) ? YAML::Any::LoadFile($filename) : {};
+ my $self = {FILENAME => $filename, H => $truehash};
+ bless $self => $package;
+}
+
+sub STORE {
+ my $self = shift;
+ $self->{H}{$_[0]} = $_[1];
+}
+
+sub FETCH {
+ my $self = shift;
+ $self->{H}{$_[0]};
+}
+
+sub EXISTS {
+ my $self = shift;
+ exists $self->{H}{$_[0]};
+}
+
+sub DESTROY {
+ my $self = shift;
+ YAML::Any::DumpFile($self->{FILENAME}, $self->{H});
+}
+
+sub SCALAR {
+ my $self = shift;
+ scalar(%{$self->{H}});
+}
+
+sub FIRSTKEY {
+ 'Fake hash from Git::SVN::Memoize::YAML';
+}
+
+sub NEXTKEY {
+ undef;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Git::SVN::Memoize::YAML - store Memoized data in YAML format
+
+=head1 SYNOPSIS
+
+ use Memoize;
+ use Git::SVN::Memoize::YAML;
+
+ tie my %cache => 'Git::SVN::Memoize::YAML', $filename;
+ memoize('slow_function', SCALAR_CACHE => [HASH => \%cache]);
+ slow_function(arguments);
+
+=head1 DESCRIPTION
+
+This module provides a class that can be used to tie a hash to a
+YAML file. The file is read when the hash is initialized and
+rewritten when the hash is destroyed.
+
+The intent is to allow L<Memoize> to back its cache with a file in
+YAML format, just like L<Memoize::Storable> allows L<Memoize> to
+back its cache with a file in Storable format. Unlike the Storable
+format, the YAML format is platform-independent and fairly stable.
+
+Carps on error.
+
+=head1 DIAGNOSTICS
+
+See L<YAML::Any>.
+
+=head1 DEPENDENCIES
+
+L<YAML::Any> from CPAN.
+
+=head1 INCOMPATIBILITIES
+
+None reported.
+
+=head1 BUGS
+
+The entire cache is read into a Perl hash when loading the file,
+so this is not very scalable.
diff --git a/perl/Git/SVN/Prompt.pm b/perl/Git/SVN/Prompt.pm
new file mode 100644
index 0000000..3a6f8af
--- /dev/null
+++ b/perl/Git/SVN/Prompt.pm
@@ -0,0 +1,202 @@
+package Git::SVN::Prompt;
+use strict;
+use warnings;
+require SVN::Core;
+use vars qw/$_no_auth_cache $_username/;
+
+sub simple {
+ my ($cred, $realm, $default_username, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ $default_username = $_username if defined $_username;
+ if (defined $default_username && length $default_username) {
+ if (defined $realm && length $realm) {
+ print STDERR "Authentication realm: $realm\n";
+ STDERR->flush;
+ }
+ $cred->username($default_username);
+ } else {
+ username($cred, $realm, $may_save, $pool);
+ }
+ $cred->password(_read_password("Password for '" .
+ $cred->username . "': ", $realm));
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub ssl_server_trust {
+ my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ print STDERR "Error validating server certificate for '$realm':\n";
+ {
+ no warnings 'once';
+ # All variables SVN::Auth::SSL::* are used only once,
+ # so we're shutting up Perl warnings about this.
+ if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
+ print STDERR " - The certificate is not issued ",
+ "by a trusted authority. Use the\n",
+ " fingerprint to validate ",
+ "the certificate manually!\n";
+ }
+ if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
+ print STDERR " - The certificate hostname ",
+ "does not match.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
+ print STDERR " - The certificate is not yet valid.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::EXPIRED) {
+ print STDERR " - The certificate has expired.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::OTHER) {
+ print STDERR " - The certificate has ",
+ "an unknown error.\n";
+ }
+ } # no warnings 'once'
+ printf STDERR
+ "Certificate information:\n".
+ " - Hostname: %s\n".
+ " - Valid: from %s until %s\n".
+ " - Issuer: %s\n".
+ " - Fingerprint: %s\n",
+ map $cert_info->$_, qw(hostname valid_from valid_until
+ issuer_dname fingerprint);
+ my $choice;
+prompt:
+ print STDERR $may_save ?
+ "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
+ "(R)eject or accept (t)emporarily? ";
+ STDERR->flush;
+ $choice = lc(substr(<STDIN> || 'R', 0, 1));
+ if ($choice =~ /^t$/i) {
+ $cred->may_save(undef);
+ } elsif ($choice =~ /^r$/i) {
+ return -1;
+ } elsif ($may_save && $choice =~ /^p$/i) {
+ $cred->may_save($may_save);
+ } else {
+ goto prompt;
+ }
+ $cred->accepted_failures($failures);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub ssl_client_cert {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ print STDERR "Client certificate filename: ";
+ STDERR->flush;
+ chomp(my $filename = <STDIN>);
+ $cred->cert_file($filename);
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub ssl_client_cert_pw {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ $cred->password(_read_password("Password: ", $realm));
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub username {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ if (defined $realm && length $realm) {
+ print STDERR "Authentication realm: $realm\n";
+ }
+ my $username;
+ if (defined $_username) {
+ $username = $_username;
+ } else {
+ print STDERR "Username: ";
+ STDERR->flush;
+ chomp($username = <STDIN>);
+ }
+ $cred->username($username);
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _read_password {
+ my ($prompt, $realm) = @_;
+ my $password = '';
+ if (exists $ENV{GIT_ASKPASS}) {
+ open(PH, "-|", $ENV{GIT_ASKPASS}, $prompt);
+ $password = <PH>;
+ $password =~ s/[\012\015]//; # \n\r
+ close(PH);
+ } else {
+ print STDERR $prompt;
+ STDERR->flush;
+ require Term::ReadKey;
+ Term::ReadKey::ReadMode('noecho');
+ while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+ last if $key =~ /[\012\015]/; # \n\r
+ $password .= $key;
+ }
+ Term::ReadKey::ReadMode('restore');
+ print STDERR "\n";
+ STDERR->flush;
+ }
+ $password;
+}
+
+1;
+__END__
+
+Git::SVN::Prompt - authentication callbacks for git-svn
+
+=head1 SYNOPSIS
+
+ use Git::SVN::Prompt qw(simple ssl_client_cert ssl_client_cert_pw
+ ssl_server_trust username);
+ use SVN::Client ();
+
+ my $cached_simple = SVN::Client::get_simple_provider();
+ my $git_simple = SVN::Client::get_simple_prompt_provider(\&simple, 2);
+ my $cached_ssl = SVN::Client::get_ssl_server_trust_file_provider();
+ my $git_ssl = SVN::Client::get_ssl_server_trust_prompt_provider(
+ \&ssl_server_trust);
+ my $cached_cert = SVN::Client::get_ssl_client_cert_file_provider();
+ my $git_cert = SVN::Client::get_ssl_client_cert_prompt_provider(
+ \&ssl_client_cert, 2);
+ my $cached_cert_pw = SVN::Client::get_ssl_client_cert_pw_file_provider();
+ my $git_cert_pw = SVN::Client::get_ssl_client_cert_pw_prompt_provider(
+ \&ssl_client_cert_pw, 2);
+ my $cached_username = SVN::Client::get_username_provider();
+ my $git_username = SVN::Client::get_username_prompt_provider(
+ \&username, 2);
+
+ my $ctx = new SVN::Client(
+ auth => [
+ $cached_simple, $git_simple,
+ $cached_ssl, $git_ssl,
+ $cached_cert, $git_cert,
+ $cached_cert_pw, $git_cert_pw,
+ $cached_username, $git_username
+ ]);
+
+=head1 DESCRIPTION
+
+This module is an implementation detail of the "git svn" command.
+It implements git-svn's authentication policy. Do not use it unless
+you are developing git-svn.
+
+The interface will change as git-svn evolves.
+
+=head1 DEPENDENCIES
+
+L<SVN::Core>.
+
+=head1 SEE ALSO
+
+L<SVN::Client>.
+
+=head1 INCOMPATIBILITIES
+
+None reported.
+
+=head1 BUGS
+
+None.
diff --git a/perl/Git/SVN/Ra.pm b/perl/Git/SVN/Ra.pm
new file mode 100644
index 0000000..23ff43e
--- /dev/null
+++ b/perl/Git/SVN/Ra.pm
@@ -0,0 +1,658 @@
+package Git::SVN::Ra;
+use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;
+use strict;
+use warnings;
+use SVN::Client;
+use SVN::Ra;
+BEGIN {
+ @ISA = qw(SVN::Ra);
+}
+
+my ($ra_invalid, $can_do_switch, %ignored_err, $RA);
+
+BEGIN {
+ # enforce temporary pool usage for some simple functions
+ no strict 'refs';
+ for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root
+ get_file/) {
+ my $SUPER = "SUPER::$f";
+ *$f = sub {
+ my $self = shift;
+ my $pool = SVN::Pool->new;
+ my @ret = $self->$SUPER(@_,$pool);
+ $pool->clear;
+ wantarray ? @ret : $ret[0];
+ };
+ }
+}
+
+sub _auth_providers () {
+ my @rv = (
+ SVN::Client::get_simple_provider(),
+ SVN::Client::get_ssl_server_trust_file_provider(),
+ SVN::Client::get_simple_prompt_provider(
+ \&Git::SVN::Prompt::simple, 2),
+ SVN::Client::get_ssl_client_cert_file_provider(),
+ SVN::Client::get_ssl_client_cert_prompt_provider(
+ \&Git::SVN::Prompt::ssl_client_cert, 2),
+ SVN::Client::get_ssl_client_cert_pw_file_provider(),
+ SVN::Client::get_ssl_client_cert_pw_prompt_provider(
+ \&Git::SVN::Prompt::ssl_client_cert_pw, 2),
+ SVN::Client::get_username_provider(),
+ SVN::Client::get_ssl_server_trust_prompt_provider(
+ \&Git::SVN::Prompt::ssl_server_trust),
+ SVN::Client::get_username_prompt_provider(
+ \&Git::SVN::Prompt::username, 2)
+ );
+
+ # earlier 1.6.x versions would segfault, and <= 1.5.x didn't have
+ # this function
+ if (::compare_svn_version('1.6.15') >= 0) {
+ my $config = SVN::Core::config_get_config($config_dir);
+ my ($p, @a);
+ # config_get_config returns all config files from
+ # ~/.subversion, auth_get_platform_specific_client_providers
+ # just wants the config "file".
+ @a = ($config->{'config'}, undef);
+ $p = SVN::Core::auth_get_platform_specific_client_providers(@a);
+ # Insert the return value from
+ # auth_get_platform_specific_providers
+ unshift @rv, @$p;
+ }
+ \@rv;
+}
+
+sub escape_uri_only {
+ my ($uri) = @_;
+ my @tmp;
+ foreach (split m{/}, $uri) {
+ s/([^~\w.%+-]|%(?![a-fA-F0-9]{2}))/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!/+$!!;
+ return $RA if ($RA && $RA->{url} eq $url);
+
+ ::_req_svn();
+
+ SVN::_Core::svn_config_ensure($config_dir, undef);
+ my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);
+ my $config = SVN::Core::config_get_config($config_dir);
+ $RA = undef;
+ my $dont_store_passwords = 1;
+ my $conf_t = ${$config}{'config'};
+ {
+ no warnings 'once';
+ # The usage of $SVN::_Core::SVN_CONFIG_* variables
+ # produces warnings that variables are used only once.
+ # I had not found the better way to shut them up, so
+ # the warnings of type 'once' are disabled in this block.
+ if (SVN::_Core::svn_config_get_bool($conf_t,
+ $SVN::_Core::SVN_CONFIG_SECTION_AUTH,
+ $SVN::_Core::SVN_CONFIG_OPTION_STORE_PASSWORDS,
+ 1) == 0) {
+ SVN::_Core::svn_auth_set_parameter($baton,
+ $SVN::_Core::SVN_AUTH_PARAM_DONT_STORE_PASSWORDS,
+ bless (\$dont_store_passwords, "_p_void"));
+ }
+ if (SVN::_Core::svn_config_get_bool($conf_t,
+ $SVN::_Core::SVN_CONFIG_SECTION_AUTH,
+ $SVN::_Core::SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
+ 1) == 0) {
+ $Git::SVN::Prompt::_no_auth_cache = 1;
+ }
+ } # no warnings 'once'
+ 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(/|$)##;
+ $self->{cache} = { check_path => { r => 0, data => {} },
+ get_dir => { r => 0, data => {} } };
+ $RA = bless $self, $class;
+}
+
+sub check_path {
+ my ($self, $path, $r) = @_;
+ my $cache = $self->{cache}->{check_path};
+ if ($r == $cache->{r} && exists $cache->{data}->{$path}) {
+ return $cache->{data}->{$path};
+ }
+ my $pool = SVN::Pool->new;
+ my $t = $self->SUPER::check_path($path, $r, $pool);
+ $pool->clear;
+ if ($r != $cache->{r}) {
+ %{$cache->{data}} = ();
+ $cache->{r} = $r;
+ }
+ $cache->{data}->{$path} = $t;
+}
+
+sub get_dir {
+ my ($self, $dir, $r) = @_;
+ my $cache = $self->{cache}->{get_dir};
+ if ($r == $cache->{r}) {
+ if (my $x = $cache->{data}->{$dir}) {
+ return wantarray ? @$x : $x->[0];
+ }
+ }
+ my $pool = SVN::Pool->new;
+ my ($d, undef, $props) = $self->SUPER::get_dir($dir, $r, $pool);
+ my %dirents = map { $_ => { kind => $d->{$_}->kind } } keys %$d;
+ $pool->clear;
+ if ($r != $cache->{r}) {
+ %{$cache->{data}} = ();
+ $cache->{r} = $r;
+ }
+ $cache->{data}->{$dir} = [ \%dirents, $r, $props ];
+ wantarray ? (\%dirents, $r, $props) : \%dirents;
+}
+
+sub DESTROY {
+ # do not call the real DESTROY since we store ourselves in $RA
+}
+
+# get_log(paths, start, end, limit,
+# discover_changed_paths, strict_node_history, receiver)
+sub get_log {
+ my ($self, @args) = @_;
+ my $pool = SVN::Pool->new;
+
+ # svn_log_changed_path_t objects passed to get_log are likely to be
+ # overwritten even if only the refs are copied to an external variable,
+ # so we should dup the structures in their entirety. Using an
+ # externally passed pool (instead of our temporary and quickly cleared
+ # pool in Git::SVN::Ra) does not help matters at all...
+ my $receiver = pop @args;
+ my $prefix = "/".$self->{svn_path};
+ $prefix =~ s#/+($)##;
+ my $prefix_regex = qr#^\Q$prefix\E#;
+ push(@args, sub {
+ my ($paths) = $_[0];
+ return &$receiver(@_) unless $paths;
+ $_[0] = ();
+ foreach my $p (keys %$paths) {
+ my $i = $paths->{$p};
+ # Make path relative to our url, not repos_root
+ $p =~ s/$prefix_regex//;
+ my %s = map { $_ => $i->$_; }
+ qw/copyfrom_path copyfrom_rev action/;
+ if ($s{'copyfrom_path'}) {
+ $s{'copyfrom_path'} =~ s/$prefix_regex//;
+ }
+ $_[0]{$p} = \%s;
+ }
+ &$receiver(@_);
+ });
+
+
+ # the limit parameter was not supported in SVN 1.1.x, so we
+ # drop it. Therefore, the receiver callback passed to it
+ # is made aware of this limitation by being wrapped if
+ # the limit passed to is being wrapped.
+ if (::compare_svn_version('1.2.0') <= 0) {
+ my $limit = splice(@args, 3, 1);
+ if ($limit > 0) {
+ my $receiver = pop @args;
+ push(@args, sub { &$receiver(@_) if (--$limit >= 0) });
+ }
+ }
+ my $ret = $self->SUPER::get_log(@args, $pool);
+ $pool->clear;
+ $ret;
+}
+
+sub trees_match {
+ my ($self, $url1, $rev1, $url2, $rev2) = @_;
+ my $ctx = SVN::Client->new(auth => _auth_providers);
+ my $out = IO::File->new_tmpfile;
+
+ # older SVN (1.1.x) doesn't take $pool as the last parameter for
+ # $ctx->diff(), so we'll create a default one
+ my $pool = SVN::Pool->new_default_sub;
+
+ $ra_invalid = 1; # this will open a new SVN::Ra connection to $url1
+ $ctx->diff([], $url1, $rev1, $url2, $rev2, 1, 1, 0, $out, $out);
+ $out->flush;
+ my $ret = (($out->stat)[7] == 0);
+ close $out or croak $!;
+
+ $ret;
+}
+
+sub get_commit_editor {
+ my ($self, $log, $cb, $pool) = @_;
+
+ my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef, 0) : ();
+ $self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
+}
+
+sub gs_do_update {
+ my ($self, $rev_a, $rev_b, $gs, $editor) = @_;
+ my $new = ($rev_a == $rev_b);
+ my $path = $gs->{path};
+
+ if ($new && -e $gs->{index}) {
+ unlink $gs->{index} or die
+ "Couldn't unlink index: $gs->{index}: $!\n";
+ }
+ my $pool = SVN::Pool->new;
+ $editor->set_path_strip($path);
+ my (@pc) = split m#/#, $path;
+ my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),
+ 1, $editor, $pool);
+ my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
+
+ # Since we can't rely on svn_ra_reparent being available, we'll
+ # just have to do some magic with set_path to make it so
+ # we only want a partial path.
+ my $sp = '';
+ my $final = join('/', @pc);
+ while (@pc) {
+ $reporter->set_path($sp, $rev_b, 0, @lock, $pool);
+ $sp .= '/' if length $sp;
+ $sp .= shift @pc;
+ }
+ die "BUG: '$sp' != '$final'\n" if ($sp ne $final);
+
+ $reporter->set_path($sp, $rev_a, $new, @lock, $pool);
+
+ $reporter->finish_report($pool);
+ $pool->clear;
+ $editor->{git_commit_ok};
+}
+
+# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and
+# svn_ra_reparent didn't work before 1.4)
+sub gs_do_switch {
+ my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_;
+ my $path = $gs->{path};
+ my $pool = SVN::Pool->new;
+
+ my $full_url = $self->{url};
+ my $old_url = $full_url;
+ $full_url .= '/' . $path if length $path;
+ my ($ra, $reparented);
+
+ if ($old_url =~ m#^svn(\+ssh)?://# ||
+ ($full_url =~ m#^https?://# &&
+ escape_url($full_url) ne $full_url)) {
+ $_[0] = undef;
+ $self = undef;
+ $RA = undef;
+ $ra = Git::SVN::Ra->new($full_url);
+ $ra_invalid = 1;
+ } elsif ($old_url ne $full_url) {
+ SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);
+ $self->{url} = $full_url;
+ $reparented = 1;
+ }
+
+ $ra ||= $self;
+ $url_b = escape_url($url_b);
+ my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
+ my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
+ $reporter->set_path('', $rev_a, 0, @lock, $pool);
+ $reporter->finish_report($pool);
+
+ if ($reparented) {
+ SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool);
+ $self->{url} = $old_url;
+ }
+
+ $pool->clear;
+ $editor->{git_commit_ok};
+}
+
+sub longest_common_path {
+ my ($gsv, $globs) = @_;
+ my %common;
+ my $common_max = scalar @$gsv;
+
+ foreach my $gs (@$gsv) {
+ my @tmp = split m#/#, $gs->{path};
+ my $p = '';
+ foreach (@tmp) {
+ $p .= length($p) ? "/$_" : $_;
+ $common{$p} ||= 0;
+ $common{$p}++;
+ }
+ }
+ $globs ||= [];
+ $common_max += scalar @$globs;
+ foreach my $glob (@$globs) {
+ my @tmp = split m#/#, $glob->{path}->{left};
+ my $p = '';
+ foreach (@tmp) {
+ $p .= length($p) ? "/$_" : $_;
+ $common{$p} ||= 0;
+ $common{$p}++;
+ }
+ }
+
+ my $longest_path = '';
+ foreach (sort {length $b <=> length $a} keys %common) {
+ if ($common{$_} == $common_max) {
+ $longest_path = $_;
+ last;
+ }
+ }
+ $longest_path;
+}
+
+sub gs_fetch_loop_common {
+ my ($self, $base, $head, $gsv, $globs) = @_;
+ return if ($base > $head);
+ my $inc = $_log_window_size;
+ my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
+ my $longest_path = longest_common_path($gsv, $globs);
+ my $ra_url = $self->{url};
+ my $find_trailing_edge;
+ while (1) {
+ my %revs;
+ my $err;
+ my $err_handler = $SVN::Error::handler;
+ $SVN::Error::handler = sub {
+ ($err) = @_;
+ skip_unknown_revs($err);
+ };
+ sub _cb {
+ my ($paths, $r, $author, $date, $log) = @_;
+ [ $paths,
+ { author => $author, date => $date, log => $log } ];
+ }
+ $self->get_log([$longest_path], $min, $max, 0, 1, 1,
+ sub { $revs{$_[1]} = _cb(@_) });
+ if ($err) {
+ print "Checked through r$max\r";
+ } else {
+ $find_trailing_edge = 1;
+ }
+ if ($err and $find_trailing_edge) {
+ print STDERR "Path '$longest_path' ",
+ "was probably deleted:\n",
+ $err->expanded_message,
+ "\nWill attempt to follow ",
+ "revisions r$min .. r$max ",
+ "committed before the deletion\n";
+ my $hi = $max;
+ while (--$hi >= $min) {
+ my $ok;
+ $self->get_log([$longest_path], $min, $hi,
+ 0, 1, 1, sub {
+ $ok = $_[1];
+ $revs{$_[1]} = _cb(@_) });
+ if ($ok) {
+ print STDERR "r$min .. r$ok OK\n";
+ last;
+ }
+ }
+ $find_trailing_edge = 0;
+ }
+ $SVN::Error::handler = $err_handler;
+
+ my %exists = map { $_->{path} => $_ } @$gsv;
+ foreach my $r (sort {$a <=> $b} keys %revs) {
+ my ($paths, $logged) = @{$revs{$r}};
+
+ foreach my $gs ($self->match_globs(\%exists, $paths,
+ $globs, $r)) {
+ if ($gs->rev_map_max >= $r) {
+ next;
+ }
+ next unless $gs->match_paths($paths, $r);
+ $gs->{logged_rev_props} = $logged;
+ if (my $last_commit = $gs->last_commit) {
+ $gs->assert_index_clean($last_commit);
+ }
+ my $log_entry = $gs->do_fetch($paths, $r);
+ if ($log_entry) {
+ $gs->do_git_commit($log_entry);
+ }
+ $Git::SVN::INDEX_FILES{$gs->{index}} = 1;
+ }
+ foreach my $g (@$globs) {
+ my $k = "svn-remote.$g->{remote}." .
+ "$g->{t}-maxRev";
+ Git::SVN::tmp_config($k, $r);
+ }
+ if ($ra_invalid) {
+ $_[0] = undef;
+ $self = undef;
+ $RA = undef;
+ $self = Git::SVN::Ra->new($ra_url);
+ $ra_invalid = undef;
+ }
+ }
+ # 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 $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";
+ Git::SVN::tmp_config($k, $max);
+ }
+ last if $max >= $head;
+ $min = $max + 1;
+ $max += $inc;
+ $max = $head if ($max > $head);
+ }
+ Git::SVN::gc();
+}
+
+sub get_dir_globbed {
+ my ($self, $left, $depth, $r) = @_;
+
+ my @x = eval { $self->get_dir($left, $r) };
+ return unless scalar @x == 3;
+ my $dirents = $x[0];
+ my @finalents;
+ foreach my $de (keys %$dirents) {
+ next if $dirents->{$de}->{kind} != $SVN::Node::dir;
+ if ($depth > 1) {
+ my @args = ("$left/$de", $depth - 1, $r);
+ foreach my $dir ($self->get_dir_globbed(@args)) {
+ push @finalents, "$de/$dir";
+ }
+ } else {
+ push @finalents, $de;
+ }
+ }
+ @finalents;
+}
+
+# return value: 0 -- don't ignore, 1 -- ignore
+sub is_ref_ignored {
+ my ($g, $p) = @_;
+ my $refname = $g->{ref}->full_path($p);
+ return 1 if defined($g->{ignore_refs_regex}) &&
+ $refname =~ m!$g->{ignore_refs_regex}!;
+ return 0 unless defined($_ignore_refs_regex);
+ return 1 if $refname =~ m!$_ignore_refs_regex!o;
+ return 0;
+}
+
+sub match_globs {
+ my ($self, $exists, $paths, $globs, $r) = @_;
+
+ sub get_dir_check {
+ my ($self, $exists, $g, $r) = @_;
+
+ my @dirs = $self->get_dir_globbed($g->{path}->{left},
+ $g->{path}->{depth},
+ $r);
+
+ foreach my $de (@dirs) {
+ my $p = $g->{path}->full_path($de);
+ next if $exists->{$p};
+ next if (length $g->{path}->{right} &&
+ ($self->check_path($p, $r) !=
+ $SVN::Node::dir));
+ next unless $p =~ /$g->{path}->{regex}/;
+ $exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
+ $g->{ref}->full_path($de), 1);
+ }
+ }
+ foreach my $g (@$globs) {
+ if (my $path = $paths->{"/$g->{path}->{left}"}) {
+ if ($path->{action} =~ /^[AR]$/) {
+ get_dir_check($self, $exists, $g, $r);
+ }
+ }
+ foreach (keys %$paths) {
+ if (/$g->{path}->{left_regex}/ &&
+ !/$g->{path}->{regex}/) {
+ next if $paths->{$_}->{action} !~ /^[AR]$/;
+ get_dir_check($self, $exists, $g, $r);
+ }
+ next unless /$g->{path}->{regex}/;
+ my $p = $1;
+ my $pathname = $g->{path}->full_path($p);
+ next if is_ref_ignored($g, $p);
+ next if $exists->{$pathname};
+ next if ($self->check_path($pathname, $r) !=
+ $SVN::Node::dir);
+ $exists->{$pathname} = Git::SVN->init(
+ $self->{url}, $pathname, undef,
+ $g->{ref}->full_path($p), 1);
+ }
+ my $c = '';
+ foreach (split m#/#, $g->{path}->{left}) {
+ $c .= "/$_";
+ next unless ($paths->{$c} &&
+ ($paths->{$c}->{action} =~ /^[AR]$/));
+ get_dir_check($self, $exists, $g, $r);
+ }
+ }
+ values %$exists;
+}
+
+sub minimize_url {
+ my ($self) = @_;
+ return $self->{url} if ($self->{url} eq $self->{repos_root});
+ my $url = $self->{repos_root};
+ my @components = split(m!/!, $self->{svn_path});
+ my $c = '';
+ do {
+ $url .= "/$c" if length $c;
+ eval {
+ my $ra = (ref $self)->new($url);
+ my $latest = $ra->get_latest_revnum;
+ $ra->get_log("", $latest, 0, 1, 0, 1, sub {});
+ };
+ } while ($@ && ($c = shift @components));
+ $url;
+}
+
+sub can_do_switch {
+ my $self = shift;
+ unless (defined $can_do_switch) {
+ my $pool = SVN::Pool->new;
+ my $rep = eval {
+ $self->do_switch(1, '', 0, $self->{url},
+ SVN::Delta::Editor->new, $pool);
+ };
+ if ($@) {
+ $can_do_switch = 0;
+ } else {
+ $rep->abort_report($pool);
+ $can_do_switch = 1;
+ }
+ $pool->clear;
+ }
+ $can_do_switch;
+}
+
+sub skip_unknown_revs {
+ my ($err) = @_;
+ my $errno = $err->apr_err();
+ # Maybe the branch we're tracking didn't
+ # exist when the repo started, so it's
+ # not an error if it doesn't, just continue
+ #
+ # Wonderfully consistent library, eh?
+ # 160013 - svn:// and file://
+ # 175002 - http(s)://
+ # 175007 - http(s):// (this repo required authorization, too...)
+ # More codes may be discovered later...
+ if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
+ my $err_key = $err->expanded_message;
+ # revision numbers change every time, filter them out
+ $err_key =~ s/\d+/\0/g;
+ $err_key = "$errno\0$err_key";
+ unless ($ignored_err{$err_key}) {
+ 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;
+ }
+ die "Error from SVN, ($errno): ", $err->expanded_message,"\n";
+}
+
+1;
+__END__
+
+Git::SVN::Ra - Subversion remote access functions for git-svn
+
+=head1 SYNOPSIS
+
+ use Git::SVN::Ra;
+
+ my $ra = Git::SVN::Ra->new($branchurl);
+ my ($dirents, $fetched_revnum, $props) =
+ $ra->get_dir('.', $SVN::Core::INVALID_REVNUM);
+
+=head1 DESCRIPTION
+
+This is a wrapper around the L<SVN::Ra> module for use by B<git-svn>.
+It fills in some default parameters (such as the authentication
+scheme), smooths over incompatibilities between libsvn versions, adds
+caching, and implements some functions specific to B<git-svn>.
+
+Do not use it unless you are developing git-svn. The interface will
+change as git-svn evolves.
+
+=head1 DEPENDENCIES
+
+Subversion perl bindings,
+L<Git::SVN>.
+
+C<Git::SVN::Ra> has not been tested using callers other than
+B<git-svn> itself.
+
+=head1 SEE ALSO
+
+L<SVN::Ra>.
+
+=head1 INCOMPATIBILITIES
+
+None reported.
+
+=head1 BUGS
+
+None.
diff --git a/perl/Makefile b/perl/Makefile
index a2ffb64..6ca7d47 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -2,9 +2,11 @@
# Makefile for perl support modules and routine
#
makfile:=perl.mak
+modules =
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
prefix_SQ = $(subst ','\'',$(prefix))
+localedir_SQ = $(subst ','\'',$(localedir))
ifndef V
QUIET = @
@@ -21,16 +23,47 @@ clean:
ifdef NO_PERL_MAKEMAKER
instdir_SQ = $(subst ','\'',$(prefix)/lib)
+
+modules += Git
+modules += Git/I18N
+modules += Git/SVN/Memoize/YAML
+modules += Git/SVN/Fetcher
+modules += Git/SVN/Editor
+modules += Git/SVN/Prompt
+modules += Git/SVN/Ra
+
$(makfile): ../GIT-CFLAGS Makefile
- echo all: private-Error.pm Git.pm > $@
- echo ' mkdir -p blib/lib' >> $@
- echo ' $(RM) blib/lib/Git.pm; cp Git.pm blib/lib/' >> $@
+ echo all: private-Error.pm Git.pm Git/I18N.pm > $@
+ set -e; \
+ for i in $(modules); \
+ do \
+ if test $$i = $${i%/*}; \
+ then \
+ subdir=; \
+ else \
+ subdir=/$${i%/*}; \
+ fi; \
+ echo ' $(RM) blib/lib/'$$i'.pm' >> $@; \
+ echo ' mkdir -p blib/lib'$$subdir >> $@; \
+ echo ' cp '$$i'.pm blib/lib/'$$i'.pm' >> $@; \
+ done
echo ' $(RM) blib/lib/Error.pm' >> $@
'$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \
echo ' cp private-Error.pm blib/lib/Error.pm' >> $@
echo install: >> $@
- echo ' mkdir -p "$$(DESTDIR)$(instdir_SQ)"' >> $@
- echo ' $(RM) "$$(DESTDIR)$(instdir_SQ)/Git.pm"; cp Git.pm "$$(DESTDIR)$(instdir_SQ)"' >> $@
+ set -e; \
+ for i in $(modules); \
+ do \
+ if test $$i = $${i%/*}; \
+ then \
+ subdir=; \
+ else \
+ subdir=/$${i%/*}; \
+ fi; \
+ echo ' $(RM) "$$(DESTDIR)$(instdir_SQ)/'$$i'.pm"' >> $@; \
+ echo ' mkdir -p "$$(DESTDIR)$(instdir_SQ)'$$subdir'"' >> $@; \
+ echo ' cp '$$i'.pm "$$(DESTDIR)$(instdir_SQ)/'$$i'.pm"' >> $@; \
+ done
echo ' $(RM) "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@
'$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \
echo ' cp private-Error.pm "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@
@@ -38,7 +71,7 @@ $(makfile): ../GIT-CFLAGS Makefile
echo ' echo $(instdir_SQ)' >> $@
else
$(makfile): Makefile.PL ../GIT-CFLAGS
- $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE=''
+ $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE='' --localedir='$(localedir_SQ)'
endif
# this is just added comfort for calling make directly in perl dir
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index 0b9deca..b54b04a 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -1,4 +1,12 @@
+use strict;
+use warnings;
use ExtUtils::MakeMaker;
+use Getopt::Long;
+
+# Sanity: die at first unknown option
+Getopt::Long::Configure qw/ pass_through /;
+
+GetOptions("localedir=s" => \my $localedir);
sub MY::postamble {
return <<'MAKE_FRAG';
@@ -16,7 +24,19 @@ endif
MAKE_FRAG
}
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+# XXX. When editing this list:
+#
+# * Please update perl/Makefile, too.
+# * Don't forget to test with NO_PERL_MAKEMAKER=YesPlease
+my %pm = (
+ 'Git.pm' => '$(INST_LIBDIR)/Git.pm',
+ 'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
+ 'Git/SVN/Memoize/YAML.pm' => '$(INST_LIBDIR)/Git/SVN/Memoize/YAML.pm',
+ 'Git/SVN/Fetcher.pm' => '$(INST_LIBDIR)/Git/SVN/Fetcher.pm',
+ 'Git/SVN/Editor.pm' => '$(INST_LIBDIR)/Git/SVN/Editor.pm',
+ 'Git/SVN/Prompt.pm' => '$(INST_LIBDIR)/Git/SVN/Prompt.pm',
+ 'Git/SVN/Ra.pm' => '$(INST_LIBDIR)/Git/SVN/Ra.pm',
+);
# We come with our own bundled Error.pm. It's not in the set of default
# Perl modules so install it if it's not available on the system yet.
@@ -33,6 +53,7 @@ WriteMakefile(
NAME => 'Git',
VERSION_FROM => 'Git.pm',
PM => \%pm,
+ PM_FILTER => qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
MAKEFILE => 'perl.mak',
INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
);
diff --git a/pkt-line.c b/pkt-line.c
index 5a04984..eaba15f 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -135,13 +135,19 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
strbuf_add(buf, buffer, n);
}
-static void safe_read(int fd, void *buffer, unsigned size)
+static int safe_read(int fd, void *buffer, unsigned size, int return_line_fail)
{
ssize_t ret = read_in_full(fd, buffer, size);
if (ret < 0)
die_errno("read error");
- else if (ret < size)
+ else if (ret < size) {
+ if (return_line_fail)
+ return -1;
+
die("The remote end hung up unexpectedly");
+ }
+
+ return ret;
}
static int packet_length(const char *linelen)
@@ -169,12 +175,14 @@ static int packet_length(const char *linelen)
return len;
}
-int packet_read_line(int fd, char *buffer, unsigned size)
+static int packet_read_internal(int fd, char *buffer, unsigned size, int return_line_fail)
{
- int len;
+ int len, ret;
char linelen[4];
- safe_read(fd, linelen, 4);
+ ret = safe_read(fd, linelen, 4, return_line_fail);
+ if (return_line_fail && ret < 0)
+ return ret;
len = packet_length(linelen);
if (len < 0)
die("protocol error: bad line length character: %.4s", linelen);
@@ -185,12 +193,24 @@ int packet_read_line(int fd, char *buffer, unsigned size)
len -= 4;
if (len >= size)
die("protocol error: bad line length %d", len);
- safe_read(fd, buffer, len);
+ ret = safe_read(fd, buffer, len, return_line_fail);
+ if (return_line_fail && ret < 0)
+ return ret;
buffer[len] = 0;
packet_trace(buffer, len, 0);
return len;
}
+int packet_read(int fd, char *buffer, unsigned size)
+{
+ return packet_read_internal(fd, buffer, size, 1);
+}
+
+int packet_read_line(int fd, char *buffer, unsigned size)
+{
+ return packet_read_internal(fd, buffer, size, 0);
+}
+
int packet_get_line(struct strbuf *out,
char **src_buf, size_t *src_len)
{
diff --git a/pkt-line.h b/pkt-line.h
index 1e5dcfe..8cfeb0c 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -13,6 +13,7 @@ void packet_buf_flush(struct strbuf *buf);
void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
int packet_read_line(int fd, char *buffer, unsigned size);
+int packet_read(int fd, char *buffer, unsigned size);
int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len);
ssize_t safe_write(int, const void *, ssize_t);
diff --git a/po/.gitignore b/po/.gitignore
index a242a86..796b96d 100644
--- a/po/.gitignore
+++ b/po/.gitignore
@@ -1 +1 @@
-/git.pot
+/build
diff --git a/po/README b/po/README
new file mode 100644
index 0000000..c1520e8
--- /dev/null
+++ b/po/README
@@ -0,0 +1,292 @@
+Core GIT Translations
+=====================
+
+This directory holds the translations for the core of Git. This document
+describes how you can contribute to the effort of enhancing the language
+coverage and maintaining the translation.
+
+The localization (l10n) coordinator, Jiang Xin <worldhello.net@gmail.com>,
+coordinates our localization effort in the l10 coordinator repository:
+
+ https://github.com/git-l10n/git-po/
+
+As a contributor for a language XX, you should first check TEAMS file in
+this directory to see whether a dedicated repository for your language XX
+exists. Fork the dedicated repository and start to work if it exists.
+
+If you are the first contributor for the language XX, please fork this
+repository, prepare and/or update the translated message file po/XX.po
+(described later), and ask the l10n coordinator to pull your work.
+
+If there are multiple contributors for the same language, please first
+coordinate among yourselves and nominate the team leader for your
+language, so that the l10n coordinator only needs to interact with one
+person per language.
+
+The overall data-flow looks like this:
+
+ +-------------------+ +------------------+
+ | Git source code | ---(1)---> | L10n coordinator |
+ | repository | <---(4)--- | repository |
+ +-------------------+ +------------------+
+ | ^
+ (2) (3)
+ V |
+ +------------------+
+ | Language Team XX |
+ +------------------+
+
+ * Translatable strings are marked in the source file.
+ * L10n coordinator pulls from the source (1)
+ * L10n coordinator updates the message template po/git.pot
+ * Language team pulls from L10n coordinator (2)
+ * Language team updates the message file po/XX.po
+ * L10n coordinator pulls from Language team (3)
+ * L10n coordinator asks the result to be pulled (4).
+
+
+Maintaining the po/git.pot file
+-------------------------------
+
+(This is done by the l10n coordinator).
+
+The po/git.pot file contains a message catalog extracted from Git's
+sources. The l10n coordinator maintains it by adding new translations with
+msginit(1), or update existing ones with msgmerge(1). In order to update
+the Git sources to extract the messages from, the l10n coordinator is
+expected to pull from the main git repository at strategic point in
+history (e.g. when a major release and release candidates are tagged),
+and then run "make pot" at the top-level directory.
+
+Language contributors use this file to prepare translations for their
+language, but they are not expected to modify it.
+
+
+Initializing a XX.po file
+-------------------------
+
+(This is done by the language teams).
+
+If your language XX does not have translated message file po/XX.po yet,
+you add a translation for the first time by running:
+
+ msginit --locale=XX
+
+in the po/ directory, where XX is the locale, e.g. "de", "is", "pt_BR",
+"zh_CN", etc.
+
+Then edit the automatically generated copyright info in your new XX.po
+to be correct, e.g. for Icelandic:
+
+ @@ -1,6 +1,6 @@
+ -# Icelandic translations for PACKAGE package.
+ -# Copyright (C) 2010 THE PACKAGE'S COPYRIGHT HOLDER
+ -# This file is distributed under the same license as the PACKAGE package.
+ +# Icelandic translations for Git.
+ +# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ +# This file is distributed under the same license as the Git package.
+ # Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
+
+And change references to PACKAGE VERSION in the PO Header Entry to
+just "Git":
+
+ perl -pi -e 's/(?<="Project-Id-Version: )PACKAGE VERSION/Git/' XX.po
+
+Once you are done testing the translation (see below), commit the result
+and ask the l10n coordinator to pull from you.
+
+
+Updating a XX.po file
+---------------------
+
+(This is done by the language teams).
+
+If you are replacing translation strings in an existing XX.po file to
+improve the translation, just edit the file.
+
+If there's an existing XX.po file for your language, but the repository
+of the l10n coordinator has newer po/git.pot file, you would need to first
+pull from the l10n coordinator (see the beginning of this document for its
+URL), and then update the existing translation by running:
+
+ msgmerge --add-location --backup=off -U XX.po git.pot
+
+in the po/ directory, where XX.po is the file you want to update.
+
+Once you are done testing the translation (see below), commit the result
+and ask the l10n coordinator to pull from you.
+
+
+Testing your changes
+--------------------
+
+(This is done by the language teams, after creating or updating XX.po file).
+
+Before you submit your changes go back to the top-level and do:
+
+ make
+
+On systems with GNU gettext (i.e. not Solaris) this will compile your
+changed PO file with `msgfmt --check`, the --check option flags many
+common errors, e.g. missing printf format strings, or translated
+messages that deviate from the originals in whether they begin/end
+with a newline or not.
+
+
+Marking strings for translation
+-------------------------------
+
+(This is done by the core developers).
+
+Before strings can be translated they first have to be marked for
+translation.
+
+Git uses an internationalization interface that wraps the system's
+gettext library, so most of the advice in your gettext documentation
+(on GNU systems `info gettext` in a terminal) applies.
+
+General advice:
+
+ - Don't mark everything for translation, only strings which will be
+ read by humans (the porcelain interface) should be translated.
+
+ The output from Git's plumbing utilities will primarily be read by
+ programs and would break scripts under non-C locales if it was
+ translated. Plumbing strings should not be translated, since
+ they're part of Git's API.
+
+ - Adjust the strings so that they're easy to translate. Most of the
+ advice in `info '(gettext)Preparing Strings'` applies here.
+
+ - If something is unclear or ambiguous you can use a "TRANSLATORS"
+ comment to tell the translators what to make of it. These will be
+ extracted by xgettext(1) and put in the po/*.po files, e.g. from
+ git-am.sh:
+
+ # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+ # in your translation. The program will only accept English
+ # input at this point.
+ gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+
+ Or in C, from builtin/revert.c:
+
+ /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+ die(_("%s: Unable to write new index file"), action_name(opts));
+
+We provide wrappers for C, Shell and Perl programs. Here's how they're
+used:
+
+C:
+
+ - Include builtin.h at the top, it'll pull in gettext.h, which
+ defines the gettext interface. Consult with the list if you need to
+ use gettext.h directly.
+
+ - The C interface is a subset of the normal GNU gettext
+ interface. We currently export these functions:
+
+ - _()
+
+ Mark and translate a string. E.g.:
+
+ printf(_("HEAD is now at %s"), hex);
+
+ - Q_()
+
+ Mark and translate a plural string. E.g.:
+
+ printf(Q_("%d commit", "%d commits", number_of_commits));
+
+ This is just a wrapper for the ngettext() function.
+
+ - N_()
+
+ A no-op pass-through macro for marking strings inside static
+ initializations, e.g.:
+
+ static const char *reset_type_names[] = {
+ N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
+ };
+
+ And then, later:
+
+ die(_("%s reset is not allowed in a bare repository"),
+ _(reset_type_names[reset_type]));
+
+ Here _() couldn't have statically determined what the translation
+ string will be, but since it was already marked for translation
+ with N_() the look-up in the message catalog will succeed.
+
+Shell:
+
+ - The Git gettext shell interface is just a wrapper for
+ gettext.sh. Import it right after git-sh-setup like this:
+
+ . git-sh-setup
+ . git-sh-i18n
+
+ And then use the gettext or eval_gettext functions:
+
+ # For constant interface messages:
+ gettext "A message for the user"; echo
+
+ # To interpolate variables:
+ details="oh noes"
+ eval_gettext "An error occured: \$details"; echo
+
+ In addition we have wrappers for messages that end with a trailing
+ newline. I.e. you could write the above as:
+
+ # For constant interface messages:
+ gettextln "A message for the user"
+
+ # To interpolate variables:
+ details="oh noes"
+ eval_gettextln "An error occured: \$details"
+
+ More documentation about the interface is available in the GNU info
+ page: `info '(gettext)sh'`. Looking at git-am.sh (the first shell
+ command to be translated) for examples is also useful:
+
+ git log --reverse -p --grep=i18n git-am.sh
+
+Perl:
+
+ - The Git::I18N module provides a limited subset of the
+ Locale::Messages functionality, e.g.:
+
+ use Git::I18N;
+ print __("Welcome to Git!\n");
+ printf __("The following error occured: %s\n"), $error;
+
+ Run `perldoc perl/Git/I18N.pm` for more info.
+
+
+Testing marked strings
+----------------------
+
+Even if you've correctly marked porcelain strings for translation
+something in the test suite might still depend on the US English
+version of the strings, e.g. to grep some error message or other
+output.
+
+To smoke out issues like these Git can be compiled with gettext poison
+support, at the top-level:
+
+ make GETTEXT_POISON=YesPlease
+
+That'll give you a git which emits gibberish on every call to
+gettext. It's obviously not meant to be installed, but you should run
+the test suite with it:
+
+ cd t && prove -j 9 ./t[0-9]*.sh
+
+If tests break with it you should inspect them manually and see if
+what you're translating is sane, i.e. that you're not translating
+plumbing output.
+
+If not you should replace calls to grep with test_i18ngrep, or
+test_cmp calls with test_i18ncmp. If that's not enough you can skip
+the whole test by making it depend on the C_LOCALE_OUTPUT
+prerequisite. See existing test files with this prerequisite for
+examples.
diff --git a/po/TEAMS b/po/TEAMS
new file mode 100644
index 0000000..e9ebc8c
--- /dev/null
+++ b/po/TEAMS
@@ -0,0 +1,47 @@
+Core Git translation language teams
+(please keep the list sorted alphabetically on language field)
+
+Language: da (Danish)
+Repository: https://github.com/git-da/git-po/
+Leader: Byrial Jensen <byrial@vip.cybercity.dk>
+
+Language: de (German)
+Repository: https://github.com/ralfth/git-po-de
+Leader: Ralf Thielow <ralf.thielow@googlemail.com>
+Members: Thomas Rast <trast@student.ethz.ch>
+ Jan Krüger <jk@jk.gs>
+ Christian Stimming <stimming@tuhh.de>
+
+Language: is (Icelandic)
+Leader: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+
+Language: it (Italian)
+Repository: https://github.com/quizzlo/git-po-it/
+Leader: Marco Paolone <marcopaolone AT gmail.com>
+Members: Stefano Lattarini <stefano.lattarini AT gmail.com>
+
+Language: nl (Dutch)
+Repository: https://github.com/vfr-nl/git-po/
+Leader: Vincent van Ravesteijn <vfr@lyx.org>
+
+Language: pt_PT (Portuguese - Portugal)
+Repository: https://github.com/marcomsousa/git-l10n-pt_PT/
+Leader: Marco Sousa <marcomsousa AT gmail.com>
+
+Language: sv (Swedish)
+Repository: https://github.com/nafmo/git-l10n-sv/
+Leader: Peter Krefting <peter@softwolves.pp.se>
+
+Language: vi (Vietnamese)
+Repository: https://github.com/vnwildman/git.git
+Leader: Trần Ngá»c Quân <vnwildman AT gmail.com>
+
+Language: zh_CN (Simplified Chinese)
+Repository: https://github.com/gotgit/git-po-zh_CN/
+Leader: Jiang Xin <worldhello.net@gmail.com>
+Members: Riku <lu.riku AT gmail.com>
+ Zhuang Ya <zhuangya AT me.com>
+ Lian Cheng <rhythm.mail AT gmail.com>
+ Yichao Yu <yyc1992 AT gmail.com>
+ ws3389 <willsmith3389 AT gmail.com>
+ Thynson <lanxingcan AT gmail.com>
diff --git a/po/da.po b/po/da.po
new file mode 100644
index 0000000..20a88ea
--- /dev/null
+++ b/po/da.po
@@ -0,0 +1,3503 @@
+# Danish translations for Git.
+# This file is distributed under the same license as the PACKAGE package.
+# Byrial Jensen <byrial@vip.cybercity.dk>, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-03-16 20:18+0800\n"
+"PO-Revision-Date: 2012-04-10 18:41+0200\n"
+"Last-Translator: Byrial Jensen <byrial@vip.cybercity.dk>\n"
+"Language-Team: Danish <dansk@dansk-gruppen.dk>\n"
+"Language: da\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"
+
+#: advice.c:34
+#, c-format
+msgid "hint: %.*s\n"
+msgstr ""
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:64
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+
+#: commit.c:47
+#, c-format
+msgid "could not parse %s"
+msgstr ""
+
+#: commit.c:49
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr ""
+
+#: compat/obstack.c:406 compat/obstack.c:408
+msgid "memory exhausted"
+msgstr ""
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr ""
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr ""
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr ""
+
+#: diff.c:105
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr ""
+
+#: diff.c:110
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr ""
+
+#: diff.c:210
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+
+#: diff.c:1336
+msgid " 0 files changed\n"
+msgstr ""
+
+#: diff.c:1340
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:1357
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:1368
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:3424
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr ""
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr ""
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr ""
+
+#: grep.c:1280
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr ""
+
+#: grep.c:1297
+#, c-format
+msgid "'%s': %s"
+msgstr ""
+
+#: grep.c:1308
+#, c-format
+msgid "'%s': short read %s"
+msgstr ""
+
+#: help.c:287
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+
+#: remote.c:1607
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1613
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural ""
+"Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1621
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: sequencer.c:120 builtin/merge.c:864 builtin/merge.c:985
+#: builtin/merge.c:1095 builtin/merge.c:1105
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr ""
+
+#: sequencer.c:122 builtin/merge.c:334 builtin/merge.c:867
+#: builtin/merge.c:1097 builtin/merge.c:1110
+#, c-format
+msgid "Could not write to '%s'"
+msgstr ""
+
+#: sequencer.c:143
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'"
+msgstr ""
+
+#: sequencer.c:146
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+
+#: sequencer.c:159 sequencer.c:685 sequencer.c:768
+#, c-format
+msgid "Could not write to %s"
+msgstr ""
+
+#: sequencer.c:162
+#, c-format
+msgid "Error wrapping up %s"
+msgstr ""
+
+#: sequencer.c:177
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr ""
+
+#: sequencer.c:179
+msgid "Your local changes would be overwritten by revert."
+msgstr ""
+
+#: sequencer.c:182
+msgid "Commit your changes or stash them to proceed."
+msgstr ""
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:232
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr ""
+
+#: sequencer.c:298
+msgid "Your index file is unmerged."
+msgstr ""
+
+#: sequencer.c:301
+msgid "You do not have a valid HEAD"
+msgstr ""
+
+#: sequencer.c:316
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr ""
+
+#: sequencer.c:324
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr ""
+
+#: sequencer.c:328
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr ""
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:339
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr ""
+
+#: sequencer.c:343
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr ""
+
+#: sequencer.c:427
+#, c-format
+msgid "could not revert %s... %s"
+msgstr ""
+
+#: sequencer.c:428
+#, c-format
+msgid "could not apply %s... %s"
+msgstr ""
+
+#: sequencer.c:450 sequencer.c:909 builtin/log.c:288 builtin/log.c:713
+#: builtin/log.c:1329 builtin/log.c:1548 builtin/merge.c:348
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr ""
+
+#: sequencer.c:453
+msgid "empty commit set passed"
+msgstr ""
+
+#: sequencer.c:461
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr ""
+
+#: sequencer.c:466
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr ""
+
+#: sequencer.c:551
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr ""
+
+#: sequencer.c:573
+#, c-format
+msgid "Could not parse line %d."
+msgstr ""
+
+#: sequencer.c:578
+msgid "No commits parsed."
+msgstr ""
+
+#: sequencer.c:591
+#, c-format
+msgid "Could not open %s"
+msgstr ""
+
+#: sequencer.c:595
+#, c-format
+msgid "Could not read %s."
+msgstr ""
+
+#: sequencer.c:602
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr ""
+
+#: sequencer.c:630
+#, c-format
+msgid "Invalid key: %s"
+msgstr ""
+
+#: sequencer.c:633
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr ""
+
+#: sequencer.c:645
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr ""
+
+#: sequencer.c:666
+msgid "a cherry-pick or revert is already in progress"
+msgstr ""
+
+#: sequencer.c:667
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr ""
+
+#: sequencer.c:671
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr ""
+
+#: sequencer.c:687 sequencer.c:772
+#, c-format
+msgid "Error wrapping up %s."
+msgstr ""
+
+#: sequencer.c:706 sequencer.c:840
+msgid "no cherry-pick or revert in progress"
+msgstr ""
+
+#: sequencer.c:708
+msgid "cannot resolve HEAD"
+msgstr ""
+
+#: sequencer.c:710
+msgid "cannot abort from a branch yet to be born"
+msgstr ""
+
+#: sequencer.c:732
+#, c-format
+msgid "cannot open %s: %s"
+msgstr ""
+
+#: sequencer.c:735
+#, c-format
+msgid "cannot read %s: %s"
+msgstr ""
+
+#: sequencer.c:736
+msgid "unexpected end of file"
+msgstr ""
+
+#: sequencer.c:742
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr ""
+
+#: sequencer.c:765
+#, c-format
+msgid "Could not format %s."
+msgstr ""
+
+#: sequencer.c:927
+msgid "Can't revert as initial commit"
+msgstr ""
+
+#: sequencer.c:928
+msgid "Can't cherry-pick into empty head"
+msgstr ""
+
+#: wt-status.c:134
+msgid "Unmerged paths:"
+msgstr ""
+
+#: wt-status.c:140 wt-status.c:157
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:142 wt-status.c:159
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:143
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr ""
+
+#: wt-status.c:151
+msgid "Changes to be committed:"
+msgstr ""
+
+#: wt-status.c:169
+msgid "Changes not staged for commit:"
+msgstr ""
+
+#: wt-status.c:173
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr ""
+
+#: wt-status.c:175
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr ""
+
+#: wt-status.c:176
+msgid ""
+" (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr ""
+
+#: wt-status.c:178
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr ""
+
+#: wt-status.c:187
+#, c-format
+msgid "%s files:"
+msgstr ""
+
+#: wt-status.c:190
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr ""
+
+#: wt-status.c:207
+msgid "bug"
+msgstr ""
+
+#: wt-status.c:212
+msgid "both deleted:"
+msgstr ""
+
+#: wt-status.c:213
+msgid "added by us:"
+msgstr ""
+
+#: wt-status.c:214
+msgid "deleted by them:"
+msgstr ""
+
+#: wt-status.c:215
+msgid "added by them:"
+msgstr ""
+
+#: wt-status.c:216
+msgid "deleted by us:"
+msgstr ""
+
+#: wt-status.c:217
+msgid "both added:"
+msgstr ""
+
+#: wt-status.c:218
+msgid "both modified:"
+msgstr ""
+
+#: wt-status.c:248
+msgid "new commits, "
+msgstr ""
+
+#: wt-status.c:250
+msgid "modified content, "
+msgstr ""
+
+#: wt-status.c:252
+msgid "untracked content, "
+msgstr ""
+
+#: wt-status.c:266
+#, c-format
+msgid "new file: %s"
+msgstr ""
+
+#: wt-status.c:269
+#, c-format
+msgid "copied: %s -> %s"
+msgstr ""
+
+#: wt-status.c:272
+#, c-format
+msgid "deleted: %s"
+msgstr ""
+
+#: wt-status.c:275
+#, c-format
+msgid "modified: %s"
+msgstr ""
+
+#: wt-status.c:278
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr ""
+
+#: wt-status.c:281
+#, c-format
+msgid "typechange: %s"
+msgstr ""
+
+#: wt-status.c:284
+#, c-format
+msgid "unknown: %s"
+msgstr ""
+
+#: wt-status.c:287
+#, c-format
+msgid "unmerged: %s"
+msgstr ""
+
+#: wt-status.c:290
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr ""
+
+#: wt-status.c:713
+msgid "On branch "
+msgstr ""
+
+#: wt-status.c:720
+msgid "Not currently on any branch."
+msgstr ""
+
+#: wt-status.c:731
+msgid "Initial commit"
+msgstr ""
+
+#: wt-status.c:745
+msgid "Untracked"
+msgstr ""
+
+#: wt-status.c:747
+msgid "Ignored"
+msgstr ""
+
+#: wt-status.c:749
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr ""
+
+#: wt-status.c:751
+msgid " (use -u option to show untracked files)"
+msgstr ""
+
+#: wt-status.c:757
+msgid "No changes"
+msgstr ""
+
+#: wt-status.c:761
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr ""
+
+#: wt-status.c:763
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr ""
+
+#: wt-status.c:765
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr ""
+
+#: wt-status.c:767
+msgid " (use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:769 wt-status.c:772 wt-status.c:775
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr ""
+
+#: wt-status.c:770
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:773
+msgid " (use -u to show untracked files)"
+msgstr ""
+
+#: wt-status.c:776
+msgid " (working directory clean)"
+msgstr ""
+
+#: wt-status.c:884
+msgid "HEAD (no branch)"
+msgstr ""
+
+#: wt-status.c:890
+msgid "Initial commit on "
+msgstr ""
+
+#: wt-status.c:905
+msgid "behind "
+msgstr ""
+
+#: wt-status.c:908 wt-status.c:911
+msgid "ahead "
+msgstr ""
+
+#: wt-status.c:913
+msgid ", behind "
+msgstr ""
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr ""
+
+#: builtin/add.c:67 builtin/commit.c:298
+msgid "updating files failed"
+msgstr ""
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr ""
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr ""
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr ""
+
+#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr ""
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr ""
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr ""
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr ""
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr ""
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr ""
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr ""
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr ""
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr ""
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr ""
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr ""
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr ""
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr ""
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr ""
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr ""
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr ""
+
+#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:358 builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr ""
+
+#: builtin/add.c:476 builtin/mv.c:229 builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr ""
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr ""
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr ""
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr ""
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr ""
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr ""
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr ""
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr ""
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr ""
+
+#: builtin/branch.c:137
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+
+#: builtin/branch.c:141
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+
+#. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
+#: builtin/branch.c:163
+msgid "remote "
+msgstr ""
+
+#: builtin/branch.c:171
+msgid "cannot use -a with -d"
+msgstr ""
+
+#: builtin/branch.c:177
+msgid "Couldn't look up commit object for HEAD"
+msgstr ""
+
+#: builtin/branch.c:182
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr ""
+
+#: builtin/branch.c:192
+#, c-format
+msgid "%sbranch '%s' not found."
+msgstr ""
+
+#: builtin/branch.c:200
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr ""
+
+#: builtin/branch.c:206
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+
+#: builtin/branch.c:214
+#, c-format
+msgid "Error deleting %sbranch '%s'"
+msgstr ""
+
+#: builtin/branch.c:219
+#, c-format
+msgid "Deleted %sbranch %s (was %s).\n"
+msgstr ""
+
+#: builtin/branch.c:224
+msgid "Update of config-file failed"
+msgstr ""
+
+#: builtin/branch.c:322
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr ""
+
+#: builtin/branch.c:394
+#, c-format
+msgid "behind %d] "
+msgstr ""
+
+#: builtin/branch.c:396
+#, c-format
+msgid "ahead %d] "
+msgstr ""
+
+#: builtin/branch.c:398
+#, c-format
+msgid "ahead %d, behind %d] "
+msgstr ""
+
+#: builtin/branch.c:501
+msgid "(no branch)"
+msgstr ""
+
+#: builtin/branch.c:566
+msgid "some refs could not be read"
+msgstr ""
+
+#: builtin/branch.c:579
+msgid "cannot rename the current branch while not on any."
+msgstr ""
+
+#: builtin/branch.c:589
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr ""
+
+#: builtin/branch.c:604
+msgid "Branch rename failed"
+msgstr ""
+
+#: builtin/branch.c:608
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr ""
+
+#: builtin/branch.c:612
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr ""
+
+#: builtin/branch.c:619
+msgid "Branch is renamed, but update of config-file failed"
+msgstr ""
+
+#: builtin/branch.c:634
+#, c-format
+msgid "malformed object name %s"
+msgstr ""
+
+#: builtin/branch.c:658
+#, c-format
+msgid "could not write branch description template: %s\n"
+msgstr ""
+
+#: builtin/branch.c:746
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr ""
+
+#: builtin/branch.c:751 builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr ""
+
+#: builtin/branch.c:809
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr ""
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr ""
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr ""
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr ""
+
+#: builtin/checkout.c:113 builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr ""
+
+#: builtin/checkout.c:115 builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr ""
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr ""
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr ""
+
+#: builtin/checkout.c:212 builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr ""
+
+#: builtin/checkout.c:234 builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr ""
+
+#: builtin/checkout.c:264 builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr ""
+
+#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
+#: builtin/merge.c:811
+msgid "unable to write new index file"
+msgstr ""
+
+#: builtin/checkout.c:319 builtin/diff.c:302 builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr ""
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr ""
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:565
+msgid "HEAD is now at"
+msgstr ""
+
+#: builtin/checkout.c:572
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:575
+#, c-format
+msgid "Already on '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:579
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:581
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:583
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:639
+#, c-format
+msgid " ... and %d more.\n"
+msgstr ""
+
+#. The singular version
+#: builtin/checkout.c:645
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/checkout.c:663
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+
+#: builtin/checkout.c:692
+msgid "internal error in revision walk"
+msgstr ""
+
+#: builtin/checkout.c:696
+msgid "Previous HEAD position was"
+msgstr ""
+
+#: builtin/checkout.c:722
+msgid "You are on a branch yet to be born"
+msgstr ""
+
+#. case (1)
+#: builtin/checkout.c:853
+#, c-format
+msgid "invalid reference: %s"
+msgstr ""
+
+#. case (1): want a tree
+#: builtin/checkout.c:892
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr ""
+
+#: builtin/checkout.c:972
+msgid "-B cannot be used with -b"
+msgstr ""
+
+#: builtin/checkout.c:981
+msgid "--patch is incompatible with all other options"
+msgstr ""
+
+#: builtin/checkout.c:984
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr ""
+
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -t"
+msgstr ""
+
+#: builtin/checkout.c:992
+msgid "--track needs a branch name"
+msgstr ""
+
+#: builtin/checkout.c:999
+msgid "Missing branch name; try -b"
+msgstr ""
+
+#: builtin/checkout.c:1005
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr ""
+
+#: builtin/checkout.c:1007
+msgid "--orphan cannot be used with -t"
+msgstr ""
+
+#: builtin/checkout.c:1017
+msgid "git checkout: -f and -m are incompatible"
+msgstr ""
+
+#: builtin/checkout.c:1051
+msgid "invalid path specification"
+msgstr ""
+
+#: builtin/checkout.c:1059
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+
+#: builtin/checkout.c:1061
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr ""
+
+#: builtin/checkout.c:1066
+msgid "git checkout: --detach does not take a path argument"
+msgstr ""
+
+#: builtin/checkout.c:1069
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+
+#: builtin/checkout.c:1088
+msgid "Cannot switch branch to a non-commit."
+msgstr ""
+
+#: builtin/checkout.c:1091
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr ""
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr ""
+
+#: builtin/clean.c:82
+msgid ""
+"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+
+#: builtin/clean.c:85
+msgid ""
+"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
+"clean"
+msgstr ""
+
+#: builtin/clean.c:155 builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:159 builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr ""
+
+#: builtin/clean.c:162 builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr ""
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr ""
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr ""
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr ""
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr ""
+
+#: builtin/clone.c:308 builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr ""
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr ""
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr ""
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr ""
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr ""
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr ""
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr ""
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr ""
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr ""
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr ""
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr ""
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr ""
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr ""
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr ""
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr ""
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr ""
+
+#: builtin/clone.c:706 builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr ""
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr ""
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr ""
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr ""
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr ""
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr ""
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr ""
+
+#: builtin/commit.c:42
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+
+#: builtin/commit.c:54
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+
+#: builtin/commit.c:59
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+
+#: builtin/commit.c:205 builtin/reset.c:33
+msgid "merge"
+msgstr ""
+
+#: builtin/commit.c:208
+msgid "cherry-pick"
+msgstr ""
+
+#: builtin/commit.c:325
+msgid "failed to unpack HEAD tree object"
+msgstr ""
+
+#: builtin/commit.c:367
+msgid "unable to create temporary index"
+msgstr ""
+
+#: builtin/commit.c:373
+msgid "interactive add failed"
+msgstr ""
+
+#: builtin/commit.c:406 builtin/commit.c:427 builtin/commit.c:473
+msgid "unable to write new_index file"
+msgstr ""
+
+#: builtin/commit.c:457
+#, c-format
+msgid "cannot do a partial commit during a %s."
+msgstr ""
+
+#: builtin/commit.c:466
+msgid "cannot read the index"
+msgstr ""
+
+#: builtin/commit.c:486
+msgid "unable to write temporary index file"
+msgstr ""
+
+#: builtin/commit.c:550 builtin/commit.c:556
+#, c-format
+msgid "invalid commit: %s"
+msgstr ""
+
+#: builtin/commit.c:579
+msgid "malformed --author parameter"
+msgstr ""
+
+#: builtin/commit.c:635
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr ""
+
+#: builtin/commit.c:670 builtin/commit.c:703 builtin/commit.c:1000
+#, c-format
+msgid "could not lookup commit %s"
+msgstr ""
+
+#: builtin/commit.c:682 builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr ""
+
+#: builtin/commit.c:684
+msgid "could not read log from standard input"
+msgstr ""
+
+#: builtin/commit.c:688
+#, c-format
+msgid "could not read log file '%s'"
+msgstr ""
+
+#: builtin/commit.c:694
+msgid "commit has empty message"
+msgstr ""
+
+#: builtin/commit.c:710
+msgid "could not read MERGE_MSG"
+msgstr ""
+
+#: builtin/commit.c:714
+msgid "could not read SQUASH_MSG"
+msgstr ""
+
+#: builtin/commit.c:718
+#, c-format
+msgid "could not read '%s'"
+msgstr ""
+
+#: builtin/commit.c:746
+#, c-format
+msgid "could not open '%s'"
+msgstr ""
+
+#: builtin/commit.c:770
+msgid "could not write commit template"
+msgstr ""
+
+#: builtin/commit.c:783
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a %s.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+
+#: builtin/commit.c:796
+msgid "Please enter the commit message for your changes."
+msgstr ""
+
+#: builtin/commit.c:799
+msgid ""
+" Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+
+#: builtin/commit.c:804
+msgid ""
+" Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+
+#: builtin/commit.c:816
+#, c-format
+msgid "%sAuthor: %s"
+msgstr ""
+
+#: builtin/commit.c:823
+#, c-format
+msgid "%sCommitter: %s"
+msgstr ""
+
+#: builtin/commit.c:843
+msgid "Cannot read index"
+msgstr ""
+
+#: builtin/commit.c:880
+msgid "Error building trees"
+msgstr ""
+
+#: builtin/commit.c:895 builtin/tag.c:357
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr ""
+
+#: builtin/commit.c:975
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr ""
+
+#: builtin/commit.c:990 builtin/commit.c:1182
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr ""
+
+#: builtin/commit.c:1030
+msgid "Using both --reset-author and --author does not make sense"
+msgstr ""
+
+#: builtin/commit.c:1041
+msgid "You have nothing to amend."
+msgstr ""
+
+#: builtin/commit.c:1043
+#, c-format
+msgid "You are in the middle of a %s -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1045
+msgid "Options --squash and --fixup cannot be used together"
+msgstr ""
+
+#: builtin/commit.c:1055
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr ""
+
+#: builtin/commit.c:1057
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr ""
+
+#: builtin/commit.c:1063
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr ""
+
+#: builtin/commit.c:1080
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr ""
+
+#: builtin/commit.c:1082
+msgid "No paths with --include/--only does not make sense."
+msgstr ""
+
+#: builtin/commit.c:1084
+msgid "Clever... amending the last one with dirty index."
+msgstr ""
+
+#: builtin/commit.c:1086
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr ""
+
+#: builtin/commit.c:1096 builtin/tag.c:556
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr ""
+
+#: builtin/commit.c:1101
+msgid "Paths with -a does not make sense."
+msgstr ""
+
+#: builtin/commit.c:1280
+msgid "couldn't look up newly created commit"
+msgstr ""
+
+#: builtin/commit.c:1282
+msgid "could not parse newly created commit"
+msgstr ""
+
+#: builtin/commit.c:1323
+msgid "detached HEAD"
+msgstr ""
+
+#: builtin/commit.c:1325
+msgid " (root-commit)"
+msgstr ""
+
+#: builtin/commit.c:1415
+msgid "could not parse HEAD commit"
+msgstr ""
+
+#: builtin/commit.c:1452 builtin/merge.c:509
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr ""
+
+#: builtin/commit.c:1459
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr ""
+
+#: builtin/commit.c:1466
+msgid "could not read MERGE_MODE"
+msgstr ""
+
+#: builtin/commit.c:1485
+#, c-format
+msgid "could not read commit message: %s"
+msgstr ""
+
+#: builtin/commit.c:1499
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr ""
+
+#: builtin/commit.c:1514 builtin/merge.c:935 builtin/merge.c:968
+msgid "failed to write commit object"
+msgstr ""
+
+#: builtin/commit.c:1535
+msgid "cannot lock HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1539
+msgid "cannot update HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1550
+msgid ""
+"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."
+msgstr ""
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr ""
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr ""
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr ""
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr ""
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr ""
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr ""
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr ""
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr ""
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr ""
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr ""
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr ""
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr ""
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr ""
+
+#: builtin/diff.c:297
+msgid "Not a git repository"
+msgstr ""
+
+#: builtin/diff.c:347
+#, c-format
+msgid "invalid object '%s' given."
+msgstr ""
+
+#: builtin/diff.c:352
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:362
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr ""
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr ""
+
+#: builtin/fetch.c:252
+#, c-format
+msgid "object %s not found"
+msgstr ""
+
+#: builtin/fetch.c:258
+msgid "[up to date]"
+msgstr ""
+
+#: builtin/fetch.c:272
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr ""
+
+#: builtin/fetch.c:273 builtin/fetch.c:351
+msgid "[rejected]"
+msgstr ""
+
+#: builtin/fetch.c:284
+msgid "[tag update]"
+msgstr ""
+
+#: builtin/fetch.c:286 builtin/fetch.c:313 builtin/fetch.c:331
+msgid " (unable to update local ref)"
+msgstr ""
+
+#: builtin/fetch.c:298
+msgid "[new tag]"
+msgstr ""
+
+#: builtin/fetch.c:302
+msgid "[new branch]"
+msgstr ""
+
+#: builtin/fetch.c:347
+msgid "unable to update local ref"
+msgstr ""
+
+#: builtin/fetch.c:347
+msgid "forced update"
+msgstr ""
+
+#: builtin/fetch.c:353
+msgid "(non-fast-forward)"
+msgstr ""
+
+#: builtin/fetch.c:384 builtin/fetch.c:676
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr ""
+
+#: builtin/fetch.c:393
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr ""
+
+#: builtin/fetch.c:479
+#, c-format
+msgid "From %.*s\n"
+msgstr ""
+
+#: builtin/fetch.c:490
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+
+#: builtin/fetch.c:540
+#, c-format
+msgid " (%s will become dangling)\n"
+msgstr ""
+
+#: builtin/fetch.c:541
+#, c-format
+msgid " (%s has become dangling)\n"
+msgstr ""
+
+#: builtin/fetch.c:548
+msgid "[deleted]"
+msgstr ""
+
+#: builtin/fetch.c:549
+msgid "(none)"
+msgstr ""
+
+#: builtin/fetch.c:666
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr ""
+
+#: builtin/fetch.c:700
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr ""
+
+#: builtin/fetch.c:777
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr ""
+
+#: builtin/fetch.c:780
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr ""
+
+#: builtin/fetch.c:879
+#, c-format
+msgid "Fetching %s\n"
+msgstr ""
+
+#: builtin/fetch.c:881
+#, c-format
+msgid "Could not fetch %s"
+msgstr ""
+
+#: builtin/fetch.c:898
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr ""
+
+#: builtin/fetch.c:918
+msgid "You need to specify a tag name."
+msgstr ""
+
+#: builtin/fetch.c:970
+msgid "fetch --all does not take a repository argument"
+msgstr ""
+
+#: builtin/fetch.c:972
+msgid "fetch --all does not make sense with refspecs"
+msgstr ""
+
+#: builtin/fetch.c:983
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr ""
+
+#: builtin/fetch.c:991
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr ""
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr ""
+
+#: builtin/gc.c:78
+msgid "Too many options specified"
+msgstr ""
+
+#: builtin/gc.c:103
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr ""
+
+#: builtin/gc.c:223
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr ""
+
+#: builtin/gc.c:226
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+
+#: builtin/gc.c:256
+msgid ""
+"There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr ""
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr ""
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr ""
+
+#: builtin/grep.c:478 builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr ""
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr ""
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr ""
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr ""
+
+#: builtin/grep.c:888
+msgid "no pattern given."
+msgstr ""
+
+#: builtin/grep.c:902
+#, c-format
+msgid "bad object %s"
+msgstr ""
+
+#: builtin/grep.c:943
+msgid "--open-files-in-pager only works on the worktree"
+msgstr ""
+
+#: builtin/grep.c:966
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr ""
+
+#: builtin/grep.c:971
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr ""
+
+#: builtin/grep.c:974
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr ""
+
+#: builtin/grep.c:982
+msgid "both --cached and trees are given."
+msgstr ""
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr ""
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr ""
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr ""
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr ""
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr ""
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr ""
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr ""
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr ""
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr ""
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr ""
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr ""
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr ""
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr ""
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr ""
+
+#: builtin/init-db.c:322 builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr ""
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr ""
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr ""
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr ""
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr ""
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr ""
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr ""
+
+#: builtin/init-db.c:521 builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr ""
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr ""
+
+#: builtin/init-db.c:554
+#, c-format
+msgid ""
+"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
+"dir=<directory>)"
+msgstr ""
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr ""
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr ""
+
+#: builtin/log.c:187
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr ""
+
+#: builtin/log.c:395 builtin/log.c:483
+#, c-format
+msgid "Could not read object %s"
+msgstr ""
+
+#: builtin/log.c:507
+#, c-format
+msgid "Unknown type: %d"
+msgstr ""
+
+#: builtin/log.c:596
+msgid "format.headers without value"
+msgstr ""
+
+#: builtin/log.c:669
+msgid "name of output directory is too long"
+msgstr ""
+
+#: builtin/log.c:680
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr ""
+
+#: builtin/log.c:694
+msgid "Need exactly one range."
+msgstr ""
+
+#: builtin/log.c:702
+msgid "Not a range."
+msgstr ""
+
+#: builtin/log.c:739
+msgid "Could not extract email from committer identity."
+msgstr ""
+
+#: builtin/log.c:785
+msgid "Cover letter needs email format"
+msgstr ""
+
+#: builtin/log.c:879
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr ""
+
+#: builtin/log.c:952
+msgid "Two output directories?"
+msgstr ""
+
+#: builtin/log.c:1173
+#, c-format
+msgid "bogus committer info %s"
+msgstr ""
+
+#: builtin/log.c:1218
+msgid "-n and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1220
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1225 builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr ""
+
+#: builtin/log.c:1228
+msgid "--name-only does not make sense"
+msgstr ""
+
+#: builtin/log.c:1230
+msgid "--name-status does not make sense"
+msgstr ""
+
+#: builtin/log.c:1232
+msgid "--check does not make sense"
+msgstr ""
+
+#: builtin/log.c:1255
+msgid "standard output, or directory, which one?"
+msgstr ""
+
+#: builtin/log.c:1257
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr ""
+
+#: builtin/log.c:1410
+msgid "Failed to create output files"
+msgstr ""
+
+#: builtin/log.c:1514
+#, c-format
+msgid ""
+"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr ""
+
+#: builtin/log.c:1530 builtin/log.c:1532 builtin/log.c:1544
+#, c-format
+msgid "Unknown commit %s"
+msgstr ""
+
+#: builtin/merge.c:91
+msgid "switch `m' requires a value"
+msgstr ""
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr ""
+
+#: builtin/merge.c:129
+#, c-format
+msgid "Available strategies are:"
+msgstr ""
+
+#: builtin/merge.c:134
+#, c-format
+msgid "Available custom strategies are:"
+msgstr ""
+
+#: builtin/merge.c:241
+msgid "could not run stash."
+msgstr ""
+
+#: builtin/merge.c:246
+msgid "stash failed"
+msgstr ""
+
+#: builtin/merge.c:251
+#, c-format
+msgid "not a valid object: %s"
+msgstr ""
+
+#: builtin/merge.c:270 builtin/merge.c:287
+msgid "read-tree failed"
+msgstr ""
+
+#: builtin/merge.c:317
+msgid " (nothing to squash)"
+msgstr ""
+
+#: builtin/merge.c:330
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:362
+msgid "Writing SQUASH_MSG"
+msgstr ""
+
+#: builtin/merge.c:364
+msgid "Finishing SQUASH_MSG"
+msgstr ""
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr ""
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr ""
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr ""
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr ""
+
+#: builtin/merge.c:696
+msgid "Unable to write index."
+msgstr ""
+
+#: builtin/merge.c:709
+msgid "Not handling anything other than two heads merge."
+msgstr ""
+
+#: builtin/merge.c:723
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr ""
+
+#: builtin/merge.c:737
+#, c-format
+msgid "unable to write %s"
+msgstr ""
+
+#: builtin/merge.c:876
+#, c-format
+msgid "Could not read from '%s'"
+msgstr ""
+
+#: builtin/merge.c:885
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:891
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+
+#: builtin/merge.c:915
+msgid "Empty commit message."
+msgstr ""
+
+#: builtin/merge.c:927
+#, c-format
+msgid "Wonderful.\n"
+msgstr ""
+
+#: builtin/merge.c:1000
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr ""
+
+#: builtin/merge.c:1016
+#, c-format
+msgid "'%s' is not a commit"
+msgstr ""
+
+#: builtin/merge.c:1057
+msgid "No current branch."
+msgstr ""
+
+#: builtin/merge.c:1059
+msgid "No remote for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1061
+msgid "No default upstream defined for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1066
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr ""
+
+#: builtin/merge.c:1188
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr ""
+
+#: builtin/merge.c:1204 git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1207 git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1211
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1214
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1223
+msgid "You cannot combine --squash with --no-ff."
+msgstr ""
+
+#: builtin/merge.c:1228
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr ""
+
+#: builtin/merge.c:1235
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr ""
+
+#: builtin/merge.c:1266
+msgid "Can merge only exactly one commit into empty head"
+msgstr ""
+
+#: builtin/merge.c:1269
+msgid "Squash commit into empty head not supported yet"
+msgstr ""
+
+#: builtin/merge.c:1271
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr ""
+
+#: builtin/merge.c:1275 builtin/merge.c:1319
+#, c-format
+msgid "%s - not something we can merge"
+msgstr ""
+
+#: builtin/merge.c:1385
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr ""
+
+#: builtin/merge.c:1423
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr ""
+
+#: builtin/merge.c:1430
+#, c-format
+msgid "Nope.\n"
+msgstr ""
+
+#: builtin/merge.c:1462
+msgid "Not possible to fast-forward, aborting."
+msgstr ""
+
+#: builtin/merge.c:1485 builtin/merge.c:1562
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr ""
+
+#: builtin/merge.c:1489
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr ""
+
+#: builtin/merge.c:1553
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:1555
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr ""
+
+#: builtin/merge.c:1564
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr ""
+
+#: builtin/merge.c:1575
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr ""
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr ""
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr ""
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr ""
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr ""
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr ""
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr ""
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr ""
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr ""
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr ""
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr ""
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr ""
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr ""
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr ""
+
+#: builtin/mv.c:215
+#, c-format
+msgid "renaming '%s' failed"
+msgstr ""
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr ""
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:175 builtin/tag.c:343
+#, c-format
+msgid "could not create file '%s'"
+msgstr ""
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr ""
+
+#: builtin/notes.c:210 builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr ""
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr ""
+
+#: builtin/notes.c:251 builtin/tag.c:521
+#, c-format
+msgid "cannot read '%s'"
+msgstr ""
+
+#: builtin/notes.c:253 builtin/tag.c:524
+#, c-format
+msgid "could not open or read '%s'"
+msgstr ""
+
+#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
+#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
+#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
+#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:537
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr ""
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr ""
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr ""
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr ""
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr ""
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr ""
+
+#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
+#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr ""
+
+#: builtin/notes.c:513 builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr ""
+
+#: builtin/notes.c:580
+#, c-format
+msgid ""
+"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:585 builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr ""
+
+#: builtin/notes.c:656
+#, c-format
+msgid ""
+"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr ""
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr ""
+
+#: builtin/notes.c:1103
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2310
+#, c-format
+msgid "unsupported index version %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2314
+#, c-format
+msgid "bad index version '%s'"
+msgstr ""
+
+#: builtin/pack-objects.c:2322
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr ""
+
+#: builtin/pack-objects.c:2326
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr ""
+
+#: builtin/push.c:44
+msgid "tag shorthand without <tag>"
+msgstr ""
+
+#: builtin/push.c:63
+msgid "--delete only accepts plain target ref names"
+msgstr ""
+
+#: builtin/push.c:73
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+
+#: builtin/push.c:80
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+
+#: builtin/push.c:88
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr ""
+
+#: builtin/push.c:111
+msgid ""
+"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr ""
+
+#: builtin/push.c:131
+#, c-format
+msgid "Pushing to %s\n"
+msgstr ""
+
+#: builtin/push.c:135
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr ""
+
+#: builtin/push.c:143
+#, c-format
+msgid ""
+"To prevent you from losing history, non-fast-forward updates were rejected\n"
+"Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"
+"'Note about fast-forwards' section of 'git push --help' for details.\n"
+msgstr ""
+
+#: builtin/push.c:160
+#, c-format
+msgid "bad repository '%s'"
+msgstr ""
+
+#: builtin/push.c:161
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote "
+"repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+
+#: builtin/push.c:176
+msgid "--all and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:177
+msgid "--all can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:182
+msgid "--mirror and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:183
+msgid "--mirror can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:188
+msgid "--all and --mirror are incompatible"
+msgstr ""
+
+#: builtin/push.c:274
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr ""
+
+#: builtin/push.c:276
+msgid "--delete doesn't make sense without any refs"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr ""
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr ""
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr ""
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr ""
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr ""
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr ""
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr ""
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr ""
+
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr ""
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr ""
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr ""
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr ""
+
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr ""
+
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr ""
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr ""
+
+#: builtin/revert.c:70 builtin/revert.c:91
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr ""
+
+#: builtin/revert.c:126
+msgid "program error"
+msgstr ""
+
+#: builtin/revert.c:209
+msgid "revert failed"
+msgstr ""
+
+#: builtin/revert.c:224
+msgid "cherry-pick failed"
+msgstr ""
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr ""
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr ""
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr ""
+
+#: builtin/tag.c:58
+#, c-format
+msgid "malformed object at '%s'"
+msgstr ""
+
+#: builtin/tag.c:205
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr ""
+
+#: builtin/tag.c:210
+#, c-format
+msgid "tag '%s' not found."
+msgstr ""
+
+#: builtin/tag.c:225
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr ""
+
+#: builtin/tag.c:237
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr ""
+
+#: builtin/tag.c:247
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+
+#: builtin/tag.c:254
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you "
+"want to.\n"
+"#\n"
+msgstr ""
+
+#: builtin/tag.c:294
+msgid "unable to sign the tag"
+msgstr ""
+
+#: builtin/tag.c:296
+msgid "unable to write tag file"
+msgstr ""
+
+#: builtin/tag.c:321
+msgid "bad object type."
+msgstr ""
+
+#: builtin/tag.c:334
+msgid "tag header too big."
+msgstr ""
+
+#: builtin/tag.c:366
+msgid "no tag message?"
+msgstr ""
+
+#: builtin/tag.c:372
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr ""
+
+#: builtin/tag.c:421
+msgid "switch 'points-at' requires an object"
+msgstr ""
+
+#: builtin/tag.c:423
+#, c-format
+msgid "malformed object name '%s'"
+msgstr ""
+
+#: builtin/tag.c:502
+msgid "-n option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:504
+msgid "--contains option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:506
+msgid "--points-at option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:514
+msgid "only one -F or -m option is allowed."
+msgstr ""
+
+#: builtin/tag.c:534
+msgid "too many params"
+msgstr ""
+
+#: builtin/tag.c:540
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr ""
+
+#: builtin/tag.c:545
+#, c-format
+msgid "tag '%s' already exists"
+msgstr ""
+
+#: builtin/tag.c:563
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr ""
+
+#: builtin/tag.c:565
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr ""
+
+#: builtin/tag.c:567
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr ""
+
+#: git-am.sh:49
+msgid "You need to set your committer info first"
+msgstr ""
+
+#: git-am.sh:136
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr ""
+
+#: git-am.sh:147
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+
+#: git-am.sh:156
+msgid "Falling back to patching base and 3-way merge..."
+msgstr ""
+
+#: git-am.sh:268
+msgid "Only one StGIT patch series can be applied at once"
+msgstr ""
+
+#: git-am.sh:355
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr ""
+
+#: git-am.sh:357
+msgid "Patch format detection failed."
+msgstr ""
+
+#: git-am.sh:411
+msgid "-d option is no longer supported. Do not use."
+msgstr ""
+
+#: git-am.sh:474
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr ""
+
+#: git-am.sh:479
+msgid "Please make up your mind. --skip or --abort?"
+msgstr ""
+
+#: git-am.sh:506
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr ""
+
+#: git-am.sh:572
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr ""
+
+#: git-am.sh:748
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:759
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr ""
+
+#: git-am.sh:795
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr ""
+
+#: git-am.sh:840
+msgid "No changes -- Patch already applied."
+msgstr ""
+
+#: git-am.sh:866
+msgid "applying to an empty history"
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr ""
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr ""
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr ""
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr ""
+
+#: git-bisect.sh:130
+#, sh-format
+msgid ""
+"Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr ""
+
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr ""
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr ""
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr ""
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr ""
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr ""
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr ""
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr ""
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr ""
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr ""
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr ""
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr ""
+
+#: git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr ""
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr ""
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr ""
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr ""
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr ""
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr ""
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr ""
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr ""
+
+#: git-stash.sh:123 git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr ""
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr ""
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr ""
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr ""
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr ""
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr ""
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr ""
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr ""
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr ""
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr ""
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr ""
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr ""
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr ""
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr ""
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr ""
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr ""
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr ""
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr ""
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr ""
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr ""
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr ""
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr ""
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr ""
+
+#: git-submodule.sh:108
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$path'"
+msgstr ""
+
+#: git-submodule.sh:149
+#, sh-format
+msgid "Clone of '$url' into submodule path '$path' failed"
+msgstr ""
+
+#: git-submodule.sh:159
+#, sh-format
+msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
+msgstr ""
+
+#: git-submodule.sh:247
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr ""
+
+#: git-submodule.sh:264
+#, sh-format
+msgid "'$path' already exists in the index"
+msgstr ""
+
+#: git-submodule.sh:281
+#, sh-format
+msgid "'$path' already exists and is not a valid git repo"
+msgstr ""
+
+#: git-submodule.sh:295
+#, sh-format
+msgid "Unable to checkout submodule '$path'"
+msgstr ""
+
+#: git-submodule.sh:300
+#, sh-format
+msgid "Failed to add submodule '$path'"
+msgstr ""
+
+#: git-submodule.sh:305
+#, sh-format
+msgid "Failed to register submodule '$path'"
+msgstr ""
+
+#: git-submodule.sh:347
+#, sh-format
+msgid "Entering '$prefix$path'"
+msgstr ""
+
+#: git-submodule.sh:359
+#, sh-format
+msgid "Stopping at '$path'; script returned non-zero status."
+msgstr ""
+
+#: git-submodule.sh:401
+#, sh-format
+msgid "No url found for submodule path '$path' in .gitmodules"
+msgstr ""
+
+#: git-submodule.sh:410
+#, sh-format
+msgid "Failed to register url for submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:418
+#, sh-format
+msgid "Failed to register update mode for submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:420
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$path'"
+msgstr ""
+
+#: git-submodule.sh:519
+#, sh-format
+msgid ""
+"Submodule path '$path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+
+#: git-submodule.sh:532
+#, sh-format
+msgid "Unable to find current revision in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:551
+#, sh-format
+msgid "Unable to fetch in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:565
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:566
+#, sh-format
+msgid "Submodule path '$path': rebased into '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:571
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:572
+#, sh-format
+msgid "Submodule path '$path': merged in '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:577
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:578
+#, sh-format
+msgid "Submodule path '$path': checked out '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:600 git-submodule.sh:923
+#, sh-format
+msgid "Failed to recurse into submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:708
+msgid "--"
+msgstr ""
+
+#: git-submodule.sh:766
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr ""
+
+#: git-submodule.sh:769
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr ""
+
+#: git-submodule.sh:772
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr ""
+
+#: git-submodule.sh:797
+msgid "blob"
+msgstr ""
+
+#: git-submodule.sh:798
+msgid "submodule"
+msgstr ""
+
+#: git-submodule.sh:969
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr ""
diff --git a/po/de.po b/po/de.po
new file mode 100644
index 0000000..70d8418
--- /dev/null
+++ b/po/de.po
@@ -0,0 +1,5647 @@
+# German translations for Git.
+# Copyright (C) 2010-2012 Ralf Thielow <ralf.thielow@googlemail.com>
+# This file is distributed under the same license as the Git package.
+# Ralf Thielow <ralf.thielow@googlemail.com>, 2010, 2011, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git 1.7.11\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-06-08 10:20+0800\n"
+"PO-Revision-Date: 2012-03-28 18:46+0200\n"
+"Last-Translator: Ralf Thielow <ralf.thielow@googlemail.com>\n"
+"Language-Team: German\n"
+"Language: de\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"
+
+#: advice.c:40
+#, c-format
+msgid "hint: %.*s\n"
+msgstr "Hinweis: %.*s\n"
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:70
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+"Korrigiere dies im Arbeitsbaum,\n"
+"und benutze dann 'git add/rm <Datei>'\n"
+"um die Auflösung entsprechend zu markieren und einzutragen,\n"
+"oder benutze 'git commit -a'."
+
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr "'%s' sieht nicht wie eine v2 Paketdatei aus"
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr "nicht erkannter Kopfbereich: %s%s (%d)"
+
+#: bundle.c:89 builtin/commit.c:696
+#, c-format
+msgid "could not open '%s'"
+msgstr "Konnte '%s' nicht öffnen"
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr "Dem Projektarchiv fehlen folgende vorrausgesetzte Versionen:"
+
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
+#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr "Einrichtung des Revisionsgangs fehlgeschlagen"
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] "Das Paket enthält %d Referenz"
+msgstr[1] "Das Paket enthält %d Referenzen"
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] "Das Paket benötigt diese Referenz"
+msgstr[1] "Das Paket benötigt diese %d Referenzen"
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr "\"rev-list\" abgebrochen"
+
+#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr "nicht erkanntes Argument: %s"
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr "Referenz '%s' wird durch \"rev-list\" Optionen ausgeschlossen"
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr "Erstellung eines leeren Pakets zurückgewiesen."
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr "Konnte Paketobjekte nicht erstellen"
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr "Erstellung der Paketobjekte abgebrochen"
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr "kann '%s' nicht erstellen"
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr "Erstellung der Paketindexdatei abgebrochen"
+
+#: commit.c:48
+#, c-format
+msgid "could not parse %s"
+msgstr "konnte %s nicht parsen"
+
+#: commit.c:50
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr "%s %s ist keine Version!"
+
+#: compat/obstack.c:406 compat/obstack.c:408
+msgid "memory exhausted"
+msgstr "Speicher verbraucht"
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr "Konnte 'git rev-list' nicht ausführen"
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr "Fehler beim Schreiben nach rev-list: %s"
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr "Fehler beim Schließen von rev-list's Standard-Eingabe: %s"
+
+#: date.c:95
+msgid "in the future"
+msgstr "in der Zukunft"
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] "vor %lu Sekunde"
+msgstr[1] "vor %lu Sekunden"
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] "vor %lu Minute"
+msgstr[1] "vor %lu Minuten"
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] "vor %lu Stunde"
+msgstr[1] "vor %lu Stunden"
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] "vor %lu Tag"
+msgstr[1] "vor %lu Tagen"
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] "vor %lu Woche"
+msgstr[1] "vor %lu Wochen"
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] "vor %lu Monat"
+msgstr[1] "vor %lu Monaten"
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] "vor %lu Jahr"
+msgstr[1] "vor %lu Jahren"
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] "%s, und %lu Monat"
+msgstr[1] "%s, und %lu Monaten"
+
+#: date.c:154 date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] "vor %lu Jahr"
+msgstr[1] "vor %lu Jahren"
+
+#: diff.c:105
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr ""
+" Fehler beim Parsen des abgeschnittenen \"dirstat\" Prozentsatzes '%.*s'\n"
+
+#: diff.c:110
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr " Unbekannter \"dirstat\" Parameter '%.*s'\n"
+
+#: diff.c:210
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+"Fehler in 'diff.dirstat' Konfigurationsvariable gefunden:\n"
+"%s"
+
+#: diff.c:1400
+msgid " 0 files changed\n"
+msgstr " 0 Dateien geändert\n"
+
+#: diff.c:1404
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] " %d Datei geändert"
+msgstr[1] " %d Dateien geändert"
+
+#: diff.c:1421
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ", %d Zeile hinzugefügt(+)"
+msgstr[1] ", %d Zeilen hinzugefügt(+)"
+
+#: diff.c:1432
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ", %d Zeile entfernt(-)"
+msgstr[1] ", %d Zeilen entfernt(-)"
+
+#: diff.c:3478
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+"Fehler beim Parsen des --dirstat/-X Optionsparameters:\n"
+"%s"
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr "konnte gpg nicht ausführen"
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr "gpg hat die Daten nicht akzeptiert"
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr "gpg beim Signieren der Daten fehlgeschlagen"
+
+#: grep.c:1320
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr "'%s': konnte nicht lesen %s"
+
+#: grep.c:1337
+#, c-format
+msgid "'%s': %s"
+msgstr "'%s': %s"
+
+#: grep.c:1348
+#, c-format
+msgid "'%s': short read %s"
+msgstr "'%s': read() zu kurz %s"
+
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr "Vorhandene Git-Kommandos in '%s'"
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr "Vorhandene Git-Kommandos irgendwo in deinem $PATH"
+
+#: help.c:270
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+"'%s' scheint ein git-Kommando zu sein, konnte aber\n"
+"nicht ausgeführt werden. Vielleicht ist git-%s fehlerhaft?"
+
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr "Uh oh. Keine Git-Kommandos auf deinem System vorhanden."
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+"Warnung: Du hast das nicht existierende Git-Kommando '%s' ausgeführt.\n"
+"Setze fort unter der Annahme das du '%s' gemeint hast"
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr "automatisch in %0.1f Sekunden..."
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr "git: '%s' ist kein Git-Kommando. Siehe 'git --help'."
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+"\n"
+"Hast du das gemeint?"
+msgstr[1] ""
+"\n"
+"Hast du eines von diesen gemeint?"
+
+#: parse-options.c:493
+msgid "..."
+msgstr "..."
+
+#: parse-options.c:511
+#, c-format
+msgid "usage: %s"
+msgstr "Verwendung: %s"
+
+#. TRANSLATORS: the colon here should align with the
+#. one in "usage: %s" translation
+#: parse-options.c:515
+#, c-format
+msgid " or: %s"
+msgstr " oder: %s"
+
+#: parse-options.c:518
+#, c-format
+msgid " %s"
+msgstr " %s"
+
+#: remote.c:1629
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] "Dein Zweig ist vor '%s' um %d Version.\n"
+msgstr[1] "Dein Zweig ist vor '%s' um %d Versionen.\n"
+
+#: remote.c:1635
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural ""
+"Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] ""
+"Dein Zweig ist zu '%s' um %d Version hinterher, und kann vorgespult werden.\n"
+msgstr[1] ""
+"Dein Zweig ist zu '%s' um %d Versionen hinterher, und kann vorgespult "
+"werden.\n"
+
+#: remote.c:1643
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+"Dein Zweig und '%s' sind divergiert,\n"
+"und haben jeweils %d und %d unterschiedliche Versionen.\n"
+msgstr[1] ""
+"Dein Zweig und '%s' sind divergiert,\n"
+"und haben jeweils %d und %d unterschiedliche Versionen.\n"
+
+#: sequencer.c:121 builtin/merge.c:865 builtin/merge.c:978
+#: builtin/merge.c:1088 builtin/merge.c:1098
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr "Konnte '%s' nicht zum Schreiben öffnen."
+
+#: sequencer.c:123 builtin/merge.c:333 builtin/merge.c:868
+#: builtin/merge.c:1090 builtin/merge.c:1103
+#, c-format
+msgid "Could not write to '%s'"
+msgstr "Konnte nicht nach '%s' schreiben."
+
+#: sequencer.c:144
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'"
+msgstr ""
+"nach Auflösung der Konflikte, markiere die korrigierten Pfade\n"
+"mit 'git add <Pfade>' oder 'git rm <Pfade>'"
+
+#: sequencer.c:147
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+"nach Auflösung der Konflikte, markiere die korrigierten Pfade\n"
+"mit 'git add <Pfade>' oder 'git rm <Pfade>'und trage das Ergebnis ein mit "
+"'git commit'"
+
+#: sequencer.c:160 sequencer.c:758 sequencer.c:841
+#, c-format
+msgid "Could not write to %s"
+msgstr "Konnte nicht nach %s schreiben"
+
+#: sequencer.c:163
+#, c-format
+msgid "Error wrapping up %s"
+msgstr "Fehler bei Nachbereitung von %s"
+
+#: sequencer.c:178
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr ""
+"Deine lokalen Änderungen würden von \"cherry-pick\" überschrieben werden."
+
+#: sequencer.c:180
+msgid "Your local changes would be overwritten by revert."
+msgstr "Deine lokalen Änderungen würden von \"revert\" überschrieben werden."
+
+#: sequencer.c:183
+msgid "Commit your changes or stash them to proceed."
+msgstr "Trage deine Änderungen ein oder benutze \"stash\" um fortzufahren."
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:233
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr "%s: Konnte neue Bereitstellungsdatei nicht schreiben"
+
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr "Konnte Version der Zweigspitze (HEAD) nicht auflösen\n"
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr "Konnte zwischengespeicherten Baum nicht aktualisieren\n"
+
+#: sequencer.c:324
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr "Konnte Version %s nicht parsen\n"
+
+#: sequencer.c:329
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr "Konnte Elternversion %s nicht parsen\n"
+
+#: sequencer.c:395
+msgid "Your index file is unmerged."
+msgstr "Deine Bereitstellungsdatei ist nicht zusammengeführt."
+
+#: sequencer.c:398
+msgid "You do not have a valid HEAD"
+msgstr "Du hast keine gültige Zweigspitze (HEAD)"
+
+#: sequencer.c:413
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr ""
+"Version %s ist eine Zusammenführung, aber die Option -m wurde nicht "
+"angegeben."
+
+#: sequencer.c:421
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr "Version %s hat keinen Elternteil %d"
+
+#: sequencer.c:425
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr ""
+"Hauptlinie wurde spezifiziert, aber Version %s ist keine Zusammenführung."
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:436
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr "%s: kann Elternversion %s nicht parsen"
+
+#: sequencer.c:440
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr "Kann keine Versionsbeschreibung für %s bekommen"
+
+#: sequencer.c:524
+#, c-format
+msgid "could not revert %s... %s"
+msgstr "Konnte %s nicht zurücksetzen... %s"
+
+#: sequencer.c:525
+#, c-format
+msgid "could not apply %s... %s"
+msgstr "Konnte %s nicht anwenden... %s"
+
+#: sequencer.c:553
+msgid "empty commit set passed"
+msgstr "leere Menge von Versionen übergeben"
+
+#: sequencer.c:561
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr "git %s: Fehler beim Lesen der Bereitstellung"
+
+#: sequencer.c:566
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr "git %s: Fehler beim Aktualisieren der Bereitstellung"
+
+#: sequencer.c:624
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr "Kann %s nicht während eines %s durchführen"
+
+#: sequencer.c:646
+#, c-format
+msgid "Could not parse line %d."
+msgstr "Konnte Zeile %d nicht parsen."
+
+#: sequencer.c:651
+msgid "No commits parsed."
+msgstr "Keine Versionen geparst."
+
+#: sequencer.c:664
+#, c-format
+msgid "Could not open %s"
+msgstr "Konnte %s nicht öffnen"
+
+#: sequencer.c:668
+#, c-format
+msgid "Could not read %s."
+msgstr "Konnte %s nicht lesen."
+
+#: sequencer.c:675
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr "Unbenutzbares Instruktionsblatt: %s"
+
+#: sequencer.c:703
+#, c-format
+msgid "Invalid key: %s"
+msgstr "Ungültiger Schlüssel: %s"
+
+#: sequencer.c:706
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr "Ungültiger Wert für %s: %s"
+
+#: sequencer.c:718
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr "Fehlerhaftes Optionsblatt: %s"
+
+#: sequencer.c:739
+msgid "a cherry-pick or revert is already in progress"
+msgstr "\"cherry-pick\" oder \"revert\" ist bereits im Gang"
+
+#: sequencer.c:740
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr "versuche \"git cherry-pick (--continue | --quit | --abort)\""
+
+#: sequencer.c:744
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr "Konnte \"sequencer\"-Verzeichnis %s nicht erstellen"
+
+#: sequencer.c:760 sequencer.c:845
+#, c-format
+msgid "Error wrapping up %s."
+msgstr "Fehler beim Einpacken von %s."
+
+#: sequencer.c:779 sequencer.c:913
+msgid "no cherry-pick or revert in progress"
+msgstr "kein \"cherry-pick\" oder \"revert\" im Gang"
+
+#: sequencer.c:781
+msgid "cannot resolve HEAD"
+msgstr "kann Zweigspitze (HEAD) nicht auflösen"
+
+#: sequencer.c:783
+msgid "cannot abort from a branch yet to be born"
+msgstr "kann nicht abbrechen: bin auf einem Zweig, der noch geboren wird"
+
+#: sequencer.c:805 builtin/apply.c:3697
+#, c-format
+msgid "cannot open %s: %s"
+msgstr "Kann %s nicht öffnen: %s"
+
+#: sequencer.c:808
+#, c-format
+msgid "cannot read %s: %s"
+msgstr "Kann %s nicht lesen: %s"
+
+#: sequencer.c:809
+msgid "unexpected end of file"
+msgstr "Unerwartetes Dateiende"
+
+#: sequencer.c:815
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr ""
+"gespeicherte \"pre-cherry-pick\" Datei der Zweigspitze (HEAD) '%s' ist "
+"beschädigt"
+
+#: sequencer.c:838
+#, c-format
+msgid "Could not format %s."
+msgstr "Konnte %s nicht formatieren."
+
+#: sequencer.c:1000
+msgid "Can't revert as initial commit"
+msgstr "Kann nicht zu initialer Version zurücksetzen."
+
+#: sequencer.c:1001
+msgid "Can't cherry-pick into empty head"
+msgstr "Kann \"cherry-pick\" nicht in einem leerem Kopf ausführen."
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "Zweigspitze (HEAD) zeigt auf keinen Zweig"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "Kein solcher Zweig '%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "Kein entferntes Projektarchiv für Zweig '%s' konfiguriert."
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr ""
+"Zweig '%s' des entfernten Projektarchivs ist kein gefolgter Ãœbernahmezweig"
+
+#: wrapper.c:413
+#, c-format
+msgid "unable to look up current user in the passwd file: %s"
+msgstr "konnte aktuellen Benutzer nicht in Passwort-Datei finden: %s"
+
+#: wrapper.c:414
+msgid "no such user"
+msgstr "kein solcher Benutzer"
+
+#: wt-status.c:135
+msgid "Unmerged paths:"
+msgstr "Nicht zusammengeführte Pfade:"
+
+#: wt-status.c:141 wt-status.c:158
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr ""
+" (benutze \"git reset %s <Datei>...\" zum Herausnehmen aus der "
+"Bereitstellung)"
+
+#: wt-status.c:143 wt-status.c:160
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr ""
+" (benutze \"git rm --cached <Datei>...\" zum Herausnehmen aus der "
+"Bereitstellung)"
+
+#: wt-status.c:144
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr ""
+" (benutze \"git add/rm <Datei>...\" um die Auflösung entsprechend zu "
+"markieren)"
+
+#: wt-status.c:152
+msgid "Changes to be committed:"
+msgstr "zum Eintragen bereitgestellte Änderungen:"
+
+#: wt-status.c:170
+msgid "Changes not staged for commit:"
+msgstr "Änderungen, die nicht zum Eintragen bereitgestellt sind:"
+
+#: wt-status.c:174
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr " (benutze \"git add <Datei>...\" zum Bereitstellen)"
+
+#: wt-status.c:176
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr " (benutze \"git add/rm <Datei>...\" zum Bereitstellen)"
+
+#: wt-status.c:177
+msgid ""
+" (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr ""
+" (benutze \"git checkout -- <Datei>...\" um die Änderungen im "
+"Arbeitsverzeichnis zu verwerfen)"
+
+#: wt-status.c:179
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr ""
+" (trage ein oder verwerfe den unbeobachteten oder geänderten Inhalt in den "
+"Unterprojekten)"
+
+#: wt-status.c:188
+#, c-format
+msgid "%s files:"
+msgstr "%s Dateien:"
+
+#: wt-status.c:191
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr " (benutze \"git %s <Datei>...\" zum Einfügen in die Eintragung)"
+
+#: wt-status.c:208
+msgid "bug"
+msgstr "Fehler"
+
+#: wt-status.c:213
+msgid "both deleted:"
+msgstr "beide gelöscht:"
+
+#: wt-status.c:214
+msgid "added by us:"
+msgstr "von uns hinzugefügt:"
+
+#: wt-status.c:215
+msgid "deleted by them:"
+msgstr "von denen gelöscht:"
+
+#: wt-status.c:216
+msgid "added by them:"
+msgstr "von denen hinzugefügt:"
+
+#: wt-status.c:217
+msgid "deleted by us:"
+msgstr "von uns gelöscht:"
+
+#: wt-status.c:218
+msgid "both added:"
+msgstr "von beiden hinzugefügt:"
+
+#: wt-status.c:219
+msgid "both modified:"
+msgstr "von beiden geändert:"
+
+#: wt-status.c:249
+msgid "new commits, "
+msgstr "neue Versionen, "
+
+#: wt-status.c:251
+msgid "modified content, "
+msgstr "geänderter Inhalt, "
+
+#: wt-status.c:253
+msgid "untracked content, "
+msgstr "unbeobachteter Inhalt, "
+
+#: wt-status.c:267
+#, c-format
+msgid "new file: %s"
+msgstr "neue Datei: %s"
+
+#: wt-status.c:270
+#, c-format
+msgid "copied: %s -> %s"
+msgstr "kopiert: %s -> %s"
+
+#: wt-status.c:273
+#, c-format
+msgid "deleted: %s"
+msgstr "gelöscht: %s"
+
+#: wt-status.c:276
+#, c-format
+msgid "modified: %s"
+msgstr "geändert: %s"
+
+#: wt-status.c:279
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr "umbenannt: %s -> %s"
+
+#: wt-status.c:282
+#, c-format
+msgid "typechange: %s"
+msgstr "Typänderung: %s"
+
+#: wt-status.c:285
+#, c-format
+msgid "unknown: %s"
+msgstr "unbekannt: %s"
+
+#: wt-status.c:288
+#, c-format
+msgid "unmerged: %s"
+msgstr "nicht zusammengeführt: %s"
+
+#: wt-status.c:291
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr "Fehler: unbehandelter Differenz-Status %c"
+
+#: wt-status.c:737
+msgid "On branch "
+msgstr "Auf Zweig "
+
+#: wt-status.c:744
+msgid "Not currently on any branch."
+msgstr "Im Moment auf keinem Zweig."
+
+#: wt-status.c:755
+msgid "Initial commit"
+msgstr "Initiale Version"
+
+#: wt-status.c:769
+msgid "Untracked"
+msgstr "Unbeobachtete"
+
+#: wt-status.c:771
+msgid "Ignored"
+msgstr "Ignorierte"
+
+#: wt-status.c:773
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr "Unbeobachtete Dateien nicht aufgelistet%s"
+
+#: wt-status.c:775
+msgid " (use -u option to show untracked files)"
+msgstr " (benutze die Option -u um unbeobachteten Dateien anzuzeigen)"
+
+#: wt-status.c:781
+msgid "No changes"
+msgstr "Keine Änderungen"
+
+#: wt-status.c:785
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr "keine Änderungen zum Eintragen hinzugefügt%s\n"
+
+#: wt-status.c:787
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr " (benutze \"git add\" und/oder \"git commit -a\")"
+
+#: wt-status.c:789
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr ""
+"nichts zum Eintragen hinzugefügt, aber es gibt unbeobachtete Dateien%s\n"
+
+#: wt-status.c:791
+msgid " (use \"git add\" to track)"
+msgstr " (benutze \"git add\" zum Beobachten)"
+
+#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr "nichts zum Eintragen%s\n"
+
+#: wt-status.c:794
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr " (Erstelle/Kopiere Dateien und benutze \"git add\" zum Beobachten)"
+
+#: wt-status.c:797
+msgid " (use -u to show untracked files)"
+msgstr " (benutze die Option -u um unbeobachtete Dateien anzuzeigen)"
+
+#: wt-status.c:800
+msgid " (working directory clean)"
+msgstr " (Arbeitsverzeichnis sauber)"
+
+#: wt-status.c:908
+msgid "HEAD (no branch)"
+msgstr "HEAD (kein Zweig)"
+
+#: wt-status.c:914
+msgid "Initial commit on "
+msgstr "Initiale Version auf "
+
+#: wt-status.c:929
+msgid "behind "
+msgstr "hinterher "
+
+#: wt-status.c:932 wt-status.c:935
+msgid "ahead "
+msgstr "voraus "
+
+#: wt-status.c:937
+msgid ", behind "
+msgstr ", hinterher "
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr "unerwarteter Differenz-Status %c"
+
+#: builtin/add.c:67 builtin/commit.c:226
+msgid "updating files failed"
+msgstr "Aktualisierung der Dateien fehlgeschlagen"
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr "entferne '%s'\n"
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr "Pfad '%s' befindet sich in Unterprojekt '%.*s'"
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr ""
+"Nicht bereitgestellte Änderungen nach Aktualisierung der Bereitstellung:"
+
+#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr "Pfadspezifikation '%s' stimmt mit keinen Dateien überein"
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr "'%s' ist über einem symbolischen Link"
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr "Konnte die Bereitstellung nicht lesen"
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr "Konnte '%s' nicht zum Schreiben öffnen."
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr "Konnte Patch nicht schreiben"
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr "Konnte Verzeichnis '%s' nicht lesen"
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr "Leerer Patch. Abgebrochen."
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr "Konnte '%s' nicht anwenden."
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr ""
+"Die folgenden Pfade werden durch eine deiner \".gitignore\" Dateien "
+"ignoriert:\n"
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr "Verwende -f wenn du diese wirklich hinzufügen möchtest.\n"
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr "keine Dateien hinzugefügt"
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr "Hinzufügen von Dateien fehlgeschlagen"
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr "-A und -u sind zueinander inkompatibel"
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr ""
+"Die Option --ignore-missing kann nur zusammen mit --dry-run benutzt werden."
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr "Nichts spezifiziert, nichts hinzugefügt.\n"
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr "Wolltest du vielleicht 'git add .' sagen?\n"
+
+#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:286 builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr "Bereitstellungsdatei beschädigt"
+
+#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr "Konnte neue Bereitstellungsdatei nicht schreiben."
+
+#: builtin/apply.c:53
+msgid "git apply [options] [<patch>...]"
+msgstr "git apply [Optionen] [<Patch>...]"
+
+#: builtin/apply.c:106
+#, c-format
+msgid "unrecognized whitespace option '%s'"
+msgstr "nicht erkannte Option für Leerzeichen: '%s'"
+
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
+msgstr "nicht erkannte Option zum Ignorieren von Leerzeichen: '%s'"
+
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
+msgstr "Kann regulären Ausdruck für Zeitstempel %s nicht verarbeiten"
+
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
+msgstr "Ausführung des regulären Ausdrucks gab %d zurück. Eingabe: %s"
+
+#: builtin/apply.c:905
+#, c-format
+msgid "unable to find filename in patch at line %d"
+msgstr "Konnte keinen Dateinamen in Zeile %d des Patches finden."
+
+#: builtin/apply.c:937
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
+msgstr ""
+"git apply: ungültiges 'git-diff' - erwartete /dev/null, erhielt %s in Zeile "
+"%d"
+
+#: builtin/apply.c:941
+#, c-format
+msgid "git apply: bad git-diff - inconsistent new filename on line %d"
+msgstr ""
+"git apply: ungültiges 'git-diff' - Inkonsistenter neuer Dateiname in Zeile %d"
+
+#: builtin/apply.c:942
+#, c-format
+msgid "git apply: bad git-diff - inconsistent old filename on line %d"
+msgstr ""
+"git apply: ungültiges 'git-diff' - Inkonsistenter alter Dateiname in Zeile %d"
+
+#: builtin/apply.c:949
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
+msgstr "git apply: ungültiges 'git-diff' - erwartete /dev/null in Zeile %d"
+
+#: builtin/apply.c:1394
+#, c-format
+msgid "recount: unexpected line: %.*s"
+msgstr "recount: unerwartete Zeile: %.*s"
+
+#: builtin/apply.c:1451
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
+msgstr "Patch-Fragment ohne Kopfbereich bei Zeile %d: %.*s"
+
+#: builtin/apply.c:1468
+#, c-format
+msgid ""
+"git diff header lacks filename information when removing %d leading pathname "
+"component (line %d)"
+msgid_plural ""
+"git diff header lacks filename information when removing %d leading pathname "
+"components (line %d)"
+msgstr[0] ""
+"Dem Kopfbereich von \"git diff\" fehlen Informationen zum Dateinamen, wenn "
+"%d vorangestellter Teil des Pfades entfernt wird (Zeile %d)"
+msgstr[1] ""
+"Dem Kopfbereich von \"git diff\" fehlen Informationen zum Dateinamen, wenn "
+"%d vorangestellte Teile des Pfades entfernt werden (Zeile %d)"
+
+#: builtin/apply.c:1628
+msgid "new file depends on old contents"
+msgstr "neue Datei hängt von alten Inhalten ab"
+
+#: builtin/apply.c:1630
+msgid "deleted file still has contents"
+msgstr "entfernte Datei hat noch Inhalte"
+
+#: builtin/apply.c:1656
+#, c-format
+msgid "corrupt patch at line %d"
+msgstr "fehlerhafter Patch bei Zeile %d"
+
+#: builtin/apply.c:1692
+#, c-format
+msgid "new file %s depends on old contents"
+msgstr "neue Datei %s hängt von alten Inhalten ab"
+
+#: builtin/apply.c:1694
+#, c-format
+msgid "deleted file %s still has contents"
+msgstr "entfernte Datei %s hat noch Inhalte"
+
+#: builtin/apply.c:1697
+#, c-format
+msgid "** warning: file %s becomes empty but is not deleted"
+msgstr "** Warnung: Datei %s wird leer, aber nicht entfernt."
+
+#: builtin/apply.c:1843
+#, c-format
+msgid "corrupt binary patch at line %d: %.*s"
+msgstr "fehlerhafter Binär-Patch bei Zeile %d: %.*s"
+
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1872
+#, c-format
+msgid "unrecognized binary patch at line %d"
+msgstr "nicht erkannter Binär-Patch bei Zeile %d"
+
+#: builtin/apply.c:1958
+#, c-format
+msgid "patch with only garbage at line %d"
+msgstr "Patch mit nutzlosen Informationen bei Zeile %d"
+
+#: builtin/apply.c:2048
+#, c-format
+msgid "unable to read symlink %s"
+msgstr "konnte symbolische Verknüpfung %s nicht lesen"
+
+#: builtin/apply.c:2052
+#, c-format
+msgid "unable to open or read %s"
+msgstr "konnte %s nicht öffnen oder lesen"
+
+#: builtin/apply.c:2123
+msgid "oops"
+msgstr "Ups"
+
+#: builtin/apply.c:2645
+#, c-format
+msgid "invalid start of line: '%c'"
+msgstr "Ungültiger Zeilenanfang: '%c'"
+
+#: builtin/apply.c:2763
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] "Patch-Bereich #%d erfolgreich angewendet bei %d (%d Zeile versetzt)"
+msgstr[1] ""
+"Patch-Bereich #%d erfolgreich angewendet bei %d (%d Zeilen versetzt)"
+
+#: builtin/apply.c:2775
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
+msgstr "Kontext reduziert zu (%ld/%ld) um Patch-Bereich bei %d anzuwenden"
+
+#: builtin/apply.c:2781
+#, c-format
+msgid ""
+"while searching for:\n"
+"%.*s"
+msgstr ""
+"bei der Suche nach:\n"
+"%.*s"
+
+#: builtin/apply.c:2800
+#, c-format
+msgid "missing binary patch data for '%s'"
+msgstr "keine Daten in Binär-Patch für '%s'"
+
+#: builtin/apply.c:2903
+#, c-format
+msgid "binary patch does not apply to '%s'"
+msgstr "Konnte Binär-Patch nicht auf '%s' anwenden"
+
+#: builtin/apply.c:2909
+#, c-format
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
+msgstr ""
+"Binär-Patch für '%s' erzeugt falsches Ergebnis (erwartete %s, bekam %s)"
+
+#: builtin/apply.c:2930
+#, c-format
+msgid "patch failed: %s:%ld"
+msgstr "Anwendung des Patches fehlgeschlagen: %s:%ld"
+
+#: builtin/apply.c:3045
+#, c-format
+msgid "patch %s has been renamed/deleted"
+msgstr "Patch %s wurde umbenannt/gelöscht"
+
+#: builtin/apply.c:3052 builtin/apply.c:3069
+#, c-format
+msgid "read of %s failed"
+msgstr "Konnte %s nicht lesen"
+
+#: builtin/apply.c:3084
+msgid "removal patch leaves file contents"
+msgstr "Lösch-Patch hinterlässt Dateiinhalte"
+
+#: builtin/apply.c:3105
+#, c-format
+msgid "%s: already exists in working directory"
+msgstr "%s existiert bereits im Arbeitsverzeichnis"
+
+#: builtin/apply.c:3143
+#, c-format
+msgid "%s: has been deleted/renamed"
+msgstr "%s wurde gelöscht/umbenannt"
+
+#: builtin/apply.c:3148 builtin/apply.c:3179
+#, c-format
+msgid "%s: %s"
+msgstr "%s: %s"
+
+#: builtin/apply.c:3159
+#, c-format
+msgid "%s: does not exist in index"
+msgstr "%s ist nicht bereitgestellt"
+
+#: builtin/apply.c:3173
+#, c-format
+msgid "%s: does not match index"
+msgstr "%s entspricht nicht der Bereitstellung"
+
+#: builtin/apply.c:3190
+#, c-format
+msgid "%s: wrong type"
+msgstr "%s: falscher Typ"
+
+#: builtin/apply.c:3192
+#, c-format
+msgid "%s has type %o, expected %o"
+msgstr "%s ist vom Typ %o, erwartete %o"
+
+#: builtin/apply.c:3247
+#, c-format
+msgid "%s: already exists in index"
+msgstr "%s ist bereits bereitgestellt"
+
+#: builtin/apply.c:3267
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o)"
+msgstr "neuer Modus (%o) von %s entspricht nicht dem alten Modus (%o)"
+
+#: builtin/apply.c:3272
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o) of %s"
+msgstr "neuer Modus (%o) von %s entspricht nicht dem alten Modus (%o) von %s"
+
+#: builtin/apply.c:3280
+#, c-format
+msgid "%s: patch does not apply"
+msgstr "%s: Patch konnte nicht angewendet werden"
+
+#: builtin/apply.c:3293
+#, c-format
+msgid "Checking patch %s..."
+msgstr "Prüfe Patch %s..."
+
+#: builtin/apply.c:3348 builtin/checkout.c:212 builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr "make_cache_entry für Pfad '%s' fehlgeschlagen"
+
+#: builtin/apply.c:3491
+#, c-format
+msgid "unable to remove %s from index"
+msgstr "konnte %s nicht aus der Bereitstellung entfernen"
+
+#: builtin/apply.c:3518
+#, c-format
+msgid "corrupt patch for subproject %s"
+msgstr "fehlerhafter Patch für Unterprojekt %s"
+
+#: builtin/apply.c:3522
+#, c-format
+msgid "unable to stat newly created file '%s'"
+msgstr "konnte neu erstellte Datei '%s' nicht lesen"
+
+#: builtin/apply.c:3527
+#, c-format
+msgid "unable to create backing store for newly created file %s"
+msgstr "kann internen Speicher für eben erstellte Datei %s nicht erzeugen"
+
+#: builtin/apply.c:3530
+#, c-format
+msgid "unable to add cache entry for %s"
+msgstr "kann für %s keinen Eintrag in den Zwischenspeicher hinzufügen"
+
+#: builtin/apply.c:3563
+#, c-format
+msgid "closing file '%s'"
+msgstr "schließe Datei '%s'"
+
+#: builtin/apply.c:3612
+#, c-format
+msgid "unable to write file '%s' mode %o"
+msgstr "konnte Datei '%s' mit Modus %o nicht schreiben"
+
+#: builtin/apply.c:3668
+#, c-format
+msgid "Applied patch %s cleanly."
+msgstr "Patch %s sauber angewendet"
+
+#: builtin/apply.c:3676
+msgid "internal error"
+msgstr "interner Fehler"
+
+#. Say this even without --verbose
+#: builtin/apply.c:3679
+#, c-format
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] "Wende Patch %%s mit %d Zurückweisung an..."
+msgstr[1] "Wende Patch %%s mit %d Zurückweisungen an..."
+
+#: builtin/apply.c:3689
+#, c-format
+msgid "truncating .rej filename to %.*s.rej"
+msgstr "Verkürze Name von .rej Datei zu %.*s.rej"
+
+#: builtin/apply.c:3710
+#, c-format
+msgid "Hunk #%d applied cleanly."
+msgstr "Patch-Bereich #%d sauber angewendet."
+
+#: builtin/apply.c:3713
+#, c-format
+msgid "Rejected hunk #%d."
+msgstr "Patch-Bereich #%d zurückgewiesen."
+
+#: builtin/apply.c:3844
+msgid "unrecognized input"
+msgstr "nicht erkannte Eingabe"
+
+#: builtin/apply.c:3855
+msgid "unable to read index file"
+msgstr "Konnte Bereitstellungsdatei nicht lesen"
+
+#: builtin/apply.c:3970 builtin/apply.c:3973
+msgid "path"
+msgstr "Pfad"
+
+#: builtin/apply.c:3971
+msgid "don't apply changes matching the given path"
+msgstr "wendet keine Änderungen im angegebenen Pfad an"
+
+#: builtin/apply.c:3974
+msgid "apply changes matching the given path"
+msgstr "wendet Änderungen nur im angegebenen Pfad an"
+
+#: builtin/apply.c:3976
+msgid "num"
+msgstr "Anzahl"
+
+#: builtin/apply.c:3977
+msgid "remove <num> leading slashes from traditional diff paths"
+msgstr ""
+"entfernt <Anzahl> vorrangestellte Schrägstriche von herkömmlichen "
+"Differenzpfaden"
+
+#: builtin/apply.c:3980
+msgid "ignore additions made by the patch"
+msgstr "ignoriert hinzugefügte Zeilen des Patches"
+
+#: builtin/apply.c:3982
+msgid "instead of applying the patch, output diffstat for the input"
+msgstr ""
+"anstatt der Anwendung des Patches, wird der \"diffstat\" für die Eingabe "
+"ausgegeben"
+
+#: builtin/apply.c:3986
+msgid "shows number of added and deleted lines in decimal notation"
+msgstr ""
+"zeigt die Anzahl von hinzugefügten/entfernten Zeilen in Dezimalnotation"
+
+#: builtin/apply.c:3988
+msgid "instead of applying the patch, output a summary for the input"
+msgstr ""
+"anstatt der Anwendung des Patches, wird eine Zusammenfassung für die Eingabe "
+"ausgegeben"
+
+#: builtin/apply.c:3990
+msgid "instead of applying the patch, see if the patch is applicable"
+msgstr ""
+"anstatt der Anwendung des Patches, zeige ob Patch angewendet werden kann"
+
+#: builtin/apply.c:3992
+msgid "make sure the patch is applicable to the current index"
+msgstr ""
+"stellt sicher, dass der Patch in der aktuellen Bereitstellung angewendet "
+"werden kann"
+
+#: builtin/apply.c:3994
+msgid "apply a patch without touching the working tree"
+msgstr "wendet einen Patch an, ohne Änderungen im Arbeitszweig vorzunehmen"
+
+#: builtin/apply.c:3996
+msgid "also apply the patch (use with --stat/--summary/--check)"
+msgstr "wendet den Patch an (Benutzung mit --stat/--summary/--check)"
+
+#: builtin/apply.c:3998
+msgid "build a temporary index based on embedded index information"
+msgstr ""
+"erstellt eine temporäre Bereitstellung basierend auf den integrierten "
+"Bereitstellungsinformationen"
+
+#: builtin/apply.c:4000
+msgid "paths are separated with NUL character"
+msgstr "Pfade sind getrennt durch NUL Zeichen"
+
+#: builtin/apply.c:4003
+msgid "ensure at least <n> lines of context match"
+msgstr ""
+"stellt sicher, dass mindestens <Anzahl> Zeilen des Kontextes übereinstimmen"
+
+#: builtin/apply.c:4004
+msgid "action"
+msgstr "Aktion"
+
+#: builtin/apply.c:4005
+msgid "detect new or modified lines that have whitespace errors"
+msgstr "ermittelt neue oder geänderte Zeilen die Fehler in Leerzeichen haben"
+
+#: builtin/apply.c:4008 builtin/apply.c:4011
+msgid "ignore changes in whitespace when finding context"
+msgstr "ignoriert Änderungen in Leerzeichen bei der Suche des Kontextes"
+
+#: builtin/apply.c:4014
+msgid "apply the patch in reverse"
+msgstr "wendet den Patch in umgekehrter Reihenfolge an"
+
+#: builtin/apply.c:4016
+msgid "don't expect at least one line of context"
+msgstr "erwartet keinen Kontext"
+
+#: builtin/apply.c:4018
+msgid "leave the rejected hunks in corresponding *.rej files"
+msgstr ""
+"hinterlässt zurückgewiesene Patch-Bereiche in den entsprechenden *.rej "
+"Dateien"
+
+#: builtin/apply.c:4020
+msgid "allow overlapping hunks"
+msgstr "erlaubt sich überlappende Patch-Bereiche"
+
+#: builtin/apply.c:4021
+msgid "be verbose"
+msgstr "erweiterte Ausgaben"
+
+#: builtin/apply.c:4023
+msgid "tolerate incorrectly detected missing new-line at the end of file"
+msgstr "toleriert fehlerhaft erkannten fehlenden Zeilenumbruch am Dateiende"
+
+#: builtin/apply.c:4026
+msgid "do not trust the line counts in the hunk headers"
+msgstr "vertraut nicht den Zeilennummern im Kopf des Patch-Bereiches"
+
+#: builtin/apply.c:4028
+msgid "root"
+msgstr "Wurzelverzeichnis"
+
+#: builtin/apply.c:4029
+msgid "prepend <root> to all filenames"
+msgstr "stellt <Wurzelverzeichnis> vor alle Dateinamen"
+
+#: builtin/apply.c:4050
+msgid "--index outside a repository"
+msgstr "--index außerhalb eines Projektarchivs"
+
+#: builtin/apply.c:4053
+msgid "--cached outside a repository"
+msgstr "--cached außerhalb eines Projektarchivs"
+
+#: builtin/apply.c:4069
+#, c-format
+msgid "can't open patch '%s'"
+msgstr "kann Patch '%s' nicht öffnen"
+
+#: builtin/apply.c:4083
+#, c-format
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] "unterdrückte %d Fehler in Leerzeichen"
+msgstr[1] "unterdrückte %d Fehler in Leerzeichen"
+
+#: builtin/apply.c:4089 builtin/apply.c:4099
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] "%d Zeile fügt Fehler in Leerzeichen hinzu."
+msgstr[1] "%d Zeilen fügen Fehler in Leerzeichen hinzu."
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr "Konnte Archiv-Datei '%s' nicht erstellen."
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr "Konnte Ausgabe nicht umleiten."
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr "git archive: Externes Archiv ohne URL"
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr "git archive: habe ACK/NAK erwartet, aber EOF bekommen"
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr "git archive: NACK %s"
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr "Fehler am anderen Ende: %s"
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr "git archive: Protokollfehler"
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr "git archive: erwartete eine Spülung (flush)"
+
+#: builtin/branch.c:144
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+"entferne Zweig '%s', der zusammengeführt wurde mit\n"
+" '%s', aber noch nicht mit der Zweigspitze (HEAD) zusammengeführt "
+"wurde."
+
+#: builtin/branch.c:148
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+"entferne Zweig '%s' nicht, der noch nicht zusammengeführt wurde mit\n"
+" '%s', obwohl er mit der Zweigspitze (HEAD) zusammengeführt wurde."
+
+#: builtin/branch.c:180
+msgid "cannot use -a with -d"
+msgstr "kann -a nicht mit -d benutzen"
+
+#: builtin/branch.c:186
+msgid "Couldn't look up commit object for HEAD"
+msgstr "Konnte Versionsobjekt für Zweigspitze (HEAD) nicht nachschlagen."
+
+#: builtin/branch.c:191
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr ""
+"Kann Zweig '%s' nicht entfernen, da du dich gerade auf diesem befindest."
+
+#: builtin/branch.c:202
+#, c-format
+msgid "remote branch '%s' not found."
+msgstr "externer Zweig '%s' nicht gefunden"
+
+#: builtin/branch.c:203
+#, c-format
+msgid "branch '%s' not found."
+msgstr "Zweig '%s' nicht gefunden."
+
+#: builtin/branch.c:210
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr "Konnte Versionsobjekt für '%s' nicht nachschlagen."
+
+#: builtin/branch.c:216
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+"Der Zweig '%s' ist nicht vollständig zusammengeführt.\n"
+"Wenn du sicher bist diesen Zweig zu entfernen, führe 'git branch -D %s' aus."
+
+#: builtin/branch.c:225
+#, c-format
+msgid "Error deleting remote branch '%s'"
+msgstr "Fehler beim Entfernen des externen Zweiges '%s'"
+
+#: builtin/branch.c:226
+#, c-format
+msgid "Error deleting branch '%s'"
+msgstr "Fehler beim Entfernen des Zweiges '%s'"
+
+#: builtin/branch.c:233
+#, c-format
+msgid "Deleted remote branch %s (was %s).\n"
+msgstr "Externer Zweig %s entfernt (war %s).\n"
+
+#: builtin/branch.c:234
+#, c-format
+msgid "Deleted branch %s (was %s).\n"
+msgstr "Zweig %s entfernt (war %s).\n"
+
+#: builtin/branch.c:239
+msgid "Update of config-file failed"
+msgstr "Aktualisierung der Konfigurationsdatei fehlgeschlagen."
+
+#: builtin/branch.c:337
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr "Zweig '%s' zeigt auf keine Version"
+
+#: builtin/branch.c:409
+#, c-format
+msgid "[%s: behind %d]"
+msgstr "[%s: %d hinterher]"
+
+#: builtin/branch.c:411
+#, c-format
+msgid "[behind %d]"
+msgstr "[%d hinterher]"
+
+#: builtin/branch.c:415
+#, c-format
+msgid "[%s: ahead %d]"
+msgstr "[%s: %d voraus]"
+
+#: builtin/branch.c:417
+#, c-format
+msgid "[ahead %d]"
+msgstr "[%d voraus]"
+
+#: builtin/branch.c:420
+#, c-format
+msgid "[%s: ahead %d, behind %d]"
+msgstr "[%s: %d voraus, %d hinterher]"
+
+#: builtin/branch.c:423
+#, c-format
+msgid "[ahead %d, behind %d]"
+msgstr "[%d voraus, %d hinterher]"
+
+#: builtin/branch.c:535
+msgid "(no branch)"
+msgstr "(kein Zweig)"
+
+#: builtin/branch.c:600
+msgid "some refs could not be read"
+msgstr "Konnte einige Referenzen nicht lesen"
+
+#: builtin/branch.c:613
+msgid "cannot rename the current branch while not on any."
+msgstr ""
+"Kann aktuellen Zweig nicht umbennen, solange du dich auf keinem befindest."
+
+#: builtin/branch.c:623
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr "Ungültiger Zweig-Name: '%s'"
+
+#: builtin/branch.c:638
+msgid "Branch rename failed"
+msgstr "Umbenennung des Zweiges fehlgeschlagen"
+
+#: builtin/branch.c:642
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr "falsch benannten Zweig '%s' umbenannt"
+
+#: builtin/branch.c:646
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr "Zweig umbenannt zu %s, aber Zweigspitze (HEAD) ist nicht aktualisiert!"
+
+#: builtin/branch.c:653
+msgid "Branch is renamed, but update of config-file failed"
+msgstr ""
+"Zweig ist umbenannt, aber die Aktualisierung der Konfigurationsdatei ist "
+"fehlgeschlagen."
+
+#: builtin/branch.c:668
+#, c-format
+msgid "malformed object name %s"
+msgstr "Missgebildeter Objektname %s"
+
+#: builtin/branch.c:692
+#, c-format
+msgid "could not write branch description template: %s"
+msgstr "Konnte Beschreibungsvorlage für Zweig nicht schreiben: %s"
+
+#: builtin/branch.c:783
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr "Konnte Zweigspitze (HEAD) nicht als gültige Referenz auflösen."
+
+#: builtin/branch.c:788 builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr "Zweigspitze (HEAD) wurde nicht unter \"refs/heads\" gefunden!"
+
+#: builtin/branch.c:808
+msgid "--column and --verbose are incompatible"
+msgstr "--column und --verbose sind inkompatibel"
+
+#: builtin/branch.c:857
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr ""
+"Die Optionen -a und -r bei 'git branch' machen mit einem Zweignamen keinen "
+"Sinn."
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr "%s ist in Ordnung\n"
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr "Um ein Paket zu erstellen wird ein Projektarchiv benötigt."
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr "Zum Entpacken wird ein Projektarchiv benötigt."
+
+#: builtin/checkout.c:113 builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr "Pfad '%s' hat nicht unsere Version."
+
+#: builtin/checkout.c:115 builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr "Pfad '%s' hat nicht deren Version."
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr "Pfad '%s' hat nicht alle notwendigen Versionen."
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr "Pfad '%s' hat nicht die notwendigen Versionen."
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr "Pfad '%s': kann nicht zusammenführen"
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr "Konnte Ergebnis der Zusammenführung von '%s' nicht hinzufügen."
+
+#: builtin/checkout.c:234 builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr "beschädigte Bereitstellungsdatei"
+
+#: builtin/checkout.c:264 builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr "Pfad '%s' ist nicht zusammengeführt."
+
+#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
+#: builtin/merge.c:812
+msgid "unable to write new index file"
+msgstr "Konnte neue Bereitstellungsdatei nicht schreiben."
+
+#: builtin/checkout.c:319 builtin/diff.c:302 builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr "diff_setup_done fehlgeschlagen"
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr "Du musst zuerst deine aktuelle Bereitstellung auflösen."
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr "Konnte \"reflog\" für '%s' nicht durchführen\n"
+
+#: builtin/checkout.c:566
+msgid "HEAD is now at"
+msgstr "Zweigspitze (HEAD) ist jetzt bei"
+
+#: builtin/checkout.c:573
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr "Setze Zweig '%s' zurück\n"
+
+#: builtin/checkout.c:576
+#, c-format
+msgid "Already on '%s'\n"
+msgstr "Bereits auf '%s'\n"
+
+#: builtin/checkout.c:580
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr "Gewechselt zu zurückgesetztem Zweig '%s'\n"
+
+#: builtin/checkout.c:582
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr "Gewechselt zu einem neuen Zweig '%s'\n"
+
+#: builtin/checkout.c:584
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr "Gewechselt zu Zweig '%s'\n"
+
+#: builtin/checkout.c:640
+#, c-format
+msgid " ... and %d more.\n"
+msgstr " ... und %d weitere.\n"
+
+#. The singular version
+#: builtin/checkout.c:646
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+"Warnung: Du bist um %d Version hinterher, nicht verbunden zu\n"
+"einem deiner Zweige:\n"
+"\n"
+"%s\n"
+msgstr[1] ""
+"Warnung: Du bist um %d Versionen hinterher, nicht verbunden zu\n"
+"einem deiner Zweige:\n"
+"\n"
+"%s\n"
+
+#: builtin/checkout.c:664
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+"Wenn du diese durch einen neuen Zweig behalten möchtest, dann könnte jetzt\n"
+"ein guter Zeitpunkt sein dies zu tun mit:\n"
+"\n"
+" git branch neuer_zweig_name %s\n"
+"\n"
+
+#: builtin/checkout.c:694
+msgid "internal error in revision walk"
+msgstr "interner Fehler im Revisionsgang"
+
+#: builtin/checkout.c:698
+msgid "Previous HEAD position was"
+msgstr "Vorherige Position der Zweigspitze (HEAD) war"
+
+#: builtin/checkout.c:724
+msgid "You are on a branch yet to be born"
+msgstr "du bist auf einem Zweig, der noch geboren wird"
+
+#. case (1)
+#: builtin/checkout.c:855
+#, c-format
+msgid "invalid reference: %s"
+msgstr "Ungültige Referenz: %s"
+
+#. case (1): want a tree
+#: builtin/checkout.c:894
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr "Referenz ist kein Baum: %s"
+
+#: builtin/checkout.c:974
+msgid "-B cannot be used with -b"
+msgstr "-B kann nicht mit -b benutzt werden"
+
+#: builtin/checkout.c:983
+msgid "--patch is incompatible with all other options"
+msgstr "--patch ist inkompatibel mit allen anderen Optionen"
+
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr "--detach kann nicht mit -b/-B/--orphan benutzt werden"
+
+#: builtin/checkout.c:988
+msgid "--detach cannot be used with -t"
+msgstr "--detach kann nicht mit -t benutzt werden"
+
+#: builtin/checkout.c:994
+msgid "--track needs a branch name"
+msgstr "--track benötigt einen Zweignamen"
+
+#: builtin/checkout.c:1001
+msgid "Missing branch name; try -b"
+msgstr "Vermisse Zweignamen; versuche -b"
+
+#: builtin/checkout.c:1007
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr "--orphan und -b|-B sind gegenseitig exklusiv"
+
+#: builtin/checkout.c:1009
+msgid "--orphan cannot be used with -t"
+msgstr "--orphan kann nicht mit -t benutzt werden"
+
+#: builtin/checkout.c:1019
+msgid "git checkout: -f and -m are incompatible"
+msgstr "git checkout: -f und -m sind inkompatibel"
+
+#: builtin/checkout.c:1053
+msgid "invalid path specification"
+msgstr "ungültige Pfadspezifikation"
+
+#: builtin/checkout.c:1061
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+"git checkout: Die Aktualisierung von Pfaden ist inkompatibel mit dem Wechsel "
+"von Zweigen.\n"
+"Hast du beabsichtigt '%s' auszuchecken, welcher nicht als Version aufgelöst "
+"werden kann?"
+
+#: builtin/checkout.c:1063
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr ""
+"git checkout: Die Aktualisierung von Pfaden ist inkompatibel mit dem Wechsel "
+"von Zweigen."
+
+#: builtin/checkout.c:1068
+msgid "git checkout: --detach does not take a path argument"
+msgstr "git checkout: --detach nimmt kein Pfad-Argument"
+
+#: builtin/checkout.c:1071
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+"git checkout: --ours/--theirs, --force and --merge sind inkompatibel wenn\n"
+"du aus der Bereitstellung auscheckst."
+
+#: builtin/checkout.c:1090
+msgid "Cannot switch branch to a non-commit."
+msgstr "Kann Zweig nur zu einer Version wechseln."
+
+#: builtin/checkout.c:1093
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr "--ours/--theirs ist inkompatibel mit den Wechseln von Zweigen."
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr "-x und -X können nicht zusammen benutzt werden"
+
+#: builtin/clean.c:82
+msgid ""
+"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+"clean.requireForce auf \"true\" gesetzt und weder -n noch -f gegeben; "
+"Säuberung verweigert"
+
+#: builtin/clean.c:85
+msgid ""
+"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
+"clean"
+msgstr ""
+"clean.requireForce standardmäßig auf \"true\" gesetzt und weder -n noch -f "
+"gegeben; Säuberung verweigert"
+
+#: builtin/clean.c:155 builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr "Würde %s entfernen\n"
+
+#: builtin/clean.c:159 builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr "Entferne %s\n"
+
+#: builtin/clean.c:162 builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr "Fehler beim Entfernen von %s"
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr "Würde nicht entfernen %s\n"
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr "Entferne nicht %s\n"
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr "Referenziertes Projektarchiv '%s' ist kein lokales Verzeichnis."
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr "Fehler beim Öffnen von '%s'"
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr "Fehler beim Erstellen von Verzeichnis '%s'"
+
+#: builtin/clone.c:308 builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr "Konnte '%s' nicht lesen"
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr "%s existiert und ist kein Verzeichnis"
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr "Konnte %s nicht lesen\n"
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr "Konnte '%s' nicht entfernen"
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr "Konnte Verknüpfung '%s' nicht erstellen"
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr "Konnte Datei nicht nach '%s' kopieren"
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr "Fertig.\n"
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr "Konnte zu klonenden externer Zweig %s nicht finden."
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr ""
+"Externe Zweigspitze (HEAD) bezieht sich auf eine nicht existierende Referenz "
+"und kann nicht ausgecheckt werden.\n"
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr "Zu viele Argumente."
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr "Du musst ein Projektarchiv zum Klonen angeben."
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr "--bare und --origin %s Optionen sind inkompatibel."
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr "Projektarchiv '%s' existiert nicht."
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr "--depth wird in lokalen Klonen ignoriert; benutze stattdessen file://."
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr "Zielpfad '%s' existiert bereits und ist kein leeres Verzeichnis."
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr "Arbeitsbaum '%s' existiert bereits."
+
+#: builtin/clone.c:706 builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr "Konnte führende Verzeichnisse von '%s' nicht erstellen."
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr "Konnte Arbeitsverzeichnis '%s' nicht erstellen."
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr "Klone in bloßes Projektarchiv '%s'...\n"
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr "Klone nach '%s'...\n"
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr "Weiß nicht wie %s zu klonen ist."
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr "externer Zweig %s nicht im anderen Projektarchiv %s gefunden"
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr "Du scheinst ein leeres Projektarchiv geklont zu haben."
+
+#: builtin/column.c:51
+msgid "--command must be the first argument"
+msgstr "Option --command muss zuerst angegeben werden"
+
+#: builtin/commit.c:43
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+"Dein Name und E-Mail Adresse wurden automatisch auf Basis\n"
+"deines Benutzer- und Rechnernamens konfiguriert. Bitte prüfe, dass diese\n"
+"zutreffend sind. Du kannst diese Meldung unterdrücken, indem du diese\n"
+"explizit setzt:\n"
+"\n"
+" git config --global user.name \"Dein Name\"\n"
+" git config --global user.email deine@emailadresse.de\n"
+"\n"
+"Nachdem du das getan hast, kannst du deine Identität für diese Version "
+"ändern mit:\n"
+"\n"
+" git commit --amend --reset-author\n"
+
+#: builtin/commit.c:55
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+"Du fragtest die jüngste Version nachzubessern, aber das würde diese leer\n"
+"machen. Du kannst Dein Kommando mit --allow-empty wiederholen, oder die\n"
+"Version mit \"git reset HEAD^\" vollständig entfernen.\n"
+
+#: builtin/commit.c:60
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+"Der letzte \"cherry-pick\" ist jetzt leer, möglicherweise durch eine "
+"Konfliktauflösung.\n"
+"Wenn du dies trotzdem eintragen willst, benutze:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Andernfalls benutze bitte 'git reset'\n"
+
+#: builtin/commit.c:253
+msgid "failed to unpack HEAD tree object"
+msgstr "Fehler beim Entpacken des Baum-Objektes der Zweigspitze (HEAD)."
+
+#: builtin/commit.c:295
+msgid "unable to create temporary index"
+msgstr "Konnte temporäre Bereitstellung nicht erstellen."
+
+#: builtin/commit.c:301
+msgid "interactive add failed"
+msgstr "interaktives Hinzufügen fehlgeschlagen"
+
+#: builtin/commit.c:334 builtin/commit.c:355 builtin/commit.c:405
+msgid "unable to write new_index file"
+msgstr "Konnte new_index Datei nicht schreiben"
+
+#: builtin/commit.c:386
+msgid "cannot do a partial commit during a merge."
+msgstr ""
+"Kann keine partielle Eintragung durchführen, während eine Zusammenführung im "
+"Gange ist."
+
+#: builtin/commit.c:388
+msgid "cannot do a partial commit during a cherry-pick."
+msgstr ""
+"Kann keine partielle Eintragung durchführen, während \"cherry-pick\" im "
+"Gange ist."
+
+#: builtin/commit.c:398
+msgid "cannot read the index"
+msgstr "Kann Bereitstellung nicht lesen"
+
+#: builtin/commit.c:418
+msgid "unable to write temporary index file"
+msgstr "Konnte temporäre Bereitstellungsdatei nicht schreiben."
+
+#: builtin/commit.c:493 builtin/commit.c:499
+#, c-format
+msgid "invalid commit: %s"
+msgstr "Ungültige Version: %s"
+
+#: builtin/commit.c:522
+msgid "malformed --author parameter"
+msgstr "Fehlerhafter --author Parameter"
+
+#: builtin/commit.c:582
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr "Fehlerhafte Identifikations-String: '%s'"
+
+#: builtin/commit.c:620 builtin/commit.c:653 builtin/commit.c:967
+#, c-format
+msgid "could not lookup commit %s"
+msgstr "Konnte Version %s nicht nachschlagen"
+
+#: builtin/commit.c:632 builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr "(lese Log-Nachricht von Standard-Eingabe)\n"
+
+#: builtin/commit.c:634
+msgid "could not read log from standard input"
+msgstr "Konnte Log nicht von Standard-Eingabe lesen."
+
+#: builtin/commit.c:638
+#, c-format
+msgid "could not read log file '%s'"
+msgstr "Konnte Log-Datei '%s' nicht lesen"
+
+#: builtin/commit.c:644
+msgid "commit has empty message"
+msgstr "Version hat eine leere Beschreibung"
+
+#: builtin/commit.c:660
+msgid "could not read MERGE_MSG"
+msgstr "Konnte MERGE_MSG nicht lesen"
+
+#: builtin/commit.c:664
+msgid "could not read SQUASH_MSG"
+msgstr "Konnte SQUASH_MSG nicht lesen"
+
+#: builtin/commit.c:668
+#, c-format
+msgid "could not read '%s'"
+msgstr "Konnte '%s' nicht lesen"
+
+#: builtin/commit.c:720
+msgid "could not write commit template"
+msgstr "Konnte Versionsvorlage nicht schreiben"
+
+#: builtin/commit.c:731
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a merge.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"Es sieht so aus, als trägst du eine Zusammenführung ein.\n"
+"Falls das nicht korrekt ist, entferne bitte die Datei\n"
+"\t%s\n"
+"und versuche es erneut.\n"
+
+#: builtin/commit.c:736
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"Es sieht so aus, als trägst du ein \"cherry-pick\" ein.\n"
+"Falls das nicht korrekt ist, entferne bitte die Datei\n"
+"\t%s\n"
+"und versuche es erneut.\n"
+
+#: builtin/commit.c:748
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+"Bitte gebe eine Versionsbeschreibung für deine Änderungen ein. Zeilen,\n"
+"die mit '#' beginnen, werden ignoriert, und eine leere Versionsbeschreibung\n"
+"bricht die Eintragung ab.\n"
+
+#: builtin/commit.c:753
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+"Bitte gebe eine Versionsbeschreibung für deine Änderungen ein. Zeilen, die\n"
+"mit '#' beginnen, werden beibehalten; wenn du möchtest, kannst du diese "
+"entfernen.\n"
+"Eine leere Versionsbeschreibung bricht die Eintragung ab.\n"
+
+#: builtin/commit.c:766
+#, c-format
+msgid "%sAuthor: %s"
+msgstr "%sAutor: %s"
+
+#: builtin/commit.c:773
+#, c-format
+msgid "%sCommitter: %s"
+msgstr "%sEintragender: %s"
+
+#: builtin/commit.c:793
+msgid "Cannot read index"
+msgstr "Kann Bereitstellung nicht lesen"
+
+#: builtin/commit.c:830
+msgid "Error building trees"
+msgstr "Fehler beim Erzeugen der Zweige"
+
+#: builtin/commit.c:845 builtin/tag.c:361
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr "Bitte liefere eine Beschreibung entweder mit der Option -m oder -F.\n"
+
+#: builtin/commit.c:942
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr "Kein existierender Autor mit '%s' gefunden."
+
+#: builtin/commit.c:957 builtin/commit.c:1157
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr "Ungültiger Modus '%s' für unbeobachtete Dateien"
+
+#: builtin/commit.c:997
+msgid "Using both --reset-author and --author does not make sense"
+msgstr "Verwendung von --reset-author und --author macht keinen Sinn."
+
+#: builtin/commit.c:1008
+msgid "You have nothing to amend."
+msgstr "Du hast nichts zum nachbessern."
+
+#: builtin/commit.c:1011
+msgid "You are in the middle of a merge -- cannot amend."
+msgstr "Eine Zusammenführung ist im Gange -- kann nicht nachbessern."
+
+#: builtin/commit.c:1013
+msgid "You are in the middle of a cherry-pick -- cannot amend."
+msgstr "\"cherry-pick\" ist im Gange -- kann nicht nachbessern."
+
+#: builtin/commit.c:1016
+msgid "Options --squash and --fixup cannot be used together"
+msgstr ""
+"Die Optionen --squash und --fixup können nicht gemeinsam benutzt werden."
+
+#: builtin/commit.c:1026
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr "Nur eines von -c/-C/-F/--fixup kann benutzt werden."
+
+#: builtin/commit.c:1028
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr "Option -m kann nicht mit -c/-C/-F/--fixup kombiniert werden"
+
+#: builtin/commit.c:1036
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr "--reset--author kann nur mit -C, -c oder --amend benutzt werden"
+
+#: builtin/commit.c:1053
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr ""
+"Nur eines von --include/--only/--all/--interactive/--patch kann benutzt "
+"werden."
+
+#: builtin/commit.c:1055
+msgid "No paths with --include/--only does not make sense."
+msgstr "--include/--only machen ohne Pfade keinen Sinn."
+
+#: builtin/commit.c:1057
+msgid "Clever... amending the last one with dirty index."
+msgstr ""
+"Klug... die letzte Version mit einer unsauberen Bereitstellung nachbessern."
+
+#: builtin/commit.c:1059
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr ""
+"Explizite Pfade ohne -i oder -o angegeben; unter der Annahme von --only "
+"Pfaden..."
+
+#: builtin/commit.c:1069 builtin/tag.c:577
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr "Ungültiger \"cleanup\" Modus %s"
+
+#: builtin/commit.c:1074
+msgid "Paths with -a does not make sense."
+msgstr "Pfade mit -a machen keinen Sinn."
+
+#: builtin/commit.c:1257
+msgid "couldn't look up newly created commit"
+msgstr "Konnte neu erstellte Version nicht nachschlagen."
+
+#: builtin/commit.c:1259
+msgid "could not parse newly created commit"
+msgstr "Konnte neulich erstellte Version nicht analysieren."
+
+#: builtin/commit.c:1300
+msgid "detached HEAD"
+msgstr "losgelöste Zweigspitze (HEAD)"
+
+#: builtin/commit.c:1302
+msgid " (root-commit)"
+msgstr " (Basis-Version)"
+
+#: builtin/commit.c:1446
+msgid "could not parse HEAD commit"
+msgstr "Konnte Version der Zweigspitze (HEAD) nicht analysieren."
+
+#: builtin/commit.c:1484 builtin/merge.c:509
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr "Konnte '%s' nicht zum Lesen öffnen."
+
+#: builtin/commit.c:1491
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr "Beschädigte MERGE_HEAD-Datei (%s)"
+
+#: builtin/commit.c:1498
+msgid "could not read MERGE_MODE"
+msgstr "Konnte MERGE_MODE nicht lesen"
+
+#: builtin/commit.c:1517
+#, c-format
+msgid "could not read commit message: %s"
+msgstr "Konnte Versionsbeschreibung nicht lesen: %s"
+
+#: builtin/commit.c:1531
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
+msgstr "Eintragung abgebrochen; du hast die Beschreibung nicht editiert.\n"
+
+#: builtin/commit.c:1536
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr "Eintragung aufgrund leerer Versionsbeschreibung abgebrochen.\n"
+
+#: builtin/commit.c:1551 builtin/merge.c:936 builtin/merge.c:961
+msgid "failed to write commit object"
+msgstr "Fehler beim Schreiben des Versionsobjektes."
+
+#: builtin/commit.c:1572
+msgid "cannot lock HEAD ref"
+msgstr "Kann Referenz der Zweigspitze (HEAD) nicht sperren."
+
+#: builtin/commit.c:1576
+msgid "cannot update HEAD ref"
+msgstr "Kann Referenz der Zweigspitze (HEAD) nicht aktualisieren."
+
+#: builtin/commit.c:1587
+msgid ""
+"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."
+msgstr ""
+"Das Projektarchiv wurde aktualisiert, aber die \"new_index\"-Datei\n"
+"konnte nicht geschrieben werden. Prüfe, dass dein Speicher nicht\n"
+"voll und Dein Kontingent nicht aufgebraucht ist und führe\n"
+"anschließend \"git reset HEAD\" zu Wiederherstellung aus."
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr "annotierte Markierung %s ist nicht verfügbar"
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr "annotierte Markierung %s hat keinen eingebetteten Namen"
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr "Markierung '%s' ist eigentlich '%s' hier"
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr "%s ist kein gültiger Objekt-Name"
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr "%s ist kein gültiges '%s' Objekt"
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr "kein Markierung entspricht exakt '%s'"
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr "suche zur Beschreibung von %s\n"
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr "beendete Suche bei %s\n"
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+"Keine annotierten Markierungen können '%s' beschreiben.\n"
+"Jedoch gab es nicht annotierte Markierungen: versuche --tags."
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+"Keine Markierungen können '%s' beschreiben.\n"
+"Versuche --always oder erstelle einige Markierungen."
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr "%lu Versionen durchlaufen\n"
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+"mehr als %i Markierungen gefunden; Führe die ersten %i auf\n"
+"Suche bei %s aufgegeben\n"
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr "--long ist inkompatibel mit --abbrev=0"
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr "Keine Namen gefunden, kann nichts beschreiben."
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr "--dirty ist inkompatibel mit Versionen"
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr "'%s': keine reguläre Datei oder symbolischer Link"
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr "Ungültige Option: %s"
+
+#: builtin/diff.c:297
+msgid "Not a git repository"
+msgstr "Kein Git-Projektarchiv"
+
+#: builtin/diff.c:347
+#, c-format
+msgid "invalid object '%s' given."
+msgstr "Objekt '%s' ist ungültig."
+
+#: builtin/diff.c:352
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr "Mehr als %d Zweige angegeben: '%s'"
+
+#: builtin/diff.c:362
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr "Mehr als zwei Blobs angegeben: '%s'"
+
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr "unbehandeltes Objekt '%s' angegeben"
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr "Konnte externe Referenz der Zweigspitze (HEAD) nicht finden."
+
+#: builtin/fetch.c:253
+#, c-format
+msgid "object %s not found"
+msgstr "Objekt %s nicht gefunden"
+
+#: builtin/fetch.c:259
+msgid "[up to date]"
+msgstr "[aktuell]"
+
+#: builtin/fetch.c:273
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr "! %-*s %-*s -> %s (kann nicht im aktuellen Zweig anfordern)"
+
+#: builtin/fetch.c:274 builtin/fetch.c:360
+msgid "[rejected]"
+msgstr "[zurückgewiesen]"
+
+#: builtin/fetch.c:285
+msgid "[tag update]"
+msgstr "[Markierungsaktualisierung]"
+
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
+msgid " (unable to update local ref)"
+msgstr " (kann lokale Referenz nicht aktualisieren)"
+
+#: builtin/fetch.c:305
+msgid "[new tag]"
+msgstr "[neue Markierung]"
+
+#: builtin/fetch.c:308
+msgid "[new branch]"
+msgstr "[neuer Zweig]"
+
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[neue Referenz]"
+
+#: builtin/fetch.c:356
+msgid "unable to update local ref"
+msgstr "kann lokale Referenz nicht aktualisieren"
+
+#: builtin/fetch.c:356
+msgid "forced update"
+msgstr "Aktualisierung erzwungen"
+
+#: builtin/fetch.c:362
+msgid "(non-fast-forward)"
+msgstr "(kein Vorspulen)"
+
+#: builtin/fetch.c:393 builtin/fetch.c:685
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr "kann %s nicht öffnen: %s\n"
+
+#: builtin/fetch.c:402
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr "%s hat nicht alle erforderlichen Objekte gesendet\n"
+
+#: builtin/fetch.c:488
+#, c-format
+msgid "From %.*s\n"
+msgstr "Von %.*s\n"
+
+#: builtin/fetch.c:499
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+"Einige lokale Referenzen konnten nicht aktualisiert werden; versuche\n"
+"'git remote prune %s' um jeden älteren, widersprüchlichen Zweig zu entfernen."
+
+#: builtin/fetch.c:549
+#, c-format
+msgid " (%s will become dangling)"
+msgstr " (%s wird unreferenziert)"
+
+#: builtin/fetch.c:550
+#, c-format
+msgid " (%s has become dangling)"
+msgstr " (%s wurde unreferenziert)"
+
+#: builtin/fetch.c:557
+msgid "[deleted]"
+msgstr "[gelöscht]"
+
+#: builtin/fetch.c:558 builtin/remote.c:1055
+msgid "(none)"
+msgstr "(nichts)"
+
+#: builtin/fetch.c:675
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr ""
+"Das Anfordern in den aktuellen Zweig %s von einem nicht-bloßen Projektarchiv "
+"wurde verweigert."
+
+#: builtin/fetch.c:709
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr "Weiß nicht wie von %s angefordert wird."
+
+#: builtin/fetch.c:786
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr "Option \"%s\" Wert \"%s\" ist nicht gültig für %s"
+
+#: builtin/fetch.c:789
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr "Option \"%s\" wird ignoriert für %s\n"
+
+#: builtin/fetch.c:888
+#, c-format
+msgid "Fetching %s\n"
+msgstr "Fordere an von %s\n"
+
+#: builtin/fetch.c:890 builtin/remote.c:100
+#, c-format
+msgid "Could not fetch %s"
+msgstr "Konnte nicht von %s anfordern"
+
+#: builtin/fetch.c:907
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr ""
+"Kein externes Projektarchiv angegeben. Bitte gebe entweder eine URL\n"
+"oder den Namen des externen Archivs an, von welchem neue\n"
+"Versionen angefordert werden sollen."
+
+#: builtin/fetch.c:927
+msgid "You need to specify a tag name."
+msgstr "Du musst den Namen der Markierung angeben."
+
+#: builtin/fetch.c:979
+msgid "fetch --all does not take a repository argument"
+msgstr "fetch --all akzeptiert kein Projektarchiv als Argument"
+
+#: builtin/fetch.c:981
+msgid "fetch --all does not make sense with refspecs"
+msgstr "fetch --all macht keinen Sinn mit Referenzspezifikationen"
+
+#: builtin/fetch.c:992
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr "Kein externes Archiv (einzeln oder Gruppe): %s"
+
+#: builtin/fetch.c:1000
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr ""
+"Abholen einer Gruppe mit Angabe von Referenzspezifikationen macht keinen "
+"Sinn."
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr "Ungültiger %s: '%s'"
+
+#: builtin/gc.c:90
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr "zu langes Objekt-Verzeichnis %.*s"
+
+#: builtin/gc.c:221
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr ""
+"Die Datenbank des Projektarchivs wird für eine optimale Performance "
+"komprimiert.\n"
+
+#: builtin/gc.c:224
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+"Die Datenbank des Projektarchivs wird für eine optimale Performance\n"
+"komprimiert. Du kannst auch \"git gc\" manuell ausführen.\n"
+"Siehe \"git help gc\" für weitere Informationen.\n"
+
+#: builtin/gc.c:251
+msgid ""
+"There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr ""
+"Es gibt zu viele unerreichbare lose Objekte; führe 'git prune' aus, um diese "
+"zu entfernen."
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr "grep: Fehler beim Erzeugen eines Thread: %s"
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr "Fehler beim Verzeichniswechsel: %s"
+
+#: builtin/grep.c:478 builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr "konnte Zweig (%s) nicht lesen"
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr "kann \"grep\" nicht mit Objekten des Typs %s durchführen"
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr "Schalter '%c' erwartet einen numerischen Wert"
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr "kann '%s' nicht öffnen"
+
+#: builtin/grep.c:885
+msgid "no pattern given."
+msgstr "keine Muster angegeben"
+
+#: builtin/grep.c:899
+#, c-format
+msgid "bad object %s"
+msgstr "ungültiges Objekt %s"
+
+#: builtin/grep.c:940
+msgid "--open-files-in-pager only works on the worktree"
+msgstr "--open-files-in-pager arbeitet nur innerhalb des Arbeitsbaums"
+
+#: builtin/grep.c:963
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr "--cached oder --untracked kann nicht mit --no-index benutzt werden"
+
+#: builtin/grep.c:968
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr "--no-index oder --untracked kann nicht mit Versionen benutzt werden"
+
+#: builtin/grep.c:971
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr ""
+"--[no-]exlude-standard kann nicht mit beobachteten Inhalten benutzt werden"
+
+#: builtin/grep.c:979
+msgid "both --cached and trees are given."
+msgstr "sowohl --cached als auch Zweige gegeben"
+
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr "nicht erkanntes Hilfeformat: %s"
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr "Konnte emacsclient nicht starten."
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr "Konnte Version des emacsclient nicht parsen."
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr "Version des emacsclient '%d' ist zu alt (< 22)."
+
+#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr "Fehler beim Ausführen von '%s': %s"
+
+#: builtin/help.c:211
+#, c-format
+msgid ""
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
+msgstr ""
+"'%s': Pfad für nicht unterstützten Handbuchbetrachter.\n"
+"Du könntest stattdessen 'man.<Werkzeug>.cmd' benutzen."
+
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
+msgstr ""
+"'%s': Kommando für unterstützten Handbuchbetrachter.\n"
+"Du könntest stattdessen 'man.<Werkzeug>.path' benutzen."
+
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr "Die allgemein verwendeten Git-Kommandos sind:"
+
+#: builtin/help.c:355
+#, c-format
+msgid "'%s': unknown man viewer."
+msgstr "'%s': unbekannter Handbuch-Betrachter."
+
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr "kein Handbuch-Betrachter konnte mit dieser Anfrage umgehen"
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr "kein Informations-Betrachter konnte mit dieser Anfrage umgehen"
+
+#: builtin/help.c:391
+#, c-format
+msgid "'%s': not a documentation directory."
+msgstr "'%s' ist kein Dokumentationsverzeichnis"
+
+#: builtin/help.c:432 builtin/help.c:439
+#, c-format
+msgid "usage: %s%s"
+msgstr "Verwendung: %s%s"
+
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
+msgstr "für `git %s' wurde der Alias `%s' angelegt"
+
+#: builtin/index-pack.c:169
+#, c-format
+msgid "object type mismatch at %s"
+msgstr "Objekt-Typen passen bei %s nicht zusammen"
+
+#: builtin/index-pack.c:189
+msgid "object of unexpected type"
+msgstr "Objekt hat unerwarteten Typ"
+
+#: builtin/index-pack.c:226
+#, c-format
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] "kann %d Byte nicht lesen"
+msgstr[1] "kann %d Bytes nicht lesen"
+
+#: builtin/index-pack.c:236
+msgid "early EOF"
+msgstr "zu frühes Dateiende"
+
+#: builtin/index-pack.c:237
+msgid "read error on input"
+msgstr "Fehler beim Lesen der Eingabe"
+
+#: builtin/index-pack.c:249
+msgid "used more bytes than were available"
+msgstr "verwendete mehr Bytes als verfügbar waren"
+
+#: builtin/index-pack.c:256
+msgid "pack too large for current definition of off_t"
+msgstr "Paket ist zu groß für die aktuelle Definition von off_t"
+
+#: builtin/index-pack.c:272
+#, c-format
+msgid "unable to create '%s'"
+msgstr "konnte '%s' nicht erstellen"
+
+#: builtin/index-pack.c:277
+#, c-format
+msgid "cannot open packfile '%s'"
+msgstr "Kann Paketdatei '%s' nicht öffnen"
+
+#: builtin/index-pack.c:291
+msgid "pack signature mismatch"
+msgstr "Paketsignatur stimmt nicht überein"
+
+#: builtin/index-pack.c:311
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
+msgstr "Paket hat ein ungültiges Objekt bei Versatz %lu: %s"
+
+#: builtin/index-pack.c:405
+#, c-format
+msgid "inflate returned %d"
+msgstr "Dekomprimierung gab %d zurück"
+
+#: builtin/index-pack.c:450
+msgid "offset value overflow for delta base object"
+msgstr "Wert für Versatz bei Differenzobjekt übergelaufen"
+
+#: builtin/index-pack.c:458
+msgid "delta base offset is out of bound"
+msgstr ""
+"Wert für Versatz bei Differenzobjekt liegt außerhalb des gültigen Bereichs"
+
+#: builtin/index-pack.c:466
+#, c-format
+msgid "unknown object type %d"
+msgstr "Unbekannter Objekt-Typ %d"
+
+#: builtin/index-pack.c:495
+msgid "cannot pread pack file"
+msgstr "Kann Paketdatei %s nicht lesen"
+
+#: builtin/index-pack.c:497
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] "frühzeitiges Ende der Paketdatei, vermisse %lu Byte"
+msgstr[1] "frühzeitiges Ende der Paketdatei, vermisse %lu Bytes"
+
+#: builtin/index-pack.c:510
+msgid "serious inflate inconsistency"
+msgstr "ernsthafte Inkonsistenz nach Dekomprimierung"
+
+#: builtin/index-pack.c:583
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "Kann existierendes Objekt %s nicht lesen."
+
+#: builtin/index-pack.c:586
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
+msgstr "SHA1 KOLLISION MIT %s GEFUNDEN !"
+
+#: builtin/index-pack.c:598
+#, c-format
+msgid "invalid blob object %s"
+msgstr "ungültiges Blob-Objekt %s"
+
+#: builtin/index-pack.c:610
+#, c-format
+msgid "invalid %s"
+msgstr "Ungültiger Objekt-Typ %s"
+
+#: builtin/index-pack.c:612
+msgid "Error in object"
+msgstr "Fehler in Objekt"
+
+#: builtin/index-pack.c:614
+#, c-format
+msgid "Not all child objects of %s are reachable"
+msgstr "Nicht alle Kind-Objekte von %s sind erreichbar"
+
+#: builtin/index-pack.c:687 builtin/index-pack.c:713
+msgid "failed to apply delta"
+msgstr "Konnte Dateiunterschied nicht anwenden"
+
+#: builtin/index-pack.c:850
+msgid "Receiving objects"
+msgstr "Empfange Objekte"
+
+#: builtin/index-pack.c:850
+msgid "Indexing objects"
+msgstr "Indiziere Objekte"
+
+#: builtin/index-pack.c:872
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr "Paket ist beschädigt (SHA1 unterschiedlich)"
+
+#: builtin/index-pack.c:877
+msgid "cannot fstat packfile"
+msgstr "kann Paketdatei nicht lesen"
+
+#: builtin/index-pack.c:880
+msgid "pack has junk at the end"
+msgstr "Paketende enthält nicht verwendbaren Inhalt"
+
+#: builtin/index-pack.c:903
+msgid "Resolving deltas"
+msgstr "Löse Unterschiede auf"
+
+#: builtin/index-pack.c:954
+msgid "confusion beyond insanity"
+msgstr "Fehler beim Auflösen der Unterschiede"
+
+#: builtin/index-pack.c:973
+#, c-format
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] "Paket hat %d unaufgelöste Unterschied"
+msgstr[1] "Paket hat %d unaufgelöste Unterschiede"
+
+#: builtin/index-pack.c:998
+#, c-format
+msgid "unable to deflate appended object (%d)"
+msgstr "Konnte angehängtes Objekt (%d) nicht komprimieren"
+
+#: builtin/index-pack.c:1077
+#, c-format
+msgid "local object %s is corrupt"
+msgstr "lokales Objekt %s ist beschädigt"
+
+#: builtin/index-pack.c:1101
+msgid "error while closing pack file"
+msgstr "Fehler beim Schließen der Paketdatei"
+
+#: builtin/index-pack.c:1114
+#, c-format
+msgid "cannot write keep file '%s'"
+msgstr "Kann Paketbeschreibungsdatei '%s' nicht schreiben"
+
+#: builtin/index-pack.c:1122
+#, c-format
+msgid "cannot close written keep file '%s'"
+msgstr "Kann eben erstellte Paketbeschreibungsdatei '%s' nicht schließen"
+
+#: builtin/index-pack.c:1135
+msgid "cannot store pack file"
+msgstr "Kann Paketdatei nicht speichern"
+
+#: builtin/index-pack.c:1146
+msgid "cannot store index file"
+msgstr "Kann Indexdatei nicht speichern"
+
+#: builtin/index-pack.c:1247
+#, c-format
+msgid "Cannot open existing pack file '%s'"
+msgstr "Kann existierende Paketdatei '%s' nicht öffnen"
+
+#: builtin/index-pack.c:1249
+#, c-format
+msgid "Cannot open existing pack idx file for '%s'"
+msgstr "Kann existierende Indexdatei für Paket '%s' nicht öffnen"
+
+#: builtin/index-pack.c:1296
+#, c-format
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] "kein Unterschied: %d Objekt"
+msgstr[1] "kein Unterschied: %d Objekte"
+
+#: builtin/index-pack.c:1303
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] "Länge der Objekt-Liste = %d: %lu Objekt"
+msgstr[1] "Länge der Objekt-Liste = %d: %lu Objekte"
+
+#: builtin/index-pack.c:1330
+msgid "Cannot come back to cwd"
+msgstr "Kann nicht zurück zu Arbeitsverzeichnis wechseln"
+
+#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
+#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#, c-format
+msgid "bad %s"
+msgstr "%s ist ungültig"
+
+#: builtin/index-pack.c:1407
+msgid "--fix-thin cannot be used without --stdin"
+msgstr "--fix-thin kann nicht ohne --stdin benutzt werden"
+
+#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr "Name der Paketdatei '%s' endet nicht mit '.pack'"
+
+#: builtin/index-pack.c:1430
+msgid "--verify with no packfile name given"
+msgstr "--verify ohne Name der Paketdatei angegeben"
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr "Konnte Gruppenschreibrecht für %s nicht setzen."
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr "zu langer Vorlagen-Name %s"
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr "Kann '%s' nicht lesen"
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr "kann Vorlage '%s' nicht lesen"
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr "kann Verzeichnis '%s' nicht öffnen"
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr "kann Verknüpfung '%s' nicht lesen"
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr "zu lange symbolische Verknüpfung %s"
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr "kann '%s' nicht mit '%s' symbolisch verknüpfen"
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr "kann '%s' nicht nach '%s' kopieren"
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr "ignoriere Vorlage %s"
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr "zu langer Vorlagen-Pfad %s"
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr "keine Vorlagen in '%s' gefunden"
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr "kopiere keine Vorlagen mit einer falschen Formatversion %d von '%s'"
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr "ungültiges Git-Verzeichnis %s"
+
+#: builtin/init-db.c:322 builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr "%s existiert bereits"
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr "kann nicht mit Dateityp %d umgehen"
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr "Konnte %s nicht nach %s verschieben"
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr "Konnte git-Verknüfung %s nicht erstellen"
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s Git-Projektarchiv in %s%s\n"
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr "Reinitialisierte existierendes"
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr "Initialisierte leeres"
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr " gemeinsames"
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr "kann aktuelles Arbeitsverzeichnis nicht ermitteln"
+
+#: builtin/init-db.c:521 builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr "kann Verzeichnis %s nicht erstellen"
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr "kann nicht in Verzeichnis %s wechseln"
+
+#: builtin/init-db.c:554
+#, c-format
+msgid ""
+"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
+"dir=<directory>)"
+msgstr ""
+"%s (oder --work-tree=<Verzeichnis>) nicht erlaubt ohne Spezifizierung von %s "
+"(oder --git-dir=<Verzeichnis>)"
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr "Kann nicht auf aktuelles Arbeitsverzeichnis zugreifen."
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr "Kann nicht auf Arbeitsbaum '%s' zugreifen."
+
+#: builtin/log.c:188
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr "letzte Ausgabe: %d %s\n"
+
+#: builtin/log.c:401 builtin/log.c:489
+#, c-format
+msgid "Could not read object %s"
+msgstr "Kann Objekt %s nicht lesen."
+
+#: builtin/log.c:513
+#, c-format
+msgid "Unknown type: %d"
+msgstr "Unbekannter Typ: %d"
+
+#: builtin/log.c:602
+msgid "format.headers without value"
+msgstr "format.headers ohne Wert"
+
+#: builtin/log.c:676
+msgid "name of output directory is too long"
+msgstr "Name des Ausgabeverzeichnisses ist zu lang."
+
+#: builtin/log.c:687
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr "Kann Patch-Datei %s nicht öffnen"
+
+#: builtin/log.c:701
+msgid "Need exactly one range."
+msgstr "Brauche genau einen Versionsbereich."
+
+#: builtin/log.c:709
+msgid "Not a range."
+msgstr "Kein Versionsbereich."
+
+#: builtin/log.c:786
+msgid "Cover letter needs email format"
+msgstr "Anschreiben benötigt E-Mail-Format"
+
+#: builtin/log.c:859
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr "ungültiges in-reply-to: %s"
+
+#: builtin/log.c:932
+msgid "Two output directories?"
+msgstr "Zwei Ausgabeverzeichnisse?"
+
+#: builtin/log.c:1153
+#, c-format
+msgid "bogus committer info %s"
+msgstr "unechte Einreicher-Informationen %s"
+
+#: builtin/log.c:1198
+msgid "-n and -k are mutually exclusive."
+msgstr "-n und -k schliessen sich gegenseitig aus"
+
+#: builtin/log.c:1200
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr "--subject-prefix und -k schliessen sich gegenseitig aus"
+
+#: builtin/log.c:1208
+msgid "--name-only does not make sense"
+msgstr "--name-only macht keinen Sinn"
+
+#: builtin/log.c:1210
+msgid "--name-status does not make sense"
+msgstr "--name-status macht keinen Sinn"
+
+#: builtin/log.c:1212
+msgid "--check does not make sense"
+msgstr "--check macht keinen Sinn"
+
+#: builtin/log.c:1235
+msgid "standard output, or directory, which one?"
+msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?"
+
+#: builtin/log.c:1237
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr "Konnte Verzeichnis '%s' nicht erstellen."
+
+#: builtin/log.c:1390
+msgid "Failed to create output files"
+msgstr "Fehler beim Erstellen der Ausgabedateien."
+
+#: builtin/log.c:1494
+#, c-format
+msgid ""
+"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr ""
+"Konnte gefolgten, externen Zweig nicht finden, bitte gebe <upstream> manuell "
+"an.\n"
+
+#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#, c-format
+msgid "Unknown commit %s"
+msgstr "Unbekannte Version %s"
+
+#: builtin/merge.c:90
+msgid "switch `m' requires a value"
+msgstr "Schalter 'm' erfordert einen Wert."
+
+#: builtin/merge.c:127
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr "Konnte Zusammenführungsstrategie '%s' nicht finden.\n"
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Available strategies are:"
+msgstr "Verfügbare Strategien sind:"
+
+#: builtin/merge.c:133
+#, c-format
+msgid "Available custom strategies are:"
+msgstr "Verfügbare benutzerdefinierte Strategien sind:"
+
+#: builtin/merge.c:240
+msgid "could not run stash."
+msgstr "Konnte \"stash\" nicht ausführen."
+
+#: builtin/merge.c:245
+msgid "stash failed"
+msgstr "\"stash\" fehlgeschlagen"
+
+#: builtin/merge.c:250
+#, c-format
+msgid "not a valid object: %s"
+msgstr "kein gültiges Objekt: %s"
+
+#: builtin/merge.c:269 builtin/merge.c:286
+msgid "read-tree failed"
+msgstr "read-tree fehlgeschlagen"
+
+#: builtin/merge.c:316
+msgid " (nothing to squash)"
+msgstr " (nichts zu quetschen)"
+
+#: builtin/merge.c:329
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr "Quetsche Version -- Zweigspitze (HEAD) wird nicht aktualisiert\n"
+
+#: builtin/merge.c:361
+msgid "Writing SQUASH_MSG"
+msgstr "Schreibe SQUASH_MSG"
+
+#: builtin/merge.c:363
+msgid "Finishing SQUASH_MSG"
+msgstr "Schließe SQUASH_MSG ab"
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr ""
+"Keine Zusammenführungsbeschreibung -- Zweigspitze (HEAD) wird nicht "
+"aktualisiert\n"
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr "'%s' zeigt auf keine Version"
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr "Ungültiger branch.%s.mergeoptions String: %s"
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr "\"git write-tree\" schlug beim Schreiben eines Baumes fehl"
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr "Lesen des Zwischenspeichers fehlgeschlagen"
+
+#: builtin/merge.c:697
+msgid "Unable to write index."
+msgstr "Konnte Bereitstellung nicht schreiben."
+
+#: builtin/merge.c:710
+msgid "Not handling anything other than two heads merge."
+msgstr "Es wird nur die Zusammenführung von zwei Zweigen behandelt."
+
+#: builtin/merge.c:724
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr "Unbekannte Option für merge-recursive: -X%s"
+
+#: builtin/merge.c:738
+#, c-format
+msgid "unable to write %s"
+msgstr "konnte %s nicht schreiben"
+
+#: builtin/merge.c:877
+#, c-format
+msgid "Could not read from '%s'"
+msgstr "konnte nicht von '%s' lesen"
+
+#: builtin/merge.c:886
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr ""
+"Zusammenführung wurde nicht eingetragen; benutze 'git commit' um die "
+"Zusammenführung abzuschließen.\n"
+
+#: builtin/merge.c:892
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+"Bitte gebe eine Versionsbeschreibung ein um zu erklären, warum diese "
+"Zusammenführung erforderlich ist,\n"
+"insbesondere wenn es einen aktualisierten, externen Zweig mit einem Thema-"
+"Zweig zusammenführt.\n"
+"\n"
+"Zeilen beginnend mit '#' werden ignoriert, und eine leere Beschreibung "
+"bricht die Eintragung ab.\n"
+
+#: builtin/merge.c:916
+msgid "Empty commit message."
+msgstr "Leere Versionsbeschreibung"
+
+#: builtin/merge.c:928
+#, c-format
+msgid "Wonderful.\n"
+msgstr "Wunderbar.\n"
+
+#: builtin/merge.c:993
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr ""
+"Automatische Zusammenführung fehlgeschlagen; behebe die Konflikte und trage "
+"dann das Ergebnis ein.\n"
+
+#: builtin/merge.c:1009
+#, c-format
+msgid "'%s' is not a commit"
+msgstr "'%s' ist keine Version"
+
+#: builtin/merge.c:1050
+msgid "No current branch."
+msgstr "Du befindest dich auf keinem Zweig."
+
+#: builtin/merge.c:1052
+msgid "No remote for the current branch."
+msgstr "Kein externes Archiv für den aktuellen Zweig."
+
+#: builtin/merge.c:1054
+msgid "No default upstream defined for the current branch."
+msgstr ""
+"Es ist kein externes Standard-Projektarchiv für den aktuellen Zweig "
+"definiert."
+
+#: builtin/merge.c:1059
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr "Kein externer Übernahmezweig für %s von %s"
+
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr "%s - nichts was wir zusammenführen können"
+
+#: builtin/merge.c:1214
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr "Es gibt keine Zusammenführung zum Abbrechen (vermisse MERGE_HEAD)"
+
+#: builtin/merge.c:1230 git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"Du hast deine Zusammenführung nicht abgeschlossen (MERGE_HEAD existiert).\n"
+"Bitte trage deine Änderungen ein, bevor du zusammenführen kannst."
+
+#: builtin/merge.c:1233 git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr ""
+"Du hast deine Zusammenführung nicht abgeschlossen (MERGE_HEAD existiert)."
+
+#: builtin/merge.c:1237
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"Du hast \"cherry-pick\" nicht abgeschlossen (CHERRY_PICK_HEAD existiert).\n"
+"Bitte trage deine Änderungen ein, bevor du zusammenführen kannst."
+
+#: builtin/merge.c:1240
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr ""
+"Du hast \"cherry-pick\" nicht abgeschlossen (CHERRY_PICK_HEAD existiert)."
+
+#: builtin/merge.c:1249
+msgid "You cannot combine --squash with --no-ff."
+msgstr "Du kannst --squash nicht mit --no-ff kombinieren."
+
+#: builtin/merge.c:1254
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr "Du kannst --no-ff nicht mit --ff--only kombinieren."
+
+#: builtin/merge.c:1261
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr "Keine Version angegeben und merge.defaultToUpstream ist nicht gesetzt."
+
+#: builtin/merge.c:1293
+msgid "Can merge only exactly one commit into empty head"
+msgstr "Kann nur exakt eine Version in einem leeren Zweig zusammenführen."
+
+#: builtin/merge.c:1296
+msgid "Squash commit into empty head not supported yet"
+msgstr "Bin auf einem Zweig, der noch geboren wird; kann nicht quetschen."
+
+#: builtin/merge.c:1298
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr "nicht vorzuspulende Version macht in einem leeren Zweig keinen Sinn"
+
+#: builtin/merge.c:1413
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr "Aktualisiere %s..%s\n"
+
+#: builtin/merge.c:1451
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr "Probiere wirklich triviale \"in-index\"-Zusammenführung...\n"
+
+#: builtin/merge.c:1458
+#, c-format
+msgid "Nope.\n"
+msgstr "Nein.\n"
+
+#: builtin/merge.c:1490
+msgid "Not possible to fast-forward, aborting."
+msgstr "Vorspulen nicht möglich, breche ab."
+
+#: builtin/merge.c:1513 builtin/merge.c:1592
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr "Rücklauf des Zweiges bis zum Ursprung...\n"
+
+#: builtin/merge.c:1517
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr "Probiere Zusammenführungsstrategie %s...\n"
+
+#: builtin/merge.c:1583
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr "Keine Zusammenführungsstrategie behandelt diese Zusammenführung.\n"
+
+#: builtin/merge.c:1585
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr "Zusammenführung mit Strategie %s fehlgeschlagen.\n"
+
+#: builtin/merge.c:1594
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr "Benutze \"%s\" um die Auflösung per Hand vorzubereiten.\n"
+
+#: builtin/merge.c:1606
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr ""
+"Automatische Zusammenführung abgeschlossen; halte, wie gewünscht, vor der "
+"Eintragung an\n"
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr "Prüfe Umbenennung von '%s' nach '%s'\n"
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr "ungültige Quelle"
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr "kann Verzeichnis nicht in sich selbst verschieben"
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr "kann Verzeichnis nicht über Datei verschieben"
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr "Huh? %.*s ist in der Bereitstellung?"
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr "Quellverzeichnis ist leer"
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr "nicht unter Versionskontrolle"
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr "Ziel existiert bereits"
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr "überschreibe '%s'"
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr "Kann nicht überschreiben"
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr "mehrere Quellen für das selbe Ziel"
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr "%s, Quelle=%s, Ziel=%s"
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr "Benenne %s nach %s um\n"
+
+#: builtin/mv.c:215 builtin/remote.c:731
+#, c-format
+msgid "renaming '%s' failed"
+msgstr "Umbenennung von '%s' fehlgeschlagen"
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr "konnte 'show' für Objekt '%s' nicht starten"
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr "konnte Datei-Deskriptor für Ausgabe von 'show' nicht öffnen"
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr "Schließen der Verbindung zu 'show' ist für Objekt '%s' fehlgeschlagen."
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr "konnte 'show' für Objekt '%s' nicht abschließen"
+
+#: builtin/notes.c:175 builtin/tag.c:347
+#, c-format
+msgid "could not create file '%s'"
+msgstr "konnte Datei '%s' nicht erstellen"
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr "Bitte liefere den Notiz-Inhalt unter Verwendung der Option -m oder -F."
+
+#: builtin/notes.c:210 builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr "Entferne Notiz für Objekt %s\n"
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr "Konnte Notiz-Objekt nicht schreiben"
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr "Die Notiz-Inhalte wurden in %s belassen"
+
+#: builtin/notes.c:251 builtin/tag.c:542
+#, c-format
+msgid "cannot read '%s'"
+msgstr "kann '%s' nicht lesen"
+
+#: builtin/notes.c:253 builtin/tag.c:545
+#, c-format
+msgid "could not open or read '%s'"
+msgstr "konnte '%s' nicht öffnen oder lesen"
+
+#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
+#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
+#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
+#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:558
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr "Konnte '%s' nicht als gültige Referenz auflösen."
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr "Fehler beim Lesen des Objektes '%s'."
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr "Kann uninitialisierten/unreferenzierten Notiz-Baum nicht eintragen."
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr "Ungültiger notes.rewriteMode Wert: '%s'"
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr ""
+"Neuschreiben der Notizen in %s zurückgewiesen (außerhalb von refs/notes/)"
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr "Ungültiger %s Wert: '%s'"
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr "Fehlerhafte Eingabezeile: '%s'."
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr "Fehler beim Kopieren der Notizen von '%s' nach '%s'"
+
+#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
+#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr "zu viele Parameter"
+
+#: builtin/notes.c:513 builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr "Kein Notiz für Objekt %s gefunden."
+
+#: builtin/notes.c:580
+#, c-format
+msgid ""
+"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+"Konnte Notizen nicht hinzufügen. Existierende Notizen für Objekt %s "
+"gefunden. Verwende '-f' um die existierenden Notizen zu überschreiben."
+
+#: builtin/notes.c:585 builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr "Überschreibe existierende Notizen für Objekt %s\n"
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr "zu wenig Parameter"
+
+#: builtin/notes.c:656
+#, c-format
+msgid ""
+"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+"Kann Notizen nicht kopieren. Existierende Notizen für Objekt %s gefunden. "
+"Verwende '-f' um die existierenden Notizen zu überschreiben."
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr "Keine Notizen für Quell-Objekt %s. Kopie nicht möglich."
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+"Die Optionen -m/-F/-c/-C sind für das Unterkommando 'edit' veraltet.\n"
+"Bitte benutze stattdessen 'git notes add -f -m/-F/-c/-C'.\n"
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr "Objekt %s hat keine Notiz\n"
+
+#: builtin/notes.c:1103 builtin/remote.c:1598
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr "Unbekanntes Unterkommando: %s"
+
+#: builtin/pack-objects.c:2337
+#, c-format
+msgid "unsupported index version %s"
+msgstr "Nicht unterstützte Bereitstellungsversion %s"
+
+#: builtin/pack-objects.c:2341
+#, c-format
+msgid "bad index version '%s'"
+msgstr "Ungültige Bereitstellungsversion '%s'"
+
+#: builtin/pack-objects.c:2364
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr "Option %s akzeptiert keine negative Form"
+
+#: builtin/pack-objects.c:2368
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr "konnte Wert '%s' für Option %s nicht parsen"
+
+#: builtin/push.c:45
+msgid "tag shorthand without <tag>"
+msgstr "Kurzschrift für Markierung ohne <Markierung>"
+
+#: builtin/push.c:64
+msgid "--delete only accepts plain target ref names"
+msgstr "--delete akzeptiert nur reine Referenz-Namen als Ziel"
+
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+"\n"
+"Um eine Variante permanent zu verwenden, siehe push.default in 'git help "
+"config'."
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+"Der Name des externen Ãœbernahmezweiges stimmt nicht mit dem Namen deines\n"
+"aktuellen Zweiges überein. Um auf den Übernahmezweig in dem externen\n"
+"Projektarchiv zu versenden, benutze:\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"Um auf den Zweig mit dem selben Namen in dem externen Projekarchiv\n"
+"zu versenden, benutze:\n"
+"\n"
+" git push %s %s\n"
+"%s"
+
+#: builtin/push.c:121
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+"Du befindest dich sich im Moment auf keinem Zweig.\n"
+"Um die Historie, führend zum aktuellen (freistehende Zweigspitze (HEAD))\n"
+"Status zu versenden, benutze\n"
+"\n"
+" git push %s HEAD:<Name-des-externen-Zweiges>\n"
+
+#: builtin/push.c:128
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+"Der aktuelle Zweig %s hat keinen Zweig im externen Projektarchiv.\n"
+"Um den aktuellen Zweig zu versenden und das Fernarchiv als externes\n"
+"Projektarchiv zu verwenden, benutze\n"
+"\n"
+" git push --set-upstream %s %s\n"
+
+#: builtin/push.c:136
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr "Der aktuelle Zweig %s hat mehrere externe Zweige, Versand verweigert."
+
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+"Du versendest nach '%s', welches kein externes Projektarchiv deines\n"
+"aktuellen Zweiges '%s' ist, ohne mir mitzuteilen, was ich versenden\n"
+"soll, um welchen externen Zweig zu aktualisieren."
+
+#: builtin/push.c:174
+msgid ""
+"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr ""
+"Du hast keine Referenzspezifikationen zum Versenden angegeben, und push."
+"default ist \"nothing\"."
+
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"Aktualisierungen wurden zurückgewiesen, weil die Spitze deines aktuellen\n"
+"Zweiges hinter seinem externen Gegenstück zurückgefallen ist. Führe die\n"
+"externen Änderungen zusammen (z.B. 'git pull') bevor du erneut versendest.\n"
+"Siehe auch die Sektion 'Note about fast-forwards' in 'git push --help'\n"
+"für weitere Details."
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+"Aktualisierungen wurden zurückgewiesen, weil die Spitze eines versendeten\n"
+"Zweiges hinter seinem externen Gegenstück zurückgefallen ist. Wenn du nicht\n"
+"beabsichtigt hast, diesen Zweig zu versenden, kannst du auch den zu "
+"versendenden\n"
+"Zweig spezifizieren oder die Konfigurationsvariable 'push.default' zu "
+"'current'\n"
+"oder 'upstream' setzen, um nur den aktuellen Zweig zu versenden."
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"Aktualisierungen wurden zurückgewiesen, weil die Spitze eines versendeten\n"
+"Zweiges hinter seinem externen Gegenstück zurückgefallen ist. Checke diesen\n"
+"Zweig aus und führe die externen Änderungen zusammen (z.B. 'git pull')\n"
+"bevor du erneut versendest.\n"
+"Siehe auch die Sektion 'Note about fast-forwards' in 'git push --help'\n"
+"für weitere Details."
+
+#: builtin/push.c:233
+#, c-format
+msgid "Pushing to %s\n"
+msgstr "Versende nach %s\n"
+
+#: builtin/push.c:237
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr "Fehler beim Versenden einiger Referenzen nach '%s'"
+
+#: builtin/push.c:269
+#, c-format
+msgid "bad repository '%s'"
+msgstr "ungültiges Projektarchiv '%s'"
+
+#: builtin/push.c:270
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote "
+"repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+"Kein Ziel zum Versenden konfiguriert.\n"
+"Entweder spezifizierst du die URL von der Kommandozeile oder konfigurierst "
+"ein externes Projektarchiv unter Benutzung von\n"
+"\n"
+" git remote add <Name> <URL>\n"
+"\n"
+"und versendest dann unter Benutzung dieses Namens\n"
+"\n"
+" git push <Name>\n"
+
+#: builtin/push.c:285
+msgid "--all and --tags are incompatible"
+msgstr "--all und --tags sind inkompatibel"
+
+#: builtin/push.c:286
+msgid "--all can't be combined with refspecs"
+msgstr "--all kann nicht mit Referenzspezifikationen kombiniert werden"
+
+#: builtin/push.c:291
+msgid "--mirror and --tags are incompatible"
+msgstr "--mirror und --tags sind inkompatibel"
+
+#: builtin/push.c:292
+msgid "--mirror can't be combined with refspecs"
+msgstr "--mirror kann nicht mit Referenzspezifikationen kombiniert werden"
+
+#: builtin/push.c:297
+msgid "--all and --mirror are incompatible"
+msgstr "--all und --mirror sind inkompatibel"
+
+#: builtin/push.c:385
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr "--delete ist inkompatibel mit --all, --mirror und --tags"
+
+#: builtin/push.c:387
+msgid "--delete doesn't make sense without any refs"
+msgstr "--delete macht ohne irgendeine Referenz keinen Sinn"
+
+#: builtin/remote.c:98
+#, c-format
+msgid "Updating %s"
+msgstr "Aktualisiere %s"
+
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
+msgstr ""
+"--mirror ist gefährlich und veraltet; bitte\n"
+"\t benutze stattdessen --mirror=fetch oder --mirror=push"
+
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
+msgstr "unbekanntes Argument für Option --mirror: %s"
+
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
+msgstr "Angabe eines Hauptzweiges macht mit --mirror keinen Sinn"
+
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
+msgstr ""
+"die Angabe von zu folgenden Zweigen macht nur mit dem Abholen von "
+"Spiegelarchiven Sinn"
+
+#: builtin/remote.c:195 builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
+msgstr "externes Projektarchiv %s existiert bereits"
+
+#: builtin/remote.c:199 builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
+msgstr "'%s' ist kein gültiger Name für ein externes Projektarchiv"
+
+#: builtin/remote.c:243
+#, c-format
+msgid "Could not setup master '%s'"
+msgstr "Konnte symbolische Referenz für Hauptzweig von '%s' nicht einrichten"
+
+#: builtin/remote.c:299
+#, c-format
+msgid "more than one %s"
+msgstr "mehr als ein %s"
+
+#: builtin/remote.c:339
+#, c-format
+msgid "Could not get fetch map for refspec %s"
+msgstr "Konnte Abholungszuordnung für Referenzspezifikation %s nicht bekommen"
+
+#: builtin/remote.c:440 builtin/remote.c:448
+msgid "(matching)"
+msgstr "(übereinstimmend)"
+
+#: builtin/remote.c:452
+msgid "(delete)"
+msgstr "(lösche)"
+
+#: builtin/remote.c:595 builtin/remote.c:601 builtin/remote.c:607
+#, c-format
+msgid "Could not append '%s' to '%s'"
+msgstr "Konnte '%s' nicht an '%s' anhängen."
+
+#: builtin/remote.c:639 builtin/remote.c:792 builtin/remote.c:890
+#, c-format
+msgid "No such remote: %s"
+msgstr "Kein solches externes Archiv: %s"
+
+#: builtin/remote.c:656
+#, c-format
+msgid "Could not rename config section '%s' to '%s'"
+msgstr "Konnte Sektion '%s' in Konfiguration nicht nach '%s' umbenennen"
+
+#: builtin/remote.c:662 builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
+msgstr "Konnte Sektion '%s' nicht aus Konfiguration entfernen"
+
+#: builtin/remote.c:677
+#, c-format
+msgid ""
+"Not updating non-default fetch refspec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
+msgstr ""
+"Keine Aktualisierung der nicht standardmäßigen Referenzspezifikation zum "
+"Abholen\n"
+"\t%s\n"
+"\tBitte aktualisiere, falls notwendig, die Konfiguration manuell."
+
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
+msgstr "Konnte '%s' nicht anhängen."
+
+#: builtin/remote.c:694
+#, c-format
+msgid "Could not set '%s'"
+msgstr "Konnte '%s' nicht setzen"
+
+#: builtin/remote.c:716
+#, c-format
+msgid "deleting '%s' failed"
+msgstr "Konnte '%s' nicht löschen"
+
+#: builtin/remote.c:750
+#, c-format
+msgid "creating '%s' failed"
+msgstr "Konnte '%s' nicht erstellen"
+
+#: builtin/remote.c:764
+#, c-format
+msgid "Could not remove branch %s"
+msgstr "Konnte Zweig %s nicht entfernen"
+
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] ""
+"Hinweis: Ein Zweig außerhalb der /refs/remotes/ Hierachie wurde nicht "
+"entfernt;\n"
+"um diesen zu entfernen, benutze:"
+msgstr[1] ""
+"Hinweis: Einige Zweige außer der /refs/remotes/ Hierarchie wurden nicht "
+"entfernt;\n"
+"um diese zu entfernen, benutze:"
+
+#: builtin/remote.c:943
+#, c-format
+msgid " new (next fetch will store in remotes/%s)"
+msgstr " neu (wird bei nächster Abholung in remotes/%s gespeichert)"
+
+#: builtin/remote.c:946
+msgid " tracked"
+msgstr " gefolgt"
+
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
+msgstr " veraltet (benutze 'git remote prune' zum Entfernen)"
+
+#: builtin/remote.c:950
+msgid " ???"
+msgstr " ???"
+
+#: builtin/remote.c:991
+#, c-format
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
+msgstr "ungültiges branch.%s.merge; kann nicht auf > 1 Zweig neu aufbauen"
+
+#: builtin/remote.c:998
+#, c-format
+msgid "rebases onto remote %s"
+msgstr "baut neu auf externen Zweig %s auf"
+
+#: builtin/remote.c:1001
+#, c-format
+msgid " merges with remote %s"
+msgstr " führt mit externem Zweig %s zusammen"
+
+#: builtin/remote.c:1002
+msgid " and with remote"
+msgstr " und mit externem Zweig"
+
+#: builtin/remote.c:1004
+#, c-format
+msgid "merges with remote %s"
+msgstr "führt mit externem Zweig %s zusammen"
+
+#: builtin/remote.c:1005
+msgid " and with remote"
+msgstr " und mit externem Zweig"
+
+#: builtin/remote.c:1051
+msgid "create"
+msgstr "erstellt"
+
+#: builtin/remote.c:1054
+msgid "delete"
+msgstr "gelöscht"
+
+#: builtin/remote.c:1058
+msgid "up to date"
+msgstr "aktuell"
+
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
+msgstr "vorspulbar"
+
+#: builtin/remote.c:1064
+msgid "local out of date"
+msgstr "lokal nicht aktuell"
+
+#: builtin/remote.c:1071
+#, c-format
+msgid " %-*s forces to %-*s (%s)"
+msgstr " %-*s erzwingt Versandt nach %-*s (%s)"
+
+#: builtin/remote.c:1074
+#, c-format
+msgid " %-*s pushes to %-*s (%s)"
+msgstr " %-*s versendet nach %-*s (%s)"
+
+#: builtin/remote.c:1078
+#, c-format
+msgid " %-*s forces to %s"
+msgstr " %-*s erzwingt Versand nach %s"
+
+#: builtin/remote.c:1081
+#, c-format
+msgid " %-*s pushes to %s"
+msgstr " %-*s versendet nach %s"
+
+#: builtin/remote.c:1118
+#, c-format
+msgid "* remote %s"
+msgstr "* externes Projektarchiv %s"
+
+#: builtin/remote.c:1119
+#, c-format
+msgid " Fetch URL: %s"
+msgstr " URL zum Abholen: %s"
+
+#: builtin/remote.c:1120 builtin/remote.c:1285
+msgid "(no URL)"
+msgstr "(keine URL)"
+
+#: builtin/remote.c:1129 builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
+msgstr " URL zum Versenden: %s"
+
+#: builtin/remote.c:1133 builtin/remote.c:1135 builtin/remote.c:1137
+#, c-format
+msgid " HEAD branch: %s"
+msgstr " Hauptzweig: %s"
+
+#: builtin/remote.c:1139
+#, c-format
+msgid ""
+" HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
+msgstr ""
+" Hauptzweig (externer Hauptzweig ist mehrdeutig, könnte einer der folgenden "
+"sein):\n"
+
+#: builtin/remote.c:1151
+#, c-format
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] " externer Zweig:%s"
+msgstr[1] " externe Zweige:%s"
+
+#: builtin/remote.c:1154 builtin/remote.c:1181
+msgid " (status not queried)"
+msgstr " (Zustand nicht abgefragt)"
+
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] " Lokaler Zweig konfiguriert für 'git pull':"
+msgstr[1] " Lokale Zweige konfiguriert für 'git pull':"
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr " Lokale Referenzen werden von 'git push' gespiegelt"
+
+#: builtin/remote.c:1178
+#, c-format
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] " Lokale Referenz konfiguriert für 'git push'%s:"
+msgstr[1] " Lokale Referenzen konfiguriert für 'git push'%s:"
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
+msgstr "Kann Hauptzweig des externen Projektarchivs nicht bestimmen"
+
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
+msgstr ""
+"Mehrere Hauptzweige im externen Projektarchiv. Bitte wähle explizit einen "
+"aus mit:"
+
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
+msgstr "Konnte %s nicht entfernen"
+
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
+msgstr "keine gültige Referenz: %s"
+
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
+msgstr "Konnte %s nicht einrichten"
+
+#: builtin/remote.c:1274
+#, c-format
+msgid " %s will become dangling!"
+msgstr " %s wird unreferenziert!"
+
+#: builtin/remote.c:1275
+#, c-format
+msgid " %s has become dangling!"
+msgstr " %s wurde unreferenziert!"
+
+#: builtin/remote.c:1281
+#, c-format
+msgid "Pruning %s"
+msgstr "entferne veraltete Zweige von %s"
+
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
+msgstr "URL: %s"
+
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
+msgstr " * [würde veralteten Zweig entfernen] %s"
+
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
+msgstr "* [veralteten Zweig entfernt] %s"
+
+#: builtin/remote.c:1387 builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
+msgstr "Kein solches externes Projektarchiv '%s'"
+
+#: builtin/remote.c:1414
+msgid "no remote specified"
+msgstr "kein externes Projektarchiv angegeben"
+
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
+msgstr "--add --delete macht keinen Sinn"
+
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
+msgstr "ungültiges altes URL Format: %s"
+
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr "Keine solche URL gefunden: %s"
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
+msgstr "Werde keine URLs entfernen, die nicht für den Versand bestimmt sind"
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr "mixed"
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr "soft"
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr "hard"
+
+#: builtin/reset.c:33
+msgid "merge"
+msgstr "zusammenführen"
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr "keep"
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr "Du hast keine gültige Zweigspitze (HEAD)."
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr "Fehler beim Finden des Baumes der Zweigspitze (HEAD)."
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr "Fehler beim Finden des Baumes von %s."
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr "Konnte neue Bereitstellungsdatei nicht schreiben."
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr "Zweigspitze (HEAD) ist jetzt bei %s"
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr "Konnte Bereitstellung nicht lesen"
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr "Nicht bereitgestellte Änderungen nach Zurücksetzung:"
+
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr ""
+"Kann keine '%s' Zurücksetzung durchführen, während eine Zusammenführung im "
+"Gange ist."
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr "Konnte Objekt '%s' nicht parsen."
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr "--patch ist inkompatibel mit --{hard,mixed,soft}"
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr ""
+"--mixed mit Pfaden ist veraltet; benutze stattdessen 'git reset -- <Pfade>'."
+
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr "Eine '%s' Zurücksetzung mit Pfaden ist nicht möglich."
+
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr "'%s' Zurücksetzung ist in einem bloßen Projektarchiv nicht erlaubt"
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr "Konnte Bereitstellungsdatei nicht zu Version '%s' zurücksetzen."
+
+#: builtin/revert.c:70 builtin/revert.c:92
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr "%s: %s kann nicht mit %s benutzt werden"
+
+#: builtin/revert.c:131
+msgid "program error"
+msgstr "Programmfehler"
+
+#: builtin/revert.c:221
+msgid "revert failed"
+msgstr "\"revert\" fehlgeschlagen"
+
+#: builtin/revert.c:236
+msgid "cherry-pick failed"
+msgstr "\"cherry-pick\" fehlgeschlagen"
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+"'%s' hat bereitgestellten Inhalt unterschiedlich zu der Datei und der\n"
+"Zweigspitze (HEAD) (benutze -f um die Entfernung zu erzwingen)"
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"'%s' hat Änderungen in der Bereitstellung\n"
+"(benutze --cached um die Datei zu behalten, oder -f um die Entfernung zu "
+"erzwingen)"
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"'%s' hat lokale Modifikationen\n"
+"(benutze --cached um die Datei zu behalten, oder -f um die Entfernung zu "
+"erzwingen)"
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr "'%s' wird nicht ohne -r rekursiv entfernt"
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr "git rm: konnte %s nicht entfernen"
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr "fehlender Autor: %s"
+
+#: builtin/tag.c:60
+#, c-format
+msgid "malformed object at '%s'"
+msgstr "fehlerhaftes Objekt bei '%s'"
+
+#: builtin/tag.c:207
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr "Markierungsname zu lang: %.*s..."
+
+#: builtin/tag.c:212
+#, c-format
+msgid "tag '%s' not found."
+msgstr "Markierung '%s' nicht gefunden."
+
+#: builtin/tag.c:227
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr "Gelöschte Markierung '%s' (war %s)\n"
+
+#: builtin/tag.c:239
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr "Konnte Markierung '%s' nicht verifizieren"
+
+#: builtin/tag.c:249
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# Gebe eine Markierungsbeschreibung ein\n"
+"# Zeilen, die mit '#' beginnen, werden ignoriert.\n"
+"#\n"
+
+#: builtin/tag.c:256
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you "
+"want to.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# Gebe eine Markierungsbeschreibung ein\n"
+"# Zeilen, die mit '#' beginnen, werden behalten; du darfst diese\n"
+"# selbst entfernen wenn du möchtest.\n"
+"#\n"
+
+#: builtin/tag.c:298
+msgid "unable to sign the tag"
+msgstr "konnte Markierung nicht signieren"
+
+#: builtin/tag.c:300
+msgid "unable to write tag file"
+msgstr "konnte Markierungsdatei nicht schreiben"
+
+#: builtin/tag.c:325
+msgid "bad object type."
+msgstr "ungültiger Objekt-Typ"
+
+#: builtin/tag.c:338
+msgid "tag header too big."
+msgstr "Markierungskopf zu groß."
+
+#: builtin/tag.c:370
+msgid "no tag message?"
+msgstr "keine Markierungsbeschreibung?"
+
+#: builtin/tag.c:376
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr "Die Markierungsbeschreibung wurde gelassen in %s\n"
+
+#: builtin/tag.c:425
+msgid "switch 'points-at' requires an object"
+msgstr "Option 'points-at' erfordert ein Objekt"
+
+#: builtin/tag.c:427
+#, c-format
+msgid "malformed object name '%s'"
+msgstr "fehlerhafter Objekt-Name '%s'"
+
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr "--column und -n sind inkompatibel"
+
+#: builtin/tag.c:523
+msgid "-n option is only allowed with -l."
+msgstr "-n Option ist nur erlaubt mit -l."
+
+#: builtin/tag.c:525
+msgid "--contains option is only allowed with -l."
+msgstr "--contains Option ist nur erlaubt mit -l."
+
+#: builtin/tag.c:527
+msgid "--points-at option is only allowed with -l."
+msgstr "--points-at Option ist nur erlaubt mit -l."
+
+#: builtin/tag.c:535
+msgid "only one -F or -m option is allowed."
+msgstr "nur eine -F oder -m Option ist erlaubt."
+
+#: builtin/tag.c:555
+msgid "too many params"
+msgstr "zu viele Parameter"
+
+#: builtin/tag.c:561
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr "'%s' ist kein gültiger Markierungsname."
+
+#: builtin/tag.c:566
+#, c-format
+msgid "tag '%s' already exists"
+msgstr "Markierung '%s' existiert bereits"
+
+#: builtin/tag.c:584
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr "%s: kann Referenz nicht sperren"
+
+#: builtin/tag.c:586
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr "%s: kann Referenz nicht aktualisieren"
+
+#: builtin/tag.c:588
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr "Aktualisierte Markierung '%s' (war %s)\n"
+
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr ""
+"Siehe 'git help <Kommando>' für weitere Informationen zu einem spezifischen "
+"Kommando"
+
+#: parse-options.h:133 parse-options.h:235
+msgid "n"
+msgstr "Anzahl"
+
+#: parse-options.h:141
+msgid "time"
+msgstr "Zeit"
+
+#: parse-options.h:149
+msgid "file"
+msgstr "Datei"
+
+#: parse-options.h:151
+msgid "when"
+msgstr "wann"
+
+#: parse-options.h:156
+msgid "no-op (backward compatibility)"
+msgstr "Kein Effekt (Rückwärtskompatibilität)"
+
+#: parse-options.h:228
+msgid "be more verbose"
+msgstr "erweiterte Ausgaben"
+
+#: parse-options.h:230
+msgid "be more quiet"
+msgstr "weniger Ausgaben"
+
+#: parse-options.h:236
+msgid "use <n> digits to display SHA-1s"
+msgstr "benutze <n> Ziffern zur Anzeige von SHA-1s"
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr "stellt Dateiinhalte zur Eintragung bereit"
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr ""
+"Findet über eine Binärsuche die Änderungen, die einen Fehler verursacht haben"
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr "Zeigt an, erstellt oder entfernt Zweige"
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr "Checkt Zweige oder Pfade im Arbeitszweig aus"
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr "Klont ein Projektarchiv in einem neuen Verzeichnis"
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr "Trägt Änderungen in das Projektarchiv ein"
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr "Zeigt Änderungen zwischen Versionen, Version und Arbeitszweig, etc. an"
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr "Lädt Objekte und Referenzen von einem anderen Projektarchiv herunter"
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr "Stellt Zeilen dar, die einem Muster entsprechen"
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr ""
+"Erstellt ein leeres Git-Projektarchiv oder initialisiert ein bestehendes neu"
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr "Zeigt Versionshistorie an"
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr "Führt zwei oder mehr Entwicklungszweige zusammen"
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr ""
+"Verschiebt oder benennt eine Datei, ein Verzeichnis, oder eine symbolische "
+"Verknüpfung um"
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr ""
+"Fordert Objekte von einem externen Projektarchiv an und führt sie mit einem "
+"anderen Projektarchiv oder einem lokalen Zweig zusammen"
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr "Aktualisiert externe Referenzen mitsamt den verbundenen Objekten"
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr "Baut lokale Versionen auf einem aktuellerem externen Zweig neu auf"
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr ""
+"Setzt die aktuelle Zweigspitze (HEAD) zu einem spezifizierten Zustand zurück"
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr "Löscht Dateien im Arbeitszweig und von der Bereitstellung"
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr "Zeigt verschiedene Arten von Objekten an"
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr "Zeigt den Zustand des Arbeitszweiges an"
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr ""
+"Erzeugt, listet auf, löscht oder verifiziert ein mit GPG signiertes "
+"Markierungsobjekt"
+
+#: git-am.sh:50
+msgid "You need to set your committer info first"
+msgstr "Du musst zuerst die Informationen des Eintragenden setzen."
+
+#: git-am.sh:95
+msgid ""
+"You seem to have moved HEAD since the last 'am' failure.\n"
+"Not rewinding to ORIG_HEAD"
+msgstr ""
+"Du scheinst seit dem letzten gescheiterten 'am' die Zweigspitze (HEAD)\n"
+"geändert zu haben.\n"
+"Keine Zurücksetzung zu ORIG_HEAD."
+
+#: git-am.sh:105
+#, sh-format
+msgid ""
+"When you have resolved this problem run \"$cmdline --resolved\".\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"Wenn du das Problem aufgelöst hast, führe \"$cmdline --resolved\" aus.\n"
+"Falls du diesen Patch auslassen möchtest, führe stattdessen "
+"\"$cmdline --skip\" aus.\n"
+"Um den ursprünglichen Zweig wiederherzustellen und die Anwendung der Patches\n"
+"abzubrechen, führe \"$cmdline --abort\" aus."
+
+#: git-am.sh:121
+msgid "Cannot fall back to three-way merge."
+msgstr "Kann nicht zu 3-Wege-Zusammenführung zurückfallen."
+
+#: git-am.sh:137
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr ""
+"Dem Projektarchiv fehlen notwendige Blobs um auf eine 3-Wege-Zusammenführung "
+"zurückzufallen."
+
+#: git-am.sh:154
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+"Hast du den Patch per Hand editiert?\n"
+"Er kann nicht auf die Blobs in seiner 'index' Zeile angewendet werden."
+
+#: git-am.sh:163
+msgid "Falling back to patching base and 3-way merge..."
+msgstr "Falle zurück zum Patchen der Basis und der 3-Wege-Zusammenführung..."
+
+#: git-am.sh:275
+msgid "Only one StGIT patch series can be applied at once"
+msgstr "Es kann nur eine StGIT Patch-Serie auf einmal angewendet werden."
+
+#: git-am.sh:362
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr "Patch-Format $patch_format wird nicht unterstützt."
+
+#: git-am.sh:364
+msgid "Patch format detection failed."
+msgstr "Patch-Formaterkennung fehlgeschlagen."
+
+#: git-am.sh:418
+msgid "-d option is no longer supported. Do not use."
+msgstr "-d Option wird nicht länger unterstützt. Nicht benutzen."
+
+#: git-am.sh:481
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr ""
+"Vorheriges Verzeichnis des Neuaufbaus $dotest existiert noch, aber mbox "
+"gegeben."
+
+#: git-am.sh:486
+msgid "Please make up your mind. --skip or --abort?"
+msgstr "Bitte werde dir klar. --skip oder --abort?"
+
+#: git-am.sh:513
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr "Es ist keine Auflösung im Gange, es wird nicht fortgesetzt."
+
+#: git-am.sh:579
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr ""
+"Unsaubere Bereitstellung: kann Patches nicht anwenden (unsauber: $files)"
+
+#: git-am.sh:671
+#, sh-format
+msgid ""
+"Patch is empty. Was it split wrong?\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"Patch ist leer. Wurde er falsch aufgeteilt?\n"
+"Wenn du diesen Patch auslassen möchtest, führe stattdessen "
+"\"$cmdline --skip\" aus.\n"
+"Um den ursprünglichen Zweig wiederherzustellen und die Anwendung der Patches\n"
+"abzubrechen, führe \"$cmdline --abort\" aus."
+
+#: git-am.sh:708
+msgid "Patch does not have a valid e-mail address."
+msgstr "Patch enthält keine gültige eMail-Adresse."
+
+#: git-am.sh:755
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr ""
+"Kann nicht interaktiv sein, ohne dass die Standard-Eingabe mit einem "
+"Terminal verbunden ist."
+
+#: git-am.sh:759
+msgid "Commit Body is:"
+msgstr "Beschreibung der Eintragung ist:"
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:766
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr "Anwenden? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+
+#: git-am.sh:802
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr "Wende an: $FIRSTLINE"
+
+#: git-am.sh:823
+msgid ""
+"No changes - did you forget to use 'git add'?\n"
+"If there is nothing left to stage, chances are that something else\n"
+"already introduced the same changes; you might want to skip this patch."
+msgstr ""
+"Keine Änderungen - hast du vergessen 'git add' zu benutzen?\n"
+"Wenn keine Änderungen mehr zum Bereitstellen vorhanden sind, könnten\n"
+"diese bereits anderweitig eingefügt worden sein; du könntest diesen Patch\n"
+"auslassen."
+
+#: git-am.sh:831
+msgid ""
+"You still have unmerged paths in your index\n"
+"did you forget to use 'git add'?"
+msgstr ""
+"Du hast immer noch nicht zusammengeführte Pfade in der Bereitstellung.\n"
+"Hast du vergessen 'git add' zu benutzen?"
+
+#: git-am.sh:847
+msgid "No changes -- Patch already applied."
+msgstr "Keine Änderungen -- Patches bereits angewendet."
+
+#: git-am.sh:857
+#, sh-format
+msgid "Patch failed at $msgnum $FIRSTLINE"
+msgstr "Anwendung des Patches fehlgeschlagen bei $msgnum $FIRSTLINE"
+
+#: git-am.sh:873
+msgid "applying to an empty history"
+msgstr "wende zu leerer Historie an"
+
+#: git-bisect.sh:48
+msgid "You need to start by \"git bisect start\""
+msgstr "Du musst mit \"git bisect start\" beginnen."
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr "Willst du, dass ich es für dich mache [Y/n]? "
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr "nicht erkannte Option: '$arg'"
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr "'$arg' scheint keine gültige Version zu sein"
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr "Ungültige Zweigspitze (HEAD) - Zweigspitze (HEAD) wird benötigt"
+
+#: git-bisect.sh:130
+#, sh-format
+msgid ""
+"Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr ""
+"Auschecken von '$start_head' fehlgeschlagen. Versuche 'git bisect reset "
+"<gueltigerzweig>'."
+
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr "\"bisect\" auf gesuchtem Zweig nicht möglich"
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr "Ungültige Zweigspitze (HEAD) - merkwürdige symbolische Referenz"
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr "Ungültiges \"bisect_write\" Argument: $state"
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr "Ungültige Referenz-Eingabe: $arg"
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr "Bitte rufe 'bisect_state' mit mindestens einem Argument auf."
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr "Ungültige Referenz-Eingabe: $rev"
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr "'git bisect bad' kann nur ein Argument entgegennehmen."
+
+#. have bad but not good. we could bisect although
+#. this is less optimum.
+#: git-bisect.sh:273
+msgid "Warning: bisecting only with a bad commit."
+msgstr "Warnung: halbiere nur mit einer fehlerhaften Version"
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr "Bist du sicher [Y/n]? "
+
+#: git-bisect.sh:289
+msgid ""
+"You need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"Du musst mindestens eine korrekte und eine fehlerhafte Version angeben.\n"
+"(Du kannst dafür \"git bisect bad\" und \"git bisect good\" benutzen.)"
+
+#: git-bisect.sh:292
+msgid ""
+"You need to start by \"git bisect start\".\n"
+"You then need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"Du musst mit \"git bisect start\" beginnen.\n"
+"Danach musst du mindestens eine korrekte und eine fehlerhafte Version "
+"angeben.\n"
+"(Du kannst dafür \"git bisect bad\" und \"git bisect good\" benutzen.)"
+
+#: git-bisect.sh:347 git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr "Wir sind nicht beim Halbieren."
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr "'$invalid' ist keine gültige Version"
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+"Konnte die ursprüngliche Zweigspitze (HEAD) '$branch' nicht auschecken.\n"
+"Versuche 'git bisect reset <Version>'."
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr "Keine Log-Datei gegeben"
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr "kann $file nicht für das Abspielen lesen"
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr "?? Was redest du da?"
+
+#: git-bisect.sh:420
+#, sh-format
+msgid "running $command"
+msgstr "führe $command aus"
+
+#: git-bisect.sh:427
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"exit code $res from '$command' is < 0 or >= 128"
+msgstr ""
+"Ausführung der Halbierung fehlgeschlagen:\n"
+"Rückkehrwert $res von '$command' ist < 0 oder >= 128"
+
+#: git-bisect.sh:453
+msgid "bisect run cannot continue any more"
+msgstr "Ausführung der Halbierung kann nicht mehr fortgesetzt werden"
+
+#: git-bisect.sh:459
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"'bisect_state $state' exited with error code $res"
+msgstr ""
+"Ausführung der Halbierung fehlgeschlagen:\n"
+"'bisect_state $state' wurde mit Fehlerwert $res beendet"
+
+#: git-bisect.sh:466
+msgid "bisect run success"
+msgstr "Halbierung erfolgreich ausgeführt"
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+"\"pull\" ist nicht möglich, weil du nicht zusammengeführte Dateien hast.\n"
+"Bitte korrigiere dies im Arbeitsbaum und benutze dann 'git add/rm <Datei>'\n"
+"um die Auflösung entsprechend zu markieren, oder benutze 'git commit -a'."
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr ""
+"\"pull\" ist nicht möglich, weil du nicht zusammengeführte Dateien hast."
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr ""
+"Aktualisiere eine ungeborenen Zweig mit Änderungen, die zur Bereitstellung "
+"hinzugefügt wurden"
+
+#. The fetch involved updating the current branch.
+#. The working tree and the index file is still based on the
+#. $orig_head commit, but we are merging into $curr_head.
+#. First update the working tree to match $curr_head.
+#: git-pull.sh:228
+#, sh-format
+msgid ""
+"Warning: fetch updated the current branch head.\n"
+"Warning: fast-forwarding your working tree from\n"
+"Warning: commit $orig_head."
+msgstr ""
+"Warnung: Die Anforderung aktualisierte die Spitze des aktuellen Zweiges.\n"
+"Warnung: Spule deinen Arbeitszweig von Version $orig_head vor."
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr "Kann nicht mehrere Zweige in einen ungeborenen Zweig zusammenführen"
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr "kann nicht auf mehrere Zweige neu aufbauen"
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr "git stash clear mit Parametern ist nicht implementiert"
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr "Du hast bisher noch keine initiale Version"
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr "Kann den aktuellen Zustand der Bereitstellung nicht speichern"
+
+#: git-stash.sh:123 git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr "Kann den aktuellen Zustand des Arbeitsbaumes nicht speichern"
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr "Keine Änderungen ausgewählt"
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr "Kann temporäre Bereitstellung nicht entfernen (kann nicht passieren)"
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr "Kann Zustand des Arbeitsbaumes nicht aufzeichnen"
+
+#. TRANSLATORS: $option is an invalid option, like
+#. `--blah-blah'. The 7 spaces at the beginning of the
+#. second line correspond to "error: ". So you should line
+#. up the second line with however many characters the
+#. translation of "error: " takes in your language. E.g. in
+#. English this is:
+#.
+#. $ git stash save --blah-blah 2>&1 | head -n 2
+#. error: unknown option for 'stash save': --blah-blah
+#. To provide a message, use git stash save -- '--blah-blah'
+#: git-stash.sh:202
+#, sh-format
+msgid ""
+"error: unknown option for 'stash save': $option\n"
+" To provide a message, use git stash save -- '$option'"
+msgstr ""
+"Fehler: unbekannte Option für 'stash save': $option\n"
+" Um eine Beschreibung anzugeben, benutze \"git stash save -- "
+"'$option'\""
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr "Keine lokalen Änderungen zum Speichern"
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr "Kann \"stash\" nicht initialisieren"
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr "Kann den aktuellen Status nicht speichern"
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr "Kann Änderungen am Arbeitsbaum nicht entfernen"
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr "Kein \"stash\" gefunden."
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr "Zu viele Revisionen angegeben: $REV"
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr "$reference ist keine gültige Referenz"
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr "'$args' ist keine \"stash\"-artige Version"
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr "'$args' ist keine \"stash\"-Referenz"
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr "unfähig die Bereitstellung zu aktualisieren"
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr ""
+"Kann \"stash\" nicht anwenden, solang eine Zusammenführung im Gange ist"
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr "Konflikte in der Bereitstellung. Versuche es ohne --index."
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr "Konnte Bereitstellungsbaum nicht speichern"
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr "Kann geänderte Dateien nicht aus der Bereitstellung herausnehmen"
+
+#: git-stash.sh:474
+msgid "Index was not unstashed."
+msgstr "Bereitstellung wurde nicht ausgelagert."
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr "Gelöscht ${REV} ($s)"
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr "${REV}: Konnte \"stash\"-Eintrag nicht löschen"
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr "Kein Zweigname spezifiziert"
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr "(Zur Wiederherstellung gebe \"git stash apply\" ein)"
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr "Kann eine Komponente von URL '$remoteurl' nicht extrahieren"
+
+#: git-submodule.sh:109
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr ""
+"Keine Unterprojekt-Zuordnung in .gitmodules für Pfad '$sm_path' gefunden"
+
+#: git-submodule.sh:150
+#, sh-format
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr "Klonen von '$url' in Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
+
+#: git-submodule.sh:160
+#, sh-format
+msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
+msgstr ""
+"Git-Verzeichnis '$a' ist Teil des Unterprojekt-Pfades '$b', oder umgekehrt"
+
+#: git-submodule.sh:249
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr "repo URL: '$repo' muss absolut sein oder mit ./|../ beginnen"
+
+#: git-submodule.sh:266
+#, sh-format
+msgid "'$sm_path' already exists in the index"
+msgstr "'$sm_path' existiert bereits in der Bereitstellung"
+
+#: git-submodule.sh:270
+#, sh-format
+msgid ""
+"The following path is ignored by one of your .gitignore files:\n"
+"$sm_path\n"
+"Use -f if you really want to add it."
+msgstr ""
+"Der folgende Pfad wird durch eine deiner \".gitignore\" Dateien "
+"ignoriert:\n"
+"$sm_path\n"
+"Benutze -f wenn du diesen wirklich hinzufügen möchtest."
+
+#: git-submodule.sh:281
+#, sh-format
+msgid "Adding existing repo at '$sm_path' to the index"
+msgstr "Füge existierendes Projektarchiv in '$sm_path' der Bereitstellung "
+"hinzu."
+
+#: git-submodule.sh:283
+#, sh-format
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "'$sm_path' existiert bereits und ist kein gültiges Git-Projektarchiv"
+
+#: git-submodule.sh:297
+#, sh-format
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr "Unfähig Unterprojekt '$sm_path' auszuchecken"
+
+#: git-submodule.sh:302
+#, sh-format
+msgid "Failed to add submodule '$sm_path'"
+msgstr "Hinzufügen von Unterprojekt '$sm_path' fehlgeschlagen"
+
+#: git-submodule.sh:307
+#, sh-format
+msgid "Failed to register submodule '$sm_path'"
+msgstr "Registierung von Unterprojekt '$sm_path' fehlgeschlagen"
+
+#: git-submodule.sh:349
+#, sh-format
+msgid "Entering '$prefix$sm_path'"
+msgstr "Betrete '$prefix$sm_path'"
+
+#: git-submodule.sh:363
+#, sh-format
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr "Stoppe bei '$sm_path'; Skript gab nicht-Null Status zurück."
+
+#: git-submodule.sh:406
+#, sh-format
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr "Keine URL für Unterprojekt-Pfad '$sm_path' in .gitmodules gefunden"
+
+#: git-submodule.sh:415
+#, sh-format
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr "Registrierung der URL für Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
+
+#: git-submodule.sh:417
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr "Unterprojekt '$name' ($url) ist für Pfad '$sm_path' registriert"
+
+#: git-submodule.sh:425
+#, sh-format
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr ""
+"Registrierung des Aktualisierungsmodus für Unterprojekt-Pfad '$sm_path' "
+"fehlgeschlagen"
+
+#: git-submodule.sh:524
+#, sh-format
+msgid ""
+"Submodule path '$sm_path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+"Unterprojekt-Pfad '$sm_path' ist nicht initialisiert\n"
+"Vielleicht möchtest du 'update --init' benutzen?"
+
+#: git-submodule.sh:537
+#, sh-format
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr "Konnte aktuelle Version in Unterprojekt-Pfad '$sm_path' nicht finden"
+
+#: git-submodule.sh:556
+#, sh-format
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr "Konnte in Unterprojekt-Pfad '$sm_path' nicht anfordern"
+
+#: git-submodule.sh:570
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr "Neuaufbau von '$sha1' in Unterprojekt-Pfad '$sm_path' nicht möglich"
+
+#: git-submodule.sh:571
+#, sh-format
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr "Unterprojekt-Pfad '$sm_path': neu aufgebaut in '$sha1'"
+
+#: git-submodule.sh:576
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr ""
+"Zusammenführung von '$sha1' in Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
+
+#: git-submodule.sh:577
+#, sh-format
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr "Unterprojekt-Pfad '$sm_path': zusammengeführt in '$sha1'"
+
+#: git-submodule.sh:582
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr "Konnte '$sha1' in Unterprojekt-Pfad '$sm_path' nicht auschecken."
+
+#: git-submodule.sh:583
+#, sh-format
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr "Unterprojekt-Pfad: '$sm_path': '$sha1' ausgecheckt"
+
+#: git-submodule.sh:605 git-submodule.sh:928
+#, sh-format
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr "Fehler bei Rekursion in Unterprojekt-Pfad '$sm_path'"
+
+#: git-submodule.sh:713
+msgid "--cached cannot be used with --files"
+msgstr "--cached kann nicht mit --files benutzt werden"
+
+#. unexpected type
+#: git-submodule.sh:753
+#, sh-format
+msgid "unexpected mode $mod_dst"
+msgstr "unerwarteter Modus $mod_dst"
+
+#: git-submodule.sh:771
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr " Warnung: $name beinhaltet nicht Version $sha1_src"
+
+#: git-submodule.sh:774
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr " Warnung: $name beinhaltet nicht Version $sha1_dst"
+
+#: git-submodule.sh:777
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr ""
+" Warnung: $name beinhaltet nicht die Versionen $sha1_src und $sha1_dst"
+
+#: git-submodule.sh:802
+msgid "blob"
+msgstr "Blob"
+
+#: git-submodule.sh:803
+msgid "submodule"
+msgstr "Unterprojekt"
+
+#: git-submodule.sh:840
+msgid "# Submodules changed but not updated:"
+msgstr "# Unterprojekte geändert, aber nicht aktualisiert:"
+
+#: git-submodule.sh:842
+msgid "# Submodule changes to be committed:"
+msgstr "# Änderungen in Unterprojekt zum Eintragen:"
+
+#: git-submodule.sh:974
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr "Synchronisiere Unterprojekt-URL für '$name'"
+
+#~ msgid "--"
+#~ msgstr "--"
+
+#~ msgid "Could not extract email from committer identity."
+#~ msgstr "Konnte E-Mail-Adresse des Einreichers nicht extrahieren."
+
+#~ msgid "cherry-pick"
+#~ msgstr "cherry-pick"
+
+#~ msgid "Please enter the commit message for your changes."
+#~ msgstr "Bitte gebe die Versionsbeschreibung für deine Änderungen ein."
+
+#~ msgid "Too many options specified"
+#~ msgstr "Zu viele Optionen angegeben"
+
+#~ msgid ""
+#~ "To prevent you from losing history, non-fast-forward updates were "
+#~ "rejected\n"
+#~ "Merge the remote changes (e.g. 'git pull') before pushing again. See "
+#~ "the\n"
+#~ "'Note about fast-forwards' section of 'git push --help' for details.\n"
+#~ msgstr ""
+#~ "Um dich vor Verlust von Historie zu bewahren, wurden nicht vorzuspulende "
+#~ "Aktualisierungen zurückgewiesen.\n"
+#~ "Führe die externen Änderungen zusammen (z.B. 'git pull') bevor du erneut "
+#~ "versendest. Siehe auch die 'Note about fast-forwards' Sektion von \n"
+#~ "'git push --help' für weitere Details.\n"
diff --git a/po/git.pot b/po/git.pot
new file mode 100644
index 0000000..b666506
--- /dev/null
+++ b/po/git.pot
@@ -0,0 +1,5213 @@
+# 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: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-06-08 10:20+0800\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"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: advice.c:40
+#, c-format
+msgid "hint: %.*s\n"
+msgstr ""
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:70
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr ""
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr ""
+
+#: bundle.c:89 builtin/commit.c:696
+#, c-format
+msgid "could not open '%s'"
+msgstr ""
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr ""
+
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
+#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr ""
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr ""
+
+#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr ""
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr ""
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr ""
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr ""
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr ""
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr ""
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr ""
+
+#: commit.c:48
+#, c-format
+msgid "could not parse %s"
+msgstr ""
+
+#: commit.c:50
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr ""
+
+#: compat/obstack.c:406 compat/obstack.c:408
+msgid "memory exhausted"
+msgstr ""
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr ""
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr ""
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr ""
+
+#: date.c:95
+msgid "in the future"
+msgstr ""
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:154 date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:105
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr ""
+
+#: diff.c:110
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr ""
+
+#: diff.c:210
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+
+#: diff.c:1400
+msgid " 0 files changed\n"
+msgstr ""
+
+#: diff.c:1404
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:1421
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:1432
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:3478
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr ""
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr ""
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr ""
+
+#: grep.c:1320
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr ""
+
+#: grep.c:1337
+#, c-format
+msgid "'%s': %s"
+msgstr ""
+
+#: grep.c:1348
+#, c-format
+msgid "'%s': short read %s"
+msgstr ""
+
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr ""
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr ""
+
+#: help.c:270
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr ""
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr ""
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr ""
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+msgstr[1] ""
+
+#: parse-options.c:493
+msgid "..."
+msgstr ""
+
+#: parse-options.c:511
+#, c-format
+msgid "usage: %s"
+msgstr ""
+
+#. TRANSLATORS: the colon here should align with the
+#. one in "usage: %s" translation
+#: parse-options.c:515
+#, c-format
+msgid " or: %s"
+msgstr ""
+
+#: parse-options.c:518
+#, c-format
+msgid " %s"
+msgstr ""
+
+#: remote.c:1629
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1635
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural ""
+"Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1643
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: sequencer.c:121 builtin/merge.c:865 builtin/merge.c:978
+#: builtin/merge.c:1088 builtin/merge.c:1098
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr ""
+
+#: sequencer.c:123 builtin/merge.c:333 builtin/merge.c:868
+#: builtin/merge.c:1090 builtin/merge.c:1103
+#, c-format
+msgid "Could not write to '%s'"
+msgstr ""
+
+#: sequencer.c:144
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'"
+msgstr ""
+
+#: sequencer.c:147
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+
+#: sequencer.c:160 sequencer.c:758 sequencer.c:841
+#, c-format
+msgid "Could not write to %s"
+msgstr ""
+
+#: sequencer.c:163
+#, c-format
+msgid "Error wrapping up %s"
+msgstr ""
+
+#: sequencer.c:178
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr ""
+
+#: sequencer.c:180
+msgid "Your local changes would be overwritten by revert."
+msgstr ""
+
+#: sequencer.c:183
+msgid "Commit your changes or stash them to proceed."
+msgstr ""
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:233
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr ""
+
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr ""
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr ""
+
+#: sequencer.c:324
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr ""
+
+#: sequencer.c:329
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr ""
+
+#: sequencer.c:395
+msgid "Your index file is unmerged."
+msgstr ""
+
+#: sequencer.c:398
+msgid "You do not have a valid HEAD"
+msgstr ""
+
+#: sequencer.c:413
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr ""
+
+#: sequencer.c:421
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr ""
+
+#: sequencer.c:425
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr ""
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:436
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr ""
+
+#: sequencer.c:440
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr ""
+
+#: sequencer.c:524
+#, c-format
+msgid "could not revert %s... %s"
+msgstr ""
+
+#: sequencer.c:525
+#, c-format
+msgid "could not apply %s... %s"
+msgstr ""
+
+#: sequencer.c:553
+msgid "empty commit set passed"
+msgstr ""
+
+#: sequencer.c:561
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr ""
+
+#: sequencer.c:566
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr ""
+
+#: sequencer.c:624
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr ""
+
+#: sequencer.c:646
+#, c-format
+msgid "Could not parse line %d."
+msgstr ""
+
+#: sequencer.c:651
+msgid "No commits parsed."
+msgstr ""
+
+#: sequencer.c:664
+#, c-format
+msgid "Could not open %s"
+msgstr ""
+
+#: sequencer.c:668
+#, c-format
+msgid "Could not read %s."
+msgstr ""
+
+#: sequencer.c:675
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr ""
+
+#: sequencer.c:703
+#, c-format
+msgid "Invalid key: %s"
+msgstr ""
+
+#: sequencer.c:706
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr ""
+
+#: sequencer.c:718
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr ""
+
+#: sequencer.c:739
+msgid "a cherry-pick or revert is already in progress"
+msgstr ""
+
+#: sequencer.c:740
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr ""
+
+#: sequencer.c:744
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr ""
+
+#: sequencer.c:760 sequencer.c:845
+#, c-format
+msgid "Error wrapping up %s."
+msgstr ""
+
+#: sequencer.c:779 sequencer.c:913
+msgid "no cherry-pick or revert in progress"
+msgstr ""
+
+#: sequencer.c:781
+msgid "cannot resolve HEAD"
+msgstr ""
+
+#: sequencer.c:783
+msgid "cannot abort from a branch yet to be born"
+msgstr ""
+
+#: sequencer.c:805 builtin/apply.c:3697
+#, c-format
+msgid "cannot open %s: %s"
+msgstr ""
+
+#: sequencer.c:808
+#, c-format
+msgid "cannot read %s: %s"
+msgstr ""
+
+#: sequencer.c:809
+msgid "unexpected end of file"
+msgstr ""
+
+#: sequencer.c:815
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr ""
+
+#: sequencer.c:838
+#, c-format
+msgid "Could not format %s."
+msgstr ""
+
+#: sequencer.c:1000
+msgid "Can't revert as initial commit"
+msgstr ""
+
+#: sequencer.c:1001
+msgid "Can't cherry-pick into empty head"
+msgstr ""
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr ""
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr ""
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr ""
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr ""
+
+#: wrapper.c:413
+#, c-format
+msgid "unable to look up current user in the passwd file: %s"
+msgstr ""
+
+#: wrapper.c:414
+msgid "no such user"
+msgstr ""
+
+#: wt-status.c:135
+msgid "Unmerged paths:"
+msgstr ""
+
+#: wt-status.c:141 wt-status.c:158
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:143 wt-status.c:160
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:144
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr ""
+
+#: wt-status.c:152
+msgid "Changes to be committed:"
+msgstr ""
+
+#: wt-status.c:170
+msgid "Changes not staged for commit:"
+msgstr ""
+
+#: wt-status.c:174
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr ""
+
+#: wt-status.c:176
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr ""
+
+#: wt-status.c:177
+msgid ""
+" (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr ""
+
+#: wt-status.c:179
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr ""
+
+#: wt-status.c:188
+#, c-format
+msgid "%s files:"
+msgstr ""
+
+#: wt-status.c:191
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr ""
+
+#: wt-status.c:208
+msgid "bug"
+msgstr ""
+
+#: wt-status.c:213
+msgid "both deleted:"
+msgstr ""
+
+#: wt-status.c:214
+msgid "added by us:"
+msgstr ""
+
+#: wt-status.c:215
+msgid "deleted by them:"
+msgstr ""
+
+#: wt-status.c:216
+msgid "added by them:"
+msgstr ""
+
+#: wt-status.c:217
+msgid "deleted by us:"
+msgstr ""
+
+#: wt-status.c:218
+msgid "both added:"
+msgstr ""
+
+#: wt-status.c:219
+msgid "both modified:"
+msgstr ""
+
+#: wt-status.c:249
+msgid "new commits, "
+msgstr ""
+
+#: wt-status.c:251
+msgid "modified content, "
+msgstr ""
+
+#: wt-status.c:253
+msgid "untracked content, "
+msgstr ""
+
+#: wt-status.c:267
+#, c-format
+msgid "new file: %s"
+msgstr ""
+
+#: wt-status.c:270
+#, c-format
+msgid "copied: %s -> %s"
+msgstr ""
+
+#: wt-status.c:273
+#, c-format
+msgid "deleted: %s"
+msgstr ""
+
+#: wt-status.c:276
+#, c-format
+msgid "modified: %s"
+msgstr ""
+
+#: wt-status.c:279
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr ""
+
+#: wt-status.c:282
+#, c-format
+msgid "typechange: %s"
+msgstr ""
+
+#: wt-status.c:285
+#, c-format
+msgid "unknown: %s"
+msgstr ""
+
+#: wt-status.c:288
+#, c-format
+msgid "unmerged: %s"
+msgstr ""
+
+#: wt-status.c:291
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr ""
+
+#: wt-status.c:737
+msgid "On branch "
+msgstr ""
+
+#: wt-status.c:744
+msgid "Not currently on any branch."
+msgstr ""
+
+#: wt-status.c:755
+msgid "Initial commit"
+msgstr ""
+
+#: wt-status.c:769
+msgid "Untracked"
+msgstr ""
+
+#: wt-status.c:771
+msgid "Ignored"
+msgstr ""
+
+#: wt-status.c:773
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr ""
+
+#: wt-status.c:775
+msgid " (use -u option to show untracked files)"
+msgstr ""
+
+#: wt-status.c:781
+msgid "No changes"
+msgstr ""
+
+#: wt-status.c:785
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr ""
+
+#: wt-status.c:787
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr ""
+
+#: wt-status.c:789
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr ""
+
+#: wt-status.c:791
+msgid " (use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr ""
+
+#: wt-status.c:794
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:797
+msgid " (use -u to show untracked files)"
+msgstr ""
+
+#: wt-status.c:800
+msgid " (working directory clean)"
+msgstr ""
+
+#: wt-status.c:908
+msgid "HEAD (no branch)"
+msgstr ""
+
+#: wt-status.c:914
+msgid "Initial commit on "
+msgstr ""
+
+#: wt-status.c:929
+msgid "behind "
+msgstr ""
+
+#: wt-status.c:932 wt-status.c:935
+msgid "ahead "
+msgstr ""
+
+#: wt-status.c:937
+msgid ", behind "
+msgstr ""
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr ""
+
+#: builtin/add.c:67 builtin/commit.c:226
+msgid "updating files failed"
+msgstr ""
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr ""
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr ""
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr ""
+
+#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr ""
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr ""
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr ""
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr ""
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr ""
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr ""
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr ""
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr ""
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr ""
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr ""
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr ""
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr ""
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr ""
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr ""
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr ""
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr ""
+
+#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:286 builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr ""
+
+#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr ""
+
+#: builtin/apply.c:53
+msgid "git apply [options] [<patch>...]"
+msgstr ""
+
+#: builtin/apply.c:106
+#, c-format
+msgid "unrecognized whitespace option '%s'"
+msgstr ""
+
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
+msgstr ""
+
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
+msgstr ""
+
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
+msgstr ""
+
+#: builtin/apply.c:905
+#, c-format
+msgid "unable to find filename in patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:937
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
+msgstr ""
+
+#: builtin/apply.c:941
+#, c-format
+msgid "git apply: bad git-diff - inconsistent new filename on line %d"
+msgstr ""
+
+#: builtin/apply.c:942
+#, c-format
+msgid "git apply: bad git-diff - inconsistent old filename on line %d"
+msgstr ""
+
+#: builtin/apply.c:949
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
+msgstr ""
+
+#: builtin/apply.c:1394
+#, c-format
+msgid "recount: unexpected line: %.*s"
+msgstr ""
+
+#: builtin/apply.c:1451
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
+msgstr ""
+
+#: builtin/apply.c:1468
+#, c-format
+msgid ""
+"git diff header lacks filename information when removing %d leading pathname "
+"component (line %d)"
+msgid_plural ""
+"git diff header lacks filename information when removing %d leading pathname "
+"components (line %d)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:1628
+msgid "new file depends on old contents"
+msgstr ""
+
+#: builtin/apply.c:1630
+msgid "deleted file still has contents"
+msgstr ""
+
+#: builtin/apply.c:1656
+#, c-format
+msgid "corrupt patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:1692
+#, c-format
+msgid "new file %s depends on old contents"
+msgstr ""
+
+#: builtin/apply.c:1694
+#, c-format
+msgid "deleted file %s still has contents"
+msgstr ""
+
+#: builtin/apply.c:1697
+#, c-format
+msgid "** warning: file %s becomes empty but is not deleted"
+msgstr ""
+
+#: builtin/apply.c:1843
+#, c-format
+msgid "corrupt binary patch at line %d: %.*s"
+msgstr ""
+
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1872
+#, c-format
+msgid "unrecognized binary patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:1958
+#, c-format
+msgid "patch with only garbage at line %d"
+msgstr ""
+
+#: builtin/apply.c:2048
+#, c-format
+msgid "unable to read symlink %s"
+msgstr ""
+
+#: builtin/apply.c:2052
+#, c-format
+msgid "unable to open or read %s"
+msgstr ""
+
+#: builtin/apply.c:2123
+msgid "oops"
+msgstr ""
+
+#: builtin/apply.c:2645
+#, c-format
+msgid "invalid start of line: '%c'"
+msgstr ""
+
+#: builtin/apply.c:2763
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:2775
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
+msgstr ""
+
+#: builtin/apply.c:2781
+#, c-format
+msgid ""
+"while searching for:\n"
+"%.*s"
+msgstr ""
+
+#: builtin/apply.c:2800
+#, c-format
+msgid "missing binary patch data for '%s'"
+msgstr ""
+
+#: builtin/apply.c:2903
+#, c-format
+msgid "binary patch does not apply to '%s'"
+msgstr ""
+
+#: builtin/apply.c:2909
+#, c-format
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
+msgstr ""
+
+#: builtin/apply.c:2930
+#, c-format
+msgid "patch failed: %s:%ld"
+msgstr ""
+
+#: builtin/apply.c:3045
+#, c-format
+msgid "patch %s has been renamed/deleted"
+msgstr ""
+
+#: builtin/apply.c:3052 builtin/apply.c:3069
+#, c-format
+msgid "read of %s failed"
+msgstr ""
+
+#: builtin/apply.c:3084
+msgid "removal patch leaves file contents"
+msgstr ""
+
+#: builtin/apply.c:3105
+#, c-format
+msgid "%s: already exists in working directory"
+msgstr ""
+
+#: builtin/apply.c:3143
+#, c-format
+msgid "%s: has been deleted/renamed"
+msgstr ""
+
+#: builtin/apply.c:3148 builtin/apply.c:3179
+#, c-format
+msgid "%s: %s"
+msgstr ""
+
+#: builtin/apply.c:3159
+#, c-format
+msgid "%s: does not exist in index"
+msgstr ""
+
+#: builtin/apply.c:3173
+#, c-format
+msgid "%s: does not match index"
+msgstr ""
+
+#: builtin/apply.c:3190
+#, c-format
+msgid "%s: wrong type"
+msgstr ""
+
+#: builtin/apply.c:3192
+#, c-format
+msgid "%s has type %o, expected %o"
+msgstr ""
+
+#: builtin/apply.c:3247
+#, c-format
+msgid "%s: already exists in index"
+msgstr ""
+
+#: builtin/apply.c:3267
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o)"
+msgstr ""
+
+#: builtin/apply.c:3272
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o) of %s"
+msgstr ""
+
+#: builtin/apply.c:3280
+#, c-format
+msgid "%s: patch does not apply"
+msgstr ""
+
+#: builtin/apply.c:3293
+#, c-format
+msgid "Checking patch %s..."
+msgstr ""
+
+#: builtin/apply.c:3348 builtin/checkout.c:212 builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr ""
+
+#: builtin/apply.c:3491
+#, c-format
+msgid "unable to remove %s from index"
+msgstr ""
+
+#: builtin/apply.c:3518
+#, c-format
+msgid "corrupt patch for subproject %s"
+msgstr ""
+
+#: builtin/apply.c:3522
+#, c-format
+msgid "unable to stat newly created file '%s'"
+msgstr ""
+
+#: builtin/apply.c:3527
+#, c-format
+msgid "unable to create backing store for newly created file %s"
+msgstr ""
+
+#: builtin/apply.c:3530
+#, c-format
+msgid "unable to add cache entry for %s"
+msgstr ""
+
+#: builtin/apply.c:3563
+#, c-format
+msgid "closing file '%s'"
+msgstr ""
+
+#: builtin/apply.c:3612
+#, c-format
+msgid "unable to write file '%s' mode %o"
+msgstr ""
+
+#: builtin/apply.c:3668
+#, c-format
+msgid "Applied patch %s cleanly."
+msgstr ""
+
+#: builtin/apply.c:3676
+msgid "internal error"
+msgstr ""
+
+#. Say this even without --verbose
+#: builtin/apply.c:3679
+#, c-format
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:3689
+#, c-format
+msgid "truncating .rej filename to %.*s.rej"
+msgstr ""
+
+#: builtin/apply.c:3710
+#, c-format
+msgid "Hunk #%d applied cleanly."
+msgstr ""
+
+#: builtin/apply.c:3713
+#, c-format
+msgid "Rejected hunk #%d."
+msgstr ""
+
+#: builtin/apply.c:3844
+msgid "unrecognized input"
+msgstr ""
+
+#: builtin/apply.c:3855
+msgid "unable to read index file"
+msgstr ""
+
+#: builtin/apply.c:3970 builtin/apply.c:3973
+msgid "path"
+msgstr ""
+
+#: builtin/apply.c:3971
+msgid "don't apply changes matching the given path"
+msgstr ""
+
+#: builtin/apply.c:3974
+msgid "apply changes matching the given path"
+msgstr ""
+
+#: builtin/apply.c:3976
+msgid "num"
+msgstr ""
+
+#: builtin/apply.c:3977
+msgid "remove <num> leading slashes from traditional diff paths"
+msgstr ""
+
+#: builtin/apply.c:3980
+msgid "ignore additions made by the patch"
+msgstr ""
+
+#: builtin/apply.c:3982
+msgid "instead of applying the patch, output diffstat for the input"
+msgstr ""
+
+#: builtin/apply.c:3986
+msgid "shows number of added and deleted lines in decimal notation"
+msgstr ""
+
+#: builtin/apply.c:3988
+msgid "instead of applying the patch, output a summary for the input"
+msgstr ""
+
+#: builtin/apply.c:3990
+msgid "instead of applying the patch, see if the patch is applicable"
+msgstr ""
+
+#: builtin/apply.c:3992
+msgid "make sure the patch is applicable to the current index"
+msgstr ""
+
+#: builtin/apply.c:3994
+msgid "apply a patch without touching the working tree"
+msgstr ""
+
+#: builtin/apply.c:3996
+msgid "also apply the patch (use with --stat/--summary/--check)"
+msgstr ""
+
+#: builtin/apply.c:3998
+msgid "build a temporary index based on embedded index information"
+msgstr ""
+
+#: builtin/apply.c:4000
+msgid "paths are separated with NUL character"
+msgstr ""
+
+#: builtin/apply.c:4003
+msgid "ensure at least <n> lines of context match"
+msgstr ""
+
+#: builtin/apply.c:4004
+msgid "action"
+msgstr ""
+
+#: builtin/apply.c:4005
+msgid "detect new or modified lines that have whitespace errors"
+msgstr ""
+
+#: builtin/apply.c:4008 builtin/apply.c:4011
+msgid "ignore changes in whitespace when finding context"
+msgstr ""
+
+#: builtin/apply.c:4014
+msgid "apply the patch in reverse"
+msgstr ""
+
+#: builtin/apply.c:4016
+msgid "don't expect at least one line of context"
+msgstr ""
+
+#: builtin/apply.c:4018
+msgid "leave the rejected hunks in corresponding *.rej files"
+msgstr ""
+
+#: builtin/apply.c:4020
+msgid "allow overlapping hunks"
+msgstr ""
+
+#: builtin/apply.c:4021
+msgid "be verbose"
+msgstr ""
+
+#: builtin/apply.c:4023
+msgid "tolerate incorrectly detected missing new-line at the end of file"
+msgstr ""
+
+#: builtin/apply.c:4026
+msgid "do not trust the line counts in the hunk headers"
+msgstr ""
+
+#: builtin/apply.c:4028
+msgid "root"
+msgstr ""
+
+#: builtin/apply.c:4029
+msgid "prepend <root> to all filenames"
+msgstr ""
+
+#: builtin/apply.c:4050
+msgid "--index outside a repository"
+msgstr ""
+
+#: builtin/apply.c:4053
+msgid "--cached outside a repository"
+msgstr ""
+
+#: builtin/apply.c:4069
+#, c-format
+msgid "can't open patch '%s'"
+msgstr ""
+
+#: builtin/apply.c:4083
+#, c-format
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:4089 builtin/apply.c:4099
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr ""
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr ""
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr ""
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr ""
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr ""
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr ""
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr ""
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr ""
+
+#: builtin/branch.c:144
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+
+#: builtin/branch.c:148
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+
+#: builtin/branch.c:180
+msgid "cannot use -a with -d"
+msgstr ""
+
+#: builtin/branch.c:186
+msgid "Couldn't look up commit object for HEAD"
+msgstr ""
+
+#: builtin/branch.c:191
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr ""
+
+#: builtin/branch.c:202
+#, c-format
+msgid "remote branch '%s' not found."
+msgstr ""
+
+#: builtin/branch.c:203
+#, c-format
+msgid "branch '%s' not found."
+msgstr ""
+
+#: builtin/branch.c:210
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr ""
+
+#: builtin/branch.c:216
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+
+#: builtin/branch.c:225
+#, c-format
+msgid "Error deleting remote branch '%s'"
+msgstr ""
+
+#: builtin/branch.c:226
+#, c-format
+msgid "Error deleting branch '%s'"
+msgstr ""
+
+#: builtin/branch.c:233
+#, c-format
+msgid "Deleted remote branch %s (was %s).\n"
+msgstr ""
+
+#: builtin/branch.c:234
+#, c-format
+msgid "Deleted branch %s (was %s).\n"
+msgstr ""
+
+#: builtin/branch.c:239
+msgid "Update of config-file failed"
+msgstr ""
+
+#: builtin/branch.c:337
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr ""
+
+#: builtin/branch.c:409
+#, c-format
+msgid "[%s: behind %d]"
+msgstr ""
+
+#: builtin/branch.c:411
+#, c-format
+msgid "[behind %d]"
+msgstr ""
+
+#: builtin/branch.c:415
+#, c-format
+msgid "[%s: ahead %d]"
+msgstr ""
+
+#: builtin/branch.c:417
+#, c-format
+msgid "[ahead %d]"
+msgstr ""
+
+#: builtin/branch.c:420
+#, c-format
+msgid "[%s: ahead %d, behind %d]"
+msgstr ""
+
+#: builtin/branch.c:423
+#, c-format
+msgid "[ahead %d, behind %d]"
+msgstr ""
+
+#: builtin/branch.c:535
+msgid "(no branch)"
+msgstr ""
+
+#: builtin/branch.c:600
+msgid "some refs could not be read"
+msgstr ""
+
+#: builtin/branch.c:613
+msgid "cannot rename the current branch while not on any."
+msgstr ""
+
+#: builtin/branch.c:623
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr ""
+
+#: builtin/branch.c:638
+msgid "Branch rename failed"
+msgstr ""
+
+#: builtin/branch.c:642
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr ""
+
+#: builtin/branch.c:646
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr ""
+
+#: builtin/branch.c:653
+msgid "Branch is renamed, but update of config-file failed"
+msgstr ""
+
+#: builtin/branch.c:668
+#, c-format
+msgid "malformed object name %s"
+msgstr ""
+
+#: builtin/branch.c:692
+#, c-format
+msgid "could not write branch description template: %s"
+msgstr ""
+
+#: builtin/branch.c:783
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr ""
+
+#: builtin/branch.c:788 builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr ""
+
+#: builtin/branch.c:808
+msgid "--column and --verbose are incompatible"
+msgstr ""
+
+#: builtin/branch.c:857
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr ""
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr ""
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr ""
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr ""
+
+#: builtin/checkout.c:113 builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr ""
+
+#: builtin/checkout.c:115 builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr ""
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr ""
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr ""
+
+#: builtin/checkout.c:234 builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr ""
+
+#: builtin/checkout.c:264 builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr ""
+
+#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
+#: builtin/merge.c:812
+msgid "unable to write new index file"
+msgstr ""
+
+#: builtin/checkout.c:319 builtin/diff.c:302 builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr ""
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr ""
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:566
+msgid "HEAD is now at"
+msgstr ""
+
+#: builtin/checkout.c:573
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:576
+#, c-format
+msgid "Already on '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:580
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:582
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:584
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:640
+#, c-format
+msgid " ... and %d more.\n"
+msgstr ""
+
+#. The singular version
+#: builtin/checkout.c:646
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/checkout.c:664
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+
+#: builtin/checkout.c:694
+msgid "internal error in revision walk"
+msgstr ""
+
+#: builtin/checkout.c:698
+msgid "Previous HEAD position was"
+msgstr ""
+
+#: builtin/checkout.c:724
+msgid "You are on a branch yet to be born"
+msgstr ""
+
+#. case (1)
+#: builtin/checkout.c:855
+#, c-format
+msgid "invalid reference: %s"
+msgstr ""
+
+#. case (1): want a tree
+#: builtin/checkout.c:894
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr ""
+
+#: builtin/checkout.c:974
+msgid "-B cannot be used with -b"
+msgstr ""
+
+#: builtin/checkout.c:983
+msgid "--patch is incompatible with all other options"
+msgstr ""
+
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr ""
+
+#: builtin/checkout.c:988
+msgid "--detach cannot be used with -t"
+msgstr ""
+
+#: builtin/checkout.c:994
+msgid "--track needs a branch name"
+msgstr ""
+
+#: builtin/checkout.c:1001
+msgid "Missing branch name; try -b"
+msgstr ""
+
+#: builtin/checkout.c:1007
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr ""
+
+#: builtin/checkout.c:1009
+msgid "--orphan cannot be used with -t"
+msgstr ""
+
+#: builtin/checkout.c:1019
+msgid "git checkout: -f and -m are incompatible"
+msgstr ""
+
+#: builtin/checkout.c:1053
+msgid "invalid path specification"
+msgstr ""
+
+#: builtin/checkout.c:1061
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+
+#: builtin/checkout.c:1063
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr ""
+
+#: builtin/checkout.c:1068
+msgid "git checkout: --detach does not take a path argument"
+msgstr ""
+
+#: builtin/checkout.c:1071
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+
+#: builtin/checkout.c:1090
+msgid "Cannot switch branch to a non-commit."
+msgstr ""
+
+#: builtin/checkout.c:1093
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr ""
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr ""
+
+#: builtin/clean.c:82
+msgid ""
+"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+
+#: builtin/clean.c:85
+msgid ""
+"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
+"clean"
+msgstr ""
+
+#: builtin/clean.c:155 builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:159 builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr ""
+
+#: builtin/clean.c:162 builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr ""
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr ""
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr ""
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr ""
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr ""
+
+#: builtin/clone.c:308 builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr ""
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr ""
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr ""
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr ""
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr ""
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr ""
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr ""
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr ""
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr ""
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr ""
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr ""
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr ""
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr ""
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr ""
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr ""
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr ""
+
+#: builtin/clone.c:706 builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr ""
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr ""
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr ""
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr ""
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr ""
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr ""
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr ""
+
+#: builtin/column.c:51
+msgid "--command must be the first argument"
+msgstr ""
+
+#: builtin/commit.c:43
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+
+#: builtin/commit.c:55
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+
+#: builtin/commit.c:60
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+
+#: builtin/commit.c:253
+msgid "failed to unpack HEAD tree object"
+msgstr ""
+
+#: builtin/commit.c:295
+msgid "unable to create temporary index"
+msgstr ""
+
+#: builtin/commit.c:301
+msgid "interactive add failed"
+msgstr ""
+
+#: builtin/commit.c:334 builtin/commit.c:355 builtin/commit.c:405
+msgid "unable to write new_index file"
+msgstr ""
+
+#: builtin/commit.c:386
+msgid "cannot do a partial commit during a merge."
+msgstr ""
+
+#: builtin/commit.c:388
+msgid "cannot do a partial commit during a cherry-pick."
+msgstr ""
+
+#: builtin/commit.c:398
+msgid "cannot read the index"
+msgstr ""
+
+#: builtin/commit.c:418
+msgid "unable to write temporary index file"
+msgstr ""
+
+#: builtin/commit.c:493 builtin/commit.c:499
+#, c-format
+msgid "invalid commit: %s"
+msgstr ""
+
+#: builtin/commit.c:522
+msgid "malformed --author parameter"
+msgstr ""
+
+#: builtin/commit.c:582
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr ""
+
+#: builtin/commit.c:620 builtin/commit.c:653 builtin/commit.c:967
+#, c-format
+msgid "could not lookup commit %s"
+msgstr ""
+
+#: builtin/commit.c:632 builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr ""
+
+#: builtin/commit.c:634
+msgid "could not read log from standard input"
+msgstr ""
+
+#: builtin/commit.c:638
+#, c-format
+msgid "could not read log file '%s'"
+msgstr ""
+
+#: builtin/commit.c:644
+msgid "commit has empty message"
+msgstr ""
+
+#: builtin/commit.c:660
+msgid "could not read MERGE_MSG"
+msgstr ""
+
+#: builtin/commit.c:664
+msgid "could not read SQUASH_MSG"
+msgstr ""
+
+#: builtin/commit.c:668
+#, c-format
+msgid "could not read '%s'"
+msgstr ""
+
+#: builtin/commit.c:720
+msgid "could not write commit template"
+msgstr ""
+
+#: builtin/commit.c:731
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a merge.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+
+#: builtin/commit.c:736
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+
+#: builtin/commit.c:748
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+
+#: builtin/commit.c:753
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+
+#: builtin/commit.c:766
+#, c-format
+msgid "%sAuthor: %s"
+msgstr ""
+
+#: builtin/commit.c:773
+#, c-format
+msgid "%sCommitter: %s"
+msgstr ""
+
+#: builtin/commit.c:793
+msgid "Cannot read index"
+msgstr ""
+
+#: builtin/commit.c:830
+msgid "Error building trees"
+msgstr ""
+
+#: builtin/commit.c:845 builtin/tag.c:361
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr ""
+
+#: builtin/commit.c:942
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr ""
+
+#: builtin/commit.c:957 builtin/commit.c:1157
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr ""
+
+#: builtin/commit.c:997
+msgid "Using both --reset-author and --author does not make sense"
+msgstr ""
+
+#: builtin/commit.c:1008
+msgid "You have nothing to amend."
+msgstr ""
+
+#: builtin/commit.c:1011
+msgid "You are in the middle of a merge -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1013
+msgid "You are in the middle of a cherry-pick -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1016
+msgid "Options --squash and --fixup cannot be used together"
+msgstr ""
+
+#: builtin/commit.c:1026
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr ""
+
+#: builtin/commit.c:1028
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr ""
+
+#: builtin/commit.c:1036
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr ""
+
+#: builtin/commit.c:1053
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr ""
+
+#: builtin/commit.c:1055
+msgid "No paths with --include/--only does not make sense."
+msgstr ""
+
+#: builtin/commit.c:1057
+msgid "Clever... amending the last one with dirty index."
+msgstr ""
+
+#: builtin/commit.c:1059
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr ""
+
+#: builtin/commit.c:1069 builtin/tag.c:577
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr ""
+
+#: builtin/commit.c:1074
+msgid "Paths with -a does not make sense."
+msgstr ""
+
+#: builtin/commit.c:1257
+msgid "couldn't look up newly created commit"
+msgstr ""
+
+#: builtin/commit.c:1259
+msgid "could not parse newly created commit"
+msgstr ""
+
+#: builtin/commit.c:1300
+msgid "detached HEAD"
+msgstr ""
+
+#: builtin/commit.c:1302
+msgid " (root-commit)"
+msgstr ""
+
+#: builtin/commit.c:1446
+msgid "could not parse HEAD commit"
+msgstr ""
+
+#: builtin/commit.c:1484 builtin/merge.c:509
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr ""
+
+#: builtin/commit.c:1491
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr ""
+
+#: builtin/commit.c:1498
+msgid "could not read MERGE_MODE"
+msgstr ""
+
+#: builtin/commit.c:1517
+#, c-format
+msgid "could not read commit message: %s"
+msgstr ""
+
+#: builtin/commit.c:1531
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
+msgstr ""
+
+#: builtin/commit.c:1536
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr ""
+
+#: builtin/commit.c:1551 builtin/merge.c:936 builtin/merge.c:961
+msgid "failed to write commit object"
+msgstr ""
+
+#: builtin/commit.c:1572
+msgid "cannot lock HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1576
+msgid "cannot update HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1587
+msgid ""
+"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."
+msgstr ""
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr ""
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr ""
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr ""
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr ""
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr ""
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr ""
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr ""
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr ""
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr ""
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr ""
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr ""
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr ""
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr ""
+
+#: builtin/diff.c:297
+msgid "Not a git repository"
+msgstr ""
+
+#: builtin/diff.c:347
+#, c-format
+msgid "invalid object '%s' given."
+msgstr ""
+
+#: builtin/diff.c:352
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:362
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr ""
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr ""
+
+#: builtin/fetch.c:253
+#, c-format
+msgid "object %s not found"
+msgstr ""
+
+#: builtin/fetch.c:259
+msgid "[up to date]"
+msgstr ""
+
+#: builtin/fetch.c:273
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr ""
+
+#: builtin/fetch.c:274 builtin/fetch.c:360
+msgid "[rejected]"
+msgstr ""
+
+#: builtin/fetch.c:285
+msgid "[tag update]"
+msgstr ""
+
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
+msgid " (unable to update local ref)"
+msgstr ""
+
+#: builtin/fetch.c:305
+msgid "[new tag]"
+msgstr ""
+
+#: builtin/fetch.c:308
+msgid "[new branch]"
+msgstr ""
+
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr ""
+
+#: builtin/fetch.c:356
+msgid "unable to update local ref"
+msgstr ""
+
+#: builtin/fetch.c:356
+msgid "forced update"
+msgstr ""
+
+#: builtin/fetch.c:362
+msgid "(non-fast-forward)"
+msgstr ""
+
+#: builtin/fetch.c:393 builtin/fetch.c:685
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr ""
+
+#: builtin/fetch.c:402
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr ""
+
+#: builtin/fetch.c:488
+#, c-format
+msgid "From %.*s\n"
+msgstr ""
+
+#: builtin/fetch.c:499
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+
+#: builtin/fetch.c:549
+#, c-format
+msgid " (%s will become dangling)"
+msgstr ""
+
+#: builtin/fetch.c:550
+#, c-format
+msgid " (%s has become dangling)"
+msgstr ""
+
+#: builtin/fetch.c:557
+msgid "[deleted]"
+msgstr ""
+
+#: builtin/fetch.c:558 builtin/remote.c:1055
+msgid "(none)"
+msgstr ""
+
+#: builtin/fetch.c:675
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr ""
+
+#: builtin/fetch.c:709
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr ""
+
+#: builtin/fetch.c:786
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr ""
+
+#: builtin/fetch.c:789
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr ""
+
+#: builtin/fetch.c:888
+#, c-format
+msgid "Fetching %s\n"
+msgstr ""
+
+#: builtin/fetch.c:890 builtin/remote.c:100
+#, c-format
+msgid "Could not fetch %s"
+msgstr ""
+
+#: builtin/fetch.c:907
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr ""
+
+#: builtin/fetch.c:927
+msgid "You need to specify a tag name."
+msgstr ""
+
+#: builtin/fetch.c:979
+msgid "fetch --all does not take a repository argument"
+msgstr ""
+
+#: builtin/fetch.c:981
+msgid "fetch --all does not make sense with refspecs"
+msgstr ""
+
+#: builtin/fetch.c:992
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr ""
+
+#: builtin/fetch.c:1000
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr ""
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr ""
+
+#: builtin/gc.c:90
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr ""
+
+#: builtin/gc.c:221
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr ""
+
+#: builtin/gc.c:224
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+
+#: builtin/gc.c:251
+msgid ""
+"There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr ""
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr ""
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr ""
+
+#: builtin/grep.c:478 builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr ""
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr ""
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr ""
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr ""
+
+#: builtin/grep.c:885
+msgid "no pattern given."
+msgstr ""
+
+#: builtin/grep.c:899
+#, c-format
+msgid "bad object %s"
+msgstr ""
+
+#: builtin/grep.c:940
+msgid "--open-files-in-pager only works on the worktree"
+msgstr ""
+
+#: builtin/grep.c:963
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr ""
+
+#: builtin/grep.c:968
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr ""
+
+#: builtin/grep.c:971
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr ""
+
+#: builtin/grep.c:979
+msgid "both --cached and trees are given."
+msgstr ""
+
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr ""
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr ""
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr ""
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr ""
+
+#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr ""
+
+#: builtin/help.c:211
+#, c-format
+msgid ""
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
+msgstr ""
+
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
+msgstr ""
+
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr ""
+
+#: builtin/help.c:355
+#, c-format
+msgid "'%s': unknown man viewer."
+msgstr ""
+
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr ""
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr ""
+
+#: builtin/help.c:391
+#, c-format
+msgid "'%s': not a documentation directory."
+msgstr ""
+
+#: builtin/help.c:432 builtin/help.c:439
+#, c-format
+msgid "usage: %s%s"
+msgstr ""
+
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
+msgstr ""
+
+#: builtin/index-pack.c:169
+#, c-format
+msgid "object type mismatch at %s"
+msgstr ""
+
+#: builtin/index-pack.c:189
+msgid "object of unexpected type"
+msgstr ""
+
+#: builtin/index-pack.c:226
+#, c-format
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:236
+msgid "early EOF"
+msgstr ""
+
+#: builtin/index-pack.c:237
+msgid "read error on input"
+msgstr ""
+
+#: builtin/index-pack.c:249
+msgid "used more bytes than were available"
+msgstr ""
+
+#: builtin/index-pack.c:256
+msgid "pack too large for current definition of off_t"
+msgstr ""
+
+#: builtin/index-pack.c:272
+#, c-format
+msgid "unable to create '%s'"
+msgstr ""
+
+#: builtin/index-pack.c:277
+#, c-format
+msgid "cannot open packfile '%s'"
+msgstr ""
+
+#: builtin/index-pack.c:291
+msgid "pack signature mismatch"
+msgstr ""
+
+#: builtin/index-pack.c:311
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
+msgstr ""
+
+#: builtin/index-pack.c:405
+#, c-format
+msgid "inflate returned %d"
+msgstr ""
+
+#: builtin/index-pack.c:450
+msgid "offset value overflow for delta base object"
+msgstr ""
+
+#: builtin/index-pack.c:458
+msgid "delta base offset is out of bound"
+msgstr ""
+
+#: builtin/index-pack.c:466
+#, c-format
+msgid "unknown object type %d"
+msgstr ""
+
+#: builtin/index-pack.c:495
+msgid "cannot pread pack file"
+msgstr ""
+
+#: builtin/index-pack.c:497
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:510
+msgid "serious inflate inconsistency"
+msgstr ""
+
+#: builtin/index-pack.c:583
+#, c-format
+msgid "cannot read existing object %s"
+msgstr ""
+
+#: builtin/index-pack.c:586
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
+msgstr ""
+
+#: builtin/index-pack.c:598
+#, c-format
+msgid "invalid blob object %s"
+msgstr ""
+
+#: builtin/index-pack.c:610
+#, c-format
+msgid "invalid %s"
+msgstr ""
+
+#: builtin/index-pack.c:612
+msgid "Error in object"
+msgstr ""
+
+#: builtin/index-pack.c:614
+#, c-format
+msgid "Not all child objects of %s are reachable"
+msgstr ""
+
+#: builtin/index-pack.c:687 builtin/index-pack.c:713
+msgid "failed to apply delta"
+msgstr ""
+
+#: builtin/index-pack.c:850
+msgid "Receiving objects"
+msgstr ""
+
+#: builtin/index-pack.c:850
+msgid "Indexing objects"
+msgstr ""
+
+#: builtin/index-pack.c:872
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr ""
+
+#: builtin/index-pack.c:877
+msgid "cannot fstat packfile"
+msgstr ""
+
+#: builtin/index-pack.c:880
+msgid "pack has junk at the end"
+msgstr ""
+
+#: builtin/index-pack.c:903
+msgid "Resolving deltas"
+msgstr ""
+
+#: builtin/index-pack.c:954
+msgid "confusion beyond insanity"
+msgstr ""
+
+#: builtin/index-pack.c:973
+#, c-format
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:998
+#, c-format
+msgid "unable to deflate appended object (%d)"
+msgstr ""
+
+#: builtin/index-pack.c:1077
+#, c-format
+msgid "local object %s is corrupt"
+msgstr ""
+
+#: builtin/index-pack.c:1101
+msgid "error while closing pack file"
+msgstr ""
+
+#: builtin/index-pack.c:1114
+#, c-format
+msgid "cannot write keep file '%s'"
+msgstr ""
+
+#: builtin/index-pack.c:1122
+#, c-format
+msgid "cannot close written keep file '%s'"
+msgstr ""
+
+#: builtin/index-pack.c:1135
+msgid "cannot store pack file"
+msgstr ""
+
+#: builtin/index-pack.c:1146
+msgid "cannot store index file"
+msgstr ""
+
+#: builtin/index-pack.c:1247
+#, c-format
+msgid "Cannot open existing pack file '%s'"
+msgstr ""
+
+#: builtin/index-pack.c:1249
+#, c-format
+msgid "Cannot open existing pack idx file for '%s'"
+msgstr ""
+
+#: builtin/index-pack.c:1296
+#, c-format
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:1303
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:1330
+msgid "Cannot come back to cwd"
+msgstr ""
+
+#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
+#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#, c-format
+msgid "bad %s"
+msgstr ""
+
+#: builtin/index-pack.c:1407
+msgid "--fix-thin cannot be used without --stdin"
+msgstr ""
+
+#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr ""
+
+#: builtin/index-pack.c:1430
+msgid "--verify with no packfile name given"
+msgstr ""
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr ""
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr ""
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr ""
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr ""
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr ""
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr ""
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr ""
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr ""
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr ""
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr ""
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr ""
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr ""
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr ""
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr ""
+
+#: builtin/init-db.c:322 builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr ""
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr ""
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr ""
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr ""
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr ""
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr ""
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr ""
+
+#: builtin/init-db.c:521 builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr ""
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr ""
+
+#: builtin/init-db.c:554
+#, c-format
+msgid ""
+"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
+"dir=<directory>)"
+msgstr ""
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr ""
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr ""
+
+#: builtin/log.c:188
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr ""
+
+#: builtin/log.c:401 builtin/log.c:489
+#, c-format
+msgid "Could not read object %s"
+msgstr ""
+
+#: builtin/log.c:513
+#, c-format
+msgid "Unknown type: %d"
+msgstr ""
+
+#: builtin/log.c:602
+msgid "format.headers without value"
+msgstr ""
+
+#: builtin/log.c:676
+msgid "name of output directory is too long"
+msgstr ""
+
+#: builtin/log.c:687
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr ""
+
+#: builtin/log.c:701
+msgid "Need exactly one range."
+msgstr ""
+
+#: builtin/log.c:709
+msgid "Not a range."
+msgstr ""
+
+#: builtin/log.c:786
+msgid "Cover letter needs email format"
+msgstr ""
+
+#: builtin/log.c:859
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr ""
+
+#: builtin/log.c:932
+msgid "Two output directories?"
+msgstr ""
+
+#: builtin/log.c:1153
+#, c-format
+msgid "bogus committer info %s"
+msgstr ""
+
+#: builtin/log.c:1198
+msgid "-n and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1200
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1208
+msgid "--name-only does not make sense"
+msgstr ""
+
+#: builtin/log.c:1210
+msgid "--name-status does not make sense"
+msgstr ""
+
+#: builtin/log.c:1212
+msgid "--check does not make sense"
+msgstr ""
+
+#: builtin/log.c:1235
+msgid "standard output, or directory, which one?"
+msgstr ""
+
+#: builtin/log.c:1237
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr ""
+
+#: builtin/log.c:1390
+msgid "Failed to create output files"
+msgstr ""
+
+#: builtin/log.c:1494
+#, c-format
+msgid ""
+"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr ""
+
+#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#, c-format
+msgid "Unknown commit %s"
+msgstr ""
+
+#: builtin/merge.c:90
+msgid "switch `m' requires a value"
+msgstr ""
+
+#: builtin/merge.c:127
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr ""
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Available strategies are:"
+msgstr ""
+
+#: builtin/merge.c:133
+#, c-format
+msgid "Available custom strategies are:"
+msgstr ""
+
+#: builtin/merge.c:240
+msgid "could not run stash."
+msgstr ""
+
+#: builtin/merge.c:245
+msgid "stash failed"
+msgstr ""
+
+#: builtin/merge.c:250
+#, c-format
+msgid "not a valid object: %s"
+msgstr ""
+
+#: builtin/merge.c:269 builtin/merge.c:286
+msgid "read-tree failed"
+msgstr ""
+
+#: builtin/merge.c:316
+msgid " (nothing to squash)"
+msgstr ""
+
+#: builtin/merge.c:329
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:361
+msgid "Writing SQUASH_MSG"
+msgstr ""
+
+#: builtin/merge.c:363
+msgid "Finishing SQUASH_MSG"
+msgstr ""
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr ""
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr ""
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr ""
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr ""
+
+#: builtin/merge.c:697
+msgid "Unable to write index."
+msgstr ""
+
+#: builtin/merge.c:710
+msgid "Not handling anything other than two heads merge."
+msgstr ""
+
+#: builtin/merge.c:724
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr ""
+
+#: builtin/merge.c:738
+#, c-format
+msgid "unable to write %s"
+msgstr ""
+
+#: builtin/merge.c:877
+#, c-format
+msgid "Could not read from '%s'"
+msgstr ""
+
+#: builtin/merge.c:886
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:892
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+
+#: builtin/merge.c:916
+msgid "Empty commit message."
+msgstr ""
+
+#: builtin/merge.c:928
+#, c-format
+msgid "Wonderful.\n"
+msgstr ""
+
+#: builtin/merge.c:993
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr ""
+
+#: builtin/merge.c:1009
+#, c-format
+msgid "'%s' is not a commit"
+msgstr ""
+
+#: builtin/merge.c:1050
+msgid "No current branch."
+msgstr ""
+
+#: builtin/merge.c:1052
+msgid "No remote for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1054
+msgid "No default upstream defined for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1059
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr ""
+
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr ""
+
+#: builtin/merge.c:1214
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr ""
+
+#: builtin/merge.c:1230 git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1233 git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1237
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1240
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1249
+msgid "You cannot combine --squash with --no-ff."
+msgstr ""
+
+#: builtin/merge.c:1254
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr ""
+
+#: builtin/merge.c:1261
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr ""
+
+#: builtin/merge.c:1293
+msgid "Can merge only exactly one commit into empty head"
+msgstr ""
+
+#: builtin/merge.c:1296
+msgid "Squash commit into empty head not supported yet"
+msgstr ""
+
+#: builtin/merge.c:1298
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr ""
+
+#: builtin/merge.c:1413
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr ""
+
+#: builtin/merge.c:1451
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr ""
+
+#: builtin/merge.c:1458
+#, c-format
+msgid "Nope.\n"
+msgstr ""
+
+#: builtin/merge.c:1490
+msgid "Not possible to fast-forward, aborting."
+msgstr ""
+
+#: builtin/merge.c:1513 builtin/merge.c:1592
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr ""
+
+#: builtin/merge.c:1517
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr ""
+
+#: builtin/merge.c:1583
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:1585
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr ""
+
+#: builtin/merge.c:1594
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr ""
+
+#: builtin/merge.c:1606
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr ""
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr ""
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr ""
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr ""
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr ""
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr ""
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr ""
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr ""
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr ""
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr ""
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr ""
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr ""
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr ""
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr ""
+
+#: builtin/mv.c:215 builtin/remote.c:731
+#, c-format
+msgid "renaming '%s' failed"
+msgstr ""
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr ""
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:175 builtin/tag.c:347
+#, c-format
+msgid "could not create file '%s'"
+msgstr ""
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr ""
+
+#: builtin/notes.c:210 builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr ""
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr ""
+
+#: builtin/notes.c:251 builtin/tag.c:542
+#, c-format
+msgid "cannot read '%s'"
+msgstr ""
+
+#: builtin/notes.c:253 builtin/tag.c:545
+#, c-format
+msgid "could not open or read '%s'"
+msgstr ""
+
+#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
+#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
+#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
+#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:558
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr ""
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr ""
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr ""
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr ""
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr ""
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr ""
+
+#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
+#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr ""
+
+#: builtin/notes.c:513 builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr ""
+
+#: builtin/notes.c:580
+#, c-format
+msgid ""
+"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:585 builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr ""
+
+#: builtin/notes.c:656
+#, c-format
+msgid ""
+"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr ""
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr ""
+
+#: builtin/notes.c:1103 builtin/remote.c:1598
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2337
+#, c-format
+msgid "unsupported index version %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2341
+#, c-format
+msgid "bad index version '%s'"
+msgstr ""
+
+#: builtin/pack-objects.c:2364
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr ""
+
+#: builtin/pack-objects.c:2368
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr ""
+
+#: builtin/push.c:45
+msgid "tag shorthand without <tag>"
+msgstr ""
+
+#: builtin/push.c:64
+msgid "--delete only accepts plain target ref names"
+msgstr ""
+
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+
+#: builtin/push.c:121
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+
+#: builtin/push.c:128
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+
+#: builtin/push.c:136
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr ""
+
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+
+#: builtin/push.c:174
+msgid ""
+"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr ""
+
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:233
+#, c-format
+msgid "Pushing to %s\n"
+msgstr ""
+
+#: builtin/push.c:237
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr ""
+
+#: builtin/push.c:269
+#, c-format
+msgid "bad repository '%s'"
+msgstr ""
+
+#: builtin/push.c:270
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote "
+"repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+
+#: builtin/push.c:285
+msgid "--all and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:286
+msgid "--all can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:291
+msgid "--mirror and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:292
+msgid "--mirror can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:297
+msgid "--all and --mirror are incompatible"
+msgstr ""
+
+#: builtin/push.c:385
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr ""
+
+#: builtin/push.c:387
+msgid "--delete doesn't make sense without any refs"
+msgstr ""
+
+#: builtin/remote.c:98
+#, c-format
+msgid "Updating %s"
+msgstr ""
+
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
+msgstr ""
+
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
+msgstr ""
+
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
+msgstr ""
+
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
+msgstr ""
+
+#: builtin/remote.c:195 builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
+msgstr ""
+
+#: builtin/remote.c:199 builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
+msgstr ""
+
+#: builtin/remote.c:243
+#, c-format
+msgid "Could not setup master '%s'"
+msgstr ""
+
+#: builtin/remote.c:299
+#, c-format
+msgid "more than one %s"
+msgstr ""
+
+#: builtin/remote.c:339
+#, c-format
+msgid "Could not get fetch map for refspec %s"
+msgstr ""
+
+#: builtin/remote.c:440 builtin/remote.c:448
+msgid "(matching)"
+msgstr ""
+
+#: builtin/remote.c:452
+msgid "(delete)"
+msgstr ""
+
+#: builtin/remote.c:595 builtin/remote.c:601 builtin/remote.c:607
+#, c-format
+msgid "Could not append '%s' to '%s'"
+msgstr ""
+
+#: builtin/remote.c:639 builtin/remote.c:792 builtin/remote.c:890
+#, c-format
+msgid "No such remote: %s"
+msgstr ""
+
+#: builtin/remote.c:656
+#, c-format
+msgid "Could not rename config section '%s' to '%s'"
+msgstr ""
+
+#: builtin/remote.c:662 builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
+msgstr ""
+
+#: builtin/remote.c:677
+#, c-format
+msgid ""
+"Not updating non-default fetch refspec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
+msgstr ""
+
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
+msgstr ""
+
+#: builtin/remote.c:694
+#, c-format
+msgid "Could not set '%s'"
+msgstr ""
+
+#: builtin/remote.c:716
+#, c-format
+msgid "deleting '%s' failed"
+msgstr ""
+
+#: builtin/remote.c:750
+#, c-format
+msgid "creating '%s' failed"
+msgstr ""
+
+#: builtin/remote.c:764
+#, c-format
+msgid "Could not remove branch %s"
+msgstr ""
+
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:943
+#, c-format
+msgid " new (next fetch will store in remotes/%s)"
+msgstr ""
+
+#: builtin/remote.c:946
+msgid " tracked"
+msgstr ""
+
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
+msgstr ""
+
+#: builtin/remote.c:950
+msgid " ???"
+msgstr ""
+
+#: builtin/remote.c:991
+#, c-format
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
+msgstr ""
+
+#: builtin/remote.c:998
+#, c-format
+msgid "rebases onto remote %s"
+msgstr ""
+
+#: builtin/remote.c:1001
+#, c-format
+msgid " merges with remote %s"
+msgstr ""
+
+#: builtin/remote.c:1002
+msgid " and with remote"
+msgstr ""
+
+#: builtin/remote.c:1004
+#, c-format
+msgid "merges with remote %s"
+msgstr ""
+
+#: builtin/remote.c:1005
+msgid " and with remote"
+msgstr ""
+
+#: builtin/remote.c:1051
+msgid "create"
+msgstr ""
+
+#: builtin/remote.c:1054
+msgid "delete"
+msgstr ""
+
+#: builtin/remote.c:1058
+msgid "up to date"
+msgstr ""
+
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
+msgstr ""
+
+#: builtin/remote.c:1064
+msgid "local out of date"
+msgstr ""
+
+#: builtin/remote.c:1071
+#, c-format
+msgid " %-*s forces to %-*s (%s)"
+msgstr ""
+
+#: builtin/remote.c:1074
+#, c-format
+msgid " %-*s pushes to %-*s (%s)"
+msgstr ""
+
+#: builtin/remote.c:1078
+#, c-format
+msgid " %-*s forces to %s"
+msgstr ""
+
+#: builtin/remote.c:1081
+#, c-format
+msgid " %-*s pushes to %s"
+msgstr ""
+
+#: builtin/remote.c:1118
+#, c-format
+msgid "* remote %s"
+msgstr ""
+
+#: builtin/remote.c:1119
+#, c-format
+msgid " Fetch URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1120 builtin/remote.c:1285
+msgid "(no URL)"
+msgstr ""
+
+#: builtin/remote.c:1129 builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1133 builtin/remote.c:1135 builtin/remote.c:1137
+#, c-format
+msgid " HEAD branch: %s"
+msgstr ""
+
+#: builtin/remote.c:1139
+#, c-format
+msgid ""
+" HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
+msgstr ""
+
+#: builtin/remote.c:1151
+#, c-format
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1154 builtin/remote.c:1181
+msgid " (status not queried)"
+msgstr ""
+
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr ""
+
+#: builtin/remote.c:1178
+#, c-format
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
+msgstr ""
+
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
+msgstr ""
+
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
+msgstr ""
+
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
+msgstr ""
+
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
+msgstr ""
+
+#: builtin/remote.c:1274
+#, c-format
+msgid " %s will become dangling!"
+msgstr ""
+
+#: builtin/remote.c:1275
+#, c-format
+msgid " %s has become dangling!"
+msgstr ""
+
+#: builtin/remote.c:1281
+#, c-format
+msgid "Pruning %s"
+msgstr ""
+
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
+msgstr ""
+
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
+msgstr ""
+
+#: builtin/remote.c:1387 builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
+msgstr ""
+
+#: builtin/remote.c:1414
+msgid "no remote specified"
+msgstr ""
+
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
+msgstr ""
+
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
+msgstr ""
+
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr ""
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "merge"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr ""
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr ""
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr ""
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr ""
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr ""
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr ""
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr ""
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr ""
+
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr ""
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr ""
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr ""
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr ""
+
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr ""
+
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr ""
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr ""
+
+#: builtin/revert.c:70 builtin/revert.c:92
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr ""
+
+#: builtin/revert.c:131
+msgid "program error"
+msgstr ""
+
+#: builtin/revert.c:221
+msgid "revert failed"
+msgstr ""
+
+#: builtin/revert.c:236
+msgid "cherry-pick failed"
+msgstr ""
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr ""
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr ""
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr ""
+
+#: builtin/tag.c:60
+#, c-format
+msgid "malformed object at '%s'"
+msgstr ""
+
+#: builtin/tag.c:207
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr ""
+
+#: builtin/tag.c:212
+#, c-format
+msgid "tag '%s' not found."
+msgstr ""
+
+#: builtin/tag.c:227
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr ""
+
+#: builtin/tag.c:239
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr ""
+
+#: builtin/tag.c:249
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+
+#: builtin/tag.c:256
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you "
+"want to.\n"
+"#\n"
+msgstr ""
+
+#: builtin/tag.c:298
+msgid "unable to sign the tag"
+msgstr ""
+
+#: builtin/tag.c:300
+msgid "unable to write tag file"
+msgstr ""
+
+#: builtin/tag.c:325
+msgid "bad object type."
+msgstr ""
+
+#: builtin/tag.c:338
+msgid "tag header too big."
+msgstr ""
+
+#: builtin/tag.c:370
+msgid "no tag message?"
+msgstr ""
+
+#: builtin/tag.c:376
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr ""
+
+#: builtin/tag.c:425
+msgid "switch 'points-at' requires an object"
+msgstr ""
+
+#: builtin/tag.c:427
+#, c-format
+msgid "malformed object name '%s'"
+msgstr ""
+
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr ""
+
+#: builtin/tag.c:523
+msgid "-n option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:525
+msgid "--contains option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:527
+msgid "--points-at option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:535
+msgid "only one -F or -m option is allowed."
+msgstr ""
+
+#: builtin/tag.c:555
+msgid "too many params"
+msgstr ""
+
+#: builtin/tag.c:561
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr ""
+
+#: builtin/tag.c:566
+#, c-format
+msgid "tag '%s' already exists"
+msgstr ""
+
+#: builtin/tag.c:584
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr ""
+
+#: builtin/tag.c:586
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr ""
+
+#: builtin/tag.c:588
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr ""
+
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr ""
+
+#: parse-options.h:133 parse-options.h:235
+msgid "n"
+msgstr ""
+
+#: parse-options.h:141
+msgid "time"
+msgstr ""
+
+#: parse-options.h:149
+msgid "file"
+msgstr ""
+
+#: parse-options.h:151
+msgid "when"
+msgstr ""
+
+#: parse-options.h:156
+msgid "no-op (backward compatibility)"
+msgstr ""
+
+#: parse-options.h:228
+msgid "be more verbose"
+msgstr ""
+
+#: parse-options.h:230
+msgid "be more quiet"
+msgstr ""
+
+#: parse-options.h:236
+msgid "use <n> digits to display SHA-1s"
+msgstr ""
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr ""
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr ""
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr ""
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr ""
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr ""
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr ""
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr ""
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr ""
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr ""
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr ""
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr ""
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr ""
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr ""
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr ""
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr ""
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr ""
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr ""
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr ""
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr ""
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr ""
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr ""
+
+#: git-am.sh:50
+msgid "You need to set your committer info first"
+msgstr ""
+
+#: git-am.sh:95
+msgid ""
+"You seem to have moved HEAD since the last 'am' failure.\n"
+"Not rewinding to ORIG_HEAD"
+msgstr ""
+
+#: git-am.sh:105
+#, sh-format
+msgid ""
+"When you have resolved this problem run \"$cmdline --resolved\".\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+
+#: git-am.sh:121
+msgid "Cannot fall back to three-way merge."
+msgstr ""
+
+#: git-am.sh:137
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr ""
+
+#: git-am.sh:154
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+
+#: git-am.sh:163
+msgid "Falling back to patching base and 3-way merge..."
+msgstr ""
+
+#: git-am.sh:275
+msgid "Only one StGIT patch series can be applied at once"
+msgstr ""
+
+#: git-am.sh:362
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr ""
+
+#: git-am.sh:364
+msgid "Patch format detection failed."
+msgstr ""
+
+#: git-am.sh:418
+msgid "-d option is no longer supported. Do not use."
+msgstr ""
+
+#: git-am.sh:481
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr ""
+
+#: git-am.sh:486
+msgid "Please make up your mind. --skip or --abort?"
+msgstr ""
+
+#: git-am.sh:513
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr ""
+
+#: git-am.sh:579
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr ""
+
+#: git-am.sh:671
+#, sh-format
+msgid ""
+"Patch is empty. Was it split wrong?\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+
+#: git-am.sh:708
+msgid "Patch does not have a valid e-mail address."
+msgstr ""
+
+#: git-am.sh:755
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr ""
+
+#: git-am.sh:759
+msgid "Commit Body is:"
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:766
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr ""
+
+#: git-am.sh:802
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr ""
+
+#: git-am.sh:823
+msgid ""
+"No changes - did you forget to use 'git add'?\n"
+"If there is nothing left to stage, chances are that something else\n"
+"already introduced the same changes; you might want to skip this patch."
+msgstr ""
+
+#: git-am.sh:831
+msgid ""
+"You still have unmerged paths in your index\n"
+"did you forget to use 'git add'?"
+msgstr ""
+
+#: git-am.sh:847
+msgid "No changes -- Patch already applied."
+msgstr ""
+
+#: git-am.sh:857
+#, sh-format
+msgid "Patch failed at $msgnum $FIRSTLINE"
+msgstr ""
+
+#: git-am.sh:873
+msgid "applying to an empty history"
+msgstr ""
+
+#: git-bisect.sh:48
+msgid "You need to start by \"git bisect start\""
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr ""
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr ""
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr ""
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr ""
+
+#: git-bisect.sh:130
+#, sh-format
+msgid ""
+"Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr ""
+
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr ""
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr ""
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr ""
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr ""
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr ""
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr ""
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr ""
+
+#. have bad but not good. we could bisect although
+#. this is less optimum.
+#: git-bisect.sh:273
+msgid "Warning: bisecting only with a bad commit."
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr ""
+
+#: git-bisect.sh:289
+msgid ""
+"You need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+
+#: git-bisect.sh:292
+msgid ""
+"You need to start by \"git bisect start\".\n"
+"You then need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+
+#: git-bisect.sh:347 git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr ""
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr ""
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr ""
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr ""
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr ""
+
+#: git-bisect.sh:420
+#, sh-format
+msgid "running $command"
+msgstr ""
+
+#: git-bisect.sh:427
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"exit code $res from '$command' is < 0 or >= 128"
+msgstr ""
+
+#: git-bisect.sh:453
+msgid "bisect run cannot continue any more"
+msgstr ""
+
+#: git-bisect.sh:459
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"'bisect_state $state' exited with error code $res"
+msgstr ""
+
+#: git-bisect.sh:466
+msgid "bisect run success"
+msgstr ""
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr ""
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr ""
+
+#. The fetch involved updating the current branch.
+#. The working tree and the index file is still based on the
+#. $orig_head commit, but we are merging into $curr_head.
+#. First update the working tree to match $curr_head.
+#: git-pull.sh:228
+#, sh-format
+msgid ""
+"Warning: fetch updated the current branch head.\n"
+"Warning: fast-forwarding your working tree from\n"
+"Warning: commit $orig_head."
+msgstr ""
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr ""
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr ""
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr ""
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr ""
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr ""
+
+#: git-stash.sh:123 git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr ""
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr ""
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr ""
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr ""
+
+#. TRANSLATORS: $option is an invalid option, like
+#. `--blah-blah'. The 7 spaces at the beginning of the
+#. second line correspond to "error: ". So you should line
+#. up the second line with however many characters the
+#. translation of "error: " takes in your language. E.g. in
+#. English this is:
+#.
+#. $ git stash save --blah-blah 2>&1 | head -n 2
+#. error: unknown option for 'stash save': --blah-blah
+#. To provide a message, use git stash save -- '--blah-blah'
+#: git-stash.sh:202
+#, sh-format
+msgid ""
+"error: unknown option for 'stash save': $option\n"
+" To provide a message, use git stash save -- '$option'"
+msgstr ""
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr ""
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr ""
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr ""
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr ""
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr ""
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr ""
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr ""
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr ""
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr ""
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr ""
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr ""
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr ""
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr ""
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr ""
+
+#: git-stash.sh:474
+msgid "Index was not unstashed."
+msgstr ""
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr ""
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr ""
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr ""
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr ""
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr ""
+
+#: git-submodule.sh:109
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:150
+#, sh-format
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr ""
+
+#: git-submodule.sh:160
+#, sh-format
+msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
+msgstr ""
+
+#: git-submodule.sh:249
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr ""
+
+#: git-submodule.sh:266
+#, sh-format
+msgid "'$sm_path' already exists in the index"
+msgstr ""
+
+#: git-submodule.sh:270
+#, sh-format
+msgid ""
+"The following path is ignored by one of your .gitignore files:\n"
+"$sm_path\n"
+"Use -f if you really want to add it."
+msgstr ""
+
+#: git-submodule.sh:281
+#, sh-format
+msgid "Adding existing repo at '$sm_path' to the index"
+msgstr ""
+
+#: git-submodule.sh:283
+#, sh-format
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr ""
+
+#: git-submodule.sh:297
+#, sh-format
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:302
+#, sh-format
+msgid "Failed to add submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:307
+#, sh-format
+msgid "Failed to register submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:349
+#, sh-format
+msgid "Entering '$prefix$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:363
+#, sh-format
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr ""
+
+#: git-submodule.sh:406
+#, sh-format
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr ""
+
+#: git-submodule.sh:415
+#, sh-format
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:417
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:425
+#, sh-format
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:524
+#, sh-format
+msgid ""
+"Submodule path '$sm_path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+
+#: git-submodule.sh:537
+#, sh-format
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:556
+#, sh-format
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:570
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:571
+#, sh-format
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:576
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:577
+#, sh-format
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:582
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:583
+#, sh-format
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:605 git-submodule.sh:928
+#, sh-format
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:713
+msgid "--cached cannot be used with --files"
+msgstr ""
+
+#. unexpected type
+#: git-submodule.sh:753
+#, sh-format
+msgid "unexpected mode $mod_dst"
+msgstr ""
+
+#: git-submodule.sh:771
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr ""
+
+#: git-submodule.sh:774
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr ""
+
+#: git-submodule.sh:777
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr ""
+
+#: git-submodule.sh:802
+msgid "blob"
+msgstr ""
+
+#: git-submodule.sh:803
+msgid "submodule"
+msgstr ""
+
+#: git-submodule.sh:840
+msgid "# Submodules changed but not updated:"
+msgstr ""
+
+#: git-submodule.sh:842
+msgid "# Submodule changes to be committed:"
+msgstr ""
+
+#: git-submodule.sh:974
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr ""
diff --git a/po/is.po b/po/is.po
new file mode 100644
index 0000000..8692a8b
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,93 @@
+# Icelandic translations for Git.
+# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+# This file is distributed under the same license as the Git package.
+# Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2010-09-20 14:44+0000\n"
+"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"Language: is\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:5
+msgid "See 'git help COMMAND' for more information on a specific command."
+msgstr "Sjá 'git help SKIPUN' til að sjá hjálp fyrir tiltekna skipun."
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:10
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:13
+#, c-format
+msgid "TEST: A C test string %s"
+msgstr "TILRAUN: C tilraunastrengur %s"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:16
+#, c-format
+msgid "TEST: Hello World!"
+msgstr "TILRAUN: Halló Heimur!"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:19
+#, c-format
+msgid "TEST: Old English Runes"
+msgstr "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛠᚻᛖ ᛒᚢᛞᛖ áš©áš¾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ áš¹á›áš¦ ᚦᚪ ᚹᛖᛥᚫ"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:22
+#, c-format
+msgid "TEST: ‘single’ and “double†quotes"
+msgstr "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:8
+msgid "TEST: A Shell test string"
+msgstr "TILRAUN: Skeljartilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:11
+#, sh-format
+msgid "TEST: A Shell test $variable"
+msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:8
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:11
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
+
+#. TRANSLATORS: The first '%s' is either "Reinitialized
+#. existing" or "Initialized empty", the second " shared" or
+#. "", and the last '%s%s' is the verbatim directory name.
+#: builtin/init-db.c:355
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s Git lind í %s%s\n"
+
+#: builtin/init-db.c:356
+msgid "Reinitialized existing"
+msgstr "Endurgerði"
+
+#: builtin/init-db.c:356
+msgid "Initialized empty"
+msgstr "Bjó til tóma"
+
+#: builtin/init-db.c:357
+msgid " shared"
+msgstr " sameiginlega"
diff --git a/po/it.po b/po/it.po
new file mode 100644
index 0000000..fe61f1a
--- /dev/null
+++ b/po/it.po
@@ -0,0 +1,5367 @@
+# Italian translations for Git.
+# Copyright (C) 2012 Marco Paolone <marcopaolone@gmail.com>
+# This file is distributed under the same license as the Git package.
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-06-08 10:20+0800\n"
+"PO-Revision-Date: 2012-06-14 14:13+0200\n"
+"Last-Translator: Marco Paolone <marcopaolone@gmail.com>\n"
+"Language-Team: Italian\n"
+"Language: it\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"
+
+#: advice.c:40
+#, c-format
+msgid "hint: %.*s\n"
+msgstr "suggerimento: %.*s\n"
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:70
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr ""
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr "header non riconosciuto: %s%s (%d)"
+
+#: bundle.c:89 builtin/commit.c:696
+#, c-format
+msgid "could not open '%s'"
+msgstr "non è stato possibile aprire '%s'"
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr ""
+
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
+#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr ""
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr ""
+
+#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr "argomento non riconosciuto: %s"
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr "il ref '%s' è escluso dalle opzioni di rev-list"
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr ""
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr ""
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr ""
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr "impossibile creare '%s'"
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr ""
+
+#: commit.c:48
+#, c-format
+msgid "could not parse %s"
+msgstr "non è stato possibile analizzare %s"
+
+#: commit.c:50
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr "%s %s non è un commit!"
+
+#: compat/obstack.c:406 compat/obstack.c:408
+msgid "memory exhausted"
+msgstr "memoria esaurita"
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr "Non è stato possibile eseguire 'git-rev-list'"
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr "scrittura nella rev-list non riuscita: %s"
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr ""
+
+#: date.c:95
+msgid "in the future"
+msgstr "in futuro"
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] "%lu secondo fa"
+msgstr[1] "%lu secondi fa"
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] "%lu un minuto fa"
+msgstr[1] "%lu minuti fa"
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] "%lu ora fa"
+msgstr[1] "%lu ore fa"
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] "%lu giorno fa"
+msgstr[1] "%lu giorni fa"
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] "%lu settimana fa"
+msgstr[1] "%lu settimane fa"
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] "%lu mese fa"
+msgstr[1] "%lu mesi fa"
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] "%lu anno"
+msgstr[1] "%lu anni"
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] "%s, %lu mese fa"
+msgstr[1] "%s, %lu mesi fa"
+
+#: date.c:154 date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] "%lu anno fa"
+msgstr[1] "%lu anni fa"
+
+#: diff.c:105
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr ""
+
+#: diff.c:110
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr " Parametro dirstat '%.*s' sconosciuto\n"
+
+#: diff.c:210
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+"Trovati errori nella variabile di configurazione 'diff.dirstat':\n"
+"%s"
+
+#: diff.c:1400
+msgid " 0 files changed\n"
+msgstr " 0 file modificati\n"
+
+#: diff.c:1404
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] " %d file modificato"
+msgstr[1] " %d file modificati"
+
+#: diff.c:1421
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ", %d inserzione(+)"
+msgstr[1] ", %d inserzioni(+)"
+
+#: diff.c:1432
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ". %d rimozione(-)"
+msgstr[1] ", %d rimozioni(-)"
+
+#: diff.c:3478
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+"Analisi del parametro dell'opzione --dirstat/-X non riuscita:\n"
+"%s"
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr "non è stato possibile eseguire gpg."
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr "gpg non ha accettato i dati"
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr "gpg non è riuscito a firmare i dati"
+
+#: grep.c:1320
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr "'%s': impossibile leggere %s"
+
+#: grep.c:1337
+#, c-format
+msgid "'%s': %s"
+msgstr "'%s': %s"
+
+#: grep.c:1348
+#, c-format
+msgid "'%s': short read %s"
+msgstr ""
+
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr "comandi git disponibili in '%s'"
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr "comandi git disponibili altrove nel tuo $PATH"
+
+#: help.c:270
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+"'%s' sembra essere un comando git, ma non è stato\n"
+"possibile eseguirlo. Forse git-%s è corrotto?"
+
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr "Oh oh. Il tuo sistema non riporta alcun comando Git."
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr "automaticamente tra %0.1f secondi..."
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr "git: '%s' non è un comando git. Vedi 'git --help'."
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+"\n"
+"Intendevi questo?"
+msgstr[1] ""
+"\n"
+"Intendevi uno di questi?"
+
+#: parse-options.c:493
+msgid "..."
+msgstr "..."
+
+#: parse-options.c:511
+#, c-format
+msgid "usage: %s"
+msgstr " uso: %s"
+
+#. TRANSLATORS: the colon here should align with the
+#. one in "usage: %s" translation
+#: parse-options.c:515
+#, c-format
+msgid " or: %s"
+msgstr "oppure: %s"
+
+#: parse-options.c:518
+#, c-format
+msgid " %s"
+msgstr " %s"
+
+#: remote.c:1629
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] "Il tuo branch è avanti rispetto a '%s' di %d commit.\n"
+msgstr[1] "Il tuo branch è avanti rispetto a '%s' di %d commit.\n"
+
+#: remote.c:1635
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural ""
+"Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1643
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: sequencer.c:121 builtin/merge.c:865 builtin/merge.c:978
+#: builtin/merge.c:1088 builtin/merge.c:1098
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr "Non è stato possibile aprire '%s' per la scrittura"
+
+#: sequencer.c:123 builtin/merge.c:333 builtin/merge.c:868
+#: builtin/merge.c:1090 builtin/merge.c:1103
+#, c-format
+msgid "Could not write to '%s'"
+msgstr "Non è stato possibile scrivere su '%s'"
+
+#: sequencer.c:144
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'"
+msgstr ""
+"dopo aver risolto i conflitti, segna i path corretti\n"
+"con 'git add <path>' o 'git rm <path>'"
+
+#: sequencer.c:147
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+"dopo aver risolto i conflitti, segna i path corretti\n"
+"con 'git add <path>' o 'git rm <path>' ed eseguire\n"
+"il commit del risultato con 'git commit'"
+
+#: sequencer.c:160 sequencer.c:758 sequencer.c:841
+#, c-format
+msgid "Could not write to %s"
+msgstr "Non è stato possibile scrivere su %s"
+
+#: sequencer.c:163
+#, c-format
+msgid "Error wrapping up %s"
+msgstr ""
+
+#: sequencer.c:178
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr "Le tue modifiche locali verranno sovrascritte da cherry-pick"
+
+#: sequencer.c:180
+msgid "Your local changes would be overwritten by revert."
+msgstr "Le tue modifiche locali verranno sovrascritte da revert."
+
+#: sequencer.c:183
+msgid "Commit your changes or stash them to proceed."
+msgstr ""
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:233
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr "%s: impossibile scrivere il nuovo index file"
+
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr "Non è stato possibile risolvere il commit HEAD\n"
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr ""
+
+#: sequencer.c:324
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr "Non è stato possibile analizzare il commit %s\n"
+
+#: sequencer.c:329
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr ""
+
+#: sequencer.c:395
+msgid "Your index file is unmerged."
+msgstr ""
+
+#: sequencer.c:398
+msgid "You do not have a valid HEAD"
+msgstr "Non hai un HEAD valido"
+
+#: sequencer.c:413
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr "Il commit %s è un merge ma non è stata specificata l'opzione -m."
+
+#: sequencer.c:421
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr ""
+
+#: sequencer.c:425
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr ""
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:436
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr ""
+
+#: sequencer.c:440
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr "Impossibile ottenere il messaggio di commit per %s"
+
+#: sequencer.c:524
+#, c-format
+msgid "could not revert %s... %s"
+msgstr "non è stato possibile eseguire il revert di %s... %s"
+
+#: sequencer.c:525
+#, c-format
+msgid "could not apply %s... %s"
+msgstr "non è stato possibile applicare %s... %s"
+
+#: sequencer.c:553
+msgid "empty commit set passed"
+msgstr "è stato passato un set di commit vuoto"
+
+#: sequencer.c:561
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr "git %s: lettura di index non riuscita"
+
+#: sequencer.c:566
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr "git %s: aggiornamento di index non riuscito"
+
+#: sequencer.c:624
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr ""
+
+#: sequencer.c:646
+#, c-format
+msgid "Could not parse line %d."
+msgstr "Non è stato possibile analizzare la riga %d."
+
+#: sequencer.c:651
+msgid "No commits parsed."
+msgstr "Nessun commit analizzato."
+
+#: sequencer.c:664
+#, c-format
+msgid "Could not open %s"
+msgstr "Non è stato possibile aprire %s"
+
+#: sequencer.c:668
+#, c-format
+msgid "Could not read %s."
+msgstr "Non è stato possibile leggere %s."
+
+#: sequencer.c:675
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr ""
+
+#: sequencer.c:703
+#, c-format
+msgid "Invalid key: %s"
+msgstr "Chiave non valida: %s"
+
+#: sequencer.c:706
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr "Valore non valido per %s: %s"
+
+#: sequencer.c:718
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr ""
+
+#: sequencer.c:739
+msgid "a cherry-pick or revert is already in progress"
+msgstr "è già in corso un'operazione di cherry-pick o di revert"
+
+#: sequencer.c:740
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr "prova \"git cherry-pick (--continue | --quit | -- abort)\""
+
+#: sequencer.c:744
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr ""
+
+#: sequencer.c:760 sequencer.c:845
+#, c-format
+msgid "Error wrapping up %s."
+msgstr ""
+
+#: sequencer.c:779 sequencer.c:913
+msgid "no cherry-pick or revert in progress"
+msgstr "nessuna operazione di cherry-pick o revert in corso"
+
+#: sequencer.c:781
+msgid "cannot resolve HEAD"
+msgstr "impossibile risolvere HEAD"
+
+#: sequencer.c:783
+msgid "cannot abort from a branch yet to be born"
+msgstr ""
+
+#: sequencer.c:805 builtin/apply.c:3697
+#, c-format
+msgid "cannot open %s: %s"
+msgstr "impossibile aprire %s: %s"
+
+#: sequencer.c:808
+#, c-format
+msgid "cannot read %s: %s"
+msgstr "impossibile leggere %s: %s"
+
+#: sequencer.c:809
+msgid "unexpected end of file"
+msgstr "fine del file inattesa"
+
+#: sequencer.c:815
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr ""
+
+#: sequencer.c:838
+#, c-format
+msgid "Could not format %s."
+msgstr ""
+
+#: sequencer.c:1000
+msgid "Can't revert as initial commit"
+msgstr "Impossibile eseguire il revert come commit iniziale"
+
+#: sequencer.c:1001
+msgid "Can't cherry-pick into empty head"
+msgstr ""
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "HEAD non punta ad un branch"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "Nessun branch esistente: '%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "Nessun upstream configurato per il branch '%s'"
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr ""
+
+#: wrapper.c:413
+#, c-format
+msgid "unable to look up current user in the passwd file: %s"
+msgstr "impossibile trovare l'utente corrente nel file passwd: %s"
+
+#: wrapper.c:414
+msgid "no such user"
+msgstr "utente non esistente"
+
+#: wt-status.c:135
+msgid "Unmerged paths:"
+msgstr ""
+
+#: wt-status.c:141 wt-status.c:158
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:143 wt-status.c:160
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:144
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr " (usa \"git add/rm <file>...\" come appropriato per la risoluzione)"
+
+#: wt-status.c:152
+msgid "Changes to be committed:"
+msgstr ""
+
+#: wt-status.c:170
+msgid "Changes not staged for commit:"
+msgstr ""
+
+#: wt-status.c:174
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr ""
+
+#: wt-status.c:176
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr ""
+
+#: wt-status.c:177
+msgid ""
+" (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr ""
+
+#: wt-status.c:179
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr ""
+
+#: wt-status.c:188
+#, c-format
+msgid "%s files:"
+msgstr "%s file:"
+
+#: wt-status.c:191
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr ""
+
+#: wt-status.c:208
+msgid "bug"
+msgstr "bug"
+
+#: wt-status.c:213
+msgid "both deleted:"
+msgstr "entrambi eliminati:"
+
+#: wt-status.c:214
+msgid "added by us:"
+msgstr "aggiunto da noi:"
+
+#: wt-status.c:215
+msgid "deleted by them:"
+msgstr "eliminato da loro:"
+
+#: wt-status.c:216
+msgid "added by them:"
+msgstr "aggiunto da loro:"
+
+#: wt-status.c:217
+msgid "deleted by us:"
+msgstr "eliminato da noi:"
+
+#: wt-status.c:218
+msgid "both added:"
+msgstr "entrambi aggiunti:"
+
+#: wt-status.c:219
+msgid "both modified:"
+msgstr "entrambi modificati:"
+
+#: wt-status.c:249
+msgid "new commits, "
+msgstr "nuovi commit, "
+
+#: wt-status.c:251
+msgid "modified content, "
+msgstr "contenuto modificato, "
+
+#: wt-status.c:253
+msgid "untracked content, "
+msgstr ""
+
+#: wt-status.c:267
+#, c-format
+msgid "new file: %s"
+msgstr "nuovo file: %s"
+
+#: wt-status.c:270
+#, c-format
+msgid "copied: %s -> %s"
+msgstr "copiato: %s -> %s"
+
+#: wt-status.c:273
+#, c-format
+msgid "deleted: %s"
+msgstr "eliminato: %s"
+
+#: wt-status.c:276
+#, c-format
+msgid "modified: %s"
+msgstr "modificato: %s"
+
+#: wt-status.c:279
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr "rinominato: %s -> %s"
+
+#: wt-status.c:282
+#, c-format
+msgid "typechange: %s"
+msgstr ""
+
+#: wt-status.c:285
+#, c-format
+msgid "unknown: %s"
+msgstr "sconosciuto: %s"
+
+#: wt-status.c:288
+#, c-format
+msgid "unmerged: %s"
+msgstr ""
+
+#: wt-status.c:291
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr ""
+
+#: wt-status.c:737
+msgid "On branch "
+msgstr "Sul branch "
+
+#: wt-status.c:744
+msgid "Not currently on any branch."
+msgstr ""
+
+#: wt-status.c:755
+msgid "Initial commit"
+msgstr "Commit iniziale"
+
+#: wt-status.c:769
+msgid "Untracked"
+msgstr ""
+
+#: wt-status.c:771
+msgid "Ignored"
+msgstr "Ignorato"
+
+#: wt-status.c:773
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr ""
+
+#: wt-status.c:775
+msgid " (use -u option to show untracked files)"
+msgstr ""
+
+#: wt-status.c:781
+msgid "No changes"
+msgstr "Nessuna modifica"
+
+#: wt-status.c:785
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr "nessuna modifica aggiunta al commit%s\n"
+
+#: wt-status.c:787
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr " (usa \"git add\" e/o \"git commit -a\")"
+
+#: wt-status.c:789
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr ""
+
+#: wt-status.c:791
+msgid " (use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr ""
+
+#: wt-status.c:794
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:797
+msgid " (use -u to show untracked files)"
+msgstr ""
+
+#: wt-status.c:800
+msgid " (working directory clean)"
+msgstr ""
+
+#: wt-status.c:908
+msgid "HEAD (no branch)"
+msgstr "HEAD (nessun branch)"
+
+#: wt-status.c:914
+msgid "Initial commit on "
+msgstr "Commit iniziale su "
+
+#: wt-status.c:929
+msgid "behind "
+msgstr "indietro "
+
+#: wt-status.c:932 wt-status.c:935
+msgid "ahead "
+msgstr "avanti "
+
+#: wt-status.c:937
+msgid ", behind "
+msgstr ", indietro "
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr "status diff %c inatteso"
+
+#: builtin/add.c:67 builtin/commit.c:226
+msgid "updating files failed"
+msgstr "aggiornamento dei file non riuscito"
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr "elimina '%s'\n"
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr "Il path '%s' è nel sottomodulo '%.*s'"
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr ""
+
+#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr ""
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr "'%s' si trova oltre un link simbolico"
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr "Non è stato possibile leggere index"
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr "Non è stato possibile aprire '%s' per la scrittura."
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr "Non è stato possibile scrivere la patch"
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr "Non è stato possibile eseguire lo stat di '%s'"
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr "Patch vuota. Operazione interrotta."
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr "Non è stato possibile applicare '%s'"
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr "I seguenti path sono stati ignorati da uno o più dei tuoi file .gitignore:\n"
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr "Usa -f se vuoi davvero aggiungerli.\n"
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr "nessun file aggiunto"
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr "aggiunta dei file non riuscita"
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr "-A e -u sono reciprocamente incompatibili"
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr "L'opzione --ignore-missing può essere usata solo con --dry-run"
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr ""
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr "Forse intendevi dire 'git add .'?\n"
+
+#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:286 builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr "index file corrotto"
+
+#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr "Impossibile scrivere il nuovo index file"
+
+#: builtin/apply.c:53
+msgid "git apply [options] [<patch>...]"
+msgstr "git apply [opzioni] [<patch>...]"
+
+#: builtin/apply.c:106
+#, c-format
+msgid "unrecognized whitespace option '%s'"
+msgstr ""
+
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
+msgstr ""
+
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
+msgstr ""
+
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
+msgstr "regexec ha restituito %d per l'input: %s"
+
+#: builtin/apply.c:905
+#, c-format
+msgid "unable to find filename in patch at line %d"
+msgstr "impossibile trovare il nome del file nella patch alla riga %d"
+
+#: builtin/apply.c:937
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
+msgstr "git apply: git-diff errato - atteso /dev/null, ricevuto %s alla riga %d"
+
+#: builtin/apply.c:941
+#, c-format
+msgid "git apply: bad git-diff - inconsistent new filename on line %d"
+msgstr "git apply: git-diff errato - nuovo nome del file inconsistente alla riga %d"
+
+#: builtin/apply.c:942
+#, c-format
+msgid "git apply: bad git-diff - inconsistent old filename on line %d"
+msgstr "git apply: git-diff errato - vecchio nome del file inconsistente alla riga %d"
+
+#: builtin/apply.c:949
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
+msgstr "git apply: git-diff errato - atteso /dev/nulla alla riga %d"
+
+#: builtin/apply.c:1394
+#, c-format
+msgid "recount: unexpected line: %.*s"
+msgstr "recount: riga inattesa: %.*s"
+
+#: builtin/apply.c:1451
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
+msgstr "frammento di patch senza intestazione alla riga %d: %.*s"
+
+#: builtin/apply.c:1468
+#, c-format
+msgid ""
+"git diff header lacks filename information when removing %d leading pathname "
+"component (line %d)"
+msgid_plural ""
+"git diff header lacks filename information when removing %d leading pathname "
+"components (line %d)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:1628
+msgid "new file depends on old contents"
+msgstr "il nuovo file dipende da contenuti precedenti"
+
+#: builtin/apply.c:1630
+msgid "deleted file still has contents"
+msgstr "il file eliminato ha ancora dei contenuti"
+
+#: builtin/apply.c:1656
+#, c-format
+msgid "corrupt patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:1692
+#, c-format
+msgid "new file %s depends on old contents"
+msgstr "il nuovo file %s dipende da contenuti precedenti"
+
+#: builtin/apply.c:1694
+#, c-format
+msgid "deleted file %s still has contents"
+msgstr "il file eliminato %s ha ancora dei contenuti"
+
+#: builtin/apply.c:1697
+#, c-format
+msgid "** warning: file %s becomes empty but is not deleted"
+msgstr "** attenzione: il file %s diventa vuoto ma non è eliminato"
+
+#: builtin/apply.c:1843
+#, c-format
+msgid "corrupt binary patch at line %d: %.*s"
+msgstr "patch binaria corrotta alla riga %d: %.*s"
+
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1872
+#, c-format
+msgid "unrecognized binary patch at line %d"
+msgstr "patch binaria non riconosciuta alla riga %d"
+
+#: builtin/apply.c:1958
+#, c-format
+msgid "patch with only garbage at line %d"
+msgstr ""
+
+#: builtin/apply.c:2048
+#, c-format
+msgid "unable to read symlink %s"
+msgstr "impossibile leggere il link simbolico %s"
+
+#: builtin/apply.c:2052
+#, c-format
+msgid "unable to open or read %s"
+msgstr "impossibile aprire o leggere %s"
+
+#: builtin/apply.c:2123
+msgid "oops"
+msgstr "oops"
+
+#: builtin/apply.c:2645
+#, c-format
+msgid "invalid start of line: '%c'"
+msgstr "inizio della riga non valido: '%c'"
+
+#: builtin/apply.c:2763
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:2775
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
+msgstr ""
+
+#: builtin/apply.c:2781
+#, c-format
+msgid ""
+"while searching for:\n"
+"%.*s"
+msgstr ""
+"durante la ricerca per:\n"
+"%.*s"
+
+#: builtin/apply.c:2800
+#, c-format
+msgid "missing binary patch data for '%s'"
+msgstr "dati della patch binaria mancanti per '%s'"
+
+#: builtin/apply.c:2903
+#, c-format
+msgid "binary patch does not apply to '%s'"
+msgstr "la patch binaria non può essere applicata a '%s'"
+
+#: builtin/apply.c:2909
+#, c-format
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
+msgstr "la patch binaria su '%s' crea risultati non corretti (atteso %s, ricevuto %s)"
+
+#: builtin/apply.c:2930
+#, c-format
+msgid "patch failed: %s:%ld"
+msgstr "patch non riuscita: %s:%ld"
+
+#: builtin/apply.c:3045
+#, c-format
+msgid "patch %s has been renamed/deleted"
+msgstr "la patch %s è stata rinominata/eliminata"
+
+#: builtin/apply.c:3052 builtin/apply.c:3069
+#, c-format
+msgid "read of %s failed"
+msgstr "lettura di %s non riuscita"
+
+#: builtin/apply.c:3084
+msgid "removal patch leaves file contents"
+msgstr "la rimozione della patch lascia contenuti del file"
+
+#: builtin/apply.c:3105
+#, c-format
+msgid "%s: already exists in working directory"
+msgstr "%s: esiste già nella directory di lavoro"
+
+#: builtin/apply.c:3143
+#, c-format
+msgid "%s: has been deleted/renamed"
+msgstr "%s: è stata eliminata/rinominata"
+
+#: builtin/apply.c:3148 builtin/apply.c:3179
+#, c-format
+msgid "%s: %s"
+msgstr "%s: %s"
+
+#: builtin/apply.c:3159
+#, c-format
+msgid "%s: does not exist in index"
+msgstr "%s: non esiste in index"
+
+#: builtin/apply.c:3173
+#, c-format
+msgid "%s: does not match index"
+msgstr "%s: non corrisponde a index"
+
+#: builtin/apply.c:3190
+#, c-format
+msgid "%s: wrong type"
+msgstr "%s: tipo errato"
+
+#: builtin/apply.c:3192
+#, c-format
+msgid "%s has type %o, expected %o"
+msgstr "%s ha il tipo %o, atteso %o"
+
+#: builtin/apply.c:3247
+#, c-format
+msgid "%s: already exists in index"
+msgstr "%s: esiste già in index"
+
+#: builtin/apply.c:3267
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o)"
+msgstr ""
+
+#: builtin/apply.c:3272
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o) of %s"
+msgstr ""
+
+#: builtin/apply.c:3280
+#, c-format
+msgid "%s: patch does not apply"
+msgstr "%s: la patch non può essere applicata"
+
+#: builtin/apply.c:3293
+#, c-format
+msgid "Checking patch %s..."
+msgstr "Controllo della patch %s..."
+
+#: builtin/apply.c:3348 builtin/checkout.c:212 builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr "make_cache_entry non riuscito per il path '%s'"
+
+#: builtin/apply.c:3491
+#, c-format
+msgid "unable to remove %s from index"
+msgstr "impossibile rimuovere %s da index"
+
+#: builtin/apply.c:3518
+#, c-format
+msgid "corrupt patch for subproject %s"
+msgstr "patch corrotta per il sottoprogetto %s"
+
+#: builtin/apply.c:3522
+#, c-format
+msgid "unable to stat newly created file '%s'"
+msgstr "impossibile eseguire lo stat del file appena creato '%s'"
+
+#: builtin/apply.c:3527
+#, c-format
+msgid "unable to create backing store for newly created file %s"
+msgstr ""
+
+#: builtin/apply.c:3530
+#, c-format
+msgid "unable to add cache entry for %s"
+msgstr "impossibile aggiungere la voce della cache per %s"
+
+#: builtin/apply.c:3563
+#, c-format
+msgid "closing file '%s'"
+msgstr "chiusura del file '%s'"
+
+#: builtin/apply.c:3612
+#, c-format
+msgid "unable to write file '%s' mode %o"
+msgstr "impossibile scrivere il file '%s' in modalità %o"
+
+#: builtin/apply.c:3668
+#, c-format
+msgid "Applied patch %s cleanly."
+msgstr "Patch %s applicata correttamente."
+
+#: builtin/apply.c:3676
+msgid "internal error"
+msgstr "errore interno"
+
+#. Say this even without --verbose
+#: builtin/apply.c:3679
+#, c-format
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:3689
+#, c-format
+msgid "truncating .rej filename to %.*s.rej"
+msgstr ""
+
+#: builtin/apply.c:3710
+#, c-format
+msgid "Hunk #%d applied cleanly."
+msgstr "Frammento #%d applicato correttamente."
+
+#: builtin/apply.c:3713
+#, c-format
+msgid "Rejected hunk #%d."
+msgstr "Frammento #%d respinto."
+
+#: builtin/apply.c:3844
+msgid "unrecognized input"
+msgstr "input non riconosciuto"
+
+#: builtin/apply.c:3855
+msgid "unable to read index file"
+msgstr "impossibile leggere index file"
+
+#: builtin/apply.c:3970 builtin/apply.c:3973
+msgid "path"
+msgstr "path"
+
+#: builtin/apply.c:3971
+msgid "don't apply changes matching the given path"
+msgstr "non applica le modifiche corrispondenti al path specificato"
+
+#: builtin/apply.c:3974
+msgid "apply changes matching the given path"
+msgstr "applica le modifiche corrispondenti al path specificato"
+
+#: builtin/apply.c:3976
+msgid "num"
+msgstr "num"
+
+#: builtin/apply.c:3977
+msgid "remove <num> leading slashes from traditional diff paths"
+msgstr ""
+
+#: builtin/apply.c:3980
+msgid "ignore additions made by the patch"
+msgstr "ignora le aggiunte create dalla patch"
+
+#: builtin/apply.c:3982
+msgid "instead of applying the patch, output diffstat for the input"
+msgstr "invece di applicare la patch, mostra l'output di diffstat per l'input"
+
+#: builtin/apply.c:3986
+msgid "shows number of added and deleted lines in decimal notation"
+msgstr "mostra il numero di righe aggiunte e eliminate in notazione decimale"
+
+#: builtin/apply.c:3988
+msgid "instead of applying the patch, output a summary for the input"
+msgstr "invece di applicare la patch, mostra un riassunto per l'input"
+
+#: builtin/apply.c:3990
+msgid "instead of applying the patch, see if the patch is applicable"
+msgstr "invece di applicare la patch, verifica se può essere applicata"
+
+#: builtin/apply.c:3992
+msgid "make sure the patch is applicable to the current index"
+msgstr "assicura che la patch sia applicabile all'attuale index"
+
+#: builtin/apply.c:3994
+msgid "apply a patch without touching the working tree"
+msgstr ""
+
+#: builtin/apply.c:3996
+msgid "also apply the patch (use with --stat/--summary/--check)"
+msgstr "applica anche la patch (da usare con --stat/--summary/--check)"
+
+#: builtin/apply.c:3998
+msgid "build a temporary index based on embedded index information"
+msgstr ""
+
+#: builtin/apply.c:4000
+msgid "paths are separated with NUL character"
+msgstr "i path sono separati con un carattere NUL"
+
+#: builtin/apply.c:4003
+msgid "ensure at least <n> lines of context match"
+msgstr "assicura almeno <n> righe di contesto corrispondente"
+
+#: builtin/apply.c:4004
+msgid "action"
+msgstr "azione"
+
+#: builtin/apply.c:4005
+msgid "detect new or modified lines that have whitespace errors"
+msgstr "rileva righe nuove o modificate che hanno errori di spazi bianchi"
+
+#: builtin/apply.c:4008 builtin/apply.c:4011
+msgid "ignore changes in whitespace when finding context"
+msgstr ""
+
+#: builtin/apply.c:4014
+msgid "apply the patch in reverse"
+msgstr "applica la patch in maniera inversa"
+
+#: builtin/apply.c:4016
+msgid "don't expect at least one line of context"
+msgstr ""
+
+#: builtin/apply.c:4018
+msgid "leave the rejected hunks in corresponding *.rej files"
+msgstr "lascia i frammenti respinti nei file .rej corrispondenti"
+
+#: builtin/apply.c:4020
+msgid "allow overlapping hunks"
+msgstr "consente la sovrapposizione dei frammenti"
+
+#: builtin/apply.c:4021
+msgid "be verbose"
+msgstr "dettagliato"
+
+#: builtin/apply.c:4023
+msgid "tolerate incorrectly detected missing new-line at the end of file"
+msgstr ""
+
+#: builtin/apply.c:4026
+msgid "do not trust the line counts in the hunk headers"
+msgstr ""
+
+#: builtin/apply.c:4028
+msgid "root"
+msgstr "radice"
+
+#: builtin/apply.c:4029
+msgid "prepend <root> to all filenames"
+msgstr "antepone <root> a tutti i nomi file"
+
+#: builtin/apply.c:4050
+msgid "--index outside a repository"
+msgstr "--index al di fuori di un repository"
+
+#: builtin/apply.c:4053
+msgid "--cached outside a repository"
+msgstr "--cached al di fuori di un repository"
+
+#: builtin/apply.c:4069
+#, c-format
+msgid "can't open patch '%s'"
+msgstr "impossibile aprire la patch '%s'"
+
+#: builtin/apply.c:4083
+#, c-format
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:4089 builtin/apply.c:4099
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] "%d riga aggiunge errori di spazi bianchi."
+msgstr[1] "%d righe aggiungono errori di spazi bianchi."
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr "non è stato possibile creare il file del repository '%s'"
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr "non è stato possibile redirigere l'output"
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr "git archive: Remote non ha un URL"
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr "git archive: atteso ACK/NAK, ricevuto EOF"
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr "git archive: NACK %s"
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr "errore remoto: %s"
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr "git archive: errore del protocollo"
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr "git archive: atteso un flush"
+
+#: builtin/branch.c:144
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+
+#: builtin/branch.c:148
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+
+#: builtin/branch.c:180
+msgid "cannot use -a with -d"
+msgstr "impossibile usare -a con -d"
+
+#: builtin/branch.c:186
+msgid "Couldn't look up commit object for HEAD"
+msgstr ""
+
+#: builtin/branch.c:191
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr ""
+
+#: builtin/branch.c:202
+#, c-format
+msgid "remote branch '%s' not found."
+msgstr "il branch remoto '%s' non è stato trovato."
+
+#: builtin/branch.c:203
+#, c-format
+msgid "branch '%s' not found."
+msgstr "branch '%s' non trovato."
+
+#: builtin/branch.c:210
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr ""
+
+#: builtin/branch.c:216
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+
+#: builtin/branch.c:225
+#, c-format
+msgid "Error deleting remote branch '%s'"
+msgstr "Errore nella rimozione del branch remoto '%s'"
+
+#: builtin/branch.c:226
+#, c-format
+msgid "Error deleting branch '%s'"
+msgstr "Errore nella rimozione del branch '%s'"
+
+#: builtin/branch.c:233
+#, c-format
+msgid "Deleted remote branch %s (was %s).\n"
+msgstr "Ramo remoto %s eliminato (era %s).\n"
+
+#: builtin/branch.c:234
+#, c-format
+msgid "Deleted branch %s (was %s).\n"
+msgstr "Ramo %s eliminato (era %s).\n"
+
+#: builtin/branch.c:239
+msgid "Update of config-file failed"
+msgstr "Aggiornamento del file di configurazione fallito"
+
+#: builtin/branch.c:337
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr "il branch '%s' non punta ad un commit"
+
+#: builtin/branch.c:409
+#, c-format
+msgid "[%s: behind %d]"
+msgstr "[%s: dietro %d]"
+
+#: builtin/branch.c:411
+#, c-format
+msgid "[behind %d]"
+msgstr "[dietro %d]"
+
+#: builtin/branch.c:415
+#, c-format
+msgid "[%s: ahead %d]"
+msgstr "[%s: avanti %d]"
+
+#: builtin/branch.c:417
+#, c-format
+msgid "[ahead %d]"
+msgstr ""
+
+#: builtin/branch.c:420
+#, c-format
+msgid "[%s: ahead %d, behind %d]"
+msgstr ""
+
+#: builtin/branch.c:423
+#, c-format
+msgid "[ahead %d, behind %d]"
+msgstr ""
+
+#: builtin/branch.c:535
+msgid "(no branch)"
+msgstr "(nessun branch)"
+
+#: builtin/branch.c:600
+msgid "some refs could not be read"
+msgstr ""
+
+#: builtin/branch.c:613
+msgid "cannot rename the current branch while not on any."
+msgstr ""
+
+#: builtin/branch.c:623
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr "Nome del branch non valido: '%s'"
+
+#: builtin/branch.c:638
+msgid "Branch rename failed"
+msgstr ""
+
+#: builtin/branch.c:642
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr ""
+
+#: builtin/branch.c:646
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr "Ramo rinominato in %s, ma HEAD non è aggiornato!"
+
+#: builtin/branch.c:653
+msgid "Branch is renamed, but update of config-file failed"
+msgstr ""
+"Il branch è stato rinominato, ma l'aggiornamento del file di configurazione "
+"è fallito"
+
+#: builtin/branch.c:668
+#, c-format
+msgid "malformed object name %s"
+msgstr "nome dell'oggetto %s errato"
+
+#: builtin/branch.c:692
+#, c-format
+msgid "could not write branch description template: %s"
+msgstr ""
+
+#: builtin/branch.c:783
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr ""
+
+#: builtin/branch.c:788 builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr ""
+
+#: builtin/branch.c:808
+msgid "--column and --verbose are incompatible"
+msgstr "--column e --verbose non sono compatibili"
+
+#: builtin/branch.c:857
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr ""
+"le opzioni -a e -r per 'git branch' non hanno senso con il nome di un branch"
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr "%s è corretto\n"
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr ""
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr ""
+
+#: builtin/checkout.c:113 builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr "il path '%s' non ha la nostra versione"
+
+#: builtin/checkout.c:115 builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr "il path '%s' non ha la loro versione"
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr "il path '%s' non ha le versioni necessarie"
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr ""
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr ""
+
+#: builtin/checkout.c:234 builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr "file index corrotto"
+
+#: builtin/checkout.c:264 builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr ""
+
+#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
+#: builtin/merge.c:812
+msgid "unable to write new index file"
+msgstr "impossibile scrivere il nuovo file index"
+
+#: builtin/checkout.c:319 builtin/diff.c:302 builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr "diff_setup_done non riuscito"
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr "è necessario risolvere prima l'attuale index"
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:566
+msgid "HEAD is now at"
+msgstr "HEAD si trova ora a"
+
+#: builtin/checkout.c:573
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr "Ripristina il branch '%s'\n"
+
+#: builtin/checkout.c:576
+#, c-format
+msgid "Already on '%s'\n"
+msgstr "Si è già su '%s'\n"
+
+#: builtin/checkout.c:580
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:582
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:584
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr "Si è passati al branch '%s'\n"
+
+#: builtin/checkout.c:640
+#, c-format
+msgid " ... and %d more.\n"
+msgstr " ... e %d altri.\n"
+
+#. The singular version
+#: builtin/checkout.c:646
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/checkout.c:664
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+"Se si vuole mantenerle creando un nuovo branch, questo potrebbe essere\n"
+"un buon momento per farlo con:\n"
+"\n"
+" git branch nuovo_nome_branch %s\n"
+"\n"
+
+#: builtin/checkout.c:694
+msgid "internal error in revision walk"
+msgstr ""
+
+#: builtin/checkout.c:698
+msgid "Previous HEAD position was"
+msgstr "La precedente posizione di HEAD era"
+
+#: builtin/checkout.c:724
+msgid "You are on a branch yet to be born"
+msgstr ""
+
+#. case (1)
+#: builtin/checkout.c:855
+#, c-format
+msgid "invalid reference: %s"
+msgstr "riferimento non valido: %s"
+
+#. case (1): want a tree
+#: builtin/checkout.c:894
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr ""
+
+#: builtin/checkout.c:974
+msgid "-B cannot be used with -b"
+msgstr "-B non può essere usata con -b"
+
+#: builtin/checkout.c:983
+msgid "--patch is incompatible with all other options"
+msgstr "--patch non è compatibile con tutte le altre opzioni"
+
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr "--detach non può essere usata con -b/-B/--orphan"
+
+#: builtin/checkout.c:988
+msgid "--detach cannot be used with -t"
+msgstr "--detach non può essere usata con -t"
+
+#: builtin/checkout.c:994
+msgid "--track needs a branch name"
+msgstr "--track necessita del nome di un branch"
+
+#: builtin/checkout.c:1001
+msgid "Missing branch name; try -b"
+msgstr "Nome del branch mancante; prova con -b"
+
+#: builtin/checkout.c:1007
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr ""
+
+#: builtin/checkout.c:1009
+msgid "--orphan cannot be used with -t"
+msgstr "--orphan non può essere usata con -t"
+
+#: builtin/checkout.c:1019
+msgid "git checkout: -f and -m are incompatible"
+msgstr "git checkout: -f e -m non sono compatibili"
+
+#: builtin/checkout.c:1053
+msgid "invalid path specification"
+msgstr ""
+
+#: builtin/checkout.c:1061
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+
+#: builtin/checkout.c:1063
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr ""
+
+#: builtin/checkout.c:1068
+msgid "git checkout: --detach does not take a path argument"
+msgstr "git checkout: --detach non prende un path come argomento"
+
+#: builtin/checkout.c:1071
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+
+#: builtin/checkout.c:1090
+msgid "Cannot switch branch to a non-commit."
+msgstr ""
+
+#: builtin/checkout.c:1093
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr "--ours/--theirs non sono compatibili con il passaggio ai branch."
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr "-x e -X non possono essere usati insieme"
+
+#: builtin/clean.c:82
+msgid ""
+"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+"clean.requireForce è impostato a vero, ma né -n né -f sono state specificate; "
+"clean interrotto"
+
+#: builtin/clean.c:85
+msgid ""
+"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
+"clean"
+msgstr ""
+"clean.requireForce è vero per default, ma né -n né -f sono state specificate; "
+"clean interrotto"
+
+#: builtin/clean.c:155 builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:159 builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr "Rimozione di %s\n"
+
+#: builtin/clean.c:162 builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr "rimozione di %s non riuscita"
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr ""
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr "il repository di riferimento '%s' non è una directory locale."
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr "apertura di '%s' non riuscita"
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr "creazione della directory '%s' non riuscita"
+
+#: builtin/clone.c:308 builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr "stat di '%s' non riuscito"
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr "%s esiste e non è una directory"
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr "stat di %s non riuscito\n"
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr "rimozione del link '%s' non riuscita"
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr "creazione del link '%s' non riuscita"
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr "copia del file in '%s' non riuscita"
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr "fatto.\n"
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr "Non è stato possibile trovare il branch remoto %s da clonare."
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr ""
+"l'HEAD remoto si riferisce ad un ref inesistente, impossibile eseguire il "
+"checkout.\n"
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr "Troppi argomenti."
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr "Devi specificare un repository da clonare."
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr "le opzioni --bare e --origin %s non sono compatibili."
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr "il repository '%s' non esiste"
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr ""
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr ""
+"il path di destinazione '%s' esiste già e non è una directory vuota."
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr ""
+
+#: builtin/clone.c:706 builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr ""
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr ""
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr "Clone nel repository spoglio '%s'...\n"
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr ""
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr "Non so come clonare %s"
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr "Il branch remoto %s non è stato trovato in upstream %s"
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr "Sembra che tu abbia clonato un repository vuoto."
+
+#: builtin/column.c:51
+msgid "--command must be the first argument"
+msgstr "--command deve essere il primo argomento"
+
+#: builtin/commit.c:43
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+"Il tuo nome e l'indirizzo email sono stati configurati automaticamente usando\n"
+"il tuo nome utente ed il nome host. Per favore, verifica che siano esatti.\n"
+"È possibile eliminare questo messaggio impostandoli esplicitamente:\n"
+"\n"
+" git config --global user.name \"Tuo Nome\"\n"
+" git config --global user.email tu@esempio.com\n"
+"\n"
+"Dopo questa operazione, puoi ripristinare l'identità usata in questo commit con:\n"
+"\n"
+" git commit --amend --reset-author\n"
+
+#: builtin/commit.c:55
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+
+#: builtin/commit.c:60
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+
+#: builtin/commit.c:253
+msgid "failed to unpack HEAD tree object"
+msgstr ""
+
+#: builtin/commit.c:295
+msgid "unable to create temporary index"
+msgstr ""
+
+#: builtin/commit.c:301
+msgid "interactive add failed"
+msgstr "add interattivo non riuscito"
+
+#: builtin/commit.c:334 builtin/commit.c:355 builtin/commit.c:405
+msgid "unable to write new_index file"
+msgstr "impossibile scrivere il file new_index"
+
+#: builtin/commit.c:386
+msgid "cannot do a partial commit during a merge."
+msgstr "impossibile eseguire un commit parziale durante un merge."
+
+#: builtin/commit.c:388
+msgid "cannot do a partial commit during a cherry-pick."
+msgstr "impossibile eseguire un commit parziale durante un cherry-pick."
+
+#: builtin/commit.c:398
+msgid "cannot read the index"
+msgstr "impossibile leggere index"
+
+#: builtin/commit.c:418
+msgid "unable to write temporary index file"
+msgstr ""
+
+#: builtin/commit.c:493 builtin/commit.c:499
+#, c-format
+msgid "invalid commit: %s"
+msgstr "commit non valido: %s"
+
+#: builtin/commit.c:522
+msgid "malformed --author parameter"
+msgstr "parametro --author malformato"
+
+#: builtin/commit.c:582
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr ""
+
+#: builtin/commit.c:620 builtin/commit.c:653 builtin/commit.c:967
+#, c-format
+msgid "could not lookup commit %s"
+msgstr "non è stato possibile trovare il commit %s"
+
+#: builtin/commit.c:632 builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr "(lettura del messaggio di log dallo standard input)\n"
+
+#: builtin/commit.c:634
+msgid "could not read log from standard input"
+msgstr "non è stato possibile leggere il log dallo standard input"
+
+#: builtin/commit.c:638
+#, c-format
+msgid "could not read log file '%s'"
+msgstr "non è stato possibile leggere il file di log '%s'"
+
+#: builtin/commit.c:644
+msgid "commit has empty message"
+msgstr "il commit ha un messaggio vuoto"
+
+#: builtin/commit.c:660
+msgid "could not read MERGE_MSG"
+msgstr "non è stato possibile leggere MERGE_MSG"
+
+#: builtin/commit.c:664
+msgid "could not read SQUASH_MSG"
+msgstr "non è stato possibile leggere SQUASH_MSG"
+
+#: builtin/commit.c:668
+#, c-format
+msgid "could not read '%s'"
+msgstr "non è stato possibile leggere '%s'"
+
+#: builtin/commit.c:720
+msgid "could not write commit template"
+msgstr "non è stato possibile scrivere il modello di commit"
+
+#: builtin/commit.c:731
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a merge.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"Sembra che si stia eseguendo il commit di un merge.\n"
+"Se l'operazione non è corretta, per favore elimina il file\n"
+"\t%s\n"
+"e riprova.\n"
+
+#: builtin/commit.c:736
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"Sembra che si stia eseguendo il commit di un cherry-pick.\n"
+"Se l'operazione non è corretta, per favore rimuovi il file\n"
+"\t%s\n"
+"e riprova.\n"
+
+#: builtin/commit.c:748
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+"Per favore inserisci il messaggio di commit per le modifiche. Le righe\n"
+"che iniziano con '#' verranno ignorate, e un messaggio vuoto annulla il commit.\n"
+
+#: builtin/commit.c:753
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+"Per favore inserisci il messaggio di commit per le modifiche. Le\n"
+"righe che iniziano con '#' verranno mantenute; possono comunque essere\n"
+"rimosse manualmente. Un messaggio vuoto annulla il commit.\n"
+
+#: builtin/commit.c:766
+#, c-format
+msgid "%sAuthor: %s"
+msgstr "%sAutore: %s"
+
+#: builtin/commit.c:773
+#, c-format
+msgid "%sCommitter: %s"
+msgstr "%sCommitter: %s"
+
+#: builtin/commit.c:793
+msgid "Cannot read index"
+msgstr "Impossibile leggere index"
+
+#: builtin/commit.c:830
+msgid "Error building trees"
+msgstr ""
+
+#: builtin/commit.c:845 builtin/tag.c:361
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr "Per favore, specifica il messaggio usando l'opzione -m o -F.\n"
+
+#: builtin/commit.c:942
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr "Nessun autore esistente trovato con '%s'"
+
+#: builtin/commit.c:957 builtin/commit.c:1157
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr ""
+
+#: builtin/commit.c:997
+msgid "Using both --reset-author and --author does not make sense"
+msgstr "L'uso di entrambe le opzioni --reset-author e --author non ha senso"
+
+#: builtin/commit.c:1008
+msgid "You have nothing to amend."
+msgstr ""
+
+#: builtin/commit.c:1011
+msgid "You are in the middle of a merge -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1013
+msgid "You are in the middle of a cherry-pick -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1016
+msgid "Options --squash and --fixup cannot be used together"
+msgstr "Le opzioni --squash e --fixup non possono essere usate insieme"
+
+#: builtin/commit.c:1026
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr "Solo una delle opzioni -c/-C/-F/--fixup può essere usata."
+
+#: builtin/commit.c:1028
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr "L'opzione -m non può essere combinata con -c/-C/-F/--fixup."
+
+#: builtin/commit.c:1036
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr "L'opzione --reset-author può essere usata solo con -C, -c o --amend."
+
+#: builtin/commit.c:1053
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr ""
+"Può essere usata solo una delle opzioni --include/--only/--all/--"
+"interactive/--patch ."
+
+#: builtin/commit.c:1055
+msgid "No paths with --include/--only does not make sense."
+msgstr "Devi specificare un path se usi --include/--only."
+
+#: builtin/commit.c:1057
+msgid "Clever... amending the last one with dirty index."
+msgstr ""
+
+#: builtin/commit.c:1059
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr ""
+
+#: builtin/commit.c:1069 builtin/tag.c:577
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr ""
+
+#: builtin/commit.c:1074
+msgid "Paths with -a does not make sense."
+msgstr "I path con -a non hanno senso."
+
+#: builtin/commit.c:1257
+msgid "couldn't look up newly created commit"
+msgstr "non è stato possibile trovare il commit appena creato"
+
+#: builtin/commit.c:1259
+msgid "could not parse newly created commit"
+msgstr "non è stato possibile analizzare il commit appena creato"
+
+#: builtin/commit.c:1300
+msgid "detached HEAD"
+msgstr ""
+
+#: builtin/commit.c:1302
+msgid " (root-commit)"
+msgstr " (root-commit)"
+
+#: builtin/commit.c:1446
+msgid "could not parse HEAD commit"
+msgstr "non è stato possibile analizzare il commit HEAD"
+
+#: builtin/commit.c:1484 builtin/merge.c:509
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr "non è stato possibile aprire '%s' per la lettura"
+
+#: builtin/commit.c:1491
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr "File MERGE_HEAD corrotto (%s)"
+
+#: builtin/commit.c:1498
+msgid "could not read MERGE_MODE"
+msgstr "non è stato possibile leggere MERGE_MODE"
+
+#: builtin/commit.c:1517
+#, c-format
+msgid "could not read commit message: %s"
+msgstr "non è stato possibile leggere il messaggio di commit: %s"
+
+#: builtin/commit.c:1531
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
+msgstr "Commit interrotto; il messaggio non è stato modificato.\n"
+
+#: builtin/commit.c:1536
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr "Interruzione del commit a causa di un messaggio di commit vuoto.\n"
+
+#: builtin/commit.c:1551 builtin/merge.c:936 builtin/merge.c:961
+msgid "failed to write commit object"
+msgstr "scrittura dell'oggetto di commit non riuscita"
+
+#: builtin/commit.c:1572
+msgid "cannot lock HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1576
+msgid "cannot update HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1587
+msgid ""
+"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."
+msgstr ""
+"Il repository è stato aggiornato, ma non è stato possibile scrivere il file\n"
+"new_index. Verifica che l'unità disco non sia piena o che la quota non sia\n"
+"stata superata, ed esegui \"git reset HEAD\" per il ripristino."
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr ""
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr ""
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr "il tag '%s' è davvero '%s' qui"
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr "Non è il nome di un oggetto valido %s"
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr "%s non è un oggetto '%s' valido"
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr "nessun tag corrisponde esattamente a '%s'"
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr ""
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+"Nessun tag può descrivere '%s'.\n"
+"Prova con --always, o crea dei tag."
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr ""
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr "--long non è compatibile con --abbrev=0"
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr ""
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr ""
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr "'%s': non è un file regolare o un link simbolico"
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr "opzione non valida: %s"
+
+#: builtin/diff.c:297
+msgid "Not a git repository"
+msgstr "Non è un repository git"
+
+#: builtin/diff.c:347
+#, c-format
+msgid "invalid object '%s' given."
+msgstr "oggetto non valido '%s' specificato."
+
+#: builtin/diff.c:352
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:362
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr "più di due blob specificati: '%s'"
+
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr "oggetto non gestito '%s' specificato."
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr ""
+
+#: builtin/fetch.c:253
+#, c-format
+msgid "object %s not found"
+msgstr "oggetto %s non trovato"
+
+#: builtin/fetch.c:259
+msgid "[up to date]"
+msgstr "[aggiornato]"
+
+#: builtin/fetch.c:273
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr ""
+
+#: builtin/fetch.c:274 builtin/fetch.c:360
+msgid "[rejected]"
+msgstr "[respinto]"
+
+#: builtin/fetch.c:285
+msgid "[tag update]"
+msgstr "[tag aggiornata]"
+
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
+msgid " (unable to update local ref)"
+msgstr " (impossibile aggiornare il ref locale)"
+
+#: builtin/fetch.c:305
+msgid "[new tag]"
+msgstr "[nuova tag]"
+
+#: builtin/fetch.c:308
+msgid "[new branch]"
+msgstr "[nuovo branch]"
+
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[nuovo ref]"
+
+#: builtin/fetch.c:356
+msgid "unable to update local ref"
+msgstr "impossibile aggiornare il ref locale"
+
+#: builtin/fetch.c:356
+msgid "forced update"
+msgstr "aggiornamento forzato"
+
+#: builtin/fetch.c:362
+msgid "(non-fast-forward)"
+msgstr ""
+
+#: builtin/fetch.c:393 builtin/fetch.c:685
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr "impossibile aprire %s: %s\n"
+
+#: builtin/fetch.c:402
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr "%s non ha inviato tutti gli oggetti necessari\n"
+
+#: builtin/fetch.c:488
+#, c-format
+msgid "From %.*s\n"
+msgstr "Da %.*s\n"
+
+#: builtin/fetch.c:499
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+"non è stato possibile aggiornare alcuni ref locali; prova con\n"
+" 'git remote prune %s' per rimuovere ogni branch che vada in conflitto"
+
+#: builtin/fetch.c:549
+#, c-format
+msgid " (%s will become dangling)"
+msgstr ""
+
+#: builtin/fetch.c:550
+#, c-format
+msgid " (%s has become dangling)"
+msgstr ""
+
+#: builtin/fetch.c:557
+msgid "[deleted]"
+msgstr "[eliminato]"
+
+#: builtin/fetch.c:558 builtin/remote.c:1055
+msgid "(none)"
+msgstr "(nessuno)"
+
+#: builtin/fetch.c:675
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr ""
+
+#: builtin/fetch.c:709
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr ""
+
+#: builtin/fetch.c:786
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr ""
+
+#: builtin/fetch.c:789
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr "L'opzione \"%s\" è ignorata per %s\n"
+
+#: builtin/fetch.c:888
+#, c-format
+msgid "Fetching %s\n"
+msgstr ""
+
+#: builtin/fetch.c:890 builtin/remote.c:100
+#, c-format
+msgid "Could not fetch %s"
+msgstr ""
+
+#: builtin/fetch.c:907
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr ""
+"Nessun repository remoto specificato. Per favore, specifica un URL o\n"
+"il nome di un remote da cui prelevare nuove revisioni."
+
+#: builtin/fetch.c:927
+msgid "You need to specify a tag name."
+msgstr "Devi specificare il nome di un tag."
+
+#: builtin/fetch.c:979
+msgid "fetch --all does not take a repository argument"
+msgstr "fetch --all non richiede il repository come argomento"
+
+#: builtin/fetch.c:981
+msgid "fetch --all does not make sense with refspecs"
+msgstr ""
+
+#: builtin/fetch.c:992
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr ""
+
+#: builtin/fetch.c:1000
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr ""
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr "%s non valido: '%s'"
+
+#: builtin/gc.c:90
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr ""
+
+#: builtin/gc.c:221
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr ""
+
+#: builtin/gc.c:224
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+
+#: builtin/gc.c:251
+msgid ""
+"There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr ""
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr "grep: creazione del thread non riuscita: %s"
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr ""
+
+#: builtin/grep.c:478 builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr "impossibile leggere il tree (%s)"
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr ""
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr "switch '%c' richiede un valore numerico"
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr "impossibile aprire '%s'"
+
+#: builtin/grep.c:885
+msgid "no pattern given."
+msgstr "nessun modello specificato."
+
+#: builtin/grep.c:899
+#, c-format
+msgid "bad object %s"
+msgstr "oggetto %s errato"
+
+#: builtin/grep.c:940
+msgid "--open-files-in-pager only works on the worktree"
+msgstr ""
+
+#: builtin/grep.c:963
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr "--cached o --untracked non può essere usato con --no-index."
+
+#: builtin/grep.c:968
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr "--no-index o --untracked non possono essere usate con le revisioni."
+
+#: builtin/grep.c:971
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr ""
+
+#: builtin/grep.c:979
+msgid "both --cached and trees are given."
+msgstr ""
+
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr "formato di aiuto '%s' non riconosciuto"
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr "Esecuzione di emacsclient non riuscita."
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr "Verifica della versione di emacsclient non riuscita."
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr "la versione '%d' di emacsclient è troppo vecchia (<22)."
+
+#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr "esecuzione di '%s' non riuscita: %s"
+
+#: builtin/help.c:211
+#, c-format
+msgid ""
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
+msgstr ""
+"'%s': path ad un visualizzatore man pages non supportato.\n"
+"Usa invece 'man.<tool>.cmd'."
+
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
+msgstr ""
+"'%s': comando per visualizzatore man pages supportato.\n"
+"Per favore usa 'man.<tool>.path' invece."
+
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr "I comandi git usati più di frequente sono:"
+
+#: builtin/help.c:355
+#, c-format
+msgid "'%s': unknown man viewer."
+msgstr "'%s': visualizzatore man sconosciuto."
+
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr "nessun visualizzatore man ha gestito la richiesta"
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr "nessun visualizzatore info ha gestito la richiesta"
+
+#: builtin/help.c:391
+#, c-format
+msgid "'%s': not a documentation directory."
+msgstr "'%s': non è una directory della documentazione."
+
+#: builtin/help.c:432 builtin/help.c:439
+#, c-format
+msgid "usage: %s%s"
+msgstr "uso: %s%s"
+
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
+msgstr "'git %s è un alias di '%s'"
+
+#: builtin/index-pack.c:169
+#, c-format
+msgid "object type mismatch at %s"
+msgstr ""
+
+#: builtin/index-pack.c:189
+msgid "object of unexpected type"
+msgstr ""
+
+#: builtin/index-pack.c:226
+#, c-format
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:236
+msgid "early EOF"
+msgstr "EOF prematuro"
+
+#: builtin/index-pack.c:237
+msgid "read error on input"
+msgstr "errore di lettura in input"
+
+#: builtin/index-pack.c:249
+msgid "used more bytes than were available"
+msgstr "usati più byte di quelli disponibili"
+
+#: builtin/index-pack.c:256
+msgid "pack too large for current definition of off_t"
+msgstr "pack troppo largo per la definizione corrente di off_t"
+
+#: builtin/index-pack.c:272
+#, c-format
+msgid "unable to create '%s'"
+msgstr "impossibile creare '%s'"
+
+#: builtin/index-pack.c:277
+#, c-format
+msgid "cannot open packfile '%s'"
+msgstr "impossibile aprire il file pack '%s'"
+
+#: builtin/index-pack.c:291
+msgid "pack signature mismatch"
+msgstr "la firma del pack non coincide"
+
+#: builtin/index-pack.c:311
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
+msgstr ""
+
+#: builtin/index-pack.c:405
+#, c-format
+msgid "inflate returned %d"
+msgstr ""
+
+#: builtin/index-pack.c:450
+msgid "offset value overflow for delta base object"
+msgstr ""
+
+#: builtin/index-pack.c:458
+msgid "delta base offset is out of bound"
+msgstr ""
+
+#: builtin/index-pack.c:466
+#, c-format
+msgid "unknown object type %d"
+msgstr "tipo di oggetto %d sconosciuto"
+
+#: builtin/index-pack.c:495
+msgid "cannot pread pack file"
+msgstr ""
+
+#: builtin/index-pack.c:497
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:510
+msgid "serious inflate inconsistency"
+msgstr ""
+
+#: builtin/index-pack.c:583
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "non è possibile leggere l'oggetto %s esistente"
+
+#: builtin/index-pack.c:586
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
+msgstr "TROVATA COLLISIONE SHA1 CON %s !"
+
+#: builtin/index-pack.c:598
+#, c-format
+msgid "invalid blob object %s"
+msgstr "oggetto blob %s non valido"
+
+#: builtin/index-pack.c:610
+#, c-format
+msgid "invalid %s"
+msgstr "%s non valido"
+
+#: builtin/index-pack.c:612
+msgid "Error in object"
+msgstr "Errore nell'oggetto"
+
+#: builtin/index-pack.c:614
+#, c-format
+msgid "Not all child objects of %s are reachable"
+msgstr "Non tutti gli oggetti figlio di %s sono raggiungibili"
+
+#: builtin/index-pack.c:687 builtin/index-pack.c:713
+msgid "failed to apply delta"
+msgstr "applicazione del delta non riuscita"
+
+#: builtin/index-pack.c:850
+msgid "Receiving objects"
+msgstr "Ricezione degli oggetti"
+
+#: builtin/index-pack.c:850
+msgid "Indexing objects"
+msgstr "Indicizzazione degli oggetti"
+
+#: builtin/index-pack.c:872
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr "il pack è corrotto (SHA1 non corrisponde)"
+
+#: builtin/index-pack.c:877
+msgid "cannot fstat packfile"
+msgstr ""
+
+#: builtin/index-pack.c:880
+msgid "pack has junk at the end"
+msgstr ""
+
+#: builtin/index-pack.c:903
+msgid "Resolving deltas"
+msgstr "Risoluzione dei delta"
+
+#: builtin/index-pack.c:954
+msgid "confusion beyond insanity"
+msgstr "confusione al di là della follia"
+
+#: builtin/index-pack.c:973
+#, c-format
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] "pack ha %d delta irrisolto"
+msgstr[1] "pack ha %d delta irrisolti"
+
+#: builtin/index-pack.c:998
+#, c-format
+msgid "unable to deflate appended object (%d)"
+msgstr ""
+
+#: builtin/index-pack.c:1077
+#, c-format
+msgid "local object %s is corrupt"
+msgstr "l'oggetto locale %s è corrotto"
+
+#: builtin/index-pack.c:1101
+msgid "error while closing pack file"
+msgstr "errore nella chiusura del file pack"
+
+#: builtin/index-pack.c:1114
+#, c-format
+msgid "cannot write keep file '%s'"
+msgstr "impossibile scrivere il file keep '%s'"
+
+#: builtin/index-pack.c:1122
+#, c-format
+msgid "cannot close written keep file '%s'"
+msgstr ""
+
+#: builtin/index-pack.c:1135
+msgid "cannot store pack file"
+msgstr "impossibile archiviare il file pack"
+
+#: builtin/index-pack.c:1146
+msgid "cannot store index file"
+msgstr "impossibile archiviare index file"
+
+#: builtin/index-pack.c:1247
+#, c-format
+msgid "Cannot open existing pack file '%s'"
+msgstr "Impossibile aprire il file pack '%s' esistente"
+
+#: builtin/index-pack.c:1249
+#, c-format
+msgid "Cannot open existing pack idx file for '%s'"
+msgstr ""
+
+#: builtin/index-pack.c:1296
+#, c-format
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] "non delta: %d oggetto"
+msgstr[1] "non delta: %d oggetti"
+
+#: builtin/index-pack.c:1303
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] "lunghezza della catena = %d: %lu oggetto"
+msgstr[1] "lunghezza della catena = %d: %lu oggetti"
+
+#: builtin/index-pack.c:1330
+msgid "Cannot come back to cwd"
+msgstr ""
+
+#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
+#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#, c-format
+msgid "bad %s"
+msgstr "%s errato"
+
+#: builtin/index-pack.c:1407
+msgid "--fix-thin cannot be used without --stdin"
+msgstr "--fix-thin non può essere usato senza --stdin"
+
+#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr "il nome del file pack '%s' non termina con '.pack'"
+
+#: builtin/index-pack.c:1430
+msgid "--verify with no packfile name given"
+msgstr "--verify senza un nome del file pack specificato"
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr "Non è stato possible rendere %s scrivibile dal gruppo"
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr ""
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr "impossibile eseguire lo stat di '%s'"
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr ""
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr ""
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr ""
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr ""
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr ""
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr ""
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr ""
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr ""
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr ""
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr ""
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr ""
+
+#: builtin/init-db.c:322 builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr "%s esiste già"
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr "impossibile gestire il tipo di file %d"
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr "impossibile spostare %s in %s"
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr "Non è stato possibile creare il link git %s"
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s repository Git in %s%s\n"
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr "Reinizializzato un esistente"
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr "Inizializzato un"
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr " condiviso"
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr ""
+
+#: builtin/init-db.c:521 builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr ""
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr ""
+
+#: builtin/init-db.c:554
+#, c-format
+msgid ""
+"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
+"dir=<directory>)"
+msgstr ""
+"%s (o --work-tree=<directory>) non consentito senza specificare %s (o --git-"
+"dir=<directory>)"
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr "Impossibile accedere alla directory di lavoro corrente"
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr ""
+
+#: builtin/log.c:188
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr "Output finale: %d %s\n"
+
+#: builtin/log.c:401 builtin/log.c:489
+#, c-format
+msgid "Could not read object %s"
+msgstr "Non è stato possibile leggere l'oggetto %s"
+
+#: builtin/log.c:513
+#, c-format
+msgid "Unknown type: %d"
+msgstr "Tipo sconosciuto: %d"
+
+#: builtin/log.c:602
+msgid "format.headers without value"
+msgstr "format.headers non ha alcun valore"
+
+#: builtin/log.c:676
+msgid "name of output directory is too long"
+msgstr "il nome della directory di output è troppo lungo"
+
+#: builtin/log.c:687
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr "Impossibile aprire il file patch %s"
+
+#: builtin/log.c:701
+msgid "Need exactly one range."
+msgstr ""
+
+#: builtin/log.c:709
+msgid "Not a range."
+msgstr ""
+
+#: builtin/log.c:786
+msgid "Cover letter needs email format"
+msgstr ""
+
+#: builtin/log.c:859
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr ""
+
+#: builtin/log.c:932
+msgid "Two output directories?"
+msgstr "Due directory di output?"
+
+#: builtin/log.c:1153
+#, c-format
+msgid "bogus committer info %s"
+msgstr ""
+
+#: builtin/log.c:1198
+msgid "-n and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1200
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1208
+msgid "--name-only does not make sense"
+msgstr "--name-only non ha senso"
+
+#: builtin/log.c:1210
+msgid "--name-status does not make sense"
+msgstr "--name-status non ha senso"
+
+#: builtin/log.c:1212
+msgid "--check does not make sense"
+msgstr "--check non ha senso"
+
+#: builtin/log.c:1235
+msgid "standard output, or directory, which one?"
+msgstr "standard output, o directory, quale dei due?"
+
+#: builtin/log.c:1237
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr "Non è stato possibile creare la directory '%s'"
+
+#: builtin/log.c:1390
+msgid "Failed to create output files"
+msgstr "Creazione dei file di output non riuscita"
+
+#: builtin/log.c:1494
+#, c-format
+msgid ""
+"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr ""
+
+#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#, c-format
+msgid "Unknown commit %s"
+msgstr "Commit %s sconosciuto"
+
+#: builtin/merge.c:90
+msgid "switch `m' requires a value"
+msgstr "lo switch 'm' richiede un valore"
+
+#: builtin/merge.c:127
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr "Non è stato possibile trovare la strategia di merge '%s'.\n"
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Available strategies are:"
+msgstr "Le strategie disponibili sono:"
+
+#: builtin/merge.c:133
+#, c-format
+msgid "Available custom strategies are:"
+msgstr "Le strategie personalizzate disponibili sono:"
+
+#: builtin/merge.c:240
+msgid "could not run stash."
+msgstr "non è stato possibile eseguire stash."
+
+#: builtin/merge.c:245
+msgid "stash failed"
+msgstr "esecuzione di stash non riuscita"
+
+#: builtin/merge.c:250
+#, c-format
+msgid "not a valid object: %s"
+msgstr "non è un oggetto valido: %s"
+
+#: builtin/merge.c:269 builtin/merge.c:286
+msgid "read-tree failed"
+msgstr "read-tree non riuscito"
+
+#: builtin/merge.c:316
+msgid " (nothing to squash)"
+msgstr ""
+
+#: builtin/merge.c:329
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:361
+msgid "Writing SQUASH_MSG"
+msgstr "Scrittura di SQUASH_MSG"
+
+#: builtin/merge.c:363
+msgid "Finishing SQUASH_MSG"
+msgstr "Completamento di SQUASH_MSG"
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr "Nessun messaggio di merge -- HEAD non viene aggiornato\n"
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr "'%s' non punta ad un commit"
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr "Stringa branch.%s.mergeoptions errata: %s"
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr ""
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr "lettura della cache non riuscita"
+
+#: builtin/merge.c:697
+msgid "Unable to write index."
+msgstr "Impossibile scrivere index."
+
+#: builtin/merge.c:710
+msgid "Not handling anything other than two heads merge."
+msgstr ""
+
+#: builtin/merge.c:724
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr "Opzione per merge-recursive sconosciuta: -X%s"
+
+#: builtin/merge.c:738
+#, c-format
+msgid "unable to write %s"
+msgstr "non è possibile scrivere %s"
+
+#: builtin/merge.c:877
+#, c-format
+msgid "Could not read from '%s'"
+msgstr "Non è stato possibile leggere da '%s'"
+
+#: builtin/merge.c:886
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:892
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+
+#: builtin/merge.c:916
+msgid "Empty commit message."
+msgstr "Messaggio di commit vuoto."
+
+#: builtin/merge.c:928
+#, c-format
+msgid "Wonderful.\n"
+msgstr "Splendido.\n"
+
+#: builtin/merge.c:993
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr ""
+"Merge automatico fallito; risolvi i conflitti ed eseguire il commit\n"
+"del risultato.\n"
+
+#: builtin/merge.c:1009
+#, c-format
+msgid "'%s' is not a commit"
+msgstr "'%s' non è un commit"
+
+#: builtin/merge.c:1050
+msgid "No current branch."
+msgstr "Nessun branch corrente."
+
+#: builtin/merge.c:1052
+msgid "No remote for the current branch."
+msgstr "Nessun remote per il branch corrente."
+
+#: builtin/merge.c:1054
+msgid "No default upstream defined for the current branch."
+msgstr "Nessun upstream di default definito per il branch corrente."
+
+#: builtin/merge.c:1059
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr ""
+
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr "%s - non è qualcosa per cui possiamo eseguire il merge"
+
+#: builtin/merge.c:1214
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr "Non c'è nessun merge da interrompere (manca MERGE_HEAD)"
+
+#: builtin/merge.c:1230 git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"Il merge non è stato concluso (esiste MERGE_HEAD).\n"
+"Per favore, esegui il commit delle modifiche prima del merge."
+
+#: builtin/merge.c:1233 git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr "Il merge non è stato concluso (esiste MERGE_HEAD)."
+
+#: builtin/merge.c:1237
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"Il cherry-pick non è stato concluso (esiste CHERRY_PICK_HEAD).\n"
+"Per favore, esegui il commit delle modifiche prima del merge."
+
+#: builtin/merge.c:1240
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr "Il tuo cherry-pick non è stato concluso (CHERRY_PICK_HEAD esiste)."
+
+#: builtin/merge.c:1249
+msgid "You cannot combine --squash with --no-ff."
+msgstr "Impossibile combinare --squash con --no-off."
+
+#: builtin/merge.c:1254
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr "Impossibile combinare --no-ff con --ff-only."
+
+#: builtin/merge.c:1261
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr "Nessun commit specificato e merge.defaultToUpstream non definito."
+
+#: builtin/merge.c:1293
+msgid "Can merge only exactly one commit into empty head"
+msgstr ""
+
+#: builtin/merge.c:1296
+msgid "Squash commit into empty head not supported yet"
+msgstr ""
+
+#: builtin/merge.c:1298
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr ""
+
+#: builtin/merge.c:1413
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr "Aggiornamento di %s..%s\n"
+
+#: builtin/merge.c:1451
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr ""
+
+#: builtin/merge.c:1458
+#, c-format
+msgid "Nope.\n"
+msgstr "No.\n"
+
+#: builtin/merge.c:1490
+msgid "Not possible to fast-forward, aborting."
+msgstr "Fast-forward non possibile, stop."
+
+#: builtin/merge.c:1513 builtin/merge.c:1592
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr ""
+
+#: builtin/merge.c:1517
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr "Tentativo con la strategia di merge %s...\n"
+
+#: builtin/merge.c:1583
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr "Nessuna strategia di merge ha gestito il merge.\n"
+
+#: builtin/merge.c:1585
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr "Merge con la strategia %s fallito.\n"
+
+#: builtin/merge.c:1594
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr ""
+
+#: builtin/merge.c:1606
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr ""
+"Il merge automatico è andato a buon fine; fermato prima del commit come "
+"richiesto\n"
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr ""
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr ""
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr "non è possibile spostare la directory in sé stessa"
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr "non è possibile spostare la directory su un file"
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr "Eh? %.*s si trova in index?"
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr "la directory sorgente è vuota"
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr "non è sotto controllo di versione"
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr "la destinazione esiste"
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr "sovrascrittura di %s in corso"
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr "Impossibile sovrascrivere"
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr "fonti multiple per la stessa destinazione"
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr "%s, sorgente=%s, destinazione=%s"
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr "Rinominazione di %s in %s in corso\n"
+
+#: builtin/mv.c:215 builtin/remote.c:731
+#, c-format
+msgid "renaming '%s' failed"
+msgstr "rinomina di '%s' non riuscita"
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr "impossibile avviare 'show' per l'oggetto '%s'"
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr ""
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:175 builtin/tag.c:347
+#, c-format
+msgid "could not create file '%s'"
+msgstr "non è stato possibile creare il file '%s'"
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr "Per favore specifica il contenuto delle note usando le opzioni -m o -F"
+
+#: builtin/notes.c:210 builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr "Rimozione della nota per l'oggetto %s\n"
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr "impossibile scrivere l'oggetto nota"
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr "Il contenuto della nota è stato lasciato in %s"
+
+#: builtin/notes.c:251 builtin/tag.c:542
+#, c-format
+msgid "cannot read '%s'"
+msgstr "impossibile leggere '%s'"
+
+#: builtin/notes.c:253 builtin/tag.c:545
+#, c-format
+msgid "could not open or read '%s'"
+msgstr "non è stato possibile aprire o leggere '%s'"
+
+#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
+#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
+#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
+#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:558
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr ""
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr "Lettura dell'oggetto '%s' non riuscita."
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr ""
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr "Valore di notes.rewriteMode errato: '%s'"
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr "Impossibile riscrivere le note in %s (al di fuori di refs/notes/)"
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr "Valore di %s errato: '%s'"
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr "Riga di input malformata: '%s'."
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr "Copia delle note da '%s' a '%s' non riuscita"
+
+#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
+#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr "troppi parametri"
+
+#: builtin/notes.c:513 builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr "Nessuna nota trovata per l'oggetto %s."
+
+#: builtin/notes.c:580
+#, c-format
+msgid ""
+"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:585 builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr "Sovrascrittura delle note esistenti per l'oggetto %s\n"
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr "troppi pochi parametri"
+
+#: builtin/notes.c:656
+#, c-format
+msgid ""
+"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+"Impossibile copiare le note. Trovate note esistenti per l'oggetto %s. Usa "
+"'-f' per sovrascrivere le note esistenti"
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr "Note mancanti per l'oggetto sorgente %s. Impossibile copiare."
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+"Le opzioni -m/-F/-c/-C per il sottocomando 'edit' sono deprecate.\n"
+"Per favore usa 'git notes add -f -m/-F/-c/-C' invece.\n"
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr "L'oggetto %s non ha note.\n"
+
+#: builtin/notes.c:1103 builtin/remote.c:1598
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr "Sottocomando sconosciuto: %s"
+
+#: builtin/pack-objects.c:2337
+#, c-format
+msgid "unsupported index version %s"
+msgstr "versione %s di index non supportata"
+
+#: builtin/pack-objects.c:2341
+#, c-format
+msgid "bad index version '%s'"
+msgstr "versione '%s' di index errata"
+
+#: builtin/pack-objects.c:2364
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr "l'opzione %s non accetta forme negative"
+
+#: builtin/pack-objects.c:2368
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr "impossibile analizzare il valore '%s' per l'opzione %s"
+
+#: builtin/push.c:45
+msgid "tag shorthand without <tag>"
+msgstr ""
+
+#: builtin/push.c:64
+msgid "--delete only accepts plain target ref names"
+msgstr "--delete accetta solo nomi dei ref di destinazione in chiaro"
+
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+
+#: builtin/push.c:121
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+
+#: builtin/push.c:128
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+"Il branch corrente %s non ha alcun branch upstream.\n"
+"Per eseguire il push del branch corrente ed impostare remote come upstream, usa\n"
+"\n"
+" git push --set-upstream %s %s\n"
+
+#: builtin/push.c:136
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr "Il branch corrente %s ha branch multipli in upstream; push non eseguito."
+
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+
+#: builtin/push.c:174
+msgid ""
+"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr ""
+"Non è stato specificato alcun refspec per il push, e push.default è \"nothing\"."
+
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:233
+#, c-format
+msgid "Pushing to %s\n"
+msgstr ""
+
+#: builtin/push.c:237
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr ""
+
+#: builtin/push.c:269
+#, c-format
+msgid "bad repository '%s'"
+msgstr "repository '%s' errato"
+
+#: builtin/push.c:270
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote "
+"repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+"Nessuna destinazione per il push configurata.\n"
+"Specifica un URL dalla riga di comando oppure configurare un repository "
+"remoto usando\n"
+"\n"
+" git remote add <nome> <url>\n"
+"\n"
+"e poi eseguire il push usando il nome del remote\n"
+"\n"
+" git push <nome>\n"
+
+#: builtin/push.c:285
+msgid "--all and --tags are incompatible"
+msgstr "--all e --tags non sono compatibili"
+
+#: builtin/push.c:286
+msgid "--all can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:291
+msgid "--mirror and --tags are incompatible"
+msgstr "--mirror e --tags non sono compatibili"
+
+#: builtin/push.c:292
+msgid "--mirror can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:297
+msgid "--all and --mirror are incompatible"
+msgstr "--all e --mirror non sono compatibili"
+
+#: builtin/push.c:385
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr "--delete non è compatibile con --all, --mirror e --tags"
+
+#: builtin/push.c:387
+msgid "--delete doesn't make sense without any refs"
+msgstr "--delete non ha senso senza alcun ref"
+
+#: builtin/remote.c:98
+#, c-format
+msgid "Updating %s"
+msgstr "Aggiornamento di %s"
+
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
+msgstr ""
+"--mirror è pericoloso e deprecato; per favore\n"
+"\t usa invece --mirror-fetch o --mirror-push"
+
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
+msgstr "argomento di mirror sconosciuto: %s"
+
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
+msgstr "specificare un branch master con --mirror non ha senso"
+
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
+msgstr ""
+
+#: builtin/remote.c:195 builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
+msgstr "il remoto %s esiste già."
+
+#: builtin/remote.c:199 builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
+msgstr "'%s' non è un nome di remoto valido"
+
+#: builtin/remote.c:243
+#, c-format
+msgid "Could not setup master '%s'"
+msgstr "Non è stato possibile configurare il master '%s'"
+
+#: builtin/remote.c:299
+#, c-format
+msgid "more than one %s"
+msgstr "più di un %s"
+
+#: builtin/remote.c:339
+#, c-format
+msgid "Could not get fetch map for refspec %s"
+msgstr ""
+
+#: builtin/remote.c:440 builtin/remote.c:448
+msgid "(matching)"
+msgstr "(corrispondente)"
+
+#: builtin/remote.c:452
+msgid "(delete)"
+msgstr "(elimina)"
+
+#: builtin/remote.c:595 builtin/remote.c:601 builtin/remote.c:607
+#, c-format
+msgid "Could not append '%s' to '%s'"
+msgstr "Non è stato possibile aggiungere '%s' a '%s'"
+
+#: builtin/remote.c:639 builtin/remote.c:792 builtin/remote.c:890
+#, c-format
+msgid "No such remote: %s"
+msgstr "Remote non esistente: %s"
+
+#: builtin/remote.c:656
+#, c-format
+msgid "Could not rename config section '%s' to '%s'"
+msgstr "Non è stato possibile rinominare la sezione di configurazione da '%s' in '%s'"
+
+#: builtin/remote.c:662 builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
+msgstr "Non è stato possibile rimuovere la sezione di configurazione '%s'"
+
+#: builtin/remote.c:677
+#, c-format
+msgid ""
+"Not updating non-default fetch refspec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
+msgstr ""
+
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
+msgstr ""
+
+#: builtin/remote.c:694
+#, c-format
+msgid "Could not set '%s'"
+msgstr "Non è stato possibile impostare '%s'"
+
+#: builtin/remote.c:716
+#, c-format
+msgid "deleting '%s' failed"
+msgstr "eliminazione di '%s' non riuscita"
+
+#: builtin/remote.c:750
+#, c-format
+msgid "creating '%s' failed"
+msgstr "creazione di '%s' non riuscita"
+
+#: builtin/remote.c:764
+#, c-format
+msgid "Could not remove branch %s"
+msgstr "Non è stato possibile rimuovere il branch %s"
+
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] ""
+"Nota: un branch al di fuori della gerarchia refs/remotes/ non è stato "
+"eliminato;\n"
+"per eliminarlo, usare:"
+msgstr[1] ""
+"Nota: alcuni branch al di fuori della gerarchia refs/remotes/ non sono stati "
+"eliminati;\n"
+"per eliminarli, usare:"
+
+#: builtin/remote.c:943
+#, c-format
+msgid " new (next fetch will store in remotes/%s)"
+msgstr ""
+
+#: builtin/remote.c:946
+msgid " tracked"
+msgstr ""
+
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
+msgstr " vecchio (usare 'git remote prune' per rimuoverlo)"
+
+#: builtin/remote.c:950
+msgid " ???"
+msgstr "???"
+
+#: builtin/remote.c:991
+#, c-format
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
+msgstr ""
+"branch.%s.merge non valido; impossibile eseguire il rebase su > 1 branch"
+
+#: builtin/remote.c:998
+#, c-format
+msgid "rebases onto remote %s"
+msgstr ""
+
+#: builtin/remote.c:1001
+#, c-format
+msgid " merges with remote %s"
+msgstr " merge con il remote %s"
+
+#: builtin/remote.c:1002
+msgid " and with remote"
+msgstr " e con il remote"
+
+#: builtin/remote.c:1004
+#, c-format
+msgid "merges with remote %s"
+msgstr "merge con il remote %s"
+
+#: builtin/remote.c:1005
+msgid " and with remote"
+msgstr ""
+
+#: builtin/remote.c:1051
+msgid "create"
+msgstr "crea"
+
+#: builtin/remote.c:1054
+msgid "delete"
+msgstr "elimina"
+
+#: builtin/remote.c:1058
+msgid "up to date"
+msgstr "aggiornato"
+
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
+msgstr ""
+
+#: builtin/remote.c:1064
+msgid "local out of date"
+msgstr "locale non aggiornato"
+
+#: builtin/remote.c:1071
+#, c-format
+msgid " %-*s forces to %-*s (%s)"
+msgstr ""
+
+#: builtin/remote.c:1074
+#, c-format
+msgid " %-*s pushes to %-*s (%s)"
+msgstr ""
+
+#: builtin/remote.c:1078
+#, c-format
+msgid " %-*s forces to %s"
+msgstr ""
+
+#: builtin/remote.c:1081
+#, c-format
+msgid " %-*s pushes to %s"
+msgstr " %-*s esegue il push su %s"
+
+#: builtin/remote.c:1118
+#, c-format
+msgid "* remote %s"
+msgstr "* remote %s"
+
+#: builtin/remote.c:1119
+#, c-format
+msgid " Fetch URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1120 builtin/remote.c:1285
+msgid "(no URL)"
+msgstr "(nessun URL)"
+
+#: builtin/remote.c:1129 builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1133 builtin/remote.c:1135 builtin/remote.c:1137
+#, c-format
+msgid " HEAD branch: %s"
+msgstr " branch HEAD: %s"
+
+#: builtin/remote.c:1139
+#, c-format
+msgid ""
+" HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
+msgstr ""
+" branch HEAD (l'HEAD remoto è ambiguo, potrebbe essere uno dei seguenti):\n"
+
+#: builtin/remote.c:1151
+#, c-format
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] " Branch remoto:%s"
+msgstr[1] " Branch remoti:%s"
+
+#: builtin/remote.c:1154 builtin/remote.c:1181
+msgid " (status not queried)"
+msgstr ""
+
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] " Branch locale configurato per 'git pull':"
+msgstr[1] " Branch locali configurati per 'git pull':"
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr ""
+
+#: builtin/remote.c:1178
+#, c-format
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] " Ref locale configurato per 'git push'%s:"
+msgstr[1] " Ref locali configurati per 'git push'%s:"
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
+msgstr "Impossibile determinare l'HEAD remoto"
+
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
+msgstr ""
+
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
+msgstr "Non è stato possibile eliminare %s"
+
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
+msgstr "Non è un ref valido: %s"
+
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
+msgstr "Non è stato possibile configurare %s"
+
+#: builtin/remote.c:1274
+#, c-format
+msgid " %s will become dangling!"
+msgstr ""
+
+#: builtin/remote.c:1275
+#, c-format
+msgid " %s has become dangling!"
+msgstr ""
+
+#: builtin/remote.c:1281
+#, c-format
+msgid "Pruning %s"
+msgstr ""
+
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
+msgstr "URL: %s"
+
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
+msgstr ""
+
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
+msgstr ""
+
+#: builtin/remote.c:1387 builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
+msgstr "Remote '%s' non esistente"
+
+#: builtin/remote.c:1414
+msgid "no remote specified"
+msgstr "nessun remote specificato"
+
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
+msgstr "--add --delete non ha senso"
+
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
+msgstr ""
+
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr "Nessuna URL trovata: %s"
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "merge"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr ""
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr ""
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr ""
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr ""
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr "Non è stato possibile scrivere il nuovo index file."
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr "HEAD ora si trova a %s"
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr "Non è stato possibile leggere index"
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr ""
+
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr "Impossibile eseguire un %s reset nel corso di un merge."
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr "Non è stato possibile analizzare l'oggetto '%s'."
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr "--patch non è compatibile con --{hard,mixed,soft}"
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr "--mixed con i path è deprecata; usa invece 'git reset -- <path>'."
+
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr ""
+
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr "%s reset non è consentito in un repository spoglio"
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr ""
+"Non è stato possibile ripristinare index file "
+"alla revisione '%s'."
+
+#: builtin/revert.c:70 builtin/revert.c:92
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr "%s: %s non può essere usata con %s"
+
+#: builtin/revert.c:131
+msgid "program error"
+msgstr "errore del programma"
+
+#: builtin/revert.c:221
+msgid "revert failed"
+msgstr "revert non riuscito"
+
+#: builtin/revert.c:236
+msgid "cherry-pick failed"
+msgstr "cherry-pick non riuscito"
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"'%s' contiene delle modifiche locali\n"
+"(usa --cached per mantenere il file, o -f per forzare la rimozione)"
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr ""
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr "git rm: non è possibile eliminare %s"
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr "Autore mancante: %s"
+
+#: builtin/tag.c:60
+#, c-format
+msgid "malformed object at '%s'"
+msgstr ""
+
+#: builtin/tag.c:207
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr "nome tag troppo lungo: %.*s..."
+
+#: builtin/tag.c:212
+#, c-format
+msgid "tag '%s' not found."
+msgstr "tag '%s' non trovato."
+
+#: builtin/tag.c:227
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr "Tag '%s' eliminato (era %s)\n"
+
+#: builtin/tag.c:239
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr "non è stato possibile verificare il tag '%s'"
+
+#: builtin/tag.c:249
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# Scrivere un messaggio associato al tag\n"
+"# Le righe che iniziano con '#' verranno ignorate.\n"
+"#\n"
+
+#: builtin/tag.c:256
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you "
+"want to.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# Scrivere un messaggio associato al tag\n"
+"# Le righe che iniziano con '#' verranno mantenute; possono essere comunque "
+"rimosse manualmente.\n"
+"#\n"
+
+#: builtin/tag.c:298
+msgid "unable to sign the tag"
+msgstr "impossibile firmare il tag"
+
+#: builtin/tag.c:300
+msgid "unable to write tag file"
+msgstr "impossibile scrivere il file di tag"
+
+#: builtin/tag.c:325
+msgid "bad object type."
+msgstr "tipo di oggetto errato."
+
+#: builtin/tag.c:338
+msgid "tag header too big."
+msgstr "intestazione del tag troppo grande."
+
+#: builtin/tag.c:370
+msgid "no tag message?"
+msgstr "nessun messaggio per il tag?"
+
+#: builtin/tag.c:376
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr "Il messaggio del tag è stato lasciato in %s\n"
+
+#: builtin/tag.c:425
+msgid "switch 'points-at' requires an object"
+msgstr "lo switch 'points-at' richiede un oggetto"
+
+#: builtin/tag.c:427
+#, c-format
+msgid "malformed object name '%s'"
+msgstr "nome oggetto '%s' malformato"
+
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr "--column e -n non sono compatibili"
+
+#: builtin/tag.c:523
+msgid "-n option is only allowed with -l."
+msgstr "l'opzione -n è consentita solo con -l."
+
+#: builtin/tag.c:525
+msgid "--contains option is only allowed with -l."
+msgstr "l'opzione --contains è consentita solo con -l."
+
+#: builtin/tag.c:527
+msgid "--points-at option is only allowed with -l."
+msgstr "l'opzione --points-at è consentita solo con -l."
+
+#: builtin/tag.c:535
+msgid "only one -F or -m option is allowed."
+msgstr "è consentita una sola opzione tra -F e -m."
+
+#: builtin/tag.c:555
+msgid "too many params"
+msgstr "troppi parametri"
+
+#: builtin/tag.c:561
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr "'%s' non è un nome tag valido."
+
+#: builtin/tag.c:566
+#, c-format
+msgid "tag '%s' already exists"
+msgstr "il tag '%s' esiste già"
+
+#: builtin/tag.c:584
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr "%s: impossibile riservare il ref"
+
+#: builtin/tag.c:586
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr "%s: impossibile aggiornare il ref"
+
+#: builtin/tag.c:588
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr "Tag '%s' aggiornato (era %s)\n"
+
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr ""
+"Vedi 'git help <comando> per maggiori informazioni su un comando "
+"specifico."
+
+#: parse-options.h:133 parse-options.h:235
+msgid "n"
+msgstr "n"
+
+#: parse-options.h:141
+msgid "time"
+msgstr "tempo"
+
+#: parse-options.h:149
+msgid "file"
+msgstr "file"
+
+#: parse-options.h:151
+msgid "when"
+msgstr "quando"
+
+#: parse-options.h:156
+msgid "no-op (backward compatibility)"
+msgstr ""
+
+#: parse-options.h:228
+msgid "be more verbose"
+msgstr "più dettagliato"
+
+#: parse-options.h:230
+msgid "be more quiet"
+msgstr "meno dettagliato"
+
+#: parse-options.h:236
+msgid "use <n> digits to display SHA-1s"
+msgstr "usare <n> cifre per mostrare gli hash SHA-1"
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr "Aggiunge il contenuto del file a index"
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr "Cerca mediante ricerca binaria la modifica che ha introdotto un bug"
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr "Elenca, crea o elimina branch"
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr ""
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr "Clona un repository in una nuova directory"
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr "Registra modifiche nel repository"
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr ""
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr "Scarica oggetti e ref da un altro repository"
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr "Stampa le righe corrispondenti ad un modello"
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr "Crea un repository git vuoto o reinizializza uno esistente"
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr "Mostra log del commit"
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr "Unisce due o più cronologie di sviluppo"
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr "Sposta o rinomina un file, una directory o un link simbolico"
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr "Combina fetche + merge da un altro repository o un branch locale"
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr "Aggiorna i ref remoti insieme agli oggetti associati"
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr ""
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr "Ripristina l'HEAD corrente allo stato specificato"
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr ""
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr "Mostra vari tipi di oggetti"
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr ""
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr "Crea, elenca, elimina o verifica un oggetto tag firmato con GPG"
+
+#: git-am.sh:50
+msgid "You need to set your committer info first"
+msgstr "È necessario impostare le informazioni sul committer"
+
+#: git-am.sh:95
+msgid ""
+"You seem to have moved HEAD since the last 'am' failure.\n"
+"Not rewinding to ORIG_HEAD"
+msgstr ""
+
+#: git-am.sh:105
+#, sh-format
+msgid ""
+"When you have resolved this problem run \"$cmdline --resolved\".\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"Quando hai risolto il problema esegui \"$cmdline --resolved\".\n"
+"Se vuoi saltare questa patch, esegui invece \"$cmdline --skip\".\n"
+"Per ripristinare il branch originale e interrompere l'applicazione delle "
+"patch esegui \"$cmdline --abort\"."
+
+#: git-am.sh:121
+msgid "Cannot fall back to three-way merge."
+msgstr ""
+
+#: git-am.sh:137
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr ""
+
+#: git-am.sh:154
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+"La tua patch è stata modificata manualmente?\n"
+"Non può essere applicata ai blob registrati nel proprio index."
+
+#: git-am.sh:163
+msgid "Falling back to patching base and 3-way merge..."
+msgstr ""
+
+#: git-am.sh:275
+msgid "Only one StGIT patch series can be applied at once"
+msgstr "Può essere applicata solo una serie di patch StGIT per volta"
+
+#: git-am.sh:362
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr "Il formato patch $patch_format non è supportato."
+
+#: git-am.sh:364
+msgid "Patch format detection failed."
+msgstr "Rilevamento del formato della patch non riuscito."
+
+#: git-am.sh:418
+msgid "-d option is no longer supported. Do not use."
+msgstr "l'opzione -d non è più supportata. Non utilizzarla."
+
+#: git-am.sh:481
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr ""
+
+#: git-am.sh:486
+msgid "Please make up your mind. --skip or --abort?"
+msgstr "Per favore, deciditi. --skip o --abort?"
+
+#: git-am.sh:513
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr ""
+
+#: git-am.sh:579
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr ""
+
+#: git-am.sh:671
+#, sh-format
+msgid ""
+"Patch is empty. Was it split wrong?\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+
+#: git-am.sh:708
+msgid "Patch does not have a valid e-mail address."
+msgstr "La patch non contiene un indirizzo email valido."
+
+#: git-am.sh:755
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr ""
+
+#: git-am.sh:759
+msgid "Commit Body is:"
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:766
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr "Applicare? sì[y]/no[n]/modifica[e]/visualizza patch[v]/accetta tutto[a] "
+
+#: git-am.sh:802
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr ""
+
+#: git-am.sh:823
+msgid ""
+"No changes - did you forget to use 'git add'?\n"
+"If there is nothing left to stage, chances are that something else\n"
+"already introduced the same changes; you might want to skip this patch."
+msgstr ""
+
+#: git-am.sh:831
+msgid ""
+"You still have unmerged paths in your index\n"
+"did you forget to use 'git add'?"
+msgstr ""
+
+#: git-am.sh:847
+msgid "No changes -- Patch already applied."
+msgstr "Nessuna modifica -- patch già applicata."
+
+#: git-am.sh:857
+#, sh-format
+msgid "Patch failed at $msgnum $FIRSTLINE"
+msgstr "Patch non riuscita a $msgnum $FIRSTLINE"
+
+#: git-am.sh:873
+msgid "applying to an empty history"
+msgstr ""
+
+#: git-bisect.sh:48
+msgid "You need to start by \"git bisect start\""
+msgstr "Devi iniziare con \"git bisect start\""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr "Vuoi che me ne occupi io [Y/n]? "
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr "opzione non riconosciuta: '$arg'"
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr "'$arg' non sembra essere una revisione valida"
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr ""
+
+#: git-bisect.sh:130
+#, sh-format
+msgid ""
+"Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr ""
+"Checkout di '$start_head' non riuscito. Prova 'git bisect reset "
+"<branch-valido>'."
+
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr ""
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr "HEAD errato - strano ref simbolico"
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr "Argomento bisect_write errato: $state"
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr ""
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr "Per favore, chiama 'bisect_state' con almeno un argomento."
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr ""
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr "'git bisect bad' può prendere un solo argomento."
+
+#. have bad but not good. we could bisect although
+#. this is less optimum.
+#: git-bisect.sh:273
+msgid "Warning: bisecting only with a bad commit."
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr "Sei sicuro? [Y/n] "
+
+#: git-bisect.sh:289
+msgid ""
+"You need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"Devi specificare almeno una revisione corretta ed una errata.\n"
+"(Puoi usare \"git bisect bad\" e \"git bisect good\" per questo scopo.)"
+
+#: git-bisect.sh:292
+msgid ""
+"You need to start by \"git bisect start\".\n"
+"You then need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+
+#: git-bisect.sh:347 git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr "Non stiamo eseguendo un bisect."
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr "'$invalid' non è un commit valido"
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr "Nessun file di log specificato"
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr ""
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr "?? di cosa si sta parlando?"
+
+#: git-bisect.sh:420
+#, sh-format
+msgid "running $command"
+msgstr "sto eseguendo $command"
+
+#: git-bisect.sh:427
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"exit code $res from '$command' is < 0 or >= 128"
+msgstr ""
+"bisect run non riuscito:\n"
+"il codice di uscita $res da '$command' è < 0 oppure >= 128"
+
+#: git-bisect.sh:453
+msgid "bisect run cannot continue any more"
+msgstr "bisect run non può più proseguire"
+
+#: git-bisect.sh:459
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"'bisect_state $state' exited with error code $res"
+msgstr ""
+"bisect run non riuscito:\n"
+"bisect_state $state è uscito con il codice di errore $res"
+
+#: git-bisect.sh:466
+msgid "bisect run success"
+msgstr "bisect run eseguito con successo"
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr ""
+"Il pull non è possibile perché ci sono file di cui non è stato eseguito il "
+"merge."
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr ""
+
+#. The fetch involved updating the current branch.
+#. The working tree and the index file is still based on the
+#. $orig_head commit, but we are merging into $curr_head.
+#. First update the working tree to match $curr_head.
+#: git-pull.sh:228
+#, sh-format
+msgid ""
+"Warning: fetch updated the current branch head.\n"
+"Warning: fast-forwarding your working tree from\n"
+"Warning: commit $orig_head."
+msgstr ""
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr "Impossibile eseguire il merge di branch multipli in un head vuoto"
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr "Impossibile eseguire il rebase su branch multipli"
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr "git stash clear con parametri non è implementato"
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr "Non hai ancora un commit iniziale"
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr "Impossibile salvare lo stato corrente di index"
+
+#: git-stash.sh:123 git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr ""
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr "Nessuna modifica selezionata"
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr ""
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr ""
+
+#. TRANSLATORS: $option is an invalid option, like
+#. `--blah-blah'. The 7 spaces at the beginning of the
+#. second line correspond to "error: ". So you should line
+#. up the second line with however many characters the
+#. translation of "error: " takes in your language. E.g. in
+#. English this is:
+#.
+#. $ git stash save --blah-blah 2>&1 | head -n 2
+#. error: unknown option for 'stash save': --blah-blah
+#. To provide a message, use git stash save -- '--blah-blah'
+#: git-stash.sh:202
+#, sh-format
+msgid ""
+"error: unknown option for 'stash save': $option\n"
+" To provide a message, use git stash save -- '$option'"
+msgstr ""
+"errore: opzione sconosciuta per 'stash save': $option\n"
+" Per aggiungere un messaggio, usare git stash save -- '$option'"
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr "Nessuna modifica locale da salvare"
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr "Impossibile inizializzare stash"
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr "Impossibile salvare lo stato attuale"
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr ""
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr "Nessuno stash trovato."
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr "Troppe revisioni specificate: $REV"
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr "$reference non è un riferimento valido"
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr "'$args' non è un commit di tipo stash"
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr "'$args' non è un referimento a uno stash"
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr "impossibile aggiornare index"
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr "Impossibile applicare uno stash nel mezzo di un merge"
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr "Ci sono conflitti in index. Prova senza --index."
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr ""
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr ""
+
+#: git-stash.sh:474
+msgid "Index was not unstashed."
+msgstr ""
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr "${REV} eliminata ($s)"
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr "${REV}: non è stato possibile rimuovere la voce di stash"
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr "Nome del branch non specificato"
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr "(Per ripristinarli digita \"git stash apply\")"
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr ""
+
+#: git-submodule.sh:109
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:150
+#, sh-format
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr ""
+
+#: git-submodule.sh:160
+#, sh-format
+msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
+msgstr ""
+
+#: git-submodule.sh:249
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr "repo URL: '$repo' deve essere assoluto o iniziare con ./|../"
+
+#: git-submodule.sh:266
+#, sh-format
+msgid "'$sm_path' already exists in the index"
+msgstr "'$sm_path' esiste già in index"
+
+#: git-submodule.sh:270
+#, sh-format
+msgid ""
+"The following path is ignored by one of your .gitignore files:\n"
+"$sm_path\n"
+"Use -f if you really want to add it."
+msgstr ""
+"Il seguente path è ignorato da uno dei tuoi file .gitignore:\n"
+"$sm_path\n"
+"Usa -f se vuoi davvero aggiungerlo."
+
+#: git-submodule.sh:281
+#, sh-format
+msgid "Adding existing repo at '$sm_path' to the index"
+msgstr ""
+
+#: git-submodule.sh:283
+#, sh-format
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "'$sm_path' esiste già e non è un repository git valido"
+
+#: git-submodule.sh:297
+#, sh-format
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:302
+#, sh-format
+msgid "Failed to add submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:307
+#, sh-format
+msgid "Failed to register submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:349
+#, sh-format
+msgid "Entering '$prefix$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:363
+#, sh-format
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr ""
+"Interruzione a '$sm_path'; lo script ha restituito uno stato diverso da zero."
+
+#: git-submodule.sh:406
+#, sh-format
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr ""
+
+#: git-submodule.sh:415
+#, sh-format
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:417
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:425
+#, sh-format
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:524
+#, sh-format
+msgid ""
+"Submodule path '$sm_path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+
+#: git-submodule.sh:537
+#, sh-format
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:556
+#, sh-format
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:570
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:571
+#, sh-format
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:576
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:577
+#, sh-format
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:582
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:583
+#, sh-format
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:605 git-submodule.sh:928
+#, sh-format
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:713
+msgid "--cached cannot be used with --files"
+msgstr "--cached non può essere usata con --files"
+
+#. unexpected type
+#: git-submodule.sh:753
+#, sh-format
+msgid "unexpected mode $mod_dst"
+msgstr "modalità $mod_dst inattesa"
+
+#: git-submodule.sh:771
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr " Attenzione: $name non contiene commit $sha1_src"
+
+#: git-submodule.sh:774
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr " Attenzione: $name non contiene commit $sha1_dst"
+
+#: git-submodule.sh:777
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr " Attenzione: $name non contiene commit $sha1_src e $sha1_dst"
+
+#: git-submodule.sh:802
+msgid "blob"
+msgstr "blob"
+
+#: git-submodule.sh:803
+msgid "submodule"
+msgstr "sottomodulo"
+
+#: git-submodule.sh:840
+msgid "# Submodules changed but not updated:"
+msgstr ""
+
+#: git-submodule.sh:842
+msgid "# Submodule changes to be committed:"
+msgstr ""
+
+#: git-submodule.sh:974
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr ""
+
+#~ msgid "--"
+#~ msgstr "--"
+
+#~ msgid "Could not extract email from committer identity."
+#~ msgstr ""
+#~ "Non è stato possibile estrarre l'indirizzo email dall'identità del "
+#~ "committer."
diff --git a/po/nl.po b/po/nl.po
new file mode 100644
index 0000000..e1399e2
--- /dev/null
+++ b/po/nl.po
@@ -0,0 +1,3493 @@
+# Dutch translations for Git.
+# Copyright (C) 2012 Vincent van Ravesteijn <vfr@lyx.org>
+# This file is distributed under the same license as the Git package.
+# Vincent van Ravesteijn <vfr@lyx.org>, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-02-28 09:17+0800\n"
+"PO-Revision-Date: 2012-03-07 12:02+0100\n"
+"Last-Translator: Vincent van Ravesteijn <vfr@lyx.org>\n"
+"Language-Team: Dutch\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ASCII\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: advice.c:34
+#, c-format
+msgid "hint: %.*s\n"
+msgstr ""
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:64
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+
+#: commit.c:47
+#, c-format
+msgid "could not parse %s"
+msgstr ""
+
+#: commit.c:49
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr ""
+
+#: compat/obstack.c:406 compat/obstack.c:408
+msgid "memory exhausted"
+msgstr ""
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr ""
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr ""
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr ""
+
+#: diff.c:104
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr ""
+
+#: diff.c:109
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr ""
+
+#: diff.c:205
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+
+#: diff.c:1331
+msgid " 0 files changed\n"
+msgstr ""
+
+#: diff.c:1335
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:1352
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:1363
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:3364
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr ""
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr ""
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr ""
+
+#: grep.c:1285
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr ""
+
+#: grep.c:1302
+#, c-format
+msgid "'%s': %s"
+msgstr ""
+
+#: grep.c:1313
+#, c-format
+msgid "'%s': short read %s"
+msgstr ""
+
+#: help.c:287
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+
+#: remote.c:1607
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1613
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural ""
+"Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1621
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: sequencer.c:120 builtin/merge.c:862 builtin/merge.c:983
+#: builtin/merge.c:1093 builtin/merge.c:1103
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr ""
+
+#: sequencer.c:122 builtin/merge.c:334 builtin/merge.c:865
+#: builtin/merge.c:1095 builtin/merge.c:1108
+#, c-format
+msgid "Could not write to '%s'"
+msgstr ""
+
+#: sequencer.c:142
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+
+#: sequencer.c:154 sequencer.c:680 sequencer.c:763
+#, c-format
+msgid "Could not write to %s"
+msgstr ""
+
+#: sequencer.c:157
+#, c-format
+msgid "Error wrapping up %s"
+msgstr ""
+
+#: sequencer.c:172
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr ""
+
+#: sequencer.c:174
+msgid "Your local changes would be overwritten by revert."
+msgstr ""
+
+#: sequencer.c:177
+msgid "Commit your changes or stash them to proceed."
+msgstr ""
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:227
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr ""
+
+#: sequencer.c:293
+msgid "Your index file is unmerged."
+msgstr ""
+
+#: sequencer.c:296
+msgid "You do not have a valid HEAD"
+msgstr ""
+
+#: sequencer.c:311
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr ""
+
+#: sequencer.c:319
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr ""
+
+#: sequencer.c:323
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr ""
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:334
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr ""
+
+#: sequencer.c:338
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr ""
+
+#: sequencer.c:422
+#, c-format
+msgid "could not revert %s... %s"
+msgstr ""
+
+#: sequencer.c:423
+#, c-format
+msgid "could not apply %s... %s"
+msgstr ""
+
+#: sequencer.c:445 sequencer.c:904 builtin/log.c:286 builtin/log.c:709
+#: builtin/log.c:1325 builtin/log.c:1544 builtin/merge.c:348
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr ""
+
+#: sequencer.c:448
+msgid "empty commit set passed"
+msgstr ""
+
+#: sequencer.c:456
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr ""
+
+#: sequencer.c:461
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr ""
+
+#: sequencer.c:546
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr ""
+
+#: sequencer.c:568
+#, c-format
+msgid "Could not parse line %d."
+msgstr ""
+
+#: sequencer.c:573
+msgid "No commits parsed."
+msgstr ""
+
+#: sequencer.c:586
+#, c-format
+msgid "Could not open %s"
+msgstr ""
+
+#: sequencer.c:590
+#, c-format
+msgid "Could not read %s."
+msgstr ""
+
+#: sequencer.c:597
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr ""
+
+#: sequencer.c:625
+#, c-format
+msgid "Invalid key: %s"
+msgstr ""
+
+#: sequencer.c:628
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr ""
+
+#: sequencer.c:640
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr ""
+
+#: sequencer.c:661
+msgid "a cherry-pick or revert is already in progress"
+msgstr ""
+
+#: sequencer.c:662
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr ""
+
+#: sequencer.c:666
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr ""
+
+#: sequencer.c:682 sequencer.c:767
+#, c-format
+msgid "Error wrapping up %s."
+msgstr ""
+
+#: sequencer.c:701 sequencer.c:835
+msgid "no cherry-pick or revert in progress"
+msgstr ""
+
+#: sequencer.c:703
+msgid "cannot resolve HEAD"
+msgstr ""
+
+#: sequencer.c:705
+msgid "cannot abort from a branch yet to be born"
+msgstr ""
+
+#: sequencer.c:727
+#, c-format
+msgid "cannot open %s: %s"
+msgstr ""
+
+#: sequencer.c:730
+#, c-format
+msgid "cannot read %s: %s"
+msgstr ""
+
+#: sequencer.c:731
+msgid "unexpected end of file"
+msgstr ""
+
+#: sequencer.c:737
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr ""
+
+#: sequencer.c:760
+#, c-format
+msgid "Could not format %s."
+msgstr ""
+
+#: sequencer.c:922
+msgid "Can't revert as initial commit"
+msgstr ""
+
+#: sequencer.c:923
+msgid "Can't cherry-pick into empty head"
+msgstr ""
+
+#: wt-status.c:134
+msgid "Unmerged paths:"
+msgstr ""
+
+#: wt-status.c:140 wt-status.c:157
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:142 wt-status.c:159
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:143
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr ""
+
+#: wt-status.c:151
+msgid "Changes to be committed:"
+msgstr ""
+
+#: wt-status.c:169
+msgid "Changes not staged for commit:"
+msgstr ""
+
+#: wt-status.c:173
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr ""
+
+#: wt-status.c:175
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr ""
+
+#: wt-status.c:176
+msgid ""
+" (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr ""
+
+#: wt-status.c:178
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr ""
+
+#: wt-status.c:187
+#, c-format
+msgid "%s files:"
+msgstr ""
+
+#: wt-status.c:190
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr ""
+
+#: wt-status.c:207
+msgid "bug"
+msgstr ""
+
+#: wt-status.c:212
+msgid "both deleted:"
+msgstr ""
+
+#: wt-status.c:213
+msgid "added by us:"
+msgstr ""
+
+#: wt-status.c:214
+msgid "deleted by them:"
+msgstr ""
+
+#: wt-status.c:215
+msgid "added by them:"
+msgstr ""
+
+#: wt-status.c:216
+msgid "deleted by us:"
+msgstr ""
+
+#: wt-status.c:217
+msgid "both added:"
+msgstr ""
+
+#: wt-status.c:218
+msgid "both modified:"
+msgstr ""
+
+#: wt-status.c:248
+msgid "new commits, "
+msgstr ""
+
+#: wt-status.c:250
+msgid "modified content, "
+msgstr ""
+
+#: wt-status.c:252
+msgid "untracked content, "
+msgstr ""
+
+#: wt-status.c:266
+#, c-format
+msgid "new file: %s"
+msgstr ""
+
+#: wt-status.c:269
+#, c-format
+msgid "copied: %s -> %s"
+msgstr ""
+
+#: wt-status.c:272
+#, c-format
+msgid "deleted: %s"
+msgstr ""
+
+#: wt-status.c:275
+#, c-format
+msgid "modified: %s"
+msgstr ""
+
+#: wt-status.c:278
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr ""
+
+#: wt-status.c:281
+#, c-format
+msgid "typechange: %s"
+msgstr ""
+
+#: wt-status.c:284
+#, c-format
+msgid "unknown: %s"
+msgstr ""
+
+#: wt-status.c:287
+#, c-format
+msgid "unmerged: %s"
+msgstr ""
+
+#: wt-status.c:290
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr ""
+
+#: wt-status.c:713
+msgid "On branch "
+msgstr ""
+
+#: wt-status.c:720
+msgid "Not currently on any branch."
+msgstr ""
+
+#: wt-status.c:731
+msgid "Initial commit"
+msgstr ""
+
+#: wt-status.c:745
+msgid "Untracked"
+msgstr ""
+
+#: wt-status.c:747
+msgid "Ignored"
+msgstr ""
+
+#: wt-status.c:749
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr ""
+
+#: wt-status.c:751
+msgid " (use -u option to show untracked files)"
+msgstr ""
+
+#: wt-status.c:757
+msgid "No changes"
+msgstr ""
+
+#: wt-status.c:761
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr ""
+
+#: wt-status.c:763
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr ""
+
+#: wt-status.c:765
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr ""
+
+#: wt-status.c:767
+msgid " (use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:769 wt-status.c:772 wt-status.c:775
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr ""
+
+#: wt-status.c:770
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:773
+msgid " (use -u to show untracked files)"
+msgstr ""
+
+#: wt-status.c:776
+msgid " (working directory clean)"
+msgstr ""
+
+#: wt-status.c:884
+msgid "HEAD (no branch)"
+msgstr ""
+
+#: wt-status.c:890
+msgid "Initial commit on "
+msgstr ""
+
+#: wt-status.c:905
+msgid "behind "
+msgstr ""
+
+#: wt-status.c:908 wt-status.c:911
+msgid "ahead "
+msgstr ""
+
+#: wt-status.c:913
+msgid ", behind "
+msgstr ""
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr ""
+
+#: builtin/add.c:67 builtin/commit.c:298
+msgid "updating files failed"
+msgstr ""
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr ""
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr ""
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr ""
+
+#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr ""
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr ""
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr ""
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr ""
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr ""
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr ""
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr ""
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr ""
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr ""
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr ""
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr ""
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr ""
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr ""
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr ""
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr ""
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr ""
+
+#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:358 builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr ""
+
+#: builtin/add.c:476 builtin/mv.c:229 builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr ""
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr ""
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr ""
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr ""
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr ""
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr ""
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr ""
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr ""
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr ""
+
+#: builtin/branch.c:137
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+
+#: builtin/branch.c:141
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+
+#. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
+#: builtin/branch.c:163
+msgid "remote "
+msgstr ""
+
+#: builtin/branch.c:171
+msgid "cannot use -a with -d"
+msgstr ""
+
+#: builtin/branch.c:177
+msgid "Couldn't look up commit object for HEAD"
+msgstr ""
+
+#: builtin/branch.c:182
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr ""
+
+#: builtin/branch.c:192
+#, c-format
+msgid "%sbranch '%s' not found."
+msgstr ""
+
+#: builtin/branch.c:200
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr ""
+
+#: builtin/branch.c:206
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+
+#: builtin/branch.c:214
+#, c-format
+msgid "Error deleting %sbranch '%s'"
+msgstr ""
+
+#: builtin/branch.c:219
+#, c-format
+msgid "Deleted %sbranch %s (was %s).\n"
+msgstr ""
+
+#: builtin/branch.c:224
+msgid "Update of config-file failed"
+msgstr ""
+
+#: builtin/branch.c:322
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr ""
+
+#: builtin/branch.c:394
+#, c-format
+msgid "behind %d] "
+msgstr ""
+
+#: builtin/branch.c:396
+#, c-format
+msgid "ahead %d] "
+msgstr ""
+
+#: builtin/branch.c:398
+#, c-format
+msgid "ahead %d, behind %d] "
+msgstr ""
+
+#: builtin/branch.c:501
+msgid "(no branch)"
+msgstr ""
+
+#: builtin/branch.c:562
+msgid "some refs could not be read"
+msgstr ""
+
+#: builtin/branch.c:575
+msgid "cannot rename the current branch while not on any."
+msgstr ""
+
+#: builtin/branch.c:585
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr ""
+
+#: builtin/branch.c:600
+msgid "Branch rename failed"
+msgstr ""
+
+#: builtin/branch.c:604
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr ""
+
+#: builtin/branch.c:608
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr ""
+
+#: builtin/branch.c:615
+msgid "Branch is renamed, but update of config-file failed"
+msgstr ""
+
+#: builtin/branch.c:630
+#, c-format
+msgid "malformed object name %s"
+msgstr ""
+
+#: builtin/branch.c:654
+#, c-format
+msgid "could not write branch description template: %s\n"
+msgstr ""
+
+#: builtin/branch.c:742
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr ""
+
+#: builtin/branch.c:747 builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr ""
+
+#: builtin/branch.c:805
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr ""
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr ""
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr ""
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr ""
+
+#: builtin/checkout.c:113 builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr ""
+
+#: builtin/checkout.c:115 builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr ""
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr ""
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr ""
+
+#: builtin/checkout.c:212 builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr ""
+
+#: builtin/checkout.c:234 builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr ""
+
+#: builtin/checkout.c:264 builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr ""
+
+#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
+#: builtin/merge.c:809
+msgid "unable to write new index file"
+msgstr ""
+
+#: builtin/checkout.c:319 builtin/diff.c:298 builtin/merge.c:406
+msgid "diff_setup_done failed"
+msgstr ""
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr ""
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:565
+msgid "HEAD is now at"
+msgstr ""
+
+#: builtin/checkout.c:572
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:575
+#, c-format
+msgid "Already on '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:579
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:581
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:583
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:639
+#, c-format
+msgid " ... and %d more.\n"
+msgstr ""
+
+#. The singular version
+#: builtin/checkout.c:645
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/checkout.c:663
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+
+#: builtin/checkout.c:692
+msgid "internal error in revision walk"
+msgstr ""
+
+#: builtin/checkout.c:696
+msgid "Previous HEAD position was"
+msgstr ""
+
+#: builtin/checkout.c:722
+msgid "You are on a branch yet to be born"
+msgstr ""
+
+#. case (1)
+#: builtin/checkout.c:853
+#, c-format
+msgid "invalid reference: %s"
+msgstr ""
+
+#. case (1): want a tree
+#: builtin/checkout.c:892
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr ""
+
+#: builtin/checkout.c:972
+msgid "-B cannot be used with -b"
+msgstr ""
+
+#: builtin/checkout.c:981
+msgid "--patch is incompatible with all other options"
+msgstr ""
+
+#: builtin/checkout.c:984
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr ""
+
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -t"
+msgstr ""
+
+#: builtin/checkout.c:992
+msgid "--track needs a branch name"
+msgstr ""
+
+#: builtin/checkout.c:999
+msgid "Missing branch name; try -b"
+msgstr ""
+
+#: builtin/checkout.c:1005
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr ""
+
+#: builtin/checkout.c:1007
+msgid "--orphan cannot be used with -t"
+msgstr ""
+
+#: builtin/checkout.c:1017
+msgid "git checkout: -f and -m are incompatible"
+msgstr ""
+
+#: builtin/checkout.c:1051
+msgid "invalid path specification"
+msgstr ""
+
+#: builtin/checkout.c:1059
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+
+#: builtin/checkout.c:1061
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr ""
+
+#: builtin/checkout.c:1066
+msgid "git checkout: --detach does not take a path argument"
+msgstr ""
+
+#: builtin/checkout.c:1069
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+
+#: builtin/checkout.c:1088
+msgid "Cannot switch branch to a non-commit."
+msgstr ""
+
+#: builtin/checkout.c:1091
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr ""
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr ""
+
+#: builtin/clean.c:82
+msgid ""
+"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+
+#: builtin/clean.c:85
+msgid ""
+"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
+"clean"
+msgstr ""
+
+#: builtin/clean.c:155 builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:159 builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr ""
+
+#: builtin/clean.c:162 builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr ""
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr ""
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr ""
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr ""
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr ""
+
+#: builtin/clone.c:308 builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr ""
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr ""
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr ""
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr ""
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr ""
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr ""
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr ""
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr ""
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr ""
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr ""
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr ""
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr ""
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr ""
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr ""
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr ""
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr ""
+
+#: builtin/clone.c:706 builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr ""
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr ""
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr ""
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr ""
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr ""
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr ""
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr ""
+
+#: builtin/commit.c:42
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+
+#: builtin/commit.c:54
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+
+#: builtin/commit.c:59
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+
+#: builtin/commit.c:205 builtin/reset.c:33
+msgid "merge"
+msgstr ""
+
+#: builtin/commit.c:208
+msgid "cherry-pick"
+msgstr ""
+
+#: builtin/commit.c:325
+msgid "failed to unpack HEAD tree object"
+msgstr ""
+
+#: builtin/commit.c:367
+msgid "unable to create temporary index"
+msgstr ""
+
+#: builtin/commit.c:373
+msgid "interactive add failed"
+msgstr ""
+
+#: builtin/commit.c:406 builtin/commit.c:427 builtin/commit.c:473
+msgid "unable to write new_index file"
+msgstr ""
+
+#: builtin/commit.c:457
+#, c-format
+msgid "cannot do a partial commit during a %s."
+msgstr ""
+
+#: builtin/commit.c:466
+msgid "cannot read the index"
+msgstr ""
+
+#: builtin/commit.c:486
+msgid "unable to write temporary index file"
+msgstr ""
+
+#: builtin/commit.c:550 builtin/commit.c:556
+#, c-format
+msgid "invalid commit: %s"
+msgstr ""
+
+#: builtin/commit.c:579
+msgid "malformed --author parameter"
+msgstr ""
+
+#: builtin/commit.c:635
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr ""
+
+#: builtin/commit.c:670 builtin/commit.c:703 builtin/commit.c:1000
+#, c-format
+msgid "could not lookup commit %s"
+msgstr ""
+
+#: builtin/commit.c:682 builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr ""
+
+#: builtin/commit.c:684
+msgid "could not read log from standard input"
+msgstr ""
+
+#: builtin/commit.c:688
+#, c-format
+msgid "could not read log file '%s'"
+msgstr ""
+
+#: builtin/commit.c:694
+msgid "commit has empty message"
+msgstr ""
+
+#: builtin/commit.c:710
+msgid "could not read MERGE_MSG"
+msgstr ""
+
+#: builtin/commit.c:714
+msgid "could not read SQUASH_MSG"
+msgstr ""
+
+#: builtin/commit.c:718
+#, c-format
+msgid "could not read '%s'"
+msgstr ""
+
+#: builtin/commit.c:746
+#, c-format
+msgid "could not open '%s'"
+msgstr ""
+
+#: builtin/commit.c:770
+msgid "could not write commit template"
+msgstr ""
+
+#: builtin/commit.c:783
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a %s.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+
+#: builtin/commit.c:796
+msgid "Please enter the commit message for your changes."
+msgstr ""
+
+#: builtin/commit.c:799
+msgid ""
+" Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+
+#: builtin/commit.c:804
+msgid ""
+" Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+
+#: builtin/commit.c:816
+#, c-format
+msgid "%sAuthor: %s"
+msgstr ""
+
+#: builtin/commit.c:823
+#, c-format
+msgid "%sCommitter: %s"
+msgstr ""
+
+#: builtin/commit.c:843
+msgid "Cannot read index"
+msgstr ""
+
+#: builtin/commit.c:880
+msgid "Error building trees"
+msgstr ""
+
+#: builtin/commit.c:895 builtin/tag.c:357
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr ""
+
+#: builtin/commit.c:975
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr ""
+
+#: builtin/commit.c:990 builtin/commit.c:1182
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr ""
+
+#: builtin/commit.c:1030
+msgid "Using both --reset-author and --author does not make sense"
+msgstr ""
+
+#: builtin/commit.c:1041
+msgid "You have nothing to amend."
+msgstr ""
+
+#: builtin/commit.c:1043
+#, c-format
+msgid "You are in the middle of a %s -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1045
+msgid "Options --squash and --fixup cannot be used together"
+msgstr ""
+
+#: builtin/commit.c:1055
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr ""
+
+#: builtin/commit.c:1057
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr ""
+
+#: builtin/commit.c:1063
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr ""
+
+#: builtin/commit.c:1080
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr ""
+
+#: builtin/commit.c:1082
+msgid "No paths with --include/--only does not make sense."
+msgstr ""
+
+#: builtin/commit.c:1084
+msgid "Clever... amending the last one with dirty index."
+msgstr ""
+
+#: builtin/commit.c:1086
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr ""
+
+#: builtin/commit.c:1096 builtin/tag.c:556
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr ""
+
+#: builtin/commit.c:1101
+msgid "Paths with -a does not make sense."
+msgstr ""
+
+#: builtin/commit.c:1280
+msgid "couldn't look up newly created commit"
+msgstr ""
+
+#: builtin/commit.c:1282
+msgid "could not parse newly created commit"
+msgstr ""
+
+#: builtin/commit.c:1323
+msgid "detached HEAD"
+msgstr ""
+
+#: builtin/commit.c:1325
+msgid " (root-commit)"
+msgstr ""
+
+#: builtin/commit.c:1415
+msgid "could not parse HEAD commit"
+msgstr ""
+
+#: builtin/commit.c:1452 builtin/merge.c:507
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr ""
+
+#: builtin/commit.c:1459
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr ""
+
+#: builtin/commit.c:1466
+msgid "could not read MERGE_MODE"
+msgstr ""
+
+#: builtin/commit.c:1485
+#, c-format
+msgid "could not read commit message: %s"
+msgstr ""
+
+#: builtin/commit.c:1499
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr ""
+
+#: builtin/commit.c:1514 builtin/merge.c:933 builtin/merge.c:966
+msgid "failed to write commit object"
+msgstr ""
+
+#: builtin/commit.c:1535
+msgid "cannot lock HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1539
+msgid "cannot update HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1550
+msgid ""
+"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."
+msgstr ""
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr ""
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr ""
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr ""
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr ""
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr ""
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr ""
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr ""
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr ""
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr ""
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr ""
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr ""
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr ""
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr ""
+
+#: builtin/diff.c:293
+msgid "Not a git repository"
+msgstr ""
+
+#: builtin/diff.c:343
+#, c-format
+msgid "invalid object '%s' given."
+msgstr ""
+
+#: builtin/diff.c:348
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:358
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:366
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr ""
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr ""
+
+#: builtin/fetch.c:252
+#, c-format
+msgid "object %s not found"
+msgstr ""
+
+#: builtin/fetch.c:258
+msgid "[up to date]"
+msgstr ""
+
+#: builtin/fetch.c:272
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr ""
+
+#: builtin/fetch.c:273 builtin/fetch.c:351
+msgid "[rejected]"
+msgstr ""
+
+#: builtin/fetch.c:284
+msgid "[tag update]"
+msgstr ""
+
+#: builtin/fetch.c:286 builtin/fetch.c:313 builtin/fetch.c:331
+msgid " (unable to update local ref)"
+msgstr ""
+
+#: builtin/fetch.c:298
+msgid "[new tag]"
+msgstr ""
+
+#: builtin/fetch.c:302
+msgid "[new branch]"
+msgstr ""
+
+#: builtin/fetch.c:347
+msgid "unable to update local ref"
+msgstr ""
+
+#: builtin/fetch.c:347
+msgid "forced update"
+msgstr ""
+
+#: builtin/fetch.c:353
+msgid "(non-fast-forward)"
+msgstr ""
+
+#: builtin/fetch.c:384 builtin/fetch.c:676
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr ""
+
+#: builtin/fetch.c:393
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr ""
+
+#: builtin/fetch.c:479
+#, c-format
+msgid "From %.*s\n"
+msgstr ""
+
+#: builtin/fetch.c:490
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+
+#: builtin/fetch.c:540
+#, c-format
+msgid " (%s will become dangling)\n"
+msgstr ""
+
+#: builtin/fetch.c:541
+#, c-format
+msgid " (%s has become dangling)\n"
+msgstr ""
+
+#: builtin/fetch.c:548
+msgid "[deleted]"
+msgstr ""
+
+#: builtin/fetch.c:549
+msgid "(none)"
+msgstr ""
+
+#: builtin/fetch.c:666
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr ""
+
+#: builtin/fetch.c:700
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr ""
+
+#: builtin/fetch.c:777
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr ""
+
+#: builtin/fetch.c:780
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr ""
+
+#: builtin/fetch.c:879
+#, c-format
+msgid "Fetching %s\n"
+msgstr ""
+
+#: builtin/fetch.c:881
+#, c-format
+msgid "Could not fetch %s"
+msgstr ""
+
+#: builtin/fetch.c:898
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr ""
+
+#: builtin/fetch.c:918
+msgid "You need to specify a tag name."
+msgstr ""
+
+#: builtin/fetch.c:970
+msgid "fetch --all does not take a repository argument"
+msgstr ""
+
+#: builtin/fetch.c:972
+msgid "fetch --all does not make sense with refspecs"
+msgstr ""
+
+#: builtin/fetch.c:983
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr ""
+
+#: builtin/fetch.c:991
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr ""
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr ""
+
+#: builtin/gc.c:78
+msgid "Too many options specified"
+msgstr ""
+
+#: builtin/gc.c:103
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr ""
+
+#: builtin/gc.c:223
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr ""
+
+#: builtin/gc.c:226
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+
+#: builtin/gc.c:256
+msgid ""
+"There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr ""
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr ""
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr ""
+
+#: builtin/grep.c:478 builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr ""
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr ""
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr ""
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr ""
+
+#: builtin/grep.c:889
+msgid "no pattern given."
+msgstr ""
+
+#: builtin/grep.c:903
+#, c-format
+msgid "bad object %s"
+msgstr ""
+
+#: builtin/grep.c:944
+msgid "--open-files-in-pager only works on the worktree"
+msgstr ""
+
+#: builtin/grep.c:967
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr ""
+
+#: builtin/grep.c:972
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr ""
+
+#: builtin/grep.c:975
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr ""
+
+#: builtin/grep.c:983
+msgid "both --cached and trees are given."
+msgstr ""
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr ""
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr ""
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr ""
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr ""
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr ""
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr ""
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr ""
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr ""
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr ""
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr ""
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr ""
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr ""
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr ""
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr ""
+
+#: builtin/init-db.c:322 builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr ""
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr ""
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr ""
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr ""
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr ""
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr ""
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr ""
+
+#: builtin/init-db.c:521 builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr ""
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr ""
+
+#: builtin/init-db.c:554
+#, c-format
+msgid ""
+"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
+"dir=<directory>)"
+msgstr ""
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr ""
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr ""
+
+#: builtin/log.c:185
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr ""
+
+#: builtin/log.c:393 builtin/log.c:479
+#, c-format
+msgid "Could not read object %s"
+msgstr ""
+
+#: builtin/log.c:503
+#, c-format
+msgid "Unknown type: %d"
+msgstr ""
+
+#: builtin/log.c:592
+msgid "format.headers without value"
+msgstr ""
+
+#: builtin/log.c:665
+msgid "name of output directory is too long"
+msgstr ""
+
+#: builtin/log.c:676
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr ""
+
+#: builtin/log.c:690
+msgid "Need exactly one range."
+msgstr ""
+
+#: builtin/log.c:698
+msgid "Not a range."
+msgstr ""
+
+#: builtin/log.c:735
+msgid "Could not extract email from committer identity."
+msgstr ""
+
+#: builtin/log.c:781
+msgid "Cover letter needs email format"
+msgstr ""
+
+#: builtin/log.c:875
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr ""
+
+#: builtin/log.c:948
+msgid "Two output directories?"
+msgstr ""
+
+#: builtin/log.c:1169
+#, c-format
+msgid "bogus committer info %s"
+msgstr ""
+
+#: builtin/log.c:1214
+msgid "-n and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1216
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1221 builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr ""
+
+#: builtin/log.c:1224
+msgid "--name-only does not make sense"
+msgstr ""
+
+#: builtin/log.c:1226
+msgid "--name-status does not make sense"
+msgstr ""
+
+#: builtin/log.c:1228
+msgid "--check does not make sense"
+msgstr ""
+
+#: builtin/log.c:1251
+msgid "standard output, or directory, which one?"
+msgstr ""
+
+#: builtin/log.c:1253
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr ""
+
+#: builtin/log.c:1406
+msgid "Failed to create output files"
+msgstr ""
+
+#: builtin/log.c:1510
+#, c-format
+msgid ""
+"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr ""
+
+#: builtin/log.c:1526 builtin/log.c:1528 builtin/log.c:1540
+#, c-format
+msgid "Unknown commit %s"
+msgstr ""
+
+#: builtin/merge.c:91
+msgid "switch `m' requires a value"
+msgstr ""
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr ""
+
+#: builtin/merge.c:129
+#, c-format
+msgid "Available strategies are:"
+msgstr ""
+
+#: builtin/merge.c:134
+#, c-format
+msgid "Available custom strategies are:"
+msgstr ""
+
+#: builtin/merge.c:241
+msgid "could not run stash."
+msgstr ""
+
+#: builtin/merge.c:246
+msgid "stash failed"
+msgstr ""
+
+#: builtin/merge.c:251
+#, c-format
+msgid "not a valid object: %s"
+msgstr ""
+
+#: builtin/merge.c:270 builtin/merge.c:287
+msgid "read-tree failed"
+msgstr ""
+
+#: builtin/merge.c:317
+msgid " (nothing to squash)"
+msgstr ""
+
+#: builtin/merge.c:330
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:362
+msgid "Writing SQUASH_MSG"
+msgstr ""
+
+#: builtin/merge.c:364
+msgid "Finishing SQUASH_MSG"
+msgstr ""
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:435
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr ""
+
+#: builtin/merge.c:534
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr ""
+
+#: builtin/merge.c:627
+msgid "git write-tree failed to write a tree"
+msgstr ""
+
+#: builtin/merge.c:677
+msgid "failed to read the cache"
+msgstr ""
+
+#: builtin/merge.c:694
+msgid "Unable to write index."
+msgstr ""
+
+#: builtin/merge.c:707
+msgid "Not handling anything other than two heads merge."
+msgstr ""
+
+#: builtin/merge.c:721
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr ""
+
+#: builtin/merge.c:735
+#, c-format
+msgid "unable to write %s"
+msgstr ""
+
+#: builtin/merge.c:874
+#, c-format
+msgid "Could not read from '%s'"
+msgstr ""
+
+#: builtin/merge.c:883
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:889
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+
+#: builtin/merge.c:913
+msgid "Empty commit message."
+msgstr ""
+
+#: builtin/merge.c:925
+#, c-format
+msgid "Wonderful.\n"
+msgstr ""
+
+#: builtin/merge.c:998
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr ""
+
+#: builtin/merge.c:1014
+#, c-format
+msgid "'%s' is not a commit"
+msgstr ""
+
+#: builtin/merge.c:1055
+msgid "No current branch."
+msgstr ""
+
+#: builtin/merge.c:1057
+msgid "No remote for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1059
+msgid "No default upstream defined for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1064
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr ""
+
+#: builtin/merge.c:1186
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr ""
+
+#: builtin/merge.c:1202 git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1205 git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1209
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1212
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1221
+msgid "You cannot combine --squash with --no-ff."
+msgstr ""
+
+#: builtin/merge.c:1226
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr ""
+
+#: builtin/merge.c:1233
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr ""
+
+#: builtin/merge.c:1264
+msgid "Can merge only exactly one commit into empty head"
+msgstr ""
+
+#: builtin/merge.c:1267
+msgid "Squash commit into empty head not supported yet"
+msgstr ""
+
+#: builtin/merge.c:1269
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr ""
+
+#: builtin/merge.c:1273 builtin/merge.c:1317
+#, c-format
+msgid "%s - not something we can merge"
+msgstr ""
+
+#: builtin/merge.c:1383
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr ""
+
+#: builtin/merge.c:1421
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr ""
+
+#: builtin/merge.c:1428
+#, c-format
+msgid "Nope.\n"
+msgstr ""
+
+#: builtin/merge.c:1460
+msgid "Not possible to fast-forward, aborting."
+msgstr ""
+
+#: builtin/merge.c:1483 builtin/merge.c:1560
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr ""
+
+#: builtin/merge.c:1487
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr ""
+
+#: builtin/merge.c:1551
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:1553
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr ""
+
+#: builtin/merge.c:1562
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr ""
+
+#: builtin/merge.c:1573
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr ""
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr ""
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr ""
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr ""
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr ""
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr ""
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr ""
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr ""
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr ""
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr ""
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr ""
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr ""
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr ""
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr ""
+
+#: builtin/mv.c:215
+#, c-format
+msgid "renaming '%s' failed"
+msgstr ""
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr ""
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:175 builtin/tag.c:343
+#, c-format
+msgid "could not create file '%s'"
+msgstr ""
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr ""
+
+#: builtin/notes.c:210 builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr ""
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr ""
+
+#: builtin/notes.c:251 builtin/tag.c:521
+#, c-format
+msgid "cannot read '%s'"
+msgstr ""
+
+#: builtin/notes.c:253 builtin/tag.c:524
+#, c-format
+msgid "could not open or read '%s'"
+msgstr ""
+
+#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
+#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
+#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
+#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:537
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr ""
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr ""
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr ""
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr ""
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr ""
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr ""
+
+#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
+#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr ""
+
+#: builtin/notes.c:513 builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr ""
+
+#: builtin/notes.c:580
+#, c-format
+msgid ""
+"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:585 builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr ""
+
+#: builtin/notes.c:656
+#, c-format
+msgid ""
+"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr ""
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr ""
+
+#: builtin/notes.c:1103
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2310
+#, c-format
+msgid "unsupported index version %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2314
+#, c-format
+msgid "bad index version '%s'"
+msgstr ""
+
+#: builtin/pack-objects.c:2322
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr ""
+
+#: builtin/pack-objects.c:2326
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr ""
+
+#: builtin/push.c:44
+msgid "tag shorthand without <tag>"
+msgstr ""
+
+#: builtin/push.c:63
+msgid "--delete only accepts plain target ref names"
+msgstr ""
+
+#: builtin/push.c:73
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+
+#: builtin/push.c:80
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+
+#: builtin/push.c:88
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr ""
+
+#: builtin/push.c:111
+msgid ""
+"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr ""
+
+#: builtin/push.c:131
+#, c-format
+msgid "Pushing to %s\n"
+msgstr ""
+
+#: builtin/push.c:135
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr ""
+
+#: builtin/push.c:143
+#, c-format
+msgid ""
+"To prevent you from losing history, non-fast-forward updates were rejected\n"
+"Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"
+"'Note about fast-forwards' section of 'git push --help' for details.\n"
+msgstr ""
+
+#: builtin/push.c:160
+#, c-format
+msgid "bad repository '%s'"
+msgstr ""
+
+#: builtin/push.c:161
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote "
+"repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+
+#: builtin/push.c:176
+msgid "--all and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:177
+msgid "--all can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:182
+msgid "--mirror and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:183
+msgid "--mirror can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:188
+msgid "--all and --mirror are incompatible"
+msgstr ""
+
+#: builtin/push.c:274
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr ""
+
+#: builtin/push.c:276
+msgid "--delete doesn't make sense without any refs"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr ""
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr ""
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr ""
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr ""
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr ""
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr ""
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr ""
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr ""
+
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr ""
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr ""
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr ""
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr ""
+
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr ""
+
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr ""
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr ""
+
+#: builtin/revert.c:70 builtin/revert.c:91
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr ""
+
+#: builtin/revert.c:126
+msgid "program error"
+msgstr ""
+
+#: builtin/revert.c:209
+msgid "revert failed"
+msgstr ""
+
+#: builtin/revert.c:224
+msgid "cherry-pick failed"
+msgstr ""
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr ""
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr ""
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr ""
+
+#: builtin/tag.c:58
+#, c-format
+msgid "malformed object at '%s'"
+msgstr ""
+
+#: builtin/tag.c:205
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr ""
+
+#: builtin/tag.c:210
+#, c-format
+msgid "tag '%s' not found."
+msgstr ""
+
+#: builtin/tag.c:225
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr ""
+
+#: builtin/tag.c:237
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr ""
+
+#: builtin/tag.c:247
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+
+#: builtin/tag.c:254
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you "
+"want to.\n"
+"#\n"
+msgstr ""
+
+#: builtin/tag.c:294
+msgid "unable to sign the tag"
+msgstr ""
+
+#: builtin/tag.c:296
+msgid "unable to write tag file"
+msgstr ""
+
+#: builtin/tag.c:321
+msgid "bad object type."
+msgstr ""
+
+#: builtin/tag.c:334
+msgid "tag header too big."
+msgstr ""
+
+#: builtin/tag.c:366
+msgid "no tag message?"
+msgstr ""
+
+#: builtin/tag.c:372
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr ""
+
+#: builtin/tag.c:421
+msgid "switch 'points-at' requires an object"
+msgstr ""
+
+#: builtin/tag.c:423
+#, c-format
+msgid "malformed object name '%s'"
+msgstr ""
+
+#: builtin/tag.c:502
+msgid "-n option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:504
+msgid "--contains option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:506
+msgid "--points-at option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:514
+msgid "only one -F or -m option is allowed."
+msgstr ""
+
+#: builtin/tag.c:534
+msgid "too many params"
+msgstr ""
+
+#: builtin/tag.c:540
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr ""
+
+#: builtin/tag.c:545
+#, c-format
+msgid "tag '%s' already exists"
+msgstr ""
+
+#: builtin/tag.c:563
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr ""
+
+#: builtin/tag.c:565
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr ""
+
+#: builtin/tag.c:567
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr ""
+
+#: git-am.sh:49
+msgid "You need to set your committer info first"
+msgstr ""
+
+#: git-am.sh:135
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr ""
+
+#: git-am.sh:144
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+
+#: git-am.sh:153
+msgid "Falling back to patching base and 3-way merge..."
+msgstr ""
+
+#: git-am.sh:265
+msgid "Only one StGIT patch series can be applied at once"
+msgstr ""
+
+#: git-am.sh:352
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr ""
+
+#: git-am.sh:354
+msgid "Patch format detection failed."
+msgstr ""
+
+#: git-am.sh:406
+msgid "-d option is no longer supported. Do not use."
+msgstr ""
+
+#: git-am.sh:469
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr ""
+
+#: git-am.sh:474
+msgid "Please make up your mind. --skip or --abort?"
+msgstr ""
+
+#: git-am.sh:501
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr ""
+
+#: git-am.sh:567
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr ""
+
+#: git-am.sh:743
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:754
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr ""
+
+#: git-am.sh:790
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr ""
+
+#: git-am.sh:835
+msgid "No changes -- Patch already applied."
+msgstr ""
+
+#: git-am.sh:861
+msgid "applying to an empty history"
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr ""
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr ""
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr ""
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr ""
+
+#: git-bisect.sh:130
+#, sh-format
+msgid ""
+"Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr ""
+
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr ""
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr ""
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr ""
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr ""
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr ""
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr ""
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr ""
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr ""
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr ""
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr ""
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr ""
+
+#: git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr ""
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr ""
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr ""
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr ""
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr ""
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr ""
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr ""
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr ""
+
+#: git-stash.sh:123 git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr ""
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr ""
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr ""
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr ""
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr ""
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr ""
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr ""
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr ""
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr ""
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr ""
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr ""
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr ""
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr ""
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr ""
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr ""
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr ""
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr ""
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr ""
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr ""
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr ""
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr ""
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr ""
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr ""
+
+#: git-submodule.sh:108
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$path'"
+msgstr ""
+
+#: git-submodule.sh:173
+#, sh-format
+msgid "Clone of '$url' into submodule path '$path' failed"
+msgstr ""
+
+#: git-submodule.sh:247
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr ""
+
+#: git-submodule.sh:264
+#, sh-format
+msgid "'$path' already exists in the index"
+msgstr ""
+
+#: git-submodule.sh:281
+#, sh-format
+msgid "'$path' already exists and is not a valid git repo"
+msgstr ""
+
+#: git-submodule.sh:295
+#, sh-format
+msgid "Unable to checkout submodule '$path'"
+msgstr ""
+
+#: git-submodule.sh:300
+#, sh-format
+msgid "Failed to add submodule '$path'"
+msgstr ""
+
+#: git-submodule.sh:305
+#, sh-format
+msgid "Failed to register submodule '$path'"
+msgstr ""
+
+#: git-submodule.sh:347
+#, sh-format
+msgid "Entering '$prefix$path'"
+msgstr ""
+
+#: git-submodule.sh:359
+#, sh-format
+msgid "Stopping at '$path'; script returned non-zero status."
+msgstr ""
+
+#: git-submodule.sh:401
+#, sh-format
+msgid "No url found for submodule path '$path' in .gitmodules"
+msgstr ""
+
+#: git-submodule.sh:410
+#, sh-format
+msgid "Failed to register url for submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:418
+#, sh-format
+msgid "Failed to register update mode for submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:420
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$path'"
+msgstr ""
+
+#: git-submodule.sh:519
+#, sh-format
+msgid ""
+"Submodule path '$path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+
+#: git-submodule.sh:532
+#, sh-format
+msgid "Unable to find current revision in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:551
+#, sh-format
+msgid "Unable to fetch in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:565
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:566
+#, sh-format
+msgid "Submodule path '$path': rebased into '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:571
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:572
+#, sh-format
+msgid "Submodule path '$path': merged in '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:577
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:578
+#, sh-format
+msgid "Submodule path '$path': checked out '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:600 git-submodule.sh:923
+#, sh-format
+msgid "Failed to recurse into submodule path '$path'"
+msgstr ""
+
+#: git-submodule.sh:708
+msgid "--"
+msgstr ""
+
+#: git-submodule.sh:766
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr ""
+
+#: git-submodule.sh:769
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr ""
+
+#: git-submodule.sh:772
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr ""
+
+#: git-submodule.sh:797
+msgid "blob"
+msgstr ""
+
+#: git-submodule.sh:798
+msgid "submodule"
+msgstr ""
+
+#: git-submodule.sh:969
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr ""
diff --git a/po/pt_PT.po b/po/pt_PT.po
new file mode 100644
index 0000000..517ec29
--- /dev/null
+++ b/po/pt_PT.po
@@ -0,0 +1,4980 @@
+# Portuguese translations for Git package.
+# Copyright (C) 2012 Marco Sousa <marcomsousa AT gmail.com>
+# This file is distributed under the same license as the Git package.
+# Contributers:
+# - Marco Sousa <marcomsousa AT gmail.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-05-08 16:06+0800\n"
+"PO-Revision-Date: 2012-05-14 21:17+0100\n"
+"Last-Translator: Marco Sousa <marcomsousa AT gmail.com>\n"
+"Language-Team: Portuguese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pt\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: advice.c:40
+#, c-format
+msgid "hint: %.*s\n"
+msgstr "dica: %.*s\n"
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:70
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr ""
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr "cabeçalho não reconhecido: %s%s (%d)"
+
+#: bundle.c:89
+#: builtin/commit.c:753
+#, c-format
+msgid "could not open '%s'"
+msgstr "não é possivel abrir '%s'"
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr ""
+
+#: bundle.c:164
+#: sequencer.c:533
+#: sequencer.c:965
+#: builtin/log.c:289
+#: builtin/log.c:719
+#: builtin/log.c:1335
+#: builtin/log.c:1554
+#: builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr ""
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] ""
+msgstr[1] ""
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr "rev-list morreu"
+
+#: bundle.c:296
+#: builtin/log.c:1231
+#: builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr "argumento não reconhecido: %s"
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr ""
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr ""
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr "Não foi possível pawn pack-objects"
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr ""
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr "não consegue crear '%s'"
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr ""
+
+#: commit.c:48
+#, c-format
+msgid "could not parse %s"
+msgstr "não consigo parsear %s"
+
+#: commit.c:50
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr "%s %s não é um commit!"
+
+#: compat/obstack.c:406
+#: compat/obstack.c:408
+msgid "memory exhausted"
+msgstr "memória esgotada"
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr "Não foi possível executar 'git rev-list'"
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr ""
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr ""
+
+#: date.c:95
+msgid "in the future"
+msgstr ""
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: date.c:154
+#: date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: diff.c:105
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr ""
+
+#: diff.c:110
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr ""
+
+#: diff.c:210
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+
+#: diff.c:1400
+msgid " 0 files changed\n"
+msgstr " 0 ficheros modificados\n"
+
+#: diff.c:1404
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] " %d ficheiro modificado"
+msgstr[1] " %d ficheiros modificados"
+
+#: diff.c:1421
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ", %d adição(+)"
+msgstr[1] ", %d adições(+)"
+
+#: diff.c:1432
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ", %d eliminado(-)"
+msgstr[1] ", %d eliminados(-)"
+
+#: diff.c:3478
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr "não consegue ejecutar gpg."
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr ""
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr ""
+
+#: grep.c:1280
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr ""
+
+#: grep.c:1297
+#, c-format
+msgid "'%s': %s"
+msgstr "'%s': %s"
+
+#: grep.c:1308
+#, c-format
+msgid "'%s': short read %s"
+msgstr ""
+
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr ""
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr ""
+
+#: help.c:270
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr ""
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr ""
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr ""
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1607
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] "A sua rama está à frente de '%s' pelo commit %d.\n"
+msgstr[1] "A sua rama está à frente de '%s' pelos commites %d.\n"
+
+#: remote.c:1613
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural "Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: remote.c:1621
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: sequencer.c:121
+#: builtin/merge.c:865
+#: builtin/merge.c:978
+#: builtin/merge.c:1088
+#: builtin/merge.c:1098
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr ""
+
+#: sequencer.c:123
+#: builtin/merge.c:333
+#: builtin/merge.c:868
+#: builtin/merge.c:1090
+#: builtin/merge.c:1103
+#, c-format
+msgid "Could not write to '%s'"
+msgstr "Não foi possível escrever para '%s'"
+
+#: sequencer.c:144
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'"
+msgstr ""
+
+#: sequencer.c:147
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+
+#: sequencer.c:160
+#: sequencer.c:741
+#: sequencer.c:824
+#, c-format
+msgid "Could not write to %s"
+msgstr "Não foi possível gravar para %s"
+
+#: sequencer.c:163
+#, c-format
+msgid "Error wrapping up %s"
+msgstr ""
+
+#: sequencer.c:178
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr ""
+
+#: sequencer.c:180
+msgid "Your local changes would be overwritten by revert."
+msgstr ""
+
+#: sequencer.c:183
+msgid "Commit your changes or stash them to proceed."
+msgstr ""
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:233
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr ""
+
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr ""
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr ""
+
+#: sequencer.c:323
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr "Não foi possível analisar commit %s\n"
+
+#: sequencer.c:328
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr "Não foi possível analisar commit parent %s\n"
+
+#: sequencer.c:358
+msgid "Your index file is unmerged."
+msgstr "O seu ficheiro de índice é não fundido."
+
+#: sequencer.c:361
+msgid "You do not have a valid HEAD"
+msgstr "Você não tem uma HEAD válida"
+
+#: sequencer.c:376
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr ""
+
+#: sequencer.c:384
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr ""
+
+#: sequencer.c:388
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr ""
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:399
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr ""
+
+#: sequencer.c:403
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr "Não é possível obter mensagem commit para %s"
+
+#: sequencer.c:491
+#, c-format
+msgid "could not revert %s... %s"
+msgstr ""
+
+#: sequencer.c:492
+#, c-format
+msgid "could not apply %s... %s"
+msgstr ""
+
+#: sequencer.c:536
+msgid "empty commit set passed"
+msgstr "passado commit com o set vazio"
+
+#: sequencer.c:544
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr ""
+
+#: sequencer.c:549
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr ""
+
+#: sequencer.c:607
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr "Não foi possível abrir %s durante um %s"
+
+#: sequencer.c:629
+#, c-format
+msgid "Could not parse line %d."
+msgstr "Não foi possível parsear linha %d."
+
+#: sequencer.c:634
+msgid "No commits parsed."
+msgstr "Nenhum commit parseado."
+
+#: sequencer.c:647
+#, c-format
+msgid "Could not open %s"
+msgstr "Não foi possível abrir %s"
+
+#: sequencer.c:651
+#, c-format
+msgid "Could not read %s."
+msgstr "Não foi possível ler %s."
+
+#: sequencer.c:658
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr ""
+
+#: sequencer.c:686
+#, c-format
+msgid "Invalid key: %s"
+msgstr ""
+
+#: sequencer.c:689
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr "Valor inválido para %s: %s"
+
+#: sequencer.c:701
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr ""
+
+#: sequencer.c:722
+msgid "a cherry-pick or revert is already in progress"
+msgstr ""
+
+#: sequencer.c:723
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr ""
+
+#: sequencer.c:727
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr ""
+
+#: sequencer.c:743
+#: sequencer.c:828
+#, c-format
+msgid "Error wrapping up %s."
+msgstr ""
+
+#: sequencer.c:762
+#: sequencer.c:896
+msgid "no cherry-pick or revert in progress"
+msgstr ""
+
+#: sequencer.c:764
+msgid "cannot resolve HEAD"
+msgstr ""
+
+#: sequencer.c:766
+msgid "cannot abort from a branch yet to be born"
+msgstr ""
+
+#: sequencer.c:788
+#: builtin/apply.c:3682
+#, c-format
+msgid "cannot open %s: %s"
+msgstr "não foi possível abrir %s: %s"
+
+#: sequencer.c:791
+#, c-format
+msgid "cannot read %s: %s"
+msgstr "não foi possível ler %s: %s"
+
+#: sequencer.c:792
+msgid "unexpected end of file"
+msgstr ""
+
+#: sequencer.c:798
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr ""
+
+#: sequencer.c:821
+#, c-format
+msgid "Could not format %s."
+msgstr "Não foi possível formatear %s."
+
+#: sequencer.c:983
+msgid "Can't revert as initial commit"
+msgstr ""
+
+#: sequencer.c:984
+msgid "Can't cherry-pick into empty head"
+msgstr ""
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr ""
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "Não existe rama '%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr ""
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr ""
+
+#: wt-status.c:135
+msgid "Unmerged paths:"
+msgstr "caminhos não fundidos:"
+
+#: wt-status.c:141
+#: wt-status.c:158
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:143
+#: wt-status.c:160
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr ""
+
+#: wt-status.c:144
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr " (usa \"git add/rm <ficheiro>...\" para marcar como resolvido)"
+
+#: wt-status.c:152
+msgid "Changes to be committed:"
+msgstr "Mudanças a serem commitadas"
+
+#: wt-status.c:170
+msgid "Changes not staged for commit:"
+msgstr ""
+
+#: wt-status.c:174
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr " (usa \"git add <ficheiro>...\" para actualizar o que vai ser commitado)"
+
+#: wt-status.c:176
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr " (usa \"git add/rm <ficheiro>...\" para actualizar o que vai ser commitado)"
+
+#: wt-status.c:177
+msgid " (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr ""
+
+#: wt-status.c:179
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr ""
+
+#: wt-status.c:188
+#, c-format
+msgid "%s files:"
+msgstr "%s ficheros:"
+
+#: wt-status.c:191
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr ""
+
+#: wt-status.c:208
+msgid "bug"
+msgstr "erro"
+
+#: wt-status.c:213
+msgid "both deleted:"
+msgstr "eliminados em ambos:"
+
+#: wt-status.c:214
+msgid "added by us:"
+msgstr "adicionado por nós:"
+
+#: wt-status.c:215
+msgid "deleted by them:"
+msgstr "eliminados por eles:"
+
+#: wt-status.c:216
+msgid "added by them:"
+msgstr "adicionados por eles:"
+
+#: wt-status.c:217
+msgid "deleted by us:"
+msgstr "eliminados por nós:"
+
+#: wt-status.c:218
+msgid "both added:"
+msgstr "adicionados em ambos:"
+
+#: wt-status.c:219
+msgid "both modified:"
+msgstr "modificados em ambos:"
+
+#: wt-status.c:249
+msgid "new commits, "
+msgstr "novos commits, "
+
+#: wt-status.c:251
+msgid "modified content, "
+msgstr "conteúdo modificado, "
+
+#: wt-status.c:253
+msgid "untracked content, "
+msgstr "conteúdo não seguido"
+
+#: wt-status.c:267
+#, c-format
+msgid "new file: %s"
+msgstr "novo ficheiro: %s"
+
+#: wt-status.c:270
+#, c-format
+msgid "copied: %s -> %s"
+msgstr "copiado: %s -> %s"
+
+#: wt-status.c:273
+#, c-format
+msgid "deleted: %s"
+msgstr "eliminado: %s"
+
+#: wt-status.c:276
+#, c-format
+msgid "modified: %s"
+msgstr "modificado: %s"
+
+#: wt-status.c:279
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr "mudado de nome: %s -> %s"
+
+#: wt-status.c:282
+#, c-format
+msgid "typechange: %s"
+msgstr ""
+
+#: wt-status.c:285
+#, c-format
+msgid "unknown: %s"
+msgstr "desconhecido: %s"
+
+#: wt-status.c:288
+#, c-format
+msgid "unmerged: %s"
+msgstr "não fundidos: %s"
+
+#: wt-status.c:291
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr ""
+
+#: wt-status.c:737
+msgid "On branch "
+msgstr "Na rama"
+
+#: wt-status.c:744
+msgid "Not currently on any branch."
+msgstr "Não está em nenhuma rama."
+
+#: wt-status.c:755
+msgid "Initial commit"
+msgstr "Commit inicial"
+
+#: wt-status.c:769
+msgid "Untracked"
+msgstr "Não seguido"
+
+#: wt-status.c:771
+msgid "Ignored"
+msgstr "Ignorado"
+
+#: wt-status.c:773
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr ""
+
+#: wt-status.c:775
+msgid " (use -u option to show untracked files)"
+msgstr ""
+
+#: wt-status.c:781
+msgid "No changes"
+msgstr "Sem mudanças"
+
+#: wt-status.c:785
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr "nenhuma alteração adicionado ao commit%s\n"
+
+#: wt-status.c:787
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr " (usa \"git add\" e/ou \"git commit -a\")"
+
+#: wt-status.c:789
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr ""
+
+#: wt-status.c:791
+msgid " (use \"git add\" to track)"
+msgstr " (usa \"git add\" para seguir)"
+
+#: wt-status.c:793
+#: wt-status.c:796
+#: wt-status.c:799
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr "nada para fazer commit%s\n"
+
+#: wt-status.c:794
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr ""
+
+#: wt-status.c:797
+msgid " (use -u to show untracked files)"
+msgstr ""
+
+#: wt-status.c:800
+msgid " (working directory clean)"
+msgstr " (directório de trabalho vacio)"
+
+#: wt-status.c:908
+msgid "HEAD (no branch)"
+msgstr "HEAD (Não é rama)"
+
+#: wt-status.c:914
+msgid "Initial commit on "
+msgstr "Commit inicial em "
+
+#: wt-status.c:929
+msgid "behind "
+msgstr "atrás "
+
+#: wt-status.c:932
+#: wt-status.c:935
+msgid "ahead "
+msgstr "a frente "
+
+#: wt-status.c:937
+msgid ", behind "
+msgstr ", atrás "
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr ""
+
+#: builtin/add.c:67
+#: builtin/commit.c:282
+msgid "updating files failed"
+msgstr "Falou a atualização dos ficheiros"
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr "eliminar '%s'\n"
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr ""
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr ""
+
+#: builtin/add.c:195
+#: builtin/add.c:456
+#: builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr ""
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr ""
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr "Não foi possível ler o indíce"
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr "Não foi possível abrir '%s' para escrever."
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr "Não consegue escrever patch"
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr ""
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr "Patch vazio. Aborted."
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr "Não foi possível aplicar o '%s'"
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr ""
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr ""
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr "nenhum ficheiros adicionado"
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr "falhou a adicionar ficheiros"
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr ""
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr ""
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr ""
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr ""
+
+#: builtin/add.c:420
+#: builtin/clean.c:95
+#: builtin/commit.c:342
+#: builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr "ficheiro index corrupto"
+
+#: builtin/add.c:476
+#: builtin/apply.c:4093
+#: builtin/mv.c:229
+#: builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr ""
+
+#: builtin/apply.c:106
+#, c-format
+msgid "unrecognized whitespace option '%s'"
+msgstr "espaço em braco não reconhecido: '%s'"
+
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
+msgstr ""
+
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
+msgstr ""
+
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
+msgstr ""
+
+#: builtin/apply.c:905
+#, c-format
+msgid "unable to find filename in patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:934
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
+msgstr ""
+
+#: builtin/apply.c:937
+#, c-format
+msgid "git apply: bad git-diff - inconsistent %s filename on line %d"
+msgstr ""
+
+#: builtin/apply.c:944
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
+msgstr ""
+
+#: builtin/apply.c:1387
+#, c-format
+msgid "recount: unexpected line: %.*s"
+msgstr ""
+
+#: builtin/apply.c:1444
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
+msgstr ""
+
+#: builtin/apply.c:1461
+#, c-format
+msgid "git diff header lacks filename information when removing %d leading pathname component (line %d)"
+msgid_plural "git diff header lacks filename information when removing %d leading pathname components (line %d)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:1621
+msgid "new file depends on old contents"
+msgstr ""
+
+#: builtin/apply.c:1623
+msgid "deleted file still has contents"
+msgstr ""
+
+#: builtin/apply.c:1649
+#, c-format
+msgid "corrupt patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:1685
+#, c-format
+msgid "new file %s depends on old contents"
+msgstr ""
+
+#: builtin/apply.c:1687
+#, c-format
+msgid "deleted file %s still has contents"
+msgstr ""
+
+#: builtin/apply.c:1690
+#, c-format
+msgid "** warning: file %s becomes empty but is not deleted"
+msgstr ""
+
+#: builtin/apply.c:1836
+#, c-format
+msgid "corrupt binary patch at line %d: %.*s"
+msgstr ""
+
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1865
+#, c-format
+msgid "unrecognized binary patch at line %d"
+msgstr ""
+
+#: builtin/apply.c:1951
+#, c-format
+msgid "patch with only garbage at line %d"
+msgstr ""
+
+#: builtin/apply.c:2041
+#, c-format
+msgid "unable to read symlink %s"
+msgstr ""
+
+#: builtin/apply.c:2045
+#, c-format
+msgid "unable to open or read %s"
+msgstr "Não foi possível abrir o ler %s"
+
+#: builtin/apply.c:2116
+msgid "oops"
+msgstr ""
+
+#: builtin/apply.c:2638
+#, c-format
+msgid "invalid start of line: '%c'"
+msgstr "começo de linha inválido: '%c'"
+
+#: builtin/apply.c:2756
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:2768
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
+msgstr ""
+
+#: builtin/apply.c:2774
+#, c-format
+msgid ""
+"while searching for:\n"
+"%.*s"
+msgstr ""
+
+#: builtin/apply.c:2793
+#, c-format
+msgid "missing binary patch data for '%s'"
+msgstr ""
+
+#: builtin/apply.c:2896
+#, c-format
+msgid "binary patch does not apply to '%s'"
+msgstr ""
+
+#: builtin/apply.c:2902
+#, c-format
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
+msgstr ""
+
+#: builtin/apply.c:2923
+#, c-format
+msgid "patch failed: %s:%ld"
+msgstr ""
+
+#: builtin/apply.c:3038
+#, c-format
+msgid "patch %s has been renamed/deleted"
+msgstr ""
+
+#: builtin/apply.c:3045
+#: builtin/apply.c:3062
+#, c-format
+msgid "read of %s failed"
+msgstr "ler %s falhou"
+
+#: builtin/apply.c:3077
+msgid "removal patch leaves file contents"
+msgstr ""
+
+#: builtin/apply.c:3098
+#, c-format
+msgid "%s: already exists in working directory"
+msgstr "%s: já existe no espaço de trabalho"
+
+#: builtin/apply.c:3136
+#, c-format
+msgid "%s: has been deleted/renamed"
+msgstr ""
+
+#: builtin/apply.c:3141
+#: builtin/apply.c:3172
+#, c-format
+msgid "%s: %s"
+msgstr "%s: %s"
+
+#: builtin/apply.c:3152
+#, c-format
+msgid "%s: does not exist in index"
+msgstr ""
+
+#: builtin/apply.c:3166
+#, c-format
+msgid "%s: does not match index"
+msgstr "%s: não tem correspondencia ao index"
+
+#: builtin/apply.c:3183
+#, c-format
+msgid "%s: wrong type"
+msgstr ""
+
+#: builtin/apply.c:3185
+#, c-format
+msgid "%s has type %o, expected %o"
+msgstr ""
+
+#: builtin/apply.c:3240
+#, c-format
+msgid "%s: already exists in index"
+msgstr "%s: já existe no indíce"
+
+#: builtin/apply.c:3259
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o)%s%s"
+msgstr ""
+
+#: builtin/apply.c:3265
+#, c-format
+msgid "%s: patch does not apply"
+msgstr ""
+
+#: builtin/apply.c:3278
+#, c-format
+msgid "Checking patch %s..."
+msgstr ""
+
+#: builtin/apply.c:3333
+#: builtin/checkout.c:212
+#: builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr ""
+
+#: builtin/apply.c:3476
+#, c-format
+msgid "unable to remove %s from index"
+msgstr ""
+
+#: builtin/apply.c:3503
+#, c-format
+msgid "corrupt patch for subproject %s"
+msgstr ""
+
+#: builtin/apply.c:3507
+#, c-format
+msgid "unable to stat newly created file '%s'"
+msgstr "não é possivel 'stat' o novo ficheiro creado '%s'"
+
+#: builtin/apply.c:3512
+#, c-format
+msgid "unable to create backing store for newly created file %s"
+msgstr ""
+
+#: builtin/apply.c:3515
+#, c-format
+msgid "unable to add cache entry for %s"
+msgstr ""
+
+#: builtin/apply.c:3548
+#, c-format
+msgid "closing file '%s'"
+msgstr "fechar fichero '%s'"
+
+#: builtin/apply.c:3597
+#, c-format
+msgid "unable to write file '%s' mode %o"
+msgstr ""
+
+#: builtin/apply.c:3653
+#, c-format
+msgid "Applied patch %s cleanly."
+msgstr ""
+
+#: builtin/apply.c:3661
+msgid "internal error"
+msgstr ""
+
+#. Say this even without --verbose
+#: builtin/apply.c:3664
+#, c-format
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:3674
+#, c-format
+msgid "truncating .rej filename to %.*s.rej"
+msgstr ""
+
+#: builtin/apply.c:3695
+#, c-format
+msgid "Hunk #%d applied cleanly."
+msgstr ""
+
+#: builtin/apply.c:3698
+#, c-format
+msgid "Rejected hunk #%d."
+msgstr ""
+
+#: builtin/apply.c:3829
+msgid "unrecognized input"
+msgstr "entrada não reconhecida"
+
+#: builtin/apply.c:3840
+msgid "unable to read index file"
+msgstr "Não foi possível ler o fichero indíce"
+
+#: builtin/apply.c:4035
+msgid "--index outside a repository"
+msgstr "--index fora de um repositorio"
+
+#: builtin/apply.c:4038
+msgid "--cached outside a repository"
+msgstr "--cached fora de um repositorio"
+
+#: builtin/apply.c:4054
+#, c-format
+msgid "can't open patch '%s'"
+msgstr "não é possivel abrir patch '%s'"
+
+#: builtin/apply.c:4068
+#, c-format
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/apply.c:4074
+#: builtin/apply.c:4084
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr ""
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr ""
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr ""
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr ""
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr ""
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr "erro remoto: %s"
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr ""
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr ""
+
+#: builtin/branch.c:144
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+
+#: builtin/branch.c:148
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+
+#: builtin/branch.c:180
+msgid "cannot use -a with -d"
+msgstr "Não é possível usar -a com um -d"
+
+#: builtin/branch.c:186
+msgid "Couldn't look up commit object for HEAD"
+msgstr ""
+
+#: builtin/branch.c:191
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr ""
+
+#: builtin/branch.c:202
+#, c-format
+msgid "remote branch '%s' not found."
+msgstr "rama remota '%s não encontrada."
+
+#: builtin/branch.c:203
+#, c-format
+msgid "branch '%s' not found."
+msgstr "rama '%s' não encontrado."
+
+#: builtin/branch.c:210
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr ""
+
+#: builtin/branch.c:216
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+
+#: builtin/branch.c:225
+#, c-format
+msgid "Error deleting remote branch '%s'"
+msgstr ""
+
+#: builtin/branch.c:226
+#, c-format
+msgid "Error deleting branch '%s'"
+msgstr "Erro a eliminar rama '%s'"
+
+#: builtin/branch.c:233
+#, c-format
+msgid "Deleted remote branch %s (was %s).\n"
+msgstr ""
+
+#: builtin/branch.c:234
+#, c-format
+msgid "Deleted branch %s (was %s).\n"
+msgstr "Eliminar rama %s (era %s).\n"
+
+#: builtin/branch.c:239
+msgid "Update of config-file failed"
+msgstr ""
+
+#: builtin/branch.c:337
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr ""
+
+#: builtin/branch.c:409
+#, c-format
+msgid "behind %d] "
+msgstr "atrás %d] "
+
+#: builtin/branch.c:411
+#, c-format
+msgid "ahead %d] "
+msgstr "a frente %d] "
+
+#: builtin/branch.c:413
+#, c-format
+msgid "ahead %d, behind %d] "
+msgstr "a frente %d, atrás %d] "
+
+#: builtin/branch.c:521
+msgid "(no branch)"
+msgstr "(não é rama)"
+
+#: builtin/branch.c:586
+msgid "some refs could not be read"
+msgstr ""
+
+#: builtin/branch.c:599
+msgid "cannot rename the current branch while not on any."
+msgstr ""
+
+#: builtin/branch.c:609
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr "Nome da rama inválida: '%s'"
+
+#: builtin/branch.c:624
+msgid "Branch rename failed"
+msgstr "Falhou renomeação da rama"
+
+#: builtin/branch.c:628
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr "Renomeado uma rama erronea '%s'"
+
+#: builtin/branch.c:632
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr ""
+
+#: builtin/branch.c:639
+msgid "Branch is renamed, but update of config-file failed"
+msgstr ""
+
+#: builtin/branch.c:654
+#, c-format
+msgid "malformed object name %s"
+msgstr ""
+
+#: builtin/branch.c:678
+#, c-format
+msgid "could not write branch description template: %s"
+msgstr ""
+
+#: builtin/branch.c:769
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr ""
+
+#: builtin/branch.c:774
+#: builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr ""
+
+#: builtin/branch.c:794
+msgid "--column and --verbose are incompatible"
+msgstr "--column e --verbose são incompatíveis"
+
+#: builtin/branch.c:843
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr ""
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr "%s está bem\n"
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr ""
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr ""
+
+#: builtin/checkout.c:113
+#: builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr ""
+
+#: builtin/checkout.c:115
+#: builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr ""
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr ""
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr ""
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr ""
+
+#: builtin/checkout.c:234
+#: builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr "ficheiro index corrupto"
+
+#: builtin/checkout.c:264
+#: builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr ""
+
+#: builtin/checkout.c:302
+#: builtin/checkout.c:498
+#: builtin/clone.c:583
+#: builtin/merge.c:812
+msgid "unable to write new index file"
+msgstr ""
+
+#: builtin/checkout.c:319
+#: builtin/diff.c:302
+#: builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr ""
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr ""
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:566
+msgid "HEAD is now at"
+msgstr "HEAD é agora em "
+
+#: builtin/checkout.c:573
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr "Reset rama '%s'\n"
+
+#: builtin/checkout.c:576
+#, c-format
+msgid "Already on '%s'\n"
+msgstr "Já em '%s'\n"
+
+#: builtin/checkout.c:580
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr ""
+
+#: builtin/checkout.c:582
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr "Mudado para a nova rama '%s'\n"
+
+#: builtin/checkout.c:584
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr "Mudado para a rama '%s'\n"
+
+#: builtin/checkout.c:640
+#, c-format
+msgid " ... and %d more.\n"
+msgstr " ... e %d mais.\n"
+
+#. The singular version
+#: builtin/checkout.c:646
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/checkout.c:664
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+
+#: builtin/checkout.c:693
+msgid "internal error in revision walk"
+msgstr ""
+
+#: builtin/checkout.c:697
+msgid "Previous HEAD position was"
+msgstr ""
+
+#: builtin/checkout.c:723
+msgid "You are on a branch yet to be born"
+msgstr ""
+
+#. case (1)
+#: builtin/checkout.c:854
+#, c-format
+msgid "invalid reference: %s"
+msgstr ""
+
+#. case (1): want a tree
+#: builtin/checkout.c:893
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr ""
+
+#: builtin/checkout.c:973
+msgid "-B cannot be used with -b"
+msgstr ""
+
+#: builtin/checkout.c:982
+msgid "--patch is incompatible with all other options"
+msgstr ""
+
+#: builtin/checkout.c:985
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr ""
+
+#: builtin/checkout.c:987
+msgid "--detach cannot be used with -t"
+msgstr ""
+
+#: builtin/checkout.c:993
+msgid "--track needs a branch name"
+msgstr ""
+
+#: builtin/checkout.c:1000
+msgid "Missing branch name; try -b"
+msgstr ""
+
+#: builtin/checkout.c:1006
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr ""
+
+#: builtin/checkout.c:1008
+msgid "--orphan cannot be used with -t"
+msgstr ""
+
+#: builtin/checkout.c:1018
+msgid "git checkout: -f and -m are incompatible"
+msgstr ""
+
+#: builtin/checkout.c:1052
+msgid "invalid path specification"
+msgstr ""
+
+#: builtin/checkout.c:1060
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+
+#: builtin/checkout.c:1062
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr ""
+
+#: builtin/checkout.c:1067
+msgid "git checkout: --detach does not take a path argument"
+msgstr ""
+
+#: builtin/checkout.c:1070
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+
+#: builtin/checkout.c:1089
+msgid "Cannot switch branch to a non-commit."
+msgstr ""
+
+#: builtin/checkout.c:1092
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr ""
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr ""
+
+#: builtin/clean.c:82
+msgid "clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+
+#: builtin/clean.c:85
+msgid "clean.requireForce defaults to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+
+#: builtin/clean.c:155
+#: builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:159
+#: builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr "Eliminando %s\n"
+
+#: builtin/clean.c:162
+#: builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr ""
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr ""
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr ""
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr ""
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr "falhou a abrir '%s'"
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr "falhou a criar o directório '%s'"
+
+#: builtin/clone.c:308
+#: builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr ""
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr ""
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr ""
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr ""
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr ""
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr "falhou a copiar o ficheiro para '%s'"
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr "terminado.\n"
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr ""
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr ""
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr "Demasiados parametros."
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr ""
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr ""
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr ""
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr ""
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr ""
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr ""
+
+#: builtin/clone.c:706
+#: builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr ""
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr ""
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr "Clonando em um repositorio nu (bare) '%s'...\n"
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr "Clonar em '%s'...\n"
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr "Não sei como clonar %s"
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr ""
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr ""
+
+#: builtin/column.c:51
+msgid "--command must be the first argument"
+msgstr ""
+
+#: builtin/commit.c:43
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+"O seu nome e endereço de e-mail foram configurados automaticamente com base\n"
+"no o seu usuario e nome da maquina. Por favor, verifique se eles são precisos.\n"
+"Você pode suprimir esta mensagem, configurando-los explicitamente:\n"
+"\n"
+" git config --global user.name \"O teu Nome\"\n"
+" git config --global user.email tu@examplo.com\n"
+"\n"
+"Após fazer isso, você pode corregir a identidade usada em este commit com:\n"
+"\n"
+" git commit --amend --reset-author\n"
+
+#: builtin/commit.c:55
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+
+#: builtin/commit.c:60
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+
+#: builtin/commit.c:309
+msgid "failed to unpack HEAD tree object"
+msgstr ""
+
+#: builtin/commit.c:351
+msgid "unable to create temporary index"
+msgstr ""
+
+#: builtin/commit.c:357
+msgid "interactive add failed"
+msgstr "falhou adicionar interativo"
+
+#: builtin/commit.c:390
+#: builtin/commit.c:411
+#: builtin/commit.c:461
+msgid "unable to write new_index file"
+msgstr ""
+
+#: builtin/commit.c:442
+msgid "cannot do a partial commit during a merge."
+msgstr ""
+
+#: builtin/commit.c:444
+msgid "cannot do a partial commit during a cherry-pick."
+msgstr ""
+
+#: builtin/commit.c:454
+msgid "cannot read the index"
+msgstr "não foi possível ler o indíce"
+
+#: builtin/commit.c:474
+msgid "unable to write temporary index file"
+msgstr ""
+
+#: builtin/commit.c:549
+#: builtin/commit.c:555
+#, c-format
+msgid "invalid commit: %s"
+msgstr "commit inválido: %s"
+
+#: builtin/commit.c:578
+msgid "malformed --author parameter"
+msgstr ""
+
+#: builtin/commit.c:639
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr ""
+
+#: builtin/commit.c:677
+#: builtin/commit.c:710
+#: builtin/commit.c:1024
+#, c-format
+msgid "could not lookup commit %s"
+msgstr ""
+
+#: builtin/commit.c:689
+#: builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr ""
+
+#: builtin/commit.c:691
+msgid "could not read log from standard input"
+msgstr ""
+
+#: builtin/commit.c:695
+#, c-format
+msgid "could not read log file '%s'"
+msgstr "não é possivel ler o ficheiro de log '%s'"
+
+#: builtin/commit.c:701
+msgid "commit has empty message"
+msgstr "a mensagem do commit está vazia"
+
+#: builtin/commit.c:717
+msgid "could not read MERGE_MSG"
+msgstr "não é possivel ler MERGE_MSG"
+
+#: builtin/commit.c:721
+msgid "could not read SQUASH_MSG"
+msgstr "não é possivel ler SQUASH_MSG"
+
+#: builtin/commit.c:725
+#, c-format
+msgid "could not read '%s'"
+msgstr "não é possivel ler '%s'"
+
+#: builtin/commit.c:777
+msgid "could not write commit template"
+msgstr ""
+
+#: builtin/commit.c:788
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a merge.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+
+#: builtin/commit.c:793
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+
+#: builtin/commit.c:805
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+
+#: builtin/commit.c:810
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+
+#: builtin/commit.c:823
+#, c-format
+msgid "%sAuthor: %s"
+msgstr "%sAutor: %s"
+
+#: builtin/commit.c:830
+#, c-format
+msgid "%sCommitter: %s"
+msgstr "%sCommitador: %s"
+
+#: builtin/commit.c:850
+msgid "Cannot read index"
+msgstr ""
+
+#: builtin/commit.c:887
+msgid "Error building trees"
+msgstr ""
+
+#: builtin/commit.c:902
+#: builtin/tag.c:361
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr ""
+
+#: builtin/commit.c:999
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr ""
+
+#: builtin/commit.c:1014
+#: builtin/commit.c:1214
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr ""
+
+#: builtin/commit.c:1054
+msgid "Using both --reset-author and --author does not make sense"
+msgstr ""
+
+#: builtin/commit.c:1065
+msgid "You have nothing to amend."
+msgstr "Você não tem nada a corregir."
+
+#: builtin/commit.c:1068
+msgid "You are in the middle of a merge -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1070
+msgid "You are in the middle of a cherry-pick -- cannot amend."
+msgstr ""
+
+#: builtin/commit.c:1073
+msgid "Options --squash and --fixup cannot be used together"
+msgstr ""
+
+#: builtin/commit.c:1083
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr ""
+
+#: builtin/commit.c:1085
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr ""
+
+#: builtin/commit.c:1093
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr ""
+
+#: builtin/commit.c:1110
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr ""
+
+#: builtin/commit.c:1112
+msgid "No paths with --include/--only does not make sense."
+msgstr ""
+
+#: builtin/commit.c:1114
+msgid "Clever... amending the last one with dirty index."
+msgstr ""
+
+#: builtin/commit.c:1116
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr ""
+
+#: builtin/commit.c:1126
+#: builtin/tag.c:577
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr ""
+
+#: builtin/commit.c:1131
+msgid "Paths with -a does not make sense."
+msgstr ""
+
+#: builtin/commit.c:1315
+msgid "couldn't look up newly created commit"
+msgstr ""
+
+#: builtin/commit.c:1317
+msgid "could not parse newly created commit"
+msgstr ""
+
+#: builtin/commit.c:1358
+msgid "detached HEAD"
+msgstr ""
+
+#: builtin/commit.c:1360
+msgid " (root-commit)"
+msgstr " (root-commit)"
+
+#: builtin/commit.c:1450
+msgid "could not parse HEAD commit"
+msgstr ""
+
+#: builtin/commit.c:1487
+#: builtin/merge.c:509
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr ""
+
+#: builtin/commit.c:1494
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr ""
+
+#: builtin/commit.c:1501
+msgid "could not read MERGE_MODE"
+msgstr ""
+
+#: builtin/commit.c:1520
+#, c-format
+msgid "could not read commit message: %s"
+msgstr ""
+
+#: builtin/commit.c:1534
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
+msgstr ""
+
+#: builtin/commit.c:1539
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr ""
+
+#: builtin/commit.c:1554
+#: builtin/merge.c:936
+#: builtin/merge.c:961
+msgid "failed to write commit object"
+msgstr ""
+
+#: builtin/commit.c:1575
+msgid "cannot lock HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1579
+msgid "cannot update HEAD ref"
+msgstr ""
+
+#: builtin/commit.c:1590
+msgid ""
+"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."
+msgstr ""
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr ""
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr ""
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr ""
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr ""
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr ""
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr ""
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr ""
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr ""
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr ""
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr "Nenhum nome encontrado, não descreve nada."
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr ""
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr ""
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr ""
+
+#: builtin/diff.c:297
+msgid "Not a git repository"
+msgstr "Não é um repositorio git"
+
+#: builtin/diff.c:347
+#, c-format
+msgid "invalid object '%s' given."
+msgstr ""
+
+#: builtin/diff.c:352
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:362
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr ""
+
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr ""
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr ""
+
+#: builtin/fetch.c:253
+#, c-format
+msgid "object %s not found"
+msgstr "objecto %s não encontrado"
+
+#: builtin/fetch.c:259
+msgid "[up to date]"
+msgstr "[Actualizada]"
+
+#: builtin/fetch.c:273
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr ""
+
+#: builtin/fetch.c:274
+#: builtin/fetch.c:360
+msgid "[rejected]"
+msgstr "[rejeitado]"
+
+#: builtin/fetch.c:285
+msgid "[tag update]"
+msgstr "[etiqueta actualizada]"
+
+#: builtin/fetch.c:287
+#: builtin/fetch.c:322
+#: builtin/fetch.c:340
+msgid " (unable to update local ref)"
+msgstr ""
+
+#: builtin/fetch.c:305
+msgid "[new tag]"
+msgstr "[nova etiqueta]"
+
+#: builtin/fetch.c:308
+msgid "[new branch]"
+msgstr "[nova rama]"
+
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[nova ref]"
+
+#: builtin/fetch.c:356
+msgid "unable to update local ref"
+msgstr ""
+
+#: builtin/fetch.c:356
+msgid "forced update"
+msgstr "actualização forçada"
+
+#: builtin/fetch.c:362
+msgid "(non-fast-forward)"
+msgstr "(non-fast-forward)"
+
+#: builtin/fetch.c:393
+#: builtin/fetch.c:685
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr "não é possivel abrir %s: %s\n"
+
+#: builtin/fetch.c:402
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr ""
+
+#: builtin/fetch.c:488
+#, c-format
+msgid "From %.*s\n"
+msgstr "Para %.*s\n"
+
+#: builtin/fetch.c:499
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+
+#: builtin/fetch.c:549
+#, c-format
+msgid " (%s will become dangling)"
+msgstr ""
+
+#: builtin/fetch.c:550
+#, c-format
+msgid " (%s has become dangling)"
+msgstr ""
+
+#: builtin/fetch.c:557
+msgid "[deleted]"
+msgstr "[eliminado]"
+
+#: builtin/fetch.c:558
+#: builtin/remote.c:1055
+msgid "(none)"
+msgstr "(nenhum)"
+
+#: builtin/fetch.c:675
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr ""
+
+#: builtin/fetch.c:709
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr ""
+
+#: builtin/fetch.c:786
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr ""
+
+#: builtin/fetch.c:789
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr ""
+
+#: builtin/fetch.c:888
+#, c-format
+msgid "Fetching %s\n"
+msgstr "Baixando %s\n"
+
+#: builtin/fetch.c:890
+#: builtin/remote.c:100
+#, c-format
+msgid "Could not fetch %s"
+msgstr ""
+
+#: builtin/fetch.c:907
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr ""
+"Nenhum repositório remoto especificado. Por favor, especifique um URL ou o\n"
+"nome remoto a partir do qual novas revisões devem ser obtida."
+
+#: builtin/fetch.c:927
+msgid "You need to specify a tag name."
+msgstr "Você precisa especificar um nome da etiqueta."
+
+#: builtin/fetch.c:979
+msgid "fetch --all does not take a repository argument"
+msgstr ""
+
+#: builtin/fetch.c:981
+msgid "fetch --all does not make sense with refspecs"
+msgstr ""
+
+#: builtin/fetch.c:992
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr ""
+
+#: builtin/fetch.c:1000
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr ""
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr "Inválido %s: '%s'"
+
+#: builtin/gc.c:90
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr ""
+
+#: builtin/gc.c:221
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr ""
+
+#: builtin/gc.c:224
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+
+#: builtin/gc.c:251
+msgid "There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr ""
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr ""
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr ""
+
+#: builtin/grep.c:478
+#: builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr ""
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr ""
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr ""
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr ""
+
+#: builtin/grep.c:888
+msgid "no pattern given."
+msgstr ""
+
+#: builtin/grep.c:902
+#, c-format
+msgid "bad object %s"
+msgstr ""
+
+#: builtin/grep.c:943
+msgid "--open-files-in-pager only works on the worktree"
+msgstr ""
+
+#: builtin/grep.c:966
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr ""
+
+#: builtin/grep.c:971
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr ""
+
+#: builtin/grep.c:974
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr ""
+
+#: builtin/grep.c:982
+msgid "both --cached and trees are given."
+msgstr ""
+
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr "formato ajuda não reconhecido '%s'"
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr ""
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr ""
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr ""
+
+#: builtin/help.c:126
+#: builtin/help.c:154
+#: builtin/help.c:163
+#: builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr ""
+
+#: builtin/help.c:211
+#, c-format
+msgid ""
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
+msgstr ""
+
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
+msgstr ""
+
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr ""
+
+#: builtin/help.c:355
+#, c-format
+msgid "'%s': unknown man viewer."
+msgstr ""
+
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr ""
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr ""
+
+#: builtin/help.c:391
+#, c-format
+msgid "'%s': not a documentation directory."
+msgstr ""
+
+#: builtin/help.c:432
+#: builtin/help.c:439
+#, c-format
+msgid "usage: %s%s"
+msgstr ""
+
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
+msgstr ""
+
+#: builtin/index-pack.c:84
+#, c-format
+msgid "object type mismatch at %s"
+msgstr ""
+
+#: builtin/index-pack.c:104
+msgid "object of unexpected type"
+msgstr ""
+
+#: builtin/index-pack.c:141
+#, c-format
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:151
+msgid "early EOF"
+msgstr ""
+
+#: builtin/index-pack.c:152
+msgid "read error on input"
+msgstr ""
+
+#: builtin/index-pack.c:164
+msgid "used more bytes than were available"
+msgstr ""
+
+#: builtin/index-pack.c:171
+msgid "pack too large for current definition of off_t"
+msgstr ""
+
+#: builtin/index-pack.c:187
+#, c-format
+msgid "unable to create '%s'"
+msgstr "não é possivel crear '%s'"
+
+#: builtin/index-pack.c:192
+#, c-format
+msgid "cannot open packfile '%s'"
+msgstr "Não é possivel abrir o ficheiro packfile '%s'"
+
+#: builtin/index-pack.c:206
+msgid "pack signature mismatch"
+msgstr ""
+
+#: builtin/index-pack.c:226
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
+msgstr ""
+
+#: builtin/index-pack.c:300
+#, c-format
+msgid "inflate returned %d"
+msgstr ""
+
+#: builtin/index-pack.c:345
+msgid "offset value overflow for delta base object"
+msgstr ""
+
+#: builtin/index-pack.c:353
+msgid "delta base offset is out of bound"
+msgstr ""
+
+#: builtin/index-pack.c:361
+#, c-format
+msgid "unknown object type %d"
+msgstr "ojecto com tipo desconhecido %d"
+
+#: builtin/index-pack.c:390
+msgid "cannot pread pack file"
+msgstr "Não é possivel pread pack file"
+
+#: builtin/index-pack.c:392
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:405
+msgid "serious inflate inconsistency"
+msgstr ""
+
+#: builtin/index-pack.c:476
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "não foi possível ler objecto existente %s"
+
+#: builtin/index-pack.c:479
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
+msgstr ""
+
+#: builtin/index-pack.c:488
+#, c-format
+msgid "invalid blob object %s"
+msgstr "inválido objecto blob %s"
+
+#: builtin/index-pack.c:500
+#, c-format
+msgid "invalid %s"
+msgstr "inválido: %s"
+
+#: builtin/index-pack.c:502
+msgid "Error in object"
+msgstr ""
+
+#: builtin/index-pack.c:504
+#, c-format
+msgid "Not all child objects of %s are reachable"
+msgstr ""
+
+#: builtin/index-pack.c:576
+#: builtin/index-pack.c:602
+msgid "failed to apply delta"
+msgstr ""
+
+#: builtin/index-pack.c:706
+msgid "Receiving objects"
+msgstr ""
+
+#: builtin/index-pack.c:706
+msgid "Indexing objects"
+msgstr ""
+
+#: builtin/index-pack.c:728
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr ""
+
+#: builtin/index-pack.c:733
+msgid "cannot fstat packfile"
+msgstr "Não é possivel fstat packfile"
+
+#: builtin/index-pack.c:736
+msgid "pack has junk at the end"
+msgstr ""
+
+#: builtin/index-pack.c:754
+msgid "Resolving deltas"
+msgstr "Resolvendo deltas"
+
+#: builtin/index-pack.c:787
+#, c-format
+msgid "unable to deflate appended object (%d)"
+msgstr ""
+
+#: builtin/index-pack.c:866
+#, c-format
+msgid "local object %s is corrupt"
+msgstr ""
+
+#: builtin/index-pack.c:890
+msgid "error while closing pack file"
+msgstr ""
+
+#: builtin/index-pack.c:903
+#, c-format
+msgid "cannot write keep file '%s'"
+msgstr "não é possivel escrever o fichero kepp '%s'"
+
+#: builtin/index-pack.c:911
+#, c-format
+msgid "cannot close written keep file '%s'"
+msgstr "Não é possivel fechar o fichero escrito '%s'"
+
+#: builtin/index-pack.c:924
+msgid "cannot store pack file"
+msgstr "Não é possivel guardar o fichero pack"
+
+#: builtin/index-pack.c:935
+msgid "cannot store index file"
+msgstr "Não é possivel guardar fichero index"
+
+#: builtin/index-pack.c:1024
+#, c-format
+msgid "Cannot open existing pack file '%s'"
+msgstr "Não é possivel abrir o existente ficheiro pack %s"
+
+#: builtin/index-pack.c:1026
+#, c-format
+msgid "Cannot open existing pack idx file for '%s'"
+msgstr "Não é possivel abrir o ficheiro 'pack idx' para '%s'"
+
+#: builtin/index-pack.c:1073
+#, c-format
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:1080
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/index-pack.c:1107
+msgid "Cannot come back to cwd"
+msgstr ""
+
+#: builtin/index-pack.c:1140
+#: builtin/index-pack.c:1143
+#: builtin/index-pack.c:1155
+#: builtin/index-pack.c:1159
+#, c-format
+msgid "bad %s"
+msgstr "inválido %s"
+
+#: builtin/index-pack.c:1173
+msgid "--fix-thin cannot be used without --stdin"
+msgstr ""
+
+#: builtin/index-pack.c:1177
+#: builtin/index-pack.c:1187
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr ""
+
+#: builtin/index-pack.c:1196
+msgid "--verify with no packfile name given"
+msgstr ""
+
+#: builtin/index-pack.c:1220
+msgid "confusion beyond insanity"
+msgstr ""
+
+#: builtin/index-pack.c:1239
+#, c-format
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr ""
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr ""
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr ""
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr ""
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr ""
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr ""
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr ""
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr ""
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr ""
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr ""
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr ""
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr ""
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr ""
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr ""
+
+#: builtin/init-db.c:322
+#: builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr "%s já existe"
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr ""
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr ""
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr ""
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr ""
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr "Inicializada vazio"
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr " partilhado"
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr ""
+
+#: builtin/init-db.c:521
+#: builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr ""
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr ""
+
+#: builtin/init-db.c:554
+#, c-format
+msgid "%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-dir=<directory>)"
+msgstr ""
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr ""
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr ""
+
+#: builtin/log.c:188
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr ""
+
+#: builtin/log.c:401
+#: builtin/log.c:489
+#, c-format
+msgid "Could not read object %s"
+msgstr ""
+
+#: builtin/log.c:513
+#, c-format
+msgid "Unknown type: %d"
+msgstr "Tipo desconhecido: %d"
+
+#: builtin/log.c:602
+msgid "format.headers without value"
+msgstr ""
+
+#: builtin/log.c:675
+msgid "name of output directory is too long"
+msgstr "nome do diretório de saída é demasiado longo"
+
+#: builtin/log.c:686
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr "Não é possivel abrir o ficheiro patch %s"
+
+#: builtin/log.c:700
+msgid "Need exactly one range."
+msgstr "Necessita de exatamente um intervalo."
+
+#: builtin/log.c:708
+msgid "Not a range."
+msgstr "Não é um intervalo."
+
+#: builtin/log.c:745
+msgid "Could not extract email from committer identity."
+msgstr "Não foi possível extrair a identidade do committer do e-mail."
+
+#: builtin/log.c:791
+msgid "Cover letter needs email format"
+msgstr "Carta de apresentação necessita um modelo de e-mail"
+
+#: builtin/log.c:885
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr ""
+
+#: builtin/log.c:958
+msgid "Two output directories?"
+msgstr "Dois diretórios de saída?"
+
+#: builtin/log.c:1179
+#, c-format
+msgid "bogus committer info %s"
+msgstr ""
+
+#: builtin/log.c:1224
+msgid "-n and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1226
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr ""
+
+#: builtin/log.c:1234
+msgid "--name-only does not make sense"
+msgstr ""
+
+#: builtin/log.c:1236
+msgid "--name-status does not make sense"
+msgstr ""
+
+#: builtin/log.c:1238
+msgid "--check does not make sense"
+msgstr ""
+
+#: builtin/log.c:1261
+msgid "standard output, or directory, which one?"
+msgstr "saída padrão, ou diretório, qual deles?"
+
+#: builtin/log.c:1263
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr ""
+
+#: builtin/log.c:1416
+msgid "Failed to create output files"
+msgstr "Falhou ao criar ficheiros de saída"
+
+#: builtin/log.c:1520
+#, c-format
+msgid "Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr ""
+
+#: builtin/log.c:1536
+#: builtin/log.c:1538
+#: builtin/log.c:1550
+#, c-format
+msgid "Unknown commit %s"
+msgstr "Commit desconhecido %s"
+
+#: builtin/merge.c:90
+msgid "switch `m' requires a value"
+msgstr ""
+
+#: builtin/merge.c:127
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr ""
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Available strategies are:"
+msgstr "As estratégias disponíveis são:"
+
+#: builtin/merge.c:133
+#, c-format
+msgid "Available custom strategies are:"
+msgstr "Estratégias personalizadas disponíveis são:"
+
+#: builtin/merge.c:240
+msgid "could not run stash."
+msgstr ""
+
+#: builtin/merge.c:245
+msgid "stash failed"
+msgstr "falhou o stash"
+
+#: builtin/merge.c:250
+#, c-format
+msgid "not a valid object: %s"
+msgstr ""
+
+#: builtin/merge.c:269
+#: builtin/merge.c:286
+msgid "read-tree failed"
+msgstr ""
+
+#: builtin/merge.c:316
+msgid " (nothing to squash)"
+msgstr " (nada para squash)"
+
+#: builtin/merge.c:329
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:361
+msgid "Writing SQUASH_MSG"
+msgstr "Escrevendo SQUASH_MSG"
+
+#: builtin/merge.c:363
+msgid "Finishing SQUASH_MSG"
+msgstr "Terminando SQUASH_MSG"
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr ""
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr ""
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr ""
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr ""
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr ""
+
+#: builtin/merge.c:697
+msgid "Unable to write index."
+msgstr ""
+
+#: builtin/merge.c:710
+msgid "Not handling anything other than two heads merge."
+msgstr ""
+
+#: builtin/merge.c:724
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr ""
+
+#: builtin/merge.c:738
+#, c-format
+msgid "unable to write %s"
+msgstr ""
+
+#: builtin/merge.c:877
+#, c-format
+msgid "Could not read from '%s'"
+msgstr ""
+
+#: builtin/merge.c:886
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr "Não commitando um merge; usa 'git commit' para completar o merge.\n"
+
+#: builtin/merge.c:892
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+
+#: builtin/merge.c:916
+msgid "Empty commit message."
+msgstr "Mensagem de commit vazia."
+
+#: builtin/merge.c:928
+#, c-format
+msgid "Wonderful.\n"
+msgstr "Fastastico.\n"
+
+#: builtin/merge.c:993
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr ""
+
+#: builtin/merge.c:1009
+#, c-format
+msgid "'%s' is not a commit"
+msgstr "'%s' não é um commit"
+
+#: builtin/merge.c:1050
+msgid "No current branch."
+msgstr "Nenhuma rama actual"
+
+#: builtin/merge.c:1052
+msgid "No remote for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1054
+msgid "No default upstream defined for the current branch."
+msgstr ""
+
+#: builtin/merge.c:1059
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr ""
+
+#: builtin/merge.c:1146
+#: builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr ""
+
+#: builtin/merge.c:1214
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr ""
+
+#: builtin/merge.c:1230
+#: git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1233
+#: git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1237
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+
+#: builtin/merge.c:1240
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr ""
+
+#: builtin/merge.c:1249
+msgid "You cannot combine --squash with --no-ff."
+msgstr ""
+
+#: builtin/merge.c:1254
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr ""
+
+#: builtin/merge.c:1261
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr ""
+
+#: builtin/merge.c:1293
+msgid "Can merge only exactly one commit into empty head"
+msgstr ""
+
+#: builtin/merge.c:1296
+msgid "Squash commit into empty head not supported yet"
+msgstr ""
+
+#: builtin/merge.c:1298
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr ""
+
+#: builtin/merge.c:1413
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr "Actualizando %s..%s\n"
+
+#: builtin/merge.c:1451
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr ""
+
+#: builtin/merge.c:1458
+#, c-format
+msgid "Nope.\n"
+msgstr "Não.\n"
+
+#: builtin/merge.c:1490
+msgid "Not possible to fast-forward, aborting."
+msgstr ""
+
+#: builtin/merge.c:1513
+#: builtin/merge.c:1592
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr ""
+
+#: builtin/merge.c:1517
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr ""
+
+#: builtin/merge.c:1583
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr ""
+
+#: builtin/merge.c:1585
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr "Fundir com a estratégia %s falhou.\n"
+
+#: builtin/merge.c:1594
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr ""
+
+#: builtin/merge.c:1606
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr ""
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr ""
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr "fonte inválida"
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr ""
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr ""
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr ""
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr "o directorio fonte está vazio"
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr "não está no controlo de versões"
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr "existe destino"
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr "subscrevendo '%s'"
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr "Não consegue subscrever"
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr "múltiplas fontes para o mesmo alvo"
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr ""
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr "Mudar de nome %s para %s\n"
+
+#: builtin/mv.c:215
+#: builtin/remote.c:731
+#, c-format
+msgid "renaming '%s' failed"
+msgstr "mudar de nome '%s' falhou"
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr ""
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr ""
+
+#: builtin/notes.c:175
+#: builtin/tag.c:347
+#, c-format
+msgid "could not create file '%s'"
+msgstr ""
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr ""
+
+#: builtin/notes.c:210
+#: builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr ""
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr ""
+
+#: builtin/notes.c:251
+#: builtin/tag.c:542
+#, c-format
+msgid "cannot read '%s'"
+msgstr "não consegue ler '%s'"
+
+#: builtin/notes.c:253
+#: builtin/tag.c:545
+#, c-format
+msgid "could not open or read '%s'"
+msgstr ""
+
+#: builtin/notes.c:272
+#: builtin/notes.c:445
+#: builtin/notes.c:447
+#: builtin/notes.c:507
+#: builtin/notes.c:561
+#: builtin/notes.c:644
+#: builtin/notes.c:649
+#: builtin/notes.c:724
+#: builtin/notes.c:766
+#: builtin/notes.c:968
+#: builtin/reset.c:293
+#: builtin/tag.c:558
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr ""
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr ""
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr ""
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr ""
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr ""
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr "Inválido %s valor: '%s'"
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr ""
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr ""
+
+#: builtin/notes.c:500
+#: builtin/notes.c:554
+#: builtin/notes.c:627
+#: builtin/notes.c:639
+#: builtin/notes.c:712
+#: builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr "demasiado parametros"
+
+#: builtin/notes.c:513
+#: builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr "Nenhuma nota encontrada para o objecto %s."
+
+#: builtin/notes.c:580
+#, c-format
+msgid "Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite existing notes"
+msgstr ""
+
+#: builtin/notes.c:585
+#: builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr ""
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr ""
+
+#: builtin/notes.c:656
+#, c-format
+msgid "Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite existing notes"
+msgstr ""
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr ""
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr ""
+
+#: builtin/notes.c:1103
+#: builtin/remote.c:1598
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2315
+#, c-format
+msgid "unsupported index version %s"
+msgstr ""
+
+#: builtin/pack-objects.c:2319
+#, c-format
+msgid "bad index version '%s'"
+msgstr ""
+
+#: builtin/pack-objects.c:2342
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr "opção %s não aceita formato negativo"
+
+#: builtin/pack-objects.c:2346
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr ""
+
+#: builtin/push.c:45
+msgid "tag shorthand without <tag>"
+msgstr ""
+
+#: builtin/push.c:64
+msgid "--delete only accepts plain target ref names"
+msgstr "--delete só aceita nomes simples para o ref de destino"
+
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+
+#: builtin/push.c:121
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+
+#: builtin/push.c:128
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+
+#: builtin/push.c:136
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr ""
+
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+
+#: builtin/push.c:174
+msgid "You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr ""
+
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:233
+#, c-format
+msgid "Pushing to %s\n"
+msgstr "Pushing para %s\n"
+
+#: builtin/push.c:237
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr ""
+
+#: builtin/push.c:269
+#, c-format
+msgid "bad repository '%s'"
+msgstr "repositorio inválido '%s'"
+
+#: builtin/push.c:270
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+
+#: builtin/push.c:285
+msgid "--all and --tags are incompatible"
+msgstr "--all e --tags are são incompatíveis"
+
+#: builtin/push.c:286
+msgid "--all can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:291
+msgid "--mirror and --tags are incompatible"
+msgstr ""
+
+#: builtin/push.c:292
+msgid "--mirror can't be combined with refspecs"
+msgstr ""
+
+#: builtin/push.c:297
+msgid "--all and --mirror are incompatible"
+msgstr ""
+
+#: builtin/push.c:385
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr ""
+
+#: builtin/push.c:387
+msgid "--delete doesn't make sense without any refs"
+msgstr ""
+
+#: builtin/remote.c:98
+#, c-format
+msgid "Updating %s"
+msgstr "Actualizando %s"
+
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
+msgstr ""
+
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
+msgstr "argumento mirror não conhecido: %s"
+
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
+msgstr ""
+
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
+msgstr ""
+
+#: builtin/remote.c:195
+#: builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
+msgstr "o remoto %s já existe"
+
+#: builtin/remote.c:199
+#: builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
+msgstr "'%s' não é um nombe remoto valido"
+
+#: builtin/remote.c:243
+#, c-format
+msgid "Could not setup master '%s'"
+msgstr "Não foi possível configurar a rama master '%s'"
+
+#: builtin/remote.c:299
+#, c-format
+msgid "more than one %s"
+msgstr ""
+
+#: builtin/remote.c:339
+#, c-format
+msgid "Could not get fetch map for refspec %s"
+msgstr ""
+
+#: builtin/remote.c:440
+#: builtin/remote.c:448
+msgid "(matching)"
+msgstr ""
+
+#: builtin/remote.c:452
+msgid "(delete)"
+msgstr "(eliminado)"
+
+#: builtin/remote.c:595
+#: builtin/remote.c:601
+#: builtin/remote.c:607
+#, c-format
+msgid "Could not append '%s' to '%s'"
+msgstr "Não foi possível adicionar o '%s' para o '%s'"
+
+#: builtin/remote.c:639
+#: builtin/remote.c:792
+#: builtin/remote.c:890
+#, c-format
+msgid "No such remote: %s"
+msgstr ""
+
+#: builtin/remote.c:656
+#, c-format
+msgid "Could not rename config section '%s' to '%s'"
+msgstr "Não foi possível renombrar a secção da configuração de '%s' para '%s'"
+
+#: builtin/remote.c:662
+#: builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
+msgstr "Não foi possível remover a secção da configuração '%s'"
+
+#: builtin/remote.c:677
+#, c-format
+msgid ""
+"Not updating non-default fetch respec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
+msgstr ""
+
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
+msgstr "Não foi possível adicionar '%s'"
+
+#: builtin/remote.c:694
+#, c-format
+msgid "Could not set '%s'"
+msgstr "Não foi possível atribuir '%s'"
+
+#: builtin/remote.c:716
+#, c-format
+msgid "deleting '%s' failed"
+msgstr "falhou eliminar '%s'"
+
+#: builtin/remote.c:750
+#, c-format
+msgid "creating '%s' failed"
+msgstr "falhou a criar '%s'"
+
+#: builtin/remote.c:764
+#, c-format
+msgid "Could not remove branch %s"
+msgstr "Não foi possível remover rama %s"
+
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:943
+#, c-format
+msgid " new (next fetch will store in remotes/%s)"
+msgstr ""
+
+#: builtin/remote.c:946
+msgid " tracked"
+msgstr "seguido"
+
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
+msgstr ""
+
+#: builtin/remote.c:950
+msgid " ???"
+msgstr " ???"
+
+#: builtin/remote.c:991
+#, c-format
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
+msgstr ""
+
+#: builtin/remote.c:998
+#, c-format
+msgid "rebases onto remote %s"
+msgstr ""
+
+#: builtin/remote.c:1001
+#, c-format
+msgid " merges with remote %s"
+msgstr "Fundir com servidor remoto %s"
+
+#: builtin/remote.c:1002
+msgid " and with remote"
+msgstr " e com remoto"
+
+#: builtin/remote.c:1004
+#, c-format
+msgid "merges with remote %s"
+msgstr "Fundir com servidor remoto %s"
+
+#: builtin/remote.c:1005
+msgid " and with remote"
+msgstr " e com remoto"
+
+#: builtin/remote.c:1051
+msgid "create"
+msgstr "creado"
+
+#: builtin/remote.c:1054
+msgid "delete"
+msgstr "eliminado"
+
+#: builtin/remote.c:1058
+msgid "up to date"
+msgstr "actualizado"
+
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
+msgstr "fast-forwardable"
+
+#: builtin/remote.c:1064
+msgid "local out of date"
+msgstr "local desatualizada"
+
+#: builtin/remote.c:1071
+#, c-format
+msgid " %-*s forces to %-*s (%s)"
+msgstr ""
+
+#: builtin/remote.c:1074
+#, c-format
+msgid " %-*s pushes to %-*s (%s)"
+msgstr ""
+
+#: builtin/remote.c:1078
+#, c-format
+msgid " %-*s forces to %s"
+msgstr ""
+
+#: builtin/remote.c:1081
+#, c-format
+msgid " %-*s pushes to %s"
+msgstr ""
+
+#: builtin/remote.c:1118
+#, c-format
+msgid "* remote %s"
+msgstr "* remota %s"
+
+#: builtin/remote.c:1119
+#, c-format
+msgid " Fetch URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1120
+#: builtin/remote.c:1285
+msgid "(no URL)"
+msgstr "(nenhum URL)"
+
+#: builtin/remote.c:1129
+#: builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
+msgstr ""
+
+#: builtin/remote.c:1133
+#: builtin/remote.c:1135
+#: builtin/remote.c:1137
+#, c-format
+msgid " HEAD branch: %s"
+msgstr "Rama HEAD: %s"
+
+#: builtin/remote.c:1139
+#, c-format
+msgid " HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
+msgstr ""
+
+#: builtin/remote.c:1151
+#, c-format
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] "Rama remota:%s"
+msgstr[1] "Ramas remotas:%s'"
+
+#: builtin/remote.c:1154
+#: builtin/remote.c:1181
+msgid " (status not queried)"
+msgstr ""
+
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr ""
+
+#: builtin/remote.c:1178
+#, c-format
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] ""
+msgstr[1] ""
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
+msgstr ""
+
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
+msgstr ""
+
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
+msgstr "Não foi possível abrir %s"
+
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
+msgstr ""
+
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
+msgstr "Não foi possível configurar %s"
+
+#: builtin/remote.c:1274
+#, c-format
+msgid " %s will become dangling!"
+msgstr ""
+
+#: builtin/remote.c:1275
+#, c-format
+msgid " %s has become dangling!"
+msgstr ""
+
+#: builtin/remote.c:1281
+#, c-format
+msgid "Pruning %s"
+msgstr "Apagando %s"
+
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
+msgstr "URL: %s"
+
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
+msgstr ""
+
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
+msgstr ""
+
+#: builtin/remote.c:1387
+#: builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
+msgstr "Não existe este remoto '%s'"
+
+#: builtin/remote.c:1414
+msgid "no remote specified"
+msgstr "Nenhum remoto especificado"
+
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
+msgstr ""
+
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
+msgstr ""
+
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr "Nenhum URL encontrado: %s"
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
+msgstr ""
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr "mistura"
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr "leve"
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr "forte"
+
+#: builtin/reset.c:33
+msgid "merge"
+msgstr "juntar"
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr "manter"
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr "Não tens a HEAD válida."
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr ""
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr ""
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr ""
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr "HEAD é agora em %s"
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr ""
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr ""
+
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr ""
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr "Não foi possível analisar objeto '%s'."
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr ""
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr ""
+
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr ""
+
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr ""
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr ""
+
+#: builtin/revert.c:70
+#: builtin/revert.c:92
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr ""
+
+#: builtin/revert.c:131
+msgid "program error"
+msgstr "erro do programa"
+
+#: builtin/revert.c:221
+msgid "revert failed"
+msgstr "falhou o revert"
+
+#: builtin/revert.c:236
+msgid "cherry-pick failed"
+msgstr "cherry-pick falhou"
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr ""
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr ""
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr "Autor em falta: %s"
+
+#: builtin/tag.c:60
+#, c-format
+msgid "malformed object at '%s'"
+msgstr ""
+
+#: builtin/tag.c:207
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr ""
+
+#: builtin/tag.c:212
+#, c-format
+msgid "tag '%s' not found."
+msgstr "etiqueta '%s' não foi encontrada."
+
+#: builtin/tag.c:227
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr ""
+
+#: builtin/tag.c:239
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr ""
+
+#: builtin/tag.c:249
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+
+#: builtin/tag.c:256
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you want to.\n"
+"#\n"
+msgstr ""
+
+#: builtin/tag.c:298
+msgid "unable to sign the tag"
+msgstr ""
+
+#: builtin/tag.c:300
+msgid "unable to write tag file"
+msgstr ""
+
+#: builtin/tag.c:325
+msgid "bad object type."
+msgstr ""
+
+#: builtin/tag.c:338
+msgid "tag header too big."
+msgstr ""
+
+#: builtin/tag.c:370
+msgid "no tag message?"
+msgstr "nenhuma mensaje para a etiqueta?"
+
+#: builtin/tag.c:376
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr ""
+
+#: builtin/tag.c:425
+msgid "switch 'points-at' requires an object"
+msgstr ""
+
+#: builtin/tag.c:427
+#, c-format
+msgid "malformed object name '%s'"
+msgstr ""
+
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr "--column e -n are são incompatíveis"
+
+#: builtin/tag.c:523
+msgid "-n option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:525
+msgid "--contains option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:527
+msgid "--points-at option is only allowed with -l."
+msgstr ""
+
+#: builtin/tag.c:535
+msgid "only one -F or -m option is allowed."
+msgstr ""
+
+#: builtin/tag.c:555
+msgid "too many params"
+msgstr "demasiado parametros"
+
+#: builtin/tag.c:561
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr ""
+
+#: builtin/tag.c:566
+#, c-format
+msgid "tag '%s' already exists"
+msgstr "etiqueta '%s' já existe"
+
+#: builtin/tag.c:584
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr ""
+
+#: builtin/tag.c:586
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr ""
+
+#: builtin/tag.c:588
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr ""
+
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr ""
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr ""
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr ""
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr "Listar, criar ou apagar ramas"
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr ""
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr ""
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr "Gravar alterações para o repositório"
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr ""
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr ""
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr ""
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr ""
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr "Mostrado logs de commits"
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr ""
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr ""
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr ""
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr ""
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr ""
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr ""
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr ""
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr ""
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr "Mostrar o estado los ramos das árvores de trabalho"
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr ""
+
+#: git-am.sh:50
+msgid "You need to set your committer info first"
+msgstr "Necessitas primeiro de especificiar os teus dados de committer"
+
+#: git-am.sh:137
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr ""
+
+#: git-am.sh:154
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+
+#: git-am.sh:163
+msgid "Falling back to patching base and 3-way merge..."
+msgstr ""
+
+#: git-am.sh:275
+msgid "Only one StGIT patch series can be applied at once"
+msgstr ""
+
+#: git-am.sh:362
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr ""
+
+#: git-am.sh:364
+msgid "Patch format detection failed."
+msgstr "Falhou a detecção do formato do patch."
+
+#: git-am.sh:418
+msgid "-d option is no longer supported. Do not use."
+msgstr ""
+
+#: git-am.sh:481
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr ""
+
+#: git-am.sh:486
+msgid "Please make up your mind. --skip or --abort?"
+msgstr ""
+
+#: git-am.sh:513
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr ""
+
+#: git-am.sh:579
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr ""
+
+#: git-am.sh:755
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:766
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr "Aplicar? Sim[y]/[n]ão/[e]ditar/[v]er patch/[a]ceitar todos "
+
+#: git-am.sh:802
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr "Aplicando: $FIRSTLINE"
+
+#: git-am.sh:847
+msgid "No changes -- Patch already applied."
+msgstr "Nenhuma mudança -- Já foi aplicado o patch."
+
+#: git-am.sh:873
+msgid "applying to an empty history"
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr "Queres que eu faça por sí [Y/n]?"
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr ""
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr ""
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr ""
+
+#: git-bisect.sh:130
+#, sh-format
+msgid "Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr ""
+
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr ""
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr ""
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr ""
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr ""
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr ""
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr ""
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr ""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr "Tens a certeza [Y/n]? "
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr ""
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr "Nenhum ficheiro de log dado"
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr ""
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr ""
+
+#: git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr "Não estamos a bisseccionar."
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr ""
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr ""
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr ""
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr "Não é possível fazer rebase com várias ramas"
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr ""
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr "Tu ainda não tens o commit inicial"
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr ""
+
+#: git-stash.sh:123
+#: git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr ""
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr "Não há alterações seleccionadas"
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr ""
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr ""
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr "Sem alterações locais para guardar"
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr "Não é possível inicializar o stash"
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr ""
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr ""
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr "nenhum stash encontrado."
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr ""
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr ""
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr ""
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr ""
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr ""
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr ""
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr ""
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr "Não foi posivel guardar o index tree"
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr ""
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr "Deixado cair ${REV} ($s)"
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr ""
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr "Nenhum nome para a rama especificado"
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr ""
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr ""
+
+#: git-submodule.sh:109
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:150
+#, sh-format
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr ""
+
+#: git-submodule.sh:160
+#, sh-format
+msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
+msgstr ""
+
+#: git-submodule.sh:249
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr ""
+
+#: git-submodule.sh:266
+#, sh-format
+msgid "'$sm_path' already exists in the index"
+msgstr ""
+
+#: git-submodule.sh:283
+#, sh-format
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr ""
+
+#: git-submodule.sh:297
+#, sh-format
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:302
+#, sh-format
+msgid "Failed to add submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:307
+#, sh-format
+msgid "Failed to register submodule '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:349
+#, sh-format
+msgid "Entering '$prefix$sm_path'"
+msgstr "Entrando '$prefix$sm_path'"
+
+#: git-submodule.sh:363
+#, sh-format
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr ""
+
+#: git-submodule.sh:405
+#, sh-format
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr ""
+
+#: git-submodule.sh:414
+#, sh-format
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:422
+#, sh-format
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:424
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:523
+#, sh-format
+msgid ""
+"Submodule path '$sm_path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+
+#: git-submodule.sh:536
+#, sh-format
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:555
+#, sh-format
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:569
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:570
+#, sh-format
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:575
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:576
+#, sh-format
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:581
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:582
+#, sh-format
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr ""
+
+#: git-submodule.sh:604
+#: git-submodule.sh:927
+#, sh-format
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr ""
+
+#: git-submodule.sh:712
+msgid "--"
+msgstr "--"
+
+#: git-submodule.sh:770
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr ""
+
+#: git-submodule.sh:773
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr ""
+
+#: git-submodule.sh:776
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr ""
+
+#: git-submodule.sh:801
+msgid "blob"
+msgstr "blob"
+
+#: git-submodule.sh:802
+msgid "submodule"
+msgstr "submódulos"
+
+#: git-submodule.sh:973
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr ""
+
+#~ msgid "cherry-pick"
+#~ msgstr "cherry-pick"
+
+#~ msgid "Please enter the commit message for your changes."
+#~ msgstr "Por favor insira a mensagem de commit das suas alterações."
+
+#~ msgid "Too many options specified"
+#~ msgstr "Demasiadas opções especificadas"
diff --git a/po/sv.po b/po/sv.po
new file mode 100644
index 0000000..b0ff6f9
--- /dev/null
+++ b/po/sv.po
@@ -0,0 +1,5615 @@
+# Swedish translations for Git.
+# Copyright (C) 2010-2012 Peter krefting <peter@softwolves.pp.se>
+# This file is distributed under the same license as the Git package.
+# Peter Krefting <peter@softwolves.pp.se>, 2010, 2011, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git 1.7.10\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-06-08 10:20+0800\n"
+"PO-Revision-Date: 2012-07-01 22:59+0100\n"
+"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
+"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
+"Language: sv\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"
+
+#: advice.c:40
+#, c-format
+msgid "hint: %.*s\n"
+msgstr "tips: %.*s\n"
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:70
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+"Rätta dem i din arbetskatalog,\n"
+"och använd sedan \"git add/rm <fil>\" som\n"
+"lämpligt för att ange lösning och checka in,\n"
+"eller använd \"git commit -a\"."
+
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr "'%s' ser inte ut som en v2-bundle-fil"
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr "okänt huvud: %s%s (%d)"
+
+#: bundle.c:89 builtin/commit.c:696
+#, c-format
+msgid "could not open '%s'"
+msgstr "kunde inte öppna \"%s\""
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr "Arkivet saknar dessa nödvändiga incheckningar:"
+
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
+#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr "misslyckades skapa revisionstraversering"
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] "Paketet (bundlen) innehåller %d referens"
+msgstr[1] "Paketet (bundlen) innehåller %d referenser"
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] "Paketet (bundlen) kräver denna referens"
+msgstr[1] "Paketet (bundlen) kräver dessa %d referenser"
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr "rev-list dog"
+
+#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr "okänt argument: %s"
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr "referensen \"%s\" exkluderas av argumenten till rev-list"
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr "Vägrar skapa ett tomt paket (bundle)."
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr "Kunde inte starta pack-objects"
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr "pack-objects misslyckades"
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr "kan inte skapa \"%s\""
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr "index-pack dog"
+
+#: commit.c:48
+#, c-format
+msgid "could not parse %s"
+msgstr "kunde inte tolka %s"
+
+#: commit.c:50
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr "%s %s är inte en incheckning!"
+
+#: compat/obstack.c:406 compat/obstack.c:408
+msgid "memory exhausted"
+msgstr "minnet slut"
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr "Kunde inte köra \"git rev-list\""
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr "kunde inte skriva till rev-list: %s"
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr "kunde inte stänga rev-list:s standard in: %s"
+
+#: date.c:95
+msgid "in the future"
+msgstr "i framtiden"
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] "%lu sekund sedan"
+msgstr[1] "%lu sekunder sedan"
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] "%lu minut sedan"
+msgstr[1] "%lu minuter sedan"
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] "%lu timme sedan"
+msgstr[1] "%lu timmar sedan"
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] "%lu dag sedan"
+msgstr[1] "%lu dagar sedan"
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] "%lu vecka sedan"
+msgstr[1] "%lu veckor sedan"
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] "%lu månad sedan"
+msgstr[1] "%lu månader sedan"
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] "%lu år"
+msgstr[1] "%lu år"
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] "%s, %lu månad sedan"
+msgstr[1] "%s, %lu månader sedan"
+
+#: date.c:154 date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] "%lu år sedan"
+msgstr[1] "%lu år sedan"
+
+#: diff.c:105
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr " Misslyckades tolka dirstat-avskärningsprocentandel \"%.*s\"\n"
+
+#: diff.c:110
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr " Okänd dirstat-parameter \"%.*s\"\n"
+
+#: diff.c:210
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+"Hittade fel i konfigurationsvariabeln \"diff.dirstat\":\n"
+"%s"
+
+#: diff.c:1400
+msgid " 0 files changed\n"
+msgstr " 0 filer ändrade\n"
+
+#: diff.c:1404
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] " %d fil ändrad"
+msgstr[1] " %d filer ändrade"
+
+#: diff.c:1421
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ", %d tillägg(+)"
+msgstr[1] ", %d tillägg(+)"
+
+#: diff.c:1432
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ", %d borttagning(-)"
+msgstr[1] ", %d borttagningar(-)"
+
+#: diff.c:3478
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+"Misslyckades tolka argument till flaggan --dirstat/-X;\n"
+"%s"
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr "kunde inte köra gpg."
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr "gpg godtog inte data"
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr "gpg misslyckades signera data"
+
+#: grep.c:1320
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr "\"%s\" kunde inte läsa %s"
+
+#: grep.c:1337
+#, c-format
+msgid "'%s': %s"
+msgstr "\"%s\": %s"
+
+#: grep.c:1348
+#, c-format
+msgid "'%s': short read %s"
+msgstr "\"%s\": kort läsning %s"
+
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr "git-kommandon tillgängliga i \"%s\""
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr "git-kommandon från andra platser i din $PATH"
+
+#: help.c:270
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+"\"%s\" verkar vara ett git-kommando, men vi kan inte\n"
+"köra det. Kanske git-%s är trasigt?"
+
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr "Oj då. Ditt system rapporterar inga Git-kommandon alls."
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+"VARNING: Du anropade ett Git-kommando vid namn \"%s\", som inte finns.\n"
+"Fortsätter under förutsättningen att du menade \"%s\""
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr "automatiskt om %0.1f sekunder..."
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr "git: \"%s\" är inte ett git-kommando. Se \"git --help\"."
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+"\n"
+"Menade du detta?"
+msgstr[1] ""
+"\n"
+"Menade du ett av dessa?"
+
+#: parse-options.c:493
+msgid "..."
+msgstr "..."
+
+#: parse-options.c:511
+#, c-format
+msgid "usage: %s"
+msgstr "användning: %s"
+
+#. TRANSLATORS: the colon here should align with the
+#. one in "usage: %s" translation
+#: parse-options.c:515
+#, c-format
+msgid " or: %s"
+msgstr " eller: %s"
+
+#: parse-options.c:518
+#, c-format
+msgid " %s"
+msgstr " %s"
+
+#: remote.c:1629
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] "Din gren ligger före \"%s\" med %d incheckning.\n"
+msgstr[1] "Din gren ligger före \"%s\" med %d incheckningar.\n"
+
+#: remote.c:1635
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural ""
+"Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] ""
+"Din gren ligger efter \"%s\" med %d incheckning, och kan snabbspolas.\n"
+msgstr[1] ""
+"Din gren ligger efter \"%s\" med %d incheckningar, och kan snabbspolas.\n"
+
+#: remote.c:1643
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+"Din gren och \"%s\" har divergerat,\n"
+"och har %d respektive %d olika incheckning.\n"
+msgstr[1] ""
+"Din gren och \"%s\" har divergerat,\n"
+"och har %d respektive %d olika incheckningar.\n"
+
+#: sequencer.c:121 builtin/merge.c:865 builtin/merge.c:978
+#: builtin/merge.c:1088 builtin/merge.c:1098
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr "Kunde inte öppna \"%s\" för skrivning"
+
+#: sequencer.c:123 builtin/merge.c:333 builtin/merge.c:868
+#: builtin/merge.c:1090 builtin/merge.c:1103
+#, c-format
+msgid "Could not write to '%s'"
+msgstr "Kunde inte skriva till \"%s\""
+
+#: sequencer.c:144
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'"
+msgstr ""
+"efter att ha löst konflikterna, markera de rättade sökvägarna\n"
+"med \"git add <sökvägar>\" eller \"git rm <sökvägar>\""
+
+#: sequencer.c:147
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+"efter att ha löst konflikterna, markera de rättade sökvägarna\n"
+"med \"git add <sökvägar>\" eller \"git rm <sökvägar>\"\n"
+"och checka in resultatet med \"git commit\""
+
+#: sequencer.c:160 sequencer.c:758 sequencer.c:841
+#, c-format
+msgid "Could not write to %s"
+msgstr "Kunde inte skriva till %s"
+
+#: sequencer.c:163
+#, c-format
+msgid "Error wrapping up %s"
+msgstr "Fel vid ombrytning av %s"
+
+#: sequencer.c:178
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr "Dina lokala ändringar skulle skrivas över av \"cherry-pick\"."
+
+#: sequencer.c:180
+msgid "Your local changes would be overwritten by revert."
+msgstr "Dina lokala ändringar skulle skrivas över av \"revert\"."
+
+#: sequencer.c:183
+msgid "Commit your changes or stash them to proceed."
+msgstr "Checka in dina ändringar eller använd \"stash\" för att fortsätta."
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:233
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr "%s: Kunde inte skriva ny indexfil"
+
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr "Kunde inte bestämma HEAD:s incheckning\n"
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr "Kan inte uppdatera cacheträd\n"
+
+#: sequencer.c:324
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr "Kunde inte tolka incheckningen %s\n"
+
+#: sequencer.c:329
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr "Kunde inte tolka föräldraincheckningen %s\n"
+
+#: sequencer.c:395
+msgid "Your index file is unmerged."
+msgstr "Din indexfil har inte slagits ihop."
+
+#: sequencer.c:398
+msgid "You do not have a valid HEAD"
+msgstr "Du har ingen giltig HEAD"
+
+#: sequencer.c:413
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr "Incheckning %s är en sammanslagning, men flaggan -m angavs inte."
+
+#: sequencer.c:421
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr "Incheckning %s har inte förälder %d"
+
+#: sequencer.c:425
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr "Huvudlinje angavs, men incheckningen %s är inte en sammanslagning"
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:436
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr "%s: kan inte tolka föräldraincheckningen %s"
+
+#: sequencer.c:440
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr "Kan inte hämta incheckningsmeddelande för %s"
+
+#: sequencer.c:524
+#, c-format
+msgid "could not revert %s... %s"
+msgstr "kunde inte ångra %s... %s"
+
+#: sequencer.c:525
+#, c-format
+msgid "could not apply %s... %s"
+msgstr "kunde inte tillämpa %s... %s"
+
+#: sequencer.c:553
+msgid "empty commit set passed"
+msgstr "den angivna uppsättningen incheckningar är tom"
+
+#: sequencer.c:561
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr "git %s: misslyckades läsa indexet"
+
+#: sequencer.c:566
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr "git %s: misslyckades uppdatera indexet"
+
+#: sequencer.c:624
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr "kan inte %s under en %s"
+
+#: sequencer.c:646
+#, c-format
+msgid "Could not parse line %d."
+msgstr "Kan inte tolka rad %d."
+
+#: sequencer.c:651
+msgid "No commits parsed."
+msgstr "Inga incheckningar lästes."
+
+#: sequencer.c:664
+#, c-format
+msgid "Could not open %s"
+msgstr "Kunde inte öppna %s"
+
+#: sequencer.c:668
+#, c-format
+msgid "Could not read %s."
+msgstr "kunde inte läsa %s."
+
+#: sequencer.c:675
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr "Oanvändbart manus: %s"
+
+#: sequencer.c:703
+#, c-format
+msgid "Invalid key: %s"
+msgstr "Felaktig nyckel: %s"
+
+#: sequencer.c:706
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr "Felaktigt värde för %s: %s"
+
+#: sequencer.c:718
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr "Trasigt manus: %s"
+
+#: sequencer.c:739
+msgid "a cherry-pick or revert is already in progress"
+msgstr "en \"cherry-pick\" eller \"revert\" pågår redan"
+
+#: sequencer.c:740
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr "testa \"git cherry-pick (--continue | --quit | --abort)\""
+
+#: sequencer.c:744
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr "Kunde inte skapa \"sequencer\"-katalogen \"%s\""
+
+#: sequencer.c:760 sequencer.c:845
+#, c-format
+msgid "Error wrapping up %s."
+msgstr "Fel vid ombrytning av %s."
+
+#: sequencer.c:779 sequencer.c:913
+msgid "no cherry-pick or revert in progress"
+msgstr "ingen \"cherry-pick\" eller \"revert\" pågår"
+
+#: sequencer.c:781
+msgid "cannot resolve HEAD"
+msgstr "kan inte bestämma HEAD"
+
+#: sequencer.c:783
+msgid "cannot abort from a branch yet to be born"
+msgstr "kan inte avbryta från en gren som ännu inte är född"
+
+#: sequencer.c:805 builtin/apply.c:3697
+#, c-format
+msgid "cannot open %s: %s"
+msgstr "kan inte öppna %s: %s"
+
+#: sequencer.c:808
+#, c-format
+msgid "cannot read %s: %s"
+msgstr "kan inte läsa %s: %s"
+
+#: sequencer.c:809
+msgid "unexpected end of file"
+msgstr "oväntat filslut"
+
+#: sequencer.c:815
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr "sparad HEAD-fil från före \"cherry-pick\", \"%s\", är trasig"
+
+#: sequencer.c:838
+#, c-format
+msgid "Could not format %s."
+msgstr "Kunde inte formatera %s."
+
+#: sequencer.c:1000
+msgid "Can't revert as initial commit"
+msgstr "Kan inte ångra som första incheckning"
+
+#: sequencer.c:1001
+msgid "Can't cherry-pick into empty head"
+msgstr "Kan inte göra \"cherry-pick\" i ett tomt huvud"
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "HEAD pekar inte på en gren"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "Okänd gren: \"%s\""
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "Ingen standarduppström angiven för grenen \"%s\""
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr "Uppströmsgrenen \"%s\" är inte lagrad som en fjärrspårande gren"
+
+#: wrapper.c:413
+#, c-format
+msgid "unable to look up current user in the passwd file: %s"
+msgstr "kan inte slå upp aktuell användare i passwd-filen: %s"
+
+#: wrapper.c:414
+msgid "no such user"
+msgstr "okänd användare"
+
+#: wt-status.c:135
+msgid "Unmerged paths:"
+msgstr "Ej sammanslagna sökvägar:"
+
+#: wt-status.c:141 wt-status.c:158
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr " (använd \"git reset %s <fil>...\" för att ta bort från kö)"
+
+#: wt-status.c:143 wt-status.c:160
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr " (använd \"git rm --cached <fil>...\" för att ta bort från kö)"
+
+#: wt-status.c:144
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr " (använd \"git add/rm <fil>...\" som lämpligt för att ange lösning)"
+
+#: wt-status.c:152
+msgid "Changes to be committed:"
+msgstr "Ändringar att checka in:"
+
+#: wt-status.c:170
+msgid "Changes not staged for commit:"
+msgstr "Ändringar ej i incheckningskön:"
+
+#: wt-status.c:174
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr ""
+" (använd \"git add <fil>...\" för att uppdatera vad som skall checkas in)"
+
+#: wt-status.c:176
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr ""
+" (använd \"git add/rm <fil>...\" för att uppdatera vad som skall checkas in)"
+
+#: wt-status.c:177
+msgid ""
+" (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr ""
+" (använd \"git checkout -- <fil>...\" för att förkasta ändringar i "
+"arbetskatalogen)"
+
+#: wt-status.c:179
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr ""
+" (checka in eller förkasta ospårat eller ändrat innehåll i undermoduler)"
+
+# %s är ett verb ("Untracked"/"Ignored"); lägg till ett -e.
+#: wt-status.c:188
+#, c-format
+msgid "%s files:"
+msgstr "%se filer:"
+
+#: wt-status.c:191
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr ""
+" (använd \"git %s <fil>...\" för att ta med i vad som skall checkas in)"
+
+#: wt-status.c:208
+msgid "bug"
+msgstr "programfel"
+
+#: wt-status.c:213
+msgid "both deleted:"
+msgstr "borttaget av bägge:"
+
+#: wt-status.c:214
+msgid "added by us:"
+msgstr "tillagt av oss:"
+
+#: wt-status.c:215
+msgid "deleted by them:"
+msgstr "borttaget av dem:"
+
+#: wt-status.c:216
+msgid "added by them:"
+msgstr "tillagt av dem:"
+
+#: wt-status.c:217
+msgid "deleted by us:"
+msgstr "borttaget av oss:"
+
+#: wt-status.c:218
+msgid "both added:"
+msgstr "tillagt av bägge:"
+
+#: wt-status.c:219
+msgid "both modified:"
+msgstr "ändrat av bägge:"
+
+#: wt-status.c:249
+msgid "new commits, "
+msgstr "nya incheckningar, "
+
+#: wt-status.c:251
+msgid "modified content, "
+msgstr "ändrat innehåll, "
+
+#: wt-status.c:253
+msgid "untracked content, "
+msgstr "ospårat innehåll, "
+
+#: wt-status.c:267
+#, c-format
+msgid "new file: %s"
+msgstr "ny fil: %s"
+
+#: wt-status.c:270
+#, c-format
+msgid "copied: %s -> %s"
+msgstr "kopierad: %s -> %s"
+
+#: wt-status.c:273
+#, c-format
+msgid "deleted: %s"
+msgstr "borttagen: %s"
+
+#: wt-status.c:276
+#, c-format
+msgid "modified: %s"
+msgstr "ändrad: %s"
+
+#: wt-status.c:279
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr "namnbyte: %s -> %s"
+
+#: wt-status.c:282
+#, c-format
+msgid "typechange: %s"
+msgstr "typbyte: %s"
+
+#: wt-status.c:285
+#, c-format
+msgid "unknown: %s"
+msgstr "okänd: %s"
+
+#: wt-status.c:288
+#, c-format
+msgid "unmerged: %s"
+msgstr "osammansl.: %s"
+
+#: wt-status.c:291
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr "programfel: diff-status %c ej hanterad"
+
+#: wt-status.c:737
+msgid "On branch "
+msgstr "PÃ¥ grenen "
+
+#: wt-status.c:744
+msgid "Not currently on any branch."
+msgstr "Inte på någon gren för närvarande."
+
+#: wt-status.c:755
+msgid "Initial commit"
+msgstr "Första incheckning"
+
+#: wt-status.c:769
+msgid "Untracked"
+msgstr "Ospårad"
+
+#: wt-status.c:771
+msgid "Ignored"
+msgstr "Ignorerad"
+
+# %s är nästa sträng eller tom.
+#: wt-status.c:773
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr "Ospårade filer visas ej%s"
+
+#: wt-status.c:775
+msgid " (use -u option to show untracked files)"
+msgstr " (använd flaggan -u för att visa ospårade filer)"
+
+#: wt-status.c:781
+msgid "No changes"
+msgstr "Inga ändringar"
+
+#: wt-status.c:785
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr "inga ändringar att checka in%s\n"
+
+#: wt-status.c:787
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr " (använd \"git add\" och/eller \"git commit -a\")"
+
+#: wt-status.c:789
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr "inget köat för incheckning, men ospårade filer finns%s\n"
+
+#: wt-status.c:791
+msgid " (use \"git add\" to track)"
+msgstr " (använd \"git add\" för att spåra)"
+
+#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr "inget att checka in%s\n"
+
+#: wt-status.c:794
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr " (skapa/kopiera filer och använd \"git add\" för att spåra)"
+
+#: wt-status.c:797
+msgid " (use -u to show untracked files)"
+msgstr " (använd -u för att visa ospårade filer)"
+
+#: wt-status.c:800
+msgid " (working directory clean)"
+msgstr " (arbetskatalogen ren)"
+
+#: wt-status.c:908
+msgid "HEAD (no branch)"
+msgstr "HEAD (ingen gren)"
+
+#: wt-status.c:914
+msgid "Initial commit on "
+msgstr "Första incheckning på "
+
+#: wt-status.c:929
+msgid "behind "
+msgstr "efter "
+
+#: wt-status.c:932 wt-status.c:935
+msgid "ahead "
+msgstr "före "
+
+#: wt-status.c:937
+msgid ", behind "
+msgstr ", efter "
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr "diff-status %c förväntades inte"
+
+#: builtin/add.c:67 builtin/commit.c:226
+msgid "updating files failed"
+msgstr "misslyckades uppdatera filer"
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr "ta bort \"%s\"\n"
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr "Sökvägen \"%s\" är i undermodulen \"%.*s\""
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr "Ospårade ändringar efter att ha uppdaterat indexet:"
+
+#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr "sökvägsangivelsen \"%s\" motsvarade inte några filer"
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr "\"%s\" är på andra sidan av en symbolisk länk"
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr "Kunde inte läsa indexet"
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr "Kunde inte öppna \"%s\" för skrivning"
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr "Kunde inte skriva patch"
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr "Kunde inte ta status på \"%s\""
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr "Tom patch. Avbryter."
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr "Kunde inte tillämpa \"%s\""
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr "Följande sökvägar ignoreras av en av dina .gitignore-filer:\n"
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr "Använd -f om du verkligen vill lägga till dem.\n"
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr "inga filer har lagts till"
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr "misslyckades lägga till filer"
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr "-A och -u är ömsesidigt inkompatibla"
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr "Flaggan --ignore-missing kan endast användas tillsammans med --dry-run"
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr "Inget angivet, inget tillagt.\n"
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr "Kanske menade du att skriva \"git add .\"?\n"
+
+#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:286 builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr "indexfilen trasig"
+
+#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr "Kunde inte skriva ny indexfil"
+
+#: builtin/apply.c:53
+msgid "git apply [options] [<patch>...]"
+msgstr "git apply [flaggor] [<patch>...]"
+
+#: builtin/apply.c:106
+#, c-format
+msgid "unrecognized whitespace option '%s'"
+msgstr "okänt alternativ för whitespace: \"%s\""
+
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
+msgstr "okänt alternativ för ignore-whitespace: \"%s\""
+
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
+msgstr "Kan inte förbereda reguljärt uttryck för tidsstämpeln %s"
+
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
+msgstr "regexec returnerade %d för indata: %s"
+
+#: builtin/apply.c:905
+#, c-format
+msgid "unable to find filename in patch at line %d"
+msgstr "kan inte hitta filnamn i patchen på rad %d"
+
+#: builtin/apply.c:937
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
+msgstr "git apply: dålig git-diff - förväntade /dev/null, fick %s på rad %d"
+
+#: builtin/apply.c:941
+#, c-format
+msgid "git apply: bad git-diff - inconsistent new filename on line %d"
+msgstr "git apply: dålig git-diff - motsägande nytt filnamn på rad %d"
+
+#: builtin/apply.c:942
+#, c-format
+msgid "git apply: bad git-diff - inconsistent old filename on line %d"
+msgstr "git apply: dålig git-diff - motsägande gammalt filnamn på rad %d"
+
+#: builtin/apply.c:949
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
+msgstr "git apply: dålig git-diff - förväntade /dev/null på rad %d"
+
+#: builtin/apply.c:1394
+#, c-format
+msgid "recount: unexpected line: %.*s"
+msgstr "recount: förväntade rad: %.*s"
+
+#: builtin/apply.c:1451
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
+msgstr "patch-fragment utan huvud på rad %d: %.*s"
+
+#: builtin/apply.c:1468
+#, c-format
+msgid ""
+"git diff header lacks filename information when removing %d leading pathname "
+"component (line %d)"
+msgid_plural ""
+"git diff header lacks filename information when removing %d leading pathname "
+"components (line %d)"
+msgstr[0] ""
+"git-diff-huvudet saknar filnamnsinformation när %d ledande sökvägskomponent\n"
+"tas bort (rad %d)"
+msgstr[1] ""
+"git-diff-huvudet saknar filnamnsinformation när %d ledande "
+"sökvägskomponenter\n"
+"tas bort (rad %d)"
+
+#: builtin/apply.c:1628
+msgid "new file depends on old contents"
+msgstr "ny fil beror på gammalt innehåll"
+
+#: builtin/apply.c:1630
+msgid "deleted file still has contents"
+msgstr "borttagen fil har fortfarande innehåll"
+
+#: builtin/apply.c:1656
+#, c-format
+msgid "corrupt patch at line %d"
+msgstr "trasig patch på rad %d"
+
+#: builtin/apply.c:1692
+#, c-format
+msgid "new file %s depends on old contents"
+msgstr "nya filen %s beror på gammalt innehåll"
+
+#: builtin/apply.c:1694
+#, c-format
+msgid "deleted file %s still has contents"
+msgstr "borttagna filen %s har fortfarande innehåll"
+
+#: builtin/apply.c:1697
+#, c-format
+msgid "** warning: file %s becomes empty but is not deleted"
+msgstr "** varning: filen %s blir tom men har inte tagits bort"
+
+#: builtin/apply.c:1843
+#, c-format
+msgid "corrupt binary patch at line %d: %.*s"
+msgstr "trasig binärpatch på rad %d: %.*s"
+
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1872
+#, c-format
+msgid "unrecognized binary patch at line %d"
+msgstr "binärpatchen på rad %d känns inte igen"
+
+#: builtin/apply.c:1958
+#, c-format
+msgid "patch with only garbage at line %d"
+msgstr "patch med bara skräp på rad %d"
+
+#: builtin/apply.c:2048
+#, c-format
+msgid "unable to read symlink %s"
+msgstr "kunde inte läsa symboliska länken %s"
+
+#: builtin/apply.c:2052
+#, c-format
+msgid "unable to open or read %s"
+msgstr "kunde inte öppna eller läsa %s"
+
+#: builtin/apply.c:2123
+msgid "oops"
+msgstr "hoppsan"
+
+#: builtin/apply.c:2645
+#, c-format
+msgid "invalid start of line: '%c'"
+msgstr "felaktig inledning på rad: \"%c\""
+
+#: builtin/apply.c:2763
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] "Stycke %d lyckades på %d (offset %d rad)."
+msgstr[1] "Stycke %d lyckades på %d (offset %d rader)."
+
+#: builtin/apply.c:2775
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
+msgstr "Sammanhang reducerat till (%ld/%ld) för att tillämpa fragment vid %d"
+
+#: builtin/apply.c:2781
+#, c-format
+msgid ""
+"while searching for:\n"
+"%.*s"
+msgstr ""
+"vid sökning efter:\n"
+"%.*s"
+
+#: builtin/apply.c:2800
+#, c-format
+msgid "missing binary patch data for '%s'"
+msgstr "saknar binära patchdata för \"%s\""
+
+#: builtin/apply.c:2903
+#, c-format
+msgid "binary patch does not apply to '%s'"
+msgstr "binärpatchen kan inte tillämpas på \"%s\""
+
+#: builtin/apply.c:2909
+#, c-format
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
+msgstr "binärpatchen på \"%s\" ger felaktigt resultat (förväntade %s, fick %s)"
+
+#: builtin/apply.c:2930
+#, c-format
+msgid "patch failed: %s:%ld"
+msgstr "patch misslyckades: %s:%ld"
+
+#: builtin/apply.c:3045
+#, c-format
+msgid "patch %s has been renamed/deleted"
+msgstr "patchen %s har ändrat namn/tagits bort"
+
+#: builtin/apply.c:3052 builtin/apply.c:3069
+#, c-format
+msgid "read of %s failed"
+msgstr "misslyckades läsa %s"
+
+#: builtin/apply.c:3084
+msgid "removal patch leaves file contents"
+msgstr "patch för borttagning lämnar kvar filinnehåll"
+
+#: builtin/apply.c:3105
+#, c-format
+msgid "%s: already exists in working directory"
+msgstr "%s: finns redan i arbetskatalogen"
+
+#: builtin/apply.c:3143
+#, c-format
+msgid "%s: has been deleted/renamed"
+msgstr "%s: har tagits bort/ändrat namn"
+
+#: builtin/apply.c:3148 builtin/apply.c:3179
+#, c-format
+msgid "%s: %s"
+msgstr "%s: %s"
+
+#: builtin/apply.c:3159
+#, c-format
+msgid "%s: does not exist in index"
+msgstr "%s: finns inte i indexet"
+
+#: builtin/apply.c:3173
+#, c-format
+msgid "%s: does not match index"
+msgstr "%s: motsvarar inte indexet"
+
+#: builtin/apply.c:3190
+#, c-format
+msgid "%s: wrong type"
+msgstr "%s: fel typ"
+
+#: builtin/apply.c:3192
+#, c-format
+msgid "%s has type %o, expected %o"
+msgstr "%s har typen %o, förväntade %o"
+
+#: builtin/apply.c:3247
+#, c-format
+msgid "%s: already exists in index"
+msgstr "%s: finns redan i indexet"
+
+#: builtin/apply.c:3267
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o)"
+msgstr "nytt läge (%o) för %s motsvarar inte gammalt läge (%o)"
+
+#: builtin/apply.c:3272
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o) of %s"
+msgstr "nytt läge (%o) för %s motsvarar inte gammalt läge (%o) för %s"
+
+#: builtin/apply.c:3280
+#, c-format
+msgid "%s: patch does not apply"
+msgstr "%s: patchen kan inte tillämpas"
+
+#: builtin/apply.c:3293
+#, c-format
+msgid "Checking patch %s..."
+msgstr "Kontrollerar patchen %s..."
+
+#: builtin/apply.c:3348 builtin/checkout.c:212 builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr "make_cache_entry misslyckades för sökvägen \"%s\""
+
+#: builtin/apply.c:3491
+#, c-format
+msgid "unable to remove %s from index"
+msgstr "kan inte ta bort %s från indexet"
+
+#: builtin/apply.c:3518
+#, c-format
+msgid "corrupt patch for subproject %s"
+msgstr "trasig patch för underprojektet %s"
+
+#: builtin/apply.c:3522
+#, c-format
+msgid "unable to stat newly created file '%s'"
+msgstr "kan inte ta status på nyligen skapade filen \"%s\""
+
+#: builtin/apply.c:3527
+#, c-format
+msgid "unable to create backing store for newly created file %s"
+msgstr "kan inte skapa säkerhetsminne för nyligen skapade filen %s"
+
+#: builtin/apply.c:3530
+#, c-format
+msgid "unable to add cache entry for %s"
+msgstr "kan inte lägga till cachepost för %s"
+
+#: builtin/apply.c:3563
+#, c-format
+msgid "closing file '%s'"
+msgstr "stänger filen \"%s\""
+
+#: builtin/apply.c:3612
+#, c-format
+msgid "unable to write file '%s' mode %o"
+msgstr "kan inte skriva filen \"%s\" läge %o"
+
+#: builtin/apply.c:3668
+#, c-format
+msgid "Applied patch %s cleanly."
+msgstr "Tillämpade patchen %s rent."
+
+#: builtin/apply.c:3676
+msgid "internal error"
+msgstr "internt fel"
+
+#. Say this even without --verbose
+#: builtin/apply.c:3679
+#, c-format
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] "Tillämpade patchen %%s med %d refuserad..."
+msgstr[1] "Tillämpade patchen %%s med %d refuserade..."
+
+#: builtin/apply.c:3689
+#, c-format
+msgid "truncating .rej filename to %.*s.rej"
+msgstr "trunkerar .rej-filnamnet till %.*s.rej"
+
+#: builtin/apply.c:3710
+#, c-format
+msgid "Hunk #%d applied cleanly."
+msgstr "Stycke %d tillämpades rent."
+
+#: builtin/apply.c:3713
+#, c-format
+msgid "Rejected hunk #%d."
+msgstr "Refuserar stycke %d."
+
+#: builtin/apply.c:3844
+msgid "unrecognized input"
+msgstr "indata känns inte igen"
+
+#: builtin/apply.c:3855
+msgid "unable to read index file"
+msgstr "kan inte läsa indexfilen"
+
+#: builtin/apply.c:3970 builtin/apply.c:3973
+msgid "path"
+msgstr "sökväg"
+
+#: builtin/apply.c:3971
+msgid "don't apply changes matching the given path"
+msgstr "tillämpa inte ändringar som motsvarar given sökväg"
+
+#: builtin/apply.c:3974
+msgid "apply changes matching the given path"
+msgstr "tillämpa ändringar som motsvarar given sökväg"
+
+#: builtin/apply.c:3976
+msgid "num"
+msgstr "antal"
+
+#: builtin/apply.c:3977
+msgid "remove <num> leading slashes from traditional diff paths"
+msgstr "ta bort <antal> inledande snedstreck från traditionella diff-sökvägar"
+
+#: builtin/apply.c:3980
+msgid "ignore additions made by the patch"
+msgstr "ignorera tillägg gjorda av patchen"
+
+#: builtin/apply.c:3982
+msgid "instead of applying the patch, output diffstat for the input"
+msgstr "istället för att tillämpa patchen, skriv ut diffstat för indata"
+
+#: builtin/apply.c:3986
+msgid "shows number of added and deleted lines in decimal notation"
+msgstr "visar antal tillagda och borttagna rader decimalt"
+
+#: builtin/apply.c:3988
+msgid "instead of applying the patch, output a summary for the input"
+msgstr "istället för att tillämpa patchen, skriv ut en summering av indata"
+
+#: builtin/apply.c:3990
+msgid "instead of applying the patch, see if the patch is applicable"
+msgstr "istället för att tillämpa patchen, se om patchen kan tillämpas"
+
+#: builtin/apply.c:3992
+msgid "make sure the patch is applicable to the current index"
+msgstr "se till att patchen kan tillämpas på aktuellt index"
+
+#: builtin/apply.c:3994
+msgid "apply a patch without touching the working tree"
+msgstr "tillämpa en patch utan att röra arbetskatalogen"
+
+#: builtin/apply.c:3996
+msgid "also apply the patch (use with --stat/--summary/--check)"
+msgstr "tillämpa också patchen (använd med --stat/--summary/--check)"
+
+#: builtin/apply.c:3998
+msgid "build a temporary index based on embedded index information"
+msgstr "bygg ett temporärt index baserat på inbyggd indexinformation"
+
+#: builtin/apply.c:4000
+msgid "paths are separated with NUL character"
+msgstr "sökvägar avdelas med NUL-tecken"
+
+#: builtin/apply.c:4003
+msgid "ensure at least <n> lines of context match"
+msgstr "se till att åtminstone <n> rader sammanhang är lika"
+
+#: builtin/apply.c:4004
+msgid "action"
+msgstr "åtgärd"
+
+#: builtin/apply.c:4005
+msgid "detect new or modified lines that have whitespace errors"
+msgstr "detektera nya eller ändrade rader som har fel i blanktecken"
+
+#: builtin/apply.c:4008 builtin/apply.c:4011
+msgid "ignore changes in whitespace when finding context"
+msgstr "ignorera ändringar i blanktecken för sammanhang"
+
+#: builtin/apply.c:4014
+msgid "apply the patch in reverse"
+msgstr "tillämpa patchen baklänges"
+
+#: builtin/apply.c:4016
+msgid "don't expect at least one line of context"
+msgstr "förvänta inte minst en rad sammanhang"
+
+#: builtin/apply.c:4018
+msgid "leave the rejected hunks in corresponding *.rej files"
+msgstr "lämna refuserade stycken i motsvarande *.rej-filer"
+
+#: builtin/apply.c:4020
+msgid "allow overlapping hunks"
+msgstr "tillåt överlappande stycken"
+
+#: builtin/apply.c:4021
+msgid "be verbose"
+msgstr "var pratsam"
+
+#: builtin/apply.c:4023
+msgid "tolerate incorrectly detected missing new-line at the end of file"
+msgstr "tolerera felaktigt detekterade saknade nyradstecken vid filslut"
+
+#: builtin/apply.c:4026
+msgid "do not trust the line counts in the hunk headers"
+msgstr "lite inte på antalet linjer i styckehuvuden"
+
+#: builtin/apply.c:4028
+msgid "root"
+msgstr "rot"
+
+#: builtin/apply.c:4029
+msgid "prepend <root> to all filenames"
+msgstr "lägg till <rot> i alla filnamn"
+
+#: builtin/apply.c:4050
+msgid "--index outside a repository"
+msgstr "--index utanför arkiv"
+
+#: builtin/apply.c:4053
+msgid "--cached outside a repository"
+msgstr "--cached utanför arkiv"
+
+#: builtin/apply.c:4069
+#, c-format
+msgid "can't open patch '%s'"
+msgstr "kan inte öppna patchen \"%s\""
+
+#: builtin/apply.c:4083
+#, c-format
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] "undertryckte %d fel i blanksteg"
+msgstr[1] "undertryckte %d fel i blanksteg"
+
+#: builtin/apply.c:4089 builtin/apply.c:4099
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] "%d rad lägger till fel i blanksteg."
+msgstr[1] "%d rader lägger till fel i blanksteg."
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr "Kunde inte skapa arkivfilen \"%s\""
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr "kunde inte omdirigera utdata"
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr "git archive: Fjärr utan URL"
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr "git archive: förväntade ACK/NAK, fick EOF"
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr "git archive: NACK %s"
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr "fjärrfel: %s"
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr "git archive: protokollfel"
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr "git archive: förväntade en tömning (flush)"
+
+#: builtin/branch.c:144
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+"tar bort grenen \"%s\" som har slagits ihop med\n"
+" \"%s\", men ännu inte slagits ihop med HEAD."
+
+#: builtin/branch.c:148
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+"tar inte bort grenen \"%s\" som inte har slagits ihop med\n"
+" \"%s\", trots att den har slagits ihop med HEAD."
+
+#: builtin/branch.c:180
+msgid "cannot use -a with -d"
+msgstr "kan inte ange -a med -d"
+
+#: builtin/branch.c:186
+msgid "Couldn't look up commit object for HEAD"
+msgstr "Kunde inte slå upp incheckningsobjekt för HEAD"
+
+#: builtin/branch.c:191
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr "Kan inte ta bort grenen \"%s\" som du befinner dig på för närvarande."
+
+#: builtin/branch.c:202
+#, c-format
+msgid "remote branch '%s' not found."
+msgstr "fjärrgrenen \"%s\" hittades inte."
+
+#: builtin/branch.c:203
+#, c-format
+msgid "branch '%s' not found."
+msgstr "grenen \"%s\" hittades inte."
+
+#: builtin/branch.c:210
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr "Kunde inte slå upp incheckningsobjekt för \"%s\""
+
+#: builtin/branch.c:216
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+"Grenen \"%s\" har inte slagits samman i sin helhet.\n"
+"Om du är säker på att du vill ta bort den, kör \"git branch -D %s\"."
+
+#: builtin/branch.c:225
+#, c-format
+msgid "Error deleting remote branch '%s'"
+msgstr "Fel vid borttagning av fjärrgrenen \"%s\""
+
+#: builtin/branch.c:226
+#, c-format
+msgid "Error deleting branch '%s'"
+msgstr "Fel vid borttagning av grenen \"%s\""
+
+#: builtin/branch.c:233
+#, c-format
+msgid "Deleted remote branch %s (was %s).\n"
+msgstr "Tog bort fjärrgrenen %s (var %s).\n"
+
+#: builtin/branch.c:234
+#, c-format
+msgid "Deleted branch %s (was %s).\n"
+msgstr "Tog bort grenen %s (var %s).\n"
+
+#: builtin/branch.c:239
+msgid "Update of config-file failed"
+msgstr "Misslyckades uppdatera konfigurationsfil"
+
+#: builtin/branch.c:337
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr "grenen \"%s\" pekar inte på en incheckning"
+
+#: builtin/branch.c:409
+#, c-format
+msgid "[%s: behind %d]"
+msgstr "[%s: bakom %d] "
+
+#: builtin/branch.c:411
+#, c-format
+msgid "[behind %d]"
+msgstr "[bakom %d] "
+
+#: builtin/branch.c:415
+#, c-format
+msgid "[%s: ahead %d]"
+msgstr "[%s: före %d] "
+
+#: builtin/branch.c:417
+#, c-format
+msgid "[ahead %d]"
+msgstr "[före %d] "
+
+#: builtin/branch.c:420
+#, c-format
+msgid "[%s: ahead %d, behind %d]"
+msgstr "[%s: före %d, bakom %d] "
+
+#: builtin/branch.c:423
+#, c-format
+msgid "[ahead %d, behind %d]"
+msgstr "[före %d, bakom %d] "
+
+#: builtin/branch.c:535
+msgid "(no branch)"
+msgstr "(ingen gren)"
+
+#: builtin/branch.c:600
+msgid "some refs could not be read"
+msgstr "vissa referenser kunde inte läsas"
+
+#: builtin/branch.c:613
+msgid "cannot rename the current branch while not on any."
+msgstr ""
+"kunde inte byta namn på aktuell gren när du inte befinner dig på någon."
+
+#: builtin/branch.c:623
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr "Felaktigt namn på gren: \"%s\""
+
+#: builtin/branch.c:638
+msgid "Branch rename failed"
+msgstr "Misslyckades byta namn på gren"
+
+#: builtin/branch.c:642
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr "Bytte bort namn på en felaktigt namngiven gren \"%s\""
+
+#: builtin/branch.c:646
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr "Grenen namnbytt till %s, men HEAD har inte uppdaterats!"
+
+#: builtin/branch.c:653
+msgid "Branch is renamed, but update of config-file failed"
+msgstr "Grenen namnbytt, men misslyckades uppdatera konfigurationsfilen"
+
+#: builtin/branch.c:668
+#, c-format
+msgid "malformed object name %s"
+msgstr "felformat objektnamn %s"
+
+#: builtin/branch.c:692
+#, c-format
+msgid "could not write branch description template: %s"
+msgstr "kunde inte skriva grenbeskrivningsmall: %s"
+
+#: builtin/branch.c:783
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr "Misslyckades slå upp HEAD som giltig referens"
+
+#: builtin/branch.c:788 builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr "HEAD hittades inte under refs/heads!"
+
+#: builtin/branch.c:808
+msgid "--column and --verbose are incompatible"
+msgstr "--column och --verbose är inkompatibla"
+
+#: builtin/branch.c:857
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr ""
+"flaggorna -a och -r på \"git branch\" kan inte anges tillsammans med ett "
+"grennamn"
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr "%s är okej\n"
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr "Behöver ett arkiv för att skapa ett paket (bundle)."
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr "Behöver ett arkiv för att packa upp ett paket (bundle)."
+
+#: builtin/checkout.c:113 builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr "sökvägen \"%s\" har inte vår version"
+
+#: builtin/checkout.c:115 builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr "sökvägen \"%s\" har inte deras version"
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr "sökvägen \"%s\" innehåller inte alla nödvändiga versioner"
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr "sökvägen \"%s\" innehåller inte nödvändiga versioner"
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr "sökväg \"%s\": kan inte slå ihop"
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr "Kunde inte lägga till sammanslagningsresultat för \"%s\""
+
+#: builtin/checkout.c:234 builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr "indexfilen är trasig"
+
+#: builtin/checkout.c:264 builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr "sökvägen \"%s\" har inte slagits ihop"
+
+#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
+#: builtin/merge.c:812
+msgid "unable to write new index file"
+msgstr "kunde inte skriva ny indexfil"
+
+#: builtin/checkout.c:319 builtin/diff.c:302 builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr "diff_setup_done misslyckades"
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr "du måste lösa ditt befintliga index först"
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr "Kan inte skapa referenslog för \"%s\"\n"
+
+#: builtin/checkout.c:566
+msgid "HEAD is now at"
+msgstr "HEAD är nu på"
+
+#: builtin/checkout.c:573
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr "Återställ gren \"%s\"\n"
+
+#: builtin/checkout.c:576
+#, c-format
+msgid "Already on '%s'\n"
+msgstr "Redan på \"%s\"\n"
+
+#: builtin/checkout.c:580
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr "Växlade till och nollställde grenen \"%s\"\n"
+
+#: builtin/checkout.c:582
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr "Växlade till en ny gren \"%s\"\n"
+
+#: builtin/checkout.c:584
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr "Växlade till grenen \"%s\"\n"
+
+#: builtin/checkout.c:640
+#, c-format
+msgid " ... and %d more.\n"
+msgstr " ... och %d till.\n"
+
+#. The singular version
+#: builtin/checkout.c:646
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+"Varning: du lämnar %d incheckning bakom dig som inte är ansluten till\n"
+"någon av dina grenar:\n"
+"\n"
+"%s\n"
+msgstr[1] ""
+"Varning: du lämnar %d incheckningar bakom dig som inte är ansluta till\n"
+"någon av dina grenar:\n"
+"\n"
+"%s\n"
+
+#: builtin/checkout.c:664
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+"Om du vill behålla dem genom att skapa en ny gren är nu en bra tidpunkt\n"
+"att göra så, med:\n"
+"\n"
+" git branch nytt_grennamn %s\n"
+"\n"
+
+#: builtin/checkout.c:694
+msgid "internal error in revision walk"
+msgstr "internt fel vid genomgång av revisioner (revision walk)"
+
+#: builtin/checkout.c:698
+msgid "Previous HEAD position was"
+msgstr "Tidigare position för HEAD var"
+
+#: builtin/checkout.c:724
+msgid "You are on a branch yet to be born"
+msgstr "Du är på en gren som ännu inte är född"
+
+#. case (1)
+#: builtin/checkout.c:855
+#, c-format
+msgid "invalid reference: %s"
+msgstr "felaktig referens: %s"
+
+#. case (1): want a tree
+#: builtin/checkout.c:894
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr "referensen är inte ett träd: %s"
+
+#: builtin/checkout.c:974
+msgid "-B cannot be used with -b"
+msgstr "-B kan inte användas med -b"
+
+#: builtin/checkout.c:983
+msgid "--patch is incompatible with all other options"
+msgstr "--patch är inkompatibel med alla andra flaggor"
+
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr "--detcah kan inte användas med -b/-B/--orphan"
+
+#: builtin/checkout.c:988
+msgid "--detach cannot be used with -t"
+msgstr "--detach kan inte användas med -t"
+
+#: builtin/checkout.c:994
+msgid "--track needs a branch name"
+msgstr "--track behöver ett namn på en gren"
+
+#: builtin/checkout.c:1001
+msgid "Missing branch name; try -b"
+msgstr "Grennamn saknas; försök med -b"
+
+#: builtin/checkout.c:1007
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr "--orphan och -b|-B kan inte användas samtidigt"
+
+#: builtin/checkout.c:1009
+msgid "--orphan cannot be used with -t"
+msgstr "--orphan kan inte användas med -t"
+
+#: builtin/checkout.c:1019
+msgid "git checkout: -f and -m are incompatible"
+msgstr "git checkout: -f och -m är inkompatibla"
+
+#: builtin/checkout.c:1053
+msgid "invalid path specification"
+msgstr "felaktig sökvägsangivelse"
+
+#: builtin/checkout.c:1061
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+"git checkout: uppdatera sökvägar är inkompatibelt med att växla gren.\n"
+"Ville du checka ut \"%s\" som inte kan lösas som en sammanslaning?"
+
+#: builtin/checkout.c:1063
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr "git checkout: uppdatera sökvägar är inkompatibelt med att växla gren."
+
+#: builtin/checkout.c:1068
+msgid "git checkout: --detach does not take a path argument"
+msgstr "git checkout: --detach tar inte en sökväg som argument"
+
+#: builtin/checkout.c:1071
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+"git checkout: --ours/--theirs, --force och --merge är inkompatibla när\n"
+"du checkar ut från indexet."
+
+#: builtin/checkout.c:1090
+msgid "Cannot switch branch to a non-commit."
+msgstr "Kan inte växla gren på en icke-incheckning."
+
+#: builtin/checkout.c:1093
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr "--ours/--theirs är inkompatibla med att byta gren."
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr "-x och -X kan inte användas samtidigt"
+
+#: builtin/clean.c:82
+msgid ""
+"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+"clean.requireForce satt till true, men varken -n eller -f angavs; vägrar "
+"städa"
+
+#: builtin/clean.c:85
+msgid ""
+"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
+"clean"
+msgstr ""
+"clean.requireForce har standardvärdet true, men varken -n eller -f angavs; "
+"vägrar städa"
+
+#: builtin/clean.c:155 builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr "Skulle ta bort %s\n"
+
+#: builtin/clean.c:159 builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr "Tar bort %s\n"
+
+#: builtin/clean.c:162 builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr "misslyckades ta bort %s"
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr "Skulle inte ta bort %s\n"
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr "Tar inte bort %s\n"
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr "referensarkivet \"%s\" är inte en lokal katalog."
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr "misslyckades öppna \"%s\""
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr "misslyckades skapa katalogen \"%s\""
+
+#: builtin/clone.c:308 builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr "misslyckades ta status på \"%s\""
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr "%s finns och är ingen katalog"
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr "misslyckades ta status på %s\n"
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr "misslyckades ta bort länken \"%s\""
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr "misslyckades skapa länken \"%s\""
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr "misslyckades kopiera filen till \"%s\""
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr "klart.\n"
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr "Kunde inte hitta fjärrgrenen %s för att klona."
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr ""
+"HEAD hos fjärren pekar på en obefintlig referens, kan inte checka ut.\n"
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr "För många argument."
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr "Du måste ange ett arkiv att klona."
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr "flaggorna --bare och --origin %s är inkompatibla."
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr "arkivet \"%s\" finns inte"
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr "--depth ignoreras i lokala kloningar; använd file:// istället"
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr "destinationssökvägen \"%s\" finns redan och är inte en tom katalog."
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr "arbetsträdet \"%s\" finns redan."
+
+#: builtin/clone.c:706 builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr "kunde inte skapa inledande kataloger för \"%s\""
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr "kunde inte skapa arbetskatalogen \"%s\""
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr "Klonar till ett naket arkiv \"%s\"...\n"
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr "Klonar till \"%s\"...\n"
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr "Vet inte hur man klonar %s"
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr "Fjärrgrenen %s hittades inte i uppströmsarkivet %s"
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr "Du verkar ha klonat ett tomt arkiv."
+
+#: builtin/column.c:51
+msgid "--command must be the first argument"
+msgstr "--command måste vara första argument"
+
+#: builtin/commit.c:43
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+"Ditt namn och e-postadress konfigurerades automatiskt baserat på\n"
+"ditt användar-id och värdnamn. Kontrollera att de är riktiga. Du\n"
+"kan förhindra det här meddelandet genom att ställa dem explicit:\n"
+"\n"
+" git config --global user.name \"Ditt namn\"\n"
+" git config --global user.email du@example.com\n"
+"\n"
+"När du gjort det kan du rätta identiteten som användes för den här\n"
+"incheckningen med:\n"
+"\n"
+" git commit --amend --reset-author\n"
+
+#: builtin/commit.c:55
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+"Du bad om att utöka den senaste incheckningen, men om du gör det\n"
+"blir den tom. Du kan köra kommandot på nytt med --allow-empty, eller\n"
+"så kan du ta bort incheckningen helt med \"git reset HEAD^\".\n"
+
+#: builtin/commit.c:60
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+"Den tidigare \"cherry-pick\":en är nu tom, kanske på grund av en löst\n"
+"konflikt. Om du vill checka in den ändå använder du:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Annars använder du \"git reset\"\n"
+
+#: builtin/commit.c:253
+msgid "failed to unpack HEAD tree object"
+msgstr "misslyckades packa upp HEAD:s trädobjekt"
+
+#: builtin/commit.c:295
+msgid "unable to create temporary index"
+msgstr "kunde inte skapa temporär indexfil"
+
+#: builtin/commit.c:301
+msgid "interactive add failed"
+msgstr "interaktiv tilläggning misslyckades"
+
+#: builtin/commit.c:334 builtin/commit.c:355 builtin/commit.c:405
+msgid "unable to write new_index file"
+msgstr "kunde inte skriva filen new_index"
+
+#: builtin/commit.c:386
+msgid "cannot do a partial commit during a merge."
+msgstr "kan inte utföra en delvis incheckning under en sammanslagning."
+
+#: builtin/commit.c:388
+msgid "cannot do a partial commit during a cherry-pick."
+msgstr "kan inte utföra en delvis incheckning under en cherry-pick."
+
+#: builtin/commit.c:398
+msgid "cannot read the index"
+msgstr "kan inte läsa indexet"
+
+#: builtin/commit.c:418
+msgid "unable to write temporary index file"
+msgstr "kunde inte skriva temporär indexfil"
+
+#: builtin/commit.c:493 builtin/commit.c:499
+#, c-format
+msgid "invalid commit: %s"
+msgstr "felaktig incheckning: %s"
+
+#: builtin/commit.c:522
+msgid "malformed --author parameter"
+msgstr "felformad \"--author\"-flagga"
+
+#: builtin/commit.c:582
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr "Felaktig indragningssträng: \"%s\""
+
+#: builtin/commit.c:620 builtin/commit.c:653 builtin/commit.c:967
+#, c-format
+msgid "could not lookup commit %s"
+msgstr "kunde inte slå upp incheckningen %s"
+
+#: builtin/commit.c:632 builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr "(läser loggmeddelande från standard in)\n"
+
+#: builtin/commit.c:634
+msgid "could not read log from standard input"
+msgstr "kunde inte läsa logg från standard in"
+
+#: builtin/commit.c:638
+#, c-format
+msgid "could not read log file '%s'"
+msgstr "kunde inte läsa loggfilen \"%s\""
+
+#: builtin/commit.c:644
+msgid "commit has empty message"
+msgstr "incheckningen har ett tomt meddelande"
+
+#: builtin/commit.c:660
+msgid "could not read MERGE_MSG"
+msgstr "kunde inte läsa MERGE_MSG"
+
+#: builtin/commit.c:664
+msgid "could not read SQUASH_MSG"
+msgstr "kunde inte läsa SQUASH_MSG"
+
+#: builtin/commit.c:668
+#, c-format
+msgid "could not read '%s'"
+msgstr "kunde inte läsa \"%s\""
+
+#: builtin/commit.c:720
+msgid "could not write commit template"
+msgstr "kunde inte skriva incheckningsmall"
+
+#: builtin/commit.c:731
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a merge.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"Det verkar som du checkar in en sammanslagning.\n"
+"Om det inte stämmer tar du bort filen\n"
+"\t%s\n"
+"och försöker igen.\n"
+
+#: builtin/commit.c:736
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"Det verkar som du checkar in en cherry-pick.\n"
+"Om det inte stämmer tar du bort filen\n"
+"\t%s\n"
+"och försöker igen.\n"
+
+#: builtin/commit.c:748
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+"Ange incheckningsmeddelandet för dina ändringar. Rader som inleds\n"
+"med \"#\" kommer ignoreras, och ett tomt meddelande avbryter incheckningen.\n"
+
+#: builtin/commit.c:753
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+"Ange incheckningsmeddelandet för dina ändringar. Rader som inleds\n"
+"med \"#\" kommer behållas; du kan själv ta bort dem om du vill.\n"
+"Ett tomt meddelande avbryter incheckningen.\n"
+
+#: builtin/commit.c:766
+#, c-format
+msgid "%sAuthor: %s"
+msgstr "%sFörfattare: %s"
+
+#: builtin/commit.c:773
+#, c-format
+msgid "%sCommitter: %s"
+msgstr "%sIncheckare: %s"
+
+#: builtin/commit.c:793
+msgid "Cannot read index"
+msgstr "Kan inte läsa indexet"
+
+#: builtin/commit.c:830
+msgid "Error building trees"
+msgstr "Fel vid byggande av träd"
+
+#: builtin/commit.c:845 builtin/tag.c:361
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr "Ange meddelandet en av flaggorna -m eller -F.\n"
+
+#: builtin/commit.c:942
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr "Hittade ingen befintlig författare med \"%s\""
+
+#: builtin/commit.c:957 builtin/commit.c:1157
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr "Ogiltigt läge för ospårade filer: \"%s\""
+
+#: builtin/commit.c:997
+msgid "Using both --reset-author and --author does not make sense"
+msgstr "Kan inte använda både --reset-author och --author"
+
+#: builtin/commit.c:1008
+msgid "You have nothing to amend."
+msgstr "Du har inget att utöka."
+
+#: builtin/commit.c:1011
+msgid "You are in the middle of a merge -- cannot amend."
+msgstr "Du är i mitten av en sammanslagning -- kan inte utöka."
+
+#: builtin/commit.c:1013
+msgid "You are in the middle of a cherry-pick -- cannot amend."
+msgstr "Du är i mitten av en cherry-pick -- kan inte utöka."
+
+#: builtin/commit.c:1016
+msgid "Options --squash and --fixup cannot be used together"
+msgstr "Flaggorna --squash och --fixup kan inte användas samtidigt"
+
+#: builtin/commit.c:1026
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr "Endast en av -c/-C/-F/--fixup kan användas."
+
+#: builtin/commit.c:1028
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr "Flaggan -m kan inte kombineras med -c/-C/-F/--fixup."
+
+#: builtin/commit.c:1036
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr "--reset-author kan endast användas med -C, -c eller --amend."
+
+#: builtin/commit.c:1053
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr ""
+"Endast en av --include/--only/--all/--interactive/--patch kan användas."
+
+#: builtin/commit.c:1055
+msgid "No paths with --include/--only does not make sense."
+msgstr "Du måste ange sökvägar tillsammans med --include/--only."
+
+#: builtin/commit.c:1057
+msgid "Clever... amending the last one with dirty index."
+msgstr "Smart... utöka den senaste med smutsigt index."
+
+#: builtin/commit.c:1059
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr "Explicita sökvägar angavs utan -i eller -o; antar --only sökvägar..."
+
+#: builtin/commit.c:1069 builtin/tag.c:577
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr "Felaktigt städningsläge %s"
+
+#: builtin/commit.c:1074
+msgid "Paths with -a does not make sense."
+msgstr "Kan inte ange sökvägar med -a."
+
+#: builtin/commit.c:1257
+msgid "couldn't look up newly created commit"
+msgstr "kunde inte slå upp en precis skapad incheckning"
+
+#: builtin/commit.c:1259
+msgid "could not parse newly created commit"
+msgstr "kunde inte tolka en precis skapad incheckning"
+
+#: builtin/commit.c:1300
+msgid "detached HEAD"
+msgstr "frånkopplad HEAD"
+
+#: builtin/commit.c:1302
+msgid " (root-commit)"
+msgstr " (rotincheckning)"
+
+#: builtin/commit.c:1446
+msgid "could not parse HEAD commit"
+msgstr "kunde inte tolka HEAD:s incheckning"
+
+#: builtin/commit.c:1484 builtin/merge.c:509
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr "kunde inte öppna \"%s\" för läsning"
+
+#: builtin/commit.c:1491
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr "Trasig MERGE_HEAD-fil (%s)"
+
+#: builtin/commit.c:1498
+msgid "could not read MERGE_MODE"
+msgstr "kunde inte läsa MERGE_MODE"
+
+#: builtin/commit.c:1517
+#, c-format
+msgid "could not read commit message: %s"
+msgstr "kunde inte läsa incheckningsmeddelande: %s"
+
+#: builtin/commit.c:1531
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
+msgstr "Avbryter incheckning; meddelandet inte redigerat.\n"
+
+#: builtin/commit.c:1536
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr "Avbryter på grund av tomt incheckningsmeddelande.\n"
+
+#: builtin/commit.c:1551 builtin/merge.c:936 builtin/merge.c:961
+msgid "failed to write commit object"
+msgstr "kunde inte skriva incheckningsobjekt"
+
+#: builtin/commit.c:1572
+msgid "cannot lock HEAD ref"
+msgstr "kunde inte låsa HEAD-referens"
+
+#: builtin/commit.c:1576
+msgid "cannot update HEAD ref"
+msgstr "kunde inte uppdatera HEAD-referens"
+
+#: builtin/commit.c:1587
+msgid ""
+"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."
+msgstr ""
+"Arkivet har uppdaterats, men kunde inte skriva filen\n"
+"new_index. Kontrollera att disken inte är full och\n"
+"att kvoten inte har överskridits, och kör sedan\n"
+"\"git reset HEAD\" för att återställa."
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr "den annoterade taggen %s inte tillgänglig"
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr "den annoterade taggen %s har inget inbäddat namn"
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr "taggen \"%s\" är i verkligheten \"%s\" här"
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr "Objektnamnet är inte giltigt: %s"
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr "%s är inte ett giltigt \"%s\"-objekt"
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr "ingen tagg motsvarar \"%s\" exakt"
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr "söker för att beskriva %s\n"
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr "avslutade sökning på %s\n"
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+"Inga annoterade taggar kan beskriva \"%s\".\n"
+"Det finns dock oannoterade taggar: testa --tags."
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+"Inga taggar kan beskriva \"%s\".\n"
+"Testa --always, eller skapa några taggar."
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr "traverserade %lu incheckningar\n"
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+"mer än %i taggar hittades; listar de %i senaste\n"
+"gav upp sökningen vid %s\n"
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr "--long är inkompatibel med --abbrev=0"
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr "Inga namn hittades, kan inte beskriva något."
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr "--dirty är inkompatibelt med \"committish\"-värden"
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr "\"%s\": inte en normal fil eller symbolisk länk"
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr "ogiltig flagga: %s"
+
+#: builtin/diff.c:297
+msgid "Not a git repository"
+msgstr "Inte ett git-arkiv"
+
+#: builtin/diff.c:347
+#, c-format
+msgid "invalid object '%s' given."
+msgstr "objektet \"%s\" som angavs är felaktigt."
+
+#: builtin/diff.c:352
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr "mer än %d träd angavs: \"%s\""
+
+#: builtin/diff.c:362
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr "mer än två blobbar angavs: \"%s\""
+
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr "ej hanterat objekt \"%s\" angavs."
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr "Kunde inte hitta fjärr-referensen HEAD"
+
+#: builtin/fetch.c:253
+#, c-format
+msgid "object %s not found"
+msgstr "objektet %s hittades inte"
+
+#: builtin/fetch.c:259
+msgid "[up to date]"
+msgstr "[àjour]"
+
+#: builtin/fetch.c:273
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr "! %-*s %-*s -> %s (kan inte hämta i aktuell gren)"
+
+#: builtin/fetch.c:274 builtin/fetch.c:360
+msgid "[rejected]"
+msgstr "[refuserad]"
+
+#: builtin/fetch.c:285
+msgid "[tag update]"
+msgstr "[uppdaterad tagg]"
+
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
+msgid " (unable to update local ref)"
+msgstr " (kunde inte uppdatera lokal ref)"
+
+#: builtin/fetch.c:305
+msgid "[new tag]"
+msgstr "[ny tagg]"
+
+#: builtin/fetch.c:308
+msgid "[new branch]"
+msgstr "[ny gren]"
+
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[ny ref]"
+
+#: builtin/fetch.c:356
+msgid "unable to update local ref"
+msgstr "kunde inte uppdatera lokal ref"
+
+#: builtin/fetch.c:356
+msgid "forced update"
+msgstr "tvingad uppdatering"
+
+#: builtin/fetch.c:362
+msgid "(non-fast-forward)"
+msgstr "(ej snabbspolad)"
+
+#: builtin/fetch.c:393 builtin/fetch.c:685
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr "kan inte öppna %s: %s\n"
+
+#: builtin/fetch.c:402
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr "%s sände inte alla nödvändiga objekt\n"
+
+#: builtin/fetch.c:488
+#, c-format
+msgid "From %.*s\n"
+msgstr "Från %.*s\n"
+
+#: builtin/fetch.c:499
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+"vissa lokala referenser kunde inte uppdateras; testa att köra\n"
+" \"git remote prune %s\" för att ta bort gamla grenar som står i konflikt"
+
+#: builtin/fetch.c:549
+#, c-format
+msgid " (%s will become dangling)"
+msgstr " (%s kommer bli dinglande)"
+
+#: builtin/fetch.c:550
+#, c-format
+msgid " (%s has become dangling)"
+msgstr " (%s har blivit dinglande)"
+
+#: builtin/fetch.c:557
+msgid "[deleted]"
+msgstr "[borttagen]"
+
+#: builtin/fetch.c:558 builtin/remote.c:1055
+msgid "(none)"
+msgstr "(ingen)"
+
+#: builtin/fetch.c:675
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr "Vägrar hämta till aktuell gren %s i ett icke-naket arkiv"
+
+#: builtin/fetch.c:709
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr "Vet inte hur man hämtar från %s"
+
+#: builtin/fetch.c:786
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr "Flaggan \"%s\" och värdet \"%s\" är inte giltigt för %s"
+
+#: builtin/fetch.c:789
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr "Flaggan \"%s\" ignoreras för %s\n"
+
+#: builtin/fetch.c:888
+#, c-format
+msgid "Fetching %s\n"
+msgstr "Hämtar %s\n"
+
+#: builtin/fetch.c:890 builtin/remote.c:100
+#, c-format
+msgid "Could not fetch %s"
+msgstr "Kunde inte hämta %s"
+
+#: builtin/fetch.c:907
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr ""
+"Inget fjärrarkiv angavs. Ange antingen en URL eller namnet på ett\n"
+"fjärrarkiv som nya incheckningar skall hämtas från."
+
+#: builtin/fetch.c:927
+msgid "You need to specify a tag name."
+msgstr "Du måste ange namnet på en tagg."
+
+#: builtin/fetch.c:979
+msgid "fetch --all does not take a repository argument"
+msgstr "fetch --all tar inte namnet på ett arkiv som argument"
+
+#: builtin/fetch.c:981
+msgid "fetch --all does not make sense with refspecs"
+msgstr "fetch --all kan inte anges med referensspecifikationer"
+
+#: builtin/fetch.c:992
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr "Fjärren eller fjärrgruppen finns inte: %s"
+
+#: builtin/fetch.c:1000
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr "Kan inte hämta från grupp och ange referensspecifikationer"
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr "Felaktigt %s: \"%s\""
+
+#: builtin/gc.c:90
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr "tokigt lång objektkatalog %.*s"
+
+#: builtin/gc.c:221
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr "Packar arkivet automatiskt för optimal prestanda.\n"
+
+#: builtin/gc.c:224
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+"Packar arkivet automatiskt för optimal prestanda. Du kan även\n"
+"köra \"git gc\" manuellt. Se \"git help gc\" för mer information.\n"
+
+#: builtin/gc.c:251
+msgid ""
+"There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr ""
+"Det finns för många onåbara lösa objekt; kör \"git prune\" för att ta bort "
+"dem."
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr "grep: misslyckades skapa tråd. %s"
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr "Kunde inte byta katalog (chdir): %s"
+
+#: builtin/grep.c:478 builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr "kunde inte läsa träd (%s)"
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr "Kunde inte \"grep\" från objekt av typen %s"
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr "flaggan \"%c\" antar ett numeriskt värde"
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr "kan inte öppna \"%s\""
+
+#: builtin/grep.c:885
+msgid "no pattern given."
+msgstr "inget mönster angavs."
+
+#: builtin/grep.c:899
+#, c-format
+msgid "bad object %s"
+msgstr "felaktigt objekt %s"
+
+#: builtin/grep.c:940
+msgid "--open-files-in-pager only works on the worktree"
+msgstr "--open-files-in-pager fungerar endast i arbetskatalogen"
+
+#: builtin/grep.c:963
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr "--cached och --untracked kan inte användas med --no-index."
+
+#: builtin/grep.c:968
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr "--no-index och --untracked kan inte användas med revisioner."
+
+#: builtin/grep.c:971
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr "--[no-]exclude-standard kan inte användas för spårat innehåll."
+
+#: builtin/grep.c:979
+msgid "both --cached and trees are given."
+msgstr "både --cached och träd angavs."
+
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr "okänt hjälpformat: %s"
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr "Misslyckades starta emacsclient."
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr "Kunde inte tolka emacsclient-version."
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr "emacsclient version \"%d\" för gammal (< 22)."
+
+#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr "exec misslyckades för \"%s\": %s"
+
+#: builtin/help.c:211
+#, c-format
+msgid ""
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
+msgstr ""
+"\"%s\": sökväg för man-visare som ej stöds.\n"
+"Använd \"man.<verktyg>.cmd\" istället."
+
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
+msgstr ""
+"\"%s\": kommando för man-visare som stöds.\n"
+"Använd \"man.<verktyg>.path\" istället."
+
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr "De mest använda git-kommandona är:"
+
+#: builtin/help.c:355
+#, c-format
+msgid "'%s': unknown man viewer."
+msgstr "\"%s\": okänd man-visare."
+
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr "ingen man-visare hanterade förfrågan"
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr "ingen info-visare hanterade förfrågan"
+
+#: builtin/help.c:391
+#, c-format
+msgid "'%s': not a documentation directory."
+msgstr "\"%s\": inte en dokumentationskatalog."
+
+#: builtin/help.c:432 builtin/help.c:439
+#, c-format
+msgid "usage: %s%s"
+msgstr "användning: %s%s"
+
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
+msgstr "\"git %s\" är ett alias för \"%s\""
+
+#: builtin/index-pack.c:169
+#, c-format
+msgid "object type mismatch at %s"
+msgstr "objekttyp stämmer inte överens vid %s"
+
+#: builtin/index-pack.c:189
+msgid "object of unexpected type"
+msgstr "objekt av oväntad typ"
+
+#: builtin/index-pack.c:226
+#, c-format
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] "kan inte fylla %d byte"
+msgstr[1] "kan inte fylla %d byte"
+
+#: builtin/index-pack.c:236
+msgid "early EOF"
+msgstr "tidigt filslut"
+
+#: builtin/index-pack.c:237
+msgid "read error on input"
+msgstr "indataläsfel"
+
+#: builtin/index-pack.c:249
+msgid "used more bytes than were available"
+msgstr "använde fler byte än tillgängligt"
+
+#: builtin/index-pack.c:256
+msgid "pack too large for current definition of off_t"
+msgstr "paket för stort för nuvarande definition av off_t"
+
+#: builtin/index-pack.c:272
+#, c-format
+msgid "unable to create '%s'"
+msgstr "kunde inte skapa \"%s\""
+
+#: builtin/index-pack.c:277
+#, c-format
+msgid "cannot open packfile '%s'"
+msgstr "kan inte öppna paketfilen \"%s\""
+
+#: builtin/index-pack.c:291
+msgid "pack signature mismatch"
+msgstr "paketsignatur stämmer inte överens"
+
+#: builtin/index-pack.c:311
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
+msgstr "paketet har felaktigt objekt vid index %lu: %s"
+
+#: builtin/index-pack.c:405
+#, c-format
+msgid "inflate returned %d"
+msgstr "inflate returnerade %d"
+
+#: builtin/index-pack.c:450
+msgid "offset value overflow for delta base object"
+msgstr "indexvärdespill för deltabasobjekt"
+
+#: builtin/index-pack.c:458
+msgid "delta base offset is out of bound"
+msgstr "deltabasindex utanför gränsen"
+
+#: builtin/index-pack.c:466
+#, c-format
+msgid "unknown object type %d"
+msgstr "okänd objekttyp %d"
+
+#: builtin/index-pack.c:495
+msgid "cannot pread pack file"
+msgstr "kan inte utföra \"pread\" på paketfil"
+
+#: builtin/index-pack.c:497
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] "för tidigt slut på paketfilen, %lu byte saknas"
+msgstr[1] "för tidigt slut på paketfilen, %lu byte saknas"
+
+#: builtin/index-pack.c:510
+msgid "serious inflate inconsistency"
+msgstr "allvarlig inflate-inkonsekvens"
+
+#: builtin/index-pack.c:583
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "kan inte läsa befintligt objekt %s"
+
+#: builtin/index-pack.c:586
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
+msgstr "SHA1-KOLLISION UPPTÄCKT VID %s !"
+
+#: builtin/index-pack.c:598
+#, c-format
+msgid "invalid blob object %s"
+msgstr "ogiltigt blob-objekt %s"
+
+#: builtin/index-pack.c:610
+#, c-format
+msgid "invalid %s"
+msgstr "ogiltigt %s"
+
+#: builtin/index-pack.c:612
+msgid "Error in object"
+msgstr "Fel i objekt"
+
+#: builtin/index-pack.c:614
+#, c-format
+msgid "Not all child objects of %s are reachable"
+msgstr "Inte alla barnobjekt för %s kan nås"
+
+#: builtin/index-pack.c:687 builtin/index-pack.c:713
+msgid "failed to apply delta"
+msgstr "misslyckades tillämpa delta"
+
+#: builtin/index-pack.c:850
+msgid "Receiving objects"
+msgstr "Tar bort objeckt"
+
+#: builtin/index-pack.c:850
+msgid "Indexing objects"
+msgstr "Skapar index för objekt"
+
+#: builtin/index-pack.c:872
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr "paketet är trasigt (SHA1 stämmer inte)"
+
+#: builtin/index-pack.c:877
+msgid "cannot fstat packfile"
+msgstr "kan inte utföra \"fstat\" på paketfil"
+
+#: builtin/index-pack.c:880
+msgid "pack has junk at the end"
+msgstr "paket har skräp i slutet"
+
+#: builtin/index-pack.c:903
+msgid "Resolving deltas"
+msgstr "Analyserar delta"
+
+#: builtin/index-pack.c:954
+msgid "confusion beyond insanity"
+msgstr "förvirrad bortom vanvett"
+
+#: builtin/index-pack.c:973
+#, c-format
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] "paketet har %d oanalyserat delta"
+msgstr[1] "paketet har %d oanalyserade delta"
+
+#: builtin/index-pack.c:998
+#, c-format
+msgid "unable to deflate appended object (%d)"
+msgstr "kunde inte utföra \"deflate\" på tillagt objekt (%d)"
+
+#: builtin/index-pack.c:1077
+#, c-format
+msgid "local object %s is corrupt"
+msgstr "lokalt objekt %s är trasigt"
+
+#: builtin/index-pack.c:1101
+msgid "error while closing pack file"
+msgstr "fel vid stängning av paketfil"
+
+#: builtin/index-pack.c:1114
+#, c-format
+msgid "cannot write keep file '%s'"
+msgstr "kan inte ta skriva \"keep\"-fil \"%s\""
+
+#: builtin/index-pack.c:1122
+#, c-format
+msgid "cannot close written keep file '%s'"
+msgstr "akn inte stänga skriven \"keep\"-fil \"%s\""
+
+#: builtin/index-pack.c:1135
+msgid "cannot store pack file"
+msgstr "kan inte spara paketfil"
+
+#: builtin/index-pack.c:1146
+msgid "cannot store index file"
+msgstr "kan inte spara indexfil"
+
+#: builtin/index-pack.c:1247
+#, c-format
+msgid "Cannot open existing pack file '%s'"
+msgstr "Kan inte öppna befintlig paketfil \"%s\""
+
+#: builtin/index-pack.c:1249
+#, c-format
+msgid "Cannot open existing pack idx file for '%s'"
+msgstr "Kan inte öppna befintligt paket-idx-fil för \"%s\""
+
+#: builtin/index-pack.c:1296
+#, c-format
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] "icke-delta: %d objekt"
+msgstr[1] "icke-delta: %d objekt"
+
+#: builtin/index-pack.c:1303
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] "kedjelängd = %d: %lu objekt"
+msgstr[1] "kedjelängd = %d: %lu objekt"
+
+#: builtin/index-pack.c:1330
+msgid "Cannot come back to cwd"
+msgstr "Kan inte gå tillbaka till arbetskatalogen (cwd)"
+
+#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
+#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#, c-format
+msgid "bad %s"
+msgstr "felaktig %s"
+
+#: builtin/index-pack.c:1407
+msgid "--fix-thin cannot be used without --stdin"
+msgstr "--fix-thin kan inte användas med --stdin"
+
+#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr "paketfilnamnet \"%s\" slutar inte med \".pack\""
+
+#: builtin/index-pack.c:1430
+msgid "--verify with no packfile name given"
+msgstr "--verify angavs utan paketfilnamn"
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr "Kunde inte göra %s skrivbar för gruppen"
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr "tokigt långt namn på mallen %s"
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr "kan inte ta status på \"%s\""
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr "kan inte ta status på mallen \"%s\""
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr "kan inte öppna katalogen (opendir) \"%s\""
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr "kan inte läsa länk (readlink) \"%s\""
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr "tokigt lång symbolisk länk %s"
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr "kan inte skapa symbolisk länk \"%s\" \"%s\""
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr "kan inte kopiera \"%s\" till \"%s\""
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr "ignorerar mallen %s"
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr "tokigt lång mallsökväg %s"
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr "mallarna hittades inte %s"
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr "kopierade inte mallar från felaktig formatversion %d från \"%s\""
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr "tokig git-katalog %s"
+
+#: builtin/init-db.c:322 builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr "%s finns redan"
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr "kan inte hantera filtyp %d"
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr "kan inte flytta %s till %s"
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr "Kunde inte skapa gitlänk %s"
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s Git-arkiv i %s%s\n"
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr "Ominitierade befintligt"
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr "Initierade tomt"
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr " delat"
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr "kan inte läsa aktuell katalog (cwd)"
+
+#: builtin/init-db.c:521 builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr "kan inte skapa katalogen (mkdir) %s"
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr "kan inte byta katalog (chdir) till %s"
+
+#: builtin/init-db.c:554
+#, c-format
+msgid ""
+"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
+"dir=<directory>)"
+msgstr ""
+"%s (eller --work-tree=<katalog>) inte tillåtet utan att ange %s (eller --git-"
+"dir=<katalog>)"
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr "Kan inte komma åt aktuell arbetskatalog"
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr "Kan inte komma åt arbetskatalogen \"%s\""
+
+#: builtin/log.c:188
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr "Slututdata: %d %s\n"
+
+#: builtin/log.c:401 builtin/log.c:489
+#, c-format
+msgid "Could not read object %s"
+msgstr "Kunde inte läsa objektet %s"
+
+#: builtin/log.c:513
+#, c-format
+msgid "Unknown type: %d"
+msgstr "Okänd typ: %d"
+
+#: builtin/log.c:602
+msgid "format.headers without value"
+msgstr "format.headers utan värde"
+
+#: builtin/log.c:676
+msgid "name of output directory is too long"
+msgstr "namnet på utdatakatalogen är för långt"
+
+#: builtin/log.c:687
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr "Kan inte öppna patchfilen %s"
+
+#: builtin/log.c:701
+msgid "Need exactly one range."
+msgstr "Behöver precis ett intervall."
+
+#: builtin/log.c:709
+msgid "Not a range."
+msgstr "Inte ett intervall."
+
+#: builtin/log.c:786
+msgid "Cover letter needs email format"
+msgstr "Omslagsbrevet behöver e-postformat"
+
+#: builtin/log.c:859
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr "tokigt in-reply-to: %s"
+
+#: builtin/log.c:932
+msgid "Two output directories?"
+msgstr "Två utdatakataloger?"
+
+#: builtin/log.c:1153
+#, c-format
+msgid "bogus committer info %s"
+msgstr "felaktig incheckarinformation %s"
+
+#: builtin/log.c:1198
+msgid "-n and -k are mutually exclusive."
+msgstr "-n och -k kan inte användas samtidigt."
+
+#: builtin/log.c:1200
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr "--subject-prefix och -k kan inte användas samtidigt."
+
+#: builtin/log.c:1208
+msgid "--name-only does not make sense"
+msgstr "kan inte använda --name-only"
+
+#: builtin/log.c:1210
+msgid "--name-status does not make sense"
+msgstr "kan inte använda --name-status"
+
+#: builtin/log.c:1212
+msgid "--check does not make sense"
+msgstr "kan inte använda --check"
+
+#: builtin/log.c:1235
+msgid "standard output, or directory, which one?"
+msgstr "standard ut, eller katalog, vilken skall det vara?"
+
+#: builtin/log.c:1237
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr "Kunde inte skapa katalogen \"%s\""
+
+#: builtin/log.c:1390
+msgid "Failed to create output files"
+msgstr "Misslyckades skapa utdatafiler"
+
+#: builtin/log.c:1494
+#, c-format
+msgid ""
+"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr "Kunde inte hitta en spårad fjärrgren, ange <uppström> manuellt.\n"
+
+#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#, c-format
+msgid "Unknown commit %s"
+msgstr "Okänd incheckning %s"
+
+#: builtin/merge.c:90
+msgid "switch `m' requires a value"
+msgstr "flaggan \"m\" behöver ett värde"
+
+#: builtin/merge.c:127
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr "Kunde inte hitta sammanslagningsstrategin \"%s\".\n"
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Available strategies are:"
+msgstr "Tillgängliga strategier är:"
+
+#: builtin/merge.c:133
+#, c-format
+msgid "Available custom strategies are:"
+msgstr "Tillgängliga skräddarsydda strategier är:"
+
+#: builtin/merge.c:240
+msgid "could not run stash."
+msgstr "kunde köra stash."
+
+#: builtin/merge.c:245
+msgid "stash failed"
+msgstr "stash misslyckades"
+
+#: builtin/merge.c:250
+#, c-format
+msgid "not a valid object: %s"
+msgstr "inte ett giltigt objekt: %s"
+
+#: builtin/merge.c:269 builtin/merge.c:286
+msgid "read-tree failed"
+msgstr "read-tree misslyckades"
+
+#: builtin/merge.c:316
+msgid " (nothing to squash)"
+msgstr " (inget att platta till)"
+
+#: builtin/merge.c:329
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr "Tillplattningsincheckning -- uppdaterar inte HEAD\n"
+
+#: builtin/merge.c:361
+msgid "Writing SQUASH_MSG"
+msgstr "Skriver SQUASH_MSG"
+
+#: builtin/merge.c:363
+msgid "Finishing SQUASH_MSG"
+msgstr "Avslutar SQUASH_MSG"
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr "Inget sammanslagningsmeddelande -- uppdaterar inte HEAD\n"
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr "\"%s\" verkar inte peka på en incheckning"
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr "Felaktig branch.%s.mergeoptions-sträng: %s"
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr "git write-tree misslyckades skriva ett träd"
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr "misslyckads läsa cachen"
+
+#: builtin/merge.c:697
+msgid "Unable to write index."
+msgstr "Kunde inte skriva indexet."
+
+#: builtin/merge.c:710
+msgid "Not handling anything other than two heads merge."
+msgstr "Hanterar inte något annat än en sammanslagning av två huvuden."
+
+#: builtin/merge.c:724
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr "Felaktig flagga för merge-recursive: -X%s"
+
+#: builtin/merge.c:738
+#, c-format
+msgid "unable to write %s"
+msgstr "kunde inte skriva %s"
+
+#: builtin/merge.c:877
+#, c-format
+msgid "Could not read from '%s'"
+msgstr "Kunde inte läsa från \"%s\""
+
+#: builtin/merge.c:886
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr ""
+"Checkar inte in sammanslagningen; använd \"git commit\" för att slutföra "
+"den.\n"
+
+#: builtin/merge.c:892
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+"Ange ett incheckningsmeddelande för att förklara varför sammanslagningen\n"
+"är nödvändig, speciellt om den slår in en uppdaterad uppström i en\n"
+"temagren.\n"
+"\n"
+"Rader som inleds med \"#\" kommer ignoreras, och ett tomt meddelande\n"
+"avbryter incheckningen.\n"
+
+#: builtin/merge.c:916
+msgid "Empty commit message."
+msgstr "Tomt incheckningsmeddelande."
+
+#: builtin/merge.c:928
+#, c-format
+msgid "Wonderful.\n"
+msgstr "Underbart.\n"
+
+#: builtin/merge.c:993
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr ""
+"Kunde inte slå ihop automatiskt; fixa konflikter och checka in resultatet.\n"
+
+#: builtin/merge.c:1009
+#, c-format
+msgid "'%s' is not a commit"
+msgstr "\"%s\" är inte en incheckning"
+
+#: builtin/merge.c:1050
+msgid "No current branch."
+msgstr "Inte på någon gren."
+
+#: builtin/merge.c:1052
+msgid "No remote for the current branch."
+msgstr "Ingen fjärr för aktuell gren."
+
+#: builtin/merge.c:1054
+msgid "No default upstream defined for the current branch."
+msgstr "Ingen standarduppström angiven för aktuell gren."
+
+#: builtin/merge.c:1059
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr "Ingen fjärrspårande gren för %s från %s"
+
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr "%s - inte något vi kan slå ihop"
+
+#: builtin/merge.c:1214
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr "Det finns ingen sammanslagning att avbryta (MERGE_HEAD saknas)."
+
+#: builtin/merge.c:1230 git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"Du har inte avslutat sammanslagningen (MERGE_HEAD finns).\n"
+"Checka in dina ändringar innan du kan slå ihop."
+
+#: builtin/merge.c:1233 git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr "Du har inte avslutat sammanslagningen (MERGE_HEAD finns)."
+
+#: builtin/merge.c:1237
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"Du har inte avslutat din \"cherry-pick\" (CHERRY_PICK_HEAD finns).\n"
+"Checka in dina ändringar innan du kan slå ihop."
+
+#: builtin/merge.c:1240
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr "Du har inte avslutat din \"cherry-pick\" (CHERRY_PICK_HEAD finns)."
+
+#: builtin/merge.c:1249
+msgid "You cannot combine --squash with --no-ff."
+msgstr "Du kan inte kombinera --squash med --no-ff."
+
+#: builtin/merge.c:1254
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr "Du kan inte kombinera --no-ff med --ff-only."
+
+#: builtin/merge.c:1261
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr "Ingen incheckning angiven och merge.defaultToUpstream är ej satt."
+
+#: builtin/merge.c:1293
+msgid "Can merge only exactly one commit into empty head"
+msgstr "Kan endast slå ihop en enda incheckning i ett tomt huvud."
+
+#: builtin/merge.c:1296
+msgid "Squash commit into empty head not supported yet"
+msgstr "Stöder inte en tillplattningsincheckning på ett tomt huvud ännu"
+
+#: builtin/merge.c:1298
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr "Icke-snabbspolad incheckning kan inte användas med ett tomt huvud"
+
+#: builtin/merge.c:1413
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr "Uppdaterar %s..%s\n"
+
+#: builtin/merge.c:1451
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr "Försöker riktigt enkel sammanslagning i indexet...\n"
+
+#: builtin/merge.c:1458
+#, c-format
+msgid "Nope.\n"
+msgstr "Nej.\n"
+
+#: builtin/merge.c:1490
+msgid "Not possible to fast-forward, aborting."
+msgstr "Kan inte snabbspola, avbryter."
+
+#: builtin/merge.c:1513 builtin/merge.c:1592
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr "Återspolar trädet till orört...\n"
+
+#: builtin/merge.c:1517
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr "Försöker sammanslagninsstrategin %s...\n"
+
+#: builtin/merge.c:1583
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr "Ingen sammanslagningsstrategi hanterade sammanslagningen.\n"
+
+#: builtin/merge.c:1585
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr "Sammanslaning med strategin %s misslyckades.\n"
+
+#: builtin/merge.c:1594
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr "Använder %s för att förbereda lösning för hand.\n"
+
+#: builtin/merge.c:1606
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr ""
+"Automatisk sammanslagning lyckades; stoppar före incheckning som önskat\n"
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr "Kontrollerar namnbyte av \"%s\" till \"%s\"\n"
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr "felaktig källa"
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr "kan inte flytta katalog till sig själv"
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr "kan inte flytta katalog över fil"
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr "Vad? %.*s är i indexet?"
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr "källkatalogen är tom"
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr "inte versionshanterad"
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr "destinationen finns"
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr "skriver över \"%s\""
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr "Kan inte skriva över"
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr "flera källor för samma mål"
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr "%s, källa=%s, mål=%s"
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr "Byter namn på %s till %s\n"
+
+#: builtin/mv.c:215 builtin/remote.c:731
+#, c-format
+msgid "renaming '%s' failed"
+msgstr "misslyckades byta namn på \"%s\""
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr "kunde inte starta \"show\" för objektet \"%s\""
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr "kunde inte öppna (fdopen) \"show\"-utdata-filhandtag"
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr "kunde inte stänga röret till \"show\" för objektet \"%s\""
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr "kunde inte avsluta \"show\" för objektet \"%s\""
+
+#: builtin/notes.c:175 builtin/tag.c:347
+#, c-format
+msgid "could not create file '%s'"
+msgstr "kunde inte skapa filen \"%s\""
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr "Ange innehåll för anteckningen med antingen -m eller -F"
+
+#: builtin/notes.c:210 builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr "Tar bort anteckning för objektet %s\n"
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr "kunde inte skriva anteckningsobjekt"
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr "Anteckningens innehåll har lämnats kvar i %s"
+
+#: builtin/notes.c:251 builtin/tag.c:542
+#, c-format
+msgid "cannot read '%s'"
+msgstr "kunde inte läsa \"%s\""
+
+#: builtin/notes.c:253 builtin/tag.c:545
+#, c-format
+msgid "could not open or read '%s'"
+msgstr "kunde inte öppna eller läsa \"%s\""
+
+#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
+#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
+#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
+#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:558
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr "Kunde inte slå upp \"%s\" som en giltig referens."
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr "Kunde inte läsa objektet \"%s\"."
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr "Kan inte checka in oinitierat/orefererat anteckningsträd"
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr "Felaktigt värde för notes.rewriteMode: '%s'"
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr "Vägrar skriva över anteckningar i %s (utanför refs/notes/)"
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr "Felaktigt värde på %s: \"%s\""
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr "Felaktig indatarad: \"%s\"."
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr "Misslyckades kopiera anteckningar från \"%s\" till \"%s\""
+
+#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
+#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr "för många parametrar"
+
+#: builtin/notes.c:513 builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr "Inga anteckningar hittades för objektet %s."
+
+#: builtin/notes.c:580
+#, c-format
+msgid ""
+"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+"Kan inte lägga till anteckningar. Hittade befintliga anteckningar för "
+"objektet %s. Använd \"-f\" för att skriva över befintliga anteckningar"
+
+#: builtin/notes.c:585 builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr "Skriver över befintliga anteckningar för objektet %s\n"
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr "för få parametrar"
+
+#: builtin/notes.c:656
+#, c-format
+msgid ""
+"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr ""
+"Kan inte kopiera anteckningar. Hittade befintliga anteckningar för objektet "
+"%s. Använd \"-f\" för att skriva över befintliga anteckningar"
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr "Anteckningar på källobjektet %s saknas. Kan inte kopiera."
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+"Flaggorna -m/-F/-c/-C rekommenderas inte för underkommandot \"edit\".\n"
+"Använd \"git notes add -f -m/-F/-c/-C\" istället.\n"
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr "Objektet %s har ingen anteckning\n"
+
+#: builtin/notes.c:1103 builtin/remote.c:1598
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr "Okänt underkommando: %s"
+
+#: builtin/pack-objects.c:2337
+#, c-format
+msgid "unsupported index version %s"
+msgstr "indexversionen %s stöds ej"
+
+#: builtin/pack-objects.c:2341
+#, c-format
+msgid "bad index version '%s'"
+msgstr "felaktig indexversion \"%s\""
+
+#: builtin/pack-objects.c:2364
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr "flaggan %s godtar inte negativ form"
+
+#: builtin/pack-objects.c:2368
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr "kunde inte tolka värdet \"%s\" för flaggan %s"
+
+#: builtin/push.c:45
+msgid "tag shorthand without <tag>"
+msgstr "taggförkortning utan <tagg>"
+
+#: builtin/push.c:64
+msgid "--delete only accepts plain target ref names"
+msgstr "--delete godtar endast enkla målreferensnamn"
+
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+"\n"
+"För att välja ett av alternativen permanent, se push.default i \"git help "
+"config\"."
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+"Uppströmsgrenen för din nuvarande gren stämmer inte överens\n"
+"med namnet på din aktuella gren. För att sända till uppströmsgrenen\n"
+"i fjärrarkivet använder du\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"För att sända till grenen med samma namn i fjärrarkivet använder du\n"
+"\n"
+" git push %s %s\n"
+"%s"
+
+#: builtin/push.c:121
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+"Du är inte på någon gren för närvarande.\n"
+"För att sända in historiken som leder till den aktuella (frånkopplat\n"
+"HEAD) situationen använder du\n"
+"\n"
+" git push %s HEAD:<namn-på-fjärrgren>\n"
+
+#: builtin/push.c:128
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+"Den aktuella grenen %s har ingen uppströmsgren.\n"
+"För att sända aktuell gren och ange fjärrarkiv som uppström använder du\n"
+"\n"
+" git push --set-upstream %s %s\n"
+
+#: builtin/push.c:136
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr "Den aktuella grenen %s har flera uppströmsgrenar, vägrar sända."
+
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+"Du sänder till fjärren \"%s\", som inte är uppströms för den\n"
+"aktuella grenen \"%s\", utan att tala om för mig vad som\n"
+"skall sändas för att uppdatera fjärrgrenen."
+
+#: builtin/push.c:174
+msgid ""
+"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr ""
+"Du angav inga referensspecifikationer att sända, och push.default är "
+"\"nothing\"."
+
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"Uppdateringar avvisades då änden på din befintliga gren är bakom\n"
+"dess fjärrmotsvarighet. Slå ihop fjärrändringarna (t.ex. \"git pull\")\n"
+"innan du sänder igen.\n"
+"Se avsnittet \"Note about fast-forward\" i \"git push --help\" för detaljer."
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+"Uppdateringar avvisades då änden på en insänd gren är bakom dess\n"
+"fjärrmotsvarighet. Om det inte var meningen att sända in grenen, bör\n"
+"du specificera grenar att sända, eller ändra inställningsvariabeln\n"
+"\"push-default\" till \"current\" eller \"upstream\" för att endast sända\n"
+"aktuell gren."
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"Uppdateringar avvisades då änden på en gren som sänds in är bakom dess\n"
+"fjärrmotsvarighet. Checka ut grenen och slå ihop fjärrändringarna (t.ex.\n"
+"\"git pull\") innan du sänder igen.\n"
+"Se avsnittet \"Note about fast-forward\" i \"git push --help\" för detaljer."
+
+#: builtin/push.c:233
+#, c-format
+msgid "Pushing to %s\n"
+msgstr "Sänder till %s\n"
+
+#: builtin/push.c:237
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr "misslyckades sända vissa referenser till \"%s\""
+
+#: builtin/push.c:269
+#, c-format
+msgid "bad repository '%s'"
+msgstr "felaktigt arkiv \"%s\""
+
+#: builtin/push.c:270
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote "
+"repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+"Ingen destination har angivits.\n"
+"Ange antingen URL:en på kommandoraden eller ställ in ett uppströmsarkiv med\n"
+"\n"
+" git remote add <namn> <url>\n"
+"\n"
+"och sänd sedan med hjälp av fjärrnamnet\n"
+"\n"
+" git push <namn>\n"
+
+#: builtin/push.c:285
+msgid "--all and --tags are incompatible"
+msgstr "--all och --tags är inkompatibla"
+
+#: builtin/push.c:286
+msgid "--all can't be combined with refspecs"
+msgstr "--all kan inte kombineras med referensspecifikationer"
+
+#: builtin/push.c:291
+msgid "--mirror and --tags are incompatible"
+msgstr "--mirror och --tags är inkompatibla"
+
+#: builtin/push.c:292
+msgid "--mirror can't be combined with refspecs"
+msgstr "--mirror kan inte kombineras med referensspecifikationer"
+
+#: builtin/push.c:297
+msgid "--all and --mirror are incompatible"
+msgstr "--all och --mirror är inkompatibla"
+
+#: builtin/push.c:385
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr "--delete är imkompatibel med --all, --mirror och --tags"
+
+#: builtin/push.c:387
+msgid "--delete doesn't make sense without any refs"
+msgstr "--delete kan inte användas utan referenser"
+
+#: builtin/remote.c:98
+#, c-format
+msgid "Updating %s"
+msgstr "Uppdaterar %s"
+
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
+msgstr ""
+"--mirror är farlig och föråldrad; använd\n"
+"\t --mirror=fetch eller --mirror=push istället"
+
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
+msgstr "okänt argument till mirror: %s"
+
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
+msgstr "att ange en master-gren ger ingen mening med --mirror"
+
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
+msgstr "att ange grenar att spåra ger mening bara med hämtningsspeglar"
+
+#: builtin/remote.c:195 builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
+msgstr "fjärrarkivet %s finns redan."
+
+#: builtin/remote.c:199 builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
+msgstr "\"%s\" är inte ett giltigt namn på fjärrarkiv"
+
+#: builtin/remote.c:243
+#, c-format
+msgid "Could not setup master '%s'"
+msgstr "Kunde inte skapa master \"%s\""
+
+#: builtin/remote.c:299
+#, c-format
+msgid "more than one %s"
+msgstr "mer än en %s"
+
+#: builtin/remote.c:339
+#, c-format
+msgid "Could not get fetch map for refspec %s"
+msgstr "Kunde inte hämta mappning för referensspecifikation %s"
+
+#: builtin/remote.c:440 builtin/remote.c:448
+msgid "(matching)"
+msgstr "(matchande)"
+
+#: builtin/remote.c:452
+msgid "(delete)"
+msgstr "(ta bort)"
+
+#: builtin/remote.c:595 builtin/remote.c:601 builtin/remote.c:607
+#, c-format
+msgid "Could not append '%s' to '%s'"
+msgstr "Kunde inte tillämpa \"%s\" på \"%s\""
+
+#: builtin/remote.c:639 builtin/remote.c:792 builtin/remote.c:890
+#, c-format
+msgid "No such remote: %s"
+msgstr "Inget sådant fjärrarkiv: %s"
+
+#: builtin/remote.c:656
+#, c-format
+msgid "Could not rename config section '%s' to '%s'"
+msgstr "Kunde inte byta namn på konfigurationssektionen \"%s\" till \"%s\""
+
+#: builtin/remote.c:662 builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
+msgstr "Kunde inte ta bort konfigurationssektionen \"%s\""
+
+#: builtin/remote.c:677
+#, c-format
+msgid ""
+"Not updating non-default fetch refspec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
+msgstr ""
+"Uppdaterar inte icke-standard hämtningsreferensspecifikation\n"
+"\t%s\n"
+"\tUppdatera konfigurationen manuellt om nödvändigt."
+
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
+msgstr "Kunde inte lägga till på \"%s\""
+
+#: builtin/remote.c:694
+#, c-format
+msgid "Could not set '%s'"
+msgstr "Kunde inte sätta \"%s\""
+
+#: builtin/remote.c:716
+#, c-format
+msgid "deleting '%s' failed"
+msgstr "misslyckades ta bort \"%s\""
+
+#: builtin/remote.c:750
+#, c-format
+msgid "creating '%s' failed"
+msgstr "misslyckades skapa \"%s\""
+
+#: builtin/remote.c:764
+#, c-format
+msgid "Could not remove branch %s"
+msgstr "Kunde inte ta bort grenen %s"
+
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] ""
+"Observera: En gren utanför hierarkin refs/remotes/ togs inte bort;\n"
+"för att ta bort den, använd:"
+msgstr[1] ""
+"Observera: Några grenar utanför hierarkin refs/remotes/ togs inte bort;\n"
+"för att ta bort dem, använd:"
+
+#: builtin/remote.c:943
+#, c-format
+msgid " new (next fetch will store in remotes/%s)"
+msgstr " ny (nästa hämtning sparar i remotes/%s)"
+
+#: builtin/remote.c:946
+msgid " tracked"
+msgstr " spårad"
+
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
+msgstr " förlegad (använd \"git remote prune\" för att ta bort)"
+
+#: builtin/remote.c:950
+msgid " ???"
+msgstr " ???"
+
+#: builtin/remote.c:991
+#, c-format
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
+msgstr "ogiltig branch.%s.merge; kan inte ombasera över > 1 gren"
+
+#: builtin/remote.c:998
+#, c-format
+msgid "rebases onto remote %s"
+msgstr "ombaseras på fjärren %s"
+
+#: builtin/remote.c:1001
+#, c-format
+msgid " merges with remote %s"
+msgstr " sammanslås med fjärren %s"
+
+#: builtin/remote.c:1002
+msgid " and with remote"
+msgstr " och med fjärren"
+
+#: builtin/remote.c:1004
+#, c-format
+msgid "merges with remote %s"
+msgstr "sammanslås med fjärren %s"
+
+#: builtin/remote.c:1005
+msgid " and with remote"
+msgstr " och med fjärren"
+
+#: builtin/remote.c:1051
+msgid "create"
+msgstr "skapa"
+
+#: builtin/remote.c:1054
+msgid "delete"
+msgstr "ta bort"
+
+#: builtin/remote.c:1058
+msgid "up to date"
+msgstr "àjour"
+
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
+msgstr "kan snabbspolas"
+
+#: builtin/remote.c:1064
+msgid "local out of date"
+msgstr "lokal föråldrad"
+
+#: builtin/remote.c:1071
+#, c-format
+msgid " %-*s forces to %-*s (%s)"
+msgstr " %-*s tvingar till %-*s (%s)"
+
+#: builtin/remote.c:1074
+#, c-format
+msgid " %-*s pushes to %-*s (%s)"
+msgstr " %-*s sänder till %-*s (%s)"
+
+#: builtin/remote.c:1078
+#, c-format
+msgid " %-*s forces to %s"
+msgstr " %-*s tvingar till %s"
+
+#: builtin/remote.c:1081
+#, c-format
+msgid " %-*s pushes to %s"
+msgstr " %-*s sänder till %s"
+
+#: builtin/remote.c:1118
+#, c-format
+msgid "* remote %s"
+msgstr "* fjärr %s"
+
+#: builtin/remote.c:1119
+#, c-format
+msgid " Fetch URL: %s"
+msgstr " Hämt-URL: %s"
+
+#: builtin/remote.c:1120 builtin/remote.c:1285
+msgid "(no URL)"
+msgstr "(ingen URL)"
+
+#: builtin/remote.c:1129 builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
+msgstr " Sänd-URL: %s"
+
+#: builtin/remote.c:1133 builtin/remote.c:1135 builtin/remote.c:1137
+#, c-format
+msgid " HEAD branch: %s"
+msgstr " HEAD-gren: %s"
+
+#: builtin/remote.c:1139
+#, c-format
+msgid ""
+" HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
+msgstr " HEAD-gren (HEAD på fjärr är tvetydig, kan vara en av följande):\n"
+
+#: builtin/remote.c:1151
+#, c-format
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] " Fjärrgren:%s"
+msgstr[1] " Fjärrgrenar:%s"
+
+#: builtin/remote.c:1154 builtin/remote.c:1181
+msgid " (status not queried)"
+msgstr " (status inte förfrågad)"
+
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] " Lokal gren konfigurerad för \"git pull\":"
+msgstr[1] " Lokala grenar konfigurerade för \"git pull\":"
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr " Lokala referenser speglas av \"git push\""
+
+#: builtin/remote.c:1178
+#, c-format
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] " Lokal referens konfigurerad för \"git push\"%s:"
+msgstr[1] " Lokala referenser konfigurerade för \"git push\"%s:"
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
+msgstr "Kan inte bestämma HEAD på fjärren"
+
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
+msgstr "Flera HEAD-grenar på fjärren. Välj en explicit med:"
+
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
+msgstr "Kunde inte ta bort %s"
+
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
+msgstr "Inte en giltig referens: %s"
+
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
+msgstr "Kunde inte ställa in %s"
+
+#: builtin/remote.c:1274
+#, c-format
+msgid " %s will become dangling!"
+msgstr " %s kommer bli dinglande!"
+
+#: builtin/remote.c:1275
+#, c-format
+msgid " %s has become dangling!"
+msgstr " %s har blivit dinglande!"
+
+#: builtin/remote.c:1281
+#, c-format
+msgid "Pruning %s"
+msgstr "Rensar %s"
+
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
+msgstr "URL: %s"
+
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
+msgstr " * [skulle rensa] %s"
+
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
+msgstr " * [rensad] %s"
+
+#: builtin/remote.c:1387 builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
+msgstr "Ingen sådan fjärr \"%s\""
+
+#: builtin/remote.c:1414
+msgid "no remote specified"
+msgstr "ingen fjärr angavs"
+
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
+msgstr "--add --delete ger ingen mening"
+
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
+msgstr "Felaktig gammalt URL-mönster: %s"
+
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr "Ingen sådan URL hittades: %s"
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
+msgstr "Kommer inte ta bort alla icke-sänd-URL:er"
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr "blandad"
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr "mjuk"
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr "hård"
+
+#: builtin/reset.c:33
+msgid "merge"
+msgstr "sammanslagning"
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr "behåll"
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr "Du har inte en giltig HEAD."
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr "Kunde inte hitta träder för HEAD."
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr "Kunde inte hitta träder för %s."
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr "Kunde inte skriva ny indexfil."
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr "HEAD är nu på %s"
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr "Kunde inte läsa indexet"
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr "Oköade ändringar efter återställning:"
+
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr "Kan inte utföra en %s återställning mitt i en sammanslagning."
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr "Kan inte tolka objektet \"%s\""
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr "--patch är inkompatibel med --{hard,mixed,soft}"
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr ""
+"--mixed rekommenderas inte med sökvägar; använd \"git reset -- <sökvägar>\"."
+
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr "Kan inte göra %s återställning med sökvägar."
+
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr "%s återställning tillåts inte i ett naket arkiv"
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr "Kunde inte återställa indexfilen till versionen \"%s\"."
+
+#: builtin/revert.c:70 builtin/revert.c:92
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr "%s: %s kan inte användas med %s"
+
+#: builtin/revert.c:131
+msgid "program error"
+msgstr "programfel"
+
+#: builtin/revert.c:221
+msgid "revert failed"
+msgstr "\"revert\" misslyckades"
+
+#: builtin/revert.c:236
+msgid "cherry-pick failed"
+msgstr "\"cherry-pick\" misslyckades"
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+"\"%s\" har köat ändringar som skiljer sig både från filen och HEAD\n"
+"(använd -f för att tvinga borttagning)"
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"\"%s\" har köade ändringar i indexet\n"
+"(använd --cached för att behålla filen eller -f för att tvinga borttagning)"
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"\"%s\" har lokala ändringar\n"
+"(använd --cached för att behålla filen eller -f för att tvinga borttagning)"
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr "tar inte bort \"%s\" rekursivt utan -r"
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr "git rm: kan inte ta bort %s"
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr "Författare saknas: %s"
+
+#: builtin/tag.c:60
+#, c-format
+msgid "malformed object at '%s'"
+msgstr "felformat objekt vid \"%s\""
+
+#: builtin/tag.c:207
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr "taggnamnet för långt: %.*s..."
+
+#: builtin/tag.c:212
+#, c-format
+msgid "tag '%s' not found."
+msgstr "taggen \"%s\" hittades inte."
+
+#: builtin/tag.c:227
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr "Tog bort tagg \"%s\" (var %s)\n"
+
+#: builtin/tag.c:239
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr "kunde inte bekräfta taggen \"%s\""
+
+#: builtin/tag.c:249
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# Skriv ett taggmeddelande\n"
+"# Rader som inleds med \"#\" ignoreras.\n"
+"#\n"
+
+#: builtin/tag.c:256
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you "
+"want to.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# Skriv ett taggmeddelande\n"
+"# Rader som inleds med \"#\" kommer behållas; du kan själv ta bort dem om\n"
+"# du vill.\n"
+"#\n"
+
+#: builtin/tag.c:298
+msgid "unable to sign the tag"
+msgstr "kunde inte signera taggen"
+
+#: builtin/tag.c:300
+msgid "unable to write tag file"
+msgstr "kunde inte skriva tagg-filen"
+
+#: builtin/tag.c:325
+msgid "bad object type."
+msgstr "felaktig objekttyp"
+
+#: builtin/tag.c:338
+msgid "tag header too big."
+msgstr "tagghuvud för stort."
+
+#: builtin/tag.c:370
+msgid "no tag message?"
+msgstr "inget taggmeddelande?"
+
+#: builtin/tag.c:376
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr "Taggmeddelandet har lämnats i %s\n"
+
+#: builtin/tag.c:425
+msgid "switch 'points-at' requires an object"
+msgstr "flaggan \"points-at\" behöver ett object"
+
+#: builtin/tag.c:427
+#, c-format
+msgid "malformed object name '%s'"
+msgstr "felformat objektnamn \"%s\""
+
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr "--column och -n är inkompatibla"
+
+#: builtin/tag.c:523
+msgid "-n option is only allowed with -l."
+msgstr "Flaggan -n är endast tillåten tillsammans med -l."
+
+#: builtin/tag.c:525
+msgid "--contains option is only allowed with -l."
+msgstr "Flaggan --contains är endast tillåten tillsammans med -l"
+
+#: builtin/tag.c:527
+msgid "--points-at option is only allowed with -l."
+msgstr "Flaggan --points-at är endast tillåten tillsammans med -l."
+
+#: builtin/tag.c:535
+msgid "only one -F or -m option is allowed."
+msgstr "endast en av flaggorna -F eller -m tillåts."
+
+#: builtin/tag.c:555
+msgid "too many params"
+msgstr "för många parametrar"
+
+#: builtin/tag.c:561
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr "\"%s\" är inte ett giltigt taggnamn."
+
+#: builtin/tag.c:566
+#, c-format
+msgid "tag '%s' already exists"
+msgstr "taggen \"%s\" finns redan"
+
+#: builtin/tag.c:584
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr "%s: kan inte låsa referensen"
+
+#: builtin/tag.c:586
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr "%s: kan inte uppdatera referensen"
+
+#: builtin/tag.c:588
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr "Uppdaterad tagg \"%s\" (var %s)\n"
+
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr ""
+"Se \"git help <kommando>\" för mer information om ett specifikt kommando."
+
+#: parse-options.h:133 parse-options.h:235
+msgid "n"
+msgstr "n"
+
+#: parse-options.h:141
+msgid "time"
+msgstr "tid"
+
+# %s är ett verb ("Untracked"/"Ignored"); lägg till ett -e.
+#: parse-options.h:149
+msgid "file"
+msgstr "fil"
+
+#: parse-options.h:151
+msgid "when"
+msgstr "när"
+
+#: parse-options.h:156
+msgid "no-op (backward compatibility)"
+msgstr "ingen funktion (bakåtkompatibilitet)"
+
+#: parse-options.h:228
+msgid "be more verbose"
+msgstr "var mer pratsam"
+
+#: parse-options.h:230
+msgid "be more quiet"
+msgstr "var mer tyst"
+
+#: parse-options.h:236
+msgid "use <n> digits to display SHA-1s"
+msgstr "använd <n> siffror för att visa SHA-1:or"
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr "Lägg filinnehåll till indexet"
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr "Binärsök för att hitta ändringen som introducerade ett fel"
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr "Visa, skapa eller ta bort grenar"
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr "Checka ut en gren eller filer i arbetskatalogen"
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr "Klona ett arkiv till en ny katalog"
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr "Protokollför ändringar i arkivet"
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr "Visa ändringar mellan incheckningar, med arbetskatalogen, osv"
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr "Hämta objekt och referenser från annat arkiv"
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr "Visa rader som motsvarar mönster"
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr "Skapa tomt git-arkiv eller ominitiera ett befintligt"
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr "Visa incheckningsloggar"
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr "Slå ihop två eller flera utvecklingshistorier"
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr "Flytta eller byt namn på en fil, katalog eller symbolisk länk"
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr "Hämta från och slå ihop med annat arkiv eller en lokal gren"
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr "Uppdatera fjärr-referenser och tillhörande objekt"
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr "Framåtanpassa lokala kommandon på uppdaterat uppströmshuvud"
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr "Återställ aktuell HEAD till angivet tillstånd"
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr "Ta bort filer från arbetskatalogen och från indexet"
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr "Visa olika sorters objekt"
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr "Visa status för arbetskatalogen"
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr "Skapa, visa, ta bort eller verifiera ett taggobjekt signerat med GPG"
+
+#: git-am.sh:50
+msgid "You need to set your committer info first"
+msgstr "Du måste ställa in din incheckarinformation först"
+
+#: git-am.sh:95
+msgid ""
+"You seem to have moved HEAD since the last 'am' failure.\n"
+"Not rewinding to ORIG_HEAD"
+msgstr ""
+"Du verkar ha flyttat HEAD sedan \"am\" sist misslyckades.\n"
+"Återställer inte till ORIG_HEAD"
+
+#: git-am.sh:105
+#, sh-format
+msgid ""
+"When you have resolved this problem run \"$cmdline --resolved\".\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"När du har löst problemet kör du \"$cmdline --resolved\".\n"
+"Om du vill hoppa över patchen kör du istället \"$cmdline --skip\".\n"
+"För att återställa originalgrenen och avbryta kör du \"$cmdline --abort\"."
+
+#: git-am.sh:121
+msgid "Cannot fall back to three-way merge."
+msgstr "Kan inte falla tillbaka på trevägssammanslagning."
+
+#: git-am.sh:137
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr ""
+"Arkivet saknar objekt som behövs för att falla tillbaka på 3-"
+"vägssammanslagning."
+
+#: git-am.sh:154
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+"Har du handredigerat din patch?\n"
+"Den kan inte tillämpas på blobbar som antecknats i dess index."
+
+#: git-am.sh:163
+msgid "Falling back to patching base and 3-way merge..."
+msgstr ""
+"Faller tillbaka på att pacha grundversionen och trevägssammanslagning..."
+
+#: git-am.sh:275
+msgid "Only one StGIT patch series can be applied at once"
+msgstr "Endast en StGIT-patchserie kan tillämpas åt gången"
+
+#: git-am.sh:362
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr "Patchformatet $patch_format stöds inte."
+
+#: git-am.sh:364
+msgid "Patch format detection failed."
+msgstr "Misslyckades detektera patchformat."
+
+#: git-am.sh:418
+msgid "-d option is no longer supported. Do not use."
+msgstr "Flaggan -d stöds inte lägre. Använd inte."
+
+#: git-am.sh:481
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr "tidigare rebase-katalog $dotest finns fortfarande, men mbox angavs."
+
+#: git-am.sh:486
+msgid "Please make up your mind. --skip or --abort?"
+msgstr "Bestäm dig. --skip eller --abort?"
+
+#: git-am.sh:513
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr "Lösningsoperation pågår inte, vi återupptar inte."
+
+#: git-am.sh:579
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr "Smutsigt index: kan inte tillämpa patchar (smutsiga: $files)"
+
+#: git-am.sh:671
+#, sh-format
+msgid ""
+"Patch is empty. Was it split wrong?\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"Patchen är tom. Delades den upp felaktigt?\n"
+"Om du vill hoppa över patchen kör du istället \"$cmdline --skip\".\n"
+"För att återställa originalgrenen och avbryta kör du \"$cmdline --abort\"."
+
+#: git-am.sh:708
+msgid "Patch does not have a valid e-mail address."
+msgstr "Patchen har inte någon giltig e-postadress."
+
+#: git-am.sh:755
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr ""
+"kan inte vara interaktiv om standard in inte är ansluten till en terminal."
+
+#: git-am.sh:759
+msgid "Commit Body is:"
+msgstr "Incheckningskroppen är:"
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:766
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr "Tillämpa? Y=ja/N=nej/E=redigera/V=visa patch/A=godta alla "
+
+#: git-am.sh:802
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr "Tillämpar: $FIRSTLINE"
+
+#: git-am.sh:823
+msgid ""
+"No changes - did you forget to use 'git add'?\n"
+"If there is nothing left to stage, chances are that something else\n"
+"already introduced the same changes; you might want to skip this patch."
+msgstr ""
+"Inga ändringar - glömde du använda \"git add\"?\n"
+"Om det inte är något kvar att köa kan det hända att något annat redan\n"
+"introducerat samma ändringar; kanske du bör hoppa över patchen."
+
+#: git-am.sh:831
+msgid ""
+"You still have unmerged paths in your index\n"
+"did you forget to use 'git add'?"
+msgstr ""
+"Du har fortfarande sökvägar som inte slagits samman i ditt index\n"
+"glömde du använda \"git add\"?"
+
+#: git-am.sh:847
+msgid "No changes -- Patch already applied."
+msgstr "Inga ändringar -- Patchen har redan tillämpats."
+
+#: git-am.sh:857
+#, sh-format
+msgid "Patch failed at $msgnum $FIRSTLINE"
+msgstr "Patchen misslyckades vid $msgnum $FIRSTLINE"
+
+#: git-am.sh:873
+msgid "applying to an empty history"
+msgstr "tillämpar på en tom historik"
+
+#: git-bisect.sh:48
+msgid "You need to start by \"git bisect start\""
+msgstr "Du måste starta med \"git bisect start\""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr "Vill du att jag ska göra det åt dig [Y=ja/N=nej]?"
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr "flaggan känns inte igen: \"$arg\""
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr "\"$arg\" verkar inte vara en giltig revision"
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr "Felaktigt HEAD - Jag behöver ett HEAD"
+
+#: git-bisect.sh:130
+#, sh-format
+msgid ""
+"Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr ""
+"Misslyckades checka ut \"$start_head\". Försök \"git bisect reset "
+"<giltiggren>\""
+
+# cogito-relaterat
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr "kör inte \"bisect\" på ett \"seeked\"-träd"
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr "Felaktigt HEAD - konstig symbolisk referens"
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr "Felaktigt argument till bisect_write: $state"
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr "Felaktig rev-indata: $arg"
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr "Anropa \"bisect_state\" med minst ett argument."
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr "Felaktig rev-indata: $rev"
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr "\"git bisect bad\" kan bara ta ett argument."
+
+#. have bad but not good. we could bisect although
+#. this is less optimum.
+#: git-bisect.sh:273
+msgid "Warning: bisecting only with a bad commit."
+msgstr "Varning: utför \"bisect\" med endast en dålig incheckning"
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr "Är du säker [Y=ja/N=nej]? "
+
+#: git-bisect.sh:289
+msgid ""
+"You need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"Du måste ange åtminstone en bra och en dålig version.\n"
+"(Du kan använda \"git bisect bad\" och \"git bisect good\" för detta.)"
+
+#: git-bisect.sh:292
+msgid ""
+"You need to start by \"git bisect start\".\n"
+"You then need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"Du måste starta med \"git bisect start\".\n"
+"Du måste sedan ange åtminstone en bra och en dålig version.\n"
+"(Du kan använda \"git bisect bad\" och \"git bisect good\" för detta.)"
+
+#: git-bisect.sh:347 git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr "Vi utför ingen bisect för tillfället."
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr "\"$invalid\" är inte en giltig incheckning"
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+"Kunde inte checka ut original-HEAD \"$branch\".\n"
+"Försök \"git bisect reset <incheckning>\"."
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr "Ingen loggfil angiven"
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr "kan inte läsa $file för uppspelning"
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr "?? vad menar du?"
+
+#: git-bisect.sh:420
+#, sh-format
+msgid "running $command"
+msgstr "kör $command"
+
+#: git-bisect.sh:427
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"exit code $res from '$command' is < 0 or >= 128"
+msgstr ""
+"\"bisect\"-körningen misslyckades:\n"
+"felkod $res från \"$command\" är < 0 eller >= 128"
+
+#: git-bisect.sh:453
+msgid "bisect run cannot continue any more"
+msgstr "\"bisect\"-körningen kan inte fortsätta längre"
+
+#: git-bisect.sh:459
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"'bisect_state $state' exited with error code $res"
+msgstr ""
+"\"bisect\"-körningen misslyckades:\n"
+"\"bisect_state $state\" avslutades med felkoden $res"
+
+#: git-bisect.sh:466
+msgid "bisect run success"
+msgstr "\"bisect\"-körningen lyckades"
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+"Du kan inte göra en \"pull\" då du har ändringar som inte checkats in.\n"
+"Rätta dem i din arbetskatalog och använd sedan \"git add/rm <fil>\"\n"
+"där det är lämpligt för att ange lösning, eller använd \"git commit -a\"."
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr "Du kan inte göra en \"pull\" då du har ändringar som inte checkats in."
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr "uppdaterar en ofödd gren med ändringar som lagts till i indexet"
+
+#. The fetch involved updating the current branch.
+#. The working tree and the index file is still based on the
+#. $orig_head commit, but we are merging into $curr_head.
+#. First update the working tree to match $curr_head.
+#: git-pull.sh:228
+#, sh-format
+msgid ""
+"Warning: fetch updated the current branch head.\n"
+"Warning: fast-forwarding your working tree from\n"
+"Warning: commit $orig_head."
+msgstr ""
+"Varning: fetch uppdaterade huvudet för aktuell gren.\n"
+"Varning: snabbspolar din arbetskatalog från\n"
+"Varning: incheckningen $orig_head."
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr "Kan inte slå ihop flera grenar i ett tomt huvud."
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr "Kan inte utföra en \"rebase\" ovanpå flera grenar"
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr "\"git stash clear\" med parametrar har inte implementerats"
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr "Du har inte den första incheckningen ännu"
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr "Kan inte spara aktuellt tillstånd för indexet"
+
+#: git-stash.sh:123 git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr "Kan inte spara aktuellt tillstånd för arbetskatalogen"
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr "Inga ändringar valda"
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr "Kan inte ta bort temporärt index (kan inte inträffa)"
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr "Kan inte registrera tillstånd för arbetskatalog"
+
+#. TRANSLATORS: $option is an invalid option, like
+#. `--blah-blah'. The 7 spaces at the beginning of the
+#. second line correspond to "error: ". So you should line
+#. up the second line with however many characters the
+#. translation of "error: " takes in your language. E.g. in
+#. English this is:
+#.
+#. $ git stash save --blah-blah 2>&1 | head -n 2
+#. error: unknown option for 'stash save': --blah-blah
+#. To provide a message, use git stash save -- '--blah-blah'
+#: git-stash.sh:202
+#, sh-format
+msgid ""
+"error: unknown option for 'stash save': $option\n"
+" To provide a message, use git stash save -- '$option'"
+msgstr ""
+"fel: felaktig flagga för \"stash save\": $option\n"
+" För att ange ett meddelande, använd git stash save -- \"$option\""
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr "Inga lokala ändringar att spara"
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr "Kan inte initiera \"stash\""
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr "Kan inte spara aktuell status"
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr "Kan inte ta bort ändringar i arbetskatalogen"
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr "Ingen \"stash\" hittades."
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr "För många revisioner angivna: $REV"
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr "$reference är inte en giltig referens."
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr "\"$args\" är inte en \"stash\"-liknande incheckning"
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr "\"$args\" är inte en \"stash\"-referens"
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr "kan inte uppdatera indexet"
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr "Kan inte tillämpa en \"stash\" mitt i en sammanslagning"
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr "Konflikter i indexet. Testa utan --index."
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr "Kunde inte spara indexträd"
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr "Kan inte ta bort ändrade filer ur kön"
+
+#: git-stash.sh:474
+msgid "Index was not unstashed."
+msgstr "Indexet har inte tagits ur kön."
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr "Kastade ${REV} ($s)"
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr "${REV}: Kunde inte kasta \"stash\"-post"
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr "Inget grennamn angavs"
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr "(För att återställa dem, skriv \"git stash apply\")"
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr "kan inte ta bort en komponent från url:en \"$remoteurl\""
+
+#: git-submodule.sh:109
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr ""
+"Hittade ingen undermodulmappning i .gitmodules för sökvägen \"$sm_path\""
+
+#: git-submodule.sh:150
+#, sh-format
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr "Misslyckades klona \"$url\" till undermodulsökvägen \"$sm_path\""
+
+#: git-submodule.sh:160
+#, sh-format
+msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
+msgstr "Gitkatalog \"$a\" ingår i underkatalogsökvägen \"$b\" eller omvänt"
+
+#: git-submodule.sh:249
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr "arkiv-URL: \"$repo\" måste vara absolut eller börja med ./|../"
+
+#: git-submodule.sh:266
+#, sh-format
+msgid "'$sm_path' already exists in the index"
+msgstr "\"$sm_path\" finns redan i indexet"
+
+#: git-submodule.sh:270
+#, sh-format
+msgid ""
+"The following path is ignored by one of your .gitignore files:\n"
+"$sm_path\n"
+"Use -f if you really want to add it."
+msgstr ""
+"Följande sökvägar ignoreras av en av dina .gitignore-filer:\n"
+"$sm_path\n"
+"Använd -f om du verkligen vill lägga till den"
+
+#: git-submodule.sh:281
+#, sh-format
+msgid "Adding existing repo at '$sm_path' to the index"
+msgstr "Lägger till befintligt arkiv i \"$sm_path\" i indexet"
+
+#: git-submodule.sh:283
+#, sh-format
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "\"$sm_path\" finns redan och är inte ett giltigt git-arkiv"
+
+#: git-submodule.sh:297
+#, sh-format
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr "Kan inte checka ut undermodulen \"$sm_path\""
+
+#: git-submodule.sh:302
+#, sh-format
+msgid "Failed to add submodule '$sm_path'"
+msgstr "Misslyckades lägga till undermodulen \"$sm_path\""
+
+#: git-submodule.sh:307
+#, sh-format
+msgid "Failed to register submodule '$sm_path'"
+msgstr "Misslyckades registrera undermodulen \"$sm_path\""
+
+#: git-submodule.sh:349
+#, sh-format
+msgid "Entering '$prefix$sm_path'"
+msgstr "GÃ¥r in i \"$prefix$sm_path\""
+
+#: git-submodule.sh:363
+#, sh-format
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr ""
+"Stoppar på \"$sm_path\"; skriptet returnerade en status skild från noll."
+
+#: git-submodule.sh:406
+#, sh-format
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr "Hittade ingen url för undermodulsökvägen \"$sm_path\" i .gitmodules"
+
+#: git-submodule.sh:415
+#, sh-format
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr "Misslyckades registrera url för underkatalogsökväg \"$sm_path\""
+
+#: git-submodule.sh:417
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr "Undermodulen \"$name\" ($url) registrerad för sökvägen \"$sm_path\""
+
+#: git-submodule.sh:425
+#, sh-format
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr ""
+"Misslyckades registrera uppdateringsläge för undermodulsökväg \"$sm_path\""
+
+#: git-submodule.sh:524
+#, sh-format
+msgid ""
+"Submodule path '$sm_path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+"Undermodulen \"$sm_path\" har inte initierats\n"
+"Kanske du vill köra \"update --init\"?"
+
+#: git-submodule.sh:537
+#, sh-format
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr "Kan inte hitta aktuell revision i undermodulsökväg \"$sm_path\""
+
+#: git-submodule.sh:556
+#, sh-format
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr "Kan inte hämta i undermodulsökväg \"$sm_path\""
+
+#: git-submodule.sh:570
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr "Kan inte ombasera \"$sha1\" i undermodulsökväg \"$sm_path\""
+
+#: git-submodule.sh:571
+#, sh-format
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr "Undermodulsökvägen \"$sm_path\": ombaserade in i \"$sha1\""
+
+#: git-submodule.sh:576
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr "Kan inte slå ihop \"$sha1\" i undermodulsökvägen \"$sm_path\""
+
+#: git-submodule.sh:577
+#, sh-format
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr "Undermodulsökvägen \"$sm_path\": sammanslagen i \"$sha1\""
+
+#: git-submodule.sh:582
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr "Kan inte checka ut \"$sha1\" i undermodulsökvägen \"$sm_path\""
+
+#: git-submodule.sh:583
+#, sh-format
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr "Undermodulsökvägen \"$sm_path\": checkade ut \"$sha1\""
+
+#: git-submodule.sh:605 git-submodule.sh:928
+#, sh-format
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr "Misslyckades rekursera in i undermodulsökvägen \"$sm_path\""
+
+#: git-submodule.sh:713
+msgid "--cached cannot be used with --files"
+msgstr "--cached kan inte användas med --files"
+
+#. unexpected type
+#: git-submodule.sh:753
+#, sh-format
+msgid "unexpected mode $mod_dst"
+msgstr "oväntat läge $mod_dst"
+
+#: git-submodule.sh:771
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr " Varning: $name innehåller inte incheckning $sha1_src"
+
+#: git-submodule.sh:774
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr " Varning: $name innehåller inte incheckning $sha1_dst"
+
+#: git-submodule.sh:777
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr " Varning: $name innehåller inte incheckningar $sha1_src och $sha1_dst"
+
+#: git-submodule.sh:802
+msgid "blob"
+msgstr "blob"
+
+#: git-submodule.sh:803
+msgid "submodule"
+msgstr "undermodul"
+
+#: git-submodule.sh:840
+msgid "# Submodules changed but not updated:"
+msgstr "# Undermoduler ändrade men inte uppdaterade:"
+
+#: git-submodule.sh:842
+msgid "# Submodule changes to be committed:"
+msgstr "# Undermodulers ändringar att checka in:"
+
+#: git-submodule.sh:974
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr "Synkroniserar undermodul-url för \"$name\""
+
+#~ msgid "cherry-pick"
+#~ msgstr "cherry-pick"
+
+#~ msgid "Please enter the commit message for your changes."
+#~ msgstr "Ange ett incheckningsmeddelande för dina ändringar."
+
+#~ msgid "Could not extract email from committer identity."
+#~ msgstr "Kunde inte extrahera e-postadress från incheckarens identitet."
+
+#~ msgid "--"
+#~ msgstr "--"
+
+#~ msgid "Too many options specified"
+#~ msgstr "För många flaggor angavs"
+
+#~ msgid "# Changed but not updated:"
+#~ msgstr "# Ändrade men inte uppdaterade:"
+
+#~ msgid "A branch named '%s' already exists."
+#~ msgstr "Det finns redan en gren som heter \"%s\""
+
+#~ msgid "path '%s' does not have all 3 versions"
+#~ msgstr "sökvägen \"%s\" har inte alla 3 versionerna"
+
+#~ msgid "git checkout: we do not like '%s' as a branch name."
+#~ msgstr "git checkout: vi tycker inte om \"%s\" som namn på en gren."
+
+#~ msgid "git checkout: branch %s already exists"
+#~ msgstr "git checkout: grenen %s finns redan"
+
+#~ msgid "Paths with --interactive does not make sense."
+#~ msgstr "Kan inte ange sökvägar med --interactive."
+
+#~ msgid "No HEAD commit to compare with (yet)"
+#~ msgstr "Ingen HEAD-incheckning att jämföra med (ännu)"
+
+#~ msgid "cannot mix --fixed-strings and regexp"
+#~ msgstr "kan inte blanda --fixed-strings och reguljära uttryck"
+
+#~ msgid "invalid --decorate option: %s"
+#~ msgstr "felaktigt värde till --decorate: %s"
+
+#~ msgid "%s; will overwrite!"
+#~ msgstr "%s; kommer skriva över!"
+
+#~ msgid "Failed to write current notes tree to database"
+#~ msgstr "Kunde inte skriva aktuellt anteckningsträd till databasen"
+
+#~ msgid "Failed to commit notes tree to database"
+#~ msgstr "Kunde inte checka in anteckningsträd i databasen"
+
+# FIXME: Untranslatable!
+#
+#~ msgid "Refusing to %s notes in %s (outside of refs/notes/)"
+#~ msgstr "Vägrar %s anteckningar i %s (utanför refs/notes/)"
+
+#~ msgid "list"
+#~ msgstr "list"
+
+#~ msgid "add"
+#~ msgstr "add"
+
+#~ msgid "copy"
+#~ msgstr "copy"
+
+#~ msgid "show"
+#~ msgstr "show"
+
+#~ msgid "remove"
+#~ msgstr "remove"
+
+#~ msgid "prune"
+#~ msgstr "prune"
+
+#~ msgid "The current branch %s is not tracking anything."
+#~ msgstr "Den aktuella grenen %s spårar ingenting."
+
+#~ msgid "No destination configured to push to."
+#~ msgstr "Har inte ställt in någon destination att sända till."
+
+#~ msgid "Reflog action message too long: %.*s..."
+#~ msgstr "Reflog-händelsemeddelande för långt: %.*s..."
+
+#~ msgid "Could not read commit message of %s"
+#~ msgstr "Kunde inte läsa incheckningsmeddelandet för %s"
+
+#~ msgid "Could not extract author email from %s"
+#~ msgstr "Kunde inte hämta författarens e-postadress från %s"
+
+#~ msgid "Could not extract author time from %s"
+#~ msgstr "Kunde inte hämta författartid från %s"
+
+#~ msgid "No author information found in %s"
+#~ msgstr "Hittade ingen författarinformation i %s"
+
+#~ msgid "cherry-pick --ff cannot be used with --signoff"
+#~ msgstr "cherry-pick --ff kan inte användas med --signoff"
+
+#~ msgid "cherry-pick --ff cannot be used with --no-commit"
+#~ msgstr "cherry-pick --ff kan inte användas med --no-commit"
+
+#~ msgid "cherry-pick --ff cannot be used with -x"
+#~ msgstr "cherry-pick --ff kan inte användas med -x"
+
+#~ msgid "cherry-pick --ff cannot be used with --edit"
+#~ msgstr "cherry-pick --ff kan inte användas med --edit"
+
+#~ msgid "committer info too long."
+#~ msgstr "incheckarinformation för lång."
+
+#~ msgid ""
+#~ "\n"
+#~ "#\n"
+#~ "# Write a tag message\n"
+#~ "#\n"
+#~ msgstr ""
+#~ "\n"
+#~ "#\n"
+#~ "# Skriv ett taggmeddelande\n"
+#~ "#\n"
+
+#~ msgid "signing key value too long (%.10s...)"
+#~ msgstr "signeringsnyckelvärdet för långt (%.10s...)"
diff --git a/po/vi.po b/po/vi.po
new file mode 100644
index 0000000..f0529f4
--- /dev/null
+++ b/po/vi.po
@@ -0,0 +1,5570 @@
+# Vietnamese translation for GIT-CORE.
+# Copyright (C) 2012, Trần Ngá»c Quân.
+# This file is distributed under the same license as the git-core package.
+# First translated by Trần Ngá»c Quân <vnwildman@gmail.com>, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-1.7.11.rc2.2.gb694fbb\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-06-08 10:20+0800\n"
+"PO-Revision-Date: 2012-06-09 14:08+0700\n"
+"Last-Translator: Trần Ngá»c Quân <vnwildman@gmail.com>\n"
+"Language-Team: Vietnamese <translation-team-vi@lists.sourceforge.net>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+"Plural-Forms: nplurals=2; plural=1;\n"
+"X-Poedit-Language: Vietnamese\n"
+"X-Poedit-Country: VIET NAM\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Poedit-Basepath: ../\n"
+
+#: advice.c:40
+#, c-format
+msgid "hint: %.*s\n"
+msgstr "gợi ý: %.*s\n"
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:70
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+"Sửa chúng trong cây làm việc,\n"
+"và sau đó sử dụng lệnh 'git add/rm <tập-tin>'\n"
+"dành riêng cho việc đánh dấu cần giải quyết và tạo lần chuyển giao,\n"
+"hoặc là sử dụng lệnh 'git commit -a'."
+
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr "'%s' không giống như tập tin v2 bundle (cụm)"
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr "phần đầu (header) không được thừa nhận: %s%s (%d)"
+
+#: bundle.c:89
+#: builtin/commit.c:696
+#, c-format
+msgid "could not open '%s'"
+msgstr "không thể mở '%s'"
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr "Khó chứa thiếu những lần chuyển giao (commit) cần trước hết này:"
+
+#: bundle.c:164
+#: sequencer.c:550
+#: sequencer.c:982
+#: builtin/log.c:289
+#: builtin/log.c:720
+#: builtin/log.c:1309
+#: builtin/log.c:1528
+#: builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr "Cài đặt việc di chuyển qua các điểm xét lại gặp lỗi"
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] "Bundle chứa %d tham chiếu (refs)"
+msgstr[1] "Bundle chứa %d tham chiếu (refs)"
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] "Lệnh bundle yêu cầu tham chiếu (refs) này"
+msgstr[1] "Lệnh bundle yêu cầu %d tham chiếu (refs) này"
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr "rev-list bị chết"
+
+#: bundle.c:296
+#: builtin/log.c:1205
+#: builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr "đối số không được thừa nhận: %s"
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr "tham chiếu '%s' bị loại trừ bởi các tùy chá»n rev-list"
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr "Từ chối tạo một bundle trống rỗng."
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr "Không thể sản sinh pack-objects"
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr "pack-objects đã chết"
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr "không thể tạo '%s'"
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr "index-pack đã chết"
+
+#: commit.c:48
+#, c-format
+msgid "could not parse %s"
+msgstr "không thể phân tích %s"
+
+#: commit.c:50
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr "%s %s không phải là một lần commit!"
+
+#: compat/obstack.c:406
+#: compat/obstack.c:408
+msgid "memory exhausted"
+msgstr "cạn bộ nhớ"
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr "Không thể chạy 'git rev-list'"
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr "gặp lỗi khi ghi vào rev-list: %s"
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr "gặp lỗi khi đóng đầu vào chuẩn stdin của rev-list: %s"
+
+#: date.c:95
+msgid "in the future"
+msgstr "trong tÆ°Æ¡ng lai"
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] "%lu giây trước"
+msgstr[1] "%lu giây trước"
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] "%lu phút trước"
+msgstr[1] "%lu phút trước"
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] "%lu giá» trÆ°á»›c"
+msgstr[1] "%lu giá» trÆ°á»›c"
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] "%lu ngày trước"
+msgstr[1] "%lu ngày trước"
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] "%lu tuần trước"
+msgstr[1] "%lu tuần trước"
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] "%lu tháng trước"
+msgstr[1] "%lu tháng trước"
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] "%lu năm"
+msgstr[1] "%lu năm"
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] "%s, %lu tháng trước"
+msgstr[1] "%s, %lu tháng trước"
+
+#: date.c:154
+#: date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] "%lu năm trước"
+msgstr[1] "%lu năm trước"
+
+#: diff.c:105
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr " Gặp lỗi khi phân tích dirstat cắt bỠphần trăm '%.*s'\n"
+
+#: diff.c:110
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr " Không hiểu đối số dirstat '%.*s'\n"
+
+#: diff.c:210
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+"Tìm thấy các lỗi trong biến cấu hình 'diff.dirstat':\n"
+"%s"
+
+#: diff.c:1400
+msgid " 0 files changed\n"
+msgstr " 0 tập tin nào bị thay đổi\n"
+
+#: diff.c:1404
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] " %d tập tin đã bị thay đổi"
+msgstr[1] " %d tập tin đã bị thay đổi"
+
+#: diff.c:1421
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ", %d được thêm vào(+)"
+msgstr[1] ", %d được thêm vào(+)"
+
+#: diff.c:1432
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ", %d bị xóa(-)"
+msgstr[1] ", %d bị xóa(-)"
+
+#: diff.c:3478
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+"Gặp lá»—i khi phân tích đối số tùy chá»n --dirstat/-X:\n"
+"%s"
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr "không thể chạy gpg."
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr "gpg đã không đồng ý dữ liệu"
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr "gpg gặp lỗi khi ký dữ liệu"
+
+#: grep.c:1320
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr "'%s': không thể Ä‘á»c %s"
+
+#: grep.c:1337
+#, c-format
+msgid "'%s': %s"
+msgstr "'%s': %s"
+
+#: grep.c:1348
+#, c-format
+msgid "'%s': short read %s"
+msgstr "'%s': Ä‘á»c ngắn %s"
+
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr "các lệnh git sẵn sàng để dùng trong '%s'"
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr "các lệnh git sẵn sàng để dùng từ một nơi khác trong $PATH của bạn"
+
+#: help.c:270
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+"'%s' trông như là một lệnh git, nhưng chúng tôi không\n"
+"thể thá»±c thi nó. Có lẽ là lệnh git-%s đã bị há»ng?"
+
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr "á»i chà. Hệ thống của bạn báo rằng chẳng có lệnh Git nào cả."
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+"CẢNH BÃO: Bạn đã gá»i lệnh Git có tên '%s', mà nó lại không sẵn có.\n"
+"Giả định rằng ý bạn là '%s'"
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr "trong %0.1f giây một cách tự động..."
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr "git: '%s' không phải là một lệnh của git. Xem thêm 'git --help'."
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+"\n"
+"Có phải ý bạn là cái này không?"
+msgstr[1] ""
+"\n"
+"Có phải ý bạn là một trong số những cái này không?"
+
+#: parse-options.c:493
+msgid "..."
+msgstr "..."
+
+#: parse-options.c:511
+#, c-format
+msgid "usage: %s"
+msgstr "cách sử dụng: %s"
+
+#. TRANSLATORS: the colon here should align with the
+#. one in "usage: %s" translation
+#: parse-options.c:515
+#, c-format
+msgid " or: %s"
+msgstr " hoặc: %s"
+
+#: parse-options.c:518
+#, c-format
+msgid " %s"
+msgstr " %s"
+
+#: remote.c:1629
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] "Nhánh của bạn là đầu của '%s' bởi %d lần chuyển giao (commit).\n"
+msgstr[1] "Nhánh của bạn là đầu của '%s' bởi %d lần chuyển giao (commit).\n"
+
+#: remote.c:1635
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural "Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] "Nhánh của bạn thì ở đằng sau '%s' bởi %d lần chuyển giao (commit), và có thể được fast-forward.\n"
+msgstr[1] "Nhánh của bạn thì ở đằng sau '%s' bởi %d lần chuyển giao (commit), và có thể được fast-forward.\n"
+
+#: remote.c:1643
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+"Nhánh của bạn và '%s' bị phân kỳ,\n"
+"và có %d và %d lần chuyển giao (commit) khác nhau cho từng cái,\n"
+"tương ứng với mỗi lần.\n"
+msgstr[1] ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+
+#: sequencer.c:121
+#: builtin/merge.c:865
+#: builtin/merge.c:978
+#: builtin/merge.c:1088
+#: builtin/merge.c:1098
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr "Không thể mở %s' để ghi"
+
+#: sequencer.c:123
+#: builtin/merge.c:333
+#: builtin/merge.c:868
+#: builtin/merge.c:1090
+#: builtin/merge.c:1103
+#, c-format
+msgid "Could not write to '%s'"
+msgstr "Không thể ghi vào '%s'"
+
+#: sequencer.c:144
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'"
+msgstr ""
+"sau khi giải quyết các xung Ä‘á»™t, đánh dấu Ä‘Æ°á»ng dẫn đã sá»­a\n"
+"vá»›i lệnh 'git add <Ä‘Æ°á»ng_dẫn>' hoặc 'git rm <Ä‘Æ°á»ng_dẫn>'"
+
+#: sequencer.c:147
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+"sau khi giải quyết các xung Ä‘á»™t, đánh dấu Ä‘Æ°á»ng dẫn đã sá»­a\n"
+"vá»›i lệnh 'git add <Ä‘Æ°á»ng_dẫn>' hoặc 'git rm <Ä‘Æ°á»ng_dẫn>'\n"
+"và chuyển giao (commit) kết quả bằng lệnh 'git commit'"
+
+#: sequencer.c:160
+#: sequencer.c:758
+#: sequencer.c:841
+#, c-format
+msgid "Could not write to %s"
+msgstr "Không thể ghi vào %s"
+
+#: sequencer.c:163
+#, c-format
+msgid "Error wrapping up %s"
+msgstr "Lá»—i bao bá»c %s"
+
+#: sequencer.c:178
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr "Các thay đổi nội bộ của bạn có thể bị ghi đè bởi lệnh cherry-pick."
+
+#: sequencer.c:180
+msgid "Your local changes would be overwritten by revert."
+msgstr "Các thay đổi nội bộ của bạn có thể bị ghi đè bởi lệnh revert."
+
+#: sequencer.c:183
+msgid "Commit your changes or stash them to proceed."
+msgstr "Chuyển giao (commit) các thay đổi hay stash chúng để tiến hành."
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:233
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr "%s: Không thể ghi tập tin lưu bảng mục lục mới"
+
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr "Không thể phân giải commit (lần chuyển giao) HEAD\n"
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr "Không thể cập nhật cây bộ nhớ đệm\n"
+
+#: sequencer.c:324
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr "Không thể phân tích commit (lần chuyển giao) %s\n"
+
+#: sequencer.c:329
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr "Không thể phân tích commit (lần chuyển giao) cha mẹ %s\n"
+
+#: sequencer.c:395
+msgid "Your index file is unmerged."
+msgstr "Tập tin lưu mục lục của bạn không được hòa trộn."
+
+#: sequencer.c:398
+msgid "You do not have a valid HEAD"
+msgstr "Bạn không có HEAD nào hợp lệ"
+
+#: sequencer.c:413
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr "Lần chuyển giao (commit) %s là má»™t lần hòa trá»™n nhÆ°ng không Ä‘Æ°a ra tùy chá»n -m."
+
+#: sequencer.c:421
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr "Lần chuyển giao (commit) %s không có cha mẹ %d"
+
+#: sequencer.c:425
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr "Luồng chính được chỉ định nhưng lần chuyển giao (commit) %s không phải là một lần hòa trộn."
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:436
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr "%s: không thể phân tích lần chuyển giao mẹ của %s"
+
+#: sequencer.c:440
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr "Không thể lấy thông điệp lần chuyển giao (commit) cho %s"
+
+#: sequencer.c:524
+#, c-format
+msgid "could not revert %s... %s"
+msgstr "không thể revert %s... %s"
+
+#: sequencer.c:525
+#, c-format
+msgid "could not apply %s... %s"
+msgstr "không thể apply (áp dụng miếng vá) %s... %s"
+
+#: sequencer.c:553
+msgid "empty commit set passed"
+msgstr "lần chuyển giao (commit) trống rỗng đặt là hợp quy cách"
+
+#: sequencer.c:561
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr "git %s: gặp lá»—i Ä‘á»c bảng mục lục"
+
+#: sequencer.c:566
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr "git %s: gặp lỗi khi làm tươi mới bảng mục lục"
+
+#: sequencer.c:624
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr "Không thể %s trong khi %s"
+
+#: sequencer.c:646
+#, c-format
+msgid "Could not parse line %d."
+msgstr "Không phân tích được dòng %d."
+
+#: sequencer.c:651
+msgid "No commits parsed."
+msgstr "Không có lần chuyển giao (commit) nào được phân tích."
+
+#: sequencer.c:664
+#, c-format
+msgid "Could not open %s"
+msgstr "Không thể mở %s"
+
+#: sequencer.c:668
+#, c-format
+msgid "Could not read %s."
+msgstr "Không thể Ä‘á»c %s."
+
+#: sequencer.c:675
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr "Bảng chỉ thị không thể dùng được: %s"
+
+#: sequencer.c:703
+#, c-format
+msgid "Invalid key: %s"
+msgstr "Khóa không đúng: %s"
+
+#: sequencer.c:706
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr "Giá trị không hợp lệ %s: %s"
+
+#: sequencer.c:718
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr "Bảng tùy chá»n dị hình: %s"
+
+#: sequencer.c:739
+msgid "a cherry-pick or revert is already in progress"
+msgstr "một thao tác cherry-pick hoặc revert đang được thực hiện"
+
+#: sequencer.c:740
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr "hãy thử \"git cherry-pick (--continue | --quit | --abort)\""
+
+#: sequencer.c:744
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr "Không thể tạo thư mục xếp dãy %s"
+
+#: sequencer.c:760
+#: sequencer.c:845
+#, c-format
+msgid "Error wrapping up %s."
+msgstr "Lá»—i bao bá»c %s."
+
+#: sequencer.c:779
+#: sequencer.c:913
+msgid "no cherry-pick or revert in progress"
+msgstr "không cherry-pick hay revert trong tiến trình"
+
+#: sequencer.c:781
+msgid "cannot resolve HEAD"
+msgstr "không thể phân giải HEAD"
+
+#: sequencer.c:783
+msgid "cannot abort from a branch yet to be born"
+msgstr "không thể hủy bỠtừ một nhánh mà nó còn chưa được tạo ra"
+
+#: sequencer.c:805
+#: builtin/apply.c:3697
+#, c-format
+msgid "cannot open %s: %s"
+msgstr "không thể mở %s: %s"
+
+#: sequencer.c:808
+#, c-format
+msgid "cannot read %s: %s"
+msgstr "không thể Ä‘á»c %s: %s"
+
+#: sequencer.c:809
+msgid "unexpected end of file"
+msgstr "kết thúc tập tin đột xuất"
+
+#: sequencer.c:815
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr "tập tin HEAD 'pre-cherry-pick' đã lÆ°u '%s' bị há»ng"
+
+#: sequencer.c:838
+#, c-format
+msgid "Could not format %s."
+msgstr "Không thể định dạng %s."
+
+#: sequencer.c:1000
+msgid "Can't revert as initial commit"
+msgstr "Không thể revert một lần chuyển giao (commit) khởi tạo"
+
+#: sequencer.c:1001
+msgid "Can't cherry-pick into empty head"
+msgstr "Không thể cherry-pick vào một đầu (head) trống rỗng"
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "HEAD không chỉ đến một nhánh nào cả"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "Không có nhánh nào như thế: '%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "Không có dòng ngược (upstream) được cấu hình cho nhánh '%s'"
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr "Nhánh dòng ngược (upstream) '%s' không được lưu lại như là một nhánh 'remote-tracking'"
+
+#: wrapper.c:413
+#, c-format
+msgid "unable to look up current user in the passwd file: %s"
+msgstr "không tìm thấy ngÆ°á»i dùng hiện tại trong tập tin passwd: %s"
+
+#: wrapper.c:414
+msgid "no such user"
+msgstr "không có ngÆ°á»i dùng nhÆ° vậy"
+
+#: wt-status.c:135
+msgid "Unmerged paths:"
+msgstr "Những Ä‘Æ°á»ng dẫn chÆ°a được hòa trá»™n:"
+
+#: wt-status.c:141
+#: wt-status.c:158
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr " (sử dụng \"git reset %s <tập-tin>...\" để bỠmột stage (trạng thái))"
+
+#: wt-status.c:143
+#: wt-status.c:160
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr " (sử dụng \"git rm --cached <tập-tin>...\" để bỠtrạng thái (stage))"
+
+#: wt-status.c:144
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr " (sử dụng \"git add/rm <tập-tin>...\" như là một cách thích hợp để đánh dấu là cần được giải quyết)"
+
+#: wt-status.c:152
+msgid "Changes to be committed:"
+msgstr "Những thay đổi sẽ được chuyển giao:"
+
+#: wt-status.c:170
+msgid "Changes not staged for commit:"
+msgstr "Các thay đổi không được đặt trạng thái (stage) cho lần chuyển giao (commit):"
+
+#: wt-status.c:174
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr " (sử dụng \"git add <tập-tin>...\" để cập nhật những gì cần chuyển giao (commit))"
+
+#: wt-status.c:176
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr " (sử dụng \"git add/rm <tập_tin>...\" để cập nhật những gì sẽ được chuyển giao)"
+
+#: wt-status.c:177
+msgid " (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr " (sử dụng \"git checkout -- <tập_tin>...\" để loại bỠnhững thay đổi trong thư mục làm việc)"
+
+#: wt-status.c:179
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr " (chuyển giao (commit) hoặc là loại bỠcác nội dung không-bị-theo-vết hay đã bị chỉnh sửa trong mô-đun-con)"
+
+#: wt-status.c:188
+#, c-format
+msgid "%s files:"
+msgstr "%s tệp tin:"
+
+#: wt-status.c:191
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr " (sử dụng \"git %s <tập-tin>...\" để bao gồm thêm vào những gì cần chuyển giao (commit))"
+
+#: wt-status.c:208
+msgid "bug"
+msgstr "lá»—i"
+
+#: wt-status.c:213
+msgid "both deleted:"
+msgstr "bị xóa bởi cả hai:"
+
+#: wt-status.c:214
+msgid "added by us:"
+msgstr "được thêm vào bởi chúng tôi:"
+
+#: wt-status.c:215
+msgid "deleted by them:"
+msgstr "bị xóa Ä‘i bởi há»:"
+
+#: wt-status.c:216
+msgid "added by them:"
+msgstr "được thêm vào bởi há»:"
+
+#: wt-status.c:217
+msgid "deleted by us:"
+msgstr "bị xóa bởi chúng tôi:"
+
+#: wt-status.c:218
+msgid "both added:"
+msgstr "được thêm vào bởi cả hai:"
+
+#: wt-status.c:219
+msgid "both modified:"
+msgstr "bị sửa bởi cả hai:"
+
+#: wt-status.c:249
+msgid "new commits, "
+msgstr " lần chuyển giao (commit) mới, "
+
+#: wt-status.c:251
+msgid "modified content, "
+msgstr "nội dung được sửa đổi,"
+
+#: wt-status.c:253
+msgid "untracked content, "
+msgstr "nội dung chưa được theo dõi"
+
+#: wt-status.c:267
+#, c-format
+msgid "new file: %s"
+msgstr "tập tin mới: %s"
+
+#: wt-status.c:270
+#, c-format
+msgid "copied: %s -> %s"
+msgstr "đã sao chép: %s -> %s"
+
+#: wt-status.c:273
+#, c-format
+msgid "deleted: %s"
+msgstr "bị xóa: %s"
+
+#: wt-status.c:276
+#, c-format
+msgid "modified: %s"
+msgstr "bị sửa đổi: %s"
+
+#: wt-status.c:279
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr "đã đổi tên: %s -> %s"
+
+#: wt-status.c:282
+#, c-format
+msgid "typechange: %s"
+msgstr "đổi-kiểu: %s"
+
+#: wt-status.c:285
+#, c-format
+msgid "unknown: %s"
+msgstr "không rõ: %s"
+
+#: wt-status.c:288
+#, c-format
+msgid "unmerged: %s"
+msgstr "chưa hòa trộn: %s"
+
+#: wt-status.c:291
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr "lỗi: không lấy được trạng thái lệnh diff %c"
+
+#: wt-status.c:737
+msgid "On branch "
+msgstr "Trên nhánh"
+
+#: wt-status.c:744
+msgid "Not currently on any branch."
+msgstr "Hiện tại chẳng ở nhánh nào cả."
+
+#: wt-status.c:755
+msgid "Initial commit"
+msgstr "Lần chuyển giao (commit) khởi đầu"
+
+#: wt-status.c:769
+msgid "Untracked"
+msgstr "Không được theo vết"
+
+#: wt-status.c:771
+msgid "Ignored"
+msgstr "Bị bỠqua"
+
+#: wt-status.c:773
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr "Những tập tin không bị theo vết không được liệt kê ra %s"
+
+#: wt-status.c:775
+msgid " (use -u option to show untracked files)"
+msgstr " (sá»­ dụng tùy chá»n -u để hiển thị các tập tin chÆ°a được theo dõi)"
+
+#: wt-status.c:781
+msgid "No changes"
+msgstr "Không có thay đổi nào"
+
+#: wt-status.c:785
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr "không có thay đổi nào được thêm vào lần chuyển giao (commit)%s\n"
+
+#: wt-status.c:787
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr " (sử dụng \"git add\" và/hoặc \"git commit -a\")"
+
+#: wt-status.c:789
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr "không có gì được thêm vào lần chuyển giao (commit) nhưng có những tập tin không được theo dấu vết hiện diện%s\n"
+
+#: wt-status.c:791
+msgid " (use \"git add\" to track)"
+msgstr " (sử dụng \"git add\" để theo dõi dấu vết)"
+
+#: wt-status.c:793
+#: wt-status.c:796
+#: wt-status.c:799
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr "không có gì để chuyển giao (commit) %s\n"
+
+#: wt-status.c:794
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr " (tạo/sao-chép các tập tin và sử dụng \"git add\" để theo dõi dấu vết)"
+
+#: wt-status.c:797
+msgid " (use -u to show untracked files)"
+msgstr " (sá»­ dụng tùy chá»n -u để hiển thị các tập tin chÆ°a được theo dõi)"
+
+#: wt-status.c:800
+msgid " (working directory clean)"
+msgstr " (thư mục làm việc sạch sẽ)"
+
+#: wt-status.c:908
+msgid "HEAD (no branch)"
+msgstr "HEAD (chưa có nhánh nào)"
+
+#: wt-status.c:914
+msgid "Initial commit on "
+msgstr "Lần chuyển giao (commit) khởi tạo trên"
+
+#: wt-status.c:929
+msgid "behind "
+msgstr "đằng sau"
+
+#: wt-status.c:932
+#: wt-status.c:935
+msgid "ahead "
+msgstr "phía trước"
+
+#: wt-status.c:937
+msgid ", behind "
+msgstr ", đằng sau"
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr "trạng thái lệnh diff không như mong đợi %c"
+
+#: builtin/add.c:67
+#: builtin/commit.c:226
+msgid "updating files failed"
+msgstr "Cập nhật tập tin gặp lỗi"
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr "gỡ bỠ'%s'\n"
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr "ÄÆ°á»ng dẫn '%s' thì ở trong mô-Ä‘un-con '%.*s'"
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr "Các thay đổi không được lưu trạng thái sau khi làm tươi mới lại bảng mục lục:"
+
+#: builtin/add.c:195
+#: builtin/add.c:456
+#: builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr "pathspec '%s' không khớp với bất kỳ tập tin nào"
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr "'%s' nằm ngoài một liên kết tượng trưng"
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr "Không thể Ä‘á»c bảng mục lục"
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr "Không thể mở '%s' để ghi"
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr "Không thể ghi ra miếng vá"
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr "không thể lấy trạng thái vỠ'%s'"
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr "Miếng vá trống rá»—ng. Äã bá» qua."
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr "Không thể apply (áp dụng miếng vá) '%s'"
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr "Các Ä‘Æ°á»ng dẫn theo sau đây sẽ bị lá» Ä‘i bởi má»™t trong các tập tin .gitignore của bạn:\n"
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr "Sá»­ dụng tùy chá»n -f nếu bạn thá»±c sá»± muốn thêm chúng vào.\n"
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr "chưa có tập tin nào được thêm vào"
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr "thêm tập tin gặp lỗi"
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr "-A và -u xung khắc nhau"
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr "Tùy chá»n --ignore-missing chỉ có thể được sá»­ dụng cùng vá»›i --dry-run"
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr "Không có gì được chỉ ra, không có gì được thêm vào.\n"
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr "Có lẽ bạn muốn nói là 'git add .' phải không?\n"
+
+#: builtin/add.c:420
+#: builtin/clean.c:95
+#: builtin/commit.c:286
+#: builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr "tập tin ghi bảng mục lục bị há»ng"
+
+#: builtin/add.c:476
+#: builtin/apply.c:4108
+#: builtin/mv.c:229
+#: builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr "Không thể ghi tập tin lưu bảng mục lục mới"
+
+#: builtin/apply.c:53
+msgid "git apply [options] [<patch>...]"
+msgstr "git apply [các-tùy-chá»n] [<miếng-vá>...]"
+
+#: builtin/apply.c:106
+#, c-format
+msgid "unrecognized whitespace option '%s'"
+msgstr "không nhận ra tùy chá»n vá» khoảng trắng '%s'"
+
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
+msgstr "không nhận ra tùy chá»n bá» qua khoảng trắng '%s'"
+
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
+msgstr "Không thể chuẩn bị biểu thức chính qui dấu vết thá»i gian (timestamp regexp) %s"
+
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
+msgstr "thi hành biểu thức chính quy trả vỠ%d cho kết xuất: %s"
+
+#: builtin/apply.c:905
+#, c-format
+msgid "unable to find filename in patch at line %d"
+msgstr "không thể tìm thấy tên tập tin trong miếng vá tại dòng %d"
+
+#: builtin/apply.c:937
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
+msgstr "git apply: git-diff sai - mong đợi /dev/null, đã nhận %s trên dòng %d"
+
+#: builtin/apply.c:941
+#, c-format
+msgid "git apply: bad git-diff - inconsistent new filename on line %d"
+msgstr "git apply: git-diff sai - tên tập tin mới mâu thuấn trên dòng %d"
+
+#: builtin/apply.c:942
+#, c-format
+msgid "git apply: bad git-diff - inconsistent old filename on line %d"
+msgstr "git apply: git-diff sai - tên tập tin cũ mâu thuấn trên dòng %d"
+
+#: builtin/apply.c:949
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
+msgstr "git apply: git-diff sai - mong đợi /dev/null trên dòng %d"
+
+#: builtin/apply.c:1394
+#, c-format
+msgid "recount: unexpected line: %.*s"
+msgstr "chi tiết: dòng không được mong đợi: %.*s"
+
+#: builtin/apply.c:1451
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
+msgstr "miếng vá phân mảnh mà không có phần đầu tại dòng %d: %.*s"
+
+#: builtin/apply.c:1468
+#, c-format
+msgid "git diff header lacks filename information when removing %d leading pathname component (line %d)"
+msgid_plural "git diff header lacks filename information when removing %d leading pathname components (line %d)"
+msgstr[0] "phần đầu diff cho git thiếu thông tin tên tập tin khi gỡ bá» Ä‘i %d trong thành phần dẫn đầu tên của Ä‘Æ°á»ng dẫn (dòng %d)"
+msgstr[1] "phần đầu diff cho git thiếu thông tin tên tập tin khi gỡ bá» Ä‘i %d trong thành phần dẫn đầu tên của Ä‘Æ°á»ng dẫn (dòng %d)"
+
+#: builtin/apply.c:1628
+msgid "new file depends on old contents"
+msgstr "tập tin mới phụ thuộc vào nội dung cũ"
+
+#: builtin/apply.c:1630
+msgid "deleted file still has contents"
+msgstr "tập tin đã xóa vẫn còn nội dung"
+
+#: builtin/apply.c:1656
+#, c-format
+msgid "corrupt patch at line %d"
+msgstr "miếng vá há»ng tại dòng %d"
+
+#: builtin/apply.c:1692
+#, c-format
+msgid "new file %s depends on old contents"
+msgstr "tập tin mới %s phụ thuộc vào nội dung cũ"
+
+#: builtin/apply.c:1694
+#, c-format
+msgid "deleted file %s still has contents"
+msgstr "tập tin đã xóa %s vẫn còn nội dung"
+
+#: builtin/apply.c:1697
+#, c-format
+msgid "** warning: file %s becomes empty but is not deleted"
+msgstr "** cảnh báo: tập tin %s trở nên trống rỗng nhưng không bị xóa"
+
+#: builtin/apply.c:1843
+#, c-format
+msgid "corrupt binary patch at line %d: %.*s"
+msgstr "miếng vá định dạng nhị phân sai há»ng tại dòng %d: %.*s"
+
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1872
+#, c-format
+msgid "unrecognized binary patch at line %d"
+msgstr "miếng vá định dạng nhị phân không được nhận ra tại dòng %d"
+
+#: builtin/apply.c:1958
+#, c-format
+msgid "patch with only garbage at line %d"
+msgstr "vá chỉ với 'garbage' tại dòng %d"
+
+#: builtin/apply.c:2048
+#, c-format
+msgid "unable to read symlink %s"
+msgstr "không thể Ä‘á»c liên kết tượng trÆ°ng %s"
+
+#: builtin/apply.c:2052
+#, c-format
+msgid "unable to open or read %s"
+msgstr "không thể mở để Ä‘á»c hay ghi %s"
+
+#: builtin/apply.c:2123
+msgid "oops"
+msgstr "ôi?"
+
+#: builtin/apply.c:2645
+#, c-format
+msgid "invalid start of line: '%c'"
+msgstr "sai khởi đầu dòng: '%c'"
+
+#: builtin/apply.c:2763
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] "Khối dữ liệu #%d thành công tại %d (offset %d dòng)."
+msgstr[1] "Khối dữ liệu #%d thành công tại %d (offset %d dòng)."
+
+#: builtin/apply.c:2775
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
+msgstr "Nội dung được giảm xuống (%ld/%ld) để áp dụng mảnh dữ liệu tại %d"
+
+#: builtin/apply.c:2781
+#, c-format
+msgid ""
+"while searching for:\n"
+"%.*s"
+msgstr ""
+"Trong khi đang tìm kiếm cho:\n"
+"%.*s"
+
+#: builtin/apply.c:2800
+#, c-format
+msgid "missing binary patch data for '%s'"
+msgstr "thiếu dữ liệu của miếng vá định dạng nhị phân cho '%s'"
+
+#: builtin/apply.c:2903
+#, c-format
+msgid "binary patch does not apply to '%s'"
+msgstr "miếng vá định dạng nhị phân không được áp dụng cho '%s'"
+
+#: builtin/apply.c:2909
+#, c-format
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
+msgstr "vá nhị phân cho '%s' tạo ra kết quả không chính xác (đang chỠ%s, đã nhận %s)"
+
+#: builtin/apply.c:2930
+#, c-format
+msgid "patch failed: %s:%ld"
+msgstr "vá gặp lỗi: %s:%ld"
+
+#: builtin/apply.c:3045
+#, c-format
+msgid "patch %s has been renamed/deleted"
+msgstr "miếng vá %s đã bị xóa/đổi tên"
+
+#: builtin/apply.c:3052
+#: builtin/apply.c:3069
+#, c-format
+msgid "read of %s failed"
+msgstr "Ä‘á»c %s gặp lá»—i"
+
+#: builtin/apply.c:3084
+msgid "removal patch leaves file contents"
+msgstr "loại bỠmiếng vá để lại nội dung tập tin"
+
+#: builtin/apply.c:3105
+#, c-format
+msgid "%s: already exists in working directory"
+msgstr "%s: đã sẵn có trong thư mục đang làm việc"
+
+#: builtin/apply.c:3143
+#, c-format
+msgid "%s: has been deleted/renamed"
+msgstr "%s: đã được xóa/thay-tên"
+
+#: builtin/apply.c:3148
+#: builtin/apply.c:3179
+#, c-format
+msgid "%s: %s"
+msgstr "%s: %s"
+
+#: builtin/apply.c:3159
+#, c-format
+msgid "%s: does not exist in index"
+msgstr "%s: không tồn tại trong bảng mục lục"
+
+#: builtin/apply.c:3173
+#, c-format
+msgid "%s: does not match index"
+msgstr "%s: không khớp trong mục lục"
+
+#: builtin/apply.c:3190
+#, c-format
+msgid "%s: wrong type"
+msgstr "%s: sai kiểu"
+
+#: builtin/apply.c:3192
+#, c-format
+msgid "%s has type %o, expected %o"
+msgstr "%s có kiểu %o, mong chỠ%o"
+
+#: builtin/apply.c:3247
+#, c-format
+msgid "%s: already exists in index"
+msgstr "%s: đã có từ trước trong bảng mục lục"
+
+#: builtin/apply.c:3267
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o)"
+msgstr "chế độ mới (%o) của %s không khớp với chế độ cũ (%o)"
+
+#: builtin/apply.c:3272
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o) of %s"
+msgstr "chế độ mới (%o) của %s không khớp với chế độ cũ (%o) của %s"
+
+#: builtin/apply.c:3280
+#, c-format
+msgid "%s: patch does not apply"
+msgstr "%s: miếng vá không được áp dụng"
+
+#: builtin/apply.c:3293
+#, c-format
+msgid "Checking patch %s..."
+msgstr "Äang kiểm tra miếng vá %s..."
+
+#: builtin/apply.c:3348
+#: builtin/checkout.c:212
+#: builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr "make_cache_entry gặp lá»—i đối vá»›i Ä‘Æ°á»ng dẫn '%s'"
+
+#: builtin/apply.c:3491
+#, c-format
+msgid "unable to remove %s from index"
+msgstr "không thể gỡ bỠ%s từ mục lục"
+
+#: builtin/apply.c:3518
+#, c-format
+msgid "corrupt patch for subproject %s"
+msgstr "miếng vá sai há»ng cho dá»± án con (subproject) %s"
+
+#: builtin/apply.c:3522
+#, c-format
+msgid "unable to stat newly created file '%s'"
+msgstr "không thể lấy trạng thái vỠtập tin %s mới hơn đã được tạo"
+
+#: builtin/apply.c:3527
+#, c-format
+msgid "unable to create backing store for newly created file %s"
+msgstr "không thể tạo 'backing store' cho tập tin được tạo mới hơn %s"
+
+#: builtin/apply.c:3530
+#, c-format
+msgid "unable to add cache entry for %s"
+msgstr "không thể thêm mục nhớ tạm cho %s"
+
+#: builtin/apply.c:3563
+#, c-format
+msgid "closing file '%s'"
+msgstr "đang đóng tập tin '%s'"
+
+#: builtin/apply.c:3612
+#, c-format
+msgid "unable to write file '%s' mode %o"
+msgstr "không thể ghi vào tập tin '%s' chế độ (mode) %o"
+
+#: builtin/apply.c:3668
+#, c-format
+msgid "Applied patch %s cleanly."
+msgstr "Äã áp dụng miếng và %s má»™t cách sạch sẽ."
+
+#: builtin/apply.c:3676
+msgid "internal error"
+msgstr "lá»—i ná»™i bá»™"
+
+#. Say this even without --verbose
+#: builtin/apply.c:3679
+#, c-format
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] "Äang áp dụng miếng vá %%s vá»›i %d lần từ chối..."
+msgstr[1] "Äang áp dụng miếng vá %%s vá»›i %d lần từ chối..."
+
+#: builtin/apply.c:3689
+#, c-format
+msgid "truncating .rej filename to %.*s.rej"
+msgstr "đang cắt cụt tên tập tin .rej thành %.*s.rej"
+
+#: builtin/apply.c:3710
+#, c-format
+msgid "Hunk #%d applied cleanly."
+msgstr "Khối nhá»› #%d được áp dụng gá»n gàng."
+
+#: builtin/apply.c:3713
+#, c-format
+msgid "Rejected hunk #%d."
+msgstr "hunk #%d bị từ chối."
+
+#: builtin/apply.c:3844
+msgid "unrecognized input"
+msgstr "không thừa nhận đầu vào"
+
+#: builtin/apply.c:3855
+msgid "unable to read index file"
+msgstr "không thể Ä‘á»c tập tin lÆ°u bảng mục lục"
+
+#: builtin/apply.c:3970
+#: builtin/apply.c:3973
+msgid "path"
+msgstr "Ä‘Æ°á»ng-dẫn"
+
+#: builtin/apply.c:3971
+msgid "don't apply changes matching the given path"
+msgstr "không áp dụng các thay đổi khá»›p vá»›i Ä‘Æ°á»ng dẫn đã cho"
+
+#: builtin/apply.c:3974
+msgid "apply changes matching the given path"
+msgstr "áp dụng các thay đổi khá»›p vá»›i Ä‘Æ°á»ng dẫn đã cho"
+
+#: builtin/apply.c:3976
+msgid "num"
+msgstr "số"
+
+#: builtin/apply.c:3977
+msgid "remove <num> leading slashes from traditional diff paths"
+msgstr "gỡ bá» <số> phần dẫn đầu (slashe) từ Ä‘Æ°á»ng dẫn diff cổ Ä‘iển"
+
+#: builtin/apply.c:3980
+msgid "ignore additions made by the patch"
+msgstr "lỠđi phần phụ thêm tạo ra bởi miếng vá"
+
+#: builtin/apply.c:3982
+msgid "instead of applying the patch, output diffstat for the input"
+msgstr "thay vì áp dụng một miếng vá, kết xuất kết quả từ lệnh diffstat cho đầu ra"
+
+#: builtin/apply.c:3986
+msgid "shows number of added and deleted lines in decimal notation"
+msgstr "hiển thị số lượng các dòng được thêm vào và xóa đi theo ký hiệu thập phân"
+
+#: builtin/apply.c:3988
+msgid "instead of applying the patch, output a summary for the input"
+msgstr "thay vì áp dụng một miếng vá, kết xuất kết quả cho đầu vào"
+
+#: builtin/apply.c:3990
+msgid "instead of applying the patch, see if the patch is applicable"
+msgstr "thay vì áp dụng miếng vá, hãy xem xem miếng vá có thích hợp không"
+
+#: builtin/apply.c:3992
+msgid "make sure the patch is applicable to the current index"
+msgstr "hãy chắc chắn là miếng vá thích hợp với bảng mục lục hiện hành"
+
+#: builtin/apply.c:3994
+msgid "apply a patch without touching the working tree"
+msgstr "áp dụng một miếng vá mà không động chạm đến cây làm việc"
+
+#: builtin/apply.c:3996
+msgid "also apply the patch (use with --stat/--summary/--check)"
+msgstr "đồng thá»i áp dụng miếng vá (sá»­ dụng vá»›i tùy chá»n --stat/--summary/--check)"
+
+#: builtin/apply.c:3998
+msgid "build a temporary index based on embedded index information"
+msgstr "xây dá»±ng bảng mục lục tạm thá»i trên cÆ¡ sở thông tin bảng mục lục được nhúng"
+
+#: builtin/apply.c:4000
+msgid "paths are separated with NUL character"
+msgstr "các Ä‘Æ°á»ng dẫn bị ngăn cách bởi ký tá»± NULL"
+
+#: builtin/apply.c:4003
+msgid "ensure at least <n> lines of context match"
+msgstr "đảm bảo rằng có ít nhất <n> dòng nội dung khớp"
+
+#: builtin/apply.c:4004
+msgid "action"
+msgstr "hành động"
+
+#: builtin/apply.c:4005
+msgid "detect new or modified lines that have whitespace errors"
+msgstr "tìm thấy một dòng mới hoặc bị sửa đổi mà nó có lỗi do khoảng trắng"
+
+#: builtin/apply.c:4008
+#: builtin/apply.c:4011
+msgid "ignore changes in whitespace when finding context"
+msgstr "lỠđi sự thay đổi do khoảng trắng khi quét nội dung"
+
+#: builtin/apply.c:4014
+msgid "apply the patch in reverse"
+msgstr "áp dụng miếng vá theo chiá»u ngược"
+
+#: builtin/apply.c:4016
+msgid "don't expect at least one line of context"
+msgstr "đừng hy vá»ng có ít nhất má»™t dòng ná»™i dung"
+
+#: builtin/apply.c:4018
+msgid "leave the rejected hunks in corresponding *.rej files"
+msgstr "để lại khối dữ liệu bị từ chối trong các tập tin *.rej tương ứng"
+
+#: builtin/apply.c:4020
+msgid "allow overlapping hunks"
+msgstr "cho phép chồng khối nhớ"
+
+#: builtin/apply.c:4021
+msgid "be verbose"
+msgstr "chi tiết"
+
+#: builtin/apply.c:4023
+msgid "tolerate incorrectly detected missing new-line at the end of file"
+msgstr "dung sai không chính xác đã tìm thấy thiếu dòng mới tại cuối tập tin"
+
+#: builtin/apply.c:4026
+msgid "do not trust the line counts in the hunk headers"
+msgstr "không tin số lượng dòng trong phần đầu khối dữ liệu"
+
+#: builtin/apply.c:4028
+msgid "root"
+msgstr "root"
+
+#: builtin/apply.c:4029
+msgid "prepend <root> to all filenames"
+msgstr "treo thêm <root> vào tất cả các tên tập tin"
+
+#: builtin/apply.c:4050
+msgid "--index outside a repository"
+msgstr "--index ở ngoài một kho chứa"
+
+#: builtin/apply.c:4053
+msgid "--cached outside a repository"
+msgstr "--cached ở ngoài một kho chứa"
+
+#: builtin/apply.c:4069
+#, c-format
+msgid "can't open patch '%s'"
+msgstr "không thể mở miếng vá '%s'"
+
+#: builtin/apply.c:4083
+#, c-format
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] "đã chấm dứt %d lỗi khoảng trắng"
+msgstr[1] "đã chấm dứt %d lỗi khoảng trắng"
+
+#: builtin/apply.c:4089
+#: builtin/apply.c:4099
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] "%d dòng thêm khoảng trắng lỗi."
+msgstr[1] "%d dòng thêm khoảng trắng lỗi."
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr "không thể tạo tập tin kho (lưu trữ, nén) '%s'"
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr "không thể chuyển hướng kết xuất"
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr "git archive: Máy chủ không có địa chỉ URL"
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr "git archive: mong đợi ACK/NAK, nhận EOF"
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr "git archive: NACK %s"
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr "lỗi máy chủ: %s"
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr "git archive: lỗi giao thức"
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr "git archive: đã mong chỠmột flush"
+
+#: builtin/branch.c:144
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+"đang xóa nhánh '%s' mà nó lại đã được hòa trộn vào\n"
+" '%s', nhưng vẫn chưa được hòa trộn vào HEAD."
+
+#: builtin/branch.c:148
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+"không xóa nhánh '%s' cái mà chưa được hòa trộng vào\n"
+" '%s', cho dù là nó đã được hòa trộn vào HEAD."
+
+#: builtin/branch.c:180
+msgid "cannot use -a with -d"
+msgstr "không thể sử dụng -a với -d"
+
+#: builtin/branch.c:186
+msgid "Couldn't look up commit object for HEAD"
+msgstr "Không thể tìm kiếm đối tượng chuyển giao (commit) cho HEAD"
+
+#: builtin/branch.c:191
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr "Không thể xóa nhánh '%s' cái mà bạn hiện nay đang ở."
+
+#: builtin/branch.c:202
+#, c-format
+msgid "remote branch '%s' not found."
+msgstr "nhánh máy chủ '%s' không tìm thấy."
+
+#: builtin/branch.c:203
+#, c-format
+msgid "branch '%s' not found."
+msgstr "không tìm thấy nhánh '%s'."
+
+#: builtin/branch.c:210
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr "Không thể tìm kiếm đối tượng chuyển giao (commit) cho '%s'"
+
+#: builtin/branch.c:216
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+"Nhánh '%s' không được trộn một cách đầy đủ.\n"
+"Nếu bạn thực sự muốn xóa nó, thì chạy lệnh 'git branch -D %s'."
+
+#: builtin/branch.c:225
+#, c-format
+msgid "Error deleting remote branch '%s'"
+msgstr "Gặp lỗi khi đang xóa nhánh máy chủ '%s'"
+
+#: builtin/branch.c:226
+#, c-format
+msgid "Error deleting branch '%s'"
+msgstr "Lỗi khi xoá bỠnhánh '%s'"
+
+#: builtin/branch.c:233
+#, c-format
+msgid "Deleted remote branch %s (was %s).\n"
+msgstr "Nhánh máy chủ đã xóa %s (trước là %s).\n"
+
+#: builtin/branch.c:234
+#, c-format
+msgid "Deleted branch %s (was %s).\n"
+msgstr "Nhánh đã bị xóa '%s' (trước là %s)\n"
+
+#: builtin/branch.c:239
+msgid "Update of config-file failed"
+msgstr "Cập nhật tệp tin cấu hình gặp lỗi"
+
+#: builtin/branch.c:337
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr "nhánh '%s' không chỉ đến một lần chuyển giao (commit) nào cả"
+
+#: builtin/branch.c:409
+#, c-format
+msgid "[%s: behind %d]"
+msgstr "[%s: đằng sau %d]"
+
+#: builtin/branch.c:411
+#, c-format
+msgid "[behind %d]"
+msgstr "[đằng sau %d]"
+
+#: builtin/branch.c:415
+#, c-format
+msgid "[%s: ahead %d]"
+msgstr "[%s: phía trước %d]"
+
+#: builtin/branch.c:417
+#, c-format
+msgid "[ahead %d]"
+msgstr "[phía trước %d]"
+
+#: builtin/branch.c:420
+#, c-format
+msgid "[%s: ahead %d, behind %d]"
+msgstr "[%s: phía trước %d, phía sau %d]"
+
+#: builtin/branch.c:423
+#, c-format
+msgid "[ahead %d, behind %d]"
+msgstr "[phía trước %d, phía sau %d]"
+
+#: builtin/branch.c:535
+msgid "(no branch)"
+msgstr "(không có nhánh nào)"
+
+#: builtin/branch.c:600
+msgid "some refs could not be read"
+msgstr "má»™t số tham chiếu đã không thể Ä‘á»c được"
+
+#: builtin/branch.c:613
+msgid "cannot rename the current branch while not on any."
+msgstr "không thể đổi tên nhánh hiện hành trong khi nó chẳng ở đâu cả."
+
+#: builtin/branch.c:623
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr "tên nhánh sai: '%s'"
+
+#: builtin/branch.c:638
+msgid "Branch rename failed"
+msgstr "Äổi tên nhánh gặp lá»—i"
+
+#: builtin/branch.c:642
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr "Äã đổi tên nhánh khuyết danh '%s' Ä‘i"
+
+#: builtin/branch.c:646
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr "Nhánh bị đổi tên thành %s, nhưng HEAD lại không được cập nhật!"
+
+#: builtin/branch.c:653
+msgid "Branch is renamed, but update of config-file failed"
+msgstr "Nhánh bị đổi tên, nhưng cập nhật tập tin cấu hình gặp lỗi"
+
+#: builtin/branch.c:668
+#, c-format
+msgid "malformed object name %s"
+msgstr "tên đối tượng dị hình %s"
+
+#: builtin/branch.c:692
+#, c-format
+msgid "could not write branch description template: %s"
+msgstr "không thể ghi vào mẫu mô tả nhánh: %s"
+
+#: builtin/branch.c:783
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr "Gặp lỗi khi giải quyết HEAD như là một tham chiếu (ref) hợp lệ."
+
+#: builtin/branch.c:788
+#: builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr "HEAD không tìm thấy ở dưới refs/heads!"
+
+#: builtin/branch.c:808
+msgid "--column and --verbose are incompatible"
+msgstr "--column và --verbose xung khắc nhau"
+
+#: builtin/branch.c:857
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr "hai tùy chá»n -a và -r áp dụng cho lệnh 'git branch' không hợp lý đối vá»›i tên nhánh"
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr "'%s' tốt\n"
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr "Cần một kho chứa để mà tạo một bundle."
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr "Cần một kho chứa để mà bung một bundle."
+
+#: builtin/checkout.c:113
+#: builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr "Ä‘Æ°á»ng dẫn '%s' không có các phiên bản của chúng ta"
+
+#: builtin/checkout.c:115
+#: builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr "Ä‘Æ°á»ng dẫn '%s' không có các phiên bản của chúng"
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr "Ä‘Æ°á»ng dẫn '%s' không có tất cả các phiên bản cần thiết"
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr "Ä‘Æ°á»ng dẫn '%s' không có các phiên bản cần thiết"
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr "Ä‘Æ°á»ng dẫn '%s': không thể hòa trá»™n"
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr "Không thể thêm kết quả hòa trộn cho '%s'"
+
+#: builtin/checkout.c:234
+#: builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr "tập tin ghi bảng mục lục bị há»ng"
+
+#: builtin/checkout.c:264
+#: builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr "Ä‘Æ°á»ng dẫn '%s' không được hòa trá»™n"
+
+#: builtin/checkout.c:302
+#: builtin/checkout.c:498
+#: builtin/clone.c:583
+#: builtin/merge.c:812
+msgid "unable to write new index file"
+msgstr "không thể ghi tập tin lưu bảng mục lục mới"
+
+#: builtin/checkout.c:319
+#: builtin/diff.c:302
+#: builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr "diff_setup_done gặp lỗi"
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr "bạn cần phải giải quyết bảng mục lục hiện tại của bạn trước đã!"
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr "Không thể thực hiện reflog cho '%s'\n"
+
+#: builtin/checkout.c:566
+msgid "HEAD is now at"
+msgstr "HEAD hiện giỠtại"
+
+#: builtin/checkout.c:573
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr "Äặt lại nhánh '%s'\n"
+
+#: builtin/checkout.c:576
+#, c-format
+msgid "Already on '%s'\n"
+msgstr "Äã sẵn sàng trên '%s'\n"
+
+#: builtin/checkout.c:580
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr "Äã chuyển tá»›i và reset nhánh '%s'\n"
+
+#: builtin/checkout.c:582
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr "Äã chuyển đến nhánh má»›i '%s'\n"
+
+#: builtin/checkout.c:584
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr "Äã chuyển đến nhánh '%s'\n"
+
+#: builtin/checkout.c:640
+#, c-format
+msgid " ... and %d more.\n"
+msgstr " ... và nhiá»u hÆ¡n %d.\n"
+
+#. The singular version
+#: builtin/checkout.c:646
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+"Cảnh báo: bạn đã rá»i bở %d lần chuyển giao (commit) lại đằng sau, không được kết nối đến\n"
+"bất kỳ nhánh nào của bạn:\n"
+"\n"
+"%s\n"
+msgstr[1] ""
+"Cảnh báo: bạn đã rá»i bở %d lần chuyển giao (commit) lại đằng sau, không được kết nối đến\n"
+"bất kỳ nhánh nào của bạn:\n"
+"\n"
+"%s\n"
+
+#: builtin/checkout.c:664
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+"Nếu bạn muốn giữ chúng bằng cách tạo ra má»™t nhánh má»›i, đây có lẽ là má»™t thá»i Ä‘iểm thích hợp\n"
+"để làm thế bằng lệnh:\n"
+"\n"
+" git branch tên_nhánh_mới %s\n"
+"\n"
+
+#: builtin/checkout.c:694
+msgid "internal error in revision walk"
+msgstr "lỗi nội bộ trong khi di chuyển qua các điểm xét lại"
+
+#: builtin/checkout.c:698
+msgid "Previous HEAD position was"
+msgstr "Vị trí kế trước của HEAD là"
+
+#: builtin/checkout.c:724
+msgid "You are on a branch yet to be born"
+msgstr "Bạn tại nhánh mà nó chưa hỠđược sinh ra"
+
+#. case (1)
+#: builtin/checkout.c:855
+#, c-format
+msgid "invalid reference: %s"
+msgstr "tham chiếu sai: %s"
+
+#. case (1): want a tree
+#: builtin/checkout.c:894
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr "tham chiếu không phải là cây:%s"
+
+#: builtin/checkout.c:974
+msgid "-B cannot be used with -b"
+msgstr "-B không thể được sử dụng với -b"
+
+#: builtin/checkout.c:983
+msgid "--patch is incompatible with all other options"
+msgstr "--patch xung khắc vá»›i tất cả các tùy chá»n khác"
+
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr "--detach không thể được sử dụng với -b/-B/--orphan"
+
+#: builtin/checkout.c:988
+msgid "--detach cannot be used with -t"
+msgstr "--detach không thể được sá»­ dụng vá»›i tùy chá»n -t"
+
+#: builtin/checkout.c:994
+msgid "--track needs a branch name"
+msgstr "--track cần tên một nhánh"
+
+#: builtin/checkout.c:1001
+msgid "Missing branch name; try -b"
+msgstr "Thiếu tên nhánh; hãy thử -b"
+
+#: builtin/checkout.c:1007
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr "Tùy chá»n --orphan và -b|-B loại từ lẫn nhau"
+
+#: builtin/checkout.c:1009
+msgid "--orphan cannot be used with -t"
+msgstr "--orphan không thể được sá»­ dụng vá»›i tùy chá»n -t"
+
+#: builtin/checkout.c:1019
+msgid "git checkout: -f and -m are incompatible"
+msgstr "git checkout: -f và -m xung khắc nhau"
+
+#: builtin/checkout.c:1053
+msgid "invalid path specification"
+msgstr "Ä‘Æ°á»ng dẫn đã cho không hợp lệ"
+
+#: builtin/checkout.c:1061
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+"git checkout: việc cập nhật các Ä‘Æ°á»ng dẫn là xung khắc vá»›i việc chuyển đổi các nhánh..\n"
+"Bạn đã có ý định checkout '%s' cái mà không thể được phân giải như là lần chuyển giao (commit)?"
+
+#: builtin/checkout.c:1063
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr "git checkout: việc cập nhật các Ä‘Æ°á»ng dẫn là xung khắc vá»›i việc chuyển đổi các nhánh."
+
+#: builtin/checkout.c:1068
+msgid "git checkout: --detach does not take a path argument"
+msgstr "git checkout: --detach không nhận má»™t đối số Ä‘Æ°á»ng dẫn"
+
+#: builtin/checkout.c:1071
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+"git checkout: --ours/--theirs, --force và --merge là xung khắc với nhau khi\n"
+"checkout."
+
+#: builtin/checkout.c:1090
+msgid "Cannot switch branch to a non-commit."
+msgstr "Không thể chuyển đến một non-commit."
+
+#: builtin/checkout.c:1093
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr "--ours/--theirs là xung khắc nhau khi chuyển đổi các nhánh."
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr "-x và -X không thể dùng cùng một lúc với nhau"
+
+#: builtin/clean.c:82
+msgid "clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr "clean.requireForce được đặt thành true và không Ä‘Æ°a ra tùy chá»n -n mà cÅ©ng không -f; từ chối lệnh dá»n dẹp (clean)"
+
+#: builtin/clean.c:85
+msgid "clean.requireForce defaults to true and neither -n nor -f given; refusing to clean"
+msgstr "clean.requireForce mặc định được đặt thành true và không Ä‘Æ°a ra tùy chá»n -n mà cÅ©ng không -f; từ chối lệnh dá»n dẹp (clean)"
+
+#: builtin/clean.c:155
+#: builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr "Có thể gỡ bỠ%s\n"
+
+#: builtin/clean.c:159
+#: builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr "Äang gỡ bá» %s\n"
+
+#: builtin/clean.c:162
+#: builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr "gặp lỗi khi gỡ bỠ%s"
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr "Không thể gỡ bỠ%s\n"
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr "Không xóa %s\n"
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr "kho tham chiếu '%s' không phải là một thư mục nội bộ."
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr "gặp lỗi khi mở '%s'"
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr "tạo thư mục \"%s\" gặp lỗi"
+
+#: builtin/clone.c:308
+#: builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr "gặp lá»—i stat (lấy trạng thái vá») '%s'"
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr "%s tồn tại nhưng không phải là một thư mục"
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr "lá»—i stat (lấy trạng thái vá») %s\n"
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr "bỠliên kết (unlink) %s không thành công"
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr "tạo được liên kết má»m tá»›i %s gặp lá»—i"
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr "sao chép tệp tin tới '%s' gặp lỗi"
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr "hoàn tất.\n"
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr "Không tìm thấy nhánh máy chủ %s để nhân bản (clone)."
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr "refers HEAD máy chủ chỉ đến ref không tồn tại, không thể checkout.\n"
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr "Có quá nhiá»u đối số."
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr "Bạn phải chỉ định một kho để mà nhân bản (clone)."
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr "tùy chá»n --bare và --origin %s xung khắc nhau."
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr "kho chứa '%s' chưa tồn tại"
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr "--depth bị lỠđi khi nhân bản nội bộ; hãy sử dụng file:// để thay thế."
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr "Ä‘Æ°á»ng dẫn đích '%s' đã có từ trÆ°á»›c và không phải là má»™t thÆ° mục rá»—ng."
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr "cây làm việc '%s' đã sẵn tồn tại rồi."
+
+#: builtin/clone.c:706
+#: builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr "không thể tạo các thư mục dẫn đầu của '%s'"
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr "không thể tạo cây thư mục làm việc dir '%s'."
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr "Äang nhân bản thành kho chứa bare '%s'...\n"
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr "Äang nhân bản thành '%s'...\n"
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr "Không biết làm cách nào để nhân bản (clone) %s"
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr "Nhánh máy chủ %s không tìm thấy trong dòng ngược (upstream) %s"
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr "Bạn hình như là đã nhân bản một kho trống rỗng."
+
+#: builtin/column.c:51
+msgid "--command must be the first argument"
+msgstr "--command phải là đối số đầu tiên"
+
+#: builtin/commit.c:43
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+"Tên và địa chỉ thư điện tử của bạn được cấu hình một cách tự động trên cơ sở\n"
+"tài khoản và địa chỉ máy chủ của bạn. Xin hãy kiểm tra xem chúng có chính xác không.\n"
+"Bạn có thể chặn những thông báo kiểu này bằng cách cài đặt các thông tin trên một cách rõ ràng:\n"
+"\n"
+" git config --global user.name \"Tên của bạn\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"Sau khi thực hiện xong, bạn có thể sửa chữa định danh được sử dụng cho lần chuyển giao (commit) này với lệnh:\n"
+"\n"
+" git commit --amend --reset-author\n"
+
+#: builtin/commit.c:55
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+"Bạn đã yêu cầu amend (tu bổ) phần lớn các lần chuyển giao (commit) gần đây, nhưng làm như thế\n"
+"có thể làm cho nó trở nên trống rỗng. Bạn có thể lặp lại lệnh của mình bằng --allow-empty,\n"
+"hoặc là bạn gỡ bỠcác lần chuyển giao một cách hoàn toàn bằng lệnh:\n"
+"\"git reset HEAD^\".\n"
+
+#: builtin/commit.c:60
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+"Lần cherry-pick trước hiện nay trống rỗng, có lẽ là bởi vì sự phân giải xung đột.\n"
+"Nếu bạn muốn chuyển giao nó cho dù thế nào đi nữa, sử dụng:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Nếu không, hãy thử sử dụng 'git reset'\n"
+
+#: builtin/commit.c:253
+msgid "failed to unpack HEAD tree object"
+msgstr "gặp lỗi khi tháo dỡ HEAD đối tượng cây"
+
+#: builtin/commit.c:295
+msgid "unable to create temporary index"
+msgstr "không thể tạo bảng mục lục tạm thá»i"
+
+#: builtin/commit.c:301
+msgid "interactive add failed"
+msgstr "việc thêm tương tác gặp lỗi"
+
+#: builtin/commit.c:334
+#: builtin/commit.c:355
+#: builtin/commit.c:405
+msgid "unable to write new_index file"
+msgstr "không thể ghi tập tin lưu bảng mục lục mới (new_index)"
+
+#: builtin/commit.c:386
+msgid "cannot do a partial commit during a merge."
+msgstr "không thể thực hiện việc chuyển giao (commit) cục bộ trong khi đang được hòa trộn."
+
+#: builtin/commit.c:388
+msgid "cannot do a partial commit during a cherry-pick."
+msgstr "không thể thực hiện việc chuyển giao (commit) bộ phận trong khi đang cherry-pick."
+
+#: builtin/commit.c:398
+msgid "cannot read the index"
+msgstr "không Ä‘á»c được bảng mục lục"
+
+#: builtin/commit.c:418
+msgid "unable to write temporary index file"
+msgstr "không thể ghi tập tin lÆ°u bảng mục lục tạm thá»i"
+
+#: builtin/commit.c:493
+#: builtin/commit.c:499
+#, c-format
+msgid "invalid commit: %s"
+msgstr "lần chuyển giao (commit) không hợp lệ: %s"
+
+#: builtin/commit.c:522
+msgid "malformed --author parameter"
+msgstr "đối số --author bị dị hình"
+
+#: builtin/commit.c:582
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr "Chuỗi thụt lỠđầu dòng dị hình: '%s'"
+
+#: builtin/commit.c:620
+#: builtin/commit.c:653
+#: builtin/commit.c:967
+#, c-format
+msgid "could not lookup commit %s"
+msgstr "không thể tìm kiếm commit (lần chuyển giao) %s"
+
+#: builtin/commit.c:632
+#: builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr "(Ä‘ang Ä‘á»c thông Ä‘iệp nhật ký từ đầu vào tiêu chuẩn)\n"
+
+#: builtin/commit.c:634
+msgid "could not read log from standard input"
+msgstr "không thể Ä‘á»c nhật ký từ đầu vào tiêu chuẩn"
+
+#: builtin/commit.c:638
+#, c-format
+msgid "could not read log file '%s'"
+msgstr "không Ä‘á»c được tệp nhật ký '%s'"
+
+#: builtin/commit.c:644
+msgid "commit has empty message"
+msgstr "lần chuyển giao (commit) có ghi chú trống rỗng"
+
+#: builtin/commit.c:660
+msgid "could not read MERGE_MSG"
+msgstr "không thể Ä‘á»c MERGE_MSG"
+
+#: builtin/commit.c:664
+msgid "could not read SQUASH_MSG"
+msgstr "không thể Ä‘á»c SQUASH_MSG"
+
+#: builtin/commit.c:668
+#, c-format
+msgid "could not read '%s'"
+msgstr "Không thể Ä‘á»c '%s'."
+
+#: builtin/commit.c:720
+msgid "could not write commit template"
+msgstr "không thể ghi mẫu commit"
+
+#: builtin/commit.c:731
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a merge.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"Nó trông giống với việc bạn đang chuyển giao một lần hòa trộn.\n"
+"Nếu không phải vậy, xin hãy gỡ bỠtập tin\n"
+"\t%s\n"
+"và thử lại.\n"
+
+#: builtin/commit.c:736
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"Nó trông giống với việc bạn đang chuyển giao một lần cherry-pick.\n"
+"Nếu không phải vậy, xin hãy gỡ bỠtập tin\n"
+"\t%s\n"
+"và thử lại.\n"
+
+#: builtin/commit.c:748
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+"Hãy nhập vào các thông tin để giải thích các thay đổi của bạn. Những dòng được\n"
+"bắt đầu bằng '#' sẽ được bỠqua, phần chú thích này nếu rỗng sẽ làm hủy bỠlần chuyển giao (commit).\n"
+
+#: builtin/commit.c:753
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+"Hãy nhập vào các thông tin để giải thích các thay đổi của bạn.Những dòng được\n"
+"bắt đầu bằng '#' sẽ được bỠqua; bạn có thể xóa chúng đi nếu muốn.\n"
+"Phần chú thích này nếu rỗng sẽ làm hủy bỠlần chuyển giao (commit).\n"
+
+#: builtin/commit.c:766
+#, c-format
+msgid "%sAuthor: %s"
+msgstr "%sTác giả: %s"
+
+#: builtin/commit.c:773
+#, c-format
+msgid "%sCommitter: %s"
+msgstr "%sNgÆ°á»i chuyển giao (commit): %s"
+
+#: builtin/commit.c:793
+msgid "Cannot read index"
+msgstr "không Ä‘á»c được bảng mục lục"
+
+#: builtin/commit.c:830
+msgid "Error building trees"
+msgstr "Gặp lỗi khi xây dựng cây"
+
+#: builtin/commit.c:845
+#: builtin/tag.c:361
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr "Xin hãy áp dụng thông Ä‘iệp sá»­ dụng hoặc là tùy chá»n -m hoặc là -F.\n"
+
+#: builtin/commit.c:942
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr "Không tìm thấy tác giả đã sẵn có với '%s'"
+
+#: builtin/commit.c:957
+#: builtin/commit.c:1157
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr "Chế độ cho các tập tin không bị theo vết không hợp lệ '%s'"
+
+#: builtin/commit.c:997
+msgid "Using both --reset-author and --author does not make sense"
+msgstr "Sá»­ dụng cả hai tùy chá»n --reset-author và --author không hợp lý"
+
+#: builtin/commit.c:1008
+msgid "You have nothing to amend."
+msgstr "Không có gì để amend (tu bổ) cả."
+
+#: builtin/commit.c:1011
+msgid "You are in the middle of a merge -- cannot amend."
+msgstr "Bạn đang ở giữa của quá trình hòa trộn -- không thể thực hiện amend (tu bổ)."
+
+#: builtin/commit.c:1013
+msgid "You are in the middle of a cherry-pick -- cannot amend."
+msgstr "Bạn đang ở giữa của quá trình cherry-pick -- không thể thực hiện amend (tu bổ)."
+
+#: builtin/commit.c:1016
+msgid "Options --squash and --fixup cannot be used together"
+msgstr "Các tùy chá»n --squash và --fixup không thể sá»­ dụng cùng vá»›i nhau"
+
+#: builtin/commit.c:1026
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr "Chỉ má»™t tùy chá»n trong số -c/-C/-F/--fixup được sá»­ dụng"
+
+#: builtin/commit.c:1028
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr "Tùy chá»n -m không thể được tổ hợp cùng vá»›i -c/-C/-F/--fixup."
+
+#: builtin/commit.c:1036
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr "--reset-author chỉ có thể được sá»­ dụng vá»›i tùy chá»n -C, -c hay --amend."
+
+#: builtin/commit.c:1053
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr "Chỉ má»™t trong các tùy chá»n --include/--only/--all/--interactive/--patch được sá»­ dụng."
+
+#: builtin/commit.c:1055
+msgid "No paths with --include/--only does not make sense."
+msgstr "Không Ä‘Æ°á»ng dẫn vá»›i các tùy chá»n --include/--only không hợp lý."
+
+#: builtin/commit.c:1057
+msgid "Clever... amending the last one with dirty index."
+msgstr "Giá»i... Ä‘ang tu bổ cái cuối vá»›i bảng mục lục phi nghÄ©a."
+
+#: builtin/commit.c:1059
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr "Những Ä‘Æ°á»ng dẫn rõ ràng được chỉ ra không có tùy chá»n -i cÅ©ng không -o; Ä‘ang giả định --only những-Ä‘Æ°á»ng-dẫn..."
+
+#: builtin/commit.c:1069
+#: builtin/tag.c:577
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr "Chế Ä‘á»™ dá»n dẹp không hợp lệ %s"
+
+#: builtin/commit.c:1074
+msgid "Paths with -a does not make sense."
+msgstr "Các Ä‘Æ°á»ng dẫn vá»›i tùy chá»n -a không hợp lý."
+
+#: builtin/commit.c:1257
+msgid "couldn't look up newly created commit"
+msgstr "không thể tìm thấy lần chuyển giao (commit) mới hơn đã được tạo"
+
+#: builtin/commit.c:1259
+msgid "could not parse newly created commit"
+msgstr "không thể phân tích cú pháp của đối tượng chuyển giao mới hơn đã được tạo"
+
+#: builtin/commit.c:1300
+msgid "detached HEAD"
+msgstr "đã rá»i khá»i HEAD"
+
+#: builtin/commit.c:1302
+msgid " (root-commit)"
+msgstr " (root-commit)"
+
+#: builtin/commit.c:1446
+msgid "could not parse HEAD commit"
+msgstr "không thể phân tích commit (lần chuyển giao) HEAD"
+
+#: builtin/commit.c:1484
+#: builtin/merge.c:509
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr "không thể mở %s' để Ä‘á»c"
+
+#: builtin/commit.c:1491
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr "Tập tin MERGE_HEAD sai há»ng (%s)"
+
+#: builtin/commit.c:1498
+msgid "could not read MERGE_MODE"
+msgstr "không thể Ä‘á»c MERGE_MODE"
+
+#: builtin/commit.c:1517
+#, c-format
+msgid "could not read commit message: %s"
+msgstr "không thể Ä‘á»c thông Ä‘iệp (message) commit (lần chuyển giao): %s"
+
+#: builtin/commit.c:1531
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
+msgstr "Äang bá» qua việc chuyển giao (commit); bạn đã không biên soạn thông Ä‘iệp (message).\n"
+
+#: builtin/commit.c:1536
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr "Äang bá» qua lần chuyển giao (commit) bởi vì thông Ä‘iệp của nó trống rá»—ng.\n"
+
+#: builtin/commit.c:1551
+#: builtin/merge.c:936
+#: builtin/merge.c:961
+msgid "failed to write commit object"
+msgstr "gặp lỗi khi ghi đối tượng chuyển giao (commit)"
+
+#: builtin/commit.c:1572
+msgid "cannot lock HEAD ref"
+msgstr "không thể khóa HEAD ref (tham chiếu)"
+
+#: builtin/commit.c:1576
+msgid "cannot update HEAD ref"
+msgstr "không thể cập nhật HEAD ref (tham chiếu)"
+
+#: builtin/commit.c:1587
+msgid ""
+"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."
+msgstr ""
+"Kho chứa đã hoàn tất việc cập nhật, nhưng không thể ghi vào\n"
+"tập tin new_index (bảng mục lục mới). Hãy kiểm tra xem đĩa có bị đầy quá\n"
+"hay quota (hạn nghạch đĩa cứng) bị vượt quá, và sau đó \"git reset HEAD\" để khắc phục."
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr "thẻ đã được ghi chú %s không sẵn để dùng"
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr "thẻ được chú giải %s không có tên nhúng"
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr "thẻ '%s' đã thực sự ở đây '%s' rồi"
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr "Không phải tên đối tượng %s hợp lệ"
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr "%s không phải là một đối tượng '%s' hợp lệ"
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr "không có thẻ nào khớp chính xác với '%s'"
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr "Äang tìm kiếm để mô tả %s\n"
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr "việc tìm kiếm đã kết thúc tại %s\n"
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+"Không có thẻ được chú giải nào được mô tả là '%s'.\n"
+"Tuy nhiên, ở đây có những thẻ không được chú giải: hãy thử --tags."
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+"Không có thẻ (tag) có thể mô tả '%s'.\n"
+"Hãy thử --always, hoặt tạo một số thẻ."
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr "đã xuyên %lu qua lần chuyển giao (commit)\n"
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+"tìm thấy nhiá»u hÆ¡n %i thẻ (tag); đã liệt kê %i gần đây nhất\n"
+"bỠđi tìm kiếm tại %s\n"
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr "--long là xung khắc vá»›i tùy chá»n --abbrev=0"
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr "Không tìm thấy các tên, không thể mô tả gì cả."
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr "--dirty là xung khắc vá»›i các tùy chá»n dành cho chuyển giao (commit)"
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr "'%s': không phải tập tin bình thÆ°á»ng hay liên kết tượng trÆ°ng"
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr "tùy chá»n sai: %s"
+
+#: builtin/diff.c:297
+msgid "Not a git repository"
+msgstr "Không phải là kho git"
+
+#: builtin/diff.c:347
+#, c-format
+msgid "invalid object '%s' given."
+msgstr "đối tượng đã cho '%s' không hợp lệ."
+
+#: builtin/diff.c:352
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr "đã chỉ ra nhiá»u hÆ¡n %d cây (tree): '%s'"
+
+#: builtin/diff.c:362
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr "đã cho nhiá»u hÆ¡n hai đối tượng blob: '%s'"
+
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr "đã cho đối tượng không thể nắm giữ '%s'."
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr "Không thể tìm thấy máy chủ cho tham chiếu HEAD"
+
+#: builtin/fetch.c:253
+#, c-format
+msgid "object %s not found"
+msgstr "Không tìm thấy đối tượng %s"
+
+#: builtin/fetch.c:259
+msgid "[up to date]"
+msgstr "[đã cập nhật]"
+
+#: builtin/fetch.c:273
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr "! %-*s %-*s -> %s (không thể fetch (lấy vá») trong nhánh hiện hành)"
+
+#: builtin/fetch.c:274
+#: builtin/fetch.c:360
+msgid "[rejected]"
+msgstr "[Bị từ chối]"
+
+#: builtin/fetch.c:285
+msgid "[tag update]"
+msgstr "[cập nhật thẻ]"
+
+#: builtin/fetch.c:287
+#: builtin/fetch.c:322
+#: builtin/fetch.c:340
+msgid " (unable to update local ref)"
+msgstr " (không thể cập nhật tham chiếu (ref) nội bộ)"
+
+#: builtin/fetch.c:305
+msgid "[new tag]"
+msgstr "[thẻ mới]"
+
+#: builtin/fetch.c:308
+msgid "[new branch]"
+msgstr "[nhánh mới]"
+
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[ref (tham chiếu) mới]"
+
+#: builtin/fetch.c:356
+msgid "unable to update local ref"
+msgstr "không thể cập nhật tham chiếu (ref) nội bộ"
+
+#: builtin/fetch.c:356
+msgid "forced update"
+msgstr "cưỡng bức cập nhật"
+
+#: builtin/fetch.c:362
+msgid "(non-fast-forward)"
+msgstr "(non-fast-forward)"
+
+#: builtin/fetch.c:393
+#: builtin/fetch.c:685
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr "không thể mở %s: %s\n"
+
+#: builtin/fetch.c:402
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr "%s đã không gửi tất cả các đối tượng cần thiết\n"
+
+#: builtin/fetch.c:488
+#, c-format
+msgid "From %.*s\n"
+msgstr "Từ %.*s\n"
+
+#: builtin/fetch.c:499
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+"một số tham chiếu (refs) nội bộ không thể được cập nhật; hãy thử chạy\n"
+" 'git remote prune %s' để bỠđi những nhánh cũ, hay bị xung đột"
+
+#: builtin/fetch.c:549
+#, c-format
+msgid " (%s will become dangling)"
+msgstr " (%s sẽ trở thành lủng lẳng (không được quản lý))"
+
+#: builtin/fetch.c:550
+#, c-format
+msgid " (%s has become dangling)"
+msgstr " (%s phải trở thành lủng lẳng (không được quản lý))"
+
+#: builtin/fetch.c:557
+msgid "[deleted]"
+msgstr "[đã xóa]"
+
+#: builtin/fetch.c:558
+#: builtin/remote.c:1055
+msgid "(none)"
+msgstr "(không)"
+
+#: builtin/fetch.c:675
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr "Từ chối việc lấy (fetch) vào trong nhánh hiện tại %s của một kho chứa không phải kho trần (bare)"
+
+#: builtin/fetch.c:709
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr "Không biết làm cách nào để lấy vỠ(fetch) từ %s"
+
+#: builtin/fetch.c:786
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr "Tùy chá»n \"%s\" có giá trị \"%s\" là không hợp lệ cho %s"
+
+#: builtin/fetch.c:789
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr "Tùy chá»n \"%s\" bị bá» qua vá»›i %s\n"
+
+#: builtin/fetch.c:888
+#, c-format
+msgid "Fetching %s\n"
+msgstr "Äang lấy (fetch) %s\n"
+
+#: builtin/fetch.c:890
+#: builtin/remote.c:100
+#, c-format
+msgid "Could not fetch %s"
+msgstr "không thể fetch (lấy) %s"
+
+#: builtin/fetch.c:907
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr ""
+"Chưa chỉ ra kho chứa máy chủ. Xin hãy chỉ định hoặc là URL hoặc\n"
+"tên máy chủ từ cái mà những Ä‘iểm xét duyệt má»›i có thể được fetch (lấy vá»)."
+
+#: builtin/fetch.c:927
+msgid "You need to specify a tag name."
+msgstr "Bạn phải định rõ tên thẻ."
+
+#: builtin/fetch.c:979
+msgid "fetch --all does not take a repository argument"
+msgstr "lệnh lấy vá» sá»­ dụng tùy chá»n --all sẽ không lấy đối số kho chứa"
+
+#: builtin/fetch.c:981
+msgid "fetch --all does not make sense with refspecs"
+msgstr "lệnh lấy vá» fetch sá»­ dụng tùy chá»n --all không hợp lý vá»›i refspecs"
+
+#: builtin/fetch.c:992
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr "không có nhóm máy chủ hay máy chủ như thế: %s"
+
+#: builtin/fetch.c:1000
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr "Việc lấy vỠcả một nhóm và chỉ định refspecs không hợp lý"
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr "%s không hợp lệ: '%s'"
+
+#: builtin/gc.c:90
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr "thư mục đối tượng dài một cách điên rồ %.*s"
+
+#: builtin/gc.c:221
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr "Tự động đóng gói kho chứa để tối ưu hóa hiệu suất làm việc.\n"
+
+#: builtin/gc.c:224
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+"Tự động đóng gói kho chứa để tối ưu hóa hiệu suất làm việc.\n"
+"chạy lệnh \"git gc\" một cách thủ công. Hãy xem \"git help gc\" để biết thêm chi tiết.\n"
+
+#: builtin/gc.c:251
+msgid "There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr "Có quá nhiá»u đối tượng tá»± do không được dùng đến; hãy chạy lệnh 'git prune' để xóa bá» chúng Ä‘i."
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr "grep: gặp lỗi tạo tuyến (thread): %s"
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr "Gặp lỗi với lệnh chdir: %s"
+
+#: builtin/grep.c:478
+#: builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr "không thể Ä‘á»c cây (%s)"
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr "không thể thá»±c hiện lệnh grep (lá»c tìm) từ đối tượng thuá»™c kiểu %s"
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr "chuyển đến `%c' mong chỠmột giá trị bằng số"
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr "không mở được '%s'"
+
+#: builtin/grep.c:885
+msgid "no pattern given."
+msgstr "chưa chỉ ra mẫu."
+
+#: builtin/grep.c:899
+#, c-format
+msgid "bad object %s"
+msgstr "đối tượng sai %s"
+
+#: builtin/grep.c:940
+msgid "--open-files-in-pager only works on the worktree"
+msgstr "--open-files-in-pager chỉ làm việc trên cây-làm-việc"
+
+#: builtin/grep.c:963
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr "--cached hay --untracked không được sử dụng với --no-index."
+
+#: builtin/grep.c:968
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr "--no-index hay --untracked không được sá»­ dụng cùng vá»›i các tùy chá»n liên quan đến revs."
+
+#: builtin/grep.c:971
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr "--[no-]exclude-standard không thể sử dụng cho nội dung lưu dấu vết."
+
+#: builtin/grep.c:979
+msgid "both --cached and trees are given."
+msgstr "cả hai --cached và các cây phải được chỉ ra."
+
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr "không nhận ra định dạng trợ giúp '%s'"
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr "Lỗi khởi chạy emacsclient."
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr "Gặp lỗi khi phân tích phiên bản emacsclient."
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr "phiên bản của emacsclient '%d' quá cũ (< 22)."
+
+#: builtin/help.c:126
+#: builtin/help.c:154
+#: builtin/help.c:163
+#: builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr "gặp lỗi khi thực thi '%s': %s"
+
+#: builtin/help.c:211
+#, c-format
+msgid ""
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
+msgstr ""
+"'%s': Ä‘Æ°á»ng dẫn không há»— trợ bá»™ trình chiếu man.\n"
+"Hãy cân nhắc đến việc sử dụng 'man.<tool>.cmd' để thay thế."
+
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
+msgstr ""
+"'%s': cmd (lệnh) hỗ trợ bộ trình chiếu man.\n"
+"Hãy cân nhắc đến việc sử dụng 'man.<tool>.path' để thay thế."
+
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr "Những lệnh git hay được sử dụng nhất là:"
+
+#: builtin/help.c:355
+#, c-format
+msgid "'%s': unknown man viewer."
+msgstr "'%s': không rõ chương trình xem man."
+
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr "không có trình xem trợ giúp dạng manpage tiếp hợp với yêu cầu"
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr "không có trình xem trợ giúp dạng info tiếp hợp với yêu cầu"
+
+#: builtin/help.c:391
+#, c-format
+msgid "'%s': not a documentation directory."
+msgstr "'%s': không phải là một thư mục tài liệu."
+
+#: builtin/help.c:432
+#: builtin/help.c:439
+#, c-format
+msgid "usage: %s%s"
+msgstr "cách sử dụng: %s%s"
+
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
+msgstr "`git %s' được đặt bí danh thành `%s'"
+
+#: builtin/index-pack.c:169
+#, c-format
+msgid "object type mismatch at %s"
+msgstr "kiểu đối tượng không khớp tại %s"
+
+#: builtin/index-pack.c:189
+msgid "object of unexpected type"
+msgstr "đối tượng của kiểu không mong đợi"
+
+#: builtin/index-pack.c:226
+#, c-format
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] "không thể Ä‘iá»n vào %d byte"
+msgstr[1] "không thể Ä‘iá»n vào %d byte"
+
+#: builtin/index-pack.c:236
+msgid "early EOF"
+msgstr "vừa đúng lúc EOF"
+
+#: builtin/index-pack.c:237
+msgid "read error on input"
+msgstr "lá»—i Ä‘á»c ở đầu vào"
+
+#: builtin/index-pack.c:249
+msgid "used more bytes than were available"
+msgstr "sá»­ dụng nhiá»u hÆ¡n số lượng byte mà nó sẵn có"
+
+#: builtin/index-pack.c:256
+msgid "pack too large for current definition of off_t"
+msgstr "pack quá lớn so với định nghĩa hiện tại của kiểu off_t"
+
+#: builtin/index-pack.c:272
+#, c-format
+msgid "unable to create '%s'"
+msgstr "không thể tạo '%s'"
+
+#: builtin/index-pack.c:277
+#, c-format
+msgid "cannot open packfile '%s'"
+msgstr "không thể mở packfile '%s'"
+
+#: builtin/index-pack.c:291
+msgid "pack signature mismatch"
+msgstr "chữ ký cho pack không khớp"
+
+#: builtin/index-pack.c:311
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
+msgstr "pack có đối tượng sai khoảng bù (offset) %lu: %s"
+
+#: builtin/index-pack.c:405
+#, c-format
+msgid "inflate returned %d"
+msgstr "xả nén trả vỠ%d"
+
+#: builtin/index-pack.c:450
+msgid "offset value overflow for delta base object"
+msgstr "tràn giá trị khoảng bù cho đối tượng delta cơ sở"
+
+#: builtin/index-pack.c:458
+msgid "delta base offset is out of bound"
+msgstr "khoảng bù cơ sở cho delta nằm ngoài phạm vi"
+
+#: builtin/index-pack.c:466
+#, c-format
+msgid "unknown object type %d"
+msgstr "không hiểu kiểu đối tượng %d"
+
+#: builtin/index-pack.c:495
+msgid "cannot pread pack file"
+msgstr "không thể chạy hàm pread cho tập tin pack"
+
+#: builtin/index-pack.c:497
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] "tập tin pack bị kết thúc sớm, %lu byte bị thiếu"
+msgstr[1] "tập tin pack bị kết thúc sớm, %lu byte bị thiếu"
+
+#: builtin/index-pack.c:510
+msgid "serious inflate inconsistency"
+msgstr "sá»± mâu thuẫn xả nén nghiêm trá»ng"
+
+#: builtin/index-pack.c:583
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "không thể Ä‘á»c đối tượng đã tồn tại %s"
+
+#: builtin/index-pack.c:586
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
+msgstr "Sá»° VA CHẠM SHA1 ÄÃ XẢY RA VỚI %s!"
+
+#: builtin/index-pack.c:598
+#, c-format
+msgid "invalid blob object %s"
+msgstr "đối tượng blob không hợp lệ %s"
+
+#: builtin/index-pack.c:610
+#, c-format
+msgid "invalid %s"
+msgstr "%s không hợp lệ"
+
+#: builtin/index-pack.c:612
+msgid "Error in object"
+msgstr "Lỗi trong đối tượng"
+
+#: builtin/index-pack.c:614
+#, c-format
+msgid "Not all child objects of %s are reachable"
+msgstr "Không phải tất cả các đối tượng con của %s là có thể với tới được"
+
+#: builtin/index-pack.c:687
+#: builtin/index-pack.c:713
+msgid "failed to apply delta"
+msgstr "gặp lỗi khi áp dụng delta"
+
+#: builtin/index-pack.c:850
+msgid "Receiving objects"
+msgstr "Äang nhận vá» các đối tượng"
+
+#: builtin/index-pack.c:850
+msgid "Indexing objects"
+msgstr "Các đối tượng bảng mục lục"
+
+#: builtin/index-pack.c:872
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr "pack bị sai há»ng (SHA1 không khá»›p)"
+
+#: builtin/index-pack.c:877
+msgid "cannot fstat packfile"
+msgstr "không thể fstat packfile"
+
+#: builtin/index-pack.c:880
+msgid "pack has junk at the end"
+msgstr "pack có phần thừa ở cuối"
+
+#: builtin/index-pack.c:903
+msgid "Resolving deltas"
+msgstr "Äang phân giải các delta"
+
+#: builtin/index-pack.c:954
+msgid "confusion beyond insanity"
+msgstr "lộn xộn hơn cả điên rồ"
+
+#: builtin/index-pack.c:973
+#, c-format
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] "pack có %d delta chưa được giải quyết"
+msgstr[1] "pack có %d delta chưa được giải quyết"
+
+#: builtin/index-pack.c:998
+#, c-format
+msgid "unable to deflate appended object (%d)"
+msgstr "không thể xả đối tượng nối thêm (%d)"
+
+#: builtin/index-pack.c:1077
+#, c-format
+msgid "local object %s is corrupt"
+msgstr "đối tượng ná»™i bá»™ %s bị há»ng"
+
+#: builtin/index-pack.c:1101
+msgid "error while closing pack file"
+msgstr "gặp lỗi trong khi đóng tập tin pack"
+
+#: builtin/index-pack.c:1114
+#, c-format
+msgid "cannot write keep file '%s'"
+msgstr "không thể ghi tập tin giữ lại '%s'"
+
+#: builtin/index-pack.c:1122
+#, c-format
+msgid "cannot close written keep file '%s'"
+msgstr "không thể đóng tập tin giữ lại đã được ghi '%s'"
+
+#: builtin/index-pack.c:1135
+msgid "cannot store pack file"
+msgstr "không thể lưu tập tin pack"
+
+#: builtin/index-pack.c:1146
+msgid "cannot store index file"
+msgstr "không thể lưu trữ tập tin ghi mục lục"
+
+#: builtin/index-pack.c:1247
+#, c-format
+msgid "Cannot open existing pack file '%s'"
+msgstr "Không thể mở tập tin pack đã sẵn có '%s' "
+
+#: builtin/index-pack.c:1249
+#, c-format
+msgid "Cannot open existing pack idx file for '%s'"
+msgstr "Không thể mở tập tin 'pack idx' cho '%s'"
+
+#: builtin/index-pack.c:1296
+#, c-format
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] "không delta: %d đối tượng"
+msgstr[1] "không delta: %d đối tượng"
+
+#: builtin/index-pack.c:1303
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] "chiá»u dài xích = %d: %lu đối tượng"
+msgstr[1] "chiá»u dài xích = %d: %lu đối tượng"
+
+#: builtin/index-pack.c:1330
+msgid "Cannot come back to cwd"
+msgstr "Không thể quay lại cwd"
+
+#: builtin/index-pack.c:1374
+#: builtin/index-pack.c:1377
+#: builtin/index-pack.c:1389
+#: builtin/index-pack.c:1393
+#, c-format
+msgid "bad %s"
+msgstr "%s sai"
+
+#: builtin/index-pack.c:1407
+msgid "--fix-thin cannot be used without --stdin"
+msgstr "--fix-thin không thể được dùng mà không có --stdin"
+
+#: builtin/index-pack.c:1411
+#: builtin/index-pack.c:1421
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr "tên tập tin packfile '%s' không được kết thúc bằng đuôi '.pack'"
+
+#: builtin/index-pack.c:1430
+msgid "--verify with no packfile name given"
+msgstr "dùng tùy chá»n --verify mà không Ä‘Æ°a ra tên packfile"
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr "Không thể làm %s được ghi bởi nhóm"
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr "tên mẫu dài một cách điên rồ %s"
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr "không thể lấy trạng thái (stat) vỠ'%s'"
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr "không thể stat (lấy trạng thái vá») mẫu '%s'"
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr "không thể opendir '%s'"
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr "không thể readlink '%s'"
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr "liên kết tượng trưng dài một cách điên rồ %s"
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr "không thể tạo liên kết tượng trưng (symlink) '%s' '%s'"
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr "không thể sao chép %s sang %s"
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr "đang lỠđi mẫu %s"
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr "Ä‘Æ°á»ng dẫn mẫu dài má»™t cách Ä‘iên rồ %s"
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr "các mẫu không được tìm thấy %s"
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr "không sao chép các mẫu của phiên bản sai định dạng %d từ '%s'"
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr "thư mục git điên rồ %s"
+
+#: builtin/init-db.c:322
+#: builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr "%s đã tồn tại rồi"
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr "không thể handle tệp tin kiểu %d"
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr "không di chuyển được %s vào %s"
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr "Không thể tạo liên kết git '%s'"
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s kho Git trong %s%s\n"
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr "Khởi tạo lại đã sẵn có rồi"
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr "Khởi tạo trống rỗng"
+
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr " đã chia sẻ"
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr "không nói chuyện được với lệnh cwd"
+
+#: builtin/init-db.c:521
+#: builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr "không thể mkdir (tạo thư mục): %s"
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr "không thể chdir (chuyển đổi thư mục) sang %s"
+
+#: builtin/init-db.c:554
+#, c-format
+msgid "%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-dir=<directory>)"
+msgstr "%s (hoặc --work-tree=<thư-mục>) không cho phép không chỉ định %s (hoặc --git-dir=<thư-mục>)"
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr "Không thể truy cập thư mục làm việc hiện hành"
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr "không thể truy cập cây (tree) làm việc '%s'"
+
+#: builtin/log.c:188
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr "Kết xuất cuối cùng: %d %s\n"
+
+#: builtin/log.c:401
+#: builtin/log.c:489
+#, c-format
+msgid "Could not read object %s"
+msgstr "Không thể Ä‘á»c đối tượng %s"
+
+#: builtin/log.c:513
+#, c-format
+msgid "Unknown type: %d"
+msgstr "Không nhận ra kiểu: %d"
+
+#: builtin/log.c:602
+msgid "format.headers without value"
+msgstr "format.headers không có giá trị cụ thể"
+
+#: builtin/log.c:676
+msgid "name of output directory is too long"
+msgstr "tên của thư mục kết xuất quá dài"
+
+#: builtin/log.c:687
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr "Không thể mở tập tin miếng vá: %s"
+
+#: builtin/log.c:701
+msgid "Need exactly one range."
+msgstr "Cần chính xác một vùng."
+
+#: builtin/log.c:709
+msgid "Not a range."
+msgstr "Không phải là một vùng."
+
+#: builtin/log.c:786
+msgid "Cover letter needs email format"
+msgstr "'Cover letter' cần cho định dạng thư"
+
+#: builtin/log.c:859
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr "in-reply-to điên rồ: %s"
+
+#: builtin/log.c:932
+msgid "Two output directories?"
+msgstr "Hai thư mục kết xuất?"
+
+#: builtin/log.c:1153
+#, c-format
+msgid "bogus committer info %s"
+msgstr "thông tin ngÆ°á»i chuyển giao không có thá»±c %s"
+
+#: builtin/log.c:1198
+msgid "-n and -k are mutually exclusive."
+msgstr "-n và -k loại từ lẫn nhau."
+
+#: builtin/log.c:1200
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr "--subject-prefix và -k xung khắc nhau."
+
+#: builtin/log.c:1208
+msgid "--name-only does not make sense"
+msgstr "--name-only không hợp lý"
+
+#: builtin/log.c:1210
+msgid "--name-status does not make sense"
+msgstr "--name-status không hợp lý"
+
+#: builtin/log.c:1212
+msgid "--check does not make sense"
+msgstr "--check không hợp lý"
+
+#: builtin/log.c:1235
+msgid "standard output, or directory, which one?"
+msgstr "đầu ra chuẩn, hay thÆ° mục, chá»n cái nào?"
+
+#: builtin/log.c:1237
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr "Không thể tạo thư mục '%s'"
+
+#: builtin/log.c:1390
+msgid "Failed to create output files"
+msgstr "Gặp lỗi khi tạo các tập tin kết xuất"
+
+#: builtin/log.c:1494
+#, c-format
+msgid "Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr "Không tìm thấy nhánh mạng bị theo vết, hãy chỉ định <dòng-ngược> một cách thủ công.\n"
+
+#: builtin/log.c:1510
+#: builtin/log.c:1512
+#: builtin/log.c:1524
+#, c-format
+msgid "Unknown commit %s"
+msgstr "Không hiểu lần chuyển giao (commit) %s"
+
+#: builtin/merge.c:90
+msgid "switch `m' requires a value"
+msgstr "switch `m' yêu cầu một giá trị"
+
+#: builtin/merge.c:127
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr "Không tìm thấy chiến lược hòa trộn '%s'.\n"
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Available strategies are:"
+msgstr "Các chiến lược sẵn sàng là:"
+
+#: builtin/merge.c:133
+#, c-format
+msgid "Available custom strategies are:"
+msgstr "Các chiến lược tùy chỉnh sẵn sàng là:"
+
+#: builtin/merge.c:240
+msgid "could not run stash."
+msgstr "không thể chạy stash."
+
+#: builtin/merge.c:245
+msgid "stash failed"
+msgstr "stash gặp lỗi"
+
+#: builtin/merge.c:250
+#, c-format
+msgid "not a valid object: %s"
+msgstr "không phải là một đối tượng hợp lệ: %s"
+
+#: builtin/merge.c:269
+#: builtin/merge.c:286
+msgid "read-tree failed"
+msgstr "read-tree gặp lỗi"
+
+#: builtin/merge.c:316
+msgid " (nothing to squash)"
+msgstr " (không có ghì để squash)"
+
+#: builtin/merge.c:329
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr "Squash commit -- không cập nhật HEAD\n"
+
+#: builtin/merge.c:361
+msgid "Writing SQUASH_MSG"
+msgstr "Äang ghi SQUASH_MSG"
+
+#: builtin/merge.c:363
+msgid "Finishing SQUASH_MSG"
+msgstr "Hoàn thành SQUASH_MSG"
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr "Không thông điệp hòa trộn -- không cập nhật HEAD\n"
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr "'%s' không chỉ đến một lần chuyển giao (commit) nào cả"
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr "Chuá»—i branch.%s.mergeoptions sai: %s"
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr "lệnh git write-tree gặp lỗi khi ghi một cây"
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr "gặp lá»—i khi Ä‘á»c bá»™ nhá»› tạm"
+
+#: builtin/merge.c:697
+msgid "Unable to write index."
+msgstr "Không thể ghi bảng mục lục"
+
+#: builtin/merge.c:710
+msgid "Not handling anything other than two heads merge."
+msgstr "Không cầm nắm gì ngoài hai head hòa trộn"
+
+#: builtin/merge.c:724
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr "Không hiểu tùy chá»n cho merge-recursive: -X%s"
+
+#: builtin/merge.c:738
+#, c-format
+msgid "unable to write %s"
+msgstr "không ghi được %s"
+
+#: builtin/merge.c:877
+#, c-format
+msgid "Could not read from '%s'"
+msgstr "Không thể Ä‘á»c từ '%s'"
+
+#: builtin/merge.c:886
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr "Vẫn chưa hòa trộn các lần chuyển giao (commit); sử dụng lệnh 'git commit' để hoàn tất việc hòa trộn.\n"
+
+#: builtin/merge.c:892
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+"Hãy nhập vào các thông tin để giải thích tại sao sự hòa trộn này là cần thiết,\n"
+"đặc biệt là khi nó hòa trộn dòng ngược đã cập nhật vào trong một nhánh topic.\n"
+"\n"
+"Những dòng được bắt đầu bằng '#' sẽ được bỠqua, và phần chú thích này nếu rỗng\n"
+"sẽ làm hủy bỠlần chuyển giao (commit).\n"
+
+#: builtin/merge.c:916
+msgid "Empty commit message."
+msgstr "Chú thích của lần commit (chuyển giao) bị trống rỗng."
+
+#: builtin/merge.c:928
+#, c-format
+msgid "Wonderful.\n"
+msgstr "Thần kỳ.\n"
+
+#: builtin/merge.c:993
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr "Việc tự động hòa trộn gặp lỗi; hãy sửa các xung đột sau đó chuyển giao (commit) kết quả.\n"
+
+#: builtin/merge.c:1009
+#, c-format
+msgid "'%s' is not a commit"
+msgstr "%s không phải là một lần commit (chuyển giao)"
+
+#: builtin/merge.c:1050
+msgid "No current branch."
+msgstr "không phải nhánh hiện hành"
+
+#: builtin/merge.c:1052
+msgid "No remote for the current branch."
+msgstr "Không có máy chủ cho nhánh hiện hành."
+
+#: builtin/merge.c:1054
+msgid "No default upstream defined for the current branch."
+msgstr "Không có dòng ngược mặc định được định nghĩa cho nhánh hiện hành."
+
+#: builtin/merge.c:1059
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr "Không nhánh mạng theo vết cho %s từ %s"
+
+#: builtin/merge.c:1146
+#: builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr "%s - không phải là một số thứ chúng tôi có thể hòa trộn"
+
+#: builtin/merge.c:1214
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr "Ở đây không có lần hòa trộn nào được hủy bỠgiữa chừng cả (không thấy MERGE_HEAD)."
+
+#: builtin/merge.c:1230
+#: git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"Bạn chưa kết thúc việc hòa trộng (MERGE_HEAD vẫn tồn tại).\n"
+"Hãy chuyển giao (commit) các thay đổi trước khi bạn có thể hòa trộn."
+
+#: builtin/merge.c:1233
+#: git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr "Bạn chưa kết thúc việc hòa trộng (MERGE_HEAD vẫn tồn tại)."
+
+#: builtin/merge.c:1237
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"Bạn chưa kết thúc việc cherry-pick (CHERRY_PICK_HEAD vẫn tồn tại).\n"
+"Hãy chuyển giao (commit) các thay đổi trước khi bạn có thể hòa trộn."
+
+#: builtin/merge.c:1240
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr "Bạn chưa kết thúc việc cherry-pick (CHERRY_PICK_HEAD vẫn tồn tại)."
+
+#: builtin/merge.c:1249
+msgid "You cannot combine --squash with --no-ff."
+msgstr "Bạn không thể tổ hợp --squash với --no-ff."
+
+#: builtin/merge.c:1254
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr "Bạn không thể tổ hợp --no-ff với --ff-only."
+
+#: builtin/merge.c:1261
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr "Không chỉ ra lần chuyển giao (commit) và merge.defaultToUpstream chưa được đặt."
+
+#: builtin/merge.c:1293
+msgid "Can merge only exactly one commit into empty head"
+msgstr "Không thể hòa trộn một cách đúng đắn một lần chuyển giao (commit) vào một head rỗng"
+
+#: builtin/merge.c:1296
+msgid "Squash commit into empty head not supported yet"
+msgstr "Squash commit vào một head trống rỗng vẫn chưa được hỗ trợ"
+
+#: builtin/merge.c:1298
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr "Chuyển giao (commit) không-fast-forward không hợp lý ở trong một head trống rỗng"
+
+#: builtin/merge.c:1413
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr "Äang cập nhật %s..%s\n"
+
+#: builtin/merge.c:1451
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr "Äang thá»­ hòa trá»™n kiểu 'trivial in-index'...\n"
+
+#: builtin/merge.c:1458
+#, c-format
+msgid "Nope.\n"
+msgstr "Không.\n"
+
+#: builtin/merge.c:1490
+msgid "Not possible to fast-forward, aborting."
+msgstr "Thực hiện lệnh fast-forward là không thể được, đang bỠqua."
+
+#: builtin/merge.c:1513
+#: builtin/merge.c:1592
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr "Äang tua lại cây thành thá»i xa xÆ°a...\n"
+
+#: builtin/merge.c:1517
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr "Äang thá»­ chiến lược hòa trá»™n %s...\n"
+
+#: builtin/merge.c:1583
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr "Không có chiến lược hòa trộn nào được nắm giữ (handle) sự hòa trộn.\n"
+
+#: builtin/merge.c:1585
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr "Hòa trộn với chiến lược %s gặp lỗi.\n"
+
+#: builtin/merge.c:1594
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr "Sử dụng %s để chuẩn bị giải quyết bằng tay.\n"
+
+#: builtin/merge.c:1606
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr "Hòa trộn tự động đã trở nên tốt; bị dừng trước khi việc chuyển giao được yêu cầu\n"
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr "Äang kiểm tra việc đổi tên của '%s' thành '%s'\n"
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr "nguồn sai"
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr "không thể di chuyển một thư mục vào trong chính nó được"
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr "không di chuyển được thư mục thông qua tập tin"
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr "Hả? %.*s trong bảng mục lục à?"
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr "thư mục nguồn là trống rỗng"
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr "không nằm dưới sự quản lý mã nguồn"
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr "đích đã tồn tại sẵn rồi"
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr "đang ghi đè lên '%s'"
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr "Không thể ghi chèn"
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr "Nhiá»u nguồn cho cùng má»™t đích"
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr "%s, nguồn=%s, đích=%s"
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr "Äang thay đổi tên %s thành %s\n"
+
+#: builtin/mv.c:215
+#: builtin/remote.c:731
+#, c-format
+msgid "renaming '%s' failed"
+msgstr "đổi tên %s gặp lỗi"
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr "không thể khởi chạy 'show' cho đối tượng '%s'"
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr "không thể fdopen 'show' (lệnh hiển thị) mô tả tập tin (fd) kết xuất"
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr "gặp lá»—i khi đóng Ä‘Æ°á»ng ống cho lệnh 'show' cho đối tượng '%s'"
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr "gặp lỗi khi hoàn thành 'show' cho đối tượng '%s'"
+
+#: builtin/notes.c:175
+#: builtin/tag.c:347
+#, c-format
+msgid "could not create file '%s'"
+msgstr "không thể tạo tập tin '%s'"
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr "Xin hãy áp dụng ná»™i dung của ghi chú sá»­ dụng hoặc là tùy chá»n -m hoặc là -F"
+
+#: builtin/notes.c:210
+#: builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr "Äang gỡ bá» ghi chú (note) cho đối tượng %s\n"
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr "không thể ghi đối tượng ghi chú (note)"
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr "Nội dung ghi chú còn lại %s"
+
+#: builtin/notes.c:251
+#: builtin/tag.c:542
+#, c-format
+msgid "cannot read '%s'"
+msgstr "không thể Ä‘á»c '%s'"
+
+#: builtin/notes.c:253
+#: builtin/tag.c:545
+#, c-format
+msgid "could not open or read '%s'"
+msgstr "không thể mở để Ä‘á»c hay ghi '%s'"
+
+#: builtin/notes.c:272
+#: builtin/notes.c:445
+#: builtin/notes.c:447
+#: builtin/notes.c:507
+#: builtin/notes.c:561
+#: builtin/notes.c:644
+#: builtin/notes.c:649
+#: builtin/notes.c:724
+#: builtin/notes.c:766
+#: builtin/notes.c:968
+#: builtin/reset.c:293
+#: builtin/tag.c:558
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr "Gặp lỗi khi giải quyết '%s' như là một tham chiếu (ref) hợp lệ."
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr "Gặp lá»—i khi Ä‘á»c đối tượng '%s'."
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr "Không thể chuyển giao (commit) chưa được khởi tạo hoặc không được tham chiếu cây ghi chú"
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr "Giá trị notes.rewriteMode sai: '%s'"
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr "Từ chối ghi đè ghi chú trong %s (nằm ngoài của refs/notes/)"
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr "Giá trị %s sai: '%s'"
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr "Dòng nhập vào dị hình: '%s'."
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr "Gặp lỗi khi sao chép ghi chú (note) từ '%s' tới '%s'"
+
+#: builtin/notes.c:500
+#: builtin/notes.c:554
+#: builtin/notes.c:627
+#: builtin/notes.c:639
+#: builtin/notes.c:712
+#: builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr "quá nhiá»u đối số"
+
+#: builtin/notes.c:513
+#: builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr "không ghi chú được tìm thấy cho đối tượng %s."
+
+#: builtin/notes.c:580
+#, c-format
+msgid "Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite existing notes"
+msgstr "Không thể thêm các ghi chú. Äã tìm thấy các ghi chú đã sẵn có cho đối tượng %s. Sá»­ dụng tùy chá»n '-f' để ghi đè lên các ghi chú cÅ©"
+
+#: builtin/notes.c:585
+#: builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr "Äang ghi đè lên ghi chú cÅ© cho đối tượng %s\n"
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr "quá ít đối số"
+
+#: builtin/notes.c:656
+#, c-format
+msgid "Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite existing notes"
+msgstr "Không thể sao chép các ghi chú. Äã tìm thấy các ghi chú đã sẵn có cho đối tượng %s. Sá»­ dụng tùy chá»n '-f' để ghi đè lên các ghi chú cÅ©"
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr "Thiếu ghi chú trên đối tượng nguốn %s. Không thể sao chép."
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+"Các tùy chá»n -m/-F/-c/-C đã cổ không còn dùng nữa cho lệnh con 'edit'.\n"
+"Xin hãy sử dụng lệnh sau để thay thế: 'git notes add -f -m/-F/-c/-C'.\n"
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr "Äối tượng %s không có ghi chú (note)\n"
+
+#: builtin/notes.c:1103
+#: builtin/remote.c:1598
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr "Không hiểu câu lệnh con: %s"
+
+#: builtin/pack-objects.c:2337
+#, c-format
+msgid "unsupported index version %s"
+msgstr "phiên bản mục lục không được hỗ trợ %s"
+
+#: builtin/pack-objects.c:2341
+#, c-format
+msgid "bad index version '%s'"
+msgstr "phiên bản mục lục sai '%s'"
+
+#: builtin/pack-objects.c:2364
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr "tùy chá»n %s không chấp nhận dạng thức âm"
+
+#: builtin/pack-objects.c:2368
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr "không thể phân tích giá trị '%s' cho tùy chá»n %s"
+
+#: builtin/push.c:45
+msgid "tag shorthand without <tag>"
+msgstr "dùng tốc ký tag không có <thẻ>"
+
+#: builtin/push.c:64
+msgid "--delete only accepts plain target ref names"
+msgstr "--delete chỉ chấp nhận các tên tham chiếu (ref) dạng thÆ°á»ng"
+
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+"\n"
+"Äể chá»n má»—i tùy chá»n má»™t cách cố định, xem push.default trong 'git help config'."
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+"Nhánh dòng ngược (upstream) của nhánh hiện tại của bạn không khớp\n"
+"vá»›i tên của nhánh hiện tại của bạn. Äể push đến nhánh dòng ngược\n"
+"trên máy chủ, sử dụng\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"Äể push tá»›i nhánh cùng tên trên máy chủ, sá»­ dụng\n"
+"\n"
+" git push %s %s\n"
+"%s"
+
+#: builtin/push.c:121
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+"Bạn hiện nay không ở một nhánh.\n"
+"Äể push lịch sá»­ hÆ°á»›ng tá»›i trạng thái hiện hành (HEAD đã bị tách rá»i)\n"
+"ngay bây giá», sá»­ dụng\n"
+"\n"
+" git push %s HEAD:<tên-của-nhánh-máy-chủ>\n"
+
+#: builtin/push.c:128
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+"Nhánh hiện tại %s không có nhánh dòng ngược (upstream) nào.\n"
+"Äể push (đẩy lên) nhánh hiện tại và đặt máy chủ nhÆ° là dòng ngược (upstream), sá»­ dụng\n"
+"\n"
+" git push --set-upstream %s %s\n"
+
+#: builtin/push.c:136
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr "Nhánh hiện tại %s có đa nhánh dòng ngược (upstream), từ chối push."
+
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+"Bạn đang push (đẩy lên) máy chủ '%s', mà nó không phải là dòng ngược (upstream) của\n"
+"nhánh hiện tại '%s' của bạn, mà không báo cho tôi biết là cái gì được push\n"
+"để cập nhật nhánh máy chủ nào."
+
+#: builtin/push.c:174
+msgid "You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr "Bạn đã không chỉ ra một refspecs nào để push, và push.default là \"không là gì cả\"."
+
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"Việc cập nhật bị từ chối bởi vì đầu mút của nhánh được push nằm đằng sau bộ\n"
+"phận tương ứng của máy chủ. Hòa trộn với các thay đổi từ máy chủ (v.d. 'git pull')\n"
+"trước khi lại push lần nữa.\n"
+"Xem trong phần 'Note about fast-forwards' trong nội dung từ lệnh 'git push --help'."
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+"Việc cập nhật bị từ chối bởi vì đầu mút của nhánh được push nằm đằng sau bộ\n"
+"phận tương ứng của máy chủ. Nếu bạn không có ý định push nhánh đó, bạn có lẽ muốn\n"
+"chỉ định các nhánh để push hoặt là đặt nội dung cho biến cấu hình 'push.default'\n"
+"thành 'current' hoặc 'upstream' để push chỉ nhánh hiện hành mà thôi."
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"Việc cập nhật bị từ chối bởi vì đầu mút của nhánh được push nằm đằng sau bộ\n"
+"phận tương ứng của máy chủ. Checkou nhánh này và hòa trộn với các thay đổi từ máy chủ\n"
+"(v.d. 'git pull') trước khi lại push lần nữa.\n"
+"Xem trong phần 'Note about fast-forwards' trong nội dung từ lệnh 'git push --help'."
+
+#: builtin/push.c:233
+#, c-format
+msgid "Pushing to %s\n"
+msgstr "Äang push (đẩy) lên %s\n"
+
+#: builtin/push.c:237
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr "gặp lỗi khi push (đẩy lên) một số tham chiếu (ref) đến '%s'"
+
+#: builtin/push.c:269
+#, c-format
+msgid "bad repository '%s'"
+msgstr "repository (kho) sai '%s'"
+
+#: builtin/push.c:270
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+"Chưa cấu hình đích để push (đẩy lên).\n"
+"Hoặc là chỉ ra URL từ dòng lệnh hoặc là cấu hình một kho máy chủ sử dụng\n"
+"\n"
+" git remote add <tên> <url>\n"
+"\n"
+"và sau đó push sử dụng tên máy chủ\n"
+"\n"
+" git push <tên>\n"
+
+#: builtin/push.c:285
+msgid "--all and --tags are incompatible"
+msgstr "--all và --tags xung khắc nhau"
+
+#: builtin/push.c:286
+msgid "--all can't be combined with refspecs"
+msgstr "--all không thể được tổ hợp cùng với refspecs"
+
+#: builtin/push.c:291
+msgid "--mirror and --tags are incompatible"
+msgstr "--mirror và --tags xung khắc nhau"
+
+#: builtin/push.c:292
+msgid "--mirror can't be combined with refspecs"
+msgstr "--mirror không thể được tổ hợp cùng với refspecs"
+
+#: builtin/push.c:297
+msgid "--all and --mirror are incompatible"
+msgstr "--all và --mirror xung khắc nhau"
+
+#: builtin/push.c:385
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr "--delete là xung khắc vá»›i các tùy chá»n --all, --mirror và --tags"
+
+#: builtin/push.c:387
+msgid "--delete doesn't make sense without any refs"
+msgstr "--delete không hợp lý nếu không có bất kỳ tham chiếu (refs) nào"
+
+#: builtin/remote.c:98
+#, c-format
+msgid "Updating %s"
+msgstr "Äang cập nhật %s"
+
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
+msgstr ""
+"--mirror nguy hiểm và không dùng nữa; xin hãy\n"
+"\t sá»­ dụng tùy chá»n --mirror=fetch hoặc --mirror=push để thay thế"
+
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
+msgstr "không hiểu tham số máy bản sao (mirror): %s"
+
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
+msgstr "Ä‘ang chỉ định má»™t nhánh master không phân biệt HOA/thÆ°á»ng vá»›i tùy chá»n --mirror"
+
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
+msgstr "chỉ định những nhánh để theo vết chỉ hợp lý với các 'fetch mirror'"
+
+#: builtin/remote.c:195
+#: builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
+msgstr "máy chủ %s đã tồn tại rồi."
+
+#: builtin/remote.c:199
+#: builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
+msgstr "'%s' không phải tên máy chủ hợp lệ"
+
+#: builtin/remote.c:243
+#, c-format
+msgid "Could not setup master '%s'"
+msgstr "Không thể cài đặt nhánh master '%s'"
+
+#: builtin/remote.c:299
+#, c-format
+msgid "more than one %s"
+msgstr "nhiá»u hÆ¡n má»™t %s"
+
+#: builtin/remote.c:339
+#, c-format
+msgid "Could not get fetch map for refspec %s"
+msgstr "Không thể lấy ánh xạ (map) fetch cho refspec %s"
+
+#: builtin/remote.c:440
+#: builtin/remote.c:448
+msgid "(matching)"
+msgstr "(mẫu)"
+
+#: builtin/remote.c:452
+msgid "(delete)"
+msgstr "(xoá)"
+
+#: builtin/remote.c:595
+#: builtin/remote.c:601
+#: builtin/remote.c:607
+#, c-format
+msgid "Could not append '%s' to '%s'"
+msgstr "Không thể nối thêm '%s' vào '%s'"
+
+#: builtin/remote.c:639
+#: builtin/remote.c:792
+#: builtin/remote.c:890
+#, c-format
+msgid "No such remote: %s"
+msgstr "Không có máy chủ nào như thế: %s"
+
+#: builtin/remote.c:656
+#, c-format
+msgid "Could not rename config section '%s' to '%s'"
+msgstr "Không thể đổi tên chương (section) cấu hình từ '%s' thành '%s'"
+
+#: builtin/remote.c:662
+#: builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
+msgstr "Không thể gỡ bỠchương (section) cấu hình '%s'"
+
+#: builtin/remote.c:677
+#, c-format
+msgid ""
+"Not updating non-default fetch refspec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
+msgstr ""
+"Không cập nhật 'non-default fetch respec'\n"
+"\t%s\n"
+"\tXin hãy cập nhật phần cấu hình một cách thủ công nếu thấy cần thiết."
+
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
+msgstr "Không thể nối thêm '%s'"
+
+#: builtin/remote.c:694
+#, c-format
+msgid "Could not set '%s'"
+msgstr "Không thể đặt '%s'"
+
+#: builtin/remote.c:716
+#, c-format
+msgid "deleting '%s' failed"
+msgstr "việc xoá %s gặp lỗi"
+
+#: builtin/remote.c:750
+#, c-format
+msgid "creating '%s' failed"
+msgstr "tạo %s gặp lỗi"
+
+#: builtin/remote.c:764
+#, c-format
+msgid "Could not remove branch %s"
+msgstr "Không thể gỡ nhánh %s"
+
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] ""
+"Chú ý: Một nhánh nằm ngoài hệ thống refs/remotes/ đã không được gỡ bỠđi;\n"
+"để xóa đi, sử dụng:"
+msgstr[1] ""
+"Chú ý: Một số nhánh nằm ngoài hệ thống refs/remotes/ đã không được gỡ bỠđi;\n"
+"để xóa đi, sử dụng:"
+
+#: builtin/remote.c:943
+#, c-format
+msgid " new (next fetch will store in remotes/%s)"
+msgstr " mới (lần lấy vỠtiếp theo sẽ lưu trong remotes/%s)"
+
+#: builtin/remote.c:946
+msgid " tracked"
+msgstr " bị theo vết"
+
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
+msgstr " cÅ© (sá»­ dụng 'git remote prune' để gỡ bá»)"
+
+#: builtin/remote.c:950
+msgid " ???"
+msgstr " ???"
+
+#: builtin/remote.c:991
+#, c-format
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
+msgstr "branch.%s.merge không hợp lệ; không thể rebase vỠphía > 1 nhánh"
+
+#: builtin/remote.c:998
+#, c-format
+msgid "rebases onto remote %s"
+msgstr "thực hiện rebase trên máy chủ %s"
+
+#: builtin/remote.c:1001
+#, c-format
+msgid " merges with remote %s"
+msgstr " hòa trộn với máy chủ %s"
+
+#: builtin/remote.c:1002
+msgid " and with remote"
+msgstr " và với máy chủ"
+
+#: builtin/remote.c:1004
+#, c-format
+msgid "merges with remote %s"
+msgstr "hòa trộn với máy chủ %s"
+
+#: builtin/remote.c:1005
+msgid " and with remote"
+msgstr " và với máy chủ"
+
+#: builtin/remote.c:1051
+msgid "create"
+msgstr "tạo"
+
+#: builtin/remote.c:1054
+msgid "delete"
+msgstr "xoá"
+
+#: builtin/remote.c:1058
+msgid "up to date"
+msgstr "đã cập nhật"
+
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
+msgstr "có-thể-fast-forward"
+
+#: builtin/remote.c:1064
+msgid "local out of date"
+msgstr "dữ liệu nội bộ đã cũ"
+
+#: builtin/remote.c:1071
+#, c-format
+msgid " %-*s forces to %-*s (%s)"
+msgstr " %-*s ép buộc thành %-*s (%s)"
+
+#: builtin/remote.c:1074
+#, c-format
+msgid " %-*s pushes to %-*s (%s)"
+msgstr " %-*s push tá»›i %-*s (%s)"
+
+#: builtin/remote.c:1078
+#, c-format
+msgid " %-*s forces to %s"
+msgstr " %-*s ép buộc thành %s"
+
+#: builtin/remote.c:1081
+#, c-format
+msgid " %-*s pushes to %s"
+msgstr " %-*s push tá»›i %s"
+
+#: builtin/remote.c:1118
+#, c-format
+msgid "* remote %s"
+msgstr "* máy chủ %s"
+
+#: builtin/remote.c:1119
+#, c-format
+msgid " Fetch URL: %s"
+msgstr " URL để lấy vỠ(fetch): %s"
+
+#: builtin/remote.c:1120
+#: builtin/remote.c:1285
+msgid "(no URL)"
+msgstr "(không có URL nào)"
+
+#: builtin/remote.c:1129
+#: builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
+msgstr " URL để đẩy lên (push) : %s"
+
+#: builtin/remote.c:1133
+#: builtin/remote.c:1135
+#: builtin/remote.c:1137
+#, c-format
+msgid " HEAD branch: %s"
+msgstr " Nhánh HEAD: %s"
+
+#: builtin/remote.c:1139
+#, c-format
+msgid " HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
+msgstr " nhánh HEAD (HEAD máy chủ là không rõ ràng, có lẽ là một trong số sau):\n"
+
+#: builtin/remote.c:1151
+#, c-format
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] " Nhánh trên máy chủ:%s"
+msgstr[1] " Những nhánh trên máy chủ:%s"
+
+#: builtin/remote.c:1154
+#: builtin/remote.c:1181
+msgid " (status not queried)"
+msgstr " (trạng thái không được yêu cầu)"
+
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] " Nhánh nội bộ đã được cấu hình cho lệnh 'git pull':"
+msgstr[1] " Những nhánh nội bộ đã được cấu hình cho lệnh 'git pull':"
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr " refs nội bộ sẽ được phản chiếu bởi lệnh 'git push'"
+
+#: builtin/remote.c:1178
+#, c-format
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] " Tham chiếu nội bộ được cấu hình cho lệnh 'git push'%s:"
+msgstr[1] " Những tham chiếu nội bộ được cấu hình cho lệnh 'git push'%s:"
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
+msgstr "Không thể xác định được HEAD máy chủ"
+
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
+msgstr "Nhiá»u nhánh HEAD máy chủ. Hãy chá»n rõ ràng má»™t:"
+
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
+msgstr "Không thể xóa bỠ%s"
+
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
+msgstr "Không phải là tham chiếu (ref) hợp lệ: %s"
+
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
+msgstr "Không thể cài đặt %s"
+
+#: builtin/remote.c:1274
+#, c-format
+msgid " %s will become dangling!"
+msgstr " %s sẽ trở thành lủng lẳng (không được quản lý)!"
+
+#: builtin/remote.c:1275
+#, c-format
+msgid " %s has become dangling!"
+msgstr " %s phải trở thành lủng lẳng (không được quản lý)!"
+
+#: builtin/remote.c:1281
+#, c-format
+msgid "Pruning %s"
+msgstr "Äang xén bá»›t %s"
+
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
+msgstr "URL: %s"
+
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
+msgstr " * [nên xén bớt] %s"
+
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
+msgstr " *[đã xén bớ] %s"
+
+#: builtin/remote.c:1387
+#: builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
+msgstr "Không có máy chủ nào có tên '%s'"
+
+#: builtin/remote.c:1414
+msgid "no remote specified"
+msgstr "chưa chỉ ra máy chủ nào"
+
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
+msgstr "--add --delete không hợp lý"
+
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
+msgstr "Kiểu mẫu URL cũ không hợp lệ: %s"
+
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr "Không tìm thấy URL như vậy: %s"
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
+msgstr "Sẽ không xóa những địa chỉ URL không-push"
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr "pha trá»™n"
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr "má»m"
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr "cứng"
+
+#: builtin/reset.c:33
+msgid "merge"
+msgstr "hòa trộn"
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr "giữ lại"
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr "Bạn không có HEAD nào hợp lệ."
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr "Gặp lỗi khi tìm cây của HEAD."
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr "Gặp lỗi khi tìm cây của %s."
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr "Không thể ghi tập tin lưu bảng mục lục mới."
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr "HEAD hiện giỠtại %s"
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr "Không thể Ä‘á»c bảng mục lục"
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr "Những thay đổi bị bỠtrạng thái (stage) sau khi reset:"
+
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr "Không thể thực hiện một %s reset ở giữa của quá trình hòa trộn."
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr "không thể phân tích đối tượng '%s'."
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr "--patch xung khắc với --{hard,mixed,soft}"
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr "--mixed vá»›i các Ä‘Æ°á»ng dẫn không còn dùng nữa; hãy thay thế bằng lệnh 'git reset -- <Ä‘Æ°á»ng_dẫn>'."
+
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr "Không thể thá»±c hiện lệnh %s reset vá»›i các Ä‘Æ°á»ng dẫn."
+
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr "%s reset không được phép trên kho bare (trên máy chủ)"
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr "Không thể đặt lại (reset) bảng mục lục thành điểm xét lại '%s'."
+
+#: builtin/revert.c:70
+#: builtin/revert.c:92
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr "%s: %s không thể được sử dụng với %s"
+
+#: builtin/revert.c:131
+msgid "program error"
+msgstr "lỗi chương trình"
+
+#: builtin/revert.c:221
+msgid "revert failed"
+msgstr "revert gặp lỗi"
+
+#: builtin/revert.c:236
+msgid "cherry-pick failed"
+msgstr "cherry-pick gặp lỗi"
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+"'%s' có nội dung được lưu trạng thái khác biệt từ cả tập tin và cả HEAD\n"
+"(sá»­ dụng -f để ép buá»™c gỡ bá»)"
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"'%s' có các thay đổi được lưu trạng thái trong bảng mục lục\n"
+"(sá»­ dụng --cached để giữ tập tin, hoặc -f để ép buá»™c gỡ bá»)"
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"'%s' có các thay đổi nội bộ\n"
+"(sá»­ dụng --cached để giữ tập tin, hoặc -f để ép buá»™c gỡ bá»)"
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr "không thể gỡ bá» '%s' má»™t cách đệ qui mà không có tùy chá»n -r"
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr "git rm: không thể gỡ bỠ%s"
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr "Thiếu tên tác giả: %s"
+
+#: builtin/tag.c:60
+#, c-format
+msgid "malformed object at '%s'"
+msgstr "đối tượng dị hình tại '%s'"
+
+#: builtin/tag.c:207
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr "tên thẻ quá dài: %.*s..."
+
+#: builtin/tag.c:212
+#, c-format
+msgid "tag '%s' not found."
+msgstr "không tìm thấy tìm thấy thẻ '%s'."
+
+#: builtin/tag.c:227
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr "Thẻ đã bị xóa '%s' (trước là %s)\n"
+
+#: builtin/tag.c:239
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr "không thể thẩm tra thẻ '%s'"
+
+#: builtin/tag.c:249
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# Viết các ghi chú cho (thẻ) tag\n"
+"# Những dòng được bắt đầu bằng '#' sẽ được bỠqua.\n"
+"#\n"
+
+#: builtin/tag.c:256
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you want to.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# Viết các ghi chú cho (thẻ) tag\n"
+"# Những dòng được bắt đầu bằng '#' sẽ được bỠqua; bạn có thể xóa chúng đi nếu muốn.\n"
+"#\n"
+
+#: builtin/tag.c:298
+msgid "unable to sign the tag"
+msgstr "không thể ký thẻ"
+
+#: builtin/tag.c:300
+msgid "unable to write tag file"
+msgstr "không thể ghi vào tập tin lưu thẻ"
+
+#: builtin/tag.c:325
+msgid "bad object type."
+msgstr "kiểu đối tượng sai."
+
+#: builtin/tag.c:338
+msgid "tag header too big."
+msgstr "đầu thẻ (tag) quá lớn."
+
+#: builtin/tag.c:370
+msgid "no tag message?"
+msgstr "không có thông điệp (message) cho thẻ (tag)?"
+
+#: builtin/tag.c:376
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr "Nội dung ghi chú còn lại %s\n"
+
+#: builtin/tag.c:425
+msgid "switch 'points-at' requires an object"
+msgstr "chuyển đến 'points-at' yêu cần một đối tượng"
+
+#: builtin/tag.c:427
+#, c-format
+msgid "malformed object name '%s'"
+msgstr "tên đối tượng dị hình '%s'"
+
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr "--column và -n xung khắc nhau"
+
+#: builtin/tag.c:523
+msgid "-n option is only allowed with -l."
+msgstr "tùy chá»n -n chỉ cho phép dùng vá»›i -l."
+
+#: builtin/tag.c:525
+msgid "--contains option is only allowed with -l."
+msgstr "tùy chá»n --contains chỉ cho phép dùng vá»›i -l."
+
+#: builtin/tag.c:527
+msgid "--points-at option is only allowed with -l."
+msgstr "tùy chá»n --points-at chỉ cho phép dùng vá»›i -l."
+
+#: builtin/tag.c:535
+msgid "only one -F or -m option is allowed."
+msgstr "chỉ có má»™t tùy chá»n -F hoặc -m là được phép."
+
+#: builtin/tag.c:555
+msgid "too many params"
+msgstr "quá nhiá»u đối số"
+
+#: builtin/tag.c:561
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr "'%s' không phải thẻ hợp lệ."
+
+#: builtin/tag.c:566
+#, c-format
+msgid "tag '%s' already exists"
+msgstr "Thẻ '%s' đã tồn tại rồi"
+
+#: builtin/tag.c:584
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr "%s: không thể khóa ref (tham chiếu)"
+
+#: builtin/tag.c:586
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr "%s: không thể cập nhật ref (tham chiếu)"
+
+#: builtin/tag.c:588
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr "Thẻ đã cập nhật '%s' (cũ là %s)\n"
+
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr "Chạy lệnh 'git help <tên-lệnh>' để có thêm thông tin vỠlệnh được chỉ ra."
+
+#: parse-options.h:133
+#: parse-options.h:235
+msgid "n"
+msgstr "n"
+
+#: parse-options.h:141
+msgid "time"
+msgstr "thá»i-gian"
+
+#: parse-options.h:149
+msgid "file"
+msgstr "tập-tin"
+
+#: parse-options.h:151
+msgid "when"
+msgstr "khi"
+
+#: parse-options.h:156
+msgid "no-op (backward compatibility)"
+msgstr "no-op (tương thích ngược)"
+
+#: parse-options.h:228
+msgid "be more verbose"
+msgstr "chi tiết hơn nữa"
+
+#: parse-options.h:230
+msgid "be more quiet"
+msgstr "im lặng hơn nữa"
+
+#: parse-options.h:236
+msgid "use <n> digits to display SHA-1s"
+msgstr "sử dụng <n> chữ số để hiển thị SHA-1s"
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr "Thêm nội dung tập tin vào bảng mục lục"
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr "Tìm kiếm bằng Ä‘iá»u tra nhị phân các thay đổi mà nó bắt đầu lá»—i"
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr "Liệt kê, tạo hay là xóa các nhánh"
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr "Checkout má»™t nhánh hay các Ä‘Æ°á»ng dẫn tá»i cây làm việc"
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr "Nhân bản một kho chứa đến một thư mục mới"
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr "Ghi các thay đổi vào kho chứa"
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr "Hiển thị các thay đổi giữa những lần chuyển giao (commit), commit và cây làm việc, v.v.."
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr "Các đối tượng và tham chiếu được tải vỠtừ kho chứa khác"
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr "In ra những dòng khớp với một mẫu"
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr "Tạo một kho git trống rỗng hay khởi tạo lại một kho đã tồn tại từ trước"
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr "hiển thị nhật ký các lần commit (chuyển giao)"
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr "Hợp nhất hai hay nhiá»u hÆ¡n lịch sá»­ của các nhà phát triển phần má»m lại vá»›i nhau"
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr "Di chuyển, đổi tên một tập tin, thư mục hay liên kết tượng trưng"
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr "Fetch (lấy vá») và hòa trá»™ng vá»›i kho khác hay nhánh ná»™i bá»™"
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr "Cập nhật tham chiếu (refs) máy chủ cùng với các đối tượng liên quan đến nó"
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr "Forward-port những lần chuyển giao nội bộ tới head dòng ngược đã cập nhật"
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr "Äặt lại HEAD hiện hành thành má»™t trạng thái được chỉ ra"
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr "Gỡ bỠcác tập tin từ cây làm việc và từ bảng mục lục"
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr "Hiển thị các kiểu khác nhau của các đối tượng"
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr "Hiển thị trạng thái cây làm việc"
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr "Tạo, liệt kê, xóa hay xác thực một đối tượng thẻ (tag) mà nó được ký sử dụng GPG"
+
+#: git-am.sh:50
+msgid "You need to set your committer info first"
+msgstr "Bạn cần đặt thông tin vá» ngÆ°á»i chuyển giao mã nguồn trÆ°á»›c đã"
+
+#: git-am.sh:95
+msgid ""
+"You seem to have moved HEAD since the last 'am' failure.\n"
+"Not rewinding to ORIG_HEAD"
+msgstr ""
+"Bạn có lẽ đã có HEAD đã bị di chuyển đi kể từ lần 'am' thất bại cuối cùng.\n"
+"Không thể chuyển tới ORIG_HEAD"
+
+#: git-am.sh:105
+#, sh-format
+msgid ""
+"When you have resolved this problem run \"$cmdline --resolved\".\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"Khi bạn cần giải quyết vấn đỠnày hãy chạy lệnh \"$cmdline --resolved\".\n"
+"Nếu bạn có ý định bỠqua miếng vá, thay vào đó bạn chạy \"$cmdline --skip\".\n"
+"Äể phục hồi lại thành nhánh nguyên bản và dừng việc vá lại thì chạy \"$cmdline --abort\"."
+
+#: git-am.sh:121
+msgid "Cannot fall back to three-way merge."
+msgstr "Äang trở lại để hòa trá»™n kiểu 'three-way'."
+
+#: git-am.sh:137
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr "Kho thiếu đối tượng blob cần thiết để trở vỠtrên '3-way merge'."
+
+#: git-am.sh:154
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+"Bạn đã sửa miếng vá của mình bằng cách thủ công à?\n"
+"Nó không thể áp dụng các blob đã được ghi lại trong bảng mục lục của nó."
+
+#: git-am.sh:163
+msgid "Falling back to patching base and 3-way merge..."
+msgstr "Äang trở lại để vá cÆ¡ sở và '3-way merge'..."
+
+#: git-am.sh:275
+msgid "Only one StGIT patch series can be applied at once"
+msgstr "Chỉ có một sê-ri miếng vá StGIT được áp dụng một lúc"
+
+#: git-am.sh:362
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr "Äịnh dạng miếng vá $patch_format không được há»— trợ."
+
+#: git-am.sh:364
+msgid "Patch format detection failed."
+msgstr "Dò tìm định dạng miếng vá gặp lỗi."
+
+#: git-am.sh:418
+msgid "-d option is no longer supported. Do not use."
+msgstr "Tùy chá»n -d không còn được há»— trợ nữa. Xin đừng sá»­ dụng."
+
+#: git-am.sh:481
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr "thư mục rebase trước $dotest vẫn chưa sẵn sàng nhưng mbox được đưa ra."
+
+#: git-am.sh:486
+msgid "Please make up your mind. --skip or --abort?"
+msgstr "Xin hãy rõ ràng. --skip hay --abort?"
+
+#: git-am.sh:513
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr "Thao tác phân giải không đang được tiến hành, chúng ta không phục hồi lại."
+
+#: git-am.sh:579
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr "Bảng mục lục sai: không thể áp dụng các miếng vá (sai: $files)"
+
+#: git-am.sh:671
+#, sh-format
+msgid ""
+"Patch is empty. Was it split wrong?\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"Miếng vá trống rỗng. Nó đã bị chia cắt sai phải không?\n"
+"Nếu bạn thích bỠqua miếng vá này, hãy chạy lệnh sau để thay thế \"$cmdline --skip\".\n"
+"Äể phục hồi lại nhánh nguyên bản và dừng vá lại hãy chạy lệnh \"$cmdline --abort\"."
+
+#: git-am.sh:708
+msgid "Patch does not have a valid e-mail address."
+msgstr "Miếng vá không có địa chỉ e-mail hợp lệ."
+
+#: git-am.sh:755
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr "không thể được tương tác mà không có stdin kết nối với một thiết bị cuối"
+
+#: git-am.sh:759
+msgid "Commit Body is:"
+msgstr "Thân của lần chuyển giao (commit) là:"
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:766
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr "Ãp dụng? đồng ý [y]/không [n]/chỉnh sá»­a [e]/hiển thị miếng [v]á/đồng ý tất cả [a]"
+
+#: git-am.sh:802
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr "Äang áp dụng (miếng vá): $FIRSTLINE"
+
+#: git-am.sh:823
+msgid ""
+"No changes - did you forget to use 'git add'?\n"
+"If there is nothing left to stage, chances are that something else\n"
+"already introduced the same changes; you might want to skip this patch."
+msgstr ""
+"Không có thay đổi nào - bạn đã quên sử dụng lệnh 'git add' à?\n"
+"Nếu ở đây không có gì còn lại stage, tình cỠlà có một số thứ khác\n"
+"đã sẵn được đưa vào với cùng nội dung thay đổi; bạn có lẽ muốn bỠqua miếng vá này."
+
+#: git-am.sh:831
+msgid ""
+"You still have unmerged paths in your index\n"
+"did you forget to use 'git add'?"
+msgstr ""
+"Bạn vẫn có những Ä‘Æ°á»ng dẫn chÆ°a được hòa trá»™n trong bảng mục lục của mình\n"
+"bạn đã quên sử dụng lệnh 'git add' à?"
+
+#: git-am.sh:847
+msgid "No changes -- Patch already applied."
+msgstr "Không thay đổi gì cả -- Miếng vá đã được áp dụng rồi."
+
+#: git-am.sh:857
+#, sh-format
+msgid "Patch failed at $msgnum $FIRSTLINE"
+msgstr "Vá gặp lỗi tại $msgnum $FIRSTLINE"
+
+#: git-am.sh:873
+msgid "applying to an empty history"
+msgstr "áp dụng vào một lịch sử trống rỗng"
+
+#: git-bisect.sh:48
+msgid "You need to start by \"git bisect start\""
+msgstr "Bạn cần khởi đầu bằng \"git bisect start\""
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr "Bạn có muốn tôi thá»±c hiện Ä‘iá»u này cho bạn không [Y/n]? "
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr "không công nhận tùy chá»n: '$arg'"
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr "'$arg' không có vẻ như là một sự xét lại hợp lệ"
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr "HEAD sai - Tôi cần một HEAD"
+
+#: git-bisect.sh:130
+#, sh-format
+msgid "Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr "Việc checkout '$start_head' gặp lỗi. Hãy thử 'git bisect reset <nhánh_hợp_lệ>'."
+
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr "sẽ không bisect trêm cây được seek"
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr "HEAD sai - tham chiếu (ref) tượng trưng kỳ lạ"
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr "Äối số bisect_write sai: $state"
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr "Äầu vào rev sai: $arg"
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr "Hãy gá»i lệnhl 'bisect_state' vá»›i ít nhất má»™t đối số."
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr "Äầu vào rev sai: $rev"
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr "'git bisect bad' có thể lấy chỉ một đối số."
+
+#. have bad but not good. we could bisect although
+#. this is less optimum.
+#: git-bisect.sh:273
+msgid "Warning: bisecting only with a bad commit."
+msgstr "Cảnh báo: chỉ thực hiện việc bisect với một lần chuyển giao (commit) sai."
+
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr "Bạn có chắc chắn chưa [Y/n]?"
+
+#: git-bisect.sh:289
+msgid ""
+"You need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"Bạn phải chỉ cho tôi ít nhất một điểm xét duyệt tốt và một điểm sai.\n"
+"(Bạn có thể sử dụng \"git bisect bad\" và \"git bisect good\" cho cái đó.)"
+
+#: git-bisect.sh:292
+msgid ""
+"You need to start by \"git bisect start\".\n"
+"You then need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"Bạn cần bắt đầu bằng lệnh \"git bisect start\".\n"
+"Bạn sau đó cần phải chỉ cho tôi ít nhất một điểm xét duyệt đúng và một điểm sai.\n"
+"(Bạn có thể sử dụng \"git bisect bad\" và \"git bisect good\" cho chúng.)"
+
+#: git-bisect.sh:347
+#: git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr "Chúng tôi không bisect."
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr "'$invalid' không phải là lần chuyển giao (commit) hợp lệ"
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+"Không thể check out original HEAD '$branch'.\n"
+"Hãy thử 'git bisect reset <commit>'."
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr "Chưa chỉ ra tập tin ghi nhật ký"
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr "không thể Ä‘á»c $file để thao diá»…n lại"
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr "?? bạn đang nói gì thế?"
+
+#: git-bisect.sh:420
+#, sh-format
+msgid "running $command"
+msgstr "đang chạy lệnh $command"
+
+#: git-bisect.sh:427
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"exit code $res from '$command' is < 0 or >= 128"
+msgstr ""
+"chạy bisect gặp lỗi:\n"
+"mã trả vỠ$res từ lệnh '$command' là < 0 hoặc >= 128"
+
+#: git-bisect.sh:453
+msgid "bisect run cannot continue any more"
+msgstr "bisect không thể tiếp tục thêm được nữa"
+
+#: git-bisect.sh:459
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"'bisect_state $state' exited with error code $res"
+msgstr ""
+"chạy bisect gặp lỗi:\n"
+"'bisect_state $state' đã thoát ra với mã lỗi $res"
+
+#: git-bisect.sh:466
+msgid "bisect run success"
+msgstr "bisect chạy thành công"
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+"Pull là không thể được bởi vì bạn có những tập tin chưa được hòa trộn.\n"
+"Xin hãy sửa chữa chúng trước, và sau đó sử dụng lệnh 'git add/rm <tập-tin>'\n"
+"để phê chuẩn việc đánh dấu đây cần được giải quyết, hoặc là sử dụng 'git commit -a'."
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr "Full là không thể thực hiện bởi vì bạn có những tập tin chưa được hòa trộn."
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr "đang cập nhật một nhánh chưa được sinh ra với các thay đổi được thêm vào bảng mục lục"
+
+#. The fetch involved updating the current branch.
+#. The working tree and the index file is still based on the
+#. $orig_head commit, but we are merging into $curr_head.
+#. First update the working tree to match $curr_head.
+#: git-pull.sh:228
+#, sh-format
+msgid ""
+"Warning: fetch updated the current branch head.\n"
+"Warning: fast-forwarding your working tree from\n"
+"Warning: commit $orig_head."
+msgstr ""
+"Cảnh báo: fetch đã cập nhật head nhánh hiện tại.\n"
+"Cảnh báo: đang fast-forward cây làm việc của bạn từ\n"
+"Cảnh báo: commit $orig_head."
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr "Không thể hòa trá»™n nhiá»u nhánh và trong má»™t head trống rá»—ng"
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr "Không thể thá»±c hiện lệnh rebase (cÆ¡ cấu lại) trên nhiá»u nhánh"
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr "git stash clear với các tham số là chưa được thực hiện (không nhận đối số)"
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr "Bạn chưa còn có lần chuyển giao (commit) khởi tạo"
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr "Không thể ghi lại trạng thái bảng mục lục hiện hành"
+
+#: git-stash.sh:123
+#: git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr "Không thể ghi lại trạng thái cây-làm-việc hiện hành"
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr "ChÆ°a có thay đổi nào được chá»n"
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr "Không thể gỡ bá» bảng mục lục tạm thá»i (không thể xảy ra)"
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr "Không thể ghi lại trạng thái cây làm việc hiện hành"
+
+#. TRANSLATORS: $option is an invalid option, like
+#. `--blah-blah'. The 7 spaces at the beginning of the
+#. second line correspond to "error: ". So you should line
+#. up the second line with however many characters the
+#. translation of "error: " takes in your language. E.g. in
+#. English this is:
+#.
+#. $ git stash save --blah-blah 2>&1 | head -n 2
+#. error: unknown option for 'stash save': --blah-blah
+#. To provide a message, use git stash save -- '--blah-blah'
+#: git-stash.sh:202
+#, sh-format
+msgid ""
+"error: unknown option for 'stash save': $option\n"
+" To provide a message, use git stash save -- '$option'"
+msgstr ""
+"lá»—i: không hiểu tùy chá»n cho 'stash save': $option\n"
+" Äể cung cấp má»™t thông Ä‘iệp, sá»­ dụng git stash save -- '$option'"
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr "Không có thay đổi nội bộ nào được ghi lại"
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr "Không thể khởi tạo stash"
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr "Không thể ghi lại trạng thái hiện hành"
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr "Không thể gỡ bỠcác thay đổi cây-làm-việc"
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr "Không tìm thấy stast nào."
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr "Chỉ ra quá nhiá»u Ä‘iểm xét lại: $REV"
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr "$reference không phải là tham chiếu hợp lệ"
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr "'$args' không phải là lần chuyển giao (commit) giống-stash"
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr "'$args' không phải tham chiếu đến stash"
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr "không thể làm tươi mới bảng mục lục"
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr "Không thể áp dụng một stash ở giữa của quá trình hòa trộn"
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr "Xung Ä‘á»™t trong bảng mục lục. Hãy thá»­ mà không dùng tùy chá»n --index."
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr "Không thể ghi lại cây chỉ mục"
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr "Không thể bỠtrạng thía của các tập tin đã được sửa chữa"
+
+#: git-stash.sh:474
+msgid "Index was not unstashed."
+msgstr "Bảng mục lục đã không được bỠstash."
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr "Äã hạ xuống ${REV} ($s)"
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr "${REV}: Không thể xóa bỠmục stash"
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr "Chưa chỉ ra tên của nhánh"
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr "(Äể phục hồi lại chúng hãy gõ \"git stash apply\")"
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr "không thể tháo bá» má»™t thành phần ra khá»i url '$remoteurl'"
+
+#: git-submodule.sh:109
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr "Không tìm thấy ánh xạ (mapping) mô-Ä‘un-con trong .gitmodules cho Ä‘Æ°á»ng dẫn '$sm_path'"
+
+#: git-submodule.sh:150
+#, sh-format
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr "Nhân bản '$url' vào Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path' gặp lá»—i"
+
+#: git-submodule.sh:160
+#, sh-format
+msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
+msgstr "Gitdir '$a' là bá»™ phận của Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$b' hoặc \"vice versa\""
+
+#: git-submodule.sh:249
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr "repo URL: '$repo' phải là Ä‘Æ°á»ng dẫn tuyệt đối hoặc là bắt đầu bằng ./|../"
+
+#: git-submodule.sh:266
+#, sh-format
+msgid "'$sm_path' already exists in the index"
+msgstr "'$sm_path' thực sự đã tồn tại ở bảng mục lục rồi"
+
+#: git-submodule.sh:270
+#, sh-format
+msgid ""
+"The following path is ignored by one of your .gitignore files:\n"
+"$sm_path\n"
+"Use -f if you really want to add it."
+msgstr ""
+"Các Ä‘Æ°á»ng dẫn theo sau đây sẽ bị lá» Ä‘i bởi má»™t trong các tập tin .gitignore của bạn:\n"
+"$sm_path\n"
+"Sử dụng -f nếu bạn thực sự muốn thêm nó vào."
+
+#: git-submodule.sh:281
+#, sh-format
+msgid "Adding existing repo at '$sm_path' to the index"
+msgstr "Äang thêm repo có sẵn tại '$sm_path' vào bảng mục lục"
+
+#: git-submodule.sh:283
+#, sh-format
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "'$sm_path' đã tồn tại từ trước và không phải là một kho git hợp lệ"
+
+#: git-submodule.sh:297
+#, sh-format
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr "Không thể checkout mô-đun con '$sm_path'"
+
+#: git-submodule.sh:302
+#, sh-format
+msgid "Failed to add submodule '$sm_path'"
+msgstr "Gặp lỗi khi thêm mô-đun con '$sm_path'"
+
+#: git-submodule.sh:307
+#, sh-format
+msgid "Failed to register submodule '$sm_path'"
+msgstr "Gặp lỗi khi đăng ký với hệ thống mô-đun con '$sm_path'"
+
+#: git-submodule.sh:349
+#, sh-format
+msgid "Entering '$prefix$sm_path'"
+msgstr "Äang nhập '$prefix$sm_path'"
+
+#: git-submodule.sh:363
+#, sh-format
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr "Dừng lại tại '$sm_path'; script trả vỠtrạng thái khác không."
+
+#: git-submodule.sh:406
+#, sh-format
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr "Không tìm thấy url cho Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path' trong .gitmodules"
+
+#: git-submodule.sh:415
+#, sh-format
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr "Gặp lá»—i khi đăng ký url cho Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path'"
+
+#: git-submodule.sh:417
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr "Mô-Ä‘un-con '$name' ($url) được đăng ký cho Ä‘Æ°á»ng dẫn '$sm_path'"
+
+#: git-submodule.sh:425
+#, sh-format
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr "Gặp lá»—i khi đăng ký chế Ä‘á»™ cập nhật cho Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path'"
+
+#: git-submodule.sh:524
+#, sh-format
+msgid ""
+"Submodule path '$sm_path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+"ÄÆ°á»ng dẫn mô-Ä‘un-con '$sm_path' chÆ°a được khởi tạo\n"
+"Có lẽ bạn muốn sử dụng lệnh 'update --init'?"
+
+#: git-submodule.sh:537
+#, sh-format
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr "Không tìm thấy Ä‘iểm xét lại hiện hành trong Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path'"
+
+#: git-submodule.sh:556
+#, sh-format
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr "Không thể lấy vá» (fetch) trong Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path'"
+
+#: git-submodule.sh:570
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr "Không thể rebase '$sha1' trong Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path'"
+
+#: git-submodule.sh:571
+#, sh-format
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr "ÄÆ°á»ng dẫn mô-Ä‘un-con '$sm_path': được rebase vào trong '$sha1'"
+
+#: git-submodule.sh:576
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr "Không thể hòa trá»™n (merge) '$sha1' trong Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path'"
+
+#: git-submodule.sh:577
+#, sh-format
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr "ÄÆ°á»ng dẫn mô-Ä‘un-con '$sm_path': được hòa trá»™n vào '$sha1'"
+
+#: git-submodule.sh:582
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr "Không thể checkout '$sha1' trong Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path'"
+
+#: git-submodule.sh:583
+#, sh-format
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr "ÄÆ°á»ng dẫn mô-Ä‘un-con '$sm_path': được checkout '$sha1'"
+
+#: git-submodule.sh:605
+#: git-submodule.sh:928
+#, sh-format
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr "Gặp lá»—i khi đệ quy vào trong Ä‘Æ°á»ng dẫn mô-Ä‘un-con '$sm_path'"
+
+#: git-submodule.sh:713
+msgid "--cached cannot be used with --files"
+msgstr "--cached không thể được sử dụng cùng với --files"
+
+#. unexpected type
+#: git-submodule.sh:753
+#, sh-format
+msgid "unexpected mode $mod_dst"
+msgstr "chế độ không như mong chỠ$mod_dst"
+
+#: git-submodule.sh:771
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr " Cảnh báo: $name không chứa lần chuyển giao (commit) $sha1_src"
+
+#: git-submodule.sh:774
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr " Cảnh báo: $name không chứa lần chuyển giao (commit) $sha1_dst"
+
+#: git-submodule.sh:777
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr " Cảnh báo: $name không chứa những lần chuyển giao (commit) $sha1_src và $sha1_dst"
+
+#: git-submodule.sh:802
+msgid "blob"
+msgstr "blob"
+
+#: git-submodule.sh:803
+msgid "submodule"
+msgstr "mô-đun con"
+
+#: git-submodule.sh:840
+msgid "# Submodules changed but not updated:"
+msgstr "# Những mô-đun-con đã bị thay đổi nhưng chưa được cập nhật:"
+
+#: git-submodule.sh:842
+msgid "# Submodule changes to be committed:"
+msgstr "# Những thay đổi mô-đun-con được chuyển giao (commit):"
+
+#: git-submodule.sh:974
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr "Äang đồng bá»™ hóa url mô-Ä‘un-con cho '$name'"
+
+#~ msgid "--"
+#~ msgstr "--"
+
+#~ msgid "Could not extract email from committer identity."
+#~ msgstr ""
+#~ "Không thể rút trích địa chỉ thÆ° Ä‘iện tá»­ từ định danh ngÆ°á»i chuyển giao"
+
+#, fuzzy
+#~ msgid "could not parse commit %s\n"
+#~ msgstr "Không thể phân tích commit (lần chuyển giao) %s\n"
+
+#, fuzzy
+#~ msgid "cherry-pick"
+#~ msgstr "< Chá»n D-Mod"
+
+#, fuzzy
+#~ msgid "Too many options specified"
+#~ msgstr "đã ghi rõ quá nhiá»u kích cỡ"
diff --git a/po/zh_CN.po b/po/zh_CN.po
new file mode 100644
index 0000000..b46b53e
--- /dev/null
+++ b/po/zh_CN.po
@@ -0,0 +1,5471 @@
+# Chinese translations for Git package
+# Git 软件包的简体中文翻译.
+# Copyright (C) 2012 Jiang Xin <worldhello.net AT gmail.com>
+# This file is distributed under the same license as the Git package.
+# Contributers:
+# - Jiang Xin <worldhello.net AT gmail.com>
+# - Riku <lu.riku AT gmail.com>
+# - Zhuang Ya <zhuangya AT me.com>
+# - Lian Cheng <rhythm.mail AT gmail.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2012-06-08 10:20+0800\n"
+"PO-Revision-Date: 2012-06-08 12:24+0800\n"
+"Last-Translator: Jiang Xin <worldhello.net@gmail.com>\n"
+"Language-Team: GitHub <https://github.com/gotgit/git/>\n"
+"Language: zh_CN\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"
+
+#: advice.c:40
+#, c-format
+msgid "hint: %.*s\n"
+msgstr "æ示:%.*s\n"
+
+#.
+#. * Message used both when 'git commit' fails and when
+#. * other commands doing a merge do.
+#.
+#: advice.c:70
+msgid ""
+"Fix them up in the work tree,\n"
+"and then use 'git add/rm <file>' as\n"
+"appropriate to mark resolution and make a commit,\n"
+"or use 'git commit -a'."
+msgstr ""
+"请先在工作区改正文件,然åŽé…Œæƒ…使用\n"
+"'git add/rm <file>' 标记解决方案,\n"
+"或使用 'git commit -a'。"
+
+#: bundle.c:36
+#, c-format
+msgid "'%s' does not look like a v2 bundle file"
+msgstr "'%s' ä¸åƒæ˜¯ä¸€ä¸ª v2 版本的包文件"
+
+#: bundle.c:63
+#, c-format
+msgid "unrecognized header: %s%s (%d)"
+msgstr "未能识别的包头:%s%s (%d)"
+
+#: bundle.c:89 builtin/commit.c:696
+#, c-format
+msgid "could not open '%s'"
+msgstr "ä¸èƒ½æ‰“å¼€ '%s'"
+
+#: bundle.c:140
+msgid "Repository lacks these prerequisite commits:"
+msgstr "版本库缺少这些必备的æ交:"
+
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
+#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: builtin/shortlog.c:181
+msgid "revision walk setup failed"
+msgstr "版本é历设置失败"
+
+#: bundle.c:186
+#, c-format
+msgid "The bundle contains %d ref"
+msgid_plural "The bundle contains %d refs"
+msgstr[0] "这个包中å«æœ‰ %d 个引用"
+msgstr[1] "这个包中å«æœ‰ %d 个引用"
+
+#: bundle.c:192
+#, c-format
+msgid "The bundle requires this ref"
+msgid_plural "The bundle requires these %d refs"
+msgstr[0] "这个包需è¦è¿™ä¸ªå¼•ç”¨"
+msgstr[1] "è¿™ä¸ªåŒ…éœ€è¦ %d 个这些引用"
+
+#: bundle.c:290
+msgid "rev-list died"
+msgstr "rev-list 终止"
+
+#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#, c-format
+msgid "unrecognized argument: %s"
+msgstr "未能识别的å‚数:%s"
+
+#: bundle.c:331
+#, c-format
+msgid "ref '%s' is excluded by the rev-list options"
+msgstr "引用 '%s' 被 rev-list 选项排除"
+
+#: bundle.c:376
+msgid "Refusing to create empty bundle."
+msgstr "ä¸èƒ½åˆ›å»ºç©ºåŒ…。"
+
+#: bundle.c:394
+msgid "Could not spawn pack-objects"
+msgstr "ä¸èƒ½ç”Ÿæˆ pack-objects 进程"
+
+#: bundle.c:412
+msgid "pack-objects died"
+msgstr "pack-objects 终止"
+
+#: bundle.c:415
+#, c-format
+msgid "cannot create '%s'"
+msgstr "ä¸èƒ½åˆ›å»º '%s'"
+
+#: bundle.c:437
+msgid "index-pack died"
+msgstr "index-pack 终止"
+
+#: commit.c:48
+#, c-format
+msgid "could not parse %s"
+msgstr "ä¸èƒ½è§£æž %s"
+
+#: commit.c:50
+#, c-format
+msgid "%s %s is not a commit!"
+msgstr "%s %s ä¸æ˜¯ä¸€ä¸ªæ交!"
+
+#: compat/obstack.c:406 compat/obstack.c:408
+msgid "memory exhausted"
+msgstr "内存耗尽"
+
+#: connected.c:39
+msgid "Could not run 'git rev-list'"
+msgstr "ä¸èƒ½æ‰§è¡Œ 'git rev-list'"
+
+#: connected.c:48
+#, c-format
+msgid "failed write to rev-list: %s"
+msgstr "无法写入 rev-list:%s"
+
+#: connected.c:56
+#, c-format
+msgid "failed to close rev-list's stdin: %s"
+msgstr "无法关闭 rev-list 的标准输入:%s"
+
+#: date.c:95
+msgid "in the future"
+msgstr "在将æ¥"
+
+#: date.c:101
+#, c-format
+msgid "%lu second ago"
+msgid_plural "%lu seconds ago"
+msgstr[0] "%lu 秒钟之å‰"
+msgstr[1] "%lu 秒钟之å‰"
+
+#: date.c:108
+#, c-format
+msgid "%lu minute ago"
+msgid_plural "%lu minutes ago"
+msgstr[0] "%lu 分钟之å‰"
+msgstr[1] "%lu 分钟之å‰"
+
+#: date.c:115
+#, c-format
+msgid "%lu hour ago"
+msgid_plural "%lu hours ago"
+msgstr[0] "%lu å°æ—¶ä¹‹å‰"
+msgstr[1] "%lu å°æ—¶ä¹‹å‰"
+
+#: date.c:122
+#, c-format
+msgid "%lu day ago"
+msgid_plural "%lu days ago"
+msgstr[0] "%lu 天之å‰"
+msgstr[1] "%lu 天之å‰"
+
+#: date.c:128
+#, c-format
+msgid "%lu week ago"
+msgid_plural "%lu weeks ago"
+msgstr[0] "%lu 周之å‰"
+msgstr[1] "%lu 周之å‰"
+
+#: date.c:135
+#, c-format
+msgid "%lu month ago"
+msgid_plural "%lu months ago"
+msgstr[0] "%lu 个月之å‰"
+msgstr[1] "%lu 个月之å‰"
+
+#: date.c:146
+#, c-format
+msgid "%lu year"
+msgid_plural "%lu years"
+msgstr[0] "%lu å¹´"
+msgstr[1] "%lu å¹´"
+
+#: date.c:149
+#, c-format
+msgid "%s, %lu month ago"
+msgid_plural "%s, %lu months ago"
+msgstr[0] "%s,%lu 个月之å‰"
+msgstr[1] "%s,%lu 个月之å‰"
+
+#: date.c:154 date.c:159
+#, c-format
+msgid "%lu year ago"
+msgid_plural "%lu years ago"
+msgstr[0] "%lu å¹´å‰"
+msgstr[1] "%lu å¹´å‰"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: diff.c:105
+#, c-format
+msgid " Failed to parse dirstat cut-off percentage '%.*s'\n"
+msgstr " æ— æ³•è§£æž dirstat 阈值 '%.*s'\n"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: diff.c:110
+#, c-format
+msgid " Unknown dirstat parameter '%.*s'\n"
+msgstr " 未知 dirstat å‚æ•° '%.*s'\n"
+
+#: diff.c:210
+#, c-format
+msgid ""
+"Found errors in 'diff.dirstat' config variable:\n"
+"%s"
+msgstr ""
+"å‘现é…ç½®å˜é‡ 'diff.dirstat' 中的错误:\n"
+"%s"
+
+#: diff.c:1400
+msgid " 0 files changed\n"
+msgstr " 0 个文件被修改\n"
+
+#: diff.c:1404
+#, c-format
+msgid " %d file changed"
+msgid_plural " %d files changed"
+msgstr[0] " %d 个文件被修改"
+msgstr[1] " %d 个文件被修改"
+
+#: diff.c:1421
+#, c-format
+msgid ", %d insertion(+)"
+msgid_plural ", %d insertions(+)"
+msgstr[0] ",æ’å…¥ %d è¡Œ(+)"
+msgstr[1] ",æ’å…¥ %d è¡Œ(+)"
+
+#: diff.c:1432
+#, c-format
+msgid ", %d deletion(-)"
+msgid_plural ", %d deletions(-)"
+msgstr[0] ",删除 %d 行(-)"
+msgstr[1] ",删除 %d 行(-)"
+
+#: diff.c:3478
+#, c-format
+msgid ""
+"Failed to parse --dirstat/-X option parameter:\n"
+"%s"
+msgstr ""
+"æ— æ³•è§£æž --dirstat/-X 选项的å‚数:\n"
+"%s"
+
+#: gpg-interface.c:59
+msgid "could not run gpg."
+msgstr "ä¸èƒ½æ‰§è¡Œ gpg。"
+
+#: gpg-interface.c:71
+msgid "gpg did not accept the data"
+msgstr "gpg 没有接å—æ•°æ®"
+
+#: gpg-interface.c:82
+msgid "gpg failed to sign the data"
+msgstr "gpg 无法为数æ®ç­¾å"
+
+#: grep.c:1320
+#, c-format
+msgid "'%s': unable to read %s"
+msgstr "'%s'ï¼šæ— æ³•è¯»å– %s"
+
+#: grep.c:1337
+#, c-format
+msgid "'%s': %s"
+msgstr "'%s':%s"
+
+#: grep.c:1348
+#, c-format
+msgid "'%s': short read %s"
+msgstr "'%s':读å–ä¸å®Œæ•´ %s"
+
+#: help.c:207
+#, c-format
+msgid "available git commands in '%s'"
+msgstr "在 '%s' 下å¯ç”¨çš„ git 命令"
+
+#: help.c:214
+msgid "git commands available from elsewhere on your $PATH"
+msgstr "在 $PATH 路径中的其他地方å¯ç”¨çš„ git 命令"
+
+#: help.c:270
+#, c-format
+msgid ""
+"'%s' appears to be a git command, but we were not\n"
+"able to execute it. Maybe git-%s is broken?"
+msgstr ""
+"'%s' åƒæ˜¯ä¸€ä¸ª git 命令,但å´æ— æ³•è¿è¡Œã€‚\n"
+"å¯èƒ½æ˜¯ git-%s å—æŸï¼Ÿ"
+
+#: help.c:327
+msgid "Uh oh. Your system reports no Git commands at all."
+msgstr "唉呀,您的系统中未å‘现 Git 命令。"
+
+#: help.c:349
+#, c-format
+msgid ""
+"WARNING: You called a Git command named '%s', which does not exist.\n"
+"Continuing under the assumption that you meant '%s'"
+msgstr ""
+"警告:您è¿è¡Œä¸€ä¸ªä¸å­˜åœ¨çš„ Git 命令 '%s'。继续执行å‡å®šæ‚¨è¦è¦è¿è¡Œçš„\n"
+"是 '%s'"
+
+#: help.c:354
+#, c-format
+msgid "in %0.1f seconds automatically..."
+msgstr "在 %0.1f 秒钟åŽè‡ªåŠ¨è¿è¡Œ..."
+
+#: help.c:361
+#, c-format
+msgid "git: '%s' is not a git command. See 'git --help'."
+msgstr "git:'%s' ä¸æ˜¯ä¸€ä¸ª git 命令。å‚è§ 'git --help'。"
+
+#: help.c:365
+msgid ""
+"\n"
+"Did you mean this?"
+msgid_plural ""
+"\n"
+"Did you mean one of these?"
+msgstr[0] ""
+"\n"
+"您指的是这个么?"
+msgstr[1] ""
+"\n"
+"您指的是这些其中一个么?"
+
+#: parse-options.c:493
+msgid "..."
+msgstr "..."
+
+#: parse-options.c:511
+#, c-format
+msgid "usage: %s"
+msgstr "用法:%s"
+
+#. TRANSLATORS: the colon here should align with the
+#. one in "usage: %s" translation
+#: parse-options.c:515
+#, c-format
+msgid " or: %s"
+msgstr " 或:%s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: parse-options.c:518
+#, c-format
+msgid " %s"
+msgstr " %s"
+
+#: remote.c:1629
+#, c-format
+msgid "Your branch is ahead of '%s' by %d commit.\n"
+msgid_plural "Your branch is ahead of '%s' by %d commits.\n"
+msgstr[0] "您的分支领先 '%s' å…± %d 个æ交。\n"
+msgstr[1] "您的分支领先 '%s' å…± %d 个æ交。\n"
+
+#: remote.c:1635
+#, c-format
+msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n"
+msgid_plural ""
+"Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n"
+msgstr[0] "您的分支è½åŽ '%s' å…± %d 个æ交,并且å¯ä»¥å¿«è¿›ã€‚\n"
+msgstr[1] "您的分支è½åŽ '%s' å…± %d 个æ交,并且å¯ä»¥å¿«è¿›ã€‚\n"
+
+#: remote.c:1643
+#, c-format
+msgid ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commit each, respectively.\n"
+msgid_plural ""
+"Your branch and '%s' have diverged,\n"
+"and have %d and %d different commits each, respectively.\n"
+msgstr[0] ""
+"您的分支和 '%s' 出现了å离,\n"
+"并且å„自分别有 %d å’Œ %d 处ä¸åŒçš„æ交。\n"
+msgstr[1] ""
+"您的分支和 '%s' 出现了å离,\n"
+"并且å„自分别有 %d å’Œ %d 处ä¸åŒçš„æ交。\n"
+
+#: sequencer.c:121 builtin/merge.c:865 builtin/merge.c:978
+#: builtin/merge.c:1088 builtin/merge.c:1098
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr "ä¸èƒ½ä¸ºå†™å…¥æ‰“å¼€ '%s'"
+
+#: sequencer.c:123 builtin/merge.c:333 builtin/merge.c:868
+#: builtin/merge.c:1090 builtin/merge.c:1103
+#, c-format
+msgid "Could not write to '%s'"
+msgstr "ä¸èƒ½å†™å…¥ '%s'"
+
+#: sequencer.c:144
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'"
+msgstr ""
+"冲çªè§£å†³å®Œæ¯•åŽï¼Œç”¨ 'git add <paths>' 或 'git rm <paths>'\n"
+"命令标记修正åŽçš„文件"
+
+#: sequencer.c:147
+msgid ""
+"after resolving the conflicts, mark the corrected paths\n"
+"with 'git add <paths>' or 'git rm <paths>'\n"
+"and commit the result with 'git commit'"
+msgstr ""
+"冲çªè§£å†³å®Œæ¯•åŽï¼Œç”¨ 'git add <paths>' 或 'git rm <paths>'\n"
+"对修正åŽçš„文件åšæ ‡è®°ï¼Œç„¶åŽç”¨ 'git commit' æ交"
+
+#: sequencer.c:160 sequencer.c:758 sequencer.c:841
+#, c-format
+msgid "Could not write to %s"
+msgstr "ä¸èƒ½å†™å…¥ %s"
+
+#: sequencer.c:163
+#, c-format
+msgid "Error wrapping up %s"
+msgstr "错误收尾 %s"
+
+#: sequencer.c:178
+msgid "Your local changes would be overwritten by cherry-pick."
+msgstr "您的本地修改将被拣选æ“作覆盖。"
+
+#: sequencer.c:180
+msgid "Your local changes would be overwritten by revert."
+msgstr "您的本地修改将被还原æ“作覆盖。"
+
+#: sequencer.c:183
+msgid "Commit your changes or stash them to proceed."
+msgstr "æ交您的修改或ä¿å­˜è¿›åº¦åŽå†ç»§ç»­ã€‚"
+
+#. TRANSLATORS: %s will be "revert" or "cherry-pick"
+#: sequencer.c:233
+#, c-format
+msgid "%s: Unable to write new index file"
+msgstr "%s:无法写入新索引文件"
+
+#: sequencer.c:261
+msgid "Could not resolve HEAD commit\n"
+msgstr "ä¸èƒ½è§£æž HEAD æ交\n"
+
+#: sequencer.c:282
+msgid "Unable to update cache tree\n"
+msgstr "ä¸èƒ½æ›´æ–°ç¼“å­˜\n"
+
+#: sequencer.c:324
+#, c-format
+msgid "Could not parse commit %s\n"
+msgstr "ä¸èƒ½è§£æžæ交 %s\n"
+
+#: sequencer.c:329
+#, c-format
+msgid "Could not parse parent commit %s\n"
+msgstr "ä¸èƒ½è§£æžçˆ¶æ交 %s\n"
+
+#: sequencer.c:395
+msgid "Your index file is unmerged."
+msgstr "您的索引文件未完æˆåˆå¹¶ã€‚"
+
+#: sequencer.c:398
+msgid "You do not have a valid HEAD"
+msgstr "您没有一个有效的 HEAD"
+
+#: sequencer.c:413
+#, c-format
+msgid "Commit %s is a merge but no -m option was given."
+msgstr "æ交 %s 是一个åˆå¹¶æ交但未æä¾› -m 选项。"
+
+#: sequencer.c:421
+#, c-format
+msgid "Commit %s does not have parent %d"
+msgstr "æ交 %s 没有父æ交 %d"
+
+#: sequencer.c:425
+#, c-format
+msgid "Mainline was specified but commit %s is not a merge."
+msgstr "指定了主线但æ交 %s ä¸æ˜¯ä¸€ä¸ªåˆå¹¶ã€‚"
+
+#. TRANSLATORS: The first %s will be "revert" or
+#. "cherry-pick", the second %s a SHA1
+#: sequencer.c:436
+#, c-format
+msgid "%s: cannot parse parent commit %s"
+msgstr "%s:ä¸èƒ½è§£æžçˆ¶æ交 %s"
+
+#: sequencer.c:440
+#, c-format
+msgid "Cannot get commit message for %s"
+msgstr "ä¸èƒ½å¾—到 %s çš„æ交说明"
+
+#: sequencer.c:524
+#, c-format
+msgid "could not revert %s... %s"
+msgstr "ä¸èƒ½è¿˜åŽŸ %s... %s"
+
+#: sequencer.c:525
+#, c-format
+msgid "could not apply %s... %s"
+msgstr "ä¸èƒ½åº”用 %s... %s"
+
+#: sequencer.c:553
+msgid "empty commit set passed"
+msgstr "æ供了空的æ交集"
+
+#: sequencer.c:561
+#, c-format
+msgid "git %s: failed to read the index"
+msgstr "git %s:无法读å–索引"
+
+#: sequencer.c:566
+#, c-format
+msgid "git %s: failed to refresh the index"
+msgstr "git %s:无法刷新索引"
+
+#: sequencer.c:624
+#, c-format
+msgid "Cannot %s during a %s"
+msgstr "无法 %s 在一个 %s 过程中"
+
+#: sequencer.c:646
+#, c-format
+msgid "Could not parse line %d."
+msgstr "ä¸èƒ½è§£æžç¬¬ %d 行。"
+
+#: sequencer.c:651
+msgid "No commits parsed."
+msgstr "没有æ交被解æžã€‚"
+
+#: sequencer.c:664
+#, c-format
+msgid "Could not open %s"
+msgstr "ä¸èƒ½æ‰“å¼€ %s"
+
+#: sequencer.c:668
+#, c-format
+msgid "Could not read %s."
+msgstr "ä¸èƒ½è¯»å– %s。"
+
+#: sequencer.c:675
+#, c-format
+msgid "Unusable instruction sheet: %s"
+msgstr "无用的指令表å•ï¼š%s"
+
+#: sequencer.c:703
+#, c-format
+msgid "Invalid key: %s"
+msgstr "无效键å:%s"
+
+#: sequencer.c:706
+#, c-format
+msgid "Invalid value for %s: %s"
+msgstr "%s 的值无效:%s"
+
+#: sequencer.c:718
+#, c-format
+msgid "Malformed options sheet: %s"
+msgstr "éžæ³•çš„选项表å•ï¼š%s"
+
+#: sequencer.c:739
+msgid "a cherry-pick or revert is already in progress"
+msgstr "一个拣选或还原æ“作已在进行"
+
+#: sequencer.c:740
+msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
+msgstr "å°è¯• \"git cherry-pick (--continue | --quit | --abort)\""
+
+#: sequencer.c:744
+#, c-format
+msgid "Could not create sequencer directory %s"
+msgstr "ä¸èƒ½åˆ›å»ºåºåˆ—目录 %s"
+
+#: sequencer.c:760 sequencer.c:845
+#, c-format
+msgid "Error wrapping up %s."
+msgstr "错误收尾 %s。"
+
+#: sequencer.c:779 sequencer.c:913
+msgid "no cherry-pick or revert in progress"
+msgstr "没有拣选或还原æ“作在进行"
+
+#: sequencer.c:781
+msgid "cannot resolve HEAD"
+msgstr "ä¸èƒ½è§£æž HEAD"
+
+#: sequencer.c:783
+msgid "cannot abort from a branch yet to be born"
+msgstr "ä¸èƒ½ä»Žå°šæœªå»ºç«‹çš„分支终止"
+
+#: sequencer.c:805 builtin/apply.c:3697
+#, c-format
+msgid "cannot open %s: %s"
+msgstr "ä¸èƒ½æ‰“å¼€ %s:%s"
+
+#: sequencer.c:808
+#, c-format
+msgid "cannot read %s: %s"
+msgstr "ä¸èƒ½è¯»å– %s:%s"
+
+#: sequencer.c:809
+msgid "unexpected end of file"
+msgstr "未预期的文件结æŸ"
+
+#: sequencer.c:815
+#, c-format
+msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
+msgstr "ä¿å­˜æ‹£é€‰æ交å‰çš„ HEAD 文件 '%s' æŸå"
+
+#: sequencer.c:838
+#, c-format
+msgid "Could not format %s."
+msgstr "ä¸èƒ½æ ¼å¼åŒ– %s。"
+
+#: sequencer.c:1000
+msgid "Can't revert as initial commit"
+msgstr "ä¸èƒ½ä½œä¸ºåˆå§‹æ交还原"
+
+#: sequencer.c:1001
+msgid "Can't cherry-pick into empty head"
+msgstr "ä¸èƒ½æ‹£é€‰åˆ°ç©ºåˆ†æ”¯"
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "HEAD 没有指å‘一个分支"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "没有此分支:'%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "尚未给分支 '%s' 设置上游"
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr "上游分支 '%s' 没有存储为一个远程跟踪分支"
+
+#: wrapper.c:413
+#, c-format
+msgid "unable to look up current user in the passwd file: %s"
+msgstr "无法在 passwd 文件中查询到当å‰ç”¨æˆ·ï¼š%s"
+
+#: wrapper.c:414
+msgid "no such user"
+msgstr "无此用户"
+
+#: wt-status.c:135
+msgid "Unmerged paths:"
+msgstr "未åˆå¹¶çš„路径:"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: wt-status.c:141 wt-status.c:158
+#, c-format
+msgid " (use \"git reset %s <file>...\" to unstage)"
+msgstr " (使用 \"git reset %s <file>...\" 撤出暂存区)"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: wt-status.c:143 wt-status.c:160
+msgid " (use \"git rm --cached <file>...\" to unstage)"
+msgstr " (使用 \"git rm --cached <file>...\" 撤出暂存区)"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: wt-status.c:144
+msgid " (use \"git add/rm <file>...\" as appropriate to mark resolution)"
+msgstr " (酌情使用 \"git add/rm <file>...\" 标记解决方案)"
+
+#: wt-status.c:152
+msgid "Changes to be committed:"
+msgstr "è¦æ交的å˜æ›´ï¼š"
+
+#: wt-status.c:170
+msgid "Changes not staged for commit:"
+msgstr "尚未暂存以备æ交的å˜æ›´ï¼š"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: wt-status.c:174
+msgid " (use \"git add <file>...\" to update what will be committed)"
+msgstr " (使用 \"git add <file>...\" æ›´æ–°è¦æ交的内容)"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: wt-status.c:176
+msgid " (use \"git add/rm <file>...\" to update what will be committed)"
+msgstr " (使用 \"git add/rm <file>...\" æ›´æ–°è¦æ交的内容)"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: wt-status.c:177
+msgid ""
+" (use \"git checkout -- <file>...\" to discard changes in working directory)"
+msgstr " (使用 \"git checkout -- <file>...\" 丢弃工作区的改动)"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: wt-status.c:179
+msgid " (commit or discard the untracked or modified content in submodules)"
+msgstr " (æ交或丢弃å­æ¨¡ç»„中未跟踪或修改的内容)"
+
+#: wt-status.c:188
+#, c-format
+msgid "%s files:"
+msgstr "%s文件:"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: wt-status.c:191
+#, c-format
+msgid " (use \"git %s <file>...\" to include in what will be committed)"
+msgstr " (使用 \"git %s <file>...\" 以包å«è¦æ交的内容)"
+
+#: wt-status.c:208
+msgid "bug"
+msgstr "bug"
+
+#: wt-status.c:213
+msgid "both deleted:"
+msgstr "åŒæ–¹åˆ é™¤ï¼š"
+
+#: wt-status.c:214
+msgid "added by us:"
+msgstr "由我们添加:"
+
+#: wt-status.c:215
+msgid "deleted by them:"
+msgstr "由他们删除:"
+
+#: wt-status.c:216
+msgid "added by them:"
+msgstr "由他们添加:"
+
+#: wt-status.c:217
+msgid "deleted by us:"
+msgstr "由我们删除:"
+
+#: wt-status.c:218
+msgid "both added:"
+msgstr "åŒæ–¹æ·»åŠ ï¼š"
+
+#: wt-status.c:219
+msgid "both modified:"
+msgstr "åŒæ–¹ä¿®æ”¹ï¼š"
+
+# 译者:末尾两个字节å¯èƒ½è¢«åˆ å‡ï¼Œå¦‚果翻译为中文标点会出现åŠä¸ªæ±‰å­—
+#: wt-status.c:249
+msgid "new commits, "
+msgstr "æ–°æ交, "
+
+# 译者:末尾两个字节å¯èƒ½è¢«åˆ å‡ï¼Œå¦‚果翻译为中文标点会出现åŠä¸ªæ±‰å­—
+#: wt-status.c:251
+msgid "modified content, "
+msgstr "修改的内容, "
+
+# 译者:末尾两个字节å¯èƒ½è¢«åˆ å‡ï¼Œå¦‚果翻译为中文标点会出现åŠä¸ªæ±‰å­—
+#: wt-status.c:253
+msgid "untracked content, "
+msgstr "未跟踪的内容, "
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: wt-status.c:267
+#, c-format
+msgid "new file: %s"
+msgstr "新文件: %s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: wt-status.c:270
+#, c-format
+msgid "copied: %s -> %s"
+msgstr "æ‹·è´ï¼š %s -> %s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: wt-status.c:273
+#, c-format
+msgid "deleted: %s"
+msgstr "删除: %s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: wt-status.c:276
+#, c-format
+msgid "modified: %s"
+msgstr "修改: %s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: wt-status.c:279
+#, c-format
+msgid "renamed: %s -> %s"
+msgstr "é‡å‘½å: %s -> %s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: wt-status.c:282
+#, c-format
+msgid "typechange: %s"
+msgstr "类型å˜æ›´ï¼š %s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: wt-status.c:285
+#, c-format
+msgid "unknown: %s"
+msgstr "未知: %s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: wt-status.c:288
+#, c-format
+msgid "unmerged: %s"
+msgstr "未åˆå¹¶ï¼š %s"
+
+#: wt-status.c:291
+#, c-format
+msgid "bug: unhandled diff status %c"
+msgstr "bug:未处ç†çš„å·®å¼‚çŠ¶æ€ %c"
+
+#: wt-status.c:737
+msgid "On branch "
+msgstr "ä½äºŽåˆ†æ”¯ "
+
+#: wt-status.c:744
+msgid "Not currently on any branch."
+msgstr "当å‰ä¸åœ¨ä»»ä½•åˆ†æ”¯ä¸Šã€‚"
+
+#: wt-status.c:755
+msgid "Initial commit"
+msgstr "åˆå§‹æ交"
+
+#: wt-status.c:769
+msgid "Untracked"
+msgstr "未跟踪的"
+
+#: wt-status.c:771
+msgid "Ignored"
+msgstr "忽略的"
+
+#: wt-status.c:773
+#, c-format
+msgid "Untracked files not listed%s"
+msgstr "未跟踪的文件没有列出%s"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: wt-status.c:775
+msgid " (use -u option to show untracked files)"
+msgstr "(使用 -u å‚数显示未跟踪的文件)"
+
+#: wt-status.c:781
+msgid "No changes"
+msgstr "没有修改"
+
+#: wt-status.c:785
+#, c-format
+msgid "no changes added to commit%s\n"
+msgstr "修改尚未加入æ交%s\n"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: wt-status.c:787
+msgid " (use \"git add\" and/or \"git commit -a\")"
+msgstr "(使用 \"git add\" 和/或 \"git commit -a\")"
+
+#: wt-status.c:789
+#, c-format
+msgid "nothing added to commit but untracked files present%s\n"
+msgstr "空æ交但存在未跟踪文件%s\n"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: wt-status.c:791
+msgid " (use \"git add\" to track)"
+msgstr "(使用 \"git add\" 建立跟踪)"
+
+#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#, c-format
+msgid "nothing to commit%s\n"
+msgstr "æ— é¡»æ交%s\n"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: wt-status.c:794
+msgid " (create/copy files and use \"git add\" to track)"
+msgstr "(新建/æ‹·è´çš„文件使用 \"git add\" 建立跟踪)"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: wt-status.c:797
+msgid " (use -u to show untracked files)"
+msgstr "(使用 -u 显示未跟踪文件)"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: wt-status.c:800
+msgid " (working directory clean)"
+msgstr "(干净的工作区)"
+
+#: wt-status.c:908
+msgid "HEAD (no branch)"
+msgstr "HEAD(éžåˆ†æ”¯ï¼‰"
+
+# 译者:注æ„ä¿æŒå¥å°¾ç©ºæ ¼
+#: wt-status.c:914
+msgid "Initial commit on "
+msgstr "åˆå§‹æ交于 "
+
+# 译者:注æ„ä¿æŒå¥å°¾ç©ºæ ¼
+#: wt-status.c:929
+msgid "behind "
+msgstr "è½åŽ "
+
+# 译者:注æ„ä¿æŒå¥å°¾ç©ºæ ¼
+#: wt-status.c:932 wt-status.c:935
+msgid "ahead "
+msgstr "领先 "
+
+# 译者:注æ„ä¿æŒå¥å°¾ç©ºæ ¼
+#: wt-status.c:937
+msgid ", behind "
+msgstr ",è½åŽ "
+
+#: builtin/add.c:62
+#, c-format
+msgid "unexpected diff status %c"
+msgstr "æ„å¤–çš„å·®å¼‚çŠ¶æ€ %c"
+
+#: builtin/add.c:67 builtin/commit.c:226
+msgid "updating files failed"
+msgstr "更新文件失败"
+
+#: builtin/add.c:77
+#, c-format
+msgid "remove '%s'\n"
+msgstr "删除 '%s'\n"
+
+#: builtin/add.c:176
+#, c-format
+msgid "Path '%s' is in submodule '%.*s'"
+msgstr "路径 '%s' 属于模组 '%.*s'"
+
+#: builtin/add.c:192
+msgid "Unstaged changes after refreshing the index:"
+msgstr "刷新索引之åŽå°šæœªè¢«æš‚存的å˜æ›´ï¼š"
+
+#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#, c-format
+msgid "pathspec '%s' did not match any files"
+msgstr "路径 '%s' 未匹é…任何文件"
+
+#: builtin/add.c:209
+#, c-format
+msgid "'%s' is beyond a symbolic link"
+msgstr "'%s' ä½äºŽç¬¦å·é“¾æŽ¥ä¸­"
+
+#: builtin/add.c:276
+msgid "Could not read the index"
+msgstr "ä¸èƒ½è¯»å–索引"
+
+#: builtin/add.c:286
+#, c-format
+msgid "Could not open '%s' for writing."
+msgstr "ä¸èƒ½ä¸ºå†™å…¥æ‰“å¼€ '%s'。"
+
+#: builtin/add.c:290
+msgid "Could not write patch"
+msgstr "ä¸èƒ½å†™è¡¥ä¸"
+
+#: builtin/add.c:295
+#, c-format
+msgid "Could not stat '%s'"
+msgstr "ä¸èƒ½æŸ¥çœ‹æ–‡ä»¶çŠ¶æ€ '%s'"
+
+#: builtin/add.c:297
+msgid "Empty patch. Aborted."
+msgstr "空补ä¸ã€‚异常终止。"
+
+#: builtin/add.c:303
+#, c-format
+msgid "Could not apply '%s'"
+msgstr "ä¸èƒ½åº”用 '%s'"
+
+#: builtin/add.c:312
+msgid "The following paths are ignored by one of your .gitignore files:\n"
+msgstr "下列路径被您的一个 .gitignore 文件所忽略:\n"
+
+#: builtin/add.c:352
+#, c-format
+msgid "Use -f if you really want to add them.\n"
+msgstr "使用 -f å‚数如果您确实è¦æ·»åŠ å®ƒä»¬ã€‚\n"
+
+#: builtin/add.c:353
+msgid "no files added"
+msgstr "没有文件被添加"
+
+#: builtin/add.c:359
+msgid "adding files failed"
+msgstr "添加文件失败"
+
+#: builtin/add.c:391
+msgid "-A and -u are mutually incompatible"
+msgstr "-A 和 -u 选项互斥"
+
+#: builtin/add.c:393
+msgid "Option --ignore-missing can only be used together with --dry-run"
+msgstr "选项 --ignore-missing åªèƒ½å’Œ --dry-run 共用"
+
+#: builtin/add.c:413
+#, c-format
+msgid "Nothing specified, nothing added.\n"
+msgstr "没有指定文件,也没有文件被添加。\n"
+
+#: builtin/add.c:414
+#, c-format
+msgid "Maybe you wanted to say 'git add .'?\n"
+msgstr "也许您想è¦æ‰§è¡Œ 'git add .'?\n"
+
+#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:286 builtin/mv.c:82
+#: builtin/rm.c:162
+msgid "index file corrupt"
+msgstr "索引文件æŸå"
+
+#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+msgid "Unable to write new index file"
+msgstr "无法写入新索引文件"
+
+#: builtin/apply.c:53
+msgid "git apply [options] [<patch>...]"
+msgstr "git apply [选项] [<è¡¥ä¸>...]"
+
+#: builtin/apply.c:106
+#, c-format
+msgid "unrecognized whitespace option '%s'"
+msgstr "未能识别的空白字符选项 '%s'"
+
+#: builtin/apply.c:121
+#, c-format
+msgid "unrecognized whitespace ignore option '%s'"
+msgstr "未能识别的空白字符忽略选项 '%s'"
+
+#: builtin/apply.c:815
+#, c-format
+msgid "Cannot prepare timestamp regexp %s"
+msgstr "æ— æ³•å‡†å¤‡æ—¶é—´æˆ³æ­£åˆ™è¡¨è¾¾å¼ %s"
+
+#: builtin/apply.c:824
+#, c-format
+msgid "regexec returned %d for input: %s"
+msgstr "regexec 返回 %d,输入为:%s"
+
+#: builtin/apply.c:905
+#, c-format
+msgid "unable to find filename in patch at line %d"
+msgstr "ä¸èƒ½åœ¨è¡¥ä¸çš„第 %d 行找到文件å"
+
+#: builtin/apply.c:937
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d"
+msgstr "git apply:错误的 git-diff - 期望 /dev/null,但在第 %2$d 行得到 %1$s"
+
+#: builtin/apply.c:941
+#, c-format
+msgid "git apply: bad git-diff - inconsistent new filename on line %d"
+msgstr "git apply:错误的 git-diff - 第 %d 行上新文件åä¸ä¸€è‡´"
+
+#: builtin/apply.c:942
+#, c-format
+msgid "git apply: bad git-diff - inconsistent old filename on line %d"
+msgstr "git apply:错误的 git-diff - 第 %d 行上旧文件åä¸ä¸€è‡´"
+
+#: builtin/apply.c:949
+#, c-format
+msgid "git apply: bad git-diff - expected /dev/null on line %d"
+msgstr "git apply:错误的 git-diff - 期望 /dev/null 于第 %d 行"
+
+#: builtin/apply.c:1394
+#, c-format
+msgid "recount: unexpected line: %.*s"
+msgstr "recount:æ„外的行:%.*s"
+
+#: builtin/apply.c:1451
+#, c-format
+msgid "patch fragment without header at line %d: %.*s"
+msgstr "第 %d 行的补ä¸ç‰‡æ®µæ²¡æœ‰å¤´ä¿¡æ¯ï¼š%.*s"
+
+#: builtin/apply.c:1468
+#, c-format
+msgid ""
+"git diff header lacks filename information when removing %d leading pathname "
+"component (line %d)"
+msgid_plural ""
+"git diff header lacks filename information when removing %d leading pathname "
+"components (line %d)"
+msgstr[0] "当移除 %d 个å‰å¯¼è·¯å¾„åŽ git diff 头缺ä¹æ–‡ä»¶åä¿¡æ¯ï¼ˆç¬¬ %d 行)"
+msgstr[1] "当移除 %d 个å‰å¯¼è·¯å¾„åŽ git diff 头缺ä¹æ–‡ä»¶åä¿¡æ¯ï¼ˆç¬¬ %d 行)"
+
+#: builtin/apply.c:1628
+msgid "new file depends on old contents"
+msgstr "新文件ä¾èµ–旧内容"
+
+#: builtin/apply.c:1630
+msgid "deleted file still has contents"
+msgstr "删除的文件ä»æœ‰å†…容"
+
+#: builtin/apply.c:1656
+#, c-format
+msgid "corrupt patch at line %d"
+msgstr "è¡¥ä¸æŸåä½äºŽç¬¬ %d è¡Œ"
+
+#: builtin/apply.c:1692
+#, c-format
+msgid "new file %s depends on old contents"
+msgstr "新文件 %s ä¾èµ–旧内容"
+
+#: builtin/apply.c:1694
+#, c-format
+msgid "deleted file %s still has contents"
+msgstr "删除的文件 %s ä»æœ‰å†…容"
+
+#: builtin/apply.c:1697
+#, c-format
+msgid "** warning: file %s becomes empty but is not deleted"
+msgstr "** 警告:文件 %s æˆä¸ºç©ºæ–‡ä»¶ä½†å¹¶æœªåˆ é™¤"
+
+#: builtin/apply.c:1843
+#, c-format
+msgid "corrupt binary patch at line %d: %.*s"
+msgstr "二进制补ä¸åœ¨ç¬¬ %d è¡ŒæŸå:%.*s"
+
+#. there has to be one hunk (forward hunk)
+#: builtin/apply.c:1872
+#, c-format
+msgid "unrecognized binary patch at line %d"
+msgstr "未能识别的二进制补ä¸ä½äºŽç¬¬ %d è¡Œ"
+
+#: builtin/apply.c:1958
+#, c-format
+msgid "patch with only garbage at line %d"
+msgstr "è¡¥ä¸æ–‡ä»¶çš„第 %d è¡Œåªæœ‰åžƒåœ¾æ•°æ®"
+
+#: builtin/apply.c:2048
+#, c-format
+msgid "unable to read symlink %s"
+msgstr "无法读å–符å·é“¾æŽ¥ %s"
+
+#: builtin/apply.c:2052
+#, c-format
+msgid "unable to open or read %s"
+msgstr "ä¸èƒ½æ‰“å¼€æˆ–è¯»å– %s"
+
+#: builtin/apply.c:2123
+msgid "oops"
+msgstr "å“Žå“Ÿ"
+
+#: builtin/apply.c:2645
+#, c-format
+msgid "invalid start of line: '%c'"
+msgstr "无效的行首字符:'%c'"
+
+#: builtin/apply.c:2763
+#, c-format
+msgid "Hunk #%d succeeded at %d (offset %d line)."
+msgid_plural "Hunk #%d succeeded at %d (offset %d lines)."
+msgstr[0] "å— #%d æˆåŠŸåº”用于 %d (å移 %d 行)"
+msgstr[1] "å— #%d æˆåŠŸåº”用于 %d (å移 %d 行)"
+
+#: builtin/apply.c:2775
+#, c-format
+msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
+msgstr "上下文å‡å°‘到(%ld/%ld)以在第 %d 行应用补ä¸ç‰‡æ®µ"
+
+#: builtin/apply.c:2781
+#, c-format
+msgid ""
+"while searching for:\n"
+"%.*s"
+msgstr ""
+"当查询:\n"
+"%.*s"
+
+#: builtin/apply.c:2800
+#, c-format
+msgid "missing binary patch data for '%s'"
+msgstr "缺失 '%s' 的二进制补ä¸æ•°æ®"
+
+#: builtin/apply.c:2903
+#, c-format
+msgid "binary patch does not apply to '%s'"
+msgstr "二进制补ä¸æœªåº”用到 '%s'"
+
+#: builtin/apply.c:2909
+#, c-format
+msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)"
+msgstr "到 '%s' 的二进制补ä¸äº§ç”Ÿäº†ä¸æ­£ç¡®çš„结果(预期 %s,得到 %s)"
+
+#: builtin/apply.c:2930
+#, c-format
+msgid "patch failed: %s:%ld"
+msgstr "打补ä¸å¤±è´¥ï¼š%s:%ld"
+
+#: builtin/apply.c:3045
+#, c-format
+msgid "patch %s has been renamed/deleted"
+msgstr "è¡¥ä¸ %s å·²ç»è¢«é‡å‘½å/删除"
+
+#: builtin/apply.c:3052 builtin/apply.c:3069
+#, c-format
+msgid "read of %s failed"
+msgstr "è¯»å– %s 失败"
+
+#: builtin/apply.c:3084
+msgid "removal patch leaves file contents"
+msgstr "移除补ä¸ä»ç•™ä¸‹äº†æ–‡ä»¶å†…容"
+
+#: builtin/apply.c:3105
+#, c-format
+msgid "%s: already exists in working directory"
+msgstr "%s:已ç»å­˜åœ¨äºŽå·¥ä½œåŒºä¸­"
+
+#: builtin/apply.c:3143
+#, c-format
+msgid "%s: has been deleted/renamed"
+msgstr "%s:已ç»è¢«åˆ é™¤/é‡å‘½å"
+
+#: builtin/apply.c:3148 builtin/apply.c:3179
+#, c-format
+msgid "%s: %s"
+msgstr "%s:%s"
+
+#: builtin/apply.c:3159
+#, c-format
+msgid "%s: does not exist in index"
+msgstr "%s:ä¸å­˜åœ¨äºŽç´¢å¼•ä¸­"
+
+#: builtin/apply.c:3173
+#, c-format
+msgid "%s: does not match index"
+msgstr "%s:和索引ä¸åŒ¹é…"
+
+#: builtin/apply.c:3190
+#, c-format
+msgid "%s: wrong type"
+msgstr "%s:错误类型"
+
+#: builtin/apply.c:3192
+#, c-format
+msgid "%s has type %o, expected %o"
+msgstr "%s 的类型是 %o,预期是 %o"
+
+#: builtin/apply.c:3247
+#, c-format
+msgid "%s: already exists in index"
+msgstr "%s:已ç»å­˜åœ¨äºŽç´¢å¼•ä¸­"
+
+#: builtin/apply.c:3267
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o)"
+msgstr "%2$s 的新模å¼ï¼ˆ%1$o)和旧模å¼ï¼ˆ%3$o)ä¸åŒ¹é…"
+
+#: builtin/apply.c:3272
+#, c-format
+msgid "new mode (%o) of %s does not match old mode (%o) of %s"
+msgstr "%2$s 的新模å¼ï¼ˆ%1$o)和 %4$s 的旧模å¼ï¼ˆ%3$o)ä¸åŒ¹é…"
+
+#: builtin/apply.c:3280
+#, c-format
+msgid "%s: patch does not apply"
+msgstr "%s:补ä¸æœªåº”用"
+
+#: builtin/apply.c:3293
+#, c-format
+msgid "Checking patch %s..."
+msgstr "æ£€æŸ¥è¡¥ä¸ %s..."
+
+#: builtin/apply.c:3348 builtin/checkout.c:212 builtin/reset.c:158
+#, c-format
+msgid "make_cache_entry failed for path '%s'"
+msgstr "对路径 '%s' çš„ make_cache_entry æ“作失败"
+
+#: builtin/apply.c:3491
+#, c-format
+msgid "unable to remove %s from index"
+msgstr "ä¸èƒ½ä»Žç´¢å¼•ä¸­ç§»é™¤ %s"
+
+#: builtin/apply.c:3518
+#, c-format
+msgid "corrupt patch for subproject %s"
+msgstr "å­é¡¹ç›® %s æŸåçš„è¡¥ä¸"
+
+#: builtin/apply.c:3522
+#, c-format
+msgid "unable to stat newly created file '%s'"
+msgstr "ä¸èƒ½æžšä¸¾æ–°å»ºæ–‡ä»¶ '%s' 的状æ€"
+
+#: builtin/apply.c:3527
+#, c-format
+msgid "unable to create backing store for newly created file %s"
+msgstr "ä¸èƒ½ä¸ºæ–°å»ºæ–‡ä»¶ %s 创建åŽç«¯å­˜å‚¨"
+
+#: builtin/apply.c:3530
+#, c-format
+msgid "unable to add cache entry for %s"
+msgstr "无法为 %s 添加缓存æ¡ç›®"
+
+#: builtin/apply.c:3563
+#, c-format
+msgid "closing file '%s'"
+msgstr "关闭文件 '%s'"
+
+#: builtin/apply.c:3612
+#, c-format
+msgid "unable to write file '%s' mode %o"
+msgstr "ä¸èƒ½å†™æ–‡ä»¶ '%s' æƒé™ %o"
+
+#: builtin/apply.c:3668
+#, c-format
+msgid "Applied patch %s cleanly."
+msgstr "æˆåŠŸåº”ç”¨è¡¥ä¸ %s。"
+
+#: builtin/apply.c:3676
+msgid "internal error"
+msgstr "内部错误"
+
+#. Say this even without --verbose
+#: builtin/apply.c:3679
+#, c-format
+msgid "Applying patch %%s with %d reject..."
+msgid_plural "Applying patch %%s with %d rejects..."
+msgstr[0] "åº”ç”¨è¡¥ä¸ %%s æ—¶ %d 个被拒ç»..."
+msgstr[1] "åº”ç”¨è¡¥ä¸ %%s æ—¶ %d 个被拒ç»..."
+
+#: builtin/apply.c:3689
+#, c-format
+msgid "truncating .rej filename to %.*s.rej"
+msgstr "截短 .rej 文件å为 %.*s.rej"
+
+#: builtin/apply.c:3710
+#, c-format
+msgid "Hunk #%d applied cleanly."
+msgstr "第 #%d 个片段æˆåŠŸåº”用。"
+
+#: builtin/apply.c:3713
+#, c-format
+msgid "Rejected hunk #%d."
+msgstr "æ‹’ç»ç¬¬ #%d 个片段。"
+
+#: builtin/apply.c:3844
+msgid "unrecognized input"
+msgstr "未能识别的输入"
+
+#: builtin/apply.c:3855
+msgid "unable to read index file"
+msgstr "无法读å–索引文件"
+
+#: builtin/apply.c:3970 builtin/apply.c:3973
+msgid "path"
+msgstr "路径"
+
+#: builtin/apply.c:3971
+msgid "don't apply changes matching the given path"
+msgstr "ä¸è¦åº”用与给出路径å‘匹é…çš„å˜æ›´"
+
+#: builtin/apply.c:3974
+msgid "apply changes matching the given path"
+msgstr "应用与给出路径å‘匹é…çš„å˜æ›´"
+
+#: builtin/apply.c:3976
+msgid "num"
+msgstr "æ•°å­—"
+
+#: builtin/apply.c:3977
+msgid "remove <num> leading slashes from traditional diff paths"
+msgstr "从传统的 diff 路径中移除 <æ•°å­—> 个å‰å¯¼è·¯å¾„"
+
+#: builtin/apply.c:3980
+msgid "ignore additions made by the patch"
+msgstr "忽略补ä¸ä¸­çš„添加的文件"
+
+#: builtin/apply.c:3982
+msgid "instead of applying the patch, output diffstat for the input"
+msgstr "ä¸åº”用补ä¸ï¼Œè€Œæ˜¯æ˜¾ç¤ºè¾“入的差异统计(diffstat)"
+
+#: builtin/apply.c:3986
+msgid "shows number of added and deleted lines in decimal notation"
+msgstr "以数字方å¼æ˜¾ç¤ºæ·»åŠ æˆ–删除行的数é‡"
+
+#: builtin/apply.c:3988
+msgid "instead of applying the patch, output a summary for the input"
+msgstr "ä¸åº”用补ä¸ï¼Œè€Œæ˜¯æ˜¾ç¤ºè¾“入的概è¦"
+
+#: builtin/apply.c:3990
+msgid "instead of applying the patch, see if the patch is applicable"
+msgstr "ä¸åº”用补ä¸ï¼Œè€Œæ˜¯æŸ¥çœ‹è¡¥ä¸æ˜¯å¦å¯åº”用"
+
+#: builtin/apply.c:3992
+msgid "make sure the patch is applicable to the current index"
+msgstr "确认补ä¸å¯ä»¥åº”用到当å‰ç´¢å¼•"
+
+#: builtin/apply.c:3994
+msgid "apply a patch without touching the working tree"
+msgstr "应用补ä¸è€Œä¸ä¿®æ”¹å·¥ä½œåŒº"
+
+#: builtin/apply.c:3996
+msgid "also apply the patch (use with --stat/--summary/--check)"
+msgstr "åŒæ—¶åº”用此补ä¸ï¼ˆå’Œ --stat/--summary/--check 共用)"
+
+#: builtin/apply.c:3998
+msgid "build a temporary index based on embedded index information"
+msgstr "创建一个临时索引基于嵌入的索引信æ¯"
+
+#: builtin/apply.c:4000
+msgid "paths are separated with NUL character"
+msgstr "路径以 NUL 字符分隔"
+
+#: builtin/apply.c:4003
+msgid "ensure at least <n> lines of context match"
+msgstr "ç¡®ä¿è‡³å°‘åŒ¹é… <n> 行上下文"
+
+#: builtin/apply.c:4004
+msgid "action"
+msgstr "动作"
+
+#: builtin/apply.c:4005
+msgid "detect new or modified lines that have whitespace errors"
+msgstr "检查新增和修改的行中间的空白字符滥用"
+
+#: builtin/apply.c:4008 builtin/apply.c:4011
+msgid "ignore changes in whitespace when finding context"
+msgstr "查找上下文时忽略空白字符的å˜æ›´"
+
+#: builtin/apply.c:4014
+msgid "apply the patch in reverse"
+msgstr "åå‘应用补ä¸"
+
+#: builtin/apply.c:4016
+msgid "don't expect at least one line of context"
+msgstr "无需至少一行上下文"
+
+#: builtin/apply.c:4018
+msgid "leave the rejected hunks in corresponding *.rej files"
+msgstr "将拒ç»çš„è¡¥ä¸ç‰‡æ®µä¿å­˜åœ¨å¯¹åº”çš„ *.rej 文件中"
+
+#: builtin/apply.c:4020
+msgid "allow overlapping hunks"
+msgstr "å…许é‡å çš„è¡¥ä¸ç‰‡æ®µ"
+
+#: builtin/apply.c:4021
+msgid "be verbose"
+msgstr "冗长输出"
+
+#: builtin/apply.c:4023
+msgid "tolerate incorrectly detected missing new-line at the end of file"
+msgstr "宽容ä¸æ­£ç¡®çš„文件末尾æ¢è¡Œç¬¦"
+
+#: builtin/apply.c:4026
+msgid "do not trust the line counts in the hunk headers"
+msgstr "ä¸ä¿¡ä»»è¡¥ä¸ç‰‡æ®µçš„头信æ¯ä¸­çš„è¡Œå·"
+
+#: builtin/apply.c:4028
+msgid "root"
+msgstr "根目录"
+
+#: builtin/apply.c:4029
+msgid "prepend <root> to all filenames"
+msgstr "为所有文件åå‰æ·»åŠ  <根目录>"
+
+#: builtin/apply.c:4050
+msgid "--index outside a repository"
+msgstr "--index 在一个版本库之外"
+
+#: builtin/apply.c:4053
+msgid "--cached outside a repository"
+msgstr "--cached 在一个版本库之外"
+
+#: builtin/apply.c:4069
+#, c-format
+msgid "can't open patch '%s'"
+msgstr "ä¸èƒ½æ‰“å¼€è¡¥ä¸ '%s'"
+
+#: builtin/apply.c:4083
+#, c-format
+msgid "squelched %d whitespace error"
+msgid_plural "squelched %d whitespace errors"
+msgstr[0] "抑制下ä»æœ‰ %d 个空白字符误用"
+msgstr[1] "抑制下ä»æœ‰ %d 个空白字符误用"
+
+#: builtin/apply.c:4089 builtin/apply.c:4099
+#, c-format
+msgid "%d line adds whitespace errors."
+msgid_plural "%d lines add whitespace errors."
+msgstr[0] "%d 行有空白字符误用。"
+msgstr[1] "%d 行有空白字符误用。"
+
+#: builtin/archive.c:17
+#, c-format
+msgid "could not create archive file '%s'"
+msgstr "ä¸èƒ½åˆ›å»ºå½’档文件 '%s'"
+
+#: builtin/archive.c:20
+msgid "could not redirect output"
+msgstr "ä¸èƒ½è¾“出é‡å®šå‘"
+
+#: builtin/archive.c:37
+msgid "git archive: Remote with no URL"
+msgstr "git archive:未æ供远程URL"
+
+#: builtin/archive.c:58
+msgid "git archive: expected ACK/NAK, got EOF"
+msgstr "git archive:期待ACK/NACK,å´å¾—到EOF"
+
+#: builtin/archive.c:63
+#, c-format
+msgid "git archive: NACK %s"
+msgstr "git archive:NACK %s"
+
+#: builtin/archive.c:65
+#, c-format
+msgid "remote error: %s"
+msgstr "远程错误:%s"
+
+#: builtin/archive.c:66
+msgid "git archive: protocol error"
+msgstr "git archive:å议错误"
+
+#: builtin/archive.c:71
+msgid "git archive: expected a flush"
+msgstr "git archive:预期一个刷新"
+
+# 译者:ä¿æŒåŽŸæ¢è¡Œæ ¼å¼ï¼Œåœ¨è¾“出时 %s 的替代内容会让字符串å˜é•¿
+#: builtin/branch.c:144
+#, c-format
+msgid ""
+"deleting branch '%s' that has been merged to\n"
+" '%s', but not yet merged to HEAD."
+msgstr ""
+"å°†è¦åˆ é™¤çš„分支 '%s' å·²ç»è¢«åˆå¹¶åˆ°\n"
+" '%s',但未åˆå¹¶åˆ° HEAD。"
+
+# 译者:ä¿æŒåŽŸæ¢è¡Œæ ¼å¼ï¼Œåœ¨è¾“出时 %s 的替代内容会让字符串å˜é•¿
+#: builtin/branch.c:148
+#, c-format
+msgid ""
+"not deleting branch '%s' that is not yet merged to\n"
+" '%s', even though it is merged to HEAD."
+msgstr ""
+"并未删除分支 '%s', 虽然它已ç»åˆå¹¶åˆ° HEAD,\n"
+" 然而å´å°šæœªè¢«åˆå¹¶åˆ°åˆ†æ”¯ '%s' 。"
+
+#: builtin/branch.c:180
+msgid "cannot use -a with -d"
+msgstr "ä¸èƒ½å°† -a å’Œ -d 共用"
+
+#: builtin/branch.c:186
+msgid "Couldn't look up commit object for HEAD"
+msgstr "无法查询 HEAD 指å‘çš„æ交对象"
+
+#: builtin/branch.c:191
+#, c-format
+msgid "Cannot delete the branch '%s' which you are currently on."
+msgstr "无法删除您当å‰æ‰€åœ¨çš„分支 '%s'。"
+
+#: builtin/branch.c:202
+#, c-format
+msgid "remote branch '%s' not found."
+msgstr "远程分支 '%s' 未å‘现。"
+
+#: builtin/branch.c:203
+#, c-format
+msgid "branch '%s' not found."
+msgstr "分支 '%s' 未å‘现。"
+
+#: builtin/branch.c:210
+#, c-format
+msgid "Couldn't look up commit object for '%s'"
+msgstr "无法查询 '%s' 指å‘çš„æ交对象"
+
+#: builtin/branch.c:216
+#, c-format
+msgid ""
+"The branch '%s' is not fully merged.\n"
+"If you are sure you want to delete it, run 'git branch -D %s'."
+msgstr ""
+"分支 '%s' 没有完全åˆå¹¶ã€‚\n"
+"如果您确认è¦åˆ é™¤å®ƒï¼Œæ‰§è¡Œ 'git branch -D %s'。"
+
+#: builtin/branch.c:225
+#, c-format
+msgid "Error deleting remote branch '%s'"
+msgstr "删除远程分支 '%s' 时出错"
+
+#: builtin/branch.c:226
+#, c-format
+msgid "Error deleting branch '%s'"
+msgstr "删除分支 '%s' 时出错"
+
+#: builtin/branch.c:233
+#, c-format
+msgid "Deleted remote branch %s (was %s).\n"
+msgstr "已删除远程分支 %s(曾为 %s)。\n"
+
+#: builtin/branch.c:234
+#, c-format
+msgid "Deleted branch %s (was %s).\n"
+msgstr "已删除分支 %s(曾为 %s)。\n"
+
+#: builtin/branch.c:239
+msgid "Update of config-file failed"
+msgstr "无法更新 config 文件"
+
+#: builtin/branch.c:337
+#, c-format
+msgid "branch '%s' does not point at a commit"
+msgstr "分支 '%s' 未指å‘一个æ交"
+
+#: builtin/branch.c:409
+#, c-format
+msgid "[%s: behind %d]"
+msgstr "[%s:è½åŽ %d]"
+
+#: builtin/branch.c:411
+#, c-format
+msgid "[behind %d]"
+msgstr "[è½åŽ %d]"
+
+#: builtin/branch.c:415
+#, c-format
+msgid "[%s: ahead %d]"
+msgstr "[%s:领先 %d]"
+
+#: builtin/branch.c:417
+#, c-format
+msgid "[ahead %d]"
+msgstr "[领先 %d]"
+
+#: builtin/branch.c:420
+#, c-format
+msgid "[%s: ahead %d, behind %d]"
+msgstr "[%s:领先 %d,è½åŽ %d]"
+
+#: builtin/branch.c:423
+#, c-format
+msgid "[ahead %d, behind %d]"
+msgstr "[领先 %d,è½åŽ %d]"
+
+#: builtin/branch.c:535
+msgid "(no branch)"
+msgstr "(éžåˆ†æ”¯ï¼‰"
+
+#: builtin/branch.c:600
+msgid "some refs could not be read"
+msgstr "一些引用ä¸èƒ½è¯»å–"
+
+#: builtin/branch.c:613
+msgid "cannot rename the current branch while not on any."
+msgstr "无法é‡å‘½å当å‰åˆ†æ”¯å› ä¸ºä¸å¤„于任何分支上。"
+
+#: builtin/branch.c:623
+#, c-format
+msgid "Invalid branch name: '%s'"
+msgstr "无效的分支å:'%s'"
+
+#: builtin/branch.c:638
+msgid "Branch rename failed"
+msgstr "分支é‡å‘½å失败"
+
+#: builtin/branch.c:642
+#, c-format
+msgid "Renamed a misnamed branch '%s' away"
+msgstr "é‡å‘½å掉一个错误命å的旧分支 '%s'"
+
+#: builtin/branch.c:646
+#, c-format
+msgid "Branch renamed to %s, but HEAD is not updated!"
+msgstr "分支é‡å‘½å为 %s,但 HEAD 没有更新ï¼"
+
+#: builtin/branch.c:653
+msgid "Branch is renamed, but update of config-file failed"
+msgstr "分支被é‡å‘½å,但更新 config 文件失败"
+
+#: builtin/branch.c:668
+#, c-format
+msgid "malformed object name %s"
+msgstr "éžæ³•çš„对象å %s"
+
+#: builtin/branch.c:692
+#, c-format
+msgid "could not write branch description template: %s"
+msgstr "ä¸èƒ½å†™åˆ†æ”¯æ述模版:%s"
+
+#: builtin/branch.c:783
+msgid "Failed to resolve HEAD as a valid ref."
+msgstr "无法将 HEAD 解æžä¸ºæœ‰æ•ˆå¼•ç”¨ã€‚"
+
+#: builtin/branch.c:788 builtin/clone.c:558
+msgid "HEAD not found below refs/heads!"
+msgstr "HEAD 没有ä½äºŽ /refs/heads 之下ï¼"
+
+#: builtin/branch.c:808
+msgid "--column and --verbose are incompatible"
+msgstr "--column å’Œ --verbose ä¸å…¼å®¹"
+
+#: builtin/branch.c:857
+msgid "-a and -r options to 'git branch' do not make sense with a branch name"
+msgstr "'git branch' çš„ -a å’Œ -r 选项带一个分支åå‚数没有æ„义"
+
+#: builtin/bundle.c:47
+#, c-format
+msgid "%s is okay\n"
+msgstr "%s å¯ä»¥\n"
+
+#: builtin/bundle.c:56
+msgid "Need a repository to create a bundle."
+msgstr "需è¦ä¸€ä¸ªç‰ˆæœ¬åº“æ¥åˆ›å»ºåŒ…。"
+
+#: builtin/bundle.c:60
+msgid "Need a repository to unbundle."
+msgstr "需è¦ä¸€ä¸ªç‰ˆæœ¬åº“æ¥è§£åŒ…。"
+
+#: builtin/checkout.c:113 builtin/checkout.c:146
+#, c-format
+msgid "path '%s' does not have our version"
+msgstr "路径 '%s' 没有我们的版本"
+
+#: builtin/checkout.c:115 builtin/checkout.c:148
+#, c-format
+msgid "path '%s' does not have their version"
+msgstr "路径 '%s' 没有他们的版本"
+
+#: builtin/checkout.c:131
+#, c-format
+msgid "path '%s' does not have all necessary versions"
+msgstr "路径 '%s' 没有全部必须的版本"
+
+#: builtin/checkout.c:175
+#, c-format
+msgid "path '%s' does not have necessary versions"
+msgstr "路径 '%s' 没有必须的版本"
+
+#: builtin/checkout.c:192
+#, c-format
+msgid "path '%s': cannot merge"
+msgstr "path '%s':无法åˆå¹¶"
+
+#: builtin/checkout.c:209
+#, c-format
+msgid "Unable to add merge result for '%s'"
+msgstr "无法为 '%s' 添加åˆå¹¶ç»“æžœ"
+
+#: builtin/checkout.c:234 builtin/checkout.c:392
+msgid "corrupt index file"
+msgstr "æŸå的索引文件"
+
+#: builtin/checkout.c:264 builtin/checkout.c:271
+#, c-format
+msgid "path '%s' is unmerged"
+msgstr "路径 '%s' 未åˆå¹¶"
+
+#: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
+#: builtin/merge.c:812
+msgid "unable to write new index file"
+msgstr "无法写新的索引文件"
+
+#: builtin/checkout.c:319 builtin/diff.c:302 builtin/merge.c:408
+msgid "diff_setup_done failed"
+msgstr "diff_setup_done 失败"
+
+#: builtin/checkout.c:414
+msgid "you need to resolve your current index first"
+msgstr "您需è¦å…ˆè§£å†³å½“å‰ç´¢å¼•çš„冲çª"
+
+#: builtin/checkout.c:533
+#, c-format
+msgid "Can not do reflog for '%s'\n"
+msgstr "ä¸èƒ½å¯¹ '%s' 执行 reflog æ“作\n"
+
+#: builtin/checkout.c:566
+msgid "HEAD is now at"
+msgstr "HEAD ç›®å‰ä½äºŽ"
+
+#: builtin/checkout.c:573
+#, c-format
+msgid "Reset branch '%s'\n"
+msgstr "é‡ç½®åˆ†æ”¯ '%s'\n"
+
+#: builtin/checkout.c:576
+#, c-format
+msgid "Already on '%s'\n"
+msgstr "å·²ç»ä½äºŽ '%s'\n"
+
+#: builtin/checkout.c:580
+#, c-format
+msgid "Switched to and reset branch '%s'\n"
+msgstr "切æ¢å¹¶é‡ç½®åˆ†æ”¯ '%s'\n"
+
+#: builtin/checkout.c:582
+#, c-format
+msgid "Switched to a new branch '%s'\n"
+msgstr "切æ¢åˆ°ä¸€ä¸ªæ–°åˆ†æ”¯ '%s'\n"
+
+#: builtin/checkout.c:584
+#, c-format
+msgid "Switched to branch '%s'\n"
+msgstr "切æ¢åˆ°åˆ†æ”¯ '%s'\n"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: builtin/checkout.c:640
+#, c-format
+msgid " ... and %d more.\n"
+msgstr " ... åŠå…¶å®ƒ %d 个。\n"
+
+#. The singular version
+#: builtin/checkout.c:646
+#, c-format
+msgid ""
+"Warning: you are leaving %d commit behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgid_plural ""
+"Warning: you are leaving %d commits behind, not connected to\n"
+"any of your branches:\n"
+"\n"
+"%s\n"
+msgstr[0] ""
+"警告:您正丢下 %d 个æ交,未和任何分支关è”:\n"
+"\n"
+"%s\n"
+msgstr[1] ""
+"警告:您正丢下 %d 个æ交,未和任何分支关è”:\n"
+"\n"
+"%s\n"
+
+#: builtin/checkout.c:664
+#, c-format
+msgid ""
+"If you want to keep them by creating a new branch, this may be a good time\n"
+"to do so with:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+msgstr ""
+"如果您想è¦é€šè¿‡åˆ›å»ºæ–°åˆ†æ”¯ä¿å­˜ä»–们,这å¯èƒ½æ˜¯ä¸€ä¸ªå¥½æ—¶å€™ã€‚\n"
+"如下æ“作:\n"
+"\n"
+" git branch new_branch_name %s\n"
+"\n"
+
+#: builtin/checkout.c:694
+msgid "internal error in revision walk"
+msgstr "在版本é历时é‡åˆ°å†…部错误"
+
+#: builtin/checkout.c:698
+msgid "Previous HEAD position was"
+msgstr "之å‰çš„ HEAD ä½ç½®æ˜¯"
+
+#: builtin/checkout.c:724
+msgid "You are on a branch yet to be born"
+msgstr "您ä½äºŽä¸€ä¸ªå°šæœªåˆå§‹åŒ–的分支"
+
+#. case (1)
+#: builtin/checkout.c:855
+#, c-format
+msgid "invalid reference: %s"
+msgstr "无效引用:%s"
+
+#. case (1): want a tree
+#: builtin/checkout.c:894
+#, c-format
+msgid "reference is not a tree: %s"
+msgstr "引用ä¸æ˜¯ä¸€ä¸ªæ ‘:%s"
+
+#: builtin/checkout.c:974
+msgid "-B cannot be used with -b"
+msgstr "-B ä¸èƒ½å’Œ -b 共用"
+
+#: builtin/checkout.c:983
+msgid "--patch is incompatible with all other options"
+msgstr "--patch 选项和其他选项ä¸å…¼å®¹"
+
+#: builtin/checkout.c:986
+msgid "--detach cannot be used with -b/-B/--orphan"
+msgstr "--detach ä¸èƒ½å’Œ -b/-B/--orphan 共用"
+
+#: builtin/checkout.c:988
+msgid "--detach cannot be used with -t"
+msgstr "--detach ä¸èƒ½å’Œ -t 共用"
+
+#: builtin/checkout.c:994
+msgid "--track needs a branch name"
+msgstr "--track 需è¦ä¸€ä¸ªåˆ†æ”¯å"
+
+#: builtin/checkout.c:1001
+msgid "Missing branch name; try -b"
+msgstr "缺少分支åï¼›å°è¯• -b"
+
+#: builtin/checkout.c:1007
+msgid "--orphan and -b|-B are mutually exclusive"
+msgstr "--orphan 和 -b|-B 互斥"
+
+#: builtin/checkout.c:1009
+msgid "--orphan cannot be used with -t"
+msgstr "--orphan ä¸èƒ½å’Œ -t 共用"
+
+#: builtin/checkout.c:1019
+msgid "git checkout: -f and -m are incompatible"
+msgstr "git checkout:-f å’Œ -m ä¸å…¼å®¹"
+
+#: builtin/checkout.c:1053
+msgid "invalid path specification"
+msgstr "无效的路径规格"
+
+#: builtin/checkout.c:1061
+#, c-format
+msgid ""
+"git checkout: updating paths is incompatible with switching branches.\n"
+"Did you intend to checkout '%s' which can not be resolved as commit?"
+msgstr ""
+"git checkout:更新路径和切æ¢åˆ†æ”¯ä¸å…¼å®¹ã€‚\n"
+"您是想è¦æ£€å‡º '%s' 但未能将其解æžä¸ºæ交么?"
+
+#: builtin/checkout.c:1063
+msgid "git checkout: updating paths is incompatible with switching branches."
+msgstr "git checkout:更新路径和切æ¢åˆ†æ”¯ä¸å…¼å®¹ã€‚"
+
+#: builtin/checkout.c:1068
+msgid "git checkout: --detach does not take a path argument"
+msgstr "git checkout:--detach ä¸è·Ÿè·¯å¾„å‚æ•°"
+
+#: builtin/checkout.c:1071
+msgid ""
+"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+"checking out of the index."
+msgstr ""
+"git checkout:在从索引检出时,--ours/--theirsã€--force å’Œ --merge ä¸å…¼å®¹ã€‚"
+
+#: builtin/checkout.c:1090
+msgid "Cannot switch branch to a non-commit."
+msgstr "无法切æ¢åˆ†æ”¯åˆ°ä¸€ä¸ªéžæ交。"
+
+#: builtin/checkout.c:1093
+msgid "--ours/--theirs is incompatible with switching branches."
+msgstr "--ours/--theirs 和切æ¢åˆ†æ”¯ä¸å…¼å®¹ã€‚"
+
+#: builtin/clean.c:78
+msgid "-x and -X cannot be used together"
+msgstr "-x å’Œ -X ä¸èƒ½å…±ç”¨"
+
+#: builtin/clean.c:82
+msgid ""
+"clean.requireForce set to true and neither -n nor -f given; refusing to clean"
+msgstr ""
+"clean.requireForce 设置为 true 且未æä¾› -n 或 -f 选项,拒ç»æ‰§è¡Œæ¸…ç†åŠ¨ä½œ"
+
+#: builtin/clean.c:85
+msgid ""
+"clean.requireForce defaults to true and neither -n nor -f given; refusing to "
+"clean"
+msgstr ""
+"clean.requireForce 默认为 true 且未æä¾› -n 或 -f 选项,拒ç»æ‰§è¡Œæ¸…ç†åŠ¨ä½œ"
+
+#: builtin/clean.c:155 builtin/clean.c:176
+#, c-format
+msgid "Would remove %s\n"
+msgstr "将删除 %s\n"
+
+#: builtin/clean.c:159 builtin/clean.c:179
+#, c-format
+msgid "Removing %s\n"
+msgstr "正删除 %s\n"
+
+#: builtin/clean.c:162 builtin/clean.c:182
+#, c-format
+msgid "failed to remove %s"
+msgstr "无法删除 %s"
+
+#: builtin/clean.c:166
+#, c-format
+msgid "Would not remove %s\n"
+msgstr "ä¸ä¼šåˆ é™¤ %s\n"
+
+#: builtin/clean.c:168
+#, c-format
+msgid "Not removing %s\n"
+msgstr "未删除 %s\n"
+
+#: builtin/clone.c:243
+#, c-format
+msgid "reference repository '%s' is not a local directory."
+msgstr "引用版本库 '%s' ä¸æ˜¯ä¸€ä¸ªæœ¬åœ°ç›®å½•ã€‚"
+
+#: builtin/clone.c:302
+#, c-format
+msgid "failed to open '%s'"
+msgstr "无法打开 '%s'"
+
+#: builtin/clone.c:306
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr "无法创建目录 '%s'"
+
+#: builtin/clone.c:308 builtin/diff.c:75
+#, c-format
+msgid "failed to stat '%s'"
+msgstr "无法枚举 '%s' 状æ€"
+
+#: builtin/clone.c:310
+#, c-format
+msgid "%s exists and is not a directory"
+msgstr "%s 存在且ä¸æ˜¯ä¸€ä¸ªç›®å½•"
+
+#: builtin/clone.c:324
+#, c-format
+msgid "failed to stat %s\n"
+msgstr "无法枚举 %s 状æ€\n"
+
+#: builtin/clone.c:341
+#, c-format
+msgid "failed to unlink '%s'"
+msgstr "无法删除 '%s'"
+
+#: builtin/clone.c:346
+#, c-format
+msgid "failed to create link '%s'"
+msgstr "无法创建链接 '%s'"
+
+#: builtin/clone.c:350
+#, c-format
+msgid "failed to copy file to '%s'"
+msgstr "无法拷è´æ–‡ä»¶è‡³ '%s'"
+
+#: builtin/clone.c:373
+#, c-format
+msgid "done.\n"
+msgstr "完æˆã€‚\n"
+
+#: builtin/clone.c:440
+#, c-format
+msgid "Could not find remote branch %s to clone."
+msgstr "ä¸èƒ½å‘现è¦å…‹éš†çš„远程分支 %s。"
+
+#: builtin/clone.c:549
+msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
+msgstr "远程 HEAD 指å‘一个ä¸å­˜åœ¨çš„引用,无法检出。\n"
+
+#: builtin/clone.c:639
+msgid "Too many arguments."
+msgstr "太多å‚数。"
+
+#: builtin/clone.c:643
+msgid "You must specify a repository to clone."
+msgstr "您必须指定一个版本库æ¥å…‹éš†ã€‚"
+
+#: builtin/clone.c:654
+#, c-format
+msgid "--bare and --origin %s options are incompatible."
+msgstr "--bare å’Œ --origin %s 选项ä¸å…¼å®¹ã€‚"
+
+#: builtin/clone.c:668
+#, c-format
+msgid "repository '%s' does not exist"
+msgstr "版本库 '%s' ä¸å­˜åœ¨"
+
+#: builtin/clone.c:673
+msgid "--depth is ignored in local clones; use file:// instead."
+msgstr "--depth 在本地克隆被忽略,改为 file:// å议试试。"
+
+#: builtin/clone.c:683
+#, c-format
+msgid "destination path '%s' already exists and is not an empty directory."
+msgstr "目标路径 '%s' å·²ç»å­˜åœ¨ï¼Œå¹¶ä¸”ä¸æ˜¯ä¸€ä¸ªç©ºç›®å½•ã€‚"
+
+#: builtin/clone.c:693
+#, c-format
+msgid "working tree '%s' already exists."
+msgstr "工作区 '%s' å·²ç»å­˜åœ¨ã€‚"
+
+#: builtin/clone.c:706 builtin/clone.c:720
+#, c-format
+msgid "could not create leading directories of '%s'"
+msgstr "ä¸èƒ½ä¸º '%s' 创建先导目录"
+
+#: builtin/clone.c:709
+#, c-format
+msgid "could not create work tree dir '%s'."
+msgstr "ä¸èƒ½ä¸º '%s' 创建工作区目录。"
+
+#: builtin/clone.c:728
+#, c-format
+msgid "Cloning into bare repository '%s'...\n"
+msgstr "克隆到裸版本库 '%s'...\n"
+
+#: builtin/clone.c:730
+#, c-format
+msgid "Cloning into '%s'...\n"
+msgstr "正克隆到 '%s'...\n"
+
+#: builtin/clone.c:786
+#, c-format
+msgid "Don't know how to clone %s"
+msgstr "ä¸çŸ¥é“如何克隆 %s"
+
+#: builtin/clone.c:835
+#, c-format
+msgid "Remote branch %s not found in upstream %s"
+msgstr "远程分支 %s 在上游 %s 未å‘现"
+
+#: builtin/clone.c:842
+msgid "You appear to have cloned an empty repository."
+msgstr "您似乎克隆了一个空版本库。"
+
+#: builtin/column.c:51
+msgid "--command must be the first argument"
+msgstr "--command 必须是第一个å‚æ•°"
+
+#: builtin/commit.c:43
+msgid ""
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n"
+msgstr ""
+"您的姓å和邮件地å€åŸºäºŽç™»å½•å和主机å进行了自动设置。请检查它们正确\n"
+"与å¦ã€‚您å¯ä»¥é€šè¿‡ä¸‹é¢çš„命令对其进行明确地设置以å…å†å‡ºçŽ°æœ¬æ示信æ¯ï¼š\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"设置完毕åŽï¼Œæ‚¨å¯ä»¥ç”¨ä¸‹é¢çš„命令æ¥ä¿®æ­£æœ¬æ¬¡æ交所使用的用户身份:\n"
+"\n"
+" git commit --amend --reset-author\n"
+
+#: builtin/commit.c:55
+msgid ""
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n"
+msgstr ""
+"您è¦ä¿®è¡¥æœ€è¿‘çš„æ交,但这么åšä¼šè®©å®ƒæˆä¸ºç©ºæ交。您å¯ä»¥é‡å¤æ‚¨çš„命令并带上\n"
+"--allow-empty 选项,或者您å¯ç”¨å‘½ä»¤ \"git reset HEAD^\" 整个删除该æ交。\n"
+
+#: builtin/commit.c:60
+msgid ""
+"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n"
+msgstr ""
+"之å‰çš„拣选æ“作现在是一个空æ交,å¯èƒ½æ˜¯ç”±å†²çªè§£å†³å¯¼è‡´çš„。如果您无论如何\n"
+"也è¦æ交,使用命令:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"å¦åˆ™ï¼Œè¯·ä½¿ç”¨å‘½ä»¤ 'git reset'\n"
+
+#: builtin/commit.c:253
+msgid "failed to unpack HEAD tree object"
+msgstr "无法解包 HEAD 树对象"
+
+#: builtin/commit.c:295
+msgid "unable to create temporary index"
+msgstr "ä¸èƒ½åˆ›å»ºä¸´æ—¶ç´¢å¼•"
+
+#: builtin/commit.c:301
+msgid "interactive add failed"
+msgstr "交互å¼æ·»åŠ å¤±è´¥"
+
+#: builtin/commit.c:334 builtin/commit.c:355 builtin/commit.c:405
+msgid "unable to write new_index file"
+msgstr "无法写 new_index 文件"
+
+#: builtin/commit.c:386
+msgid "cannot do a partial commit during a merge."
+msgstr "在åˆå¹¶è¿‡ç¨‹ä¸­ä¸èƒ½åšéƒ¨åˆ†æ交。"
+
+#: builtin/commit.c:388
+msgid "cannot do a partial commit during a cherry-pick."
+msgstr "在拣选过程中ä¸èƒ½åšéƒ¨åˆ†æ交。"
+
+#: builtin/commit.c:398
+msgid "cannot read the index"
+msgstr "无法读å–索引"
+
+#: builtin/commit.c:418
+msgid "unable to write temporary index file"
+msgstr "无法写临时索引文件"
+
+#: builtin/commit.c:493 builtin/commit.c:499
+#, c-format
+msgid "invalid commit: %s"
+msgstr "无效的æ交:%s"
+
+#: builtin/commit.c:522
+msgid "malformed --author parameter"
+msgstr "éžæ³•çš„ --author å‚æ•°"
+
+#: builtin/commit.c:582
+#, c-format
+msgid "Malformed ident string: '%s'"
+msgstr "éžæ³•çš„身份字符串:'%s'"
+
+#: builtin/commit.c:620 builtin/commit.c:653 builtin/commit.c:967
+#, c-format
+msgid "could not lookup commit %s"
+msgstr "ä¸èƒ½æŸ¥è¯¢æ交 %s"
+
+#: builtin/commit.c:632 builtin/shortlog.c:296
+#, c-format
+msgid "(reading log message from standard input)\n"
+msgstr "(正从标准输入中读å–日志信æ¯ï¼‰\n"
+
+#: builtin/commit.c:634
+msgid "could not read log from standard input"
+msgstr "ä¸èƒ½ä»Žæ ‡å‡†è¾“入中读å–日志信æ¯"
+
+#: builtin/commit.c:638
+#, c-format
+msgid "could not read log file '%s'"
+msgstr "ä¸èƒ½è¯»å–日志文件 '%s'"
+
+#: builtin/commit.c:644
+msgid "commit has empty message"
+msgstr "æ交说明为空"
+
+#: builtin/commit.c:660
+msgid "could not read MERGE_MSG"
+msgstr "ä¸èƒ½è¯»å– MERGE_MSG"
+
+#: builtin/commit.c:664
+msgid "could not read SQUASH_MSG"
+msgstr "ä¸èƒ½è¯»å– SQUASH_MSG"
+
+#: builtin/commit.c:668
+#, c-format
+msgid "could not read '%s'"
+msgstr "ä¸èƒ½è¯»å– '%s'"
+
+#: builtin/commit.c:720
+msgid "could not write commit template"
+msgstr "ä¸èƒ½å†™æ交模版"
+
+#: builtin/commit.c:731
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a merge.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"看起æ¥æ‚¨æ­£åœ¨åšä¸€ä¸ªåˆå¹¶æ交。如果ä¸å¯¹ï¼Œè¯·åˆ é™¤æ–‡ä»¶\n"
+"\t%s\n"
+"然åŽé‡è¯•ã€‚\n"
+
+#: builtin/commit.c:736
+#, c-format
+msgid ""
+"\n"
+"It looks like you may be committing a cherry-pick.\n"
+"If this is not correct, please remove the file\n"
+"\t%s\n"
+"and try again.\n"
+msgstr ""
+"\n"
+"看起æ¥æ‚¨æ­£åœ¨åšä¸€ä¸ªæ‹£é€‰æ交。如果ä¸å¯¹ï¼Œè¯·åˆ é™¤æ–‡ä»¶\n"
+"\t%s\n"
+"然åŽé‡è¯•ã€‚\n"
+
+#: builtin/commit.c:748
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be ignored, and an empty message aborts the commit.\n"
+msgstr ""
+"请为您的å˜æ›´è¾“å…¥æ交说明。以 '#' 开始的行将被忽略,而一个空的æ交\n"
+"说明将会终止æ交。\n"
+
+#: builtin/commit.c:753
+msgid ""
+"Please enter the commit message for your changes. Lines starting\n"
+"with '#' will be kept; you may remove them yourself if you want to.\n"
+"An empty message aborts the commit.\n"
+msgstr ""
+"请为您的å˜æ›´è¾“å…¥æ交说明。以 '#' 开始的行将被ä¿ç•™ï¼Œæ‚¨å¯ä»¥åˆ é™¤å®ƒä»¬\n"
+"如果您想这样åšçš„è¯ã€‚而一个空的æ交说明将会终止æ交。\n"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: builtin/commit.c:766
+#, c-format
+msgid "%sAuthor: %s"
+msgstr "%s作者: %s"
+
+# 译者:为ä¿è¯åœ¨è¾“出中对é½ï¼Œæ³¨æ„调整å¥ä¸­ç©ºæ ¼ï¼
+#: builtin/commit.c:773
+#, c-format
+msgid "%sCommitter: %s"
+msgstr "%sæ交者: %s"
+
+#: builtin/commit.c:793
+msgid "Cannot read index"
+msgstr "无法读å–索引"
+
+#: builtin/commit.c:830
+msgid "Error building trees"
+msgstr "无法创建树对象"
+
+#: builtin/commit.c:845 builtin/tag.c:361
+#, c-format
+msgid "Please supply the message using either -m or -F option.\n"
+msgstr "请使用 -m 或者 -F 选项æä¾›æ交说明。\n"
+
+#: builtin/commit.c:942
+#, c-format
+msgid "No existing author found with '%s'"
+msgstr "æ²¡æœ‰æ‰¾åˆ°åŒ¹é… '%s' 的作者"
+
+#: builtin/commit.c:957 builtin/commit.c:1157
+#, c-format
+msgid "Invalid untracked files mode '%s'"
+msgstr "无效的未追踪文件å‚æ•° '%s'"
+
+#: builtin/commit.c:997
+msgid "Using both --reset-author and --author does not make sense"
+msgstr "åŒæ—¶ä½¿ç”¨ --reset-author å’Œ --author 没有æ„义"
+
+#: builtin/commit.c:1008
+msgid "You have nothing to amend."
+msgstr "您没有å¯ä¿®è¡¥çš„æ交。"
+
+#: builtin/commit.c:1011
+msgid "You are in the middle of a merge -- cannot amend."
+msgstr "您正处于一个åˆå¹¶è¿‡ç¨‹ä¸­ -- 无法修补æ交。"
+
+#: builtin/commit.c:1013
+msgid "You are in the middle of a cherry-pick -- cannot amend."
+msgstr "您正处于一个拣选过程中 -- 无法修补æ交。"
+
+#: builtin/commit.c:1016
+msgid "Options --squash and --fixup cannot be used together"
+msgstr "选项 --squash å’Œ --fixup ä¸èƒ½å…±ç”¨"
+
+#: builtin/commit.c:1026
+msgid "Only one of -c/-C/-F/--fixup can be used."
+msgstr "åªèƒ½ç”¨ä¸€ä¸ª -c/-C/-F/--fixup 选项。"
+
+#: builtin/commit.c:1028
+msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
+msgstr "选项 -m ä¸èƒ½å’Œ -c/-C/-F/--fixup 共用。"
+
+#: builtin/commit.c:1036
+msgid "--reset-author can be used only with -C, -c or --amend."
+msgstr "--reset-author åªèƒ½å’Œ -Cã€-c 或 --amend 共用。"
+
+#: builtin/commit.c:1053
+msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
+msgstr "åªèƒ½ç”¨ä¸€ä¸ª --include/--only/--all/--interactive/--patch 选项。"
+
+#: builtin/commit.c:1055
+msgid "No paths with --include/--only does not make sense."
+msgstr "å‚æ•° --include/--only ä¸è·Ÿè·¯å¾„没有æ„义。"
+
+#: builtin/commit.c:1057
+msgid "Clever... amending the last one with dirty index."
+msgstr "èªæ˜Ž... 在索引ä¸å¹²å‡€ä¸‹ä¿®è¡¥æœ€åŽçš„æ交。"
+
+#: builtin/commit.c:1059
+msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
+msgstr "指定了明确的路径而没有使用 -i 或 -o 选项;认为是 --only paths..."
+
+#: builtin/commit.c:1069 builtin/tag.c:577
+#, c-format
+msgid "Invalid cleanup mode %s"
+msgstr "无效的清ç†æ¨¡å¼ %s"
+
+#: builtin/commit.c:1074
+msgid "Paths with -a does not make sense."
+msgstr "路径和 -a 选项共用没有æ„义。"
+
+#: builtin/commit.c:1257
+msgid "couldn't look up newly created commit"
+msgstr "无法找到新创建的æ交"
+
+#: builtin/commit.c:1259
+msgid "could not parse newly created commit"
+msgstr "ä¸èƒ½è§£æžæ–°åˆ›å»ºçš„æ交"
+
+#: builtin/commit.c:1300
+msgid "detached HEAD"
+msgstr "分离头指针"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: builtin/commit.c:1302
+msgid " (root-commit)"
+msgstr "(根æ交)"
+
+#: builtin/commit.c:1446
+msgid "could not parse HEAD commit"
+msgstr "ä¸èƒ½è§£æž HEAD æ交"
+
+#: builtin/commit.c:1484 builtin/merge.c:509
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr "ä¸èƒ½ä¸ºè¯»å…¥æ‰“å¼€ '%s'"
+
+#: builtin/commit.c:1491
+#, c-format
+msgid "Corrupt MERGE_HEAD file (%s)"
+msgstr "æŸåçš„ MERGE_HEAD 文件(%s)"
+
+#: builtin/commit.c:1498
+msgid "could not read MERGE_MODE"
+msgstr "ä¸èƒ½è¯»å– MERGE_MODE"
+
+#: builtin/commit.c:1517
+#, c-format
+msgid "could not read commit message: %s"
+msgstr "ä¸èƒ½è¯»å–æ交说明:%s"
+
+#: builtin/commit.c:1531
+#, c-format
+msgid "Aborting commit; you did not edit the message.\n"
+msgstr "终止æ交;您未更改æ¥è‡ªæ¨¡ç‰ˆçš„æ交说明。\n"
+
+#: builtin/commit.c:1536
+#, c-format
+msgid "Aborting commit due to empty commit message.\n"
+msgstr "终止æ交因为æ交说明为空。\n"
+
+#: builtin/commit.c:1551 builtin/merge.c:936 builtin/merge.c:961
+msgid "failed to write commit object"
+msgstr "无法写æ交对象"
+
+#: builtin/commit.c:1572
+msgid "cannot lock HEAD ref"
+msgstr "无法é”定 HEAD 引用"
+
+#: builtin/commit.c:1576
+msgid "cannot update HEAD ref"
+msgstr "无法更新 HEAD 引用"
+
+#: builtin/commit.c:1587
+msgid ""
+"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."
+msgstr ""
+"版本库已更新,但无法写 new_index 文件。检查是å¦ç£ç›˜å·²æ»¡\n"
+"或ç£ç›˜é…é¢å·²è€—尽,然åŽæ‰§è¡Œ \"git reset HEAD\" æ¢å¤ã€‚"
+
+#: builtin/describe.c:234
+#, c-format
+msgid "annotated tag %s not available"
+msgstr "注释 tag %s 无效"
+
+#: builtin/describe.c:238
+#, c-format
+msgid "annotated tag %s has no embedded name"
+msgstr "注释 tag %s 没有嵌入å称"
+
+#: builtin/describe.c:240
+#, c-format
+msgid "tag '%s' is really '%s' here"
+msgstr "tag '%s' 的确是在 '%s'"
+
+#: builtin/describe.c:267
+#, c-format
+msgid "Not a valid object name %s"
+msgstr "ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„对象å %s"
+
+#: builtin/describe.c:270
+#, c-format
+msgid "%s is not a valid '%s' object"
+msgstr "%s ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„ '%s' 对象"
+
+#: builtin/describe.c:287
+#, c-format
+msgid "no tag exactly matches '%s'"
+msgstr "没有 tag å‡†ç¡®åŒ¹é… '%s'"
+
+#: builtin/describe.c:289
+#, c-format
+msgid "searching to describe %s\n"
+msgstr "æœç´¢æè¿° %s\n"
+
+#: builtin/describe.c:329
+#, c-format
+msgid "finished search at %s\n"
+msgstr "完æˆæœç´¢ %s\n"
+
+#: builtin/describe.c:353
+#, c-format
+msgid ""
+"No annotated tags can describe '%s'.\n"
+"However, there were unannotated tags: try --tags."
+msgstr ""
+"没有注释 tag 能æè¿° '%s'。\n"
+"然而,有éžæ³¨é‡Š tag:å°è¯• --tags。"
+
+#: builtin/describe.c:357
+#, c-format
+msgid ""
+"No tags can describe '%s'.\n"
+"Try --always, or create some tags."
+msgstr ""
+"没有注释 tag 能æè¿° '%s'。\n"
+"å°è¯• --always,或者创建一些 tag。"
+
+#: builtin/describe.c:378
+#, c-format
+msgid "traversed %lu commits\n"
+msgstr "å·²é历 %lu 个æ交\n"
+
+#: builtin/describe.c:381
+#, c-format
+msgid ""
+"more than %i tags found; listed %i most recent\n"
+"gave up search at %s\n"
+msgstr ""
+"å‘现多于 %i 个 tag,列出最近的 %i 个\n"
+"在 %s 放弃æœç´¢\n"
+
+#: builtin/describe.c:436
+msgid "--long is incompatible with --abbrev=0"
+msgstr "--long 与 --abbrev=0 ä¸å…¼å®¹"
+
+#: builtin/describe.c:462
+msgid "No names found, cannot describe anything."
+msgstr "没有å‘现å称,无法æ述任何东西。"
+
+#: builtin/describe.c:482
+msgid "--dirty is incompatible with committishes"
+msgstr "--dirty ä¸èƒ½ä¸Žæ交共用"
+
+#: builtin/diff.c:77
+#, c-format
+msgid "'%s': not a regular file or symlink"
+msgstr "'%s':ä¸æ˜¯ä¸€ä¸ªæ­£è§„文件或符å·é“¾æŽ¥"
+
+#: builtin/diff.c:220
+#, c-format
+msgid "invalid option: %s"
+msgstr "无效选项:%s"
+
+#: builtin/diff.c:297
+msgid "Not a git repository"
+msgstr "ä¸æ˜¯ä¸€ä¸ª git 版本库"
+
+#: builtin/diff.c:347
+#, c-format
+msgid "invalid object '%s' given."
+msgstr "æ供了无效对象 '%s'。"
+
+#: builtin/diff.c:352
+#, c-format
+msgid "more than %d trees given: '%s'"
+msgstr "æ供了超过 %d 个树对象:'%s'"
+
+#: builtin/diff.c:362
+#, c-format
+msgid "more than two blobs given: '%s'"
+msgstr "æ供了超过两个 blob 对象:'%s'"
+
+#: builtin/diff.c:370
+#, c-format
+msgid "unhandled object '%s' given."
+msgstr "æ供了无法处ç†çš„对象 '%s'。"
+
+#: builtin/fetch.c:200
+msgid "Couldn't find remote ref HEAD"
+msgstr "无法å‘现远程 HEAD 引用"
+
+#: builtin/fetch.c:253
+#, c-format
+msgid "object %s not found"
+msgstr "对象 %s 未å‘现"
+
+#: builtin/fetch.c:259
+msgid "[up to date]"
+msgstr "[最新]"
+
+#: builtin/fetch.c:273
+#, c-format
+msgid "! %-*s %-*s -> %s (can't fetch in current branch)"
+msgstr "! %-*s %-*s -> %s (在当å‰åˆ†æ”¯ä¸‹ä¸èƒ½èŽ·å–)"
+
+#: builtin/fetch.c:274 builtin/fetch.c:360
+msgid "[rejected]"
+msgstr "[已拒ç»]"
+
+#: builtin/fetch.c:285
+msgid "[tag update]"
+msgstr "[tagæ›´æ–°]"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
+msgid " (unable to update local ref)"
+msgstr " (ä¸èƒ½æ›´æ–°æœ¬åœ°å¼•ç”¨ï¼‰"
+
+#: builtin/fetch.c:305
+msgid "[new tag]"
+msgstr "[æ–°tag]"
+
+#: builtin/fetch.c:308
+msgid "[new branch]"
+msgstr "[新分支]"
+
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[新引用]"
+
+#: builtin/fetch.c:356
+msgid "unable to update local ref"
+msgstr "ä¸èƒ½æ›´æ–°æœ¬åœ°å¼•ç”¨"
+
+#: builtin/fetch.c:356
+msgid "forced update"
+msgstr "强制更新"
+
+#: builtin/fetch.c:362
+msgid "(non-fast-forward)"
+msgstr "(éžå¿«è¿›å¼ï¼‰"
+
+#: builtin/fetch.c:393 builtin/fetch.c:685
+#, c-format
+msgid "cannot open %s: %s\n"
+msgstr "无法打开 %s:%s\n"
+
+#: builtin/fetch.c:402
+#, c-format
+msgid "%s did not send all necessary objects\n"
+msgstr "%s 未å‘é€æ‰€æœ‰å¿…须的对象\n"
+
+#: builtin/fetch.c:488
+#, c-format
+msgid "From %.*s\n"
+msgstr "æ¥è‡ª %.*s\n"
+
+#: builtin/fetch.c:499
+#, c-format
+msgid ""
+"some local refs could not be updated; try running\n"
+" 'git remote prune %s' to remove any old, conflicting branches"
+msgstr ""
+"一些本地引用ä¸èƒ½è¢«æ›´æ–°ï¼›å°è¯•è¿è¡Œ\n"
+" 'git remote prune %s' æ¥åˆ é™¤æ—§çš„ã€æœ‰å†²çªçš„分支"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: builtin/fetch.c:549
+#, c-format
+msgid " (%s will become dangling)"
+msgstr " (%s å°†æˆä¸ºæ‚¬ç©ºçŠ¶æ€ï¼‰"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: builtin/fetch.c:550
+#, c-format
+msgid " (%s has become dangling)"
+msgstr " (%s å·²æˆä¸ºæ‚¬ç©ºçŠ¶æ€ï¼‰"
+
+#: builtin/fetch.c:557
+msgid "[deleted]"
+msgstr "[已删除]"
+
+#: builtin/fetch.c:558 builtin/remote.c:1055
+msgid "(none)"
+msgstr "(无)"
+
+#: builtin/fetch.c:675
+#, c-format
+msgid "Refusing to fetch into current branch %s of non-bare repository"
+msgstr "æ‹’ç»èŽ·å–到éžè£¸ç‰ˆæœ¬åº“的当å‰åˆ†æ”¯ %s"
+
+#: builtin/fetch.c:709
+#, c-format
+msgid "Don't know how to fetch from %s"
+msgstr "ä¸çŸ¥é“如何从 %s 获å–"
+
+#: builtin/fetch.c:786
+#, c-format
+msgid "Option \"%s\" value \"%s\" is not valid for %s"
+msgstr "选项 \"%s\" 的值 \"%s\" 对于 %s 是无效的"
+
+#: builtin/fetch.c:789
+#, c-format
+msgid "Option \"%s\" is ignored for %s\n"
+msgstr "选项 \"%s\" 为 %s 所忽略\n"
+
+#: builtin/fetch.c:888
+#, c-format
+msgid "Fetching %s\n"
+msgstr "æ­£åœ¨èŽ·å– %s\n"
+
+#: builtin/fetch.c:890 builtin/remote.c:100
+#, c-format
+msgid "Could not fetch %s"
+msgstr "ä¸èƒ½èŽ·å– %s"
+
+#: builtin/fetch.c:907
+msgid ""
+"No remote repository specified. Please, specify either a URL or a\n"
+"remote name from which new revisions should be fetched."
+msgstr "未指定远程版本库。请通过一个URL或远程版本库å指定,用以获å–æ–°æ交。"
+
+#: builtin/fetch.c:927
+msgid "You need to specify a tag name."
+msgstr "您需è¦æŒ‡å®šä¸€ä¸ª tag å称。"
+
+#: builtin/fetch.c:979
+msgid "fetch --all does not take a repository argument"
+msgstr "fetch --all ä¸èƒ½å¸¦ä¸€ä¸ªç‰ˆæœ¬åº“å‚æ•°"
+
+#: builtin/fetch.c:981
+msgid "fetch --all does not make sense with refspecs"
+msgstr "fetch --all 带引用表达å¼æ²¡æœ‰ä»»ä½•æ„义"
+
+#: builtin/fetch.c:992
+#, c-format
+msgid "No such remote or remote group: %s"
+msgstr "没有这样的远程或远程组:%s"
+
+#: builtin/fetch.c:1000
+msgid "Fetching a group and specifying refspecs does not make sense"
+msgstr "获å–组并指定引用表达å¼æ²¡æœ‰æ„义"
+
+#: builtin/gc.c:63
+#, c-format
+msgid "Invalid %s: '%s'"
+msgstr "无效的 %s:'%s'"
+
+#: builtin/gc.c:90
+#, c-format
+msgid "insanely long object directory %.*s"
+msgstr "ä¸æ­£å¸¸çš„长对象目录 %.*s"
+
+#: builtin/gc.c:221
+#, c-format
+msgid "Auto packing the repository for optimum performance.\n"
+msgstr "自动打包版本库以求最佳性能。\n"
+
+#: builtin/gc.c:224
+#, c-format
+msgid ""
+"Auto packing the repository for optimum performance. You may also\n"
+"run \"git gc\" manually. See \"git help gc\" for more information.\n"
+msgstr ""
+"自动打包版本库以求最佳性能。您还å¯ä»¥æ‰‹åŠ¨è¿è¡Œ \"git gc\"。\n"
+"å‚è§ \"git help gc\" 以获å–更多信æ¯ã€‚\n"
+
+#: builtin/gc.c:251
+msgid ""
+"There are too many unreachable loose objects; run 'git prune' to remove them."
+msgstr "有太多ä¸å¯è¾¾çš„æ¾æ•£å¯¹è±¡ï¼Œè¿è¡Œ 'git prune' 删除它们。"
+
+#: builtin/grep.c:216
+#, c-format
+msgid "grep: failed to create thread: %s"
+msgstr "grep:无法创建线程:%s"
+
+#: builtin/grep.c:402
+#, c-format
+msgid "Failed to chdir: %s"
+msgstr "无法切æ¢ç›®å½•ï¼š%s"
+
+#: builtin/grep.c:478 builtin/grep.c:512
+#, c-format
+msgid "unable to read tree (%s)"
+msgstr "无法读å–树(%s)"
+
+#: builtin/grep.c:526
+#, c-format
+msgid "unable to grep from object of type %s"
+msgstr "无法抓å–æ¥è‡ªäºŽ %s 类型的对象"
+
+#: builtin/grep.c:584
+#, c-format
+msgid "switch `%c' expects a numerical value"
+msgstr "开关 `%c' 期望一个数字值"
+
+#: builtin/grep.c:601
+#, c-format
+msgid "cannot open '%s'"
+msgstr "ä¸èƒ½æ‰“å¼€ '%s'"
+
+#: builtin/grep.c:885
+msgid "no pattern given."
+msgstr "未æ供模å¼åŒ¹é…。"
+
+#: builtin/grep.c:899
+#, c-format
+msgid "bad object %s"
+msgstr "å对象 %s"
+
+#: builtin/grep.c:940
+msgid "--open-files-in-pager only works on the worktree"
+msgstr "--open-files-in-pager 仅用于工作区"
+
+#: builtin/grep.c:963
+msgid "--cached or --untracked cannot be used with --no-index."
+msgstr "--cached 或 --untracked ä¸èƒ½ä¸Ž --no-index 共用。"
+
+#: builtin/grep.c:968
+msgid "--no-index or --untracked cannot be used with revs."
+msgstr "--no-index 或 --untracked ä¸èƒ½å’Œç‰ˆæœ¬å…±ç”¨ã€‚"
+
+#: builtin/grep.c:971
+msgid "--[no-]exclude-standard cannot be used for tracked contents."
+msgstr "--[no-]exclude-standard ä¸èƒ½ç”¨äºŽå·²è·Ÿè¸ªå†…容。"
+
+#: builtin/grep.c:979
+msgid "both --cached and trees are given."
+msgstr "åŒæ—¶ç»™å‡ºäº† --cached 和树对象。"
+
+#: builtin/help.c:59
+#, c-format
+msgid "unrecognized help format '%s'"
+msgstr "æœªèƒ½è¯†åˆ«çš„å¸®åŠ©æ ¼å¼ '%s'"
+
+#: builtin/help.c:87
+msgid "Failed to start emacsclient."
+msgstr "无法å¯åŠ¨ emacsclient。"
+
+#: builtin/help.c:100
+msgid "Failed to parse emacsclient version."
+msgstr "æ— æ³•è§£æž emacsclient 版本。"
+
+#: builtin/help.c:108
+#, c-format
+msgid "emacsclient version '%d' too old (< 22)."
+msgstr "emacsclient 版本 '%d' å¤ªè€ (< 22)。"
+
+#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#, c-format
+msgid "failed to exec '%s': %s"
+msgstr "无法执行 '%s':%s"
+
+#: builtin/help.c:211
+#, c-format
+msgid ""
+"'%s': path for unsupported man viewer.\n"
+"Please consider using 'man.<tool>.cmd' instead."
+msgstr ""
+"'%s':ä¸æ”¯æŒçš„ man 手册查看器的路径。\n"
+"请使用 'man.<tool>.cmd'。"
+
+#: builtin/help.c:223
+#, c-format
+msgid ""
+"'%s': cmd for supported man viewer.\n"
+"Please consider using 'man.<tool>.path' instead."
+msgstr ""
+"'%s': 支æŒçš„ man 手册查看器命令。\n"
+"请使用 'man.<tool>.path'。"
+
+#: builtin/help.c:287
+msgid "The most commonly used git commands are:"
+msgstr "最常用的 git 命令有:"
+
+#: builtin/help.c:355
+#, c-format
+msgid "'%s': unknown man viewer."
+msgstr "'%s':未知的 man 查看器。"
+
+#: builtin/help.c:372
+msgid "no man viewer handled the request"
+msgstr "没有 man 查看器处ç†æ­¤è¯·æ±‚"
+
+#: builtin/help.c:380
+msgid "no info viewer handled the request"
+msgstr "没有 info 查看器处ç†æ­¤è¯·æ±‚"
+
+#: builtin/help.c:391
+#, c-format
+msgid "'%s': not a documentation directory."
+msgstr "'%s':ä¸æ˜¯ä¸€ä¸ªæ–‡æ¡£ç›®å½•ã€‚"
+
+#: builtin/help.c:432 builtin/help.c:439
+#, c-format
+msgid "usage: %s%s"
+msgstr "用法:%s%s"
+
+#: builtin/help.c:453
+#, c-format
+msgid "`git %s' is aliased to `%s'"
+msgstr "`git %s' 是 `%s' 的别å"
+
+#: builtin/index-pack.c:169
+#, c-format
+msgid "object type mismatch at %s"
+msgstr "%s 的对象类型ä¸åŒ¹é…"
+
+#: builtin/index-pack.c:189
+msgid "object of unexpected type"
+msgstr "æ„外的类型的对象"
+
+#: builtin/index-pack.c:226
+#, c-format
+msgid "cannot fill %d byte"
+msgid_plural "cannot fill %d bytes"
+msgstr[0] "无法填充 %d 字节"
+msgstr[1] "无法填充 %d 字节"
+
+#: builtin/index-pack.c:236
+msgid "early EOF"
+msgstr "过早的文件结æŸç¬¦ï¼ˆEOF)"
+
+#: builtin/index-pack.c:237
+msgid "read error on input"
+msgstr "输入上的读错误"
+
+#: builtin/index-pack.c:249
+msgid "used more bytes than were available"
+msgstr "用掉了超过å¯ç”¨çš„字节"
+
+#: builtin/index-pack.c:256
+msgid "pack too large for current definition of off_t"
+msgstr "åŒ…å¤ªå¤§è¶…è¿‡äº†å½“å‰ off_t 的定义"
+
+#: builtin/index-pack.c:272
+#, c-format
+msgid "unable to create '%s'"
+msgstr "ä¸èƒ½åˆ›å»º '%s'"
+
+#: builtin/index-pack.c:277
+#, c-format
+msgid "cannot open packfile '%s'"
+msgstr "无法打开包文件 '%s'"
+
+#: builtin/index-pack.c:291
+msgid "pack signature mismatch"
+msgstr "包签åä¸åŒ¹é…"
+
+#: builtin/index-pack.c:311
+#, c-format
+msgid "pack has bad object at offset %lu: %s"
+msgstr "包中有错误的对象ä½äºŽ %lu:%s"
+
+#: builtin/index-pack.c:405
+#, c-format
+msgid "inflate returned %d"
+msgstr "解压缩返回 %d"
+
+#: builtin/index-pack.c:450
+msgid "offset value overflow for delta base object"
+msgstr "å移值覆盖了 delta 基准对象"
+
+#: builtin/index-pack.c:458
+msgid "delta base offset is out of bound"
+msgstr "delta 基准å移越界"
+
+#: builtin/index-pack.c:466
+#, c-format
+msgid "unknown object type %d"
+msgstr "未知对象类型 %d"
+
+#: builtin/index-pack.c:495
+msgid "cannot pread pack file"
+msgstr "无法读å–包文件"
+
+#: builtin/index-pack.c:497
+#, c-format
+msgid "premature end of pack file, %lu byte missing"
+msgid_plural "premature end of pack file, %lu bytes missing"
+msgstr[0] "包文件过早结æŸï¼Œç¼ºå°‘ %lu 字节"
+msgstr[1] "包文件过早结æŸï¼Œç¼ºå°‘ %lu 字节"
+
+#: builtin/index-pack.c:510
+msgid "serious inflate inconsistency"
+msgstr "解压缩严é‡çš„ä¸ä¸€è‡´"
+
+#: builtin/index-pack.c:583
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "ä¸èƒ½è¯»å–现存对象 %s"
+
+#: builtin/index-pack.c:586
+#, c-format
+msgid "SHA1 COLLISION FOUND WITH %s !"
+msgstr "å‘现 %s 出现 SHA1 冲çªï¼"
+
+#: builtin/index-pack.c:598
+#, c-format
+msgid "invalid blob object %s"
+msgstr "无效的 blob 对象 %s"
+
+#: builtin/index-pack.c:610
+#, c-format
+msgid "invalid %s"
+msgstr "无效的 %s"
+
+#: builtin/index-pack.c:612
+msgid "Error in object"
+msgstr "对象中出错"
+
+#: builtin/index-pack.c:614
+#, c-format
+msgid "Not all child objects of %s are reachable"
+msgstr "%s 的所有å­å¯¹è±¡å¹¶éžéƒ½å¯è¾¾"
+
+#: builtin/index-pack.c:687 builtin/index-pack.c:713
+msgid "failed to apply delta"
+msgstr "无法应用 delta"
+
+#: builtin/index-pack.c:850
+msgid "Receiving objects"
+msgstr "接收对象中"
+
+#: builtin/index-pack.c:850
+msgid "Indexing objects"
+msgstr "索引对象中"
+
+#: builtin/index-pack.c:872
+msgid "pack is corrupted (SHA1 mismatch)"
+msgstr "包冲çªï¼ˆSHA1 ä¸åŒ¹é…)"
+
+#: builtin/index-pack.c:877
+msgid "cannot fstat packfile"
+msgstr "ä¸èƒ½æžšä¸¾åŒ…文件状æ€"
+
+#: builtin/index-pack.c:880
+msgid "pack has junk at the end"
+msgstr "包的结尾有垃圾数æ®"
+
+#: builtin/index-pack.c:903
+msgid "Resolving deltas"
+msgstr "å¤„ç† delta 中"
+
+#: builtin/index-pack.c:954
+msgid "confusion beyond insanity"
+msgstr "ä¸å¯ç†å–»"
+
+#: builtin/index-pack.c:973
+#, c-format
+msgid "pack has %d unresolved delta"
+msgid_plural "pack has %d unresolved deltas"
+msgstr[0] "包有 %d 个未解决的 delta"
+msgstr[1] "包有 %d 个未解决的 delta"
+
+#: builtin/index-pack.c:998
+#, c-format
+msgid "unable to deflate appended object (%d)"
+msgstr "ä¸èƒ½ç¼©å°é™„加对象(%d)"
+
+#: builtin/index-pack.c:1077
+#, c-format
+msgid "local object %s is corrupt"
+msgstr "本地对象 %s å·²æŸå"
+
+#: builtin/index-pack.c:1101
+msgid "error while closing pack file"
+msgstr "关闭包文件时出错"
+
+#: builtin/index-pack.c:1114
+#, c-format
+msgid "cannot write keep file '%s'"
+msgstr "无法写ä¿ç•™æ–‡ä»¶ '%s'"
+
+#: builtin/index-pack.c:1122
+#, c-format
+msgid "cannot close written keep file '%s'"
+msgstr "无法关闭ä¿ç•™æ–‡ä»¶ '%s'"
+
+#: builtin/index-pack.c:1135
+msgid "cannot store pack file"
+msgstr "无法存储包文件"
+
+#: builtin/index-pack.c:1146
+msgid "cannot store index file"
+msgstr "无法存储索引文件"
+
+#: builtin/index-pack.c:1247
+#, c-format
+msgid "Cannot open existing pack file '%s'"
+msgstr "无法打开现存包文件 '%s'"
+
+#: builtin/index-pack.c:1249
+#, c-format
+msgid "Cannot open existing pack idx file for '%s'"
+msgstr "无法为 %s 打开包索引文件"
+
+#: builtin/index-pack.c:1296
+#, c-format
+msgid "non delta: %d object"
+msgid_plural "non delta: %d objects"
+msgstr[0] "éž delta:%d 个对象"
+msgstr[1] "éž delta:%d 个对象"
+
+#: builtin/index-pack.c:1303
+#, c-format
+msgid "chain length = %d: %lu object"
+msgid_plural "chain length = %d: %lu objects"
+msgstr[0] "链长 = %d: %lu 对象"
+msgstr[1] "链长 = %d: %lu 对象"
+
+#: builtin/index-pack.c:1330
+msgid "Cannot come back to cwd"
+msgstr "无法返回当å‰å·¥ä½œç›®å½•"
+
+#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
+#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#, c-format
+msgid "bad %s"
+msgstr "错误选项 %s"
+
+#: builtin/index-pack.c:1407
+msgid "--fix-thin cannot be used without --stdin"
+msgstr "--fix-thin ä¸èƒ½å’Œ --stdin 共用"
+
+#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#, c-format
+msgid "packfile name '%s' does not end with '.pack'"
+msgstr "包å '%s' 没有以 '.pack' 结尾"
+
+#: builtin/index-pack.c:1430
+msgid "--verify with no packfile name given"
+msgstr "--verify 没有æ供包åå‚æ•°"
+
+#: builtin/init-db.c:35
+#, c-format
+msgid "Could not make %s writable by group"
+msgstr "ä¸èƒ½è®¾ç½® %s 为组å¯å†™"
+
+#: builtin/init-db.c:62
+#, c-format
+msgid "insanely long template name %s"
+msgstr "太长的模版å %s"
+
+#: builtin/init-db.c:67
+#, c-format
+msgid "cannot stat '%s'"
+msgstr "ä¸èƒ½æžšä¸¾ '%s' 状æ€"
+
+#: builtin/init-db.c:73
+#, c-format
+msgid "cannot stat template '%s'"
+msgstr "ä¸èƒ½æžšä¸¾æ¨¡ç‰ˆ '%s' 状æ€"
+
+#: builtin/init-db.c:80
+#, c-format
+msgid "cannot opendir '%s'"
+msgstr "ä¸èƒ½æ‰“开目录 '%s'"
+
+#: builtin/init-db.c:97
+#, c-format
+msgid "cannot readlink '%s'"
+msgstr "ä¸èƒ½è¯»å–链接 '%s'"
+
+#: builtin/init-db.c:99
+#, c-format
+msgid "insanely long symlink %s"
+msgstr "太长的符å·é“¾æŽ¥ %s"
+
+#: builtin/init-db.c:102
+#, c-format
+msgid "cannot symlink '%s' '%s'"
+msgstr "ä¸èƒ½è‡ª '%s' 到 '%s' 创建符å·é“¾æŽ¥"
+
+#: builtin/init-db.c:106
+#, c-format
+msgid "cannot copy '%s' to '%s'"
+msgstr "ä¸èƒ½æ‹·è´ '%s' 至 '%s'"
+
+#: builtin/init-db.c:110
+#, c-format
+msgid "ignoring template %s"
+msgstr "忽略模版 %s"
+
+#: builtin/init-db.c:133
+#, c-format
+msgid "insanely long template path %s"
+msgstr "太长的模版路径 %s"
+
+#: builtin/init-db.c:141
+#, c-format
+msgid "templates not found %s"
+msgstr "模版未找到 %s"
+
+#: builtin/init-db.c:154
+#, c-format
+msgid "not copying templates of a wrong format version %d from '%s'"
+msgstr "没有从 '%2$s' å¤åˆ¶å¸¦æœ‰é”™è¯¯ç‰ˆæœ¬ %1$d 的模版"
+
+#: builtin/init-db.c:192
+#, c-format
+msgid "insane git directory %s"
+msgstr "ä¸æ­£å¸¸çš„ git 目录 %s"
+
+#: builtin/init-db.c:322 builtin/init-db.c:325
+#, c-format
+msgid "%s already exists"
+msgstr "%s å·²ç»å­˜åœ¨"
+
+#: builtin/init-db.c:354
+#, c-format
+msgid "unable to handle file type %d"
+msgstr "ä¸èƒ½å¤„ç† %d 类型的文件"
+
+#: builtin/init-db.c:357
+#, c-format
+msgid "unable to move %s to %s"
+msgstr "ä¸èƒ½ç§»åŠ¨ %s 至 %s"
+
+#: builtin/init-db.c:362
+#, c-format
+msgid "Could not create git link %s"
+msgstr "ä¸èƒ½åˆ›å»º git link %s"
+
+#.
+#. * TRANSLATORS: The first '%s' is either "Reinitialized
+#. * existing" or "Initialized empty", the second " shared" or
+#. * "", and the last '%s%s' is the verbatim directory name.
+#.
+#: builtin/init-db.c:419
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s Git 版本库于 %s%s\n"
+
+#: builtin/init-db.c:420
+msgid "Reinitialized existing"
+msgstr "é‡æ–°åˆå§‹åŒ–现存的"
+
+#: builtin/init-db.c:420
+msgid "Initialized empty"
+msgstr "åˆå§‹åŒ–空的"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: builtin/init-db.c:421
+msgid " shared"
+msgstr "共享"
+
+#: builtin/init-db.c:440
+msgid "cannot tell cwd"
+msgstr "无法获知当å‰è·¯å¾„"
+
+#: builtin/init-db.c:521 builtin/init-db.c:528
+#, c-format
+msgid "cannot mkdir %s"
+msgstr "ä¸èƒ½åˆ›å»ºç›®å½• %s"
+
+#: builtin/init-db.c:532
+#, c-format
+msgid "cannot chdir to %s"
+msgstr "ä¸èƒ½åˆ‡æ¢ç›®å½•åˆ° %s"
+
+#: builtin/init-db.c:554
+#, c-format
+msgid ""
+"%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
+"dir=<directory>)"
+msgstr ""
+"ä¸å…许 %s(或 --work-tree=<directory>)而没有指定 %s(或 --git-"
+"dir=<directory>)"
+
+#: builtin/init-db.c:578
+msgid "Cannot access current working directory"
+msgstr "ä¸èƒ½è®¿é—®å½“å‰å·¥ä½œç›®å½•"
+
+#: builtin/init-db.c:585
+#, c-format
+msgid "Cannot access work tree '%s'"
+msgstr "ä¸èƒ½è®¿é—®å·¥ä½œåŒº '%s'"
+
+#: builtin/log.c:188
+#, c-format
+msgid "Final output: %d %s\n"
+msgstr "最终输出:%d %s\n"
+
+#: builtin/log.c:401 builtin/log.c:489
+#, c-format
+msgid "Could not read object %s"
+msgstr "ä¸èƒ½è¯»å–对象 %s"
+
+#: builtin/log.c:513
+#, c-format
+msgid "Unknown type: %d"
+msgstr "未知类型:%d"
+
+#: builtin/log.c:602
+msgid "format.headers without value"
+msgstr "format.headers 没有值"
+
+#: builtin/log.c:676
+msgid "name of output directory is too long"
+msgstr "输出目录å太长"
+
+#: builtin/log.c:687
+#, c-format
+msgid "Cannot open patch file %s"
+msgstr "无法打开补ä¸æ–‡ä»¶ %s"
+
+#: builtin/log.c:701
+msgid "Need exactly one range."
+msgstr "åªéœ€è¦ä¸€ä¸ªèŒƒå›´ã€‚"
+
+#: builtin/log.c:709
+msgid "Not a range."
+msgstr "ä¸æ˜¯ä¸€ä¸ªèŒƒå›´ã€‚"
+
+#: builtin/log.c:786
+msgid "Cover letter needs email format"
+msgstr "ä¿¡å°éœ€è¦é‚®ä»¶åœ°å€æ ¼å¼"
+
+#: builtin/log.c:859
+#, c-format
+msgid "insane in-reply-to: %s"
+msgstr "ä¸æ­£å¸¸çš„ in-reply-to:%s"
+
+#: builtin/log.c:932
+msgid "Two output directories?"
+msgstr "两个输出目录?"
+
+#: builtin/log.c:1153
+#, c-format
+msgid "bogus committer info %s"
+msgstr "虚å‡çš„æäº¤è€…ä¿¡æ¯ %s"
+
+#: builtin/log.c:1198
+msgid "-n and -k are mutually exclusive."
+msgstr "-n 和 -k 互斥。"
+
+#: builtin/log.c:1200
+msgid "--subject-prefix and -k are mutually exclusive."
+msgstr "--subject-prefix 和 -k 互斥。"
+
+#: builtin/log.c:1208
+msgid "--name-only does not make sense"
+msgstr "--name-only æ— æ„义"
+
+#: builtin/log.c:1210
+msgid "--name-status does not make sense"
+msgstr "--name-status æ— æ„义"
+
+#: builtin/log.c:1212
+msgid "--check does not make sense"
+msgstr "--check æ— æ„义"
+
+#: builtin/log.c:1235
+msgid "standard output, or directory, which one?"
+msgstr "标准输出或目录,哪一个?"
+
+#: builtin/log.c:1237
+#, c-format
+msgid "Could not create directory '%s'"
+msgstr "ä¸èƒ½åˆ›å»ºç›®å½• '%s'"
+
+#: builtin/log.c:1390
+msgid "Failed to create output files"
+msgstr "无法创建输出文件"
+
+#: builtin/log.c:1494
+#, c-format
+msgid ""
+"Could not find a tracked remote branch, please specify <upstream> manually.\n"
+msgstr "ä¸èƒ½æ‰¾åˆ°è·Ÿè¸ªçš„远程分支,请手工指定 <upstream>。\n"
+
+#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#, c-format
+msgid "Unknown commit %s"
+msgstr "未知æ交 %s"
+
+#: builtin/merge.c:90
+msgid "switch `m' requires a value"
+msgstr "开关 `m' 需è¦ä¸€ä¸ªå€¼"
+
+#: builtin/merge.c:127
+#, c-format
+msgid "Could not find merge strategy '%s'.\n"
+msgstr "ä¸èƒ½æ‰¾åˆ°åˆå¹¶ç­–ç•¥ '%s'。\n"
+
+#: builtin/merge.c:128
+#, c-format
+msgid "Available strategies are:"
+msgstr "å¯ç”¨çš„策略有:"
+
+#: builtin/merge.c:133
+#, c-format
+msgid "Available custom strategies are:"
+msgstr "å¯ç”¨çš„自定义策略有:"
+
+#: builtin/merge.c:240
+msgid "could not run stash."
+msgstr "ä¸èƒ½è¿›è¡Œè¿›åº¦ä¿å­˜ã€‚"
+
+#: builtin/merge.c:245
+msgid "stash failed"
+msgstr "进度ä¿å­˜å¤±è´¥"
+
+#: builtin/merge.c:250
+#, c-format
+msgid "not a valid object: %s"
+msgstr "ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆå¯¹è±¡ï¼š%s"
+
+#: builtin/merge.c:269 builtin/merge.c:286
+msgid "read-tree failed"
+msgstr "读å–树失败"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: builtin/merge.c:316
+msgid " (nothing to squash)"
+msgstr " (无å¯åŽ‹ç¼©ï¼‰"
+
+#: builtin/merge.c:329
+#, c-format
+msgid "Squash commit -- not updating HEAD\n"
+msgstr "压缩æ交 -- 未更新 HEAD\n"
+
+#: builtin/merge.c:361
+msgid "Writing SQUASH_MSG"
+msgstr "写入 SQUASH_MSG"
+
+#: builtin/merge.c:363
+msgid "Finishing SQUASH_MSG"
+msgstr "å®Œæˆ SQUASH_MSG"
+
+#: builtin/merge.c:386
+#, c-format
+msgid "No merge message -- not updating HEAD\n"
+msgstr "æ— åˆå¹¶ä¿¡æ¯ -- 未更新 HEAD\n"
+
+#: builtin/merge.c:437
+#, c-format
+msgid "'%s' does not point to a commit"
+msgstr "'%s' 没有指å‘一个æ交"
+
+#: builtin/merge.c:536
+#, c-format
+msgid "Bad branch.%s.mergeoptions string: %s"
+msgstr "åçš„ branch.%s.mergeoptions 字符串:%s"
+
+#: builtin/merge.c:629
+msgid "git write-tree failed to write a tree"
+msgstr "git write-tree 无法写入一树对象"
+
+#: builtin/merge.c:679
+msgid "failed to read the cache"
+msgstr "无法读å–缓存"
+
+#: builtin/merge.c:697
+msgid "Unable to write index."
+msgstr "ä¸èƒ½å†™ç´¢å¼•ã€‚"
+
+#: builtin/merge.c:710
+msgid "Not handling anything other than two heads merge."
+msgstr "ä¸èƒ½å¤„ç†ä¸¤ä¸ªå¤´åˆå¹¶ä¹‹å¤–的任何æ“作。"
+
+#: builtin/merge.c:724
+#, c-format
+msgid "Unknown option for merge-recursive: -X%s"
+msgstr "merge-recursive 的未知选项:-X%s"
+
+#: builtin/merge.c:738
+#, c-format
+msgid "unable to write %s"
+msgstr "ä¸èƒ½å†™ %s"
+
+#: builtin/merge.c:877
+#, c-format
+msgid "Could not read from '%s'"
+msgstr "ä¸èƒ½ä»Ž '%s' 读å–"
+
+#: builtin/merge.c:886
+#, c-format
+msgid "Not committing merge; use 'git commit' to complete the merge.\n"
+msgstr "未æ交åˆå¹¶ï¼Œä½¿ç”¨ 'git commit' 完æˆæ­¤æ¬¡åˆå¹¶ã€‚\n"
+
+#: builtin/merge.c:892
+msgid ""
+"Please enter a commit message to explain why this merge is necessary,\n"
+"especially if it merges an updated upstream into a topic branch.\n"
+"\n"
+"Lines starting with '#' will be ignored, and an empty message aborts\n"
+"the commit.\n"
+msgstr ""
+"请输入一个æ交信æ¯ä»¥è§£é‡Šæ­¤åˆå¹¶çš„å¿…è¦æ€§ï¼Œå°¤å…¶æ˜¯å°†ä¸€ä¸ªæ›´æ–°åŽçš„上游分支\n"
+"åˆå¹¶åˆ°ä¸»é¢˜åˆ†æ”¯ã€‚\n"
+"\n"
+"以 '#' 开头的行将被忽略,而且空æ交说明将会终止æ交。\n"
+
+#: builtin/merge.c:916
+msgid "Empty commit message."
+msgstr "空æ交信æ¯ã€‚"
+
+#: builtin/merge.c:928
+#, c-format
+msgid "Wonderful.\n"
+msgstr "太棒了。\n"
+
+#: builtin/merge.c:993
+#, c-format
+msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
+msgstr "自动åˆå¹¶å¤±è´¥ï¼Œä¿®æ­£å†²çªç„¶åŽæ交修正的结果。\n"
+
+#: builtin/merge.c:1009
+#, c-format
+msgid "'%s' is not a commit"
+msgstr "'%s' ä¸æ˜¯ä¸€ä¸ªæ交"
+
+#: builtin/merge.c:1050
+msgid "No current branch."
+msgstr "没有当å‰åˆ†æ”¯ã€‚"
+
+#: builtin/merge.c:1052
+msgid "No remote for the current branch."
+msgstr "当å‰åˆ†æ”¯æ²¡æœ‰å¯¹åº”的远程版本库。"
+
+#: builtin/merge.c:1054
+msgid "No default upstream defined for the current branch."
+msgstr "当å‰åˆ†æ”¯æ²¡æœ‰å®šä¹‰é»˜è®¤çš„上游分支。"
+
+#: builtin/merge.c:1059
+#, c-format
+msgid "No remote tracking branch for %s from %s"
+msgstr "%s 没有æ¥è‡ª %s 的远程跟踪分支"
+
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr "%s - ä¸èƒ½è¢«åˆå¹¶"
+
+#: builtin/merge.c:1214
+msgid "There is no merge to abort (MERGE_HEAD missing)."
+msgstr "没有è¦ç»ˆæ­¢çš„åˆå¹¶ï¼ˆMERGE_HEAD 丢失)。"
+
+#: builtin/merge.c:1230 git-pull.sh:31
+msgid ""
+"You have not concluded your merge (MERGE_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"您尚未结æŸæ‚¨çš„åˆå¹¶ï¼ˆå­˜åœ¨ MERGE_HEAD)。\n"
+"请在åˆå¹¶å‰å…ˆæ交您的修改。"
+
+#: builtin/merge.c:1233 git-pull.sh:34
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr "您尚未结æŸæ‚¨çš„åˆå¹¶ï¼ˆå­˜åœ¨ MERGE_HEAD)。"
+
+#: builtin/merge.c:1237
+msgid ""
+"You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+"Please, commit your changes before you can merge."
+msgstr ""
+"您尚未结æŸæ‚¨çš„拣选(存在 CHERRY_PICK_HEAD)。\n"
+"请在åˆå¹¶å‰å…ˆæ交您的修改。"
+
+#: builtin/merge.c:1240
+msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
+msgstr "您尚未结æŸæ‚¨çš„拣选(存在 CHERRY_PICK_HEAD)。"
+
+#: builtin/merge.c:1249
+msgid "You cannot combine --squash with --no-ff."
+msgstr "您ä¸èƒ½å°† --squash 与 --no-ff 共用。"
+
+#: builtin/merge.c:1254
+msgid "You cannot combine --no-ff with --ff-only."
+msgstr "您ä¸èƒ½å°† --no-ff 与 --ff-only 共用。"
+
+#: builtin/merge.c:1261
+msgid "No commit specified and merge.defaultToUpstream not set."
+msgstr "未指定æ交并且 merge.defaultToUpstream 未设置。"
+
+#: builtin/merge.c:1293
+msgid "Can merge only exactly one commit into empty head"
+msgstr "åªèƒ½å°†ä¸€ä¸ªæ交åˆå¹¶åˆ°ç©ºåˆ†æ”¯ä¸Š"
+
+#: builtin/merge.c:1296
+msgid "Squash commit into empty head not supported yet"
+msgstr "å°šä¸æ”¯æŒåˆ°ç©ºåˆ†æ”¯çš„压缩æ交"
+
+#: builtin/merge.c:1298
+msgid "Non-fast-forward commit does not make sense into an empty head"
+msgstr "到空分支的éžå¿«è¿›å¼æ交没有æ„义"
+
+#: builtin/merge.c:1413
+#, c-format
+msgid "Updating %s..%s\n"
+msgstr "æ›´æ–° %s..%s\n"
+
+#: builtin/merge.c:1451
+#, c-format
+msgid "Trying really trivial in-index merge...\n"
+msgstr "å°è¯•éžå¸¸å°çš„索引内åˆå¹¶...\n"
+
+#: builtin/merge.c:1458
+#, c-format
+msgid "Nope.\n"
+msgstr "无。\n"
+
+#: builtin/merge.c:1490
+msgid "Not possible to fast-forward, aborting."
+msgstr "无法快进,终止。"
+
+#: builtin/merge.c:1513 builtin/merge.c:1592
+#, c-format
+msgid "Rewinding the tree to pristine...\n"
+msgstr "将树回滚至原始状æ€...\n"
+
+#: builtin/merge.c:1517
+#, c-format
+msgid "Trying merge strategy %s...\n"
+msgstr "å°è¯•åˆå¹¶ç­–ç•¥ %s...\n"
+
+#: builtin/merge.c:1583
+#, c-format
+msgid "No merge strategy handled the merge.\n"
+msgstr "没有åˆå¹¶ç­–略处ç†æ­¤åˆå¹¶ã€‚\n"
+
+#: builtin/merge.c:1585
+#, c-format
+msgid "Merge with strategy %s failed.\n"
+msgstr "使用策略 %s åˆå¹¶å¤±è´¥ã€‚\n"
+
+#: builtin/merge.c:1594
+#, c-format
+msgid "Using the %s to prepare resolving by hand.\n"
+msgstr "使用 %s 以准备手工解决。\n"
+
+#: builtin/merge.c:1606
+#, c-format
+msgid "Automatic merge went well; stopped before committing as requested\n"
+msgstr "自动åˆå¹¶è¿›å±•é¡ºåˆ©ï¼ŒæŒ‰è¦æ±‚在æ交å‰åœæ­¢\n"
+
+#: builtin/mv.c:108
+#, c-format
+msgid "Checking rename of '%s' to '%s'\n"
+msgstr "检查 '%s' 到 '%s' çš„é‡å‘½å\n"
+
+#: builtin/mv.c:112
+msgid "bad source"
+msgstr "åçš„æº"
+
+#: builtin/mv.c:115
+msgid "can not move directory into itself"
+msgstr "ä¸èƒ½å°†ç›®å½•ç§»åŠ¨åˆ°è‡ªèº«"
+
+#: builtin/mv.c:118
+msgid "cannot move directory over file"
+msgstr "ä¸èƒ½å°†ç›®å½•ç§»åŠ¨åˆ°æ–‡ä»¶"
+
+#: builtin/mv.c:128
+#, c-format
+msgid "Huh? %.*s is in index?"
+msgstr "嗯?%.*s 在索引中?"
+
+#: builtin/mv.c:140
+msgid "source directory is empty"
+msgstr "æºç›®å½•ä¸ºç©º"
+
+#: builtin/mv.c:171
+msgid "not under version control"
+msgstr "ä¸åœ¨ç‰ˆæœ¬æŽ§åˆ¶ä¹‹ä¸‹"
+
+#: builtin/mv.c:173
+msgid "destination exists"
+msgstr "目标已存在"
+
+#: builtin/mv.c:181
+#, c-format
+msgid "overwriting '%s'"
+msgstr "覆盖 '%s'"
+
+#: builtin/mv.c:184
+msgid "Cannot overwrite"
+msgstr "ä¸èƒ½è¦†ç›–"
+
+#: builtin/mv.c:187
+msgid "multiple sources for the same target"
+msgstr "åŒä¸€ç›®æ ‡å…·æœ‰å¤šä¸ªæº"
+
+#: builtin/mv.c:202
+#, c-format
+msgid "%s, source=%s, destination=%s"
+msgstr "%s,æº=%s,目标=%s"
+
+#: builtin/mv.c:212
+#, c-format
+msgid "Renaming %s to %s\n"
+msgstr "é‡å‘½å %s 至 %s\n"
+
+#: builtin/mv.c:215 builtin/remote.c:731
+#, c-format
+msgid "renaming '%s' failed"
+msgstr "é‡å‘½å '%s' 失败"
+
+#: builtin/notes.c:139
+#, c-format
+msgid "unable to start 'show' for object '%s'"
+msgstr "ä¸èƒ½ä¸ºå¯¹è±¡ '%s' 开始 'show'"
+
+#: builtin/notes.c:145
+msgid "can't fdopen 'show' output fd"
+msgstr "ä¸èƒ½æ‰“å¼€ 'show' 输出文件å¥æŸ„"
+
+#: builtin/notes.c:155
+#, c-format
+msgid "failed to close pipe to 'show' for object '%s'"
+msgstr "无法为对象 '%s' çš„ 'show' 关闭管é“"
+
+#: builtin/notes.c:158
+#, c-format
+msgid "failed to finish 'show' for object '%s'"
+msgstr "无法为对象 '%s' å®Œæˆ 'show'"
+
+#: builtin/notes.c:175 builtin/tag.c:347
+#, c-format
+msgid "could not create file '%s'"
+msgstr "ä¸èƒ½åˆ›å»ºæ–‡ä»¶ '%s'"
+
+#: builtin/notes.c:189
+msgid "Please supply the note contents using either -m or -F option"
+msgstr "请通过 -m 或 -F 选项为注解æ供内容"
+
+#: builtin/notes.c:210 builtin/notes.c:973
+#, c-format
+msgid "Removing note for object %s\n"
+msgstr "删除对象 %s 的注解\n"
+
+#: builtin/notes.c:215
+msgid "unable to write note object"
+msgstr "ä¸èƒ½å†™æ³¨è§£å¯¹è±¡"
+
+#: builtin/notes.c:217
+#, c-format
+msgid "The note contents has been left in %s"
+msgstr "注解内容被留在文件 %s 中"
+
+#: builtin/notes.c:251 builtin/tag.c:542
+#, c-format
+msgid "cannot read '%s'"
+msgstr "ä¸èƒ½è¯»å– '%s'"
+
+#: builtin/notes.c:253 builtin/tag.c:545
+#, c-format
+msgid "could not open or read '%s'"
+msgstr "ä¸èƒ½æ‰“å¼€æˆ–è¯»å– '%s'"
+
+#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447
+#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644
+#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766
+#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:558
+#, c-format
+msgid "Failed to resolve '%s' as a valid ref."
+msgstr "æ— æ³•è§£æž '%s' 为一个有效引用。"
+
+#: builtin/notes.c:275
+#, c-format
+msgid "Failed to read object '%s'."
+msgstr "无法读å–对象 '%s'。"
+
+#: builtin/notes.c:299
+msgid "Cannot commit uninitialized/unreferenced notes tree"
+msgstr "ä¸èƒ½æ交未åˆå§‹åŒ–/未引用的注解树"
+
+#: builtin/notes.c:340
+#, c-format
+msgid "Bad notes.rewriteMode value: '%s'"
+msgstr "åçš„ notes.rewriteMode 值:'%s'"
+
+#: builtin/notes.c:350
+#, c-format
+msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
+msgstr "æ‹’ç»å‘ %s(在 refs/notes/ 之外)写入注解"
+
+#. TRANSLATORS: The first %s is the name of the
+#. environment variable, the second %s is its value
+#: builtin/notes.c:377
+#, c-format
+msgid "Bad %s value: '%s'"
+msgstr "åçš„ %s 值:'%s'"
+
+#: builtin/notes.c:441
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr "éžæ³•çš„输入行:'%s'。"
+
+#: builtin/notes.c:456
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr "无法从 '%s' 到 '%s' æ‹·è´æ³¨è§£"
+
+#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627
+#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759
+#: builtin/notes.c:1033
+msgid "too many parameters"
+msgstr "å‚数太多"
+
+#: builtin/notes.c:513 builtin/notes.c:772
+#, c-format
+msgid "No note found for object %s."
+msgstr "未å‘现对象 %s 的注解。"
+
+#: builtin/notes.c:580
+#, c-format
+msgid ""
+"Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr "ä¸èƒ½æ·»åŠ æ³¨è§£ã€‚å‘现对象 %s 已存在注解。使用 '-f' 覆盖现存注解"
+
+#: builtin/notes.c:585 builtin/notes.c:662
+#, c-format
+msgid "Overwriting existing notes for object %s\n"
+msgstr "覆盖对象 %s 现存注解\n"
+
+#: builtin/notes.c:635
+msgid "too few parameters"
+msgstr "å‚数太少"
+
+#: builtin/notes.c:656
+#, c-format
+msgid ""
+"Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite "
+"existing notes"
+msgstr "ä¸èƒ½æ‹·è´æ³¨è§£ã€‚å‘现对象 %s 已存在注解。使用 '-f' 覆盖现存注解"
+
+#: builtin/notes.c:668
+#, c-format
+msgid "Missing notes on source object %s. Cannot copy."
+msgstr "æºå¯¹è±¡ %s 缺少注解。ä¸èƒ½æ‹·è´ã€‚"
+
+#: builtin/notes.c:717
+#, c-format
+msgid ""
+"The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
+"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
+msgstr ""
+"å­å‘½ä»¤ 'edit' 的选项 -m/-F/-c/-C 已弃用。\n"
+"请æ¢ç”¨ 'git notes add -f -m/-F/-c/-C'。\n"
+
+#: builtin/notes.c:971
+#, c-format
+msgid "Object %s has no note\n"
+msgstr "对象 %s 没有注解\n"
+
+#: builtin/notes.c:1103 builtin/remote.c:1598
+#, c-format
+msgid "Unknown subcommand: %s"
+msgstr "未知å­å‘½ä»¤ï¼š%s"
+
+#: builtin/pack-objects.c:2337
+#, c-format
+msgid "unsupported index version %s"
+msgstr "ä¸æ”¯æŒçš„索引版本 %s"
+
+#: builtin/pack-objects.c:2341
+#, c-format
+msgid "bad index version '%s'"
+msgstr "å的索引版本 '%s'"
+
+#: builtin/pack-objects.c:2364
+#, c-format
+msgid "option %s does not accept negative form"
+msgstr "选项 %s ä¸æŽ¥å—å¦å®šæ ¼å¼"
+
+#: builtin/pack-objects.c:2368
+#, c-format
+msgid "unable to parse value '%s' for option %s"
+msgstr "ä¸èƒ½è§£æžé€‰é¡¹ %1$s 的值 '%2$s'"
+
+#: builtin/push.c:45
+msgid "tag shorthand without <tag>"
+msgstr "tag 简写没有跟 <tag> å‚æ•°"
+
+#: builtin/push.c:64
+msgid "--delete only accepts plain target ref names"
+msgstr "--delete åªæŽ¥å—简å•çš„目标引用å"
+
+#: builtin/push.c:99
+msgid ""
+"\n"
+"To choose either option permanently, see push.default in 'git help config'."
+msgstr ""
+"\n"
+"为了永久地选择任一选项,å‚è§ 'git help config' 中的 push.default。"
+
+#: builtin/push.c:102
+#, c-format
+msgid ""
+"The upstream branch of your current branch does not match\n"
+"the name of your current branch. To push to the upstream branch\n"
+"on the remote, use\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"To push to the branch of the same name on the remote, use\n"
+"\n"
+" git push %s %s\n"
+"%s"
+msgstr ""
+"如果您当å‰åˆ†æ”¯çš„上游分支和您当å‰åˆ†æ”¯åä¸åŒ¹é…,为推é€åˆ°è¿œç¨‹çš„\n"
+"上游分支,使用\n"
+"\n"
+" git push %s HEAD:%s\n"
+"\n"
+"为推é€è‡³è¿œç¨‹åŒå分支,使用\n"
+"\n"
+" git push %s %s\n"
+"%s"
+
+#: builtin/push.c:121
+#, c-format
+msgid ""
+"You are not currently on a branch.\n"
+"To push the history leading to the current (detached HEAD)\n"
+"state now, use\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+msgstr ""
+"您当å‰ä¸åœ¨ä¸€ä¸ªåˆ†æ”¯ä¸Šã€‚\n"
+"现在为推é€å½“å‰ï¼ˆåˆ†ç¦»å¤´æŒ‡é’ˆï¼‰çš„历å²ï¼Œä½¿ç”¨\n"
+"\n"
+" git push %s HEAD:<name-of-remote-branch>\n"
+
+#: builtin/push.c:128
+#, c-format
+msgid ""
+"The current branch %s has no upstream branch.\n"
+"To push the current branch and set the remote as upstream, use\n"
+"\n"
+" git push --set-upstream %s %s\n"
+msgstr ""
+"当å‰åˆ†æ”¯ %s 没有对应的上游分支。\n"
+"为推é€å½“å‰åˆ†æ”¯å¹¶å»ºç«‹ä¸Žè¿œç¨‹ä¸Šæ¸¸çš„跟踪,使用\n"
+"\n"
+" git push --set-upstream %s %s\n"
+
+#: builtin/push.c:136
+#, c-format
+msgid "The current branch %s has multiple upstream branches, refusing to push."
+msgstr "当å‰åˆ†æ”¯ %s 有多个上游分支,拒ç»æŽ¨é€ã€‚"
+
+#: builtin/push.c:139
+#, c-format
+msgid ""
+"You are pushing to remote '%s', which is not the upstream of\n"
+"your current branch '%s', without telling me what to push\n"
+"to update which remote branch."
+msgstr ""
+"您正推é€è‡³è¿œç¨‹ '%s'(其并éžå½“å‰åˆ†æ”¯ '%s' 的上游),\n"
+"而没有告诉我è¦æŽ¨é€ä»€ä¹ˆã€æ›´æ–°å“ªä¸ªè¿œç¨‹åˆ†æ”¯ã€‚"
+
+#: builtin/push.c:174
+msgid ""
+"You didn't specify any refspecs to push, and push.default is \"nothing\"."
+msgstr "您没有为推é€æŒ‡å®šä»»ä½•å¼•ç”¨è¡¨è¾¾å¼ï¼Œå¹¶ä¸” push.default 为 \"nothing\"。"
+
+#: builtin/push.c:181
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"更新被拒ç»ï¼Œå› ä¸ºæ‚¨å½“å‰åˆ†æ”¯çš„最新æ交è½åŽäºŽå…¶å¯¹åº”的远程分支。\n"
+"å†æ¬¡æŽ¨é€å‰ï¼Œå…ˆä¸Žè¿œç¨‹å˜æ›´åˆå¹¶ï¼ˆå¦‚ 'git pull')。详è§\n"
+"'git push --help' 中的 'Note about fast-forwards' å°èŠ‚。"
+
+#: builtin/push.c:187
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+"更新被拒ç»ï¼Œå› ä¸ºæŽ¨é€çš„一个分支的最新æ交è½åŽäºŽå…¶å¯¹åº”的远程分支。\n"
+"如果您并éžæœ‰æ„推é€è¯¥åˆ†æ”¯ï¼Œæ‚¨å¯ä»¥åœ¨æŽ¨é€æ—¶æŒ‡å®šè¦æŽ¨é€çš„分支,或者将\n"
+"é…ç½®å˜é‡ 'push.default' 设置为 'current' 或 'upstream' 以便åªæŽ¨é€å½“å‰åˆ†æ”¯ã€‚"
+
+#: builtin/push.c:193
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"更新被拒ç»ï¼Œå› ä¸ºæŽ¨é€çš„一个分支的最新æ交è½åŽäºŽå…¶å¯¹åº”的远程分支。\n"
+"检出该分支并与远程å˜æ›´åˆå¹¶ï¼ˆå¦‚ 'git pull'),然åŽå†æŽ¨é€ã€‚详è§\n"
+"'git push --help' 中的 'Note about fast-forwards' å°èŠ‚。"
+
+#: builtin/push.c:233
+#, c-format
+msgid "Pushing to %s\n"
+msgstr "推é€åˆ° %s\n"
+
+#: builtin/push.c:237
+#, c-format
+msgid "failed to push some refs to '%s'"
+msgstr "无法推é€ä¸€äº›å¼•ç”¨åˆ° '%s'"
+
+#: builtin/push.c:269
+#, c-format
+msgid "bad repository '%s'"
+msgstr "å的版本库 '%s'"
+
+#: builtin/push.c:270
+msgid ""
+"No configured push destination.\n"
+"Either specify the URL from the command-line or configure a remote "
+"repository using\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"and then push using the remote name\n"
+"\n"
+" git push <name>\n"
+msgstr ""
+"没有é…置推é€ç›®æ ‡ã€‚\n"
+"或者通过命令行指定URL,或者用下é¢å‘½ä»¤é…置一个远程版本库\n"
+"\n"
+" git remote add <name> <url>\n"
+"\n"
+"然åŽä½¿ç”¨è¯¥è¿œç¨‹ç‰ˆæœ¬åº“å执行推é€\n"
+"\n"
+" git push <name>\n"
+
+#: builtin/push.c:285
+msgid "--all and --tags are incompatible"
+msgstr "--all å’Œ --tags ä¸å…¼å®¹"
+
+#: builtin/push.c:286
+msgid "--all can't be combined with refspecs"
+msgstr "--all ä¸èƒ½å’Œå¼•ç”¨è¡¨è¾¾å¼å…±ç”¨"
+
+#: builtin/push.c:291
+msgid "--mirror and --tags are incompatible"
+msgstr "--mirror å’Œ --tags ä¸å…¼å®¹"
+
+#: builtin/push.c:292
+msgid "--mirror can't be combined with refspecs"
+msgstr "--mirror ä¸èƒ½å’Œå¼•ç”¨è¡¨è¾¾å¼å…±ç”¨"
+
+#: builtin/push.c:297
+msgid "--all and --mirror are incompatible"
+msgstr "--all å’Œ --mirror ä¸å…¼å®¹"
+
+#: builtin/push.c:385
+msgid "--delete is incompatible with --all, --mirror and --tags"
+msgstr "--delete 与 --allã€--mirror åŠ --tags ä¸å…¼å®¹"
+
+#: builtin/push.c:387
+msgid "--delete doesn't make sense without any refs"
+msgstr "--delete 未接任何引用没有æ„义"
+
+#: builtin/remote.c:98
+#, c-format
+msgid "Updating %s"
+msgstr "更新 %s 中"
+
+#: builtin/remote.c:130
+msgid ""
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead"
+msgstr ""
+"--mirror 选项å±é™©ä¸”过时,请使用 --mirror=fetch\n"
+"\t 或 --mirror=push"
+
+#: builtin/remote.c:147
+#, c-format
+msgid "unknown mirror argument: %s"
+msgstr "未知的镜åƒå‚数:%s"
+
+#: builtin/remote.c:185
+msgid "specifying a master branch makes no sense with --mirror"
+msgstr "指定一个 master 分支并使用 --mirror 选项没有æ„义"
+
+#: builtin/remote.c:187
+msgid "specifying branches to track makes sense only with fetch mirrors"
+msgstr "指定è¦è·Ÿè¸ªçš„分支åªåœ¨ä¸ŽèŽ·å–é•œåƒå…±ç”¨æ‰æœ‰æ„义"
+
+#: builtin/remote.c:195 builtin/remote.c:646
+#, c-format
+msgid "remote %s already exists."
+msgstr "远程 %s å·²ç»å­˜åœ¨ã€‚"
+
+#: builtin/remote.c:199 builtin/remote.c:650
+#, c-format
+msgid "'%s' is not a valid remote name"
+msgstr "'%s' ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„远程å称"
+
+#: builtin/remote.c:243
+#, c-format
+msgid "Could not setup master '%s'"
+msgstr "无法设置 master '%s'"
+
+#: builtin/remote.c:299
+#, c-format
+msgid "more than one %s"
+msgstr "多于一个 %s"
+
+#: builtin/remote.c:339
+#, c-format
+msgid "Could not get fetch map for refspec %s"
+msgstr "æ— æ³•å¾—åˆ°å¼•ç”¨è¡¨è¾¾å¼ %s 的获å–列表"
+
+#: builtin/remote.c:440 builtin/remote.c:448
+msgid "(matching)"
+msgstr "(匹é…)"
+
+#: builtin/remote.c:452
+msgid "(delete)"
+msgstr "(删除)"
+
+#: builtin/remote.c:595 builtin/remote.c:601 builtin/remote.c:607
+#, c-format
+msgid "Could not append '%s' to '%s'"
+msgstr "ä¸èƒ½æ·»åŠ  '%s' 至 '%s'"
+
+#: builtin/remote.c:639 builtin/remote.c:792 builtin/remote.c:890
+#, c-format
+msgid "No such remote: %s"
+msgstr "没有这样的远程:%s"
+
+#: builtin/remote.c:656
+#, c-format
+msgid "Could not rename config section '%s' to '%s'"
+msgstr "ä¸èƒ½é‡å‘½åé…ç½®å°èŠ‚ '%s' 到 '%s'"
+
+#: builtin/remote.c:662 builtin/remote.c:799
+#, c-format
+msgid "Could not remove config section '%s'"
+msgstr "ä¸èƒ½ç§»é™¤é…ç½®å°èŠ‚ '%s'"
+
+#: builtin/remote.c:677
+#, c-format
+msgid ""
+"Not updating non-default fetch refspec\n"
+"\t%s\n"
+"\tPlease update the configuration manually if necessary."
+msgstr ""
+"没有更新éžé»˜è®¤çš„获å–引用表达å¼\n"
+"\t%s\n"
+"\t如果必è¦è¯·æ‰‹åŠ¨æ›´æ–°é…置。"
+
+#: builtin/remote.c:683
+#, c-format
+msgid "Could not append '%s'"
+msgstr "ä¸èƒ½è¿½åŠ  '%s'"
+
+#: builtin/remote.c:694
+#, c-format
+msgid "Could not set '%s'"
+msgstr "ä¸èƒ½è®¾ç½® '%s'"
+
+#: builtin/remote.c:716
+#, c-format
+msgid "deleting '%s' failed"
+msgstr "删除 '%s' 失败"
+
+#: builtin/remote.c:750
+#, c-format
+msgid "creating '%s' failed"
+msgstr "创建 '%s' 失败"
+
+#: builtin/remote.c:764
+#, c-format
+msgid "Could not remove branch %s"
+msgstr "无法移除分支 %s"
+
+#: builtin/remote.c:834
+msgid ""
+"Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+"to delete it, use:"
+msgid_plural ""
+"Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+"to delete them, use:"
+msgstr[0] "注æ„:ref/remotes 层级之外的一个分支未被移除。è¦åˆ é™¤å®ƒï¼Œä½¿ç”¨ï¼š"
+msgstr[1] "注æ„:ref/remotes 层级之外的一些分支未被移除。è¦åˆ é™¤å®ƒä»¬ï¼Œä½¿ç”¨ï¼š"
+
+#: builtin/remote.c:943
+#, c-format
+msgid " new (next fetch will store in remotes/%s)"
+msgstr " 新的(下一次获å–将存储于 remotes/%s)"
+
+#: builtin/remote.c:946
+msgid " tracked"
+msgstr " 已跟踪"
+
+#: builtin/remote.c:948
+msgid " stale (use 'git remote prune' to remove)"
+msgstr " 过时(使用 'git remote prune' æ¥ç§»é™¤ï¼‰"
+
+#: builtin/remote.c:950
+msgid " ???"
+msgstr " ???"
+
+#: builtin/remote.c:991
+#, c-format
+msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
+msgstr "无效的 branch.%s.merge,ä¸èƒ½å˜åŸºåˆ°ä¸€ä¸ªä»¥ä¸Šçš„分支"
+
+#: builtin/remote.c:998
+#, c-format
+msgid "rebases onto remote %s"
+msgstr "å˜åŸºåˆ°è¿œç¨‹ %s"
+
+#: builtin/remote.c:1001
+#, c-format
+msgid " merges with remote %s"
+msgstr " 与远程 %s åˆå¹¶"
+
+#: builtin/remote.c:1002
+msgid " and with remote"
+msgstr " 且有远程"
+
+#: builtin/remote.c:1004
+#, c-format
+msgid "merges with remote %s"
+msgstr "与远程 %s åˆå¹¶"
+
+#: builtin/remote.c:1005
+msgid " and with remote"
+msgstr " 且有远程"
+
+#: builtin/remote.c:1051
+msgid "create"
+msgstr "创建"
+
+#: builtin/remote.c:1054
+msgid "delete"
+msgstr "删除"
+
+#: builtin/remote.c:1058
+msgid "up to date"
+msgstr "最新"
+
+#: builtin/remote.c:1061
+msgid "fast-forwardable"
+msgstr "å¯å¿«è¿›"
+
+#: builtin/remote.c:1064
+msgid "local out of date"
+msgstr "本地已过时"
+
+#: builtin/remote.c:1071
+#, c-format
+msgid " %-*s forces to %-*s (%s)"
+msgstr " %-*s 强制推é€è‡³ %-*s (%s)"
+
+#: builtin/remote.c:1074
+#, c-format
+msgid " %-*s pushes to %-*s (%s)"
+msgstr " %-*s 推é€è‡³ %-*s (%s)"
+
+#: builtin/remote.c:1078
+#, c-format
+msgid " %-*s forces to %s"
+msgstr " %-*s 强制推é€è‡³ %s"
+
+#: builtin/remote.c:1081
+#, c-format
+msgid " %-*s pushes to %s"
+msgstr " %-*s 推é€è‡³ %s"
+
+#: builtin/remote.c:1118
+#, c-format
+msgid "* remote %s"
+msgstr "* 远程 %s"
+
+#: builtin/remote.c:1119
+#, c-format
+msgid " Fetch URL: %s"
+msgstr " 获å–地å€ï¼š%s"
+
+#: builtin/remote.c:1120 builtin/remote.c:1285
+msgid "(no URL)"
+msgstr "(æ—  URL)"
+
+#: builtin/remote.c:1129 builtin/remote.c:1131
+#, c-format
+msgid " Push URL: %s"
+msgstr " 推é€åœ°å€ï¼š%s"
+
+#: builtin/remote.c:1133 builtin/remote.c:1135 builtin/remote.c:1137
+#, c-format
+msgid " HEAD branch: %s"
+msgstr " HEAD分支:%s"
+
+#: builtin/remote.c:1139
+#, c-format
+msgid ""
+" HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
+msgstr " HEAD 分支(远程 HEAD 模糊,å¯èƒ½æ˜¯ä¸‹åˆ—中的一个):\n"
+
+#: builtin/remote.c:1151
+#, c-format
+msgid " Remote branch:%s"
+msgid_plural " Remote branches:%s"
+msgstr[0] " 远程分支:%s"
+msgstr[1] " 远程分支:%s"
+
+# 译者:中文字符串拼接,å¯åˆ é™¤å‰å¯¼ç©ºæ ¼
+#: builtin/remote.c:1154 builtin/remote.c:1181
+msgid " (status not queried)"
+msgstr "(状æ€æœªæŸ¥è¯¢ï¼‰"
+
+#: builtin/remote.c:1163
+msgid " Local branch configured for 'git pull':"
+msgid_plural " Local branches configured for 'git pull':"
+msgstr[0] " 为 'git pull' é…置的本地分支:"
+msgstr[1] " 为 'git pull' é…置的本地分支:"
+
+#: builtin/remote.c:1171
+msgid " Local refs will be mirrored by 'git push'"
+msgstr " 本地引用将在 'git push' 时被镜åƒ"
+
+#: builtin/remote.c:1178
+#, c-format
+msgid " Local ref configured for 'git push'%s:"
+msgid_plural " Local refs configured for 'git push'%s:"
+msgstr[0] " 为 'git push' é…置的本地引用%s:"
+msgstr[1] " 为 'git push' é…置的本地引用%s:"
+
+#: builtin/remote.c:1216
+msgid "Cannot determine remote HEAD"
+msgstr "无法确定远程 HEAD"
+
+#: builtin/remote.c:1218
+msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
+msgstr "多个远程 HEAD 分支。请明确地选择一个用命令:"
+
+#: builtin/remote.c:1228
+#, c-format
+msgid "Could not delete %s"
+msgstr "无法删除 %s"
+
+#: builtin/remote.c:1236
+#, c-format
+msgid "Not a valid ref: %s"
+msgstr "ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆå¼•ç”¨ï¼š%s"
+
+#: builtin/remote.c:1238
+#, c-format
+msgid "Could not setup %s"
+msgstr "ä¸èƒ½è®¾ç½® %s"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: builtin/remote.c:1274
+#, c-format
+msgid " %s will become dangling!"
+msgstr " %s å°†æˆä¸ºæ‚¬ç©ºçŠ¶æ€ï¼"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: builtin/remote.c:1275
+#, c-format
+msgid " %s has become dangling!"
+msgstr " %s å·²æˆä¸ºæ‚¬ç©ºçŠ¶æ€ï¼"
+
+#: builtin/remote.c:1281
+#, c-format
+msgid "Pruning %s"
+msgstr "修剪 %s"
+
+#: builtin/remote.c:1282
+#, c-format
+msgid "URL: %s"
+msgstr "URL:%s"
+
+#: builtin/remote.c:1295
+#, c-format
+msgid " * [would prune] %s"
+msgstr " * [将删除] %s"
+
+#: builtin/remote.c:1298
+#, c-format
+msgid " * [pruned] %s"
+msgstr " * [已删除] %s"
+
+#: builtin/remote.c:1387 builtin/remote.c:1461
+#, c-format
+msgid "No such remote '%s'"
+msgstr "没有此远程 '%s'"
+
+#: builtin/remote.c:1414
+msgid "no remote specified"
+msgstr "未指定远程"
+
+#: builtin/remote.c:1447
+msgid "--add --delete doesn't make sense"
+msgstr "--add --delete æ— æ„义"
+
+#: builtin/remote.c:1487
+#, c-format
+msgid "Invalid old URL pattern: %s"
+msgstr "无效的旧URL匹é…模版:%s"
+
+#: builtin/remote.c:1495
+#, c-format
+msgid "No such URL found: %s"
+msgstr "未找到此URL:%s"
+
+#: builtin/remote.c:1497
+msgid "Will not delete all non-push URLs"
+msgstr "å°†ä¸ä¼šåˆ é™¤æ‰€æœ‰éžæŽ¨é€URL地å€"
+
+#: builtin/reset.c:33
+msgid "mixed"
+msgstr "æ··æ‚"
+
+#: builtin/reset.c:33
+msgid "soft"
+msgstr "软性"
+
+#: builtin/reset.c:33
+msgid "hard"
+msgstr "硬性"
+
+#: builtin/reset.c:33
+msgid "merge"
+msgstr "åˆå¹¶"
+
+#: builtin/reset.c:33
+msgid "keep"
+msgstr "ä¿æŒ"
+
+#: builtin/reset.c:77
+msgid "You do not have a valid HEAD."
+msgstr "您没有一个有效的 HEAD。"
+
+#: builtin/reset.c:79
+msgid "Failed to find tree of HEAD."
+msgstr "无法找到 HEAD 指å‘的树。"
+
+#: builtin/reset.c:85
+#, c-format
+msgid "Failed to find tree of %s."
+msgstr "无法找到 %s 指å‘的树。"
+
+#: builtin/reset.c:96
+msgid "Could not write new index file."
+msgstr "ä¸èƒ½å†™å…¥æ–°çš„索引文件。"
+
+#: builtin/reset.c:106
+#, c-format
+msgid "HEAD is now at %s"
+msgstr "HEAD 现在ä½äºŽ %s"
+
+#: builtin/reset.c:130
+msgid "Could not read index"
+msgstr "ä¸èƒ½è¯»å–索引"
+
+#: builtin/reset.c:133
+msgid "Unstaged changes after reset:"
+msgstr "é‡ç½®åŽæ’¤å‡ºæš‚存区的å˜æ›´ï¼š"
+
+# 译者:汉字之间无空格,故删除%så‰åŽç©ºæ ¼
+#: builtin/reset.c:223
+#, c-format
+msgid "Cannot do a %s reset in the middle of a merge."
+msgstr "在åˆå¹¶è¿‡ç¨‹ä¸­ä¸èƒ½åš%sé‡ç½®æ“作。"
+
+#: builtin/reset.c:297
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr "ä¸èƒ½è§£æžå¯¹è±¡ '%s'。"
+
+#: builtin/reset.c:302
+msgid "--patch is incompatible with --{hard,mixed,soft}"
+msgstr "--patch 与 --{hard,mixed,soft} ä¸å…¼å®¹"
+
+#: builtin/reset.c:311
+msgid "--mixed with paths is deprecated; use 'git reset -- <paths>' instead."
+msgstr "--mixed 带路径已弃用,代之以 'git reset -- <paths>'。"
+
+# 译者:汉字之间无空格,故删除%så‰åŽç©ºæ ¼
+#: builtin/reset.c:313
+#, c-format
+msgid "Cannot do %s reset with paths."
+msgstr "ä¸èƒ½å¸¦è·¯å¾„进行%sé‡ç½®ã€‚"
+
+# 译者:汉字之间无空格,故删除%så‰åŽç©ºæ ¼
+#: builtin/reset.c:325
+#, c-format
+msgid "%s reset is not allowed in a bare repository"
+msgstr "ä¸èƒ½å¯¹è£¸ç‰ˆæœ¬åº“进行%sé‡ç½®"
+
+#: builtin/reset.c:341
+#, c-format
+msgid "Could not reset index file to revision '%s'."
+msgstr "ä¸èƒ½é‡ç½®ç´¢å¼•æ–‡ä»¶è‡³ç‰ˆæœ¬ '%s'。"
+
+#: builtin/revert.c:70 builtin/revert.c:92
+#, c-format
+msgid "%s: %s cannot be used with %s"
+msgstr "%s:%s ä¸èƒ½å’Œ %s 共用"
+
+#: builtin/revert.c:131
+msgid "program error"
+msgstr "程åºé”™è¯¯"
+
+#: builtin/revert.c:221
+msgid "revert failed"
+msgstr "还原失败"
+
+#: builtin/revert.c:236
+msgid "cherry-pick failed"
+msgstr "拣选失败"
+
+#: builtin/rm.c:109
+#, c-format
+msgid ""
+"'%s' has staged content different from both the file and the HEAD\n"
+"(use -f to force removal)"
+msgstr ""
+"'%s' æš‚å­˜çš„å†…å®¹å’Œå·¥ä½œåŒºåŠ HEAD 中的都ä¸ä¸€æ ·\n"
+"(使用 -f 强制删除)"
+
+#: builtin/rm.c:115
+#, c-format
+msgid ""
+"'%s' has changes staged in the index\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"'%s' 有å˜æ›´å·²æš‚存至索引中\n"
+"(使用 --cached ä¿å­˜æ–‡ä»¶ï¼Œæˆ–用 -f 强制删除)"
+
+#: builtin/rm.c:119
+#, c-format
+msgid ""
+"'%s' has local modifications\n"
+"(use --cached to keep the file, or -f to force removal)"
+msgstr ""
+"'%s' 有本地修改\n"
+"(使用 --cached ä¿å­˜æ–‡ä»¶ï¼Œæˆ–用 -f 强制删除)"
+
+#: builtin/rm.c:194
+#, c-format
+msgid "not removing '%s' recursively without -r"
+msgstr "未æä¾› -r 选项ä¸ä¼šé€’归删除 '%s'"
+
+#: builtin/rm.c:230
+#, c-format
+msgid "git rm: unable to remove %s"
+msgstr "git rm:ä¸èƒ½åˆ é™¤ %s"
+
+#: builtin/shortlog.c:157
+#, c-format
+msgid "Missing author: %s"
+msgstr "缺少作者:%s"
+
+#: builtin/tag.c:60
+#, c-format
+msgid "malformed object at '%s'"
+msgstr "éžæ³•çš„对象于 '%s'"
+
+#: builtin/tag.c:207
+#, c-format
+msgid "tag name too long: %.*s..."
+msgstr "tag å字太长:%.*s..."
+
+#: builtin/tag.c:212
+#, c-format
+msgid "tag '%s' not found."
+msgstr "tag '%s' 未å‘现。"
+
+#: builtin/tag.c:227
+#, c-format
+msgid "Deleted tag '%s' (was %s)\n"
+msgstr "已删除 tag '%s'(曾为 %s)\n"
+
+#: builtin/tag.c:239
+#, c-format
+msgid "could not verify the tag '%s'"
+msgstr "ä¸èƒ½æ ¡éªŒ tag '%s'"
+
+#: builtin/tag.c:249
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be ignored.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# 输入一个 tag 说明\n"
+"# 以 '#' 开头的行将被忽略。\n"
+"#\n"
+
+#: builtin/tag.c:256
+msgid ""
+"\n"
+"#\n"
+"# Write a tag message\n"
+"# Lines starting with '#' will be kept; you may remove them yourself if you "
+"want to.\n"
+"#\n"
+msgstr ""
+"\n"
+"#\n"
+"# 输入一个 tag 说明\n"
+"# 以 '#' 开头的行将被忽略,您å¯ä»¥åˆ é™¤å®ƒä»¬å¦‚果您想这样åšã€‚\n"
+"#\n"
+
+#: builtin/tag.c:298
+msgid "unable to sign the tag"
+msgstr "无法签署 tag"
+
+#: builtin/tag.c:300
+msgid "unable to write tag file"
+msgstr "无法写 tag 文件"
+
+#: builtin/tag.c:325
+msgid "bad object type."
+msgstr "å的对象类型。"
+
+#: builtin/tag.c:338
+msgid "tag header too big."
+msgstr "tag 头信æ¯å¤ªå¤§ã€‚"
+
+#: builtin/tag.c:370
+msgid "no tag message?"
+msgstr "无 tag 说明?"
+
+#: builtin/tag.c:376
+#, c-format
+msgid "The tag message has been left in %s\n"
+msgstr "tag 说明被ä¿ç•™åœ¨ %s\n"
+
+#: builtin/tag.c:425
+msgid "switch 'points-at' requires an object"
+msgstr "开关 'points-at' 需è¦ä¸€ä¸ªå¯¹è±¡"
+
+#: builtin/tag.c:427
+#, c-format
+msgid "malformed object name '%s'"
+msgstr "éžæ³•çš„对象å '%s'"
+
+#: builtin/tag.c:506
+msgid "--column and -n are incompatible"
+msgstr "--column å’Œ -n ä¸å…¼å®¹"
+
+#: builtin/tag.c:523
+msgid "-n option is only allowed with -l."
+msgstr "-n 选项åªå…许和 -l 共用。"
+
+#: builtin/tag.c:525
+msgid "--contains option is only allowed with -l."
+msgstr "--contains 选项åªå…许和 -l 共用。"
+
+#: builtin/tag.c:527
+msgid "--points-at option is only allowed with -l."
+msgstr "--points-at 选项åªå…许和 -l 共用。"
+
+#: builtin/tag.c:535
+msgid "only one -F or -m option is allowed."
+msgstr "åªå…许一个 -F 或 -m 选项。"
+
+#: builtin/tag.c:555
+msgid "too many params"
+msgstr "太多å‚æ•°"
+
+#: builtin/tag.c:561
+#, c-format
+msgid "'%s' is not a valid tag name."
+msgstr "'%s' ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„tagå称。"
+
+#: builtin/tag.c:566
+#, c-format
+msgid "tag '%s' already exists"
+msgstr "tag '%s' 已存在"
+
+#: builtin/tag.c:584
+#, c-format
+msgid "%s: cannot lock the ref"
+msgstr "%s:ä¸èƒ½é”定引用"
+
+#: builtin/tag.c:586
+#, c-format
+msgid "%s: cannot update the ref"
+msgstr "%s:ä¸èƒ½æ›´æ–°å¼•ç”¨"
+
+#: builtin/tag.c:588
+#, c-format
+msgid "Updated tag '%s' (was %s)\n"
+msgstr "已更新tag '%s'(曾为 %s)\n"
+
+#: git.c:16
+msgid "See 'git help <command>' for more information on a specific command."
+msgstr "å‚è§ 'git help <command>' 以获得该特定命令的详细信æ¯ã€‚"
+
+#: parse-options.h:133 parse-options.h:235
+msgid "n"
+msgstr "æ•°å­—"
+
+#: parse-options.h:141
+msgid "time"
+msgstr "时间"
+
+#: parse-options.h:149
+msgid "file"
+msgstr "文件"
+
+#: parse-options.h:151
+msgid "when"
+msgstr "何时"
+
+#: parse-options.h:156
+msgid "no-op (backward compatibility)"
+msgstr "空æ“作(å‘åŽå…¼å®¹ï¼‰"
+
+#: parse-options.h:228
+msgid "be more verbose"
+msgstr "更加详细"
+
+#: parse-options.h:230
+msgid "be more quiet"
+msgstr "更加安é™"
+
+#: parse-options.h:236
+msgid "use <n> digits to display SHA-1s"
+msgstr "用 <n> ä½æ•°å­—显示 SHA-1 哈希值"
+
+#: common-cmds.h:8
+msgid "Add file contents to the index"
+msgstr "添加文件内容至索引"
+
+#: common-cmds.h:9
+msgid "Find by binary search the change that introduced a bug"
+msgstr "通过二分查找定ä½å¼•å…¥ bug çš„å˜æ›´"
+
+#: common-cmds.h:10
+msgid "List, create, or delete branches"
+msgstr "列出ã€åˆ›å»ºæˆ–删除分支"
+
+#: common-cmds.h:11
+msgid "Checkout a branch or paths to the working tree"
+msgstr "检出一个分支或路径到工作区"
+
+#: common-cmds.h:12
+msgid "Clone a repository into a new directory"
+msgstr "克隆一个版本库到一个新目录"
+
+#: common-cmds.h:13
+msgid "Record changes to the repository"
+msgstr "记录å˜æ›´åˆ°ç‰ˆæœ¬åº“"
+
+#: common-cmds.h:14
+msgid "Show changes between commits, commit and working tree, etc"
+msgstr "显示æ交之间ã€æ交和工作区之间等的差异"
+
+#: common-cmds.h:15
+msgid "Download objects and refs from another repository"
+msgstr "从å¦å¤–一个版本库下载对象和引用"
+
+#: common-cmds.h:16
+msgid "Print lines matching a pattern"
+msgstr "输出和模å¼åŒ¹é…çš„è¡Œ"
+
+#: common-cmds.h:17
+msgid "Create an empty git repository or reinitialize an existing one"
+msgstr "创建一个空的 git 版本库或者é‡æ–°åˆå§‹åŒ–一个"
+
+#: common-cmds.h:18
+msgid "Show commit logs"
+msgstr "显示æ交日志"
+
+#: common-cmds.h:19
+msgid "Join two or more development histories together"
+msgstr "åˆå¹¶ä¸¤ä¸ªæˆ–更多开å‘历å²"
+
+#: common-cmds.h:20
+msgid "Move or rename a file, a directory, or a symlink"
+msgstr "移动或é‡å‘½å一个文件ã€ç›®å½•æˆ–符å·é“¾æŽ¥"
+
+#: common-cmds.h:21
+msgid "Fetch from and merge with another repository or a local branch"
+msgstr "获å–并åˆå¹¶å¦å¤–的版本库或一个本地分支"
+
+#: common-cmds.h:22
+msgid "Update remote refs along with associated objects"
+msgstr "更新远程引用和相关的对象"
+
+#: common-cmds.h:23
+msgid "Forward-port local commits to the updated upstream head"
+msgstr "本地æ交转移至更新åŽçš„上游分支中"
+
+#: common-cmds.h:24
+msgid "Reset current HEAD to the specified state"
+msgstr "é‡ç½®å½“å‰HEAD到指定状æ€"
+
+#: common-cmds.h:25
+msgid "Remove files from the working tree and from the index"
+msgstr "从工作区和索引中删除文件"
+
+#: common-cmds.h:26
+msgid "Show various types of objects"
+msgstr "显示å„ç§ç±»åž‹çš„对象"
+
+#: common-cmds.h:27
+msgid "Show the working tree status"
+msgstr "显示工作区状æ€"
+
+#: common-cmds.h:28
+msgid "Create, list, delete or verify a tag object signed with GPG"
+msgstr "创建ã€åˆ—出ã€åˆ é™¤æˆ–校验一个GPGç­¾åçš„ tag 对象"
+
+#: git-am.sh:50
+msgid "You need to set your committer info first"
+msgstr "您需è¦å…ˆè®¾ç½®ä½ çš„æ交者信æ¯"
+
+#: git-am.sh:95
+msgid ""
+"You seem to have moved HEAD since the last 'am' failure.\n"
+"Not rewinding to ORIG_HEAD"
+msgstr ""
+"您好åƒåœ¨ä¸Šä¸€æ¬¡ 'am' 失败åŽç§»åŠ¨äº† HEAD。未回退至 ORIG_HEAD"
+
+#: git-am.sh:105
+#, sh-format
+msgid ""
+"When you have resolved this problem run \"$cmdline --resolved\".\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"当您解决了此问题åŽï¼Œæ‰§è¡Œ \"$cmdline --resolved\"。\n"
+"如果您想跳过此补ä¸ï¼Œåˆ™æ‰§è¡Œ \"$cmdline --skip\"。\n"
+"è¦æ¢å¤åŽŸåˆ†æ”¯å¹¶åœæ­¢æ‰“è¡¥ä¸ï¼Œæ‰§è¡Œ \"$cmdline --abort\"。"
+
+#: git-am.sh:121
+msgid "Cannot fall back to three-way merge."
+msgstr "无法求助于三路åˆå¹¶ã€‚"
+
+#: git-am.sh:137
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr "版本库缺ä¹å¿…è¦çš„ blob æ•°æ®ä»¥è¿›è¡Œä¸‰è·¯åˆå¹¶ã€‚"
+
+#: git-am.sh:154
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr ""
+"您是å¦æ›¾æ‰‹åŠ¨ç¼–辑过您的补ä¸ï¼Ÿ\n"
+"无法应用补ä¸åˆ°ç´¢å¼•ä¸­çš„æ•°æ®ä¸Šã€‚"
+
+#: git-am.sh:163
+msgid "Falling back to patching base and 3-way merge..."
+msgstr "转而在基础版本上打补ä¸åŠè¿›è¡Œä¸‰è·¯åˆå¹¶..."
+
+#: git-am.sh:275
+msgid "Only one StGIT patch series can be applied at once"
+msgstr "一次åªèƒ½æœ‰ä¸€ä¸ª StGIT è¡¥ä¸é˜Ÿåˆ—被应用"
+
+#: git-am.sh:362
+#, sh-format
+msgid "Patch format $patch_format is not supported."
+msgstr "ä¸æ”¯æŒ $patch_format è¡¥ä¸æ ¼å¼ã€‚"
+
+#: git-am.sh:364
+msgid "Patch format detection failed."
+msgstr "è¡¥ä¸æ ¼å¼æ£€æµ‹å¤±è´¥ã€‚"
+
+#: git-am.sh:418
+msgid "-d option is no longer supported. Do not use."
+msgstr "ä¸å†æ”¯æŒ -d 选项。ä¸è¦ä½¿ç”¨ã€‚"
+
+#: git-am.sh:481
+#, sh-format
+msgid "previous rebase directory $dotest still exists but mbox given."
+msgstr "之å‰çš„å˜åŸºç›®å½• $dotest ä»ç„¶å­˜åœ¨ä½†ç»™å‡ºäº†mbox。"
+
+#: git-am.sh:486
+msgid "Please make up your mind. --skip or --abort?"
+msgstr "请下决心。--skip 或是 --abort ?"
+
+#: git-am.sh:513
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr "解决æ“作未进行,我们ä¸ä¼šç»§ç»­ã€‚"
+
+#: git-am.sh:579
+#, sh-format
+msgid "Dirty index: cannot apply patches (dirty: $files)"
+msgstr "è„的索引:ä¸èƒ½åº”用补ä¸ï¼ˆè„文件:$files)"
+
+#: git-am.sh:671
+#, sh-format
+msgid ""
+"Patch is empty. Was it split wrong?\n"
+"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
+"To restore the original branch and stop patching run \"$cmdline --abort\"."
+msgstr ""
+"è¡¥ä¸ä¸ºç©ºã€‚是ä¸æ˜¯åˆ‡åˆ†é”™è¯¯ï¼Ÿ\n"
+"如果您想è¦è·³è¿‡è¿™ä¸ªè¡¥ä¸ï¼Œæ‰§è¡Œ \"$cmdline --skip\"。\n"
+"è¦æ¢å¤åŽŸåˆ†æ”¯å¹¶åœæ­¢æ‰“è¡¥ä¸ï¼Œæ‰§è¡Œ \"$cmdline --abort\"。"
+
+#: git-am.sh:708
+msgid "Patch does not have a valid e-mail address."
+msgstr "è¡¥ä¸ä¸­æ²¡æœ‰ä¸€ä¸ªæœ‰æ•ˆçš„邮件地å€ã€‚"
+
+#: git-am.sh:755
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr "标准输入没有和终端关è”,ä¸èƒ½è¿›è¡Œäº¤äº’å¼æ“作。"
+
+#: git-am.sh:759
+msgid "Commit Body is:"
+msgstr "æ交内容为:"
+
+# 译者:注æ„ä¿æŒå¥å°¾ç©ºæ ¼
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: git-am.sh:766
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+msgstr "应用?[y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+
+#: git-am.sh:802
+#, sh-format
+msgid "Applying: $FIRSTLINE"
+msgstr "正应用:$FIRSTLINE"
+
+#: git-am.sh:823
+msgid ""
+"No changes - did you forget to use 'git add'?\n"
+"If there is nothing left to stage, chances are that something else\n"
+"already introduced the same changes; you might want to skip this patch."
+msgstr ""
+"没有å˜æ›´ —— 您是ä¸æ˜¯å¿˜äº†æ‰§è¡Œ 'git add'?\n"
+"如果没有什么è¦æ·»åŠ åˆ°æš‚存区的,则很å¯èƒ½æ˜¯å…¶å®ƒæ交已ç»å¼•å…¥äº†ç›¸åŒçš„å˜æ›´ã€‚\n"
+"您也许想è¦è·³è¿‡è¿™ä¸ªè¡¥ä¸ã€‚"
+
+#: git-am.sh:831
+msgid ""
+"You still have unmerged paths in your index\n"
+"did you forget to use 'git add'?"
+msgstr ""
+"您的索引中ä»æœ‰æœªåˆå¹¶çš„路径。您是å¦å¿˜äº†æ‰§è¡Œ 'git add'?"
+
+#: git-am.sh:847
+msgid "No changes -- Patch already applied."
+msgstr "没有å˜æ›´ -- è¡¥ä¸å·²ç»åº”用过。"
+
+#: git-am.sh:857
+#, sh-format
+msgid "Patch failed at $msgnum $FIRSTLINE"
+msgstr "è¡¥ä¸å¤±è´¥äºŽ $msgnum $FIRSTLINE"
+
+#: git-am.sh:873
+msgid "applying to an empty history"
+msgstr "正应用到一个空历å²ä¸Š"
+
+#: git-bisect.sh:48
+msgid "You need to start by \"git bisect start\""
+msgstr "您需è¦æ‰§è¡Œ \"git bisect start\" æ¥å¼€å§‹"
+
+# 译者:注æ„ä¿æŒå¥å°¾ç©ºæ ¼
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:54
+msgid "Do you want me to do it for you [Y/n]? "
+msgstr "您想让我为您这样åšä¹ˆ[Y/n]? "
+
+#: git-bisect.sh:95
+#, sh-format
+msgid "unrecognised option: '$arg'"
+msgstr "未能识别的选项:'$arg'"
+
+#: git-bisect.sh:99
+#, sh-format
+msgid "'$arg' does not appear to be a valid revision"
+msgstr "'$arg' 看起æ¥ä¸åƒæ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„版本"
+
+#: git-bisect.sh:117
+msgid "Bad HEAD - I need a HEAD"
+msgstr "åçš„ HEAD - 我需è¦ä¸€ä¸ª HEAD"
+
+#: git-bisect.sh:130
+#, sh-format
+msgid ""
+"Checking out '$start_head' failed. Try 'git bisect reset <validbranch>'."
+msgstr "检出 '$start_head' 失败。å°è¯• 'git bisect reset <validbranch>'。"
+
+#: git-bisect.sh:140
+msgid "won't bisect on seeked tree"
+msgstr "ä¸ä¼šåœ¨å·²æŸ¥æ‰¾è¿‡çš„树上二分查找"
+
+#: git-bisect.sh:144
+msgid "Bad HEAD - strange symbolic ref"
+msgstr "åçš„ HEAD - 奇怪的符å·å¼•ç”¨"
+
+#: git-bisect.sh:189
+#, sh-format
+msgid "Bad bisect_write argument: $state"
+msgstr "åçš„ bisect_write å‚数:$state"
+
+#: git-bisect.sh:218
+#, sh-format
+msgid "Bad rev input: $arg"
+msgstr "输入å的版本:$arg"
+
+#: git-bisect.sh:232
+msgid "Please call 'bisect_state' with at least one argument."
+msgstr "请在调用 'bisect_state' 时跟至少一个å‚数。"
+
+#: git-bisect.sh:244
+#, sh-format
+msgid "Bad rev input: $rev"
+msgstr "输入å的版本:$rev"
+
+#: git-bisect.sh:250
+msgid "'git bisect bad' can take only one argument."
+msgstr "'git bisect bad' åªèƒ½å¸¦ä¸€ä¸ªå‚数。"
+
+#. have bad but not good. we could bisect although
+#. this is less optimum.
+#: git-bisect.sh:273
+msgid "Warning: bisecting only with a bad commit."
+msgstr "警告:在仅有一个åæ交下进行二分查找。"
+
+# 译者:注æ„ä¿æŒå¥å°¾ç©ºæ ¼
+#. TRANSLATORS: Make sure to include [Y] and [n] in your
+#. translation. The program will only accept English input
+#. at this point.
+#: git-bisect.sh:279
+msgid "Are you sure [Y/n]? "
+msgstr "您确认么[Y/n]? "
+
+#: git-bisect.sh:289
+msgid ""
+"You need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"您需è¦ç»™æˆ‘至少一个好版本和一个å版本。\n"
+"(您å¯ä»¥ç”¨ \"git bisect bad\" å’Œ \"git bisect good\" 命令æ¥æ ‡è¯†ã€‚)"
+
+#: git-bisect.sh:292
+msgid ""
+"You need to start by \"git bisect start\".\n"
+"You then need to give me at least one good and one bad revisions.\n"
+"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
+msgstr ""
+"您需è¦æ‰§è¡Œ \"git bisect start\" æ¥å¼€å§‹ã€‚\n"
+"然åŽéœ€è¦æ供我至少一个好版本和一个å版本。\n"
+"(您å¯ä»¥ç”¨ \"git bisect bad\" å’Œ \"git bisect good\" 命令æ¥æ ‡è¯†ã€‚)"
+
+#: git-bisect.sh:347 git-bisect.sh:474
+msgid "We are not bisecting."
+msgstr "我们没有在二分查找。"
+
+#: git-bisect.sh:354
+#, sh-format
+msgid "'$invalid' is not a valid commit"
+msgstr "'$invalid' ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„æ交"
+
+#: git-bisect.sh:363
+#, sh-format
+msgid ""
+"Could not check out original HEAD '$branch'.\n"
+"Try 'git bisect reset <commit>'."
+msgstr ""
+"ä¸èƒ½æ£€å‡ºåŽŸå§‹ HEAD '$branch'。\n"
+"å°è¯• 'git bisect reset <commit>'。"
+
+#: git-bisect.sh:390
+msgid "No logfile given"
+msgstr "未æ供日志文件"
+
+#: git-bisect.sh:391
+#, sh-format
+msgid "cannot read $file for replaying"
+msgstr "ä¸èƒ½è¯»å– $file æ¥é‡æ”¾"
+
+#: git-bisect.sh:408
+msgid "?? what are you talking about?"
+msgstr "?? 您在说什么?"
+
+#: git-bisect.sh:420
+#, sh-format
+msgid "running $command"
+msgstr "è¿è¡Œ $command"
+
+#: git-bisect.sh:427
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"exit code $res from '$command' is < 0 or >= 128"
+msgstr ""
+"二分查找è¿è¡Œå¤±è´¥ï¼š\n"
+"命令 '$command' çš„é€€å‡ºç  $res 或者å°äºŽ 0 或者大于等于 128"
+
+#: git-bisect.sh:453
+msgid "bisect run cannot continue any more"
+msgstr "二分查找ä¸èƒ½ç»§ç»­è¿è¡Œ"
+
+#: git-bisect.sh:459
+#, sh-format
+msgid ""
+"bisect run failed:\n"
+"'bisect_state $state' exited with error code $res"
+msgstr ""
+"二分查找è¿è¡Œå¤±è´¥ï¼š\n"
+"'bisect_state $state' 退出ç ä¸º $res"
+
+#: git-bisect.sh:466
+msgid "bisect run success"
+msgstr "二分查找è¿è¡ŒæˆåŠŸ"
+
+#: git-pull.sh:21
+msgid ""
+"Pull is not possible because you have unmerged files.\n"
+"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
+"as appropriate to mark resolution, or use 'git commit -a'."
+msgstr ""
+"Pull ä¸å¯ç”¨ï¼Œå› ä¸ºæ‚¨å°šæœ‰æœªåˆå¹¶çš„文件。请先在工作区改正文件,\n"
+"然åŽé…Œæƒ…使用 'git add/rm <file>' 标记解决方案,\n"
+"或者使用 'git commit -a'。"
+
+#: git-pull.sh:25
+msgid "Pull is not possible because you have unmerged files."
+msgstr "Pull ä¸å¯ç”¨ï¼Œå› ä¸ºæ‚¨å°šæœ‰æœªåˆå¹¶çš„文件。"
+
+#: git-pull.sh:197
+msgid "updating an unborn branch with changes added to the index"
+msgstr "更新尚未诞生的分支,å˜æ›´æ·»åŠ è‡³ç´¢å¼•"
+
+#. The fetch involved updating the current branch.
+#. The working tree and the index file is still based on the
+#. $orig_head commit, but we are merging into $curr_head.
+#. First update the working tree to match $curr_head.
+#: git-pull.sh:228
+#, sh-format
+msgid ""
+"Warning: fetch updated the current branch head.\n"
+"Warning: fast-forwarding your working tree from\n"
+"Warning: commit $orig_head."
+msgstr ""
+"警告:fetch 更新了当å‰çš„分支。您的工作区\n"
+"警告:从原æ交 $orig_head 快进。"
+
+#: git-pull.sh:253
+msgid "Cannot merge multiple branches into empty head"
+msgstr "无法将多个分支åˆå¹¶åˆ°ç©ºåˆ†æ”¯"
+
+#: git-pull.sh:257
+msgid "Cannot rebase onto multiple branches"
+msgstr "无法å˜åŸºåˆ°å¤šä¸ªåˆ†æ”¯"
+
+#: git-stash.sh:51
+msgid "git stash clear with parameters is unimplemented"
+msgstr "git stash clear ä¸æ”¯æŒå‚æ•°"
+
+#: git-stash.sh:74
+msgid "You do not have the initial commit yet"
+msgstr "您尚未建立åˆå§‹æ交"
+
+#: git-stash.sh:89
+msgid "Cannot save the current index state"
+msgstr "无法ä¿å­˜å½“å‰ç´¢å¼•çŠ¶æ€"
+
+#: git-stash.sh:123 git-stash.sh:136
+msgid "Cannot save the current worktree state"
+msgstr "无法ä¿å­˜å½“å‰å·¥ä½œåŒºçŠ¶æ€"
+
+#: git-stash.sh:140
+msgid "No changes selected"
+msgstr "没有选择å˜æ›´"
+
+#: git-stash.sh:143
+msgid "Cannot remove temporary index (can't happen)"
+msgstr "无法删除临时索引(ä¸åº”å‘生)"
+
+#: git-stash.sh:156
+msgid "Cannot record working tree state"
+msgstr "ä¸èƒ½è®°å½•å·¥ä½œåŒºçŠ¶æ€"
+
+#. TRANSLATORS: $option is an invalid option, like
+#. `--blah-blah'. The 7 spaces at the beginning of the
+#. second line correspond to "error: ". So you should line
+#. up the second line with however many characters the
+#. translation of "error: " takes in your language. E.g. in
+#. English this is:
+#.
+#. $ git stash save --blah-blah 2>&1 | head -n 2
+#. error: unknown option for 'stash save': --blah-blah
+#. To provide a message, use git stash save -- '--blah-blah'
+#: git-stash.sh:202
+#, sh-format
+msgid ""
+"error: unknown option for 'stash save': $option\n"
+" To provide a message, use git stash save -- '$option'"
+msgstr ""
+"错误:'stash save' 的未知选项:$option\n"
+" è¦æ供一个æè¿°ä¿¡æ¯ï¼Œä½¿ç”¨ git stash save -- '$option'"
+
+#: git-stash.sh:223
+msgid "No local changes to save"
+msgstr "没有è¦ä¿å­˜çš„本地修改"
+
+#: git-stash.sh:227
+msgid "Cannot initialize stash"
+msgstr "无法åˆå§‹åŒ– stash"
+
+#: git-stash.sh:235
+msgid "Cannot save the current status"
+msgstr "无法ä¿å­˜å½“å‰çŠ¶æ€"
+
+#: git-stash.sh:253
+msgid "Cannot remove worktree changes"
+msgstr "无法删除工作区å˜æ›´"
+
+#: git-stash.sh:352
+msgid "No stash found."
+msgstr "未å‘现 stash。"
+
+#: git-stash.sh:359
+#, sh-format
+msgid "Too many revisions specified: $REV"
+msgstr "指定了太多的版本:$REV"
+
+#: git-stash.sh:365
+#, sh-format
+msgid "$reference is not valid reference"
+msgstr "$reference ä¸æ˜¯æœ‰æ•ˆçš„引用"
+
+#: git-stash.sh:393
+#, sh-format
+msgid "'$args' is not a stash-like commit"
+msgstr "'$args' ä¸æ˜¯ stash æ ·æ交"
+
+#: git-stash.sh:404
+#, sh-format
+msgid "'$args' is not a stash reference"
+msgstr "'$args' ä¸æ˜¯ä¸€ä¸ª stash 引用"
+
+#: git-stash.sh:412
+msgid "unable to refresh index"
+msgstr "无法刷新索引"
+
+#: git-stash.sh:416
+msgid "Cannot apply a stash in the middle of a merge"
+msgstr "无法在åˆå¹¶è¿‡ç¨‹ä¸­æ¢å¤è¿›åº¦"
+
+#: git-stash.sh:424
+msgid "Conflicts in index. Try without --index."
+msgstr "索引中有冲çªã€‚å°è¯•ä¸ä½¿ç”¨ --index。"
+
+#: git-stash.sh:426
+msgid "Could not save index tree"
+msgstr "ä¸èƒ½ä¿å­˜ç´¢å¼•æ ‘"
+
+#: git-stash.sh:460
+msgid "Cannot unstage modified files"
+msgstr "无法还原修改的文件"
+
+#: git-stash.sh:474
+msgid "Index was not unstashed."
+msgstr "索引的进度没有被æ¢å¤ã€‚"
+
+#: git-stash.sh:491
+#, sh-format
+msgid "Dropped ${REV} ($s)"
+msgstr "丢弃了 ${REV} ($s)"
+
+#: git-stash.sh:492
+#, sh-format
+msgid "${REV}: Could not drop stash entry"
+msgstr "${REV}:ä¸èƒ½ä¸¢å¼ƒè¿›åº¦æ¡ç›®"
+
+#: git-stash.sh:499
+msgid "No branch name specified"
+msgstr "未指定分支å"
+
+#: git-stash.sh:570
+msgid "(To restore them type \"git stash apply\")"
+msgstr "(为æ¢å¤æ•°æ®è¾“å…¥ \"git stash apply\")"
+
+#: git-submodule.sh:56
+#, sh-format
+msgid "cannot strip one component off url '$remoteurl'"
+msgstr "无法从 url '$remoteurl' 剥离一个组件"
+
+#: git-submodule.sh:109
+#, sh-format
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr "未在 .gitmodules 中å‘现路径 '$sm_path' çš„å­æ¨¡ç»„映射"
+
+#: git-submodule.sh:150
+#, sh-format
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr "无法克隆 '$url' 到å­æ¨¡ç»„路径 '$sm_path'"
+
+#: git-submodule.sh:160
+#, sh-format
+msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
+msgstr "Gitdir '$a' 在å­æ¨¡ç»„路径 '$b' 之下或者相å"
+
+#: git-submodule.sh:249
+#, sh-format
+msgid "repo URL: '$repo' must be absolute or begin with ./|../"
+msgstr "版本库URL:'$repo' 必须是ç»å¯¹è·¯å¾„或以 ./|../ 起始"
+
+#: git-submodule.sh:266
+#, sh-format
+msgid "'$sm_path' already exists in the index"
+msgstr "'$sm_path' å·²ç»å­˜åœ¨äºŽç´¢å¼•ä¸­"
+
+#: git-submodule.sh:270
+#, sh-format
+msgid ""
+"The following path is ignored by one of your .gitignore files:\n"
+"$sm_path\n"
+"Use -f if you really want to add it."
+msgstr ""
+"以下路径被您的一个 .gitignore 文件所忽略:\n"
+"$sm_path\n"
+"如果您确实想添加它,使用 -f å‚数。"
+
+#: git-submodule.sh:281
+#, sh-format
+msgid "Adding existing repo at '$sm_path' to the index"
+msgstr "添加ä½äºŽ '$sm_path' 的现存版本库到索引"
+
+#: git-submodule.sh:283
+#, sh-format
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "'$sm_path' 已存在且ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„ git 版本库"
+
+#: git-submodule.sh:297
+#, sh-format
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr "ä¸èƒ½æ£€å‡ºå­æ¨¡ç»„ '$sm_path'"
+
+#: git-submodule.sh:302
+#, sh-format
+msgid "Failed to add submodule '$sm_path'"
+msgstr "无法添加å­æ¨¡ç»„ '$sm_path'"
+
+#: git-submodule.sh:307
+#, sh-format
+msgid "Failed to register submodule '$sm_path'"
+msgstr "无法注册å­æ¨¡ç»„ '$sm_path'"
+
+#: git-submodule.sh:349
+#, sh-format
+msgid "Entering '$prefix$sm_path'"
+msgstr "正在进入 '$prefix$sm_path'"
+
+#: git-submodule.sh:363
+#, sh-format
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr "åœæ­¢äºŽ '$sm_path',脚本返回éžé›¶å€¼ã€‚"
+
+#: git-submodule.sh:406
+#, sh-format
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr "在 .gitmodules 中未找到å­æ¨¡ç»„路径 '$sm_path' çš„ url"
+
+#: git-submodule.sh:415
+#, sh-format
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr "无法为å­æ¨¡ç»„路径 '$sm_path' 注册 url"
+
+#: git-submodule.sh:417
+#, sh-format
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr "å­æ¨¡ç»„ '$name' ($url) 已为路径 '$sm_path' 注册"
+
+#: git-submodule.sh:425
+#, sh-format
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr "无法为å­æ¨¡ç»„路径 '$sm_path' 注册更新模å¼"
+
+#: git-submodule.sh:524
+#, sh-format
+msgid ""
+"Submodule path '$sm_path' not initialized\n"
+"Maybe you want to use 'update --init'?"
+msgstr ""
+"å­æ¨¡ç»„路径 '$sm_path' 没有åˆå§‹åŒ–\n"
+"也许您想用 'update --init'?"
+
+#: git-submodule.sh:537
+#, sh-format
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr "无法在å­æ¨¡ç»„路径 '$sm_path' 中找到当å‰ç‰ˆæœ¬"
+
+#: git-submodule.sh:556
+#, sh-format
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr "无法在å­æ¨¡ç»„路径 '$sm_path' 中获å–"
+
+#: git-submodule.sh:570
+#, sh-format
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr "无法在å­æ¨¡ç»„路径 '$sm_path' 中å˜åŸº '$sha1'"
+
+#: git-submodule.sh:571
+#, sh-format
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr "å­æ¨¡ç»„路径 '$sm_path':å˜åŸºè‡³ '$sha1'"
+
+#: git-submodule.sh:576
+#, sh-format
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr "无法åˆå¹¶ '$sha1' 到å­æ¨¡ç»„路径 '$sm_path' 中"
+
+#: git-submodule.sh:577
+#, sh-format
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr "å­æ¨¡ç»„路径 '$sm_path':已åˆå¹¶å…¥ '$sha1'"
+
+#: git-submodule.sh:582
+#, sh-format
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr "无法在å­æ¨¡ç»„路径 '$sm_path' 中检出 '$sha1'"
+
+#: git-submodule.sh:583
+#, sh-format
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr "å­æ¨¡ç»„路径 '$sm_path':检出 '$sha1'"
+
+#: git-submodule.sh:605 git-submodule.sh:928
+#, sh-format
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr "无法递归进å­æ¨¡ç»„路径 '$sm_path'"
+
+#: git-submodule.sh:713
+msgid "--cached cannot be used with --files"
+msgstr "--cached ä¸èƒ½å’Œ --files 共用"
+
+#. unexpected type
+#: git-submodule.sh:753
+#, sh-format
+msgid "unexpected mode $mod_dst"
+msgstr "æ„å¤–çš„æ¨¡å¼ $mod_dst"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: git-submodule.sh:771
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_src"
+msgstr " 警告:$name 未包å«æ交 $sha1_src"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: git-submodule.sh:774
+#, sh-format
+msgid " Warn: $name doesn't contain commit $sha1_dst"
+msgstr " 警告:$name 未包å«æ交 $sha1_dst"
+
+# 译者:注æ„ä¿æŒå‰å¯¼ç©ºæ ¼
+#: git-submodule.sh:777
+#, sh-format
+msgid " Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+msgstr " 警告:$name 未包å«æ交 $sha1_src å’Œ $sha1_dst"
+
+#: git-submodule.sh:802
+msgid "blob"
+msgstr "blob"
+
+#: git-submodule.sh:803
+msgid "submodule"
+msgstr "å­æ¨¡ç»„"
+
+#: git-submodule.sh:840
+msgid "# Submodules changed but not updated:"
+msgstr "# å­æ¨¡ç»„已修改但尚未更新:"
+
+#: git-submodule.sh:842
+msgid "# Submodule changes to be committed:"
+msgstr "è¦æ交的å­æ¨¡ç»„å˜æ›´ï¼š"
+
+#: git-submodule.sh:974
+#, sh-format
+msgid "Synchronizing submodule url for '$name'"
+msgstr "为 '$name' åŒæ­¥å­æ¨¡ç»„ url"
diff --git a/pretty.c b/pretty.c
index f45eb54..8b1ea9f 100644
--- a/pretty.c
+++ b/pretty.c
@@ -9,6 +9,7 @@
#include "notes.h"
#include "color.h"
#include "reflog-walk.h"
+#include "gpg-interface.h"
static char *user_format;
static struct cmt_fmt_map {
@@ -438,12 +439,14 @@ static char *get_header(const struct commit *commit, const char *key)
int key_len = strlen(key);
const char *line = commit->buffer;
- for (;;) {
+ while (line) {
const char *eol = strchr(line, '\n'), *next;
if (line == eol)
return NULL;
if (!eol) {
+ warning("malformed commit (header is missing newline): %s",
+ sha1_to_hex(commit->object.sha1));
eol = line + strlen(line);
next = NULL;
} else
@@ -455,6 +458,7 @@ static char *get_header(const struct commit *commit, const char *key)
}
line = next;
}
+ return NULL;
}
static char *replace_encoding_header(char *buf, const char *encoding)
@@ -530,41 +534,26 @@ static size_t format_person_part(struct strbuf *sb, char part,
{
/* currently all placeholders have same length */
const int placeholder_len = 2;
- int start, end, tz = 0;
+ int tz;
unsigned long date = 0;
- char *ep;
- const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
char person_name[1024];
char person_mail[1024];
+ struct ident_split s;
+ const char *name_start, *name_end, *mail_start, *mail_end;
- /* 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)
+ if (split_ident_line(&s, msg, len) < 0)
goto skip;
- /* Seek for both name and email part */
- name_start = msg;
- name_end = msg+end;
- while (name_end > name_start && isspace(*(name_end-1)))
- name_end--;
- mail_start = msg+end+1;
- mail_end = mail_start;
- while (mail_end < msg_end && *mail_end != '>')
- mail_end++;
- if (mail_end == msg_end)
- goto skip;
- end = mail_end-msg;
+ name_start = s.name_begin;
+ name_end = s.name_end;
+ mail_start = s.mail_begin;
+ mail_end = s.mail_end;
if (part == 'N' || part == 'E') { /* mailmap lookup */
- strlcpy(person_name, name_start, name_end-name_start+1);
- strlcpy(person_mail, mail_start, mail_end-mail_start+1);
+ snprintf(person_name, sizeof(person_name), "%.*s",
+ (int)(name_end - name_start), name_start);
+ snprintf(person_mail, sizeof(person_mail), "%.*s",
+ (int)(mail_end - mail_start), mail_start);
mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
name_start = person_name;
name_end = name_start + strlen(person_name);
@@ -580,28 +569,20 @@ static size_t format_person_part(struct strbuf *sb, char part,
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)
+ if (!s.date_begin)
goto skip;
+ date = strtoul(s.date_begin, NULL, 10);
+
if (part == 't') { /* date, UNIX timestamp */
- strbuf_add(sb, msg + start, ep - (msg + start));
+ strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);
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;
- }
+ tz = strtoul(s.tz_begin + 1, NULL, 10);
+ if (*s.tz_begin == '-')
+ tz = -tz;
switch (part) {
case 'd': /* date */
@@ -620,8 +601,9 @@ static size_t format_person_part(struct strbuf *sb, char part,
skip:
/*
- * bogus commit, 'sb' cannot be updated, but we still need to
- * compute a valid return value.
+ * reading from either a bogus commit, or a reflog entry with
+ * %gn, %ge, etc.; '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')
@@ -640,6 +622,12 @@ struct format_commit_context {
const struct pretty_print_context *pretty_ctx;
unsigned commit_header_parsed:1;
unsigned commit_message_parsed:1;
+ unsigned commit_signature_parsed:1;
+ struct {
+ char *gpg_output;
+ char good_bad;
+ char *signer;
+ } signature;
char *message;
size_t width, indent1, indent2;
@@ -822,6 +810,76 @@ static void rewrap_message_tail(struct strbuf *sb,
c->indent2 = new_indent2;
}
+static struct {
+ char result;
+ const char *check;
+} signature_check[] = {
+ { 'G', ": Good signature from " },
+ { 'B', ": BAD signature from " },
+};
+
+static void parse_signature_lines(struct format_commit_context *ctx)
+{
+ const char *buf = ctx->signature.gpg_output;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+ const char *found = strstr(buf, signature_check[i].check);
+ const char *next;
+ if (!found)
+ continue;
+ ctx->signature.good_bad = signature_check[i].result;
+ found += strlen(signature_check[i].check);
+ next = strchrnul(found, '\n');
+ ctx->signature.signer = xmemdupz(found, next - found);
+ break;
+ }
+}
+
+static void parse_commit_signature(struct format_commit_context *ctx)
+{
+ struct strbuf payload = STRBUF_INIT;
+ struct strbuf signature = STRBUF_INIT;
+ struct strbuf gpg_output = STRBUF_INIT;
+ int status;
+
+ ctx->commit_signature_parsed = 1;
+
+ if (parse_signed_commit(ctx->commit->object.sha1,
+ &payload, &signature) <= 0)
+ goto out;
+ status = verify_signed_buffer(payload.buf, payload.len,
+ signature.buf, signature.len,
+ &gpg_output);
+ if (status && !gpg_output.len)
+ goto out;
+ ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
+ parse_signature_lines(ctx);
+
+ out:
+ strbuf_release(&gpg_output);
+ strbuf_release(&payload);
+ strbuf_release(&signature);
+}
+
+
+static int format_reflog_person(struct strbuf *sb,
+ char part,
+ struct reflog_walk_info *log,
+ enum date_mode dmode)
+{
+ const char *ident;
+
+ if (!log)
+ return 2;
+
+ ident = get_reflog_ident(log);
+ if (!ident)
+ return 2;
+
+ return format_person_part(sb, part, ident, strlen(ident), dmode);
+}
+
static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
void *context)
{
@@ -957,12 +1015,21 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
get_reflog_selector(sb,
c->pretty_ctx->reflog_info,
c->pretty_ctx->date_mode,
+ c->pretty_ctx->date_mode_explicit,
(placeholder[1] == 'd'));
return 2;
case 's': /* reflog message */
if (c->pretty_ctx->reflog_info)
get_reflog_message(sb, c->pretty_ctx->reflog_info);
return 2;
+ case 'n':
+ case 'N':
+ case 'e':
+ case 'E':
+ return format_reflog_person(sb,
+ placeholder[1],
+ c->pretty_ctx->reflog_info,
+ c->pretty_ctx->date_mode);
}
return 0; /* unknown %g placeholder */
case 'N':
@@ -974,6 +1041,30 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
return 0;
}
+ if (placeholder[0] == 'G') {
+ if (!c->commit_signature_parsed)
+ parse_commit_signature(c);
+ switch (placeholder[1]) {
+ case 'G':
+ if (c->signature.gpg_output)
+ strbuf_addstr(sb, c->signature.gpg_output);
+ break;
+ case '?':
+ switch (c->signature.good_bad) {
+ case 'G':
+ case 'B':
+ strbuf_addch(sb, c->signature.good_bad);
+ }
+ break;
+ case 'S':
+ if (c->signature.signer)
+ strbuf_addstr(sb, c->signature.signer);
+ break;
+ }
+ return 2;
+ }
+
+
/* For the rest we have to parse the commit header. */
if (!c->commit_header_parsed)
parse_commit_header(c);
@@ -1094,7 +1185,6 @@ void format_commit_message(const struct commit *commit,
{
struct format_commit_context context;
static const char utf8[] = "UTF-8";
- const char *enc;
const char *output_enc = pretty_ctx->output_encoding;
memset(&context, 0, sizeof(context));
@@ -1103,10 +1193,13 @@ void format_commit_message(const struct commit *commit,
context.wrap_start = sb->len;
context.message = commit->buffer;
if (output_enc) {
- enc = get_header(commit, "encoding");
- enc = enc ? enc : utf8;
- if (strcmp(enc, output_enc))
+ char *enc = get_header(commit, "encoding");
+ if (strcmp(enc ? enc : utf8, output_enc)) {
context.message = logmsg_reencode(commit, output_enc);
+ if (!context.message)
+ context.message = commit->buffer;
+ }
+ free(enc);
}
strbuf_expand(sb, format, format_commit_item, &context);
@@ -1114,6 +1207,8 @@ void format_commit_message(const struct commit *commit,
if (context.message != commit->buffer)
free(context.message);
+ free(context.signature.gpg_output);
+ free(context.signature.signer);
}
static void pp_header(const struct pretty_print_context *pp,
diff --git a/prompt.c b/prompt.c
new file mode 100644
index 0000000..d851807
--- /dev/null
+++ b/prompt.c
@@ -0,0 +1,72 @@
+#include "cache.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "prompt.h"
+#include "compat/terminal.h"
+
+static char *do_askpass(const char *cmd, const char *prompt)
+{
+ struct child_process pass;
+ const char *args[3];
+ static struct strbuf buffer = STRBUF_INIT;
+ int err = 0;
+
+ args[0] = cmd;
+ args[1] = prompt;
+ args[2] = NULL;
+
+ memset(&pass, 0, sizeof(pass));
+ pass.argv = args;
+ pass.out = -1;
+
+ if (start_command(&pass))
+ return NULL;
+
+ if (strbuf_read(&buffer, pass.out, 20) < 0)
+ err = 1;
+
+ close(pass.out);
+
+ if (finish_command(&pass))
+ err = 1;
+
+ if (err) {
+ error("unable to read askpass response from '%s'", cmd);
+ strbuf_release(&buffer);
+ return NULL;
+ }
+
+ strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
+
+ return strbuf_detach(&buffer, NULL);
+}
+
+char *git_prompt(const char *prompt, int flags)
+{
+ char *r = NULL;
+
+ if (flags & PROMPT_ASKPASS) {
+ const char *askpass;
+
+ askpass = getenv("GIT_ASKPASS");
+ if (!askpass)
+ askpass = askpass_program;
+ if (!askpass)
+ askpass = getenv("SSH_ASKPASS");
+ if (askpass && *askpass)
+ r = do_askpass(askpass, prompt);
+ }
+
+ if (!r)
+ r = git_terminal_prompt(prompt, flags & PROMPT_ECHO);
+ if (!r) {
+ /* prompts already contain ": " at the end */
+ die("could not read %s%s", prompt, strerror(errno));
+ }
+ return r;
+}
+
+char *git_getpass(const char *prompt)
+{
+ return git_prompt(prompt, PROMPT_ASKPASS);
+}
diff --git a/prompt.h b/prompt.h
new file mode 100644
index 0000000..04f321a
--- /dev/null
+++ b/prompt.h
@@ -0,0 +1,10 @@
+#ifndef PROMPT_H
+#define PROMPT_H
+
+#define PROMPT_ASKPASS (1<<0)
+#define PROMPT_ECHO (1<<1)
+
+char *git_prompt(const char *prompt, int flags);
+char *git_getpass(const char *prompt);
+
+#endif /* PROMPT_H */
diff --git a/quote.c b/quote.c
index 532fd3b..911229f 100644
--- a/quote.c
+++ b/quote.c
@@ -1,5 +1,6 @@
#include "cache.h"
#include "quote.h"
+#include "argv-array.h"
int quote_path_fully = 1;
@@ -120,7 +121,9 @@ char *sq_dequote(char *arg)
return sq_dequote_step(arg, NULL);
}
-int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
+static int sq_dequote_to_argv_internal(char *arg,
+ const char ***argv, int *nr, int *alloc,
+ struct argv_array *array)
{
char *next = arg;
@@ -130,13 +133,27 @@ int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
char *dequoted = sq_dequote_step(next, &next);
if (!dequoted)
return -1;
- ALLOC_GROW(*argv, *nr + 1, *alloc);
- (*argv)[(*nr)++] = dequoted;
+ if (argv) {
+ ALLOC_GROW(*argv, *nr + 1, *alloc);
+ (*argv)[(*nr)++] = dequoted;
+ }
+ if (array)
+ argv_array_push(array, dequoted);
} while (next);
return 0;
}
+int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
+{
+ return sq_dequote_to_argv_internal(arg, argv, nr, alloc, NULL);
+}
+
+int sq_dequote_to_argv_array(char *arg, struct argv_array *array)
+{
+ return sq_dequote_to_argv_internal(arg, NULL, NULL, NULL, array);
+}
+
/* 1 means: quote as octal
* 0 means: quote as octal if (quote_path_fully)
* -1 means: never quote
diff --git a/quote.h b/quote.h
index 024e21d..133155a 100644
--- a/quote.h
+++ b/quote.h
@@ -40,12 +40,19 @@ extern char *sq_dequote(char *);
/*
* Same as the above, but can be used to unwrap many arguments in the
- * same string separated by space. "next" is changed to point to the
- * next argument that should be passed as first parameter. When there
- * is no more argument to be dequoted, "next" is updated to point to NULL.
+ * same string separated by space. Like sq_quote, it works in place,
+ * modifying arg and appending pointers into it to argv.
*/
extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc);
+/*
+ * Same as above, but store the unquoted strings in an argv_array. We will
+ * still modify arg in place, but unlike sq_dequote_to_argv, the argv_array
+ * will duplicate and take ownership of the strings.
+ */
+struct argv_array;
+extern int sq_dequote_to_argv_array(char *arg, struct argv_array *);
+
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);
diff --git a/reachable.c b/reachable.c
index 3fc6b1d..bf79706 100644
--- a/reachable.c
+++ b/reachable.c
@@ -7,11 +7,25 @@
#include "revision.h"
#include "reachable.h"
#include "cache-tree.h"
+#include "progress.h"
+
+struct connectivity_progress {
+ struct progress *progress;
+ unsigned long count;
+};
+
+static void update_progress(struct connectivity_progress *cp)
+{
+ cp->count++;
+ if ((cp->count & 1023) == 0)
+ display_progress(cp->progress, cp->count);
+}
static void process_blob(struct blob *blob,
struct object_array *p,
struct name_path *path,
- const char *name)
+ const char *name,
+ struct connectivity_progress *cp)
{
struct object *obj = &blob->object;
@@ -20,6 +34,7 @@ static void process_blob(struct blob *blob,
if (obj->flags & SEEN)
return;
obj->flags |= SEEN;
+ update_progress(cp);
/* Nothing to do, really .. The blob lookup was the important part */
}
@@ -34,7 +49,8 @@ static void process_gitlink(const unsigned char *sha1,
static void process_tree(struct tree *tree,
struct object_array *p,
struct name_path *path,
- const char *name)
+ const char *name,
+ struct connectivity_progress *cp)
{
struct object *obj = &tree->object;
struct tree_desc desc;
@@ -46,6 +62,7 @@ static void process_tree(struct tree *tree,
if (obj->flags & SEEN)
return;
obj->flags |= SEEN;
+ update_progress(cp);
if (parse_tree(tree) < 0)
die("bad tree object %s", sha1_to_hex(obj->sha1));
add_object(obj, p, path, name);
@@ -57,23 +74,25 @@ static void process_tree(struct tree *tree,
while (tree_entry(&desc, &entry)) {
if (S_ISDIR(entry.mode))
- process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
+ process_tree(lookup_tree(entry.sha1), p, &me, entry.path, cp);
else if (S_ISGITLINK(entry.mode))
process_gitlink(entry.sha1, p, &me, entry.path);
else
- process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
+ process_blob(lookup_blob(entry.sha1), p, &me, entry.path, cp);
}
free(tree->buffer);
tree->buffer = NULL;
}
-static void process_tag(struct tag *tag, struct object_array *p, const char *name)
+static void process_tag(struct tag *tag, struct object_array *p,
+ const char *name, struct connectivity_progress *cp)
{
struct object *obj = &tag->object;
if (obj->flags & SEEN)
return;
obj->flags |= SEEN;
+ update_progress(cp);
if (parse_tag(tag) < 0)
die("bad tag object %s", sha1_to_hex(obj->sha1));
@@ -81,15 +100,18 @@ static void process_tag(struct tag *tag, struct object_array *p, const char *nam
add_object(tag->tagged, p, NULL, name);
}
-static void walk_commit_list(struct rev_info *revs)
+static void walk_commit_list(struct rev_info *revs,
+ struct connectivity_progress *cp)
{
int i;
struct commit *commit;
struct object_array objects = OBJECT_ARRAY_INIT;
/* Walk all commits, process their trees */
- while ((commit = get_revision(revs)) != NULL)
- process_tree(commit->tree, &objects, NULL, "");
+ while ((commit = get_revision(revs)) != NULL) {
+ process_tree(commit->tree, &objects, NULL, "", cp);
+ update_progress(cp);
+ }
/* Then walk all the pending objects, recursively processing them too */
for (i = 0; i < revs->pending.nr; i++) {
@@ -97,15 +119,15 @@ static void walk_commit_list(struct rev_info *revs)
struct object *obj = pending->item;
const char *name = pending->name;
if (obj->type == OBJ_TAG) {
- process_tag((struct tag *) obj, &objects, name);
+ process_tag((struct tag *) obj, &objects, name, cp);
continue;
}
if (obj->type == OBJ_TREE) {
- process_tree((struct tree *)obj, &objects, NULL, name);
+ process_tree((struct tree *)obj, &objects, NULL, name, cp);
continue;
}
if (obj->type == OBJ_BLOB) {
- process_blob((struct blob *)obj, &objects, NULL, name);
+ process_blob((struct blob *)obj, &objects, NULL, name, cp);
continue;
}
die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
@@ -191,8 +213,11 @@ static void add_cache_refs(struct rev_info *revs)
add_cache_tree(active_cache_tree, revs);
}
-void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
+void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
+ struct progress *progress)
{
+ struct connectivity_progress cp;
+
/*
* Set up revision parsing, and mark us as being interested
* in all object types, not just commits.
@@ -211,11 +236,15 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
if (mark_reflog)
for_each_reflog(add_one_reflog, revs);
+ cp.progress = progress;
+ cp.count = 0;
+
/*
* Set up the revision walk - this will move all commits
* from the pending list to the commit walking list.
*/
if (prepare_revision_walk(revs))
die("revision walk setup failed");
- walk_commit_list(revs);
+ walk_commit_list(revs, &cp);
+ display_progress(cp.progress, cp.count);
}
diff --git a/reachable.h b/reachable.h
index 4075181..5d082ad 100644
--- a/reachable.h
+++ b/reachable.h
@@ -1,6 +1,7 @@
#ifndef REACHEABLE_H
#define REACHEABLE_H
-extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog);
+struct progress;
+extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog, struct progress *);
#endif
diff --git a/read-cache.c b/read-cache.c
index 0a64103..ef355cc 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -12,6 +12,8 @@
#include "commit.h"
#include "blob.h"
#include "resolve-undo.h"
+#include "strbuf.h"
+#include "varint.h"
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
@@ -157,16 +159,6 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
return 0;
}
-static int is_empty_blob_sha1(const unsigned char *sha1)
-{
- static const unsigned char empty_blob_sha1[20] = {
- 0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b,
- 0x29,0xae,0x77,0x5a,0xd8,0xc2,0xe4,0x8c,0x53,0x91
- };
-
- return !hashcmp(sha1, empty_blob_sha1);
-}
-
static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
{
unsigned int changed = 0;
@@ -726,11 +718,12 @@ static int verify_dotfile(const char *rest)
* has already been discarded, we now test
* the rest.
*/
- switch (*rest) {
+
/* "." is not allowed */
- case '\0': case '/':
+ if (*rest == '\0' || is_dir_sep(*rest))
return 0;
+ switch (*rest) {
/*
* ".git" followed by NUL or slash is bad. This
* shares the path end test with the ".." case.
@@ -743,7 +736,7 @@ static int verify_dotfile(const char *rest)
rest += 2;
/* fallthrough */
case '.':
- if (rest[1] == '\0' || rest[1] == '/')
+ if (rest[1] == '\0' || is_dir_sep(rest[1]))
return 0;
}
return 1;
@@ -753,23 +746,19 @@ int verify_path(const char *path)
{
char c;
+ if (has_dos_drive_prefix(path))
+ return 0;
+
goto inside;
for (;;) {
if (!c)
return 1;
- if (c == '/') {
+ if (is_dir_sep(c)) {
inside:
c = *path++;
- switch (c) {
- default:
- continue;
- case '/': case '\0':
- break;
- case '.':
- if (verify_dotfile(path))
- continue;
- }
- return 0;
+ if ((c == '.' && !verify_dotfile(path)) ||
+ is_dir_sep(c) || c == '\0')
+ return 0;
}
c = *path++;
}
@@ -1004,7 +993,8 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
*/
static struct cache_entry *refresh_cache_ent(struct index_state *istate,
struct cache_entry *ce,
- unsigned int options, int *err)
+ unsigned int options, int *err,
+ int *changed_ret)
{
struct stat st;
struct cache_entry *updated;
@@ -1036,6 +1026,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
}
changed = ie_match_stat(istate, ce, &st, options);
+ if (changed_ret)
+ *changed_ret = changed;
if (!changed) {
/*
* The path is unchanged. If we were told to ignore
@@ -1105,19 +1097,31 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
int first = 1;
int in_porcelain = (flags & REFRESH_IN_PORCELAIN);
unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0;
- const char *needs_update_fmt;
- const char *needs_merge_fmt;
-
- needs_update_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
- needs_merge_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
+ const char *modified_fmt;
+ const char *deleted_fmt;
+ const char *typechange_fmt;
+ const char *added_fmt;
+ const char *unmerged_fmt;
+
+ modified_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
+ deleted_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n");
+ typechange_fmt = (in_porcelain ? "T\t%s\n" : "%s needs update\n");
+ added_fmt = (in_porcelain ? "A\t%s\n" : "%s needs update\n");
+ unmerged_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce, *new;
int cache_errno = 0;
+ int changed = 0;
+ int filtered = 0;
ce = istate->cache[i];
if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
continue;
+ if (pathspec &&
+ !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+ filtered = 1;
+
if (ce_stage(ce)) {
while ((i < istate->cache_nr) &&
! strcmp(istate->cache[i]->name, ce->name))
@@ -1125,18 +1129,22 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
i--;
if (allow_unmerged)
continue;
- show_file(needs_merge_fmt, ce->name, in_porcelain, &first, header_msg);
+ if (!filtered)
+ show_file(unmerged_fmt, ce->name, in_porcelain,
+ &first, header_msg);
has_errors = 1;
continue;
}
- if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+ if (filtered)
continue;
- new = refresh_cache_ent(istate, ce, options, &cache_errno);
+ new = refresh_cache_ent(istate, ce, options, &cache_errno, &changed);
if (new == ce)
continue;
if (!new) {
+ const char *fmt;
+
if (not_new && cache_errno == ENOENT)
continue;
if (really && cache_errno == EINVAL) {
@@ -1148,7 +1156,17 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
}
if (quiet)
continue;
- show_file(needs_update_fmt, ce->name, in_porcelain, &first, header_msg);
+
+ if (cache_errno == ENOENT)
+ fmt = deleted_fmt;
+ else if (ce->ce_flags & CE_INTENT_TO_ADD)
+ fmt = added_fmt; /* must be before other checks */
+ else if (changed & TYPE_CHANGED)
+ fmt = typechange_fmt;
+ else
+ fmt = modified_fmt;
+ show_file(fmt,
+ ce->name, in_porcelain, &first, header_msg);
has_errors = 1;
continue;
}
@@ -1160,18 +1178,77 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
{
- return refresh_cache_ent(&the_index, ce, really, NULL);
+ return refresh_cache_ent(&the_index, ce, really, NULL, NULL);
}
+
+/*****************************************************************
+ * Index File I/O
+ *****************************************************************/
+
+#define INDEX_FORMAT_DEFAULT 3
+
+/*
+ * dev/ino/uid/gid/size are also just tracked to the low 32 bits
+ * Again - this is just a (very strong in practice) heuristic that
+ * the inode hasn't changed.
+ *
+ * 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 */
+};
+
+/*
+ * This struct is used when CE_EXTENDED bit is 1
+ * The struct must match ondisk_cache_entry exactly from
+ * ctime till flags
+ */
+struct ondisk_cache_entry_extended {
+ 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;
+ unsigned short flags2;
+ char name[FLEX_ARRAY]; /* more */
+};
+
+/* These are only used for v3 or lower */
+#define align_flex_name(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
+#define ondisk_cache_entry_size(len) align_flex_name(ondisk_cache_entry,len)
+#define ondisk_cache_entry_extended_size(len) align_flex_name(ondisk_cache_entry_extended,len)
+#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
+ ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
+ ondisk_cache_entry_size(ce_namelen(ce)))
+
static int verify_hdr(struct cache_header *hdr, unsigned long size)
{
git_SHA_CTX c;
unsigned char sha1[20];
+ int hdr_version;
if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
return error("bad signature");
- if (hdr->hdr_version != htonl(2) && hdr->hdr_version != htonl(3))
- return error("bad index version");
+ hdr_version = ntohl(hdr->hdr_version);
+ if (hdr_version < 2 || 4 < hdr_version)
+ return error("bad index version %d", hdr_version);
git_SHA1_Init(&c);
git_SHA1_Update(&c, hdr, size - 20);
git_SHA1_Final(sha1, &c);
@@ -1205,63 +1282,115 @@ 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)
+#ifndef NEEDS_ALIGNED_ACCESS
+#define ntoh_s(var) ntohs(var)
+#define ntoh_l(var) ntohl(var)
+#else
+static inline uint16_t ntoh_s_force_align(void *p)
+{
+ uint16_t x;
+ memcpy(&x, p, sizeof(x));
+ return ntohs(x);
+}
+static inline uint32_t ntoh_l_force_align(void *p)
+{
+ uint32_t x;
+ memcpy(&x, p, sizeof(x));
+ return ntohl(x);
+}
+#define ntoh_s(var) ntoh_s_force_align(&(var))
+#define ntoh_l(var) ntoh_l_force_align(&(var))
+#endif
+
+static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *ondisk,
+ unsigned int flags,
+ const char *name,
+ size_t len)
+{
+ struct cache_entry *ce = xmalloc(cache_entry_size(len));
+
+ ce->ce_ctime.sec = ntoh_l(ondisk->ctime.sec);
+ ce->ce_mtime.sec = ntoh_l(ondisk->mtime.sec);
+ ce->ce_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
+ ce->ce_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
+ ce->ce_dev = ntoh_l(ondisk->dev);
+ ce->ce_ino = ntoh_l(ondisk->ino);
+ ce->ce_mode = ntoh_l(ondisk->mode);
+ ce->ce_uid = ntoh_l(ondisk->uid);
+ ce->ce_gid = ntoh_l(ondisk->gid);
+ ce->ce_size = ntoh_l(ondisk->size);
+ ce->ce_flags = flags;
+ hashcpy(ce->sha1, ondisk->sha1);
+ memcpy(ce->name, name, len);
+ ce->name[len] = '\0';
+ return ce;
+}
+
+/*
+ * Adjacent cache entries tend to share the leading paths, so it makes
+ * sense to only store the differences in later entries. In the v4
+ * on-disk format of the index, each on-disk cache entry stores the
+ * number of bytes to be stripped from the end of the previous name,
+ * and the bytes to append to the result, to come up with its name.
+ */
+static unsigned long expand_name_field(struct strbuf *name, const char *cp_)
+{
+ const unsigned char *ep, *cp = (const unsigned char *)cp_;
+ size_t len = decode_varint(&cp);
+
+ if (name->len < len)
+ die("malformed name field in the index");
+ strbuf_remove(name, name->len - len, len);
+ for (ep = cp; *ep; ep++)
+ ; /* find the end */
+ strbuf_add(name, cp, ep - cp);
+ return (const char *)ep + 1 - cp_;
+}
+
+static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk,
+ unsigned long *ent_size,
+ struct strbuf *previous_name)
{
+ struct cache_entry *ce;
size_t len;
const char *name;
+ unsigned int flags;
- ce->ce_ctime.sec = ntohl(ondisk->ctime.sec);
- ce->ce_mtime.sec = ntohl(ondisk->mtime.sec);
- ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec);
- ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec);
- 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;
+ flags = ntoh_s(ondisk->flags);
+ len = flags & CE_NAMEMASK;
- if (ce->ce_flags & CE_EXTENDED) {
+ if (flags & CE_EXTENDED) {
struct ondisk_cache_entry_extended *ondisk2;
int extended_flags;
ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
- extended_flags = ntohs(ondisk2->flags2) << 16;
+ extended_flags = ntoh_s(ondisk2->flags2) << 16;
/* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
if (extended_flags & ~CE_EXTENDED_FLAGS)
die("Unknown index entry format %08x", extended_flags);
- ce->ce_flags |= extended_flags;
+ flags |= extended_flags;
name = ondisk2->name;
}
else
name = ondisk->name;
- if (len == CE_NAMEMASK)
- len = strlen(name);
- /*
- * NEEDSWORK: If the original index is crafted, this copy could
- * go unchecked.
- */
- memcpy(ce->name, 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;
+ if (!previous_name) {
+ /* v3 and earlier */
+ if (len == CE_NAMEMASK)
+ len = strlen(name);
+ ce = cache_entry_from_ondisk(ondisk, flags, name, len);
+
+ *ent_size = ondisk_ce_size(ce);
+ } else {
+ unsigned long consumed;
+ consumed = expand_name_field(previous_name, name);
+ ce = cache_entry_from_ondisk(ondisk, flags,
+ previous_name->buf,
+ previous_name->len);
+
+ *ent_size = (name - ((char *)ondisk)) + consumed;
+ }
+ return ce;
}
/* remember to discard_cache() before reading a different cache! */
@@ -1269,10 +1398,11 @@ int read_index_from(struct index_state *istate, const char *path)
{
int fd, i;
struct stat st;
- unsigned long src_offset, dst_offset;
+ unsigned long src_offset;
struct cache_header *hdr;
void *mmap;
size_t mmap_size;
+ struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
errno = EBUSY;
if (istate->initialized)
@@ -1305,33 +1435,30 @@ int read_index_from(struct index_state *istate, const char *path)
if (verify_hdr(hdr, mmap_size) < 0)
goto unmap;
+ istate->version = ntohl(hdr->hdr_version);
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 *));
-
- /*
- * 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));
istate->initialized = 1;
+ if (istate->version == 4)
+ previous_name = &previous_name_buf;
+ else
+ previous_name = NULL;
+
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;
+ unsigned long consumed;
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);
+ ce = create_from_disk(disk_ce, &consumed, previous_name);
set_index_entry(istate, i, ce);
- src_offset += ondisk_ce_size(ce);
- dst_offset += ce_size(ce);
+ src_offset += consumed;
}
+ strbuf_release(&previous_name_buf);
istate->timestamp.sec = st.st_mtime;
istate->timestamp.nsec = ST_MTIME_NSEC(st);
@@ -1364,11 +1491,15 @@ unmap:
int is_index_unborn(struct index_state *istate)
{
- return (!istate->cache_nr && !istate->alloc && !istate->timestamp.sec);
+ return (!istate->cache_nr && !istate->timestamp.sec);
}
int discard_index(struct index_state *istate)
{
+ int i;
+
+ for (i = 0; i < istate->cache_nr; i++)
+ free(istate->cache[i]);
resolve_undo_clear_index(istate);
istate->cache_nr = 0;
istate->cache_changed = 0;
@@ -1377,8 +1508,6 @@ int discard_index(struct index_state *istate)
istate->name_hash_initialized = 0;
free_hash(&istate->name_hash);
cache_tree_free(&(istate->cache_tree));
- free(istate->alloc);
- istate->alloc = NULL;
istate->initialized = 0;
/* no need to throw away allocated active_cache */
@@ -1513,13 +1642,10 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
}
}
-static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
+/* Copy miscellaneous fields but not the name */
+static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
+ struct cache_entry *ce)
{
- int size = ondisk_ce_size(ce);
- struct ondisk_cache_entry *ondisk = xcalloc(1, size);
- char *name;
- int result;
-
ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
@@ -1536,11 +1662,52 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
struct ondisk_cache_entry_extended *ondisk2;
ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16);
- name = ondisk2->name;
+ return ondisk2->name;
+ }
+ else {
+ return ondisk->name;
+ }
+}
+
+static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
+ struct strbuf *previous_name)
+{
+ int size;
+ struct ondisk_cache_entry *ondisk;
+ char *name;
+ int result;
+
+ if (!previous_name) {
+ size = ondisk_ce_size(ce);
+ ondisk = xcalloc(1, size);
+ name = copy_cache_entry_to_ondisk(ondisk, ce);
+ memcpy(name, ce->name, ce_namelen(ce));
+ } else {
+ int common, to_remove, prefix_size;
+ unsigned char to_remove_vi[16];
+ for (common = 0;
+ (ce->name[common] &&
+ common < previous_name->len &&
+ ce->name[common] == previous_name->buf[common]);
+ common++)
+ ; /* still matching */
+ to_remove = previous_name->len - common;
+ prefix_size = encode_varint(to_remove, to_remove_vi);
+
+ if (ce->ce_flags & CE_EXTENDED)
+ size = offsetof(struct ondisk_cache_entry_extended, name);
+ else
+ size = offsetof(struct ondisk_cache_entry, name);
+ size += prefix_size + (ce_namelen(ce) - common + 1);
+
+ ondisk = xcalloc(1, size);
+ name = copy_cache_entry_to_ondisk(ondisk, ce);
+ memcpy(name, to_remove_vi, prefix_size);
+ memcpy(name + prefix_size, ce->name + common, ce_namelen(ce) - common);
+
+ strbuf_splice(previous_name, common, to_remove,
+ ce->name + common, ce_namelen(ce) - common);
}
- else
- name = ondisk->name;
- memcpy(name, ce->name, ce_namelen(ce));
result = ce_write(c, fd, ondisk, size);
free(ondisk);
@@ -1576,10 +1743,11 @@ int write_index(struct index_state *istate, int newfd)
{
git_SHA_CTX c;
struct cache_header hdr;
- int i, err, removed, extended;
+ int i, err, removed, extended, hdr_version;
struct cache_entry **cache = istate->cache;
int entries = istate->cache_nr;
struct stat st;
+ struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
for (i = removed = extended = 0; i < entries; i++) {
if (cache[i]->ce_flags & CE_REMOVE)
@@ -1593,24 +1761,34 @@ int write_index(struct index_state *istate, int newfd)
}
}
+ if (!istate->version)
+ istate->version = INDEX_FORMAT_DEFAULT;
+
+ /* demote version 3 to version 2 when the latter suffices */
+ if (istate->version == 3 || istate->version == 2)
+ istate->version = extended ? 3 : 2;
+
+ hdr_version = istate->version;
+
hdr.hdr_signature = htonl(CACHE_SIGNATURE);
- /* for extended format, increase version so older git won't try to read it */
- hdr.hdr_version = htonl(extended ? 3 : 2);
+ hdr.hdr_version = htonl(hdr_version);
hdr.hdr_entries = htonl(entries - removed);
git_SHA1_Init(&c);
if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
return -1;
+ previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
if (ce->ce_flags & CE_REMOVE)
continue;
if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
ce_smudge_racily_clean_entry(ce);
- if (ce_write_entry(&c, newfd, ce) < 0)
+ if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
return -1;
}
+ strbuf_release(&previous_name_buf);
/* Write extension data here */
if (istate->cache_tree) {
diff --git a/reflog-walk.c b/reflog-walk.c
index 5d81d39..b2fbdb2 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -50,9 +50,13 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)
for_each_reflog_ent(ref, read_one_reflog, reflogs);
if (reflogs->nr == 0) {
unsigned char sha1[20];
- const char *name = resolve_ref(ref, sha1, 1, NULL);
- if (name)
+ const char *name;
+ void *name_to_free;
+ name = name_to_free = resolve_refdup(ref, sha1, 1, NULL);
+ if (name) {
for_each_reflog_ent(name, read_one_reflog, reflogs);
+ free(name_to_free);
+ }
}
if (reflogs->nr == 0) {
int len = strlen(ref);
@@ -122,7 +126,12 @@ static void add_commit_info(struct commit *commit, void *util,
}
struct commit_reflog {
- int flag, recno;
+ int recno;
+ enum selector_type {
+ SELECTOR_NONE,
+ SELECTOR_INDEX,
+ SELECTOR_DATE
+ } selector;
struct complete_reflogs *reflogs;
};
@@ -146,6 +155,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
struct complete_reflogs *reflogs;
char *branch, *at = strchr(name, '@');
struct commit_reflog *commit_reflog;
+ enum selector_type selector = SELECTOR_NONE;
if (commit->object.flags & UNINTERESTING)
die ("Cannot walk reflogs for %s", name);
@@ -158,7 +168,10 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
if (*ep != '}') {
recno = -1;
timestamp = approxidate(at + 2);
+ selector = SELECTOR_DATE;
}
+ else
+ selector = SELECTOR_INDEX;
} else
recno = 0;
@@ -168,11 +181,11 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
else {
if (*branch == '\0') {
unsigned char sha1[20];
- const char *head = resolve_ref("HEAD", sha1, 0, NULL);
- if (!head)
- die ("No current branch");
free(branch);
- branch = xstrdup(head);
+ branch = resolve_refdup("HEAD", sha1, 0, NULL);
+ if (!branch)
+ die ("No current branch");
+
}
reflogs = read_complete_reflog(branch);
if (!reflogs || reflogs->nr == 0) {
@@ -196,7 +209,6 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
commit_reflog = xcalloc(sizeof(struct commit_reflog), 1);
if (recno < 0) {
- commit_reflog->flag = 1;
commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp);
if (commit_reflog->recno < 0) {
free(branch);
@@ -205,6 +217,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
}
} else
commit_reflog->recno = reflogs->nr - recno - 1;
+ commit_reflog->selector = selector;
commit_reflog->reflogs = reflogs;
add_commit_info(commit, commit_reflog, &info->reflogs);
@@ -243,7 +256,7 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
void get_reflog_selector(struct strbuf *sb,
struct reflog_walk_info *reflog_info,
- enum date_mode dmode,
+ enum date_mode dmode, int force_date,
int shorten)
{
struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
@@ -263,7 +276,8 @@ void get_reflog_selector(struct strbuf *sb,
}
strbuf_addf(sb, "%s@{", printed_ref);
- if (commit_reflog->flag || dmode) {
+ if (commit_reflog->selector == SELECTOR_DATE ||
+ (commit_reflog->selector == SELECTOR_NONE && force_date)) {
info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode));
} else {
@@ -291,8 +305,20 @@ void get_reflog_message(struct strbuf *sb,
strbuf_add(sb, info->message, len);
}
+const char *get_reflog_ident(struct reflog_walk_info *reflog_info)
+{
+ struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
+ struct reflog_info *info;
+
+ if (!commit_reflog)
+ return NULL;
+
+ info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
+ return info->email;
+}
+
void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
- enum date_mode dmode)
+ enum date_mode dmode, int force_date)
{
if (reflog_info && reflog_info->last_commit_reflog) {
struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
@@ -300,7 +326,7 @@ void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
struct strbuf selector = STRBUF_INIT;
info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
- get_reflog_selector(&selector, reflog_info, dmode, 0);
+ get_reflog_selector(&selector, reflog_info, dmode, force_date, 0);
if (oneline) {
printf("%s: %s", selector.buf, info->message);
}
diff --git a/reflog-walk.h b/reflog-walk.h
index 7bd2cd4..50265f5 100644
--- a/reflog-walk.h
+++ b/reflog-walk.h
@@ -11,12 +11,13 @@ extern int add_reflog_for_walk(struct reflog_walk_info *info,
extern void fake_reflog_parent(struct reflog_walk_info *info,
struct commit *commit);
extern void show_reflog_message(struct reflog_walk_info *info, int,
- enum date_mode);
+ enum date_mode, int force_date);
extern void get_reflog_message(struct strbuf *sb,
struct reflog_walk_info *reflog_info);
+extern const char *get_reflog_ident(struct reflog_walk_info *reflog_info);
extern void get_reflog_selector(struct strbuf *sb,
struct reflog_walk_info *reflog_info,
- enum date_mode dmode,
+ enum date_mode dmode, int force_date,
int shorten);
#endif
diff --git a/refs.c b/refs.c
index 4c1fd47..da74a2b 100644
--- a/refs.c
+++ b/refs.c
@@ -4,195 +4,815 @@
#include "tag.h"
#include "dir.h"
-/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
-#define REF_KNOWS_PEELED 04
-#define REF_BROKEN 010
+/*
+ * Make sure "ref" is something reasonable to have under ".git/refs/";
+ * We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
+ * - it ends with a "/".
+ * - it ends with ".lock"
+ * - it contains a "\" (backslash)
+ */
-struct ref_list {
- struct ref_list *next;
- unsigned char flag; /* ISSYMREF? ISPACKED? */
+/* Return true iff ch is not allowed in reference names. */
+static inline int bad_ref_char(int ch)
+{
+ if (((unsigned) ch) <= ' ' || ch == 0x7f ||
+ ch == '~' || ch == '^' || ch == ':' || ch == '\\')
+ return 1;
+ /* 2.13 Pattern Matching Notation */
+ if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
+ return 1;
+ return 0;
+}
+
+/*
+ * Try to read one refname component from the front of refname. Return
+ * the length of the component found, or -1 if the component is not
+ * legal.
+ */
+static int check_refname_component(const char *refname, int flags)
+{
+ const char *cp;
+ char last = '\0';
+
+ for (cp = refname; ; cp++) {
+ char ch = *cp;
+ if (ch == '\0' || ch == '/')
+ break;
+ if (bad_ref_char(ch))
+ return -1; /* Illegal character in refname. */
+ if (last == '.' && ch == '.')
+ return -1; /* Refname contains "..". */
+ if (last == '@' && ch == '{')
+ return -1; /* Refname contains "@{". */
+ last = ch;
+ }
+ if (cp == refname)
+ return 0; /* Component has zero length. */
+ if (refname[0] == '.') {
+ if (!(flags & REFNAME_DOT_COMPONENT))
+ return -1; /* Component starts with '.'. */
+ /*
+ * Even if leading dots are allowed, don't allow "."
+ * as a component (".." is prevented by a rule above).
+ */
+ if (refname[1] == '\0')
+ return -1; /* Component equals ".". */
+ }
+ if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
+ return -1; /* Refname ends with ".lock". */
+ return cp - refname;
+}
+
+int check_refname_format(const char *refname, int flags)
+{
+ int component_len, component_count = 0;
+
+ while (1) {
+ /* We are at the start of a path component. */
+ component_len = check_refname_component(refname, flags);
+ if (component_len <= 0) {
+ if ((flags & REFNAME_REFSPEC_PATTERN) &&
+ refname[0] == '*' &&
+ (refname[1] == '\0' || refname[1] == '/')) {
+ /* Accept one wildcard as a full refname component. */
+ flags &= ~REFNAME_REFSPEC_PATTERN;
+ component_len = 1;
+ } else {
+ return -1;
+ }
+ }
+ component_count++;
+ if (refname[component_len] == '\0')
+ break;
+ /* Skip to next component. */
+ refname += component_len + 1;
+ }
+
+ if (refname[component_len - 1] == '.')
+ return -1; /* Refname ends with '.'. */
+ if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
+ return -1; /* Refname has only one component. */
+ return 0;
+}
+
+struct ref_entry;
+
+/*
+ * Information used (along with the information in ref_entry) to
+ * describe a single cached reference. This data structure only
+ * occurs embedded in a union in struct ref_entry, and only when
+ * (ref_entry->flag & REF_DIR) is zero.
+ */
+struct ref_value {
unsigned char sha1[20];
unsigned char peeled[20];
- char name[FLEX_ARRAY];
};
-static const char *parse_ref_line(char *line, unsigned char *sha1)
-{
+struct ref_cache;
+
+/*
+ * Information used (along with the information in ref_entry) to
+ * describe a level in the hierarchy of references. This data
+ * structure only occurs embedded in a union in struct ref_entry, and
+ * only when (ref_entry.flag & REF_DIR) is set. In that case,
+ * (ref_entry.flag & REF_INCOMPLETE) determines whether the references
+ * in the directory have already been read:
+ *
+ * (ref_entry.flag & REF_INCOMPLETE) unset -- a directory of loose
+ * or packed references, already read.
+ *
+ * (ref_entry.flag & REF_INCOMPLETE) set -- a directory of loose
+ * references that hasn't been read yet (nor has any of its
+ * subdirectories).
+ *
+ * Entries within a directory are stored within a growable array of
+ * pointers to ref_entries (entries, nr, alloc). Entries 0 <= i <
+ * sorted are sorted by their component name in strcmp() order and the
+ * remaining entries are unsorted.
+ *
+ * Loose references are read lazily, one directory at a time. When a
+ * directory of loose references is read, then all of the references
+ * in that directory are stored, and REF_INCOMPLETE stubs are created
+ * for any subdirectories, but the subdirectories themselves are not
+ * read. The reading is triggered by get_ref_dir().
+ */
+struct ref_dir {
+ int nr, alloc;
+
/*
- * 42: the answer to everything.
- *
- * In this case, it happens to be the answer to
- * 40 (length of sha1 hex representation)
- * +1 (space in between hex and name)
- * +1 (newline at the end of the line)
+ * Entries with index 0 <= i < sorted are sorted by name. New
+ * entries are appended to the list unsorted, and are sorted
+ * only when required; thus we avoid the need to sort the list
+ * after the addition of every reference.
*/
- int len = strlen(line) - 42;
+ int sorted;
- if (len <= 0)
- return NULL;
- if (get_sha1_hex(line, sha1) < 0)
- return NULL;
- if (!isspace(line[40]))
+ /* A pointer to the ref_cache that contains this ref_dir. */
+ struct ref_cache *ref_cache;
+
+ struct ref_entry **entries;
+};
+
+/* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */
+#define REF_KNOWS_PEELED 0x08
+
+/* ref_entry represents a directory of references */
+#define REF_DIR 0x10
+
+/*
+ * Entry has not yet been read from disk (used only for REF_DIR
+ * entries representing loose references)
+ */
+#define REF_INCOMPLETE 0x20
+
+/*
+ * A ref_entry represents either a reference or a "subdirectory" of
+ * references.
+ *
+ * Each directory in the reference namespace is represented by a
+ * ref_entry with (flags & REF_DIR) set and containing a subdir member
+ * that holds the entries in that directory that have been read so
+ * far. If (flags & REF_INCOMPLETE) is set, then the directory and
+ * its subdirectories haven't been read yet. REF_INCOMPLETE is only
+ * used for loose reference directories.
+ *
+ * References are represented by a ref_entry with (flags & REF_DIR)
+ * unset and a value member that describes the reference's value. The
+ * flag member is at the ref_entry level, but it is also needed to
+ * interpret the contents of the value field (in other words, a
+ * ref_value object is not very much use without the enclosing
+ * ref_entry).
+ *
+ * Reference names cannot end with slash and directories' names are
+ * always stored with a trailing slash (except for the top-level
+ * directory, which is always denoted by ""). This has two nice
+ * consequences: (1) when the entries in each subdir are sorted
+ * lexicographically by name (as they usually are), the references in
+ * a whole tree can be generated in lexicographic order by traversing
+ * the tree in left-to-right, depth-first order; (2) the names of
+ * references and subdirectories cannot conflict, and therefore the
+ * presence of an empty subdirectory does not block the creation of a
+ * similarly-named reference. (The fact that reference names with the
+ * same leading components can conflict *with each other* is a
+ * separate issue that is regulated by is_refname_available().)
+ *
+ * Please note that the name field contains the fully-qualified
+ * reference (or subdirectory) name. Space could be saved by only
+ * storing the relative names. But that would require the full names
+ * to be generated on the fly when iterating in do_for_each_ref(), and
+ * would break callback functions, who have always been able to assume
+ * that the name strings that they are passed will not be freed during
+ * the iteration.
+ */
+struct ref_entry {
+ unsigned char flag; /* ISSYMREF? ISPACKED? */
+ union {
+ struct ref_value value; /* if not (flags&REF_DIR) */
+ struct ref_dir subdir; /* if (flags&REF_DIR) */
+ } u;
+ /*
+ * The full name of the reference (e.g., "refs/heads/master")
+ * or the full name of the directory with a trailing slash
+ * (e.g., "refs/heads/"):
+ */
+ char name[FLEX_ARRAY];
+};
+
+static void read_loose_refs(const char *dirname, struct ref_dir *dir);
+
+static struct ref_dir *get_ref_dir(struct ref_entry *entry)
+{
+ struct ref_dir *dir;
+ assert(entry->flag & REF_DIR);
+ dir = &entry->u.subdir;
+ if (entry->flag & REF_INCOMPLETE) {
+ read_loose_refs(entry->name, dir);
+ entry->flag &= ~REF_INCOMPLETE;
+ }
+ return dir;
+}
+
+static struct ref_entry *create_ref_entry(const char *refname,
+ const unsigned char *sha1, int flag,
+ int check_name)
+{
+ int len;
+ struct ref_entry *ref;
+
+ if (check_name &&
+ check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
+ die("Reference has invalid format: '%s'", refname);
+ len = strlen(refname) + 1;
+ ref = xmalloc(sizeof(struct ref_entry) + len);
+ hashcpy(ref->u.value.sha1, sha1);
+ hashclr(ref->u.value.peeled);
+ memcpy(ref->name, refname, len);
+ ref->flag = flag;
+ return ref;
+}
+
+static void clear_ref_dir(struct ref_dir *dir);
+
+static void free_ref_entry(struct ref_entry *entry)
+{
+ if (entry->flag & REF_DIR) {
+ /*
+ * Do not use get_ref_dir() here, as that might
+ * trigger the reading of loose refs.
+ */
+ clear_ref_dir(&entry->u.subdir);
+ }
+ free(entry);
+}
+
+/*
+ * Add a ref_entry to the end of dir (unsorted). Entry is always
+ * stored directly in dir; no recursion into subdirectories is
+ * done.
+ */
+static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry)
+{
+ ALLOC_GROW(dir->entries, dir->nr + 1, dir->alloc);
+ dir->entries[dir->nr++] = entry;
+ /* optimize for the case that entries are added in order */
+ if (dir->nr == 1 ||
+ (dir->nr == dir->sorted + 1 &&
+ strcmp(dir->entries[dir->nr - 2]->name,
+ dir->entries[dir->nr - 1]->name) < 0))
+ dir->sorted = dir->nr;
+}
+
+/*
+ * Clear and free all entries in dir, recursively.
+ */
+static void clear_ref_dir(struct ref_dir *dir)
+{
+ int i;
+ for (i = 0; i < dir->nr; i++)
+ free_ref_entry(dir->entries[i]);
+ free(dir->entries);
+ dir->sorted = dir->nr = dir->alloc = 0;
+ dir->entries = NULL;
+}
+
+/*
+ * Create a struct ref_entry object for the specified dirname.
+ * dirname is the name of the directory with a trailing slash (e.g.,
+ * "refs/heads/") or "" for the top-level directory.
+ */
+static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache,
+ const char *dirname, size_t len,
+ int incomplete)
+{
+ struct ref_entry *direntry;
+ direntry = xcalloc(1, sizeof(struct ref_entry) + len + 1);
+ memcpy(direntry->name, dirname, len);
+ direntry->name[len] = '\0';
+ direntry->u.subdir.ref_cache = ref_cache;
+ direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0);
+ return direntry;
+}
+
+static int ref_entry_cmp(const void *a, const void *b)
+{
+ struct ref_entry *one = *(struct ref_entry **)a;
+ struct ref_entry *two = *(struct ref_entry **)b;
+ return strcmp(one->name, two->name);
+}
+
+static void sort_ref_dir(struct ref_dir *dir);
+
+struct string_slice {
+ size_t len;
+ const char *str;
+};
+
+static int ref_entry_cmp_sslice(const void *key_, const void *ent_)
+{
+ struct string_slice *key = (struct string_slice *)key_;
+ struct ref_entry *ent = *(struct ref_entry **)ent_;
+ int entlen = strlen(ent->name);
+ int cmplen = key->len < entlen ? key->len : entlen;
+ int cmp = memcmp(key->str, ent->name, cmplen);
+ if (cmp)
+ return cmp;
+ return key->len - entlen;
+}
+
+/*
+ * Return the entry with the given refname from the ref_dir
+ * (non-recursively), sorting dir if necessary. Return NULL if no
+ * such entry is found. dir must already be complete.
+ */
+static struct ref_entry *search_ref_dir(struct ref_dir *dir,
+ const char *refname, size_t len)
+{
+ struct ref_entry **r;
+ struct string_slice key;
+
+ if (refname == NULL || !dir->nr)
return NULL;
- line += 41;
- if (isspace(*line))
+
+ sort_ref_dir(dir);
+ key.len = len;
+ key.str = refname;
+ r = bsearch(&key, dir->entries, dir->nr, sizeof(*dir->entries),
+ ref_entry_cmp_sslice);
+
+ if (r == NULL)
return NULL;
- if (line[len] != '\n')
+
+ return *r;
+}
+
+/*
+ * Search for a directory entry directly within dir (without
+ * recursing). Sort dir if necessary. subdirname must be a directory
+ * name (i.e., end in '/'). If mkdir is set, then create the
+ * directory if it is missing; otherwise, return NULL if the desired
+ * directory cannot be found. dir must already be complete.
+ */
+static struct ref_dir *search_for_subdir(struct ref_dir *dir,
+ const char *subdirname, size_t len,
+ int mkdir)
+{
+ struct ref_entry *entry = search_ref_dir(dir, subdirname, len);
+ if (!entry) {
+ if (!mkdir)
+ return NULL;
+ /*
+ * Since dir is complete, the absence of a subdir
+ * means that the subdir really doesn't exist;
+ * therefore, create an empty record for it but mark
+ * the record complete.
+ */
+ entry = create_dir_entry(dir->ref_cache, subdirname, len, 0);
+ add_entry_to_dir(dir, entry);
+ }
+ return get_ref_dir(entry);
+}
+
+/*
+ * If refname is a reference name, find the ref_dir within the dir
+ * tree that should hold refname. If refname is a directory name
+ * (i.e., ends in '/'), then return that ref_dir itself. dir must
+ * represent the top-level directory and must already be complete.
+ * Sort ref_dirs and recurse into subdirectories as necessary. If
+ * mkdir is set, then create any missing directories; otherwise,
+ * return NULL if the desired directory cannot be found.
+ */
+static struct ref_dir *find_containing_dir(struct ref_dir *dir,
+ const char *refname, int mkdir)
+{
+ const char *slash;
+ for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
+ size_t dirnamelen = slash - refname + 1;
+ struct ref_dir *subdir;
+ subdir = search_for_subdir(dir, refname, dirnamelen, mkdir);
+ if (!subdir) {
+ dir = NULL;
+ break;
+ }
+ dir = subdir;
+ }
+
+ return dir;
+}
+
+/*
+ * Find the value entry with the given name in dir, sorting ref_dirs
+ * and recursing into subdirectories as necessary. If the name is not
+ * found or it corresponds to a directory entry, return NULL.
+ */
+static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname)
+{
+ struct ref_entry *entry;
+ dir = find_containing_dir(dir, refname, 0);
+ if (!dir)
return NULL;
- line[len] = 0;
+ entry = search_ref_dir(dir, refname, strlen(refname));
+ return (entry && !(entry->flag & REF_DIR)) ? entry : NULL;
+}
- return line;
+/*
+ * Add a ref_entry to the ref_dir (unsorted), recursing into
+ * subdirectories as necessary. dir must represent the top-level
+ * directory. Return 0 on success.
+ */
+static int add_ref(struct ref_dir *dir, struct ref_entry *ref)
+{
+ dir = find_containing_dir(dir, ref->name, 1);
+ if (!dir)
+ return -1;
+ add_entry_to_dir(dir, ref);
+ return 0;
}
-static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
- int flag, struct ref_list *list,
- struct ref_list **new_entry)
+/*
+ * Emit a warning and return true iff ref1 and ref2 have the same name
+ * and the same sha1. Die if they have the same name but different
+ * sha1s.
+ */
+static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2)
{
- int len;
- struct ref_list *entry;
-
- /* Allocate it and add it in.. */
- len = strlen(name) + 1;
- entry = xmalloc(sizeof(struct ref_list) + len);
- hashcpy(entry->sha1, sha1);
- hashclr(entry->peeled);
- memcpy(entry->name, name, len);
- entry->flag = flag;
- entry->next = list;
- if (new_entry)
- *new_entry = entry;
- return entry;
-}
-
-/* merge sort the ref list */
-static struct ref_list *sort_ref_list(struct ref_list *list)
-{
- int psize, qsize, last_merge_count, cmp;
- struct ref_list *p, *q, *l, *e;
- struct ref_list *new_list = list;
- int k = 1;
- int merge_count = 0;
-
- if (!list)
- return list;
-
- do {
- last_merge_count = merge_count;
- merge_count = 0;
-
- psize = 0;
-
- p = new_list;
- q = new_list;
- new_list = NULL;
- l = NULL;
-
- while (p) {
- merge_count++;
-
- while (psize < k && q->next) {
- q = q->next;
- psize++;
- }
- qsize = k;
-
- while ((psize > 0) || (qsize > 0 && q)) {
- if (qsize == 0 || !q) {
- e = p;
- p = p->next;
- psize--;
- } else if (psize == 0) {
- e = q;
- q = q->next;
- qsize--;
- } else {
- cmp = strcmp(q->name, p->name);
- if (cmp < 0) {
- e = q;
- q = q->next;
- qsize--;
- } else if (cmp > 0) {
- e = p;
- p = p->next;
- psize--;
- } else {
- if (hashcmp(q->sha1, p->sha1))
- die("Duplicated ref, and SHA1s don't match: %s",
- q->name);
- warning("Duplicated ref: %s", q->name);
- e = q;
- q = q->next;
- qsize--;
- free(e);
- e = p;
- p = p->next;
- psize--;
- }
- }
+ if (strcmp(ref1->name, ref2->name))
+ return 0;
+
+ /* Duplicate name; make sure that they don't conflict: */
+
+ if ((ref1->flag & REF_DIR) || (ref2->flag & REF_DIR))
+ /* This is impossible by construction */
+ die("Reference directory conflict: %s", ref1->name);
+
+ if (hashcmp(ref1->u.value.sha1, ref2->u.value.sha1))
+ die("Duplicated ref, and SHA1s don't match: %s", ref1->name);
+
+ warning("Duplicated ref: %s", ref1->name);
+ return 1;
+}
+
+/*
+ * Sort the entries in dir non-recursively (if they are not already
+ * sorted) and remove any duplicate entries.
+ */
+static void sort_ref_dir(struct ref_dir *dir)
+{
+ int i, j;
+ struct ref_entry *last = NULL;
+
+ /*
+ * This check also prevents passing a zero-length array to qsort(),
+ * which is a problem on some platforms.
+ */
+ if (dir->sorted == dir->nr)
+ return;
- e->next = NULL;
+ qsort(dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp);
- if (l)
- l->next = e;
- if (!new_list)
- new_list = e;
- l = e;
+ /* Remove any duplicates: */
+ for (i = 0, j = 0; j < dir->nr; j++) {
+ struct ref_entry *entry = dir->entries[j];
+ if (last && is_dup_ref(last, entry))
+ free_ref_entry(entry);
+ else
+ last = dir->entries[i++] = entry;
+ }
+ dir->sorted = dir->nr = i;
+}
+
+#define DO_FOR_EACH_INCLUDE_BROKEN 01
+
+static struct ref_entry *current_ref;
+
+static int do_one_ref(const char *base, each_ref_fn fn, int trim,
+ int flags, void *cb_data, struct ref_entry *entry)
+{
+ int retval;
+ if (prefixcmp(entry->name, base))
+ return 0;
+
+ if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+ if (entry->flag & REF_ISBROKEN)
+ return 0; /* ignore broken refs e.g. dangling symref */
+ if (!has_sha1_file(entry->u.value.sha1)) {
+ error("%s does not point to a valid object!", entry->name);
+ return 0;
+ }
+ }
+ current_ref = entry;
+ retval = fn(entry->name + trim, entry->u.value.sha1, entry->flag, cb_data);
+ current_ref = NULL;
+ return retval;
+}
+
+/*
+ * Call fn for each reference in dir that has index in the range
+ * offset <= index < dir->nr. Recurse into subdirectories that are in
+ * that index range, sorting them before iterating. This function
+ * does not sort dir itself; it should be sorted beforehand.
+ */
+static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
+ const char *base,
+ each_ref_fn fn, int trim, int flags, void *cb_data)
+{
+ int i;
+ assert(dir->sorted == dir->nr);
+ for (i = offset; i < dir->nr; i++) {
+ struct ref_entry *entry = dir->entries[i];
+ int retval;
+ if (entry->flag & REF_DIR) {
+ struct ref_dir *subdir = get_ref_dir(entry);
+ sort_ref_dir(subdir);
+ retval = do_for_each_ref_in_dir(subdir, 0,
+ base, fn, trim, flags, cb_data);
+ } else {
+ retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
+ }
+ if (retval)
+ return retval;
+ }
+ return 0;
+}
+
+/*
+ * Call fn for each reference in the union of dir1 and dir2, in order
+ * by refname. Recurse into subdirectories. If a value entry appears
+ * in both dir1 and dir2, then only process the version that is in
+ * dir2. The input dirs must already be sorted, but subdirs will be
+ * sorted as needed.
+ */
+static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
+ struct ref_dir *dir2,
+ const char *base, each_ref_fn fn, int trim,
+ int flags, void *cb_data)
+{
+ int retval;
+ int i1 = 0, i2 = 0;
+
+ assert(dir1->sorted == dir1->nr);
+ assert(dir2->sorted == dir2->nr);
+ while (1) {
+ struct ref_entry *e1, *e2;
+ int cmp;
+ if (i1 == dir1->nr) {
+ return do_for_each_ref_in_dir(dir2, i2,
+ base, fn, trim, flags, cb_data);
+ }
+ if (i2 == dir2->nr) {
+ return do_for_each_ref_in_dir(dir1, i1,
+ base, fn, trim, flags, cb_data);
+ }
+ e1 = dir1->entries[i1];
+ e2 = dir2->entries[i2];
+ cmp = strcmp(e1->name, e2->name);
+ if (cmp == 0) {
+ if ((e1->flag & REF_DIR) && (e2->flag & REF_DIR)) {
+ /* Both are directories; descend them in parallel. */
+ struct ref_dir *subdir1 = get_ref_dir(e1);
+ struct ref_dir *subdir2 = get_ref_dir(e2);
+ sort_ref_dir(subdir1);
+ sort_ref_dir(subdir2);
+ retval = do_for_each_ref_in_dirs(
+ subdir1, subdir2,
+ base, fn, trim, flags, cb_data);
+ i1++;
+ i2++;
+ } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) {
+ /* Both are references; ignore the one from dir1. */
+ retval = do_one_ref(base, fn, trim, flags, cb_data, e2);
+ i1++;
+ i2++;
+ } else {
+ die("conflict between reference and directory: %s",
+ e1->name);
+ }
+ } else {
+ struct ref_entry *e;
+ if (cmp < 0) {
+ e = e1;
+ i1++;
+ } else {
+ e = e2;
+ i2++;
+ }
+ if (e->flag & REF_DIR) {
+ struct ref_dir *subdir = get_ref_dir(e);
+ sort_ref_dir(subdir);
+ retval = do_for_each_ref_in_dir(
+ subdir, 0,
+ base, fn, trim, flags, cb_data);
+ } else {
+ retval = do_one_ref(base, fn, trim, flags, cb_data, e);
}
+ }
+ if (retval)
+ return retval;
+ }
+ if (i1 < dir1->nr)
+ return do_for_each_ref_in_dir(dir1, i1,
+ base, fn, trim, flags, cb_data);
+ if (i2 < dir2->nr)
+ return do_for_each_ref_in_dir(dir2, i2,
+ base, fn, trim, flags, cb_data);
+ return 0;
+}
+
+/*
+ * Return true iff refname1 and refname2 conflict with each other.
+ * Two reference names conflict if one of them exactly matches the
+ * leading components of the other; e.g., "foo/bar" conflicts with
+ * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
+ * "foo/barbados".
+ */
+static int names_conflict(const char *refname1, const char *refname2)
+{
+ for (; *refname1 && *refname1 == *refname2; refname1++, refname2++)
+ ;
+ return (*refname1 == '\0' && *refname2 == '/')
+ || (*refname1 == '/' && *refname2 == '\0');
+}
- p = q;
- };
+struct name_conflict_cb {
+ const char *refname;
+ const char *oldrefname;
+ const char *conflicting_refname;
+};
- k = k * 2;
- } while ((last_merge_count != merge_count) || (last_merge_count != 1));
+static int name_conflict_fn(const char *existingrefname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
+ if (data->oldrefname && !strcmp(data->oldrefname, existingrefname))
+ return 0;
+ if (names_conflict(data->refname, existingrefname)) {
+ data->conflicting_refname = existingrefname;
+ return 1;
+ }
+ return 0;
+}
- return new_list;
+/*
+ * Return true iff a reference named refname could be created without
+ * conflicting with the name of an existing reference in array. If
+ * oldrefname is non-NULL, ignore potential conflicts with oldrefname
+ * (e.g., because oldrefname is scheduled for deletion in the same
+ * operation).
+ */
+static int is_refname_available(const char *refname, const char *oldrefname,
+ struct ref_dir *dir)
+{
+ struct name_conflict_cb data;
+ data.refname = refname;
+ data.oldrefname = oldrefname;
+ data.conflicting_refname = NULL;
+
+ sort_ref_dir(dir);
+ if (do_for_each_ref_in_dir(dir, 0, "", name_conflict_fn,
+ 0, DO_FOR_EACH_INCLUDE_BROKEN,
+ &data)) {
+ error("'%s' exists; cannot create '%s'",
+ data.conflicting_refname, refname);
+ return 0;
+ }
+ return 1;
}
/*
* Future: need to be in "struct repository"
* when doing a full libification.
*/
-static struct cached_refs {
- char did_loose;
- char did_packed;
- struct ref_list *loose;
- struct ref_list *packed;
-} cached_refs, submodule_refs;
-static struct ref_list *current_ref;
+static struct ref_cache {
+ struct ref_cache *next;
+ struct ref_entry *loose;
+ struct ref_entry *packed;
+ /* The submodule name, or "" for the main repo. */
+ char name[FLEX_ARRAY];
+} *ref_cache;
-static struct ref_list *extra_refs;
+static void clear_packed_ref_cache(struct ref_cache *refs)
+{
+ if (refs->packed) {
+ free_ref_entry(refs->packed);
+ refs->packed = NULL;
+ }
+}
-static void free_ref_list(struct ref_list *list)
+static void clear_loose_ref_cache(struct ref_cache *refs)
{
- struct ref_list *next;
- for ( ; list; list = next) {
- next = list->next;
- free(list);
+ if (refs->loose) {
+ free_ref_entry(refs->loose);
+ refs->loose = NULL;
}
}
-static void invalidate_cached_refs(void)
+static struct ref_cache *create_ref_cache(const char *submodule)
+{
+ int len;
+ struct ref_cache *refs;
+ if (!submodule)
+ submodule = "";
+ len = strlen(submodule) + 1;
+ refs = xcalloc(1, sizeof(struct ref_cache) + len);
+ memcpy(refs->name, submodule, len);
+ return refs;
+}
+
+/*
+ * Return a pointer to a ref_cache for the specified submodule. For
+ * the main repository, use submodule==NULL. The returned structure
+ * will be allocated and initialized but not necessarily populated; it
+ * should not be freed.
+ */
+static struct ref_cache *get_ref_cache(const char *submodule)
{
- struct cached_refs *ca = &cached_refs;
+ struct ref_cache *refs = ref_cache;
+ if (!submodule)
+ submodule = "";
+ while (refs) {
+ if (!strcmp(submodule, refs->name))
+ return refs;
+ refs = refs->next;
+ }
- if (ca->did_loose && ca->loose)
- free_ref_list(ca->loose);
- if (ca->did_packed && ca->packed)
- free_ref_list(ca->packed);
- ca->loose = ca->packed = NULL;
- ca->did_loose = ca->did_packed = 0;
+ refs = create_ref_cache(submodule);
+ refs->next = ref_cache;
+ ref_cache = refs;
+ return refs;
}
-static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
+void invalidate_ref_cache(const char *submodule)
{
- struct ref_list *list = NULL;
- struct ref_list *last = NULL;
+ struct ref_cache *refs = get_ref_cache(submodule);
+ clear_packed_ref_cache(refs);
+ clear_loose_ref_cache(refs);
+}
+
+/*
+ * Parse one line from a packed-refs file. Write the SHA1 to sha1.
+ * Return a pointer to the refname within the line (null-terminated),
+ * or NULL if there was a problem.
+ */
+static const char *parse_ref_line(char *line, unsigned char *sha1)
+{
+ /*
+ * 42: the answer to everything.
+ *
+ * In this case, it happens to be the answer to
+ * 40 (length of sha1 hex representation)
+ * +1 (space in between hex and name)
+ * +1 (newline at the end of the line)
+ */
+ int len = strlen(line) - 42;
+
+ if (len <= 0)
+ return NULL;
+ if (get_sha1_hex(line, sha1) < 0)
+ return NULL;
+ if (!isspace(line[40]))
+ return NULL;
+ line += 41;
+ if (isspace(*line))
+ return NULL;
+ if (line[len] != '\n')
+ return NULL;
+ line[len] = 0;
+
+ return line;
+}
+
+static void read_packed_refs(FILE *f, struct ref_dir *dir)
+{
+ struct ref_entry *last = NULL;
char refline[PATH_MAX];
int flag = REF_ISPACKED;
while (fgets(refline, sizeof(refline), f)) {
unsigned char sha1[20];
- const char *name;
+ const char *refname;
static const char header[] = "# pack-refs with:";
if (!strncmp(refline, header, sizeof(header)-1)) {
@@ -203,9 +823,10 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
continue;
}
- name = parse_ref_line(refline, sha1);
- if (name) {
- list = add_ref(name, sha1, flag, list, &last);
+ refname = parse_ref_line(refline, sha1);
+ if (refname) {
+ last = create_ref_entry(refname, sha1, flag, 1);
+ add_ref(dir, last);
continue;
}
if (last &&
@@ -213,206 +834,161 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
strlen(refline) == 42 &&
refline[41] == '\n' &&
!get_sha1_hex(refline + 1, sha1))
- hashcpy(last->peeled, sha1);
+ hashcpy(last->u.value.peeled, sha1);
}
- cached_refs->packed = sort_ref_list(list);
-}
-
-void add_extra_ref(const char *name, const unsigned char *sha1, int flag)
-{
- extra_refs = add_ref(name, sha1, flag, extra_refs, NULL);
}
-void clear_extra_refs(void)
+static struct ref_dir *get_packed_refs(struct ref_cache *refs)
{
- free_ref_list(extra_refs);
- extra_refs = NULL;
-}
-
-static struct ref_list *get_packed_refs(const char *submodule)
-{
- const char *packed_refs_file;
- struct cached_refs *refs;
-
- if (submodule) {
- packed_refs_file = git_path_submodule(submodule, "packed-refs");
- refs = &submodule_refs;
- free_ref_list(refs->packed);
- } else {
- packed_refs_file = git_path("packed-refs");
- refs = &cached_refs;
- }
+ if (!refs->packed) {
+ const char *packed_refs_file;
+ FILE *f;
- if (!refs->did_packed || submodule) {
- FILE *f = fopen(packed_refs_file, "r");
- refs->packed = NULL;
+ refs->packed = create_dir_entry(refs, "", 0, 0);
+ if (*refs->name)
+ packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+ else
+ packed_refs_file = git_path("packed-refs");
+ f = fopen(packed_refs_file, "r");
if (f) {
- read_packed_refs(f, refs);
+ read_packed_refs(f, get_ref_dir(refs->packed));
fclose(f);
}
- refs->did_packed = 1;
}
- return refs->packed;
+ return get_ref_dir(refs->packed);
+}
+
+void add_packed_ref(const char *refname, const unsigned char *sha1)
+{
+ add_ref(get_packed_refs(get_ref_cache(NULL)),
+ create_ref_entry(refname, sha1, REF_ISPACKED, 1));
}
-static struct ref_list *get_ref_dir(const char *submodule, const char *base,
- struct ref_list *list)
+/*
+ * Read the loose references from the namespace dirname into dir
+ * (without recursing). dirname must end with '/'. dir must be the
+ * directory entry corresponding to dirname.
+ */
+static void read_loose_refs(const char *dirname, struct ref_dir *dir)
{
- DIR *dir;
+ struct ref_cache *refs = dir->ref_cache;
+ DIR *d;
const char *path;
+ struct dirent *de;
+ int dirnamelen = strlen(dirname);
+ struct strbuf refname;
- if (submodule)
- path = git_path_submodule(submodule, "%s", base);
+ if (*refs->name)
+ path = git_path_submodule(refs->name, "%s", dirname);
else
- path = git_path("%s", base);
-
+ path = git_path("%s", dirname);
- dir = opendir(path);
+ d = opendir(path);
+ if (!d)
+ return;
- if (dir) {
- struct dirent *de;
- int baselen = strlen(base);
- char *ref = xmalloc(baselen + 257);
+ strbuf_init(&refname, dirnamelen + 257);
+ strbuf_add(&refname, dirname, dirnamelen);
- memcpy(ref, base, baselen);
- if (baselen && base[baselen-1] != '/')
- ref[baselen++] = '/';
-
- while ((de = readdir(dir)) != NULL) {
- unsigned char sha1[20];
- struct stat st;
- int flag;
- int namelen;
- const char *refdir;
+ while ((de = readdir(d)) != NULL) {
+ unsigned char sha1[20];
+ struct stat st;
+ int flag;
+ const char *refdir;
- if (de->d_name[0] == '.')
- continue;
- namelen = strlen(de->d_name);
- if (namelen > 255)
- continue;
- if (has_extension(de->d_name, ".lock"))
- continue;
- memcpy(ref + baselen, de->d_name, namelen+1);
- refdir = submodule
- ? git_path_submodule(submodule, "%s", ref)
- : git_path("%s", ref);
- if (stat(refdir, &st) < 0)
- continue;
- if (S_ISDIR(st.st_mode)) {
- list = get_ref_dir(submodule, ref, list);
- continue;
- }
- if (submodule) {
+ if (de->d_name[0] == '.')
+ continue;
+ if (has_extension(de->d_name, ".lock"))
+ continue;
+ strbuf_addstr(&refname, de->d_name);
+ refdir = *refs->name
+ ? git_path_submodule(refs->name, "%s", refname.buf)
+ : git_path("%s", refname.buf);
+ if (stat(refdir, &st) < 0) {
+ ; /* silently ignore */
+ } else if (S_ISDIR(st.st_mode)) {
+ strbuf_addch(&refname, '/');
+ add_entry_to_dir(dir,
+ create_dir_entry(refs, refname.buf,
+ refname.len, 1));
+ } else {
+ if (*refs->name) {
hashclr(sha1);
flag = 0;
- if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
- hashclr(sha1);
- flag |= REF_BROKEN;
- }
- } else
- if (!resolve_ref(ref, sha1, 1, &flag)) {
+ if (resolve_gitlink_ref(refs->name, refname.buf, sha1) < 0) {
hashclr(sha1);
- flag |= REF_BROKEN;
+ flag |= REF_ISBROKEN;
}
- list = add_ref(ref, sha1, flag, list, NULL);
+ } else if (read_ref_full(refname.buf, sha1, 1, &flag)) {
+ hashclr(sha1);
+ flag |= REF_ISBROKEN;
+ }
+ add_entry_to_dir(dir,
+ create_ref_entry(refname.buf, sha1, flag, 1));
}
- free(ref);
- closedir(dir);
+ strbuf_setlen(&refname, dirnamelen);
}
- return sort_ref_list(list);
+ strbuf_release(&refname);
+ closedir(d);
}
-struct warn_if_dangling_data {
- FILE *fp;
- const char *refname;
- const char *msg_fmt;
-};
-
-static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
-{
- struct warn_if_dangling_data *d = cb_data;
- const char *resolves_to;
- unsigned char junk[20];
-
- if (!(flags & REF_ISSYMREF))
- return 0;
-
- resolves_to = resolve_ref(refname, junk, 0, NULL);
- if (!resolves_to || strcmp(resolves_to, d->refname))
- return 0;
-
- fprintf(d->fp, d->msg_fmt, refname);
- return 0;
-}
-
-void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
+static struct ref_dir *get_loose_refs(struct ref_cache *refs)
{
- struct warn_if_dangling_data data;
-
- data.fp = fp;
- data.refname = refname;
- data.msg_fmt = msg_fmt;
- for_each_rawref(warn_if_dangling_symref, &data);
-}
-
-static struct ref_list *get_loose_refs(const char *submodule)
-{
- if (submodule) {
- free_ref_list(submodule_refs.loose);
- submodule_refs.loose = get_ref_dir(submodule, "refs", NULL);
- return submodule_refs.loose;
- }
-
- if (!cached_refs.did_loose) {
- cached_refs.loose = get_ref_dir(NULL, "refs", NULL);
- cached_refs.did_loose = 1;
+ if (!refs->loose) {
+ /*
+ * Mark the top-level directory complete because we
+ * are about to read the only subdirectory that can
+ * hold references:
+ */
+ refs->loose = create_dir_entry(refs, "", 0, 0);
+ /*
+ * Create an incomplete entry for "refs/":
+ */
+ add_entry_to_dir(get_ref_dir(refs->loose),
+ create_dir_entry(refs, "refs/", 5, 1));
}
- return cached_refs.loose;
+ return get_ref_dir(refs->loose);
}
/* We allow "recursive" symbolic refs. Only within reason, though */
#define MAXDEPTH 5
#define MAXREFLEN (1024)
-static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result)
+/*
+ * Called by resolve_gitlink_ref_recursive() after it failed to read
+ * from the loose refs in ref_cache refs. Find <refname> in the
+ * packed-refs file for the submodule.
+ */
+static int resolve_gitlink_packed_ref(struct ref_cache *refs,
+ const char *refname, unsigned char *sha1)
{
- FILE *f;
- struct cached_refs refs;
- struct ref_list *ref;
- int retval;
+ struct ref_entry *ref;
+ struct ref_dir *dir = get_packed_refs(refs);
- strcpy(name + pathlen, "packed-refs");
- f = fopen(name, "r");
- if (!f)
+ ref = find_ref(dir, refname);
+ if (ref == NULL)
return -1;
- read_packed_refs(f, &refs);
- fclose(f);
- ref = refs.packed;
- retval = -1;
- while (ref) {
- if (!strcmp(ref->name, refname)) {
- retval = 0;
- memcpy(result, ref->sha1, 20);
- break;
- }
- ref = ref->next;
- }
- free_ref_list(refs.packed);
- return retval;
+
+ memcpy(sha1, ref->u.value.sha1, 20);
+ return 0;
}
-static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *refname, unsigned char *result, int recursion)
+static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
+ const char *refname, unsigned char *sha1,
+ int recursion)
{
- int fd, len = strlen(refname);
+ int fd, len;
char buffer[128], *p;
+ char *path;
- if (recursion > MAXDEPTH || len > MAXREFLEN)
+ if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
return -1;
- memcpy(name + pathlen, refname, len+1);
- fd = open(name, O_RDONLY);
+ path = *refs->name
+ ? git_path_submodule(refs->name, "%s", refname)
+ : git_path("%s", refname);
+ fd = open(path, O_RDONLY);
if (fd < 0)
- return resolve_gitlink_packed_ref(name, pathlen, refname, result);
+ return resolve_gitlink_packed_ref(refs, refname, sha1);
len = read(fd, buffer, sizeof(buffer)-1);
close(fd);
@@ -423,7 +999,7 @@ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *re
buffer[len] = 0;
/* Was it a detached head or an old-fashioned symlink? */
- if (!get_sha1_hex(buffer, result))
+ if (!get_sha1_hex(buffer, sha1))
return 0;
/* Symref? */
@@ -433,60 +1009,55 @@ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *re
while (isspace(*p))
p++;
- return resolve_gitlink_ref_recursive(name, pathlen, p, result, recursion+1);
+ return resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1);
}
-int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *result)
+int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1)
{
int len = strlen(path), retval;
- char *gitdir;
- const char *tmp;
+ char *submodule;
+ struct ref_cache *refs;
while (len && path[len-1] == '/')
len--;
if (!len)
return -1;
- gitdir = xmalloc(len + MAXREFLEN + 8);
- memcpy(gitdir, path, len);
- memcpy(gitdir + len, "/.git", 6);
- len += 5;
-
- tmp = read_gitfile(gitdir);
- if (tmp) {
- free(gitdir);
- len = strlen(tmp);
- gitdir = xmalloc(len + MAXREFLEN + 3);
- memcpy(gitdir, tmp, len);
- }
- gitdir[len] = '/';
- gitdir[++len] = '\0';
- retval = resolve_gitlink_ref_recursive(gitdir, len, refname, result, 0);
- free(gitdir);
+ submodule = xstrndup(path, len);
+ refs = get_ref_cache(submodule);
+ free(submodule);
+
+ retval = resolve_gitlink_ref_recursive(refs, refname, sha1, 0);
return retval;
}
/*
- * If the "reading" argument is set, this function finds out what _object_
- * the ref points at by "reading" the ref. The ref, if it is not symbolic,
- * has to exist, and if it is symbolic, it has to point at an existing ref,
- * because the "read" goes through the symref to the ref it points at.
- *
- * The access that is not "reading" may often be "writing", but does not
- * have to; it can be merely checking _where it leads to_. If it is a
- * prelude to "writing" to the ref, a write to a symref that points at
- * yet-to-be-born ref will create the real ref pointed by the symref.
- * reading=0 allows the caller to check where such a symref leads to.
+ * Try to read ref from the packed references. On success, set sha1
+ * and return 0; otherwise, return -1.
*/
-const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
+static int get_packed_ref(const char *refname, unsigned char *sha1)
+{
+ struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
+ struct ref_entry *entry = find_ref(packed, refname);
+ if (entry) {
+ hashcpy(sha1, entry->u.value.sha1);
+ return 0;
+ }
+ return -1;
+}
+
+const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
{
int depth = MAXDEPTH;
ssize_t len;
char buffer[256];
- static char ref_buffer[256];
+ static char refname_buffer[256];
if (flag)
*flag = 0;
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
+ return NULL;
+
for (;;) {
char path[PATH_MAX];
struct stat st;
@@ -496,32 +1067,39 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
if (--depth < 0)
return NULL;
- git_snpath(path, sizeof(path), "%s", ref);
- /* Special case: non-existing file. */
+ git_snpath(path, sizeof(path), "%s", refname);
+
if (lstat(path, &st) < 0) {
- struct ref_list *list = get_packed_refs(NULL);
- while (list) {
- if (!strcmp(ref, list->name)) {
- hashcpy(sha1, list->sha1);
- if (flag)
- *flag |= REF_ISPACKED;
- return ref;
- }
- list = list->next;
+ if (errno != ENOENT)
+ return NULL;
+ /*
+ * The loose reference file does not exist;
+ * check for a packed reference.
+ */
+ if (!get_packed_ref(refname, sha1)) {
+ if (flag)
+ *flag |= REF_ISPACKED;
+ return refname;
}
- if (reading || errno != ENOENT)
+ /* The reference is not a packed reference, either. */
+ if (reading) {
return NULL;
- hashclr(sha1);
- return ref;
+ } else {
+ hashclr(sha1);
+ return refname;
+ }
}
/* Follow "normalized" - ie "refs/.." symlinks by hand */
if (S_ISLNK(st.st_mode)) {
len = readlink(path, buffer, sizeof(buffer)-1);
- if (len >= 5 && !memcmp("refs/", buffer, 5)) {
- buffer[len] = 0;
- strcpy(ref_buffer, buffer);
- ref = ref_buffer;
+ if (len < 0)
+ return NULL;
+ buffer[len] = 0;
+ if (!prefixcmp(buffer, "refs/") &&
+ !check_refname_format(buffer, 0)) {
+ strcpy(refname_buffer, buffer);
+ refname = refname_buffer;
if (flag)
*flag |= REF_ISSYMREF;
continue;
@@ -543,27 +1121,42 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
return NULL;
len = read_in_full(fd, buffer, sizeof(buffer)-1);
close(fd);
+ if (len < 0)
+ return NULL;
+ while (len && isspace(buffer[len-1]))
+ len--;
+ buffer[len] = '\0';
/*
* Is it a symbolic ref?
*/
- if (len < 4 || memcmp("ref:", buffer, 4))
+ if (prefixcmp(buffer, "ref:"))
break;
- buf = buffer + 4;
- len -= 4;
- while (len && isspace(*buf))
- buf++, len--;
- while (len && isspace(buf[len-1]))
- len--;
- buf[len] = 0;
- memcpy(ref_buffer, buf, len + 1);
- ref = ref_buffer;
if (flag)
*flag |= REF_ISSYMREF;
+ buf = buffer + 4;
+ while (isspace(*buf))
+ buf++;
+ if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
+ if (flag)
+ *flag |= REF_ISBROKEN;
+ return NULL;
+ }
+ refname = strcpy(refname_buffer, buf);
}
- if (len < 40 || get_sha1_hex(buffer, sha1))
+ /* Please note that FETCH_HEAD has a second line containing other data. */
+ if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) {
+ if (flag)
+ *flag |= REF_ISBROKEN;
return NULL;
- return ref;
+ }
+ return refname;
+}
+
+char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
+{
+ const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag);
+ return ret ? xstrdup(ret) : NULL;
}
/* The argument to filter_refs */
@@ -573,80 +1166,66 @@ struct ref_filter {
void *cb_data;
};
-int read_ref(const char *ref, unsigned char *sha1)
+int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags)
{
- if (resolve_ref(ref, sha1, 1, NULL))
+ if (resolve_ref_unsafe(refname, sha1, reading, flags))
return 0;
return -1;
}
-#define DO_FOR_EACH_INCLUDE_BROKEN 01
-static int do_one_ref(const char *base, each_ref_fn fn, int trim,
- int flags, void *cb_data, struct ref_list *entry)
+int read_ref(const char *refname, unsigned char *sha1)
{
- if (strncmp(base, entry->name, trim))
- return 0;
+ return read_ref_full(refname, sha1, 1, NULL);
+}
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
- if (entry->flag & REF_BROKEN)
- return 0; /* ignore dangling symref */
- if (!has_sha1_file(entry->sha1)) {
- error("%s does not point to a valid object!", entry->name);
- return 0;
- }
- }
- current_ref = entry;
- return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
+int ref_exists(const char *refname)
+{
+ unsigned char sha1[20];
+ return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
}
-static int filter_refs(const char *ref, const unsigned char *sha, int flags,
- void *data)
+static int filter_refs(const char *refname, const unsigned char *sha1, int flags,
+ void *data)
{
struct ref_filter *filter = (struct ref_filter *)data;
- if (fnmatch(filter->pattern, ref, 0))
+ if (fnmatch(filter->pattern, refname, 0))
return 0;
- return filter->fn(ref, sha, flags, filter->cb_data);
+ return filter->fn(refname, sha1, flags, filter->cb_data);
}
-int peel_ref(const char *ref, unsigned char *sha1)
+int peel_ref(const char *refname, unsigned char *sha1)
{
int flag;
unsigned char base[20];
struct object *o;
- if (current_ref && (current_ref->name == ref
- || !strcmp(current_ref->name, ref))) {
+ if (current_ref && (current_ref->name == refname
+ || !strcmp(current_ref->name, refname))) {
if (current_ref->flag & REF_KNOWS_PEELED) {
- hashcpy(sha1, current_ref->peeled);
+ hashcpy(sha1, current_ref->u.value.peeled);
return 0;
}
- hashcpy(base, current_ref->sha1);
+ hashcpy(base, current_ref->u.value.sha1);
goto fallback;
}
- if (!resolve_ref(ref, base, 1, &flag))
+ if (read_ref_full(refname, base, 1, &flag))
return -1;
if ((flag & REF_ISPACKED)) {
- struct ref_list *list = get_packed_refs(NULL);
+ struct ref_dir *dir = get_packed_refs(get_ref_cache(NULL));
+ struct ref_entry *r = find_ref(dir, refname);
- while (list) {
- if (!strcmp(list->name, ref)) {
- if (list->flag & REF_KNOWS_PEELED) {
- hashcpy(sha1, list->peeled);
- return 0;
- }
- /* older pack-refs did not leave peeled ones */
- break;
- }
- list = list->next;
+ if (r != NULL && r->flag & REF_KNOWS_PEELED) {
+ hashcpy(sha1, r->u.value.peeled);
+ return 0;
}
}
fallback:
o = parse_object(base);
if (o && o->type == OBJ_TAG) {
- o = deref_tag(o, ref, 0);
+ o = deref_tag(o, refname, 0);
if (o) {
hashcpy(sha1, o->sha1);
return 0;
@@ -655,49 +1234,75 @@ fallback:
return -1;
}
-static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
- int trim, int flags, void *cb_data)
+struct warn_if_dangling_data {
+ FILE *fp;
+ const char *refname;
+ const char *msg_fmt;
+};
+
+static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
{
- int retval = 0;
- struct ref_list *packed = get_packed_refs(submodule);
- struct ref_list *loose = get_loose_refs(submodule);
+ struct warn_if_dangling_data *d = cb_data;
+ const char *resolves_to;
+ unsigned char junk[20];
+
+ if (!(flags & REF_ISSYMREF))
+ return 0;
+
+ resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
+ if (!resolves_to || strcmp(resolves_to, d->refname))
+ return 0;
- struct ref_list *extra;
+ fprintf(d->fp, d->msg_fmt, refname);
+ fputc('\n', d->fp);
+ return 0;
+}
- for (extra = extra_refs; extra; extra = extra->next)
- retval = do_one_ref(base, fn, trim, flags, cb_data, extra);
+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
+{
+ struct warn_if_dangling_data data;
- while (packed && loose) {
- struct ref_list *entry;
- int cmp = strcmp(packed->name, loose->name);
- if (!cmp) {
- packed = packed->next;
- continue;
- }
- if (cmp > 0) {
- entry = loose;
- loose = loose->next;
- } else {
- entry = packed;
- packed = packed->next;
- }
- retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
- if (retval)
- goto end_each;
- }
+ data.fp = fp;
+ data.refname = refname;
+ data.msg_fmt = msg_fmt;
+ for_each_rawref(warn_if_dangling_symref, &data);
+}
- for (packed = packed ? packed : loose; packed; packed = packed->next) {
- retval = do_one_ref(base, fn, trim, flags, cb_data, packed);
- if (retval)
- goto end_each;
+static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
+ int trim, int flags, void *cb_data)
+{
+ struct ref_cache *refs = get_ref_cache(submodule);
+ struct ref_dir *packed_dir = get_packed_refs(refs);
+ struct ref_dir *loose_dir = get_loose_refs(refs);
+ int retval = 0;
+
+ if (base && *base) {
+ packed_dir = find_containing_dir(packed_dir, base, 0);
+ loose_dir = find_containing_dir(loose_dir, base, 0);
+ }
+
+ if (packed_dir && loose_dir) {
+ sort_ref_dir(packed_dir);
+ sort_ref_dir(loose_dir);
+ retval = do_for_each_ref_in_dirs(
+ packed_dir, loose_dir,
+ base, fn, trim, flags, cb_data);
+ } else if (packed_dir) {
+ sort_ref_dir(packed_dir);
+ retval = do_for_each_ref_in_dir(
+ packed_dir, 0,
+ base, fn, trim, flags, cb_data);
+ } else if (loose_dir) {
+ sort_ref_dir(loose_dir);
+ retval = do_for_each_ref_in_dir(
+ loose_dir, 0,
+ base, fn, trim, flags, cb_data);
}
-end_each:
- current_ref = NULL;
return retval;
}
-
static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
unsigned char sha1[20];
@@ -710,7 +1315,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
return 0;
}
- if (resolve_ref("HEAD", sha1, 1, &flag))
+ if (!read_ref_full("HEAD", sha1, 1, &flag))
return fn("HEAD", sha1, flag, cb_data);
return 0;
@@ -728,12 +1333,12 @@ int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
int for_each_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(NULL, "refs/", fn, 0, 0, cb_data);
+ return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
}
int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(submodule, "refs/", fn, 0, 0, cb_data);
+ return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
}
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
@@ -782,6 +1387,31 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
}
+int head_ref_namespaced(each_ref_fn fn, void *cb_data)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int ret = 0;
+ unsigned char sha1[20];
+ int flag;
+
+ strbuf_addf(&buf, "%sHEAD", get_git_namespace());
+ if (!read_ref_full(buf.buf, sha1, 1, &flag))
+ ret = fn(buf.buf, sha1, flag, cb_data);
+ strbuf_release(&buf);
+
+ return ret;
+}
+
+int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+ strbuf_addf(&buf, "%srefs/", get_git_namespace());
+ ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
+ strbuf_release(&buf);
+ return ret;
+}
+
int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
const char *prefix, void *cb_data)
{
@@ -819,88 +1449,10 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
int for_each_rawref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(NULL, "refs/", fn, 0,
+ return do_for_each_ref(NULL, "", fn, 0,
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
-/*
- * Make sure "ref" is something reasonable to have under ".git/refs/";
- * We do not like it if:
- *
- * - any path component of it begins with ".", or
- * - it has double dots "..", or
- * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
- * - it ends with a "/".
- * - it ends with ".lock"
- * - it contains a "\" (backslash)
- */
-
-static inline int bad_ref_char(int ch)
-{
- if (((unsigned) ch) <= ' ' || ch == 0x7f ||
- ch == '~' || ch == '^' || ch == ':' || ch == '\\')
- return 1;
- /* 2.13 Pattern Matching Notation */
- if (ch == '?' || ch == '[') /* Unsupported */
- return 1;
- if (ch == '*') /* Supported at the end */
- return 2;
- return 0;
-}
-
-int check_ref_format(const char *ref)
-{
- int ch, level, bad_type, last;
- int ret = CHECK_REF_FORMAT_OK;
- const char *cp = ref;
-
- level = 0;
- while (1) {
- while ((ch = *cp++) == '/')
- ; /* tolerate duplicated slashes */
- if (!ch)
- /* should not end with slashes */
- return CHECK_REF_FORMAT_ERROR;
-
- /* we are at the beginning of the path component */
- if (ch == '.')
- return CHECK_REF_FORMAT_ERROR;
- bad_type = bad_ref_char(ch);
- if (bad_type) {
- if (bad_type == 2 && (!*cp || *cp == '/') &&
- ret == CHECK_REF_FORMAT_OK)
- ret = CHECK_REF_FORMAT_WILDCARD;
- else
- return CHECK_REF_FORMAT_ERROR;
- }
-
- last = ch;
- /* scan the rest of the path component */
- while ((ch = *cp++) != 0) {
- bad_type = bad_ref_char(ch);
- if (bad_type)
- return CHECK_REF_FORMAT_ERROR;
- if (ch == '/')
- break;
- if (last == '.' && ch == '.')
- return CHECK_REF_FORMAT_ERROR;
- if (last == '@' && ch == '{')
- return CHECK_REF_FORMAT_ERROR;
- last = ch;
- }
- level++;
- if (!ch) {
- if (ref <= cp - 2 && cp[-2] == '.')
- return CHECK_REF_FORMAT_ERROR;
- if (level < 2)
- return CHECK_REF_FORMAT_ONELEVEL;
- if (has_extension(ref, ".lock"))
- return CHECK_REF_FORMAT_ERROR;
- return ret;
- }
- }
-}
-
const char *prettify_refname(const char *name)
{
return name + (
@@ -920,13 +1472,6 @@ const char *ref_rev_parse_rules[] = {
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;
@@ -944,7 +1489,7 @@ int refname_match(const char *abbrev_name, const char *full_name, const char **r
static struct ref_lock *verify_lock(struct ref_lock *lock,
const unsigned char *old_sha1, int mustexist)
{
- if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+ if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
error("Can't verify ref %s", lock->ref_name);
unlock_ref(lock);
return NULL;
@@ -977,33 +1522,100 @@ static int remove_empty_directories(const char *file)
return result;
}
-static int is_refname_available(const char *ref, const char *oldref,
- struct ref_list *list, int quiet)
-{
- int namlen = strlen(ref); /* e.g. 'foo/bar' */
- while (list) {
- /* list->name could be 'foo' or 'foo/bar/baz' */
- if (!oldref || strcmp(oldref, list->name)) {
- int len = strlen(list->name);
- int cmplen = (namlen < len) ? namlen : len;
- const char *lead = (namlen < len) ? list->name : ref;
- if (!strncmp(ref, list->name, cmplen) &&
- lead[cmplen] == '/') {
- if (!quiet)
- error("'%s' exists; cannot create '%s'",
- list->name, ref);
- return 0;
- }
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is a magic short-hand form
+ * to name a branch.
+ */
+static char *substitute_branch_name(const char **string, int *len)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int ret = interpret_branch_name(*string, &buf);
+
+ if (ret == *len) {
+ size_t size;
+ *string = strbuf_detach(&buf, &size);
+ *len = size;
+ return (char *)*string;
+ }
+
+ return NULL;
+}
+
+int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
+{
+ char *last_branch = substitute_branch_name(&str, &len);
+ const char **p, *r;
+ int refs_found = 0;
+
+ *ref = NULL;
+ for (p = ref_rev_parse_rules; *p; p++) {
+ char fullref[PATH_MAX];
+ unsigned char sha1_from_ref[20];
+ unsigned char *this_result;
+ int flag;
+
+ this_result = refs_found ? sha1_from_ref : sha1;
+ mksnpath(fullref, sizeof(fullref), *p, len, str);
+ r = resolve_ref_unsafe(fullref, this_result, 1, &flag);
+ if (r) {
+ if (!refs_found++)
+ *ref = xstrdup(r);
+ if (!warn_ambiguous_refs)
+ break;
+ } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) {
+ warning("ignoring dangling symref %s.", fullref);
+ } else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {
+ warning("ignoring broken ref %s.", fullref);
}
- list = list->next;
}
- return 1;
+ free(last_branch);
+ return refs_found;
}
-static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
+int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
+{
+ char *last_branch = substitute_branch_name(&str, &len);
+ const char **p;
+ int logs_found = 0;
+
+ *log = NULL;
+ for (p = ref_rev_parse_rules; *p; p++) {
+ struct stat st;
+ unsigned char hash[20];
+ char path[PATH_MAX];
+ const char *ref, *it;
+
+ mksnpath(path, sizeof(path), *p, len, str);
+ ref = resolve_ref_unsafe(path, hash, 1, NULL);
+ if (!ref)
+ continue;
+ if (!stat(git_path("logs/%s", path), &st) &&
+ S_ISREG(st.st_mode))
+ it = path;
+ else if (strcmp(ref, path) &&
+ !stat(git_path("logs/%s", ref), &st) &&
+ S_ISREG(st.st_mode))
+ it = ref;
+ else
+ continue;
+ if (!logs_found++) {
+ *log = xstrdup(it);
+ hashcpy(sha1, hash);
+ }
+ if (!warn_ambiguous_refs)
+ break;
+ }
+ free(last_branch);
+ return logs_found;
+}
+
+static struct ref_lock *lock_ref_sha1_basic(const char *refname,
+ const unsigned char *old_sha1,
+ int flags, int *type_p)
{
char *ref_file;
- const char *orig_ref = ref;
+ const char *orig_refname = refname;
struct ref_lock *lock;
int last_errno = 0;
int type, lflags;
@@ -1013,27 +1625,27 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
- ref = resolve_ref(ref, lock->old_sha1, mustexist, &type);
- if (!ref && errno == EISDIR) {
+ refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type);
+ if (!refname && errno == EISDIR) {
/* we are trying to lock foo but we used to
* have foo/bar which now does not exist;
* it is normal for the empty directory 'foo'
* to remain.
*/
- ref_file = git_path("%s", orig_ref);
+ ref_file = git_path("%s", orig_refname);
if (remove_empty_directories(ref_file)) {
last_errno = errno;
- error("there are still refs under '%s'", orig_ref);
+ error("there are still refs under '%s'", orig_refname);
goto error_return;
}
- ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, &type);
+ refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type);
}
if (type_p)
*type_p = type;
- if (!ref) {
+ if (!refname) {
last_errno = errno;
error("unable to resolve reference %s: %s",
- orig_ref, strerror(errno));
+ orig_refname, strerror(errno));
goto error_return;
}
missing = is_null_sha1(lock->old_sha1);
@@ -1043,7 +1655,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
* name is a proper prefix of our refname.
*/
if (missing &&
- !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) {
+ !is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) {
last_errno = ENOTDIR;
goto error_return;
}
@@ -1052,12 +1664,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
lflags = LOCK_DIE_ON_ERROR;
if (flags & REF_NODEREF) {
- ref = orig_ref;
+ refname = orig_refname;
lflags |= LOCK_NODEREF;
}
- lock->ref_name = xstrdup(ref);
- lock->orig_ref_name = xstrdup(orig_ref);
- ref_file = git_path("%s", ref);
+ lock->ref_name = xstrdup(refname);
+ lock->orig_ref_name = xstrdup(orig_refname);
+ ref_file = git_path("%s", refname);
if (missing)
lock->force_write = 1;
if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
@@ -1078,62 +1690,61 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
return NULL;
}
-struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
+struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
{
char refpath[PATH_MAX];
- if (check_ref_format(ref))
+ if (check_refname_format(refname, 0))
return NULL;
- strcpy(refpath, mkpath("refs/%s", ref));
+ strcpy(refpath, mkpath("refs/%s", refname));
return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
}
-struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags)
+struct ref_lock *lock_any_ref_for_update(const char *refname,
+ const unsigned char *old_sha1, int flags)
{
- switch (check_ref_format(ref)) {
- default:
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
return NULL;
- case 0:
- case CHECK_REF_FORMAT_ONELEVEL:
- return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
- }
+ return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
+}
+
+struct repack_without_ref_sb {
+ const char *refname;
+ int fd;
+};
+
+static int repack_without_ref_fn(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct repack_without_ref_sb *data = cb_data;
+ char line[PATH_MAX + 100];
+ int len;
+
+ if (!strcmp(data->refname, refname))
+ return 0;
+ len = snprintf(line, sizeof(line), "%s %s\n",
+ sha1_to_hex(sha1), refname);
+ /* this should not happen but just being defensive */
+ if (len > sizeof(line))
+ die("too long a refname '%s'", refname);
+ write_or_die(data->fd, line, len);
+ return 0;
}
static struct lock_file packlock;
static int repack_without_ref(const char *refname)
{
- struct ref_list *list, *packed_ref_list;
- int fd;
- int found = 0;
-
- packed_ref_list = get_packed_refs(NULL);
- for (list = packed_ref_list; list; list = list->next) {
- if (!strcmp(refname, list->name)) {
- found = 1;
- break;
- }
- }
- if (!found)
+ struct repack_without_ref_sb data;
+ struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
+ if (find_ref(packed, refname) == NULL)
return 0;
- fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
- if (fd < 0) {
+ data.refname = refname;
+ data.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
+ if (data.fd < 0) {
unable_to_lock_error(git_path("packed-refs"), errno);
return error("cannot delete '%s' from packed refs", refname);
}
-
- for (list = packed_ref_list; list; list = list->next) {
- char line[PATH_MAX + 100];
- int len;
-
- if (!strcmp(refname, list->name))
- continue;
- len = snprintf(line, sizeof(line), "%s %s\n",
- sha1_to_hex(list->sha1), list->name);
- /* this should not happen but just being defensive */
- if (len > sizeof(line))
- die("too long a refname '%s'", list->name);
- write_or_die(fd, line, len);
- }
+ do_for_each_ref_in_dir(packed, 0, "", repack_without_ref_fn, 0, 0, &data);
return commit_lock_file(&packlock);
}
@@ -1170,7 +1781,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
ret |= repack_without_ref(refname);
unlink_or_warn(git_path("logs/%s", lock->ref_name));
- invalidate_cached_refs();
+ invalidate_ref_cache(NULL);
unlock_ref(lock);
return ret;
}
@@ -1184,104 +1795,98 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
*/
#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log"
-int rename_ref(const char *oldref, const char *newref, const char *logmsg)
+int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{
- static const char renamed_ref[] = "RENAMED-REF";
unsigned char sha1[20], orig_sha1[20];
int flag = 0, logmoved = 0;
struct ref_lock *lock;
struct stat loginfo;
- int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+ int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
const char *symref = NULL;
+ struct ref_cache *refs = get_ref_cache(NULL);
if (log && S_ISLNK(loginfo.st_mode))
- return error("reflog for %s is a symlink", oldref);
+ return error("reflog for %s is a symlink", oldrefname);
- symref = resolve_ref(oldref, orig_sha1, 1, &flag);
+ symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);
if (flag & REF_ISSYMREF)
return error("refname %s is a symbolic ref, renaming it is not supported",
- oldref);
+ oldrefname);
if (!symref)
- return error("refname %s not found", oldref);
+ return error("refname %s not found", oldrefname);
- if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0))
+ if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs)))
return 1;
- if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))
+ if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs)))
return 1;
- lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
- if (!lock)
- return error("unable to lock %s", renamed_ref);
- lock->force_write = 1;
- if (write_ref_sha1(lock, orig_sha1, logmsg))
- return error("unable to save current sha1 in %s", renamed_ref);
-
- if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG)))
+ if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
- oldref, strerror(errno));
+ oldrefname, strerror(errno));
- if (delete_ref(oldref, orig_sha1, REF_NODEREF)) {
- error("unable to delete old %s", oldref);
+ if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) {
+ error("unable to delete old %s", oldrefname);
goto rollback;
}
- if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1, REF_NODEREF)) {
+ if (!read_ref_full(newrefname, sha1, 1, &flag) &&
+ delete_ref(newrefname, sha1, REF_NODEREF)) {
if (errno==EISDIR) {
- if (remove_empty_directories(git_path("%s", newref))) {
- error("Directory not empty: %s", newref);
+ if (remove_empty_directories(git_path("%s", newrefname))) {
+ error("Directory not empty: %s", newrefname);
goto rollback;
}
} else {
- error("unable to delete existing %s", newref);
+ error("unable to delete existing %s", newrefname);
goto rollback;
}
}
- if (log && safe_create_leading_directories(git_path("logs/%s", newref))) {
- error("unable to create directory for %s", newref);
+ if (log && safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+ error("unable to create directory for %s", newrefname);
goto rollback;
}
retry:
- if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newref))) {
+ if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
if (errno==EISDIR || errno==ENOTDIR) {
/*
* rename(a, b) when b is an existing
* directory ought to result in ISDIR, but
* Solaris 5.8 gives ENOTDIR. Sheesh.
*/
- if (remove_empty_directories(git_path("logs/%s", newref))) {
- error("Directory not empty: logs/%s", newref);
+ if (remove_empty_directories(git_path("logs/%s", newrefname))) {
+ error("Directory not empty: logs/%s", newrefname);
goto rollback;
}
goto retry;
} else {
error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
- newref, strerror(errno));
+ newrefname, strerror(errno));
goto rollback;
}
}
logmoved = log;
- lock = lock_ref_sha1_basic(newref, NULL, 0, NULL);
+ lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);
if (!lock) {
- error("unable to lock %s for update", newref);
+ error("unable to lock %s for update", newrefname);
goto rollback;
}
lock->force_write = 1;
hashcpy(lock->old_sha1, orig_sha1);
if (write_ref_sha1(lock, orig_sha1, logmsg)) {
- error("unable to write current sha1 into %s", newref);
+ error("unable to write current sha1 into %s", newrefname);
goto rollback;
}
return 0;
rollback:
- lock = lock_ref_sha1_basic(oldref, NULL, 0, NULL);
+ lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);
if (!lock) {
- error("unable to lock %s for rollback", oldref);
+ error("unable to lock %s for rollback", oldrefname);
goto rollbacklog;
}
@@ -1289,17 +1894,17 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
flag = log_all_ref_updates;
log_all_ref_updates = 0;
if (write_ref_sha1(lock, orig_sha1, NULL))
- error("unable to write current sha1 into %s", oldref);
+ error("unable to write current sha1 into %s", oldrefname);
log_all_ref_updates = flag;
rollbacklog:
- if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref)))
+ if (logmoved && rename(git_path("logs/%s", newrefname), git_path("logs/%s", oldrefname)))
error("unable to restore logfile %s from %s: %s",
- oldref, newref, strerror(errno));
+ oldrefname, newrefname, strerror(errno));
if (!logmoved && log &&
- rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref)))
+ rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldrefname)))
error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s",
- oldref, strerror(errno));
+ oldrefname, strerror(errno));
return 1;
}
@@ -1356,16 +1961,16 @@ static int copy_msg(char *buf, const char *msg)
return cp - buf;
}
-int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
+int log_ref_setup(const char *refname, char *logfile, int bufsize)
{
int logfd, oflags = O_APPEND | O_WRONLY;
- git_snpath(logfile, bufsize, "logs/%s", ref_name);
+ git_snpath(logfile, bufsize, "logs/%s", refname);
if (log_all_ref_updates &&
- (!prefixcmp(ref_name, "refs/heads/") ||
- !prefixcmp(ref_name, "refs/remotes/") ||
- !prefixcmp(ref_name, "refs/notes/") ||
- !strcmp(ref_name, "HEAD"))) {
+ (!prefixcmp(refname, "refs/heads/") ||
+ !prefixcmp(refname, "refs/remotes/") ||
+ !prefixcmp(refname, "refs/notes/") ||
+ !strcmp(refname, "HEAD"))) {
if (safe_create_leading_directories(logfile) < 0)
return error("unable to create directory for %s",
logfile);
@@ -1395,7 +2000,7 @@ int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
return 0;
}
-static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
+static int log_ref_write(const char *refname, const unsigned char *old_sha1,
const unsigned char *new_sha1, const char *msg)
{
int logfd, result, written, oflags = O_APPEND | O_WRONLY;
@@ -1408,7 +2013,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
if (log_all_ref_updates < 0)
log_all_ref_updates = !is_bare_repository();
- result = log_ref_setup(ref_name, log_file, sizeof(log_file));
+ result = log_ref_setup(refname, log_file, sizeof(log_file));
if (result)
return result;
@@ -1469,7 +2074,7 @@ int write_ref_sha1(struct ref_lock *lock,
unlock_ref(lock);
return -1;
}
- invalidate_cached_refs();
+ clear_loose_ref_cache(get_ref_cache(NULL));
if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
(strcmp(lock->ref_name, lock->orig_ref_name) &&
log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
@@ -1492,7 +2097,7 @@ int write_ref_sha1(struct ref_lock *lock,
unsigned char head_sha1[20];
int head_flag;
const char *head_ref;
- head_ref = resolve_ref("HEAD", head_sha1, 1, &head_flag);
+ head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name))
log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
@@ -1579,7 +2184,9 @@ static char *ref_msg(const char *line, const char *endp)
return xmemdupz(line, ep - line);
}
-int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
+int read_ref_at(const char *refname, unsigned long at_time, int cnt,
+ unsigned char *sha1, char **msg,
+ unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
{
const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
char *tz_c;
@@ -1590,7 +2197,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
void *log_mapped;
size_t mapsz;
- logfile = git_path("logs/%s", ref);
+ logfile = git_path("logs/%s", refname);
logfd = open(logfile, O_RDONLY, 0);
if (logfd < 0)
die_errno("Unable to read log '%s'", logfile);
@@ -1683,14 +2290,14 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
return 1;
}
-int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
+int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
{
const char *logfile;
FILE *logfp;
struct strbuf sb = STRBUF_INIT;
int ret = 0;
- logfile = git_path("logs/%s", ref);
+ logfile = git_path("logs/%s", refname);
logfp = fopen(logfile, "r");
if (!logfp)
return -1;
@@ -1741,62 +2348,64 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs,
return ret;
}
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
{
- return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+ return for_each_recent_reflog_ent(refname, fn, 0, cb_data);
}
-static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
+/*
+ * Call fn for each reflog in the namespace indicated by name. name
+ * must be empty or end with '/'. Name will be used as a scratch
+ * space, but its contents will be restored before return.
+ */
+static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data)
{
- DIR *dir = opendir(git_path("logs/%s", base));
+ DIR *d = opendir(git_path("logs/%s", name->buf));
int retval = 0;
+ struct dirent *de;
+ int oldlen = name->len;
- if (dir) {
- struct dirent *de;
- int baselen = strlen(base);
- char *log = xmalloc(baselen + 257);
-
- memcpy(log, base, baselen);
- if (baselen && base[baselen-1] != '/')
- log[baselen++] = '/';
+ if (!d)
+ return name->len ? errno : 0;
- while ((de = readdir(dir)) != NULL) {
- struct stat st;
- int namelen;
+ while ((de = readdir(d)) != NULL) {
+ struct stat st;
- if (de->d_name[0] == '.')
- continue;
- namelen = strlen(de->d_name);
- if (namelen > 255)
- continue;
- if (has_extension(de->d_name, ".lock"))
- continue;
- memcpy(log + baselen, de->d_name, namelen+1);
- if (stat(git_path("logs/%s", log), &st) < 0)
- continue;
+ if (de->d_name[0] == '.')
+ continue;
+ if (has_extension(de->d_name, ".lock"))
+ continue;
+ strbuf_addstr(name, de->d_name);
+ if (stat(git_path("logs/%s", name->buf), &st) < 0) {
+ ; /* silently ignore */
+ } else {
if (S_ISDIR(st.st_mode)) {
- retval = do_for_each_reflog(log, fn, cb_data);
+ strbuf_addch(name, '/');
+ retval = do_for_each_reflog(name, fn, cb_data);
} else {
unsigned char sha1[20];
- if (!resolve_ref(log, sha1, 0, NULL))
- retval = error("bad ref for %s", log);
+ if (read_ref_full(name->buf, sha1, 0, NULL))
+ retval = error("bad ref for %s", name->buf);
else
- retval = fn(log, sha1, 0, cb_data);
+ retval = fn(name->buf, sha1, 0, cb_data);
}
if (retval)
break;
}
- free(log);
- closedir(dir);
+ strbuf_setlen(name, oldlen);
}
- else if (*base)
- return errno;
+ closedir(d);
return retval;
}
int for_each_reflog(each_ref_fn fn, void *cb_data)
{
- return do_for_each_reflog("", fn, cb_data);
+ int retval;
+ struct strbuf name;
+ strbuf_init(&name, PATH_MAX);
+ retval = do_for_each_reflog(&name, fn, cb_data);
+ strbuf_release(&name);
+ return retval;
}
int update_ref(const char *action, const char *refname,
@@ -1826,12 +2435,6 @@ int update_ref(const char *action, const char *refname,
return 0;
}
-int ref_exists(char *refname)
-{
- unsigned char sha1[20];
- return !!resolve_ref(refname, sha1, 1, NULL);
-}
-
struct ref *find_ref_by_name(const struct ref *list, const char *name)
{
for ( ; list; list = list->next)
@@ -1863,7 +2466,7 @@ static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
return;
}
-char *shorten_unambiguous_ref(const char *ref, int strict)
+char *shorten_unambiguous_ref(const char *refname, int strict)
{
int i;
static char **scanf_fmts;
@@ -1892,10 +2495,10 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
/* bail out if there are no rules */
if (!nr_rules)
- return xstrdup(ref);
+ return xstrdup(refname);
- /* buffer for scanf result, at most ref must fit */
- short_name = xstrdup(ref);
+ /* buffer for scanf result, at most refname must fit */
+ short_name = xstrdup(refname);
/* skip first rule, it will always match */
for (i = nr_rules - 1; i > 0 ; --i) {
@@ -1903,7 +2506,7 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
int rules_to_fail = i;
int short_name_len;
- if (1 != sscanf(ref, scanf_fmts[i], short_name))
+ if (1 != sscanf(refname, scanf_fmts[i], short_name))
continue;
short_name_len = strlen(short_name);
@@ -1921,7 +2524,6 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
*/
for (j = 0; j < rules_to_fail; j++) {
const char *rule = ref_rev_parse_rules[j];
- unsigned char short_objectname[20];
char refname[PATH_MAX];
/* skip matched rule */
@@ -1935,7 +2537,7 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
*/
mksnpath(refname, sizeof(refname),
rule, short_name_len, short_name);
- if (!read_ref(refname, short_objectname))
+ if (ref_exists(refname))
break;
}
@@ -1948,5 +2550,5 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
}
free(short_name);
- return xstrdup(ref);
+ return xstrdup(refname);
}
diff --git a/refs.h b/refs.h
index 5de06e5..d6c2fe2 100644
--- a/refs.h
+++ b/refs.h
@@ -10,12 +10,16 @@ struct ref_lock {
int force_write;
};
-#define REF_ISSYMREF 01
-#define REF_ISPACKED 02
+#define REF_ISSYMREF 0x01
+#define REF_ISPACKED 0x02
+#define REF_ISBROKEN 0x04
/*
- * Calls the specified function for each ref file until it returns nonzero,
- * and returns the value
+ * Calls the specified function for each ref file until it returns
+ * nonzero, and returns the value. Please note that it is not safe to
+ * modify references while an iteration is in progress, unless the
+ * same callback function invocation that modifies the reference also
+ * returns a nonzero value to immediately stop the iteration.
*/
typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
extern int head_ref(each_ref_fn, void *);
@@ -36,6 +40,9 @@ extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, voi
extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int head_ref_namespaced(each_ref_fn fn, void *cb_data);
+extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
+
static inline const char *has_glob_specials(const char *pattern)
{
return strpbrk(pattern, "?*[");
@@ -47,23 +54,23 @@ extern int for_each_rawref(each_ref_fn, void *);
extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
/*
- * Extra refs will be listed by for_each_ref() before any actual refs
- * for the duration of this process or until clear_extra_refs() is
- * called. Only extra refs added before for_each_ref() is called will
- * be listed on a given call of for_each_ref().
+ * Add a reference to the in-memory packed reference cache. To actually
+ * write the reference to the packed-refs file, call pack_refs().
*/
-extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
-extern void clear_extra_refs(void);
-extern int ref_exists(char *);
+extern void add_packed_ref(const char *refname, const unsigned char *sha1);
+
+extern int ref_exists(const char *);
-extern int peel_ref(const char *, unsigned char *);
+extern int peel_ref(const char *refname, 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);
+extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);
/** Locks any ref (for 'HEAD' type refs). */
#define REF_NODEREF 0x01
-extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags);
+extern struct ref_lock *lock_any_ref_for_update(const char *refname,
+ const unsigned char *old_sha1,
+ int flags);
/** Close the file descriptor owned by a lock and return the status */
extern int close_ref(struct ref_lock *lock);
@@ -77,16 +84,26 @@ extern void unlock_ref(struct ref_lock *lock);
/** Writes sha1 into the ref specified by the lock. **/
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
+/*
+ * Invalidate the reference cache for the specified submodule. Use
+ * submodule=NULL to invalidate the cache for the main module. This
+ * function must be called if references are changed via a mechanism
+ * other than the refs API.
+ */
+extern void invalidate_ref_cache(const char *submodule);
+
/** Setup reflog before using. **/
int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
/** Reads log for the value of ref during at_time. **/
-extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
+extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
+ unsigned char *sha1, char **msg,
+ unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
/* iterate over reflog entries */
typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
-int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
+int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
+int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data);
/*
* Calls the specified function for each reflog file until it returns nonzero,
@@ -94,20 +111,35 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, voi
*/
extern int for_each_reflog(each_ref_fn, void *);
-#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);
+#define REFNAME_ALLOW_ONELEVEL 1
+#define REFNAME_REFSPEC_PATTERN 2
+#define REFNAME_DOT_COMPONENT 4
+
+/*
+ * Return 0 iff refname has the correct format for a refname according
+ * to the rules described in Documentation/git-check-ref-format.txt.
+ * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level
+ * reference names. If REFNAME_REFSPEC_PATTERN is set in flags, then
+ * allow a "*" wildcard character in place of one of the name
+ * components. No leading or repeated slashes are accepted. If
+ * REFNAME_DOT_COMPONENT is set in flags, then allow refname
+ * components to start with "." (but not a whole component equal to
+ * "." or "..").
+ */
+extern int check_refname_format(const char *refname, int flags);
extern const char *prettify_refname(const char *refname);
-extern char *shorten_unambiguous_ref(const char *ref, int strict);
+extern char *shorten_unambiguous_ref(const char *refname, int strict);
/** rename ref, return 0 on success **/
extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
-/** resolve ref in nested "gitlink" repository */
-extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result);
+/**
+ * Resolve refname in the nested "gitlink" repository that is located
+ * at path. If the resolution is successful, return 0 and set sha1 to
+ * the name of the object; otherwise, return a non-zero value.
+ */
+extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
/** lock a ref and then write its file */
enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR };
diff --git a/remote-curl.c b/remote-curl.c
index 69831e9..04a9d62 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -115,7 +115,7 @@ static struct discovery* discover_refs(const char *service)
http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
/* try again with "plain" url (no ? or & appended) */
- if (http_ret != HTTP_OK) {
+ if (http_ret != HTTP_OK && http_ret != HTTP_NOAUTH) {
free(refs_url);
strbuf_reset(&buffer);
@@ -188,7 +188,7 @@ static int write_discovery(int in, int out, void *data)
return err;
}
-static struct ref *parse_git_refs(struct discovery *heads)
+static struct ref *parse_git_refs(struct discovery *heads, int for_push)
{
struct ref *list = NULL;
struct async async;
@@ -200,7 +200,8 @@ static struct ref *parse_git_refs(struct discovery *heads)
if (start_async(&async))
die("cannot start thread to parse advertised refs");
- get_remote_heads(async.out, &list, 0, NULL, 0, NULL);
+ get_remote_heads(async.out, &list,
+ for_push ? REF_NORMAL : 0, NULL);
close(async.out);
if (finish_async(&async))
die("ref parsing thread failed");
@@ -268,7 +269,7 @@ static struct ref *get_refs(int for_push)
heads = discover_refs("git-upload-pack");
if (heads->proto_git)
- return parse_git_refs(heads);
+ return parse_git_refs(heads, for_push);
return parse_info_refs(heads);
}
@@ -289,6 +290,7 @@ static void output_refs(struct ref *refs)
struct rpc_state {
const char *service_name;
const char **argv;
+ struct strbuf *stdin_preamble;
char *service_url;
char *hdr_content_type;
char *hdr_accept;
@@ -534,6 +536,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
{
const char *svc = rpc->service_name;
struct strbuf buf = STRBUF_INIT;
+ struct strbuf *preamble = rpc->stdin_preamble;
struct child_process client;
int err = 0;
@@ -544,6 +547,8 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
client.argv = rpc->argv;
if (start_command(&client))
exit(1);
+ if (preamble)
+ write_or_die(client.in, preamble->buf, preamble->len);
if (heads)
write_or_die(client.in, heads->buf, heads->len);
@@ -573,7 +578,14 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
close(client.in);
client.in = -1;
- strbuf_read(&rpc->result, client.out, 0);
+ if (!err) {
+ strbuf_read(&rpc->result, client.out, 0);
+ } else {
+ char buf[4096];
+ for (;;)
+ if (xread(client.out, buf, sizeof(buf)) <= 0)
+ break;
+ }
close(client.out);
client.out = -1;
@@ -618,13 +630,14 @@ static int fetch_git(struct discovery *heads,
int nr_heads, struct ref **to_fetch)
{
struct rpc_state rpc;
+ struct strbuf preamble = STRBUF_INIT;
char *depth_arg = NULL;
- const char **argv;
int argc = 0, i, err;
+ const char *argv[15];
- argv = xmalloc((15 + nr_heads) * sizeof(char*));
argv[argc++] = "fetch-pack";
argv[argc++] = "--stateless-rpc";
+ argv[argc++] = "--stdin";
argv[argc++] = "--lock-pack";
if (options.followtags)
argv[argc++] = "--include-tag";
@@ -643,24 +656,27 @@ static int fetch_git(struct discovery *heads,
argv[argc++] = depth_arg;
}
argv[argc++] = url;
+ argv[argc++] = NULL;
+
for (i = 0; i < nr_heads; i++) {
struct ref *ref = to_fetch[i];
if (!ref->name || !*ref->name)
die("cannot fetch by sha1 over smart http");
- argv[argc++] = ref->name;
+ packet_buf_write(&preamble, "%s\n", ref->name);
}
- argv[argc++] = NULL;
+ packet_buf_flush(&preamble);
memset(&rpc, 0, sizeof(rpc));
rpc.service_name = "git-upload-pack",
rpc.argv = argv;
+ rpc.stdin_preamble = &preamble;
rpc.gzip_request = 1;
err = rpc_service(&rpc, heads);
if (rpc.result.len)
safe_write(1, rpc.result.buf, rpc.result.len);
strbuf_release(&rpc.result);
- free(argv);
+ strbuf_release(&preamble);
free(depth_arg);
return err;
}
@@ -762,8 +778,11 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
argv[argc++] = "--thin";
if (options.dry_run)
argv[argc++] = "--dry-run";
- if (options.verbosity > 1)
+ if (options.verbosity == 0)
+ argv[argc++] = "--quiet";
+ else if (options.verbosity > 1)
argv[argc++] = "--verbose";
+ argv[argc++] = options.progress ? "--progress" : "--no-progress";
argv[argc++] = url;
for (i = 0; i < nr_spec; i++)
argv[argc++] = specs[i];
@@ -797,7 +816,7 @@ static int push(int nr_spec, char **specs)
static void parse_push(struct strbuf *buf)
{
char **specs = NULL;
- int alloc_spec = 0, nr_spec = 0, i;
+ int alloc_spec = 0, nr_spec = 0, i, ret;
do {
if (!prefixcmp(buf->buf, "push ")) {
@@ -814,12 +833,13 @@ static void parse_push(struct strbuf *buf)
break;
} while (1);
- if (push(nr_spec, specs))
- exit(128); /* error already reported */
-
+ ret = push(nr_spec, specs);
printf("\n");
fflush(stdout);
+ if (ret)
+ exit(128); /* error already reported */
+
free_specs:
for (i = 0; i < nr_spec; i++)
free(specs[i]);
@@ -852,10 +872,17 @@ int main(int argc, const char **argv)
url = strbuf_detach(&buf, NULL);
- http_init(remote);
+ http_init(remote, url, 0);
do {
- if (strbuf_getline(&buf, stdin, '\n') == EOF)
+ if (strbuf_getline(&buf, stdin, '\n') == EOF) {
+ if (ferror(stdin))
+ fprintf(stderr, "Error reading command stream\n");
+ else
+ fprintf(stderr, "Unexpected end of command stream\n");
+ return 1;
+ }
+ if (buf.len == 0)
break;
if (!prefixcmp(buf.buf, "fetch ")) {
if (nongit)
@@ -895,6 +922,7 @@ int main(int argc, const char **argv)
printf("\n");
fflush(stdout);
} else {
+ fprintf(stderr, "Unknown command '%s'\n", buf.buf);
return 1;
}
strbuf_reset(&buf);
diff --git a/remote.c b/remote.c
index ca42a12..6833538 100644
--- a/remote.c
+++ b/remote.c
@@ -7,6 +7,9 @@
#include "dir.h"
#include "tag.h"
#include "string-list.h"
+#include "mergesort.h"
+
+enum map_direction { FROM_SRC, FROM_DST };
static struct refspec s_tag_refspec = {
0,
@@ -482,7 +485,7 @@ static void read_config(void)
return;
default_remote_name = xstrdup("origin");
current_branch = NULL;
- head_ref = resolve_ref("HEAD", sha1, 0, &flag);
+ head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
!prefixcmp(head_ref, "refs/heads/")) {
current_branch =
@@ -493,23 +496,6 @@ static void read_config(void)
}
/*
- * We need to make sure the remote-tracking branches are well formed, but a
- * wildcard refspec in "struct refspec" must have a trailing slash. We
- * temporarily drop the trailing '/' while calling check_ref_format(),
- * and put it back. The caller knows that a CHECK_REF_FORMAT_ONELEVEL
- * error return is Ok for a wildcard refspec.
- */
-static int verify_refname(char *name, int is_glob)
-{
- int result;
-
- result = check_ref_format(name);
- if (is_glob && result == CHECK_REF_FORMAT_WILDCARD)
- result = CHECK_REF_FORMAT_OK;
- return result;
-}
-
-/*
* This function frees a refspec array.
* Warning: code paths should be checked to ensure that the src
* and dst pointers are always freeable pointers as well
@@ -532,13 +518,13 @@ static void free_refspecs(struct refspec *refspec, int nr_refspec)
static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify)
{
int i;
- int st;
struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
for (i = 0; i < nr_refspec; i++) {
size_t llen;
int is_glob;
const char *lhs, *rhs;
+ int flags;
is_glob = 0;
@@ -576,6 +562,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
rs[i].pattern = is_glob;
rs[i].src = xstrndup(lhs, llen);
+ flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
if (fetch) {
/*
@@ -585,26 +572,20 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
*/
if (!*rs[i].src)
; /* empty is ok */
- else {
- st = verify_refname(rs[i].src, is_glob);
- if (st && st != CHECK_REF_FORMAT_ONELEVEL)
- goto invalid;
- }
+ else if (check_refname_format(rs[i].src, flags))
+ goto invalid;
/*
* RHS
* - missing is ok, and is same as empty.
* - empty is ok; it means not to store.
* - otherwise it must be a valid looking ref.
*/
- if (!rs[i].dst) {
+ if (!rs[i].dst)
; /* ok */
- } else if (!*rs[i].dst) {
+ else if (!*rs[i].dst)
; /* ok */
- } else {
- st = verify_refname(rs[i].dst, is_glob);
- if (st && st != CHECK_REF_FORMAT_ONELEVEL)
- goto invalid;
- }
+ else if (check_refname_format(rs[i].dst, flags))
+ goto invalid;
} else {
/*
* LHS
@@ -616,8 +597,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
if (!*rs[i].src)
; /* empty is ok */
else if (is_glob) {
- st = verify_refname(rs[i].src, is_glob);
- if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+ if (check_refname_format(rs[i].src, flags))
goto invalid;
}
else
@@ -630,14 +610,12 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
* - otherwise it must be a valid looking ref.
*/
if (!rs[i].dst) {
- st = verify_refname(rs[i].src, is_glob);
- if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+ if (check_refname_format(rs[i].src, flags))
goto invalid;
} else if (!*rs[i].dst) {
goto invalid;
} else {
- st = verify_refname(rs[i].dst, is_glob);
- if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+ if (check_refname_format(rs[i].dst, flags))
goto invalid;
}
}
@@ -828,59 +806,56 @@ static int match_name_with_pattern(const char *key, const char *name,
return ret;
}
-char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
- const char *name)
+static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
{
int i;
- char *ret = NULL;
- for (i = 0; i < nr_refspec; i++) {
- struct refspec *refspec = refspecs + i;
- if (refspec->pattern) {
- if (match_name_with_pattern(refspec->src, name,
- refspec->dst, &ret))
- return ret;
- } else if (!strcmp(refspec->src, name))
- return strdup(refspec->dst);
- }
- return NULL;
-}
+ int find_src = !query->src;
-int remote_find_tracking(struct remote *remote, struct refspec *refspec)
-{
- int find_src = refspec->src == NULL;
- char *needle, **result;
- int i;
+ if (find_src && !query->dst)
+ return error("query_refspecs: need either src or dst");
- if (find_src) {
- if (!refspec->dst)
- return error("find_tracking: need either src or dst");
- needle = refspec->dst;
- result = &refspec->src;
- } else {
- needle = refspec->src;
- result = &refspec->dst;
- }
+ for (i = 0; i < ref_count; i++) {
+ struct refspec *refspec = &refs[i];
+ const char *key = find_src ? refspec->dst : refspec->src;
+ const char *value = find_src ? refspec->src : refspec->dst;
+ const char *needle = find_src ? query->dst : query->src;
+ char **result = find_src ? &query->src : &query->dst;
- for (i = 0; i < remote->fetch_refspec_nr; i++) {
- struct refspec *fetch = &remote->fetch[i];
- const char *key = find_src ? fetch->dst : fetch->src;
- const char *value = find_src ? fetch->src : fetch->dst;
- if (!fetch->dst)
+ if (!refspec->dst)
continue;
- if (fetch->pattern) {
+ if (refspec->pattern) {
if (match_name_with_pattern(key, needle, value, result)) {
- refspec->force = fetch->force;
+ query->force = refspec->force;
return 0;
}
} else if (!strcmp(needle, key)) {
*result = xstrdup(value);
- refspec->force = fetch->force;
+ query->force = refspec->force;
return 0;
}
}
return -1;
}
+char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+ const char *name)
+{
+ struct refspec query;
+
+ memset(&query, 0, sizeof(struct refspec));
+ query.src = (char *)name;
+
+ if (query_refspecs(refspecs, nr_refspec, &query))
+ return NULL;
+
+ return query.dst;
+}
+
+int remote_find_tracking(struct remote *remote, struct refspec *refspec)
+{
+ return query_refspecs(remote->fetch, remote->fetch_refspec_nr, refspec);
+}
+
static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
const char *name)
{
@@ -896,7 +871,7 @@ struct ref *alloc_ref(const char *name)
return alloc_ref_with_prefix("", 0, name);
}
-static struct ref *copy_ref(const struct ref *ref)
+struct ref *copy_ref(const struct ref *ref)
{
struct ref *cpy;
size_t len;
@@ -944,6 +919,27 @@ void free_refs(struct ref *ref)
}
}
+int ref_compare_name(const void *va, const void *vb)
+{
+ const struct ref *a = va, *b = vb;
+ return strcmp(a->name, b->name);
+}
+
+static void *ref_list_get_next(const void *a)
+{
+ return ((const struct ref *)a)->next;
+}
+
+static void ref_list_set_next(void *a, void *next)
+{
+ ((struct ref *)a)->next = next;
+}
+
+void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))
+{
+ *l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);
+}
+
static int count_refspec_match(const char *pattern,
struct ref *refs,
struct ref **matched_ref)
@@ -1006,16 +1002,20 @@ static void tail_link_ref(struct ref *ref, struct ref ***tail)
*tail = &ref->next;
}
+static struct ref *alloc_delete_ref(void)
+{
+ struct ref *ref = alloc_ref("(delete)");
+ hashclr(ref->new_sha1);
+ return ref;
+}
+
static struct ref *try_explicit_object_name(const char *name)
{
unsigned char sha1[20];
struct ref *ref;
- if (!*name) {
- ref = alloc_ref("(delete)");
- hashclr(ref->new_sha1);
- return ref;
- }
+ if (!*name)
+ return alloc_delete_ref();
if (get_sha1(name, sha1))
return NULL;
ref = alloc_ref(name);
@@ -1035,7 +1035,7 @@ static char *guess_ref(const char *name, struct ref *peer)
struct strbuf buf = STRBUF_INIT;
unsigned char sha1[20];
- const char *r = resolve_ref(peer->name, sha1, 1, NULL);
+ const char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL);
if (!r)
return NULL;
@@ -1086,7 +1086,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
unsigned char sha1[20];
int flag;
- dst_value = resolve_ref(matched_src->name, sha1, 1, &flag);
+ dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag);
if (!dst_value ||
((flag & REF_ISSYMREF) &&
prefixcmp(dst_value, "refs/heads/")))
@@ -1138,10 +1138,11 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
return errs;
}
-static const struct refspec *check_pattern_match(const struct refspec *rs,
- int rs_nr,
- const struct ref *src)
+static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref *ref,
+ int send_mirror, int direction, const struct refspec **ret_pat)
{
+ const struct refspec *pat;
+ char *name;
int i;
int matching_refs = -1;
for (i = 0; i < rs_nr; i++) {
@@ -1151,14 +1152,36 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
continue;
}
- if (rs[i].pattern && match_name_with_pattern(rs[i].src, src->name,
- NULL, NULL))
- return rs + i;
+ if (rs[i].pattern) {
+ const char *dst_side = rs[i].dst ? rs[i].dst : rs[i].src;
+ int match;
+ if (direction == FROM_SRC)
+ match = match_name_with_pattern(rs[i].src, ref->name, dst_side, &name);
+ else
+ match = match_name_with_pattern(dst_side, ref->name, rs[i].src, &name);
+ if (match) {
+ matching_refs = i;
+ break;
+ }
+ }
}
- if (matching_refs != -1)
- return rs + matching_refs;
- else
+ if (matching_refs == -1)
return NULL;
+
+ pat = rs + matching_refs;
+ if (pat->matching) {
+ /*
+ * "matching refs"; traditionally we pushed everything
+ * including refs outside refs/heads/ hierarchy, but
+ * that does not make much sense these days.
+ */
+ if (!send_mirror && prefixcmp(ref->name, "refs/heads/"))
+ return NULL;
+ name = xstrdup(ref->name);
+ }
+ if (ret_pat)
+ *ret_pat = pat;
+ return name;
}
static struct ref **tail_ref(struct ref **head)
@@ -1170,19 +1193,23 @@ static struct ref **tail_ref(struct ref **head)
}
/*
- * Note. This is used only by "push"; refspec matching rules for
- * push and fetch are subtly different, so do not try to reuse it
- * without thinking.
+ * Given the set of refs the local repository has, the set of refs the
+ * remote repository has, and the refspec used for push, determine
+ * what remote refs we will update and with what value by setting
+ * peer_ref (which object is being pushed) and force (if the push is
+ * forced) in elements of "dst". The function may add new elements to
+ * dst (e.g. pushing to a new branch, done in match_explicit_refs).
*/
-int match_refs(struct ref *src, struct ref **dst,
- int nr_refspec, const char **refspec, int flags)
+int match_push_refs(struct ref *src, struct ref **dst,
+ int nr_refspec, const char **refspec, int flags)
{
struct refspec *rs;
int send_all = flags & MATCH_REFS_ALL;
int send_mirror = flags & MATCH_REFS_MIRROR;
+ int send_prune = flags & MATCH_REFS_PRUNE;
int errs;
static const char *default_refspec[] = { ":", NULL };
- struct ref **dst_tail = tail_ref(dst);
+ struct ref *ref, **dst_tail = tail_ref(dst);
if (!nr_refspec) {
nr_refspec = 1;
@@ -1192,39 +1219,23 @@ int match_refs(struct ref *src, struct ref **dst,
errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec);
/* pick the remainder */
- for ( ; src; src = src->next) {
+ for (ref = src; ref; ref = ref->next) {
struct ref *dst_peer;
const struct refspec *pat = NULL;
char *dst_name;
- if (src->peer_ref)
- continue;
- pat = check_pattern_match(rs, nr_refspec, src);
- if (!pat)
+ if (ref->peer_ref)
continue;
- if (pat->matching) {
- /*
- * "matching refs"; traditionally we pushed everything
- * including refs outside refs/heads/ hierarchy, but
- * that does not make much sense these days.
- */
- if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
- continue;
- dst_name = xstrdup(src->name);
+ dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat);
+ if (!dst_name)
+ continue;
- } else {
- const char *dst_side = pat->dst ? pat->dst : pat->src;
- if (!match_name_with_pattern(pat->src, src->name,
- dst_side, &dst_name))
- die("Didn't think it matches any more");
- }
dst_peer = find_ref_by_name(*dst, dst_name);
if (dst_peer) {
if (dst_peer->peer_ref)
/* We're already sending something to this ref. */
goto free_name;
-
} else {
if (pat->matching && !(send_all || send_mirror))
/*
@@ -1236,13 +1247,30 @@ int match_refs(struct ref *src, struct ref **dst,
/* Create a new one and link it */
dst_peer = make_linked_ref(dst_name, &dst_tail);
- hashcpy(dst_peer->new_sha1, src->new_sha1);
+ hashcpy(dst_peer->new_sha1, ref->new_sha1);
}
- dst_peer->peer_ref = copy_ref(src);
+ dst_peer->peer_ref = copy_ref(ref);
dst_peer->force = pat->force;
free_name:
free(dst_name);
}
+ if (send_prune) {
+ /* check for missing refs on the remote */
+ for (ref = *dst; ref; ref = ref->next) {
+ char *src_name;
+
+ if (ref->peer_ref)
+ /* We're already sending something to this ref. */
+ continue;
+
+ src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL);
+ if (src_name) {
+ if (!find_ref_by_name(src, src_name))
+ ref->peer_ref = alloc_delete_ref();
+ free(src_name);
+ }
+ }
+ }
if (errs)
return -1;
return 0;
@@ -1427,8 +1455,8 @@ int get_fetch_map(const struct ref *remote_refs,
for (rmp = &ref_map; *rmp; ) {
if ((*rmp)->peer_ref) {
- int st = check_ref_format((*rmp)->peer_ref->name + 5);
- if (st && st != CHECK_REF_FORMAT_ONELEVEL) {
+ if (check_refname_format((*rmp)->peer_ref->name + 5,
+ REFNAME_ALLOW_ONELEVEL)) {
struct ref *ignore = *rmp;
error("* Ignoring funny ref '%s' locally",
(*rmp)->peer_ref->name);
@@ -1532,13 +1560,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
* nothing to report.
*/
base = branch->merge[0]->dst;
- if (!resolve_ref(base, sha1, 1, NULL))
+ if (read_ref(base, sha1))
return 0;
theirs = lookup_commit_reference(sha1);
if (!theirs)
return 0;
- if (!resolve_ref(branch->refname, sha1, 1, NULL))
+ if (read_ref(branch->refname, sha1))
return 0;
ours = lookup_commit_reference(sha1);
if (!ours)
@@ -1597,19 +1625,29 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
base = branch->merge[0]->dst;
base = shorten_unambiguous_ref(base, 0);
if (!num_theirs)
- strbuf_addf(sb, "Your branch is ahead of '%s' "
- "by %d commit%s.\n",
- base, num_ours, (num_ours == 1) ? "" : "s");
+ strbuf_addf(sb,
+ Q_("Your branch is ahead of '%s' by %d commit.\n",
+ "Your branch is ahead of '%s' by %d commits.\n",
+ num_ours),
+ base, num_ours);
else if (!num_ours)
- strbuf_addf(sb, "Your branch is behind '%s' "
- "by %d commit%s, "
- "and can be fast-forwarded.\n",
- base, num_theirs, (num_theirs == 1) ? "" : "s");
+ strbuf_addf(sb,
+ Q_("Your branch is behind '%s' by %d commit, "
+ "and can be fast-forwarded.\n",
+ "Your branch is behind '%s' by %d commits, "
+ "and can be fast-forwarded.\n",
+ num_theirs),
+ base, num_theirs);
else
- strbuf_addf(sb, "Your branch and '%s' have diverged,\n"
- "and have %d and %d different commit(s) each, "
- "respectively.\n",
- base, num_ours, num_theirs);
+ strbuf_addf(sb,
+ Q_("Your branch and '%s' have diverged,\n"
+ "and have %d and %d different commit each, "
+ "respectively.\n",
+ "Your branch and '%s' have diverged,\n"
+ "and have %d and %d different commits each, "
+ "respectively.\n",
+ num_theirs),
+ base, num_ours, num_theirs);
return 1;
}
@@ -1620,7 +1658,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla
int len;
/* we already know it starts with refs/ to get here */
- if (check_ref_format(refname + 5))
+ if (check_refname_format(refname + 5, 0))
return 0;
len = strlen(refname) + 1;
@@ -1667,7 +1705,9 @@ struct ref *guess_remote_head(const struct ref *head,
/* Look for another ref that points there */
for (r = refs; r; r = r->next) {
- if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) {
+ if (r != head &&
+ !prefixcmp(r->name, "refs/heads/") &&
+ !hashcmp(r->old_sha1, head->old_sha1)) {
*tail = copy_ref(r);
tail = &((*tail)->next);
if (!all)
@@ -1679,36 +1719,47 @@ struct ref *guess_remote_head(const struct ref *head,
}
struct stale_heads_info {
- struct remote *remote;
struct string_list *ref_names;
struct ref **stale_refs_tail;
+ struct refspec *refs;
+ int ref_count;
};
static int get_stale_heads_cb(const char *refname,
const unsigned char *sha1, int flags, void *cb_data)
{
struct stale_heads_info *info = cb_data;
- struct refspec refspec;
- memset(&refspec, 0, sizeof(refspec));
- refspec.dst = (char *)refname;
- if (!remote_find_tracking(info->remote, &refspec)) {
- if (!((flags & REF_ISSYMREF) ||
- string_list_has_string(info->ref_names, refspec.src))) {
- struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
- hashcpy(ref->new_sha1, sha1);
- }
+ struct refspec query;
+ memset(&query, 0, sizeof(struct refspec));
+ query.dst = (char *)refname;
+
+ if (query_refspecs(info->refs, info->ref_count, &query))
+ return 0; /* No matches */
+
+ /*
+ * If we did find a suitable refspec and it's not a symref and
+ * it's not in the list of refs that currently exist in that
+ * remote we consider it to be stale.
+ */
+ if (!((flags & REF_ISSYMREF) ||
+ string_list_has_string(info->ref_names, query.src))) {
+ struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
+ hashcpy(ref->new_sha1, sha1);
}
+
+ free(query.src);
return 0;
}
-struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
+struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map)
{
struct ref *ref, *stale_refs = NULL;
struct string_list ref_names = STRING_LIST_INIT_NODUP;
struct stale_heads_info info;
- info.remote = remote;
info.ref_names = &ref_names;
info.stale_refs_tail = &stale_refs;
+ info.refs = refs;
+ info.ref_count = ref_count;
for (ref = fetch_map; ref; ref = ref->next)
string_list_append(&ref_names, ref->name);
sort_string_list(&ref_names);
diff --git a/remote.h b/remote.h
index 888d7c1..251d8fd 100644
--- a/remote.h
+++ b/remote.h
@@ -70,8 +70,10 @@ struct refspec {
extern const struct refspec *tag_refspec;
struct ref *alloc_ref(const char *name);
-
+struct ref *copy_ref(const struct ref *ref);
struct ref *copy_ref_list(const struct ref *ref);
+void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));
+int ref_compare_name(const void *, const void *);
int check_ref_type(const struct ref *ref, int flags);
@@ -96,8 +98,8 @@ void free_refspec(int nr_refspec, struct refspec *refspec);
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
const char *name);
-int match_refs(struct ref *src, struct ref **dst,
- int nr_refspec, const char **refspec, int all);
+int match_push_refs(struct ref *src, struct ref **dst,
+ int nr_refspec, const char **refspec, int all);
void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
int force_update);
@@ -145,7 +147,8 @@ int branch_merge_matches(struct branch *, int n, const char *);
enum match_refs_flags {
MATCH_REFS_NONE = 0,
MATCH_REFS_ALL = (1 << 0),
- MATCH_REFS_MIRROR = (1 << 1)
+ MATCH_REFS_MIRROR = (1 << 1),
+ MATCH_REFS_PRUNE = (1 << 2)
};
/* Reporting of tracking info */
@@ -164,6 +167,6 @@ struct ref *guess_remote_head(const struct ref *head,
int all);
/* Return refs which no longer exist on remote */
-struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map);
+struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map);
#endif
diff --git a/rerere.c b/rerere.c
index dcb525a..da18fc3 100644
--- a/rerere.c
+++ b/rerere.c
@@ -544,13 +544,13 @@ static int do_plain_rerere(struct string_list *rr, int fd)
if (has_rerere_resolution(name)) {
if (!merge(name, path)) {
- if (rerere_autoupdate)
+ const char *msg;
+ if (rerere_autoupdate) {
string_list_insert(&update, path);
- fprintf(stderr,
- "%s '%s' using previous resolution.\n",
- rerere_autoupdate
- ? "Staged" : "Resolved",
- path);
+ msg = "Staged '%s' using previous resolution.\n";
+ } else
+ msg = "Resolved '%s' using previous resolution.\n";
+ fprintf(stderr, msg, path);
goto mark_resolved;
}
}
diff --git a/revision.c b/revision.c
index c46cfaa..5b81a92 100644
--- a/revision.c
+++ b/revision.c
@@ -40,6 +40,47 @@ char *path_name(const struct name_path *path, const char *name)
return n;
}
+static int show_path_component_truncated(FILE *out, const char *name, int len)
+{
+ int cnt;
+ for (cnt = 0; cnt < len; cnt++) {
+ int ch = name[cnt];
+ if (!ch || ch == '\n')
+ return -1;
+ fputc(ch, out);
+ }
+ return len;
+}
+
+static int show_path_truncated(FILE *out, const struct name_path *path)
+{
+ int emitted, ours;
+
+ if (!path)
+ return 0;
+ emitted = show_path_truncated(out, path->up);
+ if (emitted < 0)
+ return emitted;
+ if (emitted)
+ fputc('/', out);
+ ours = show_path_component_truncated(out, path->elem, path->elem_len);
+ if (ours < 0)
+ return ours;
+ return ours || emitted;
+}
+
+void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component)
+{
+ struct name_path leaf;
+ leaf.up = (struct name_path *)path;
+ leaf.elem = component;
+ leaf.elem_len = strlen(component);
+
+ fprintf(out, "%s ", sha1_to_hex(obj->sha1));
+ show_path_truncated(out, &leaf);
+ fputc('\n', out);
+}
+
void add_object(struct object *obj,
struct object_array *p,
struct name_path *path,
@@ -98,11 +139,32 @@ void mark_tree_uninteresting(struct tree *tree)
void mark_parents_uninteresting(struct commit *commit)
{
- struct commit_list *parents = commit->parents;
+ struct commit_list *parents = NULL, *l;
+
+ for (l = commit->parents; l; l = l->next)
+ commit_list_insert(l->item, &parents);
while (parents) {
struct commit *commit = parents->item;
- if (!(commit->object.flags & UNINTERESTING)) {
+ l = parents;
+ parents = parents->next;
+ free(l);
+
+ while (commit) {
+ /*
+ * A missing commit is ok iff its parent is marked
+ * uninteresting.
+ *
+ * We just mark such a thing parsed, so that when
+ * it is popped next time around, we won't be trying
+ * to parse it and get an error.
+ */
+ if (!has_sha1_file(commit->object.sha1))
+ commit->object.parsed = 1;
+
+ if (commit->object.flags & UNINTERESTING)
+ break;
+
commit->object.flags |= UNINTERESTING;
/*
@@ -113,21 +175,13 @@ void mark_parents_uninteresting(struct commit *commit)
* wasn't uninteresting), in which case we need
* to mark its parents recursively too..
*/
- if (commit->parents)
- mark_parents_uninteresting(commit);
- }
+ if (!commit->parents)
+ break;
- /*
- * A missing commit is ok iff its parent is marked
- * uninteresting.
- *
- * We just mark such a thing parsed, so that when
- * it is popped next time around, we won't be trying
- * to parse it and get an error.
- */
- if (!has_sha1_file(commit->object.sha1))
- commit->object.parsed = 1;
- parents = parents->next;
+ for (l = commit->parents->next; l; l = l->next)
+ commit_list_insert(l->item, &parents);
+ commit = commit->parents->item;
+ }
}
}
@@ -185,6 +239,13 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con
return object;
}
+void add_pending_sha1(struct rev_info *revs, const char *name,
+ const unsigned char *sha1, unsigned int flags)
+{
+ struct object *object = get_reference(revs, name, sha1, flags);
+ add_pending_object(revs, object, name);
+}
+
static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
{
unsigned long flags = object->flags;
@@ -368,7 +429,7 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
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;
+ int tree_changed = 0, tree_same = 0, nth_parent = 0;
/*
* If we don't do pruning, everything is interesting
@@ -396,6 +457,14 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
while ((parent = *pp) != NULL) {
struct commit *p = parent->item;
+ /*
+ * Do not compare with later parents when we care only about
+ * the first parent chain, in order to avoid derailing the
+ * traversal to follow a side branch that brought everything
+ * in the path we are limited to by the pathspec.
+ */
+ if (revs->first_parent_only && nth_parent++)
+ break;
if (parse_commit(p) < 0)
die("cannot simplify commit %s (because of %s)",
sha1_to_hex(commit->object.sha1),
@@ -729,12 +798,16 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li
* to filter the result of "A..B" further to the ones that can actually
* reach A.
*/
-static struct commit_list *collect_bottom_commits(struct commit_list *list)
+static struct commit_list *collect_bottom_commits(struct rev_info *revs)
{
- struct commit_list *elem, *bottom = NULL;
- for (elem = list; elem; elem = elem->next)
- if (elem->item->object.flags & UNINTERESTING)
- commit_list_insert(elem->item, &bottom);
+ struct commit_list *bottom = NULL;
+ int i;
+ for (i = 0; i < revs->cmdline.nr; i++) {
+ struct rev_cmdline_entry *elem = &revs->cmdline.rev[i];
+ if ((elem->flags & UNINTERESTING) &&
+ elem->item->type == OBJ_COMMIT)
+ commit_list_insert((struct commit *)elem->item, &bottom);
+ }
return bottom;
}
@@ -765,7 +838,7 @@ static int limit_list(struct rev_info *revs)
struct commit_list *bottom = NULL;
if (revs->ancestry_path) {
- bottom = collect_bottom_commits(list);
+ bottom = collect_bottom_commits(revs);
if (!bottom)
die("--ancestry-path given but there are no bottom commits");
}
@@ -822,6 +895,23 @@ static int limit_list(struct rev_info *revs)
return 0;
}
+static void add_rev_cmdline(struct rev_info *revs,
+ struct object *item,
+ const char *name,
+ int whence,
+ unsigned flags)
+{
+ struct rev_cmdline_info *info = &revs->cmdline;
+ int nr = info->nr;
+
+ ALLOC_GROW(info->rev, nr + 1, info->alloc);
+ info->rev[nr].item = item;
+ info->rev[nr].name = name;
+ info->rev[nr].whence = whence;
+ info->rev[nr].flags = flags;
+ info->nr++;
+}
+
struct all_refs_cb {
int all_flags;
int warned_bad_reflog;
@@ -834,7 +924,8 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
struct all_refs_cb *cb = cb_data;
struct object *object = get_reference(cb->all_revs, path, sha1,
cb->all_flags);
- add_pending_object(cb->all_revs, object, path);
+ add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
+ add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags);
return 0;
}
@@ -860,6 +951,7 @@ static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
struct object *o = parse_object(sha1);
if (o) {
o->flags |= cb->all_flags;
+ /* ??? CMDLINEFLAGS ??? */
add_pending_object(cb->all_revs, o, "");
}
else if (!cb->warned_bad_reflog) {
@@ -896,12 +988,13 @@ static void handle_reflog(struct rev_info *revs, unsigned flags)
for_each_reflog(handle_one_reflog, &cb);
}
-static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
+static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
{
unsigned char sha1[20];
struct object *it;
struct commit *commit;
struct commit_list *parents;
+ const char *arg = arg_;
if (*arg == '^') {
flags ^= UNINTERESTING;
@@ -925,6 +1018,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
for (parents = commit->parents; parents; parents = parents->next) {
it = &parents->item->object;
it->flags |= flags;
+ add_rev_cmdline(revs, it, arg_, REV_CMD_PARENTS_ONLY, flags);
add_pending_object(revs, it, arg);
}
return 1;
@@ -986,10 +1080,12 @@ static void prepare_show_merge(struct rev_info *revs)
const char **prune = NULL;
int i, prune_num = 1; /* counting terminating NULL */
- if (get_sha1("HEAD", sha1) || !(head = lookup_commit(sha1)))
+ if (get_sha1("HEAD", sha1))
die("--merge without HEAD?");
- if (get_sha1("MERGE_HEAD", sha1) || !(other = lookup_commit(sha1)))
+ head = lookup_commit_or_die(sha1, "HEAD");
+ if (get_sha1("MERGE_HEAD", sha1))
die("--merge without MERGE_HEAD?");
+ other = lookup_commit_or_die(sha1, "MERGE_HEAD");
add_pending_object(revs, &head->object, "HEAD");
add_pending_object(revs, &other->object, "MERGE_HEAD");
bases = get_merge_bases(head, other, 1);
@@ -1018,7 +1114,7 @@ static void prepare_show_merge(struct rev_info *revs)
revs->limited = 1;
}
-int handle_revision_arg(const char *arg, struct rev_info *revs,
+int handle_revision_arg(const char *arg_, struct rev_info *revs,
int flags,
int cant_be_filename)
{
@@ -1027,6 +1123,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
struct object *object;
unsigned char sha1[20];
int local_flags;
+ const char *arg = arg_;
dotdot = strstr(arg, "..");
if (dotdot) {
@@ -1035,6 +1132,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
const char *this = arg;
int symmetric = *next == '.';
unsigned int flags_exclude = flags ^ UNINTERESTING;
+ unsigned int a_flags;
*dotdot = 0;
next += symmetric;
@@ -1069,10 +1167,15 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
add_pending_commit_list(revs, exclude,
flags_exclude);
free_commit_list(exclude);
- a->object.flags |= flags | SYMMETRIC_LEFT;
+ a_flags = flags | SYMMETRIC_LEFT;
} else
- a->object.flags |= flags_exclude;
+ a_flags = flags_exclude;
+ a->object.flags |= a_flags;
b->object.flags |= flags;
+ add_rev_cmdline(revs, &a->object, this,
+ REV_CMD_LEFT, a_flags);
+ add_rev_cmdline(revs, &b->object, next,
+ REV_CMD_RIGHT, flags);
add_pending_object(revs, &a->object, this);
add_pending_object(revs, &b->object, next);
return 0;
@@ -1103,6 +1206,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
if (!cant_be_filename)
verify_non_filename(revs->prefix, arg);
object = get_reference(revs, arg, sha1, flags ^ local_flags);
+ add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
add_pending_object_with_mode(revs, object, arg, mode);
return 0;
}
@@ -1254,11 +1358,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->topo_order = 1;
} else if (!strcmp(arg, "--simplify-merges")) {
revs->simplify_merges = 1;
+ revs->topo_order = 1;
revs->rewrite_parents = 1;
revs->simplify_history = 0;
revs->limited = 1;
} else if (!strcmp(arg, "--simplify-by-decoration")) {
revs->simplify_merges = 1;
+ revs->topo_order = 1;
revs->rewrite_parents = 1;
revs->simplify_history = 0;
revs->simplify_by_decoration = 1;
@@ -1342,6 +1448,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->tree_objects = 1;
revs->blob_objects = 1;
revs->edge_hint = 1;
+ } else if (!strcmp(arg, "--verify-objects")) {
+ revs->tag_objects = 1;
+ revs->tree_objects = 1;
+ revs->blob_objects = 1;
+ revs->verify_objects = 1;
} else if (!strcmp(arg, "--unpacked")) {
revs->unpacked = 1;
} else if (!prefixcmp(arg, "--unpacked=")) {
@@ -1381,6 +1492,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->show_notes = 1;
revs->show_notes_given = 1;
revs->notes_opt.use_default_notes = 1;
+ } else if (!strcmp(arg, "--show-signature")) {
+ revs->show_signature = 1;
} else if (!prefixcmp(arg, "--show-notes=") ||
!prefixcmp(arg, "--notes=")) {
struct strbuf buf = STRBUF_INIT;
@@ -1471,6 +1584,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->grep_filter.regflags |= REG_EXTENDED;
} else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
revs->grep_filter.regflags |= REG_ICASE;
+ DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE);
} else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
revs->grep_filter.fixed = 1;
} else if (!strcmp(arg, "--all-match")) {
@@ -1603,17 +1717,21 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
submodule = opt->submodule;
/* First, search for "--" */
- seen_dashdash = 0;
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (strcmp(arg, "--"))
- continue;
- argv[i] = NULL;
- argc = i;
- if (argv[i + 1])
- append_prune_data(&prune_data, argv + i + 1);
+ if (opt && opt->assume_dashdash) {
seen_dashdash = 1;
- break;
+ } else {
+ seen_dashdash = 0;
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (strcmp(arg, "--"))
+ continue;
+ argv[i] = NULL;
+ argc = i;
+ if (argv[i + 1])
+ append_prune_data(&prune_data, argv + i + 1);
+ seen_dashdash = 1;
+ break;
+ }
}
/* Second, deal with arguments and options */
@@ -1665,7 +1783,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
* but the latter we have checked in the main loop.
*/
for (j = i; j < argc; j++)
- verify_filename(revs->prefix, argv[j]);
+ verify_filename(revs->prefix, argv[j], j == i);
append_prune_data(&prune_data, argv + i);
break;
@@ -1831,8 +1949,9 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
}
/*
- * Do we know what commit all of our parents should be rewritten to?
- * Otherwise we are not ready to rewrite this one yet.
+ * Do we know what commit all of our parents that matter
+ * should be rewritten to? Otherwise we are not ready to
+ * rewrite this one yet.
*/
for (cnt = 0, p = commit->parents; p; p = p->next) {
pst = locate_simplify_state(revs, p->item);
@@ -1840,6 +1959,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
tail = &commit_list_insert(p->item, tail)->next;
cnt++;
}
+ if (revs->first_parent_only)
+ break;
}
if (cnt) {
tail = &commit_list_insert(commit, tail)->next;
@@ -1852,8 +1973,13 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
for (p = commit->parents; p; p = p->next) {
pst = locate_simplify_state(revs, p->item);
p->item = pst->simplified;
+ if (revs->first_parent_only)
+ break;
}
- cnt = remove_duplicate_parents(commit);
+ if (!revs->first_parent_only)
+ cnt = remove_duplicate_parents(commit);
+ else
+ cnt = 1;
/*
* It is possible that we are a merge and one side branch
@@ -1897,25 +2023,31 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
static void simplify_merges(struct rev_info *revs)
{
- struct commit_list *list;
+ struct commit_list *list, *next;
struct commit_list *yet_to_do, **tail;
+ struct commit *commit;
- if (!revs->topo_order)
- sort_in_topological_order(&revs->commits, revs->lifo);
if (!revs->prune)
return;
/* feed the list reversed */
yet_to_do = NULL;
- for (list = revs->commits; list; list = list->next)
- commit_list_insert(list->item, &yet_to_do);
+ for (list = revs->commits; list; list = next) {
+ commit = list->item;
+ next = list->next;
+ /*
+ * Do not free(list) here yet; the original list
+ * is used later in this function.
+ */
+ commit_list_insert(commit, &yet_to_do);
+ }
while (yet_to_do) {
list = yet_to_do;
yet_to_do = NULL;
tail = &yet_to_do;
while (list) {
- struct commit *commit = list->item;
- struct commit_list *next = list->next;
+ commit = list->item;
+ next = list->next;
free(list);
list = next;
tail = simplify_one(revs, commit, tail);
@@ -1927,9 +2059,10 @@ static void simplify_merges(struct rev_info *revs)
revs->commits = NULL;
tail = &revs->commits;
while (list) {
- struct commit *commit = list->item;
- struct commit_list *next = list->next;
struct merge_simplify_state *st;
+
+ commit = list->item;
+ next = list->next;
free(list);
list = next;
st = locate_simplify_state(revs, commit);
@@ -1950,10 +2083,16 @@ static void set_children(struct rev_info *revs)
}
}
+void reset_revision_walk(void)
+{
+ clear_object_flags(SEEN | ADDED | SHOWN);
+}
+
int prepare_revision_walk(struct rev_info *revs)
{
int nr = revs->pending.nr;
struct object_array_entry *e, *list;
+ struct commit_list **next = &revs->commits;
e = list = revs->pending.objects;
revs->pending.nr = 0;
@@ -1964,12 +2103,14 @@ int prepare_revision_walk(struct rev_info *revs)
if (commit) {
if (!(commit->object.flags & SEEN)) {
commit->object.flags |= SEEN;
- commit_list_insert_by_date(commit, &revs->commits);
+ next = commit_list_append(commit, next);
}
}
e++;
}
- free(list);
+ commit_list_sort_by_date(&revs->commits);
+ if (!revs->leak_pending)
+ free(list);
if (revs->no_walk)
return 0;
@@ -2037,7 +2178,6 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list)
return 1;
return grep_buffer(&opt->grep_filter,
- NULL, /* we say nothing, not even filename */
commit->buffer, strlen(commit->buffer));
}
diff --git a/revision.h b/revision.h
index 3d64ada..863f4f6 100644
--- a/revision.h
+++ b/revision.h
@@ -24,6 +24,23 @@ struct rev_info;
struct log_info;
struct string_list;
+struct rev_cmdline_info {
+ unsigned int nr;
+ unsigned int alloc;
+ struct rev_cmdline_entry {
+ struct object *item;
+ const char *name;
+ enum {
+ REV_CMD_REF,
+ REV_CMD_PARENTS_ONLY,
+ REV_CMD_LEFT,
+ REV_CMD_RIGHT,
+ REV_CMD_REV
+ } whence;
+ unsigned flags;
+ } *rev;
+};
+
struct rev_info {
/* Starting list */
struct commit_list *commits;
@@ -32,6 +49,9 @@ struct rev_info {
/* Parents of shown commits */
struct object_array boundary_commits;
+ /* The end-points specified by the end user */
+ struct rev_cmdline_info cmdline;
+
/* Basic information */
const char *prefix;
const char *def;
@@ -53,6 +73,7 @@ struct rev_info {
tag_objects:1,
tree_objects:1,
blob_objects:1,
+ verify_objects:1,
edge_hint:1,
limited:1,
unpacked:1,
@@ -89,6 +110,7 @@ struct rev_info {
show_merge:1,
show_notes:1,
show_notes_given:1,
+ show_signature:1,
pretty_given:1,
abbrev_commit:1,
abbrev_commit_given:1,
@@ -97,6 +119,7 @@ struct rev_info {
date_mode_explicit:1,
preserve_subject:1;
unsigned int disable_stdin:1;
+ unsigned int leak_pending:1;
enum date_mode date_mode;
@@ -160,6 +183,7 @@ struct setup_revision_opt {
const char *def;
void (*tweak)(struct rev_info *, struct setup_revision_opt *);
const char *submodule;
+ int assume_dashdash;
};
extern void init_revisions(struct rev_info *revs, const char *prefix);
@@ -169,6 +193,7 @@ extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ct
const char * const usagestr[]);
extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
+extern void reset_revision_walk(void);
extern int prepare_revision_walk(struct rev_info *revs);
extern struct commit *get_revision(struct rev_info *revs);
extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);
@@ -185,12 +210,15 @@ struct name_path {
char *path_name(const struct name_path *path, const char *name);
+extern void show_object_with_name(FILE *, struct object *, const struct name_path *, const char *);
+
extern void add_object(struct object *obj,
struct object_array *p,
struct name_path *path,
const char *name);
extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
+extern void add_pending_sha1(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags);
extern void add_head_to_pending(struct rev_info *);
diff --git a/run-command.c b/run-command.c
index a2796c4..606791d 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1,6 +1,69 @@
#include "cache.h"
#include "run-command.h"
#include "exec_cmd.h"
+#include "sigchain.h"
+#include "argv-array.h"
+
+#ifndef SHELL_PATH
+# define SHELL_PATH "/bin/sh"
+#endif
+
+struct child_to_clean {
+ pid_t pid;
+ struct child_to_clean *next;
+};
+static struct child_to_clean *children_to_clean;
+static int installed_child_cleanup_handler;
+
+static void cleanup_children(int sig)
+{
+ while (children_to_clean) {
+ struct child_to_clean *p = children_to_clean;
+ children_to_clean = p->next;
+ kill(p->pid, sig);
+ free(p);
+ }
+}
+
+static void cleanup_children_on_signal(int sig)
+{
+ cleanup_children(sig);
+ sigchain_pop(sig);
+ raise(sig);
+}
+
+static void cleanup_children_on_exit(void)
+{
+ cleanup_children(SIGTERM);
+}
+
+static void mark_child_for_cleanup(pid_t pid)
+{
+ struct child_to_clean *p = xmalloc(sizeof(*p));
+ p->pid = pid;
+ p->next = children_to_clean;
+ children_to_clean = p;
+
+ if (!installed_child_cleanup_handler) {
+ atexit(cleanup_children_on_exit);
+ sigchain_push_common(cleanup_children_on_signal);
+ installed_child_cleanup_handler = 1;
+ }
+}
+
+static void clear_child_for_cleanup(pid_t pid)
+{
+ struct child_to_clean **last, *p;
+
+ last = &children_to_clean;
+ for (p = children_to_clean; p; p = p->next) {
+ if (p->pid == pid) {
+ *last = p->next;
+ free(p);
+ return;
+ }
+ }
+}
static inline void close_pair(int fd[2])
{
@@ -17,6 +80,68 @@ static inline void dup_devnull(int to)
}
#endif
+static char *locate_in_PATH(const char *file)
+{
+ const char *p = getenv("PATH");
+ struct strbuf buf = STRBUF_INIT;
+
+ if (!p || !*p)
+ return NULL;
+
+ while (1) {
+ const char *end = strchrnul(p, ':');
+
+ strbuf_reset(&buf);
+
+ /* POSIX specifies an empty entry as the current directory. */
+ if (end != p) {
+ strbuf_add(&buf, p, end - p);
+ strbuf_addch(&buf, '/');
+ }
+ strbuf_addstr(&buf, file);
+
+ if (!access(buf.buf, F_OK))
+ return strbuf_detach(&buf, NULL);
+
+ if (!*end)
+ break;
+ p = end + 1;
+ }
+
+ strbuf_release(&buf);
+ return NULL;
+}
+
+static int exists_in_PATH(const char *file)
+{
+ char *r = locate_in_PATH(file);
+ free(r);
+ return r != NULL;
+}
+
+int sane_execvp(const char *file, char * const argv[])
+{
+ if (!execvp(file, argv))
+ return 0; /* cannot happen ;-) */
+
+ /*
+ * When a command can't be found because one of the directories
+ * listed in $PATH is unsearchable, execvp reports EACCES, but
+ * careful usability testing (read: analysis of occasional bug
+ * reports) reveals that "No such file or directory" is more
+ * intuitive.
+ *
+ * We avoid commands with "/", because execvp will not do $PATH
+ * lookups in that case.
+ *
+ * The reassignment of EACCES to errno looks like a no-op below,
+ * but we need to protect against exists_in_PATH overwriting errno.
+ */
+ if (errno == EACCES && !strchr(file, '/'))
+ errno = exists_in_PATH(file) ? EACCES : ENOENT;
+ return -1;
+}
+
static const char **prepare_shell_cmd(const char **argv)
{
int argc, nargc = 0;
@@ -31,7 +156,11 @@ static const char **prepare_shell_cmd(const char **argv)
die("BUG: shell command is empty");
if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
+#ifndef WIN32
+ nargv[nargc++] = SHELL_PATH;
+#else
nargv[nargc++] = "sh";
+#endif
nargv[nargc++] = "-c";
if (argc < 2)
@@ -55,7 +184,7 @@ static int execv_shell_cmd(const char **argv)
{
const char **nargv = prepare_shell_cmd(argv);
trace_argv_printf(nargv, "trace: exec:");
- execvp(nargv[0], (char **)nargv);
+ sane_execvp(nargv[0], (char **)nargv);
free(nargv);
return -1;
}
@@ -129,6 +258,9 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
} else {
error("waitpid is confused (%s)", argv0);
}
+
+ clear_child_for_cleanup(pid);
+
errno = failed_errno;
return code;
}
@@ -277,7 +409,7 @@ fail_pipe:
} else if (cmd->use_shell) {
execv_shell_cmd(cmd->argv);
} else {
- execvp(cmd->argv[0], (char *const*) cmd->argv);
+ sane_execvp(cmd->argv[0], (char *const*) cmd->argv);
}
if (errno == ENOENT) {
if (!cmd->silent_exec_failure)
@@ -291,6 +423,8 @@ fail_pipe:
if (cmd->pid < 0)
error("cannot fork() for %s: %s", cmd->argv[0],
strerror(failed_errno = errno));
+ else if (cmd->clean_on_exit)
+ mark_child_for_cleanup(cmd->pid);
/*
* Wait for child's execvp. If the execvp succeeds (or if fork()
@@ -311,6 +445,7 @@ fail_pipe:
cmd->pid = -1;
}
close(notify_pipe[0]);
+
}
#else
{
@@ -355,6 +490,8 @@ fail_pipe:
failed_errno = errno;
if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
+ if (cmd->clean_on_exit && cmd->pid >= 0)
+ mark_child_for_cleanup(cmd->pid);
if (cmd->env)
free_environ(env);
@@ -430,6 +567,7 @@ static void prepare_run_command_v_opt(struct child_process *cmd,
cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0;
+ cmd->clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
}
int run_command_v_opt(const char **argv, int opt)
@@ -539,6 +677,8 @@ int start_async(struct async *async)
exit(!!async->proc(proc_in, proc_out, async->data));
}
+ mark_child_for_cleanup(async->pid);
+
if (need_in)
close(fdin[0]);
else if (async->in)
@@ -605,26 +745,23 @@ int finish_async(struct async *async)
int run_hook(const char *index_file, const char *name, ...)
{
struct child_process hook;
- const char **argv = NULL, *env[2];
+ struct argv_array argv = ARGV_ARRAY_INIT;
+ const char *p, *env[2];
char index[PATH_MAX];
va_list args;
int ret;
- size_t i = 0, alloc = 0;
if (access(git_path("hooks/%s", name), X_OK) < 0)
return 0;
va_start(args, name);
- ALLOC_GROW(argv, i + 1, alloc);
- argv[i++] = git_path("hooks/%s", name);
- while (argv[i-1]) {
- ALLOC_GROW(argv, i + 1, alloc);
- argv[i++] = va_arg(args, const char *);
- }
+ argv_array_push(&argv, git_path("hooks/%s", name));
+ while ((p = va_arg(args, const char *)))
+ argv_array_push(&argv, p);
va_end(args);
memset(&hook, 0, sizeof(hook));
- hook.argv = argv;
+ hook.argv = argv.argv;
hook.no_stdin = 1;
hook.stdout_to_stderr = 1;
if (index_file) {
@@ -635,6 +772,6 @@ int run_hook(const char *index_file, const char *name, ...)
}
ret = run_command(&hook);
- free(argv);
+ argv_array_clear(&argv);
return ret;
}
diff --git a/run-command.h b/run-command.h
index 56491b9..44f7d2b 100644
--- a/run-command.h
+++ b/run-command.h
@@ -38,6 +38,7 @@ struct child_process {
unsigned silent_exec_failure:1;
unsigned stdout_to_stderr:1;
unsigned use_shell:1;
+ unsigned clean_on_exit:1;
void (*preexec_cb)(void);
};
@@ -52,6 +53,7 @@ extern int run_hook(const char *index_file, const char *name, ...);
#define RUN_COMMAND_STDOUT_TO_STDERR 4
#define RUN_SILENT_EXEC_FAILURE 8
#define RUN_USING_SHELL 16
+#define RUN_CLEAN_ON_EXIT 32
int run_command_v_opt(const char **argv, int opt);
/*
diff --git a/sequencer.c b/sequencer.c
new file mode 100644
index 0000000..bf078f2
--- /dev/null
+++ b/sequencer.c
@@ -0,0 +1,1006 @@
+#include "cache.h"
+#include "sequencer.h"
+#include "dir.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "utf8.h"
+#include "cache-tree.h"
+#include "diff.h"
+#include "revision.h"
+#include "rerere.h"
+#include "merge-recursive.h"
+#include "refs.h"
+#include "argv-array.h"
+
+#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
+
+void remove_sequencer_state(void)
+{
+ struct strbuf seq_dir = STRBUF_INIT;
+
+ strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR));
+ remove_dir_recursively(&seq_dir, 0);
+ strbuf_release(&seq_dir);
+}
+
+static const char *action_name(const struct replay_opts *opts)
+{
+ return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
+}
+
+static char *get_encoding(const char *message);
+
+struct commit_message {
+ char *parent_label;
+ const char *label;
+ const char *subject;
+ char *reencoded_message;
+ const char *message;
+};
+
+static int get_message(struct commit *commit, struct commit_message *out)
+{
+ const char *encoding;
+ const char *abbrev, *subject;
+ int abbrev_len, subject_len;
+ char *q;
+
+ if (!commit->buffer)
+ return -1;
+ encoding = get_encoding(commit->buffer);
+ if (!encoding)
+ encoding = "UTF-8";
+ if (!git_commit_encoding)
+ git_commit_encoding = "UTF-8";
+
+ out->reencoded_message = NULL;
+ out->message = commit->buffer;
+ if (strcmp(encoding, git_commit_encoding))
+ out->reencoded_message = reencode_string(commit->buffer,
+ git_commit_encoding, encoding);
+ if (out->reencoded_message)
+ out->message = out->reencoded_message;
+
+ abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+ abbrev_len = strlen(abbrev);
+
+ subject_len = find_commit_subject(out->message, &subject);
+
+ out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
+ strlen("... ") + subject_len + 1);
+ q = out->parent_label;
+ q = mempcpy(q, "parent of ", strlen("parent of "));
+ out->label = q;
+ q = mempcpy(q, abbrev, abbrev_len);
+ q = mempcpy(q, "... ", strlen("... "));
+ out->subject = q;
+ q = mempcpy(q, subject, subject_len);
+ *q = '\0';
+ return 0;
+}
+
+static void free_message(struct commit_message *msg)
+{
+ free(msg->parent_label);
+ free(msg->reencoded_message);
+}
+
+static char *get_encoding(const char *message)
+{
+ const char *p = message, *eol;
+
+ while (*p && *p != '\n') {
+ for (eol = p + 1; *eol && *eol != '\n'; eol++)
+ ; /* do nothing */
+ if (!prefixcmp(p, "encoding ")) {
+ char *result = xmalloc(eol - 8 - p);
+ strlcpy(result, p + 9, eol - 8 - p);
+ return result;
+ }
+ p = eol;
+ if (*p == '\n')
+ p++;
+ }
+ return NULL;
+}
+
+static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
+{
+ const char *filename;
+ int fd;
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
+
+ filename = git_path("%s", pseudoref);
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
+ die_errno(_("Could not write to '%s'"), filename);
+ strbuf_release(&buf);
+}
+
+static void print_advice(int show_hint, struct replay_opts *opts)
+{
+ char *msg = getenv("GIT_CHERRY_PICK_HELP");
+
+ if (msg) {
+ fprintf(stderr, "%s\n", msg);
+ /*
+ * A conflict has occured but the porcelain
+ * (typically rebase --interactive) wants to take care
+ * of the commit itself so remove CHERRY_PICK_HEAD
+ */
+ unlink(git_path("CHERRY_PICK_HEAD"));
+ return;
+ }
+
+ if (show_hint) {
+ if (opts->no_commit)
+ advise(_("after resolving the conflicts, mark the corrected paths\n"
+ "with 'git add <paths>' or 'git rm <paths>'"));
+ else
+ advise(_("after resolving the conflicts, mark the corrected paths\n"
+ "with 'git add <paths>' or 'git rm <paths>'\n"
+ "and commit the result with 'git commit'"));
+ }
+}
+
+static void write_message(struct strbuf *msgbuf, const char *filename)
+{
+ static struct lock_file msg_file;
+
+ int msg_fd = hold_lock_file_for_update(&msg_file, filename,
+ LOCK_DIE_ON_ERROR);
+ if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
+ die_errno(_("Could not write to %s"), filename);
+ strbuf_release(msgbuf);
+ if (commit_lock_file(&msg_file) < 0)
+ die(_("Error wrapping up %s"), filename);
+}
+
+static struct tree *empty_tree(void)
+{
+ return lookup_tree(EMPTY_TREE_SHA1_BIN);
+}
+
+static int error_dirty_index(struct replay_opts *opts)
+{
+ if (read_cache_unmerged())
+ return error_resolve_conflict(action_name(opts));
+
+ /* Different translation strings for cherry-pick and revert */
+ if (opts->action == REPLAY_PICK)
+ error(_("Your local changes would be overwritten by cherry-pick."));
+ else
+ error(_("Your local changes would be overwritten by revert."));
+
+ if (advice_commit_before_merge)
+ advise(_("Commit your changes or stash them to proceed."));
+ return -1;
+}
+
+static int fast_forward_to(const unsigned char *to, const unsigned char *from)
+{
+ struct ref_lock *ref_lock;
+
+ read_cache();
+ if (checkout_fast_forward(from, to))
+ exit(1); /* the callee should have complained already */
+ ref_lock = lock_any_ref_for_update("HEAD", from, 0);
+ return write_ref_sha1(ref_lock, to, "cherry-pick");
+}
+
+static int do_recursive_merge(struct commit *base, struct commit *next,
+ const char *base_label, const char *next_label,
+ unsigned char *head, struct strbuf *msgbuf,
+ struct replay_opts *opts)
+{
+ struct merge_options o;
+ struct tree *result, *next_tree, *base_tree, *head_tree;
+ int clean, index_fd;
+ const char **xopt;
+ static struct lock_file index_lock;
+
+ index_fd = hold_locked_index(&index_lock, 1);
+
+ read_cache();
+
+ init_merge_options(&o);
+ o.ancestor = base ? base_label : "(empty tree)";
+ o.branch1 = "HEAD";
+ o.branch2 = next ? next_label : "(empty tree)";
+
+ head_tree = parse_tree_indirect(head);
+ next_tree = next ? next->tree : empty_tree();
+ base_tree = base ? base->tree : empty_tree();
+
+ for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
+ parse_merge_opt(&o, *xopt);
+
+ clean = merge_trees(&o,
+ head_tree,
+ next_tree, base_tree, &result);
+
+ if (active_cache_changed &&
+ (write_cache(index_fd, active_cache, active_nr) ||
+ commit_locked_index(&index_lock)))
+ /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+ die(_("%s: Unable to write new index file"), action_name(opts));
+ rollback_lock_file(&index_lock);
+
+ if (!clean) {
+ int i;
+ strbuf_addstr(msgbuf, "\nConflicts:\n");
+ for (i = 0; i < active_nr;) {
+ struct cache_entry *ce = active_cache[i++];
+ if (ce_stage(ce)) {
+ strbuf_addch(msgbuf, '\t');
+ strbuf_addstr(msgbuf, ce->name);
+ strbuf_addch(msgbuf, '\n');
+ while (i < active_nr && !strcmp(ce->name,
+ active_cache[i]->name))
+ i++;
+ }
+ }
+ }
+
+ return !clean;
+}
+
+static int is_index_unchanged(void)
+{
+ unsigned char head_sha1[20];
+ struct commit *head_commit;
+
+ if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+ return error(_("Could not resolve HEAD commit\n"));
+
+ head_commit = lookup_commit(head_sha1);
+
+ /*
+ * If head_commit is NULL, check_commit, called from
+ * lookup_commit, would have indicated that head_commit is not
+ * a commit object already. parse_commit() will return failure
+ * without further complaints in such a case. Otherwise, if
+ * the commit is invalid, parse_commit() will complain. So
+ * there is nothing for us to say here. Just return failure.
+ */
+ if (parse_commit(head_commit))
+ return -1;
+
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+
+ if (!cache_tree_fully_valid(active_cache_tree))
+ if (cache_tree_update(active_cache_tree, active_cache,
+ active_nr, 0))
+ return error(_("Unable to update cache tree\n"));
+
+ return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
+}
+
+/*
+ * If we are cherry-pick, and if the merge did not result in
+ * hand-editing, we will hit this commit and inherit the original
+ * author date and name.
+ * If we are revert, or if our cherry-pick results in a hand merge,
+ * we had better say that the current user is responsible for that.
+ */
+static int run_git_commit(const char *defmsg, struct replay_opts *opts,
+ int allow_empty)
+{
+ struct argv_array array;
+ int rc;
+
+ argv_array_init(&array);
+ argv_array_push(&array, "commit");
+ argv_array_push(&array, "-n");
+
+ if (opts->signoff)
+ argv_array_push(&array, "-s");
+ if (!opts->edit) {
+ argv_array_push(&array, "-F");
+ argv_array_push(&array, defmsg);
+ }
+
+ if (allow_empty)
+ argv_array_push(&array, "--allow-empty");
+
+ rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+ argv_array_clear(&array);
+ return rc;
+}
+
+static int is_original_commit_empty(struct commit *commit)
+{
+ const unsigned char *ptree_sha1;
+
+ if (parse_commit(commit))
+ return error(_("Could not parse commit %s\n"),
+ sha1_to_hex(commit->object.sha1));
+ if (commit->parents) {
+ struct commit *parent = commit->parents->item;
+ if (parse_commit(parent))
+ return error(_("Could not parse parent commit %s\n"),
+ sha1_to_hex(parent->object.sha1));
+ ptree_sha1 = parent->tree->object.sha1;
+ } else {
+ ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
+ }
+
+ return !hashcmp(ptree_sha1, commit->tree->object.sha1);
+}
+
+/*
+ * Do we run "git commit" with "--allow-empty"?
+ */
+static int allow_empty(struct replay_opts *opts, struct commit *commit)
+{
+ int index_unchanged, empty_commit;
+
+ /*
+ * Three cases:
+ *
+ * (1) we do not allow empty at all and error out.
+ *
+ * (2) we allow ones that were initially empty, but
+ * forbid the ones that become empty;
+ *
+ * (3) we allow both.
+ */
+ if (!opts->allow_empty)
+ return 0; /* let "git commit" barf as necessary */
+
+ index_unchanged = is_index_unchanged();
+ if (index_unchanged < 0)
+ return index_unchanged;
+ if (!index_unchanged)
+ return 0; /* we do not have to say --allow-empty */
+
+ if (opts->keep_redundant_commits)
+ return 1;
+
+ empty_commit = is_original_commit_empty(commit);
+ if (empty_commit < 0)
+ return empty_commit;
+ if (!empty_commit)
+ return 0;
+ else
+ return 1;
+}
+
+static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
+{
+ unsigned char head[20];
+ struct commit *base, *next, *parent;
+ const char *base_label, *next_label;
+ struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
+ char *defmsg = NULL;
+ struct strbuf msgbuf = STRBUF_INIT;
+ int res;
+
+ if (opts->no_commit) {
+ /*
+ * We do not intend to commit immediately. We just want to
+ * merge the differences in, so let's compute the tree
+ * that represents the "current" state for merge-recursive
+ * to work on.
+ */
+ if (write_cache_as_tree(head, 0, NULL))
+ die (_("Your index file is unmerged."));
+ } else {
+ if (get_sha1("HEAD", head))
+ return error(_("You do not have a valid HEAD"));
+ if (index_differs_from("HEAD", 0))
+ return error_dirty_index(opts);
+ }
+ discard_cache();
+
+ if (!commit->parents) {
+ parent = NULL;
+ }
+ else if (commit->parents->next) {
+ /* Reverting or cherry-picking a merge commit */
+ int cnt;
+ struct commit_list *p;
+
+ if (!opts->mainline)
+ return error(_("Commit %s is a merge but no -m option was given."),
+ sha1_to_hex(commit->object.sha1));
+
+ for (cnt = 1, p = commit->parents;
+ cnt != opts->mainline && p;
+ cnt++)
+ p = p->next;
+ if (cnt != opts->mainline || !p)
+ return error(_("Commit %s does not have parent %d"),
+ sha1_to_hex(commit->object.sha1), opts->mainline);
+ parent = p->item;
+ } else if (0 < opts->mainline)
+ return error(_("Mainline was specified but commit %s is not a merge."),
+ sha1_to_hex(commit->object.sha1));
+ else
+ parent = commit->parents->item;
+
+ if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))
+ return fast_forward_to(commit->object.sha1, head);
+
+ if (parent && parse_commit(parent) < 0)
+ /* TRANSLATORS: The first %s will be "revert" or
+ "cherry-pick", the second %s a SHA1 */
+ return error(_("%s: cannot parse parent commit %s"),
+ action_name(opts), sha1_to_hex(parent->object.sha1));
+
+ if (get_message(commit, &msg) != 0)
+ return error(_("Cannot get commit message for %s"),
+ sha1_to_hex(commit->object.sha1));
+
+ /*
+ * "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.
+ */
+
+ defmsg = git_pathdup("MERGE_MSG");
+
+ if (opts->action == REPLAY_REVERT) {
+ base = commit;
+ base_label = msg.label;
+ next = parent;
+ next_label = msg.parent_label;
+ strbuf_addstr(&msgbuf, "Revert \"");
+ strbuf_addstr(&msgbuf, msg.subject);
+ strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
+ strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
+
+ if (commit->parents && commit->parents->next) {
+ strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
+ strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
+ }
+ strbuf_addstr(&msgbuf, ".\n");
+ } else {
+ const char *p;
+
+ base = parent;
+ base_label = msg.parent_label;
+ next = commit;
+ next_label = msg.label;
+
+ /*
+ * Append the commit log message to msgbuf; it starts
+ * after the tree, parent, author, committer
+ * information followed by "\n\n".
+ */
+ p = strstr(msg.message, "\n\n");
+ if (p) {
+ p += 2;
+ strbuf_addstr(&msgbuf, p);
+ }
+
+ if (opts->record_origin) {
+ strbuf_addstr(&msgbuf, "(cherry picked from commit ");
+ strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
+ strbuf_addstr(&msgbuf, ")\n");
+ }
+ }
+
+ if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
+ res = do_recursive_merge(base, next, base_label, next_label,
+ head, &msgbuf, opts);
+ write_message(&msgbuf, defmsg);
+ } else {
+ struct commit_list *common = NULL;
+ struct commit_list *remotes = NULL;
+
+ write_message(&msgbuf, defmsg);
+
+ commit_list_insert(base, &common);
+ commit_list_insert(next, &remotes);
+ res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
+ common, sha1_to_hex(head), remotes);
+ free_commit_list(common);
+ free_commit_list(remotes);
+ }
+
+ /*
+ * If the merge was clean or if it failed due to conflict, we write
+ * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
+ * However, if the merge did not even start, then we don't want to
+ * write it at all.
+ */
+ if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
+ write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
+ if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
+ write_cherry_pick_head(commit, "REVERT_HEAD");
+
+ if (res) {
+ error(opts->action == REPLAY_REVERT
+ ? _("could not revert %s... %s")
+ : _("could not apply %s... %s"),
+ find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
+ msg.subject);
+ print_advice(res == 1, opts);
+ rerere(opts->allow_rerere_auto);
+ } else {
+ int allow = allow_empty(opts, commit);
+ if (allow < 0)
+ return allow;
+ if (!opts->no_commit)
+ res = run_git_commit(defmsg, opts, allow);
+ }
+
+ free_message(&msg);
+ free(defmsg);
+
+ return res;
+}
+
+static void prepare_revs(struct replay_opts *opts)
+{
+ if (opts->action != REPLAY_REVERT)
+ opts->revs->reverse ^= 1;
+
+ if (prepare_revision_walk(opts->revs))
+ die(_("revision walk setup failed"));
+
+ if (!opts->revs->commits)
+ die(_("empty commit set passed"));
+}
+
+static void read_and_refresh_cache(struct replay_opts *opts)
+{
+ static struct lock_file index_lock;
+ int index_fd = hold_locked_index(&index_lock, 0);
+ if (read_index_preload(&the_index, NULL) < 0)
+ die(_("git %s: failed to read the index"), action_name(opts));
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+ if (the_index.cache_changed) {
+ if (write_index(&the_index, index_fd) ||
+ commit_locked_index(&index_lock))
+ die(_("git %s: failed to refresh the index"), action_name(opts));
+ }
+ rollback_lock_file(&index_lock);
+}
+
+static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
+ struct replay_opts *opts)
+{
+ struct commit_list *cur = NULL;
+ const char *sha1_abbrev = NULL;
+ const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick";
+ const char *subject;
+ int subject_len;
+
+ for (cur = todo_list; cur; cur = cur->next) {
+ sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
+ subject_len = find_commit_subject(cur->item->buffer, &subject);
+ strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
+ subject_len, subject);
+ }
+ return 0;
+}
+
+static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts)
+{
+ unsigned char commit_sha1[20];
+ enum replay_action action;
+ char *end_of_object_name;
+ int saved, status, padding;
+
+ if (!prefixcmp(bol, "pick")) {
+ action = REPLAY_PICK;
+ bol += strlen("pick");
+ } else if (!prefixcmp(bol, "revert")) {
+ action = REPLAY_REVERT;
+ bol += strlen("revert");
+ } else
+ return NULL;
+
+ /* Eat up extra spaces/ tabs before object name */
+ padding = strspn(bol, " \t");
+ if (!padding)
+ return NULL;
+ bol += padding;
+
+ end_of_object_name = bol + strcspn(bol, " \t\n");
+ saved = *end_of_object_name;
+ *end_of_object_name = '\0';
+ status = get_sha1(bol, commit_sha1);
+ *end_of_object_name = saved;
+
+ /*
+ * Verify that the action matches up with the one in
+ * opts; we don't support arbitrary instructions
+ */
+ if (action != opts->action) {
+ const char *action_str;
+ action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick";
+ error(_("Cannot %s during a %s"), action_str, action_name(opts));
+ return NULL;
+ }
+
+ if (status < 0)
+ return NULL;
+
+ return lookup_commit_reference(commit_sha1);
+}
+
+static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
+ struct replay_opts *opts)
+{
+ struct commit_list **next = todo_list;
+ struct commit *commit;
+ char *p = buf;
+ int i;
+
+ for (i = 1; *p; i++) {
+ char *eol = strchrnul(p, '\n');
+ commit = parse_insn_line(p, eol, opts);
+ if (!commit)
+ return error(_("Could not parse line %d."), i);
+ next = commit_list_append(commit, next);
+ p = *eol ? eol + 1 : eol;
+ }
+ if (!*todo_list)
+ return error(_("No commits parsed."));
+ return 0;
+}
+
+static void read_populate_todo(struct commit_list **todo_list,
+ struct replay_opts *opts)
+{
+ const char *todo_file = git_path(SEQ_TODO_FILE);
+ struct strbuf buf = STRBUF_INIT;
+ int fd, res;
+
+ fd = open(todo_file, O_RDONLY);
+ if (fd < 0)
+ die_errno(_("Could not open %s"), todo_file);
+ if (strbuf_read(&buf, fd, 0) < 0) {
+ close(fd);
+ strbuf_release(&buf);
+ die(_("Could not read %s."), todo_file);
+ }
+ close(fd);
+
+ res = parse_insn_buffer(buf.buf, todo_list, opts);
+ strbuf_release(&buf);
+ if (res)
+ die(_("Unusable instruction sheet: %s"), todo_file);
+}
+
+static int populate_opts_cb(const char *key, const char *value, void *data)
+{
+ struct replay_opts *opts = data;
+ int error_flag = 1;
+
+ if (!value)
+ error_flag = 0;
+ else if (!strcmp(key, "options.no-commit"))
+ opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.edit"))
+ opts->edit = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.signoff"))
+ opts->signoff = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.record-origin"))
+ opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.allow-ff"))
+ opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
+ else if (!strcmp(key, "options.mainline"))
+ opts->mainline = git_config_int(key, value);
+ else if (!strcmp(key, "options.strategy"))
+ git_config_string(&opts->strategy, key, value);
+ else if (!strcmp(key, "options.strategy-option")) {
+ ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+ opts->xopts[opts->xopts_nr++] = xstrdup(value);
+ } else
+ return error(_("Invalid key: %s"), key);
+
+ if (!error_flag)
+ return error(_("Invalid value for %s: %s"), key, value);
+
+ return 0;
+}
+
+static void read_populate_opts(struct replay_opts **opts_ptr)
+{
+ const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+ if (!file_exists(opts_file))
+ return;
+ if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
+ die(_("Malformed options sheet: %s"), opts_file);
+}
+
+static void walk_revs_populate_todo(struct commit_list **todo_list,
+ struct replay_opts *opts)
+{
+ struct commit *commit;
+ struct commit_list **next;
+
+ prepare_revs(opts);
+
+ next = todo_list;
+ while ((commit = get_revision(opts->revs)))
+ next = commit_list_append(commit, next);
+}
+
+static int create_seq_dir(void)
+{
+ const char *seq_dir = git_path(SEQ_DIR);
+
+ if (file_exists(seq_dir)) {
+ error(_("a cherry-pick or revert is already in progress"));
+ advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
+ return -1;
+ }
+ else if (mkdir(seq_dir, 0777) < 0)
+ die_errno(_("Could not create sequencer directory %s"), seq_dir);
+ return 0;
+}
+
+static void save_head(const char *head)
+{
+ const char *head_file = git_path(SEQ_HEAD_FILE);
+ static struct lock_file head_lock;
+ struct strbuf buf = STRBUF_INIT;
+ int fd;
+
+ fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
+ strbuf_addf(&buf, "%s\n", head);
+ if (write_in_full(fd, buf.buf, buf.len) < 0)
+ die_errno(_("Could not write to %s"), head_file);
+ if (commit_lock_file(&head_lock) < 0)
+ die(_("Error wrapping up %s."), head_file);
+}
+
+static int reset_for_rollback(const unsigned char *sha1)
+{
+ const char *argv[4]; /* reset --merge <arg> + NULL */
+ argv[0] = "reset";
+ argv[1] = "--merge";
+ argv[2] = sha1_to_hex(sha1);
+ argv[3] = NULL;
+ return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int rollback_single_pick(void)
+{
+ unsigned char head_sha1[20];
+
+ if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+ !file_exists(git_path("REVERT_HEAD")))
+ return error(_("no cherry-pick or revert in progress"));
+ if (read_ref_full("HEAD", head_sha1, 0, NULL))
+ return error(_("cannot resolve HEAD"));
+ if (is_null_sha1(head_sha1))
+ return error(_("cannot abort from a branch yet to be born"));
+ return reset_for_rollback(head_sha1);
+}
+
+static int sequencer_rollback(struct replay_opts *opts)
+{
+ const char *filename;
+ FILE *f;
+ unsigned char sha1[20];
+ struct strbuf buf = STRBUF_INIT;
+
+ filename = git_path(SEQ_HEAD_FILE);
+ f = fopen(filename, "r");
+ if (!f && errno == ENOENT) {
+ /*
+ * There is no multiple-cherry-pick in progress.
+ * If CHERRY_PICK_HEAD or REVERT_HEAD indicates
+ * a single-cherry-pick in progress, abort that.
+ */
+ return rollback_single_pick();
+ }
+ if (!f)
+ return error(_("cannot open %s: %s"), filename,
+ strerror(errno));
+ if (strbuf_getline(&buf, f, '\n')) {
+ error(_("cannot read %s: %s"), filename, ferror(f) ?
+ strerror(errno) : _("unexpected end of file"));
+ fclose(f);
+ goto fail;
+ }
+ fclose(f);
+ if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
+ error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
+ filename);
+ goto fail;
+ }
+ if (reset_for_rollback(sha1))
+ goto fail;
+ remove_sequencer_state();
+ strbuf_release(&buf);
+ return 0;
+fail:
+ strbuf_release(&buf);
+ return -1;
+}
+
+static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
+{
+ const char *todo_file = git_path(SEQ_TODO_FILE);
+ static struct lock_file todo_lock;
+ struct strbuf buf = STRBUF_INIT;
+ int fd;
+
+ fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
+ if (format_todo(&buf, todo_list, opts) < 0)
+ die(_("Could not format %s."), todo_file);
+ if (write_in_full(fd, buf.buf, buf.len) < 0) {
+ strbuf_release(&buf);
+ die_errno(_("Could not write to %s"), todo_file);
+ }
+ if (commit_lock_file(&todo_lock) < 0) {
+ strbuf_release(&buf);
+ die(_("Error wrapping up %s."), todo_file);
+ }
+ strbuf_release(&buf);
+}
+
+static void save_opts(struct replay_opts *opts)
+{
+ const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+ if (opts->no_commit)
+ git_config_set_in_file(opts_file, "options.no-commit", "true");
+ if (opts->edit)
+ git_config_set_in_file(opts_file, "options.edit", "true");
+ if (opts->signoff)
+ git_config_set_in_file(opts_file, "options.signoff", "true");
+ if (opts->record_origin)
+ git_config_set_in_file(opts_file, "options.record-origin", "true");
+ if (opts->allow_ff)
+ git_config_set_in_file(opts_file, "options.allow-ff", "true");
+ if (opts->mainline) {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf, "%d", opts->mainline);
+ git_config_set_in_file(opts_file, "options.mainline", buf.buf);
+ strbuf_release(&buf);
+ }
+ if (opts->strategy)
+ git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
+ if (opts->xopts) {
+ int i;
+ for (i = 0; i < opts->xopts_nr; i++)
+ git_config_set_multivar_in_file(opts_file,
+ "options.strategy-option",
+ opts->xopts[i], "^$", 0);
+ }
+}
+
+static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
+{
+ struct commit_list *cur;
+ int res;
+
+ setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+ if (opts->allow_ff)
+ assert(!(opts->signoff || opts->no_commit ||
+ opts->record_origin || opts->edit));
+ read_and_refresh_cache(opts);
+
+ for (cur = todo_list; cur; cur = cur->next) {
+ save_todo(cur, opts);
+ res = do_pick_commit(cur->item, opts);
+ if (res)
+ return res;
+ }
+
+ /*
+ * Sequence of picks finished successfully; cleanup by
+ * removing the .git/sequencer directory
+ */
+ remove_sequencer_state();
+ return 0;
+}
+
+static int continue_single_pick(void)
+{
+ const char *argv[] = { "commit", NULL };
+
+ if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+ !file_exists(git_path("REVERT_HEAD")))
+ return error(_("no cherry-pick or revert in progress"));
+ return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int sequencer_continue(struct replay_opts *opts)
+{
+ struct commit_list *todo_list = NULL;
+
+ if (!file_exists(git_path(SEQ_TODO_FILE)))
+ return continue_single_pick();
+ read_populate_opts(&opts);
+ read_populate_todo(&todo_list, opts);
+
+ /* Verify that the conflict has been resolved */
+ if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
+ file_exists(git_path("REVERT_HEAD"))) {
+ int ret = continue_single_pick();
+ if (ret)
+ return ret;
+ }
+ if (index_differs_from("HEAD", 0))
+ return error_dirty_index(opts);
+ todo_list = todo_list->next;
+ return pick_commits(todo_list, opts);
+}
+
+static int single_pick(struct commit *cmit, struct replay_opts *opts)
+{
+ setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+ return do_pick_commit(cmit, opts);
+}
+
+int sequencer_pick_revisions(struct replay_opts *opts)
+{
+ struct commit_list *todo_list = NULL;
+ unsigned char sha1[20];
+
+ if (opts->subcommand == REPLAY_NONE)
+ assert(opts->revs);
+
+ read_and_refresh_cache(opts);
+
+ /*
+ * Decide what to do depending on the arguments; a fresh
+ * cherry-pick should be handled differently from an existing
+ * one that is being continued
+ */
+ if (opts->subcommand == REPLAY_REMOVE_STATE) {
+ remove_sequencer_state();
+ return 0;
+ }
+ if (opts->subcommand == REPLAY_ROLLBACK)
+ return sequencer_rollback(opts);
+ if (opts->subcommand == REPLAY_CONTINUE)
+ return sequencer_continue(opts);
+
+ /*
+ * If we were called as "git cherry-pick <commit>", just
+ * cherry-pick/revert it, set CHERRY_PICK_HEAD /
+ * REVERT_HEAD, and don't touch the sequencer state.
+ * This means it is possible to cherry-pick in the middle
+ * of a cherry-pick sequence.
+ */
+ if (opts->revs->cmdline.nr == 1 &&
+ opts->revs->cmdline.rev->whence == REV_CMD_REV &&
+ opts->revs->no_walk &&
+ !opts->revs->cmdline.rev->flags) {
+ struct commit *cmit;
+ if (prepare_revision_walk(opts->revs))
+ die(_("revision walk setup failed"));
+ cmit = get_revision(opts->revs);
+ if (!cmit || get_revision(opts->revs))
+ die("BUG: expected exactly one commit from walk");
+ return single_pick(cmit, opts);
+ }
+
+ /*
+ * Start a new cherry-pick/ revert sequence; but
+ * first, make sure that an existing one isn't in
+ * progress
+ */
+
+ walk_revs_populate_todo(&todo_list, opts);
+ if (create_seq_dir() < 0)
+ return -1;
+ if (get_sha1("HEAD", sha1)) {
+ if (opts->action == REPLAY_REVERT)
+ return error(_("Can't revert as initial commit"));
+ return error(_("Can't cherry-pick into empty head"));
+ }
+ save_head(sha1_to_hex(sha1));
+ save_opts(opts);
+ return pick_commits(todo_list, opts);
+}
diff --git a/sequencer.h b/sequencer.h
new file mode 100644
index 0000000..aa5f17c
--- /dev/null
+++ b/sequencer.h
@@ -0,0 +1,51 @@
+#ifndef SEQUENCER_H
+#define SEQUENCER_H
+
+#define SEQ_DIR "sequencer"
+#define SEQ_HEAD_FILE "sequencer/head"
+#define SEQ_TODO_FILE "sequencer/todo"
+#define SEQ_OPTS_FILE "sequencer/opts"
+
+enum replay_action {
+ REPLAY_REVERT,
+ REPLAY_PICK
+};
+
+enum replay_subcommand {
+ REPLAY_NONE,
+ REPLAY_REMOVE_STATE,
+ REPLAY_CONTINUE,
+ REPLAY_ROLLBACK
+};
+
+struct replay_opts {
+ enum replay_action action;
+ enum replay_subcommand subcommand;
+
+ /* Boolean options */
+ int edit;
+ int record_origin;
+ int no_commit;
+ int signoff;
+ int allow_ff;
+ int allow_rerere_auto;
+ int allow_empty;
+ int keep_redundant_commits;
+
+ int mainline;
+
+ /* Merge strategy */
+ const char *strategy;
+ const char **xopts;
+ size_t xopts_nr, xopts_alloc;
+
+ /* Only used by REPLAY_NONE */
+ struct rev_info *revs;
+};
+
+/* Removes SEQ_DIR. */
+extern void remove_sequencer_state(void);
+
+int sequencer_pick_revisions(struct replay_opts *opts);
+
+#endif
diff --git a/setup.c b/setup.c
index 1b06db5..e114977 100644
--- a/setup.c
+++ b/setup.c
@@ -4,7 +4,7 @@
static int inside_git_dir = -1;
static int inside_work_tree = -1;
-char *prefix_path(const char *prefix, int len, const char *path)
+static char *prefix_path_gently(const char *prefix, int len, const char *path)
{
const char *orig = path;
char *sanitized;
@@ -31,7 +31,8 @@ char *prefix_path(const char *prefix, int len, const char *path)
if (strncmp(sanitized, work_tree, len) ||
(len > root_len && sanitized[len] != '\0' && sanitized[len] != '/')) {
error_out:
- die("'%s' is outside repository", orig);
+ free(sanitized);
+ return NULL;
}
if (sanitized[len] == '/')
len++;
@@ -40,32 +41,23 @@ char *prefix_path(const char *prefix, int len, const char *path)
return sanitized;
}
-/*
- * Unlike prefix_path, this should be used if the named file does
- * not have to interact with index entry; i.e. name of a random file
- * on the filesystem.
- */
-const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
+char *prefix_path(const char *prefix, int len, const char *path)
{
- static char path[PATH_MAX];
-#ifndef WIN32
- if (!pfx_len || is_absolute_path(arg))
- return arg;
- memcpy(path, pfx, pfx_len);
- strcpy(path + pfx_len, arg);
-#else
- char *p;
- /* don't add prefix to absolute paths, but still replace '\' by '/' */
- if (is_absolute_path(arg))
- pfx_len = 0;
- else if (pfx_len)
- memcpy(path, pfx, pfx_len);
- strcpy(path + pfx_len, arg);
- for (p = path + pfx_len; *p; p++)
- if (*p == '\\')
- *p = '/';
-#endif
- return path;
+ char *r = prefix_path_gently(prefix, len, path);
+ if (!r)
+ die("'%s' is outside repository", path);
+ return r;
+}
+
+int path_inside_repo(const char *prefix, const char *path)
+{
+ int len = prefix ? strlen(prefix) : 0;
+ char *r = prefix_path_gently(prefix, len, path);
+ if (r) {
+ free(r);
+ return 1;
+ }
+ return 0;
}
int check_filename(const char *prefix, const char *arg)
@@ -81,11 +73,17 @@ int check_filename(const char *prefix, const char *arg)
die_errno("failed to stat '%s'", arg);
}
-static void NORETURN die_verify_filename(const char *prefix, const char *arg)
+static void NORETURN die_verify_filename(const char *prefix,
+ const char *arg,
+ int diagnose_misspelt_rev)
{
unsigned char sha1[20];
unsigned mode;
+ if (!diagnose_misspelt_rev)
+ die("%s: no such path in the working tree.\n"
+ "Use '-- <path>...' to specify paths that do not exist locally.",
+ arg);
/*
* Saying "'(icase)foo' does not exist in the index" when the
* user gave us ":(icase)foo" is just stupid. A magic pathspec
@@ -108,14 +106,29 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg)
* as true, because even if such a filename were to exist, we want
* it to be preceded by the "--" marker (or we want the user to
* use a format like "./-filename")
+ *
+ * The "diagnose_misspelt_rev" is used to provide a user-friendly
+ * diagnosis when dying upon finding that "name" is not a pathname.
+ * If set to 1, the diagnosis will try to diagnose "name" as an
+ * invalid object name (e.g. HEAD:foo). If set to 0, the diagnosis
+ * will only complain about an inexisting file.
+ *
+ * This function is typically called to check that a "file or rev"
+ * argument is unambiguous. In this case, the caller will want
+ * diagnose_misspelt_rev == 1 when verifying the first non-rev
+ * argument (which could have been a revision), and
+ * diagnose_misspelt_rev == 0 for the next ones (because we already
+ * saw a filename, there's not ambiguity anymore).
*/
-void verify_filename(const char *prefix, const char *arg)
+void verify_filename(const char *prefix,
+ const char *arg,
+ int diagnose_misspelt_rev)
{
if (*arg == '-')
die("bad flag '%s' used after filename", arg);
if (check_filename(prefix, arg))
return;
- die_verify_filename(prefix, arg);
+ die_verify_filename(prefix, arg, diagnose_misspelt_rev);
}
/*
@@ -275,7 +288,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
* a proper "ref:", or a regular file HEAD that has a properly
* formatted sha1 object name.
*/
-static int is_git_directory(const char *suspect)
+int is_git_directory(const char *suspect)
{
char path[PATH_MAX];
size_t len = strlen(suspect);
@@ -597,13 +610,15 @@ static const char *setup_nongit(const char *cwd, int *nongit_ok)
return NULL;
}
-static dev_t get_device_or_die(const char *path, const char *prefix)
+static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_len)
{
struct stat buf;
- if (stat(path, &buf))
- die_errno("failed to stat '%s%s%s'",
+ if (stat(path, &buf)) {
+ die_errno("failed to stat '%*s%s%s'",
+ prefix_len,
prefix ? prefix : "",
prefix ? "/" : "", path);
+ }
return buf.st_dev;
}
@@ -617,7 +632,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
static char cwd[PATH_MAX+1];
const char *gitdirenv, *ret;
char *gitfile;
- int len, offset, ceil_offset;
+ int len, offset, offset_parent, ceil_offset;
dev_t current_device = 0;
int one_filesystem = 1;
@@ -659,7 +674,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
*/
one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
if (one_filesystem)
- current_device = get_device_or_die(".", NULL);
+ current_device = get_device_or_die(".", NULL, 0);
for (;;) {
gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
if (gitfile)
@@ -681,11 +696,12 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
if (is_git_directory("."))
return setup_bare_git_dir(cwd, offset, len, nongit_ok);
- while (--offset > ceil_offset && cwd[offset] != '/');
- if (offset <= ceil_offset)
+ offset_parent = offset;
+ while (--offset_parent > ceil_offset && cwd[offset_parent] != '/');
+ if (offset_parent <= ceil_offset)
return setup_nongit(cwd, nongit_ok);
if (one_filesystem) {
- dev_t parent_device = get_device_or_die("..", cwd);
+ dev_t parent_device = get_device_or_die("..", cwd, offset);
if (parent_device != current_device) {
if (nongit_ok) {
if (chdir(cwd))
@@ -694,7 +710,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
return NULL;
}
cwd[offset] = '\0';
- die("Not a git repository (or any parent up to mount parent %s)\n"
+ die("Not a git repository (or any parent up to mount point %s)\n"
"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd);
}
}
@@ -702,6 +718,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
cwd[offset] = '\0';
die_errno("Cannot change to '%s/..'", cwd);
}
+ offset = offset_parent;
}
}
@@ -710,6 +727,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
const char *prefix;
prefix = setup_git_directory_gently_1(nongit_ok);
+ if (prefix)
+ setenv("GIT_PREFIX", prefix, 1);
+ else
+ setenv("GIT_PREFIX", "", 1);
+
if (startup_info) {
startup_info->have_repository = !nongit_ok || !*nongit_ok;
startup_info->prefix = prefix;
@@ -803,3 +825,10 @@ const char *setup_git_directory(void)
{
return setup_git_directory_gently(NULL);
}
+
+const char *resolve_gitdir(const char *suspect)
+{
+ if (is_git_directory(suspect))
+ return suspect;
+ return read_gitfile(suspect);
+}
diff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c
index 9d2e971..5ddd688 100644
--- a/sh-i18n--envsubst.c
+++ b/sh-i18n--envsubst.c
@@ -73,6 +73,7 @@ main (int argc, char *argv[])
{
case 1:
error ("we won't substitute all variables on stdin for you");
+ break;
/*
all_variables = 1;
subst_from_stdin ();
diff --git a/sha1_file.c b/sha1_file.c
index 32268d1..4ccaf7a 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -18,6 +18,8 @@
#include "refs.h"
#include "pack-revindex.h"
#include "sha1-lookup.h"
+#include "bulk-checkin.h"
+#include "streaming.h"
#ifndef O_NOATIME
#if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -53,6 +55,8 @@ static struct cached_object empty_tree = {
0
};
+static struct packed_git *last_found_pack;
+
static struct cached_object *find_cached_object(const unsigned char *sha1)
{
int i;
@@ -225,7 +229,6 @@ char *sha1_pack_index_name(const unsigned char *sha1)
struct alternate_object_database *alt_odb_list;
static struct alternate_object_database **alt_odb_tail;
-static void read_info_alternates(const char * alternates, int depth);
static int git_open_noatime(const char *name);
/*
@@ -248,27 +251,30 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative
const char *objdir = get_object_directory();
struct alternate_object_database *ent;
struct alternate_object_database *alt;
- /* 43 = 40-byte + 2 '/' + terminating NUL */
- int pfxlen = len;
- int entlen = pfxlen + 43;
- int base_len = -1;
+ int pfxlen, entlen;
+ struct strbuf pathbuf = STRBUF_INIT;
if (!is_absolute_path(entry) && relative_base) {
- /* Relative alt-odb */
- if (base_len < 0)
- base_len = strlen(relative_base) + 1;
- entlen += base_len;
- pfxlen += base_len;
+ strbuf_addstr(&pathbuf, real_path(relative_base));
+ strbuf_addch(&pathbuf, '/');
}
- ent = xmalloc(sizeof(*ent) + entlen);
+ strbuf_add(&pathbuf, entry, len);
- 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);
- }
- else
- memcpy(ent->base, entry, pfxlen);
+ normalize_path_copy(pathbuf.buf, pathbuf.buf);
+
+ pfxlen = strlen(pathbuf.buf);
+
+ /*
+ * The trailing slash after the directory name is given by
+ * this function at the end. Remove duplicates.
+ */
+ while (pfxlen && pathbuf.buf[pfxlen-1] == '/')
+ pfxlen -= 1;
+
+ entlen = pfxlen + 43; /* '/' + 2 hex + '/' + 38 hex + NUL */
+ ent = xmalloc(sizeof(*ent) + entlen);
+ memcpy(ent->base, pathbuf.buf, pfxlen);
+ strbuf_release(&pathbuf);
ent->name = ent->base + pfxlen + 1;
ent->base[pfxlen + 3] = '/';
@@ -347,7 +353,7 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
}
}
-static void read_info_alternates(const char * relative_base, int depth)
+void read_info_alternates(const char * relative_base, int depth)
{
char *map;
size_t mapsz;
@@ -716,6 +722,8 @@ void free_pack_by_name(const char *pack_name)
close_pack_index(p);
free(p->bad_object_sha1);
*pp = p->next;
+ if (last_found_pack == p)
+ last_found_pack = NULL;
free(p);
return;
}
@@ -1138,10 +1146,47 @@ static const struct packed_git *has_packed_and_bad(const unsigned char *sha1)
return NULL;
}
-int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
+/*
+ * With an in-core object data in "map", rehash it to make sure the
+ * object name actually matches "sha1" to detect object corruption.
+ * With "map" == NULL, try reading the object named with "sha1" using
+ * the streaming interface and rehash it to do the same.
+ */
+int check_sha1_signature(const unsigned char *sha1, void *map,
+ unsigned long size, const char *type)
{
unsigned char real_sha1[20];
- hash_sha1_file(map, size, type, real_sha1);
+ enum object_type obj_type;
+ struct git_istream *st;
+ git_SHA_CTX c;
+ char hdr[32];
+ int hdrlen;
+
+ if (map) {
+ hash_sha1_file(map, size, type, real_sha1);
+ return hashcmp(sha1, real_sha1) ? -1 : 0;
+ }
+
+ st = open_istream(sha1, &obj_type, &size, NULL);
+ if (!st)
+ return -1;
+
+ /* Generate the header */
+ hdrlen = sprintf(hdr, "%s %lu", typename(obj_type), size) + 1;
+
+ /* Sha1.. */
+ git_SHA1_Init(&c);
+ git_SHA1_Update(&c, hdr, hdrlen);
+ for (;;) {
+ char buf[1024 * 16];
+ ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+ if (!readlen)
+ break;
+ git_SHA1_Update(&c, buf, readlen);
+ }
+ git_SHA1_Final(real_sha1, &c);
+ close_istream(st);
return hashcmp(sha1, real_sha1) ? -1 : 0;
}
@@ -1186,7 +1231,7 @@ static int open_sha1_file(const unsigned char *sha1)
return -1;
}
-static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
{
void *map;
int fd;
@@ -1198,6 +1243,11 @@ static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
if (!fstat(fd, &st)) {
*size = xsize_t(st.st_size);
+ if (!*size) {
+ /* mmap() is forbidden on empty files */
+ error("object file %s is empty", sha1_file_name(sha1));
+ return NULL;
+ }
map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
}
close(fd);
@@ -1217,14 +1267,34 @@ static int experimental_loose_object(unsigned char *map)
unsigned int word;
/*
- * Is it a zlib-compressed buffer? If so, the first byte
- * must be 0x78 (15-bit window size, deflated), and the
- * first 16-bit word is evenly divisible by 31. If so,
- * we are looking at the official format, not the experimental
- * one.
+ * We must determine if the buffer contains the standard
+ * zlib-deflated stream or the experimental format based
+ * on the in-pack object format. Compare the header byte
+ * for each format:
+ *
+ * RFC1950 zlib w/ deflate : 0www1000 : 0 <= www <= 7
+ * Experimental pack-based : Stttssss : ttt = 1,2,3,4
+ *
+ * If bit 7 is clear and bits 0-3 equal 8, the buffer MUST be
+ * in standard loose-object format, UNLESS it is a Git-pack
+ * format object *exactly* 8 bytes in size when inflated.
+ *
+ * However, RFC1950 also specifies that the 1st 16-bit word
+ * must be divisible by 31 - this checksum tells us our buffer
+ * is in the standard format, giving a false positive only if
+ * the 1st word of the Git-pack format object happens to be
+ * divisible by 31, ie:
+ * ((byte0 * 256) + byte1) % 31 = 0
+ * => 0ttt10000www1000 % 31 = 0
+ *
+ * As it happens, this case can only arise for www=3 & ttt=1
+ * - ie, a Commit object, which would have to be 8 bytes in
+ * size. As no Commit can be that small, we find that the
+ * combination of these two criteria (bitmask & checksum)
+ * can always correctly determine the buffer format.
*/
word = (map[0] << 8) + map[1];
- if (map[0] == 0x78 && !(word % 31))
+ if ((map[0] & 0x8F) == 0x08 && !(word % 31))
return 0;
else
return 1;
@@ -1244,7 +1314,8 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
while (c & 0x80) {
if (len <= used || bitsizeof(long) <= shift) {
error("bad object header");
- return 0;
+ size = used = 0;
+ break;
}
c = buf[used++];
size += (c & 0x7f) << shift;
@@ -1254,7 +1325,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
return used;
}
-static int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
+int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
{
unsigned long size, used;
static const char valid_loose_object_type[8] = {
@@ -1346,7 +1417,7 @@ static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long s
* too permissive for what we want to check. So do an anal
* object header parse by hand.
*/
-static int parse_sha1_header(const char *hdr, unsigned long *sizep)
+int parse_sha1_header(const char *hdr, unsigned long *sizep)
{
char type[10];
int i;
@@ -1485,7 +1556,7 @@ static off_t get_delta_base(struct packed_git *p,
/* forward declaration for a mutually recursive function */
static int packed_object_info(struct packed_git *p, off_t offset,
- unsigned long *sizep);
+ unsigned long *sizep, int *rtype);
static int packed_delta_info(struct packed_git *p,
struct pack_window **w_curs,
@@ -1499,7 +1570,7 @@ static int packed_delta_info(struct packed_git *p,
base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
if (!base_offset)
return OBJ_BAD;
- type = packed_object_info(p, base_offset, NULL);
+ type = packed_object_info(p, base_offset, NULL, NULL);
if (type <= OBJ_NONE) {
struct revindex_entry *revidx;
const unsigned char *base_sha1;
@@ -1527,10 +1598,10 @@ static int packed_delta_info(struct packed_git *p,
return type;
}
-static int unpack_object_header(struct packed_git *p,
- struct pack_window **w_curs,
- off_t *curpos,
- unsigned long *sizep)
+int unpack_object_header(struct packed_git *p,
+ struct pack_window **w_curs,
+ off_t *curpos,
+ unsigned long *sizep)
{
unsigned char *base;
unsigned long left;
@@ -1553,63 +1624,8 @@ static int unpack_object_header(struct packed_git *p,
return type;
}
-const char *packed_object_info_detail(struct packed_git *p,
- off_t obj_offset,
- unsigned long *size,
- unsigned long *store_size,
- unsigned int *delta_chain_length,
- unsigned char *base_sha1)
-{
- struct pack_window *w_curs = NULL;
- off_t curpos;
- unsigned long dummy;
- unsigned char *next_sha1;
- enum object_type type;
- struct revindex_entry *revidx;
-
- *delta_chain_length = 0;
- curpos = obj_offset;
- type = unpack_object_header(p, &w_curs, &curpos, size);
-
- revidx = find_pack_revindex(p, obj_offset);
- *store_size = revidx[1].offset - obj_offset;
-
- for (;;) {
- switch (type) {
- default:
- die("pack %s contains unknown object type %d",
- p->pack_name, type);
- case OBJ_COMMIT:
- case OBJ_TREE:
- case OBJ_BLOB:
- case OBJ_TAG:
- unuse_pack(&w_curs);
- return typename(type);
- case OBJ_OFS_DELTA:
- obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
- if (!obj_offset)
- die("pack %s contains bad delta base reference of type %s",
- p->pack_name, typename(type));
- if (*delta_chain_length == 0) {
- revidx = find_pack_revindex(p, obj_offset);
- hashcpy(base_sha1, nth_packed_object_sha1(p, revidx->nr));
- }
- break;
- case OBJ_REF_DELTA:
- next_sha1 = use_pack(p, &w_curs, curpos, NULL);
- if (*delta_chain_length == 0)
- hashcpy(base_sha1, next_sha1);
- obj_offset = find_pack_entry_one(next_sha1, p);
- break;
- }
- (*delta_chain_length)++;
- curpos = obj_offset;
- type = unpack_object_header(p, &w_curs, &curpos, &dummy);
- }
-}
-
static int packed_object_info(struct packed_git *p, off_t obj_offset,
- unsigned long *sizep)
+ unsigned long *sizep, int *rtype)
{
struct pack_window *w_curs = NULL;
unsigned long size;
@@ -1617,6 +1633,8 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset,
enum object_type type;
type = unpack_object_header(p, &w_curs, &curpos, &size);
+ if (rtype)
+ *rtype = type; /* representation type */
switch (type) {
case OBJ_OFS_DELTA:
@@ -1699,6 +1717,13 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
return hash % MAX_DELTA_CACHE;
}
+static int in_delta_base_cache(struct packed_git *p, off_t base_offset)
+{
+ unsigned long hash = pack_entry_hash(p, base_offset);
+ struct delta_base_cache_entry *ent = delta_base_cache + hash;
+ return (ent->data && ent->p == p && ent->base_offset == base_offset);
+}
+
static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
unsigned long *base_size, enum object_type *type, int keep_cache)
{
@@ -1843,6 +1868,24 @@ static void *unpack_delta_entry(struct packed_git *p,
return result;
}
+static void write_pack_access_log(struct packed_git *p, off_t obj_offset)
+{
+ static FILE *log_file;
+
+ if (!log_file) {
+ log_file = fopen(log_pack_access, "w");
+ if (!log_file) {
+ error("cannot open pack access log '%s' for writing: %s",
+ log_pack_access, strerror(errno));
+ log_pack_access = NULL;
+ return;
+ }
+ }
+ fprintf(log_file, "%s %"PRIuMAX"\n",
+ p->pack_name, (uintmax_t)obj_offset);
+ fflush(log_file);
+}
+
int do_check_packed_object_crc;
void *unpack_entry(struct packed_git *p, off_t obj_offset,
@@ -1852,6 +1895,9 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
off_t curpos = obj_offset;
void *data;
+ if (log_pack_access)
+ write_pack_access_log(p, obj_offset);
+
if (do_check_packed_object_crc && p->index_version > 1) {
struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
unsigned long len = revidx[1].offset - obj_offset;
@@ -1989,7 +2035,7 @@ off_t find_pack_entry_one(const unsigned char *sha1,
return 0;
}
-static int is_pack_valid(struct packed_git *p)
+int is_pack_valid(struct packed_git *p)
{
/* An already open pack is known to be valid. */
if (p->pack_fd != -1)
@@ -2010,54 +2056,58 @@ static int is_pack_valid(struct packed_git *p)
return !open_packed_git(p);
}
+static int fill_pack_entry(const unsigned char *sha1,
+ struct pack_entry *e,
+ struct packed_git *p)
+{
+ off_t offset;
+
+ if (p->num_bad_objects) {
+ unsigned i;
+ for (i = 0; i < p->num_bad_objects; i++)
+ if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
+ return 0;
+ }
+
+ offset = find_pack_entry_one(sha1, p);
+ if (!offset)
+ return 0;
+
+ /*
+ * We are about to tell the caller where they can locate the
+ * requested object. We better make sure the packfile is
+ * still here and can be accessed before supplying that
+ * answer, as it may have been deleted since the index was
+ * loaded!
+ */
+ if (!is_pack_valid(p)) {
+ warning("packfile %s cannot be accessed", p->pack_name);
+ return 0;
+ }
+ e->offset = offset;
+ e->p = p;
+ hashcpy(e->sha1, sha1);
+ return 1;
+}
+
static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
{
- static struct packed_git *last_found = (void *)1;
struct packed_git *p;
- off_t offset;
prepare_packed_git();
if (!packed_git)
return 0;
- p = (last_found == (void *)1) ? packed_git : last_found;
- do {
- if (p->num_bad_objects) {
- unsigned i;
- for (i = 0; i < p->num_bad_objects; i++)
- if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
- goto next;
- }
+ if (last_found_pack && fill_pack_entry(sha1, e, last_found_pack))
+ return 1;
- offset = find_pack_entry_one(sha1, p);
- if (offset) {
- /*
- * We are about to tell the caller where they can
- * locate the requested object. We better make
- * sure the packfile is still here and can be
- * accessed before supplying that answer, as
- * it may have been deleted since the index
- * was loaded!
- */
- if (!is_pack_valid(p)) {
- error("packfile %s cannot be accessed", p->pack_name);
- goto next;
- }
- e->offset = offset;
- e->p = p;
- hashcpy(e->sha1, sha1);
- last_found = p;
- return 1;
- }
+ for (p = packed_git; p; p = p->next) {
+ if (p == last_found_pack || !fill_pack_entry(sha1, e, p))
+ continue;
- next:
- if (p == last_found)
- p = packed_git;
- else
- p = p->next;
- if (p == last_found)
- p = p->next;
- } while (p);
+ last_found_pack = p;
+ return 1;
+ }
return 0;
}
@@ -2097,24 +2147,28 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size
return status;
}
-int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
+/* returns enum object_type or negative */
+int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
{
struct cached_object *co;
struct pack_entry e;
- int status;
+ int status, rtype;
co = find_cached_object(sha1);
if (co) {
- if (sizep)
- *sizep = co->size;
+ if (oi->sizep)
+ *(oi->sizep) = co->size;
+ oi->whence = OI_CACHED;
return co->type;
}
if (!find_pack_entry(sha1, &e)) {
/* Most likely it's a loose object. */
- status = sha1_loose_object_info(sha1, sizep);
- if (status >= 0)
+ status = sha1_loose_object_info(sha1, oi->sizep);
+ if (status >= 0) {
+ oi->whence = OI_LOOSE;
return status;
+ }
/* Not a loose object; someone else may have just packed it. */
reprepare_packed_git();
@@ -2122,15 +2176,31 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
return status;
}
- status = packed_object_info(e.p, e.offset, sizep);
+ status = packed_object_info(e.p, e.offset, oi->sizep, &rtype);
if (status < 0) {
mark_bad_packed_object(e.p, sha1);
- status = sha1_object_info(sha1, sizep);
+ status = sha1_object_info_extended(sha1, oi);
+ } else if (in_delta_base_cache(e.p, e.offset)) {
+ oi->whence = OI_DBCACHED;
+ } else {
+ oi->whence = OI_PACKED;
+ oi->u.packed.offset = e.offset;
+ oi->u.packed.pack = e.p;
+ oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
+ rtype == OBJ_OFS_DELTA);
}
return status;
}
+int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
+{
+ struct object_info oi;
+
+ oi.sizep = sizep;
+ return sha1_object_info_extended(sha1, &oi);
+}
+
static void *read_packed_sha1(const unsigned char *sha1,
enum object_type *type, unsigned long *size)
{
@@ -2346,7 +2416,7 @@ int move_temp_to_file(const char *tmpfile, const char *filename)
unlink_or_warn(tmpfile);
if (ret) {
if (ret != EEXIST) {
- return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret));
+ return error("unable to write sha1 filename %s: %s", filename, strerror(ret));
}
/* FIXME!!! Collision check here ? */
}
@@ -2432,15 +2502,15 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
git_SHA_CTX c;
unsigned char parano_sha1[20];
char *filename;
- static char tmpfile[PATH_MAX];
+ static char tmp_file[PATH_MAX];
filename = sha1_file_name(sha1);
- fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
+ fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
if (fd < 0) {
if (errno == EACCES)
- return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
+ return error("insufficient permission for adding an object to repository database %s", get_object_directory());
else
- return error("unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno));
+ return error("unable to create temporary file: %s", strerror(errno));
}
/* Set it up */
@@ -2485,12 +2555,12 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
struct utimbuf utb;
utb.actime = mtime;
utb.modtime = mtime;
- if (utime(tmpfile, &utb) < 0)
+ if (utime(tmp_file, &utb) < 0)
warning("failed utime() on %s: %s",
- tmpfile, strerror(errno));
+ tmp_file, strerror(errno));
}
- return move_temp_to_file(tmpfile, filename);
+ return move_temp_to_file(tmp_file, filename);
}
int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
@@ -2598,7 +2668,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
if ((type == OBJ_BLOB) && path) {
struct strbuf nbuf = STRBUF_INIT;
if (convert_to_git(path, buf, size, &nbuf,
- write_object ? safe_crlf : 0)) {
+ write_object ? safe_crlf : SAFE_CRLF_FALSE)) {
buf = strbuf_detach(&nbuf, &size);
re_allocated = 1;
}
@@ -2661,82 +2731,25 @@ static int index_core(unsigned char *sha1, int fd, size_t size,
}
/*
- * This creates one packfile per large blob, because the caller
- * immediately wants the result sha1, and fast-import can report the
- * object name via marks mechanism only by closing the created
- * packfile.
+ * This creates one packfile per large blob unless bulk-checkin
+ * machinery is "plugged".
*
* This also bypasses the usual "convert-to-git" dance, and that is on
* purpose. We could write a streaming version of the converting
* functions and insert that before feeding the data to fast-import
- * (or equivalent in-core API described above), but the primary
- * motivation for trying to stream from the working tree file and to
- * avoid mmaping it in core is to deal with large binary blobs, and
- * by definition they do _not_ want to get any conversion.
+ * (or equivalent in-core API described above). However, that is
+ * somewhat complicated, as we do not know the size of the filter
+ * result, which we need to know beforehand when writing a git object.
+ * Since the primary motivation for trying to stream from the working
+ * tree file and to avoid mmaping it in core is to deal with large
+ * binary blobs, they generally do not want to get any conversion, and
+ * callers should avoid this code path when filters are requested.
*/
static int index_stream(unsigned char *sha1, int fd, size_t size,
enum object_type type, const char *path,
unsigned flags)
{
- struct child_process fast_import;
- char export_marks[512];
- const char *argv[] = { "fast-import", "--quiet", export_marks, NULL };
- char tmpfile[512];
- char fast_import_cmd[512];
- char buf[512];
- int len, tmpfd;
-
- strcpy(tmpfile, git_path("hashstream_XXXXXX"));
- tmpfd = git_mkstemp_mode(tmpfile, 0600);
- if (tmpfd < 0)
- die_errno("cannot create tempfile: %s", tmpfile);
- if (close(tmpfd))
- die_errno("cannot close tempfile: %s", tmpfile);
- sprintf(export_marks, "--export-marks=%s", tmpfile);
-
- memset(&fast_import, 0, sizeof(fast_import));
- fast_import.in = -1;
- fast_import.argv = argv;
- fast_import.git_cmd = 1;
- if (start_command(&fast_import))
- die_errno("index-stream: git fast-import failed");
-
- len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n",
- (unsigned long) size);
- write_or_whine(fast_import.in, fast_import_cmd, len,
- "index-stream: feeding fast-import");
- while (size) {
- char buf[10240];
- size_t sz = size < sizeof(buf) ? size : sizeof(buf);
- size_t actual;
-
- actual = read_in_full(fd, buf, sz);
- if (actual < 0)
- die_errno("index-stream: reading input");
- if (write_in_full(fast_import.in, buf, actual) != actual)
- die_errno("index-stream: feeding fast-import");
- size -= actual;
- }
- if (close(fast_import.in))
- die_errno("index-stream: closing fast-import");
- if (finish_command(&fast_import))
- die_errno("index-stream: finishing fast-import");
-
- tmpfd = open(tmpfile, O_RDONLY);
- if (tmpfd < 0)
- die_errno("index-stream: cannot open fast-import mark");
- len = read(tmpfd, buf, sizeof(buf));
- if (len < 0)
- die_errno("index-stream: reading fast-import mark");
- if (close(tmpfd) < 0)
- die_errno("index-stream: closing fast-import mark");
- if (unlink(tmpfile))
- die_errno("index-stream: unlinking fast-import mark");
- if (len != 44 ||
- memcmp(":1 ", buf, 3) ||
- get_sha1_hex(buf + 3, sha1))
- die_errno("index-stream: unexpected fast-import mark: <%s>", buf);
- return 0;
+ return index_bulk_checkin(sha1, fd, size, type, path, flags);
}
int index_fd(unsigned char *sha1, int fd, struct stat *st,
@@ -2747,7 +2760,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st,
if (!S_ISREG(st->st_mode))
ret = index_pipe(sha1, fd, type, path, flags);
- else if (size <= big_file_threshold || type != OBJ_BLOB)
+ else if (size <= big_file_threshold || type != OBJ_BLOB ||
+ (path && would_convert_to_git(path, NULL, 0, 0)))
ret = index_core(sha1, fd, size, type, path, flags);
else
ret = index_stream(sha1, fd, size, type, path, flags);
diff --git a/sha1_name.c b/sha1_name.c
index ff5992a..5d81ea0 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -241,91 +241,6 @@ static int ambiguous_path(const char *path, int len)
return slash;
}
-/*
- * *string and *len will only be substituted, and *string returned (for
- * later free()ing) if the string passed in is a magic short-hand form
- * to name a branch.
- */
-static char *substitute_branch_name(const char **string, int *len)
-{
- struct strbuf buf = STRBUF_INIT;
- int ret = interpret_branch_name(*string, &buf);
-
- if (ret == *len) {
- size_t size;
- *string = strbuf_detach(&buf, &size);
- *len = size;
- return (char *)*string;
- }
-
- return NULL;
-}
-
-int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
-{
- char *last_branch = substitute_branch_name(&str, &len);
- const char **p, *r;
- int refs_found = 0;
-
- *ref = NULL;
- for (p = ref_rev_parse_rules; *p; p++) {
- char fullref[PATH_MAX];
- unsigned char sha1_from_ref[20];
- unsigned char *this_result;
- int flag;
-
- this_result = refs_found ? sha1_from_ref : sha1;
- mksnpath(fullref, sizeof(fullref), *p, len, str);
- r = resolve_ref(fullref, this_result, 1, &flag);
- if (r) {
- if (!refs_found++)
- *ref = xstrdup(r);
- if (!warn_ambiguous_refs)
- break;
- } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD"))
- warning("ignoring dangling symref %s.", fullref);
- }
- free(last_branch);
- return refs_found;
-}
-
-int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
-{
- char *last_branch = substitute_branch_name(&str, &len);
- const char **p;
- int logs_found = 0;
-
- *log = NULL;
- for (p = ref_rev_parse_rules; *p; p++) {
- struct stat st;
- unsigned char hash[20];
- char path[PATH_MAX];
- const char *ref, *it;
-
- mksnpath(path, sizeof(path), *p, len, str);
- ref = resolve_ref(path, hash, 1, NULL);
- if (!ref)
- continue;
- if (!stat(git_path("logs/%s", path), &st) &&
- S_ISREG(st.st_mode))
- it = path;
- else if (strcmp(ref, path) &&
- !stat(git_path("logs/%s", ref), &st) &&
- S_ISREG(st.st_mode))
- it = ref;
- else
- continue;
- if (!logs_found++) {
- *log = xstrdup(it);
- hashcpy(sha1, hash);
- }
- if (!warn_ambiguous_refs)
- break;
- }
- free(last_branch);
- return logs_found;
-}
-
static inline int upstream_mark(const char *string, int len)
{
const char *suffix[] = { "@{upstream}", "@{u}" };
@@ -501,12 +416,6 @@ struct object *peel_to_type(const char *name, int namelen,
{
if (name && !namelen)
namelen = strlen(name);
- if (!o) {
- unsigned char sha1[20];
- if (get_sha1_1(name, namelen, sha1))
- return NULL;
- o = parse_object(sha1);
- }
while (1) {
if (!o || (!o->parsed && !parse_object(o->sha1)))
return NULL;
@@ -947,10 +856,22 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
len = cp + tmp_len - name;
cp = xstrndup(name, cp - name);
upstream = branch_get(*cp ? cp : NULL);
- if (!upstream
- || !upstream->merge
- || !upstream->merge[0]->dst)
- return error("No upstream branch found for '%s'", cp);
+ /*
+ * Upstream can be NULL only if cp refers to HEAD and HEAD
+ * points to something different than a branch.
+ */
+ if (!upstream)
+ return error(_("HEAD does not point to a branch"));
+ if (!upstream->merge || !upstream->merge[0]->dst) {
+ if (!ref_exists(upstream->refname))
+ return error(_("No such branch: '%s'"), cp);
+ if (!upstream->merge)
+ return error(_("No upstream configured for branch '%s'"),
+ upstream->name);
+ return error(
+ _("Upstream branch '%s' not stored as a remote-tracking branch"),
+ upstream->merge[0]->src);
+ }
free(cp);
cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
strbuf_reset(buf);
@@ -972,9 +893,9 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
{
strbuf_branchname(sb, name);
if (name[0] == '-')
- return CHECK_REF_FORMAT_ERROR;
+ return -1;
strbuf_splice(sb, 0, 0, "refs/heads/", 11);
- return check_ref_format(sb->buf);
+ return check_refname_format(sb->buf, 0);
}
/*
@@ -1206,7 +1127,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
if (new_filename)
filename = new_filename;
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
- if (only_to_die) {
+ if (ret && only_to_die) {
diagnose_invalid_sha1_path(prefix, filename,
tree_sha1, object_name);
free(object_name);
diff --git a/shell.c b/shell.c
index abb8622..84b237f 100644
--- a/shell.c
+++ b/shell.c
@@ -137,6 +137,8 @@ int main(int argc, char **argv)
int devnull_fd;
int count;
+ git_setup_gettext();
+
git_extract_argv0_path(argv[0]);
/*
diff --git a/show-index.c b/show-index.c
index 4c0ac13..5a9eed7 100644
--- a/show-index.c
+++ b/show-index.c
@@ -11,6 +11,8 @@ int main(int argc, char **argv)
unsigned int version;
static unsigned int top_index[256];
+ git_setup_gettext();
+
if (argc != 1)
usage(show_index_usage);
if (fread(top_index, 2 * 4, 1, stdin) != 1)
@@ -48,7 +50,7 @@ int main(int argc, char **argv)
unsigned char sha1[20];
uint32_t crc;
uint32_t off;
- } *entries = malloc(nr * sizeof(entries[0]));
+ } *entries = xmalloc(nr * sizeof(entries[0]));
for (i = 0; i < nr; i++)
if (fread(entries[i].sha1, 20, 1, stdin) != 1)
die("unable to read sha1 %u/%u", i, nr);
diff --git a/strbuf.c b/strbuf.c
index 1a7df12..ec88266 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -30,10 +30,8 @@ void strbuf_init(struct strbuf *sb, size_t hint)
{
sb->alloc = sb->len = 0;
sb->buf = strbuf_slopbuf;
- if (hint) {
+ if (hint)
strbuf_grow(sb, hint);
- sb->buf[0] = '\0';
- }
}
void strbuf_release(struct strbuf *sb)
@@ -65,12 +63,15 @@ void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc)
void strbuf_grow(struct strbuf *sb, size_t extra)
{
+ int new_buf = !sb->alloc;
if (unsigned_add_overflows(extra, 1) ||
unsigned_add_overflows(sb->len, extra + 1))
die("you want to use way too much memory");
- if (!sb->alloc)
+ if (new_buf)
sb->buf = NULL;
ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
+ if (new_buf)
+ sb->buf[0] = '\0';
}
void strbuf_trim(struct strbuf *sb)
@@ -356,7 +357,6 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
{
int ch;
- strbuf_grow(sb, 0);
if (feof(fp))
return EOF;
@@ -383,6 +383,22 @@ int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
return 0;
}
+int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term)
+{
+ strbuf_reset(sb);
+
+ while (1) {
+ char ch;
+ ssize_t len = xread(fd, &ch, 1);
+ if (len <= 0)
+ return EOF;
+ strbuf_addch(sb, ch);
+ if (ch == term)
+ break;
+ }
+ return 0;
+}
+
int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
{
int fd, len;
@@ -397,3 +413,87 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
return len;
}
+
+void strbuf_add_lines(struct strbuf *out, const char *prefix,
+ const char *buf, size_t size)
+{
+ while (size) {
+ const char *next = memchr(buf, '\n', size);
+ next = next ? (next + 1) : (buf + size);
+ strbuf_addstr(out, prefix);
+ strbuf_add(out, buf, next - buf);
+ size -= next - buf;
+ buf = next;
+ }
+ strbuf_complete_line(out);
+}
+
+static int is_rfc3986_reserved(char ch)
+{
+ switch (ch) {
+ case '!': case '*': case '\'': case '(': case ')': case ';':
+ case ':': case '@': case '&': case '=': case '+': case '$':
+ case ',': case '/': case '?': case '#': case '[': case ']':
+ return 1;
+ }
+ return 0;
+}
+
+static int is_rfc3986_unreserved(char ch)
+{
+ return isalnum(ch) ||
+ ch == '-' || ch == '_' || ch == '.' || ch == '~';
+}
+
+void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,
+ int reserved)
+{
+ strbuf_grow(sb, len);
+ while (len--) {
+ char ch = *s++;
+ if (is_rfc3986_unreserved(ch) ||
+ (!reserved && is_rfc3986_reserved(ch)))
+ strbuf_addch(sb, ch);
+ else
+ strbuf_addf(sb, "%%%02x", ch);
+ }
+}
+
+void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
+ int reserved)
+{
+ strbuf_add_urlencode(sb, s, strlen(s), reserved);
+}
+
+void strbuf_addf_ln(struct strbuf *sb, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ strbuf_vaddf(sb, fmt, ap);
+ va_end(ap);
+ strbuf_addch(sb, '\n');
+}
+
+int printf_ln(const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = vprintf(fmt, ap);
+ va_end(ap);
+ if (ret < 0 || putchar('\n') == EOF)
+ return -1;
+ return ret + 1;
+}
+
+int fprintf_ln(FILE *fp, const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = vfprintf(fp, fmt, ap);
+ va_end(ap);
+ if (ret < 0 || putc('\n', fp) == EOF)
+ return -1;
+ return ret + 1;
+}
diff --git a/strbuf.h b/strbuf.h
index 46a33f8..b888d40 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -99,6 +99,16 @@ __attribute__((format (printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
__attribute__((format (printf,2,0)))
extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
+__attribute__((format (printf,2,3)))
+extern void strbuf_addf_ln(struct strbuf *sb, const char *fmt, ...);
+
+extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size);
+
+static inline void strbuf_complete_line(struct strbuf *sb)
+{
+ if (sb->len && sb->buf[sb->len - 1] != '\n')
+ strbuf_addch(sb, '\n');
+}
extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
/* XXX: if read fails, any partial read is undone */
@@ -108,6 +118,7 @@ extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
extern int strbuf_getwholeline(struct strbuf *, FILE *, int);
extern int strbuf_getline(struct strbuf *, FILE *, int);
+extern int strbuf_getwholeline_fd(struct strbuf *, int, int);
extern void stripspace(struct strbuf *buf, int skip_comments);
extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
@@ -115,4 +126,14 @@ extern int launch_editor(const char *path, struct strbuf *buffer, const char *co
extern int strbuf_branchname(struct strbuf *sb, const char *name);
extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
+extern void strbuf_add_urlencode(struct strbuf *, const char *, size_t,
+ int reserved);
+extern void strbuf_addstr_urlencode(struct strbuf *, const char *,
+ int reserved);
+
+__attribute__((format (printf,1,2)))
+extern int printf_ln(const char *fmt, ...);
+__attribute__((format (printf,2,3)))
+extern int fprintf_ln(FILE *fp, const char *fmt, ...);
+
#endif /* STRBUF_H */
diff --git a/streaming.c b/streaming.c
new file mode 100644
index 0000000..4d978e5
--- /dev/null
+++ b/streaming.c
@@ -0,0 +1,546 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#include "cache.h"
+#include "streaming.h"
+
+enum input_source {
+ stream_error = -1,
+ incore = 0,
+ loose = 1,
+ pack_non_delta = 2
+};
+
+typedef int (*open_istream_fn)(struct git_istream *,
+ struct object_info *,
+ const unsigned char *,
+ enum object_type *);
+typedef int (*close_istream_fn)(struct git_istream *);
+typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t);
+
+struct stream_vtbl {
+ close_istream_fn close;
+ read_istream_fn read;
+};
+
+#define open_method_decl(name) \
+ int open_istream_ ##name \
+ (struct git_istream *st, struct object_info *oi, \
+ const unsigned char *sha1, \
+ enum object_type *type)
+
+#define close_method_decl(name) \
+ int close_istream_ ##name \
+ (struct git_istream *st)
+
+#define read_method_decl(name) \
+ ssize_t read_istream_ ##name \
+ (struct git_istream *st, char *buf, size_t sz)
+
+/* forward declaration */
+static open_method_decl(incore);
+static open_method_decl(loose);
+static open_method_decl(pack_non_delta);
+static struct git_istream *attach_stream_filter(struct git_istream *st,
+ struct stream_filter *filter);
+
+
+static open_istream_fn open_istream_tbl[] = {
+ open_istream_incore,
+ open_istream_loose,
+ open_istream_pack_non_delta,
+};
+
+#define FILTER_BUFFER (1024*16)
+
+struct filtered_istream {
+ struct git_istream *upstream;
+ struct stream_filter *filter;
+ char ibuf[FILTER_BUFFER];
+ char obuf[FILTER_BUFFER];
+ int i_end, i_ptr;
+ int o_end, o_ptr;
+ int input_finished;
+};
+
+struct git_istream {
+ const struct stream_vtbl *vtbl;
+ unsigned long size; /* inflated size of full object */
+ git_zstream z;
+ enum { z_unused, z_used, z_done, z_error } z_state;
+
+ union {
+ struct {
+ char *buf; /* from read_object() */
+ unsigned long read_ptr;
+ } incore;
+
+ struct {
+ void *mapped;
+ unsigned long mapsize;
+ char hdr[32];
+ int hdr_avail;
+ int hdr_used;
+ } loose;
+
+ struct {
+ struct packed_git *pack;
+ off_t pos;
+ } in_pack;
+
+ struct filtered_istream filtered;
+ } u;
+};
+
+int close_istream(struct git_istream *st)
+{
+ int r = st->vtbl->close(st);
+ free(st);
+ return r;
+}
+
+ssize_t read_istream(struct git_istream *st, void *buf, size_t sz)
+{
+ return st->vtbl->read(st, buf, sz);
+}
+
+static enum input_source istream_source(const unsigned char *sha1,
+ enum object_type *type,
+ struct object_info *oi)
+{
+ unsigned long size;
+ int status;
+
+ oi->sizep = &size;
+ status = sha1_object_info_extended(sha1, oi);
+ if (status < 0)
+ return stream_error;
+ *type = status;
+
+ switch (oi->whence) {
+ case OI_LOOSE:
+ return loose;
+ case OI_PACKED:
+ if (!oi->u.packed.is_delta && big_file_threshold < size)
+ return pack_non_delta;
+ /* fallthru */
+ default:
+ return incore;
+ }
+}
+
+struct git_istream *open_istream(const unsigned char *sha1,
+ enum object_type *type,
+ unsigned long *size,
+ struct stream_filter *filter)
+{
+ struct git_istream *st;
+ struct object_info oi;
+ const unsigned char *real = lookup_replace_object(sha1);
+ enum input_source src = istream_source(real, type, &oi);
+
+ if (src < 0)
+ return NULL;
+
+ st = xmalloc(sizeof(*st));
+ if (open_istream_tbl[src](st, &oi, real, type)) {
+ if (open_istream_incore(st, &oi, real, type)) {
+ free(st);
+ return NULL;
+ }
+ }
+ if (st && filter) {
+ /* Add "&& !is_null_stream_filter(filter)" for performance */
+ struct git_istream *nst = attach_stream_filter(st, filter);
+ if (!nst)
+ close_istream(st);
+ st = nst;
+ }
+
+ *size = st->size;
+ return st;
+}
+
+
+/*****************************************************************
+ *
+ * Common helpers
+ *
+ *****************************************************************/
+
+static void close_deflated_stream(struct git_istream *st)
+{
+ if (st->z_state == z_used)
+ git_inflate_end(&st->z);
+}
+
+
+/*****************************************************************
+ *
+ * Filtered stream
+ *
+ *****************************************************************/
+
+static close_method_decl(filtered)
+{
+ free_stream_filter(st->u.filtered.filter);
+ return close_istream(st->u.filtered.upstream);
+}
+
+static read_method_decl(filtered)
+{
+ struct filtered_istream *fs = &(st->u.filtered);
+ size_t filled = 0;
+
+ while (sz) {
+ /* do we already have filtered output? */
+ if (fs->o_ptr < fs->o_end) {
+ size_t to_move = fs->o_end - fs->o_ptr;
+ if (sz < to_move)
+ to_move = sz;
+ memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move);
+ fs->o_ptr += to_move;
+ sz -= to_move;
+ filled += to_move;
+ continue;
+ }
+ fs->o_end = fs->o_ptr = 0;
+
+ /* do we have anything to feed the filter with? */
+ if (fs->i_ptr < fs->i_end) {
+ size_t to_feed = fs->i_end - fs->i_ptr;
+ size_t to_receive = FILTER_BUFFER;
+ if (stream_filter(fs->filter,
+ fs->ibuf + fs->i_ptr, &to_feed,
+ fs->obuf, &to_receive))
+ return -1;
+ fs->i_ptr = fs->i_end - to_feed;
+ fs->o_end = FILTER_BUFFER - to_receive;
+ continue;
+ }
+
+ /* tell the filter to drain upon no more input */
+ if (fs->input_finished) {
+ size_t to_receive = FILTER_BUFFER;
+ if (stream_filter(fs->filter,
+ NULL, NULL,
+ fs->obuf, &to_receive))
+ return -1;
+ fs->o_end = FILTER_BUFFER - to_receive;
+ if (!fs->o_end)
+ break;
+ continue;
+ }
+ fs->i_end = fs->i_ptr = 0;
+
+ /* refill the input from the upstream */
+ if (!fs->input_finished) {
+ fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER);
+ if (fs->i_end < 0)
+ break;
+ if (fs->i_end)
+ continue;
+ }
+ fs->input_finished = 1;
+ }
+ return filled;
+}
+
+static struct stream_vtbl filtered_vtbl = {
+ close_istream_filtered,
+ read_istream_filtered,
+};
+
+static struct git_istream *attach_stream_filter(struct git_istream *st,
+ struct stream_filter *filter)
+{
+ struct git_istream *ifs = xmalloc(sizeof(*ifs));
+ struct filtered_istream *fs = &(ifs->u.filtered);
+
+ ifs->vtbl = &filtered_vtbl;
+ fs->upstream = st;
+ fs->filter = filter;
+ fs->i_end = fs->i_ptr = 0;
+ fs->o_end = fs->o_ptr = 0;
+ fs->input_finished = 0;
+ ifs->size = -1; /* unknown */
+ return ifs;
+}
+
+/*****************************************************************
+ *
+ * Loose object stream
+ *
+ *****************************************************************/
+
+static read_method_decl(loose)
+{
+ size_t total_read = 0;
+
+ switch (st->z_state) {
+ case z_done:
+ return 0;
+ case z_error:
+ return -1;
+ default:
+ break;
+ }
+
+ if (st->u.loose.hdr_used < st->u.loose.hdr_avail) {
+ size_t to_copy = st->u.loose.hdr_avail - st->u.loose.hdr_used;
+ if (sz < to_copy)
+ to_copy = sz;
+ memcpy(buf, st->u.loose.hdr + st->u.loose.hdr_used, to_copy);
+ st->u.loose.hdr_used += to_copy;
+ total_read += to_copy;
+ }
+
+ while (total_read < sz) {
+ int status;
+
+ st->z.next_out = (unsigned char *)buf + total_read;
+ st->z.avail_out = sz - total_read;
+ status = git_inflate(&st->z, Z_FINISH);
+
+ total_read = st->z.next_out - (unsigned char *)buf;
+
+ if (status == Z_STREAM_END) {
+ git_inflate_end(&st->z);
+ st->z_state = z_done;
+ break;
+ }
+ if (status != Z_OK && status != Z_BUF_ERROR) {
+ git_inflate_end(&st->z);
+ st->z_state = z_error;
+ return -1;
+ }
+ }
+ return total_read;
+}
+
+static close_method_decl(loose)
+{
+ close_deflated_stream(st);
+ munmap(st->u.loose.mapped, st->u.loose.mapsize);
+ return 0;
+}
+
+static struct stream_vtbl loose_vtbl = {
+ close_istream_loose,
+ read_istream_loose,
+};
+
+static open_method_decl(loose)
+{
+ st->u.loose.mapped = map_sha1_file(sha1, &st->u.loose.mapsize);
+ if (!st->u.loose.mapped)
+ return -1;
+ if (unpack_sha1_header(&st->z,
+ st->u.loose.mapped,
+ st->u.loose.mapsize,
+ st->u.loose.hdr,
+ sizeof(st->u.loose.hdr)) < 0) {
+ git_inflate_end(&st->z);
+ munmap(st->u.loose.mapped, st->u.loose.mapsize);
+ return -1;
+ }
+
+ parse_sha1_header(st->u.loose.hdr, &st->size);
+ st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1;
+ st->u.loose.hdr_avail = st->z.total_out;
+ st->z_state = z_used;
+
+ st->vtbl = &loose_vtbl;
+ return 0;
+}
+
+
+/*****************************************************************
+ *
+ * Non-delta packed object stream
+ *
+ *****************************************************************/
+
+static read_method_decl(pack_non_delta)
+{
+ size_t total_read = 0;
+
+ switch (st->z_state) {
+ case z_unused:
+ memset(&st->z, 0, sizeof(st->z));
+ git_inflate_init(&st->z);
+ st->z_state = z_used;
+ break;
+ case z_done:
+ return 0;
+ case z_error:
+ return -1;
+ case z_used:
+ break;
+ }
+
+ while (total_read < sz) {
+ int status;
+ struct pack_window *window = NULL;
+ unsigned char *mapped;
+
+ mapped = use_pack(st->u.in_pack.pack, &window,
+ st->u.in_pack.pos, &st->z.avail_in);
+
+ st->z.next_out = (unsigned char *)buf + total_read;
+ st->z.avail_out = sz - total_read;
+ st->z.next_in = mapped;
+ status = git_inflate(&st->z, Z_FINISH);
+
+ st->u.in_pack.pos += st->z.next_in - mapped;
+ total_read = st->z.next_out - (unsigned char *)buf;
+ unuse_pack(&window);
+
+ if (status == Z_STREAM_END) {
+ git_inflate_end(&st->z);
+ st->z_state = z_done;
+ break;
+ }
+ if (status != Z_OK && status != Z_BUF_ERROR) {
+ git_inflate_end(&st->z);
+ st->z_state = z_error;
+ return -1;
+ }
+ }
+ return total_read;
+}
+
+static close_method_decl(pack_non_delta)
+{
+ close_deflated_stream(st);
+ return 0;
+}
+
+static struct stream_vtbl pack_non_delta_vtbl = {
+ close_istream_pack_non_delta,
+ read_istream_pack_non_delta,
+};
+
+static open_method_decl(pack_non_delta)
+{
+ struct pack_window *window;
+ enum object_type in_pack_type;
+
+ st->u.in_pack.pack = oi->u.packed.pack;
+ st->u.in_pack.pos = oi->u.packed.offset;
+ window = NULL;
+
+ in_pack_type = unpack_object_header(st->u.in_pack.pack,
+ &window,
+ &st->u.in_pack.pos,
+ &st->size);
+ unuse_pack(&window);
+ switch (in_pack_type) {
+ default:
+ return -1; /* we do not do deltas for now */
+ case OBJ_COMMIT:
+ case OBJ_TREE:
+ case OBJ_BLOB:
+ case OBJ_TAG:
+ break;
+ }
+ st->z_state = z_unused;
+ st->vtbl = &pack_non_delta_vtbl;
+ return 0;
+}
+
+
+/*****************************************************************
+ *
+ * In-core stream
+ *
+ *****************************************************************/
+
+static close_method_decl(incore)
+{
+ free(st->u.incore.buf);
+ return 0;
+}
+
+static read_method_decl(incore)
+{
+ size_t read_size = sz;
+ size_t remainder = st->size - st->u.incore.read_ptr;
+
+ if (remainder <= read_size)
+ read_size = remainder;
+ if (read_size) {
+ memcpy(buf, st->u.incore.buf + st->u.incore.read_ptr, read_size);
+ st->u.incore.read_ptr += read_size;
+ }
+ return read_size;
+}
+
+static struct stream_vtbl incore_vtbl = {
+ close_istream_incore,
+ read_istream_incore,
+};
+
+static open_method_decl(incore)
+{
+ st->u.incore.buf = read_sha1_file_extended(sha1, type, &st->size, 0);
+ st->u.incore.read_ptr = 0;
+ st->vtbl = &incore_vtbl;
+
+ return st->u.incore.buf ? 0 : -1;
+}
+
+
+/****************************************************************
+ * Users of streaming interface
+ ****************************************************************/
+
+int stream_blob_to_fd(int fd, unsigned const char *sha1, struct stream_filter *filter,
+ int can_seek)
+{
+ struct git_istream *st;
+ enum object_type type;
+ unsigned long sz;
+ ssize_t kept = 0;
+ int result = -1;
+
+ st = open_istream(sha1, &type, &sz, filter);
+ if (!st)
+ return result;
+ if (type != OBJ_BLOB)
+ goto close_and_exit;
+ for (;;) {
+ char buf[1024 * 16];
+ ssize_t wrote, holeto;
+ ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+ if (!readlen)
+ break;
+ if (can_seek && sizeof(buf) == readlen) {
+ for (holeto = 0; holeto < readlen; holeto++)
+ if (buf[holeto])
+ break;
+ if (readlen == holeto) {
+ kept += holeto;
+ continue;
+ }
+ }
+
+ if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
+ goto close_and_exit;
+ else
+ kept = 0;
+ wrote = write_in_full(fd, buf, readlen);
+
+ if (wrote != readlen)
+ goto close_and_exit;
+ }
+ if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
+ write(fd, "", 1) != 1))
+ goto close_and_exit;
+ result = 0;
+
+ close_and_exit:
+ close_istream(st);
+ return result;
+}
diff --git a/streaming.h b/streaming.h
new file mode 100644
index 0000000..1d05c2a
--- /dev/null
+++ b/streaming.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef STREAMING_H
+#define STREAMING_H 1
+#include "cache.h"
+
+/* opaque */
+struct git_istream;
+
+extern struct git_istream *open_istream(const unsigned char *, enum object_type *, unsigned long *, struct stream_filter *);
+extern int close_istream(struct git_istream *);
+extern ssize_t read_istream(struct git_istream *, void *, size_t);
+
+extern int stream_blob_to_fd(int fd, const unsigned char *, struct stream_filter *, int can_seek);
+
+#endif /* STREAMING_H */
diff --git a/string-list.c b/string-list.c
index 5168118..d9810ab 100644
--- a/string-list.c
+++ b/string-list.c
@@ -185,3 +185,12 @@ int unsorted_string_list_has_string(struct string_list *list,
return unsorted_string_list_lookup(list, string) != NULL;
}
+void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util)
+{
+ if (list->strdup_strings)
+ free(list->items[i].string);
+ if (free_util)
+ free(list->items[i].util);
+ list->items[i] = list->items[list->nr-1];
+ list->nr--;
+}
diff --git a/string-list.h b/string-list.h
index bda6983..0684cb7 100644
--- a/string-list.h
+++ b/string-list.h
@@ -44,4 +44,5 @@ void sort_string_list(struct string_list *list);
int unsorted_string_list_has_string(struct string_list *list, const char *string);
struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
const char *string);
+void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);
#endif /* STRING_LIST_H */
diff --git a/submodule.c b/submodule.c
index 11de09a..959d349 100644
--- a/submodule.c
+++ b/submodule.c
@@ -8,12 +8,18 @@
#include "diffcore.h"
#include "refs.h"
#include "string-list.h"
+#include "sha1-array.h"
+#include "argv-array.h"
static struct string_list config_name_for_path;
static struct string_list config_fetch_recurse_submodules_for_name;
static struct string_list config_ignore_for_name;
static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
static struct string_list changed_submodule_paths;
+static int initialized_fetch_ref_tips;
+static struct sha1_array ref_tips_before_fetch;
+static struct sha1_array ref_tips_after_fetch;
+
/*
* The following flag is set if the .gitmodules file is unmerged. We then
* disable recursion for all submodules where .git/config doesn't have a
@@ -57,6 +63,9 @@ static int add_submodule_odb(const char *path)
alt_odb->name[40] = '\0';
alt_odb->name[41] = '\0';
alt_odb_list = alt_odb;
+
+ /* add possible alternates from the submodule */
+ read_info_alternates(objects_directory.buf, 0);
prepare_alt_odb();
done:
strbuf_release(&objects_directory);
@@ -308,6 +317,150 @@ void set_config_fetch_recurse_submodules(int value)
config_fetch_recurse_submodules = value;
}
+static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+ return 1;
+}
+
+static int submodule_needs_pushing(const char *path, const unsigned char sha1[20])
+{
+ if (add_submodule_odb(path) || !lookup_commit_reference(sha1))
+ return 0;
+
+ if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
+ struct child_process cp;
+ const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL};
+ struct strbuf buf = STRBUF_INIT;
+ int needs_pushing = 0;
+
+ argv[1] = sha1_to_hex(sha1);
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = local_repo_env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+ cp.out = -1;
+ cp.dir = path;
+ if (start_command(&cp))
+ die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s",
+ sha1_to_hex(sha1), path);
+ if (strbuf_read(&buf, cp.out, 41))
+ needs_pushing = 1;
+ finish_command(&cp);
+ close(cp.out);
+ strbuf_release(&buf);
+ return needs_pushing;
+ }
+
+ return 0;
+}
+
+static void collect_submodules_from_diff(struct diff_queue_struct *q,
+ struct diff_options *options,
+ void *data)
+{
+ int i;
+ struct string_list *needs_pushing = data;
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (!S_ISGITLINK(p->two->mode))
+ continue;
+ if (submodule_needs_pushing(p->two->path, p->two->sha1))
+ string_list_insert(needs_pushing, p->two->path);
+ }
+}
+
+static void find_unpushed_submodule_commits(struct commit *commit,
+ struct string_list *needs_pushing)
+{
+ struct rev_info rev;
+
+ init_revisions(&rev, NULL);
+ rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = collect_submodules_from_diff;
+ rev.diffopt.format_callback_data = needs_pushing;
+ diff_tree_combined_merge(commit, 1, &rev);
+}
+
+int find_unpushed_submodules(unsigned char new_sha1[20],
+ const char *remotes_name, struct string_list *needs_pushing)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
+ int argc = ARRAY_SIZE(argv) - 1;
+ char *sha1_copy;
+
+ struct strbuf remotes_arg = STRBUF_INIT;
+
+ strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
+ init_revisions(&rev, NULL);
+ sha1_copy = xstrdup(sha1_to_hex(new_sha1));
+ argv[1] = sha1_copy;
+ argv[3] = remotes_arg.buf;
+ setup_revisions(argc, argv, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ while ((commit = get_revision(&rev)) != NULL)
+ find_unpushed_submodule_commits(commit, needs_pushing);
+
+ reset_revision_walk();
+ free(sha1_copy);
+ strbuf_release(&remotes_arg);
+
+ return needs_pushing->nr;
+}
+
+static int push_submodule(const char *path)
+{
+ if (add_submodule_odb(path))
+ return 1;
+
+ if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
+ struct child_process cp;
+ const char *argv[] = {"push", NULL};
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = local_repo_env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+ cp.dir = path;
+ if (run_command(&cp))
+ return 0;
+ close(cp.out);
+ }
+
+ return 1;
+}
+
+int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name)
+{
+ int i, ret = 1;
+ struct string_list needs_pushing;
+
+ memset(&needs_pushing, 0, sizeof(struct string_list));
+ needs_pushing.strdup_strings = 1;
+
+ if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing))
+ return 1;
+
+ for (i = 0; i < needs_pushing.nr; i++) {
+ const char *path = needs_pushing.items[i].string;
+ fprintf(stderr, "Pushing submodule '%s'\n", path);
+ if (!push_submodule(path)) {
+ fprintf(stderr, "Unable to push submodule '%s'\n", path);
+ ret = 0;
+ }
+ }
+
+ string_list_clear(&needs_pushing, 0);
+
+ return ret;
+}
+
static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
{
int is_present = 0;
@@ -366,20 +519,46 @@ static void submodule_collect_changed_cb(struct diff_queue_struct *q,
}
}
+static int add_sha1_to_array(const char *ref, const unsigned char *sha1,
+ int flags, void *data)
+{
+ sha1_array_append(data, sha1);
+ return 0;
+}
+
void check_for_new_submodule_commits(unsigned char new_sha1[20])
{
+ if (!initialized_fetch_ref_tips) {
+ for_each_ref(add_sha1_to_array, &ref_tips_before_fetch);
+ initialized_fetch_ref_tips = 1;
+ }
+
+ sha1_array_append(&ref_tips_after_fetch, new_sha1);
+}
+
+static void add_sha1_to_argv(const unsigned char sha1[20], void *data)
+{
+ argv_array_push(data, sha1_to_hex(sha1));
+}
+
+static void calculate_changed_submodule_paths(void)
+{
struct rev_info rev;
struct commit *commit;
- const char *argv[] = {NULL, NULL, "--not", "--all", NULL};
- int argc = ARRAY_SIZE(argv) - 1;
+ struct argv_array argv = ARGV_ARRAY_INIT;
/* No need to check if there are no submodules configured */
if (!config_name_for_path.nr)
return;
init_revisions(&rev, NULL);
- argv[1] = xstrdup(sha1_to_hex(new_sha1));
- setup_revisions(argc, argv, &rev, NULL);
+ argv_array_push(&argv, "--"); /* argv[0] program name */
+ sha1_array_for_each_unique(&ref_tips_after_fetch,
+ add_sha1_to_argv, &argv);
+ argv_array_push(&argv, "--not");
+ sha1_array_for_each_unique(&ref_tips_before_fetch,
+ add_sha1_to_argv, &argv);
+ setup_revisions(argv.argc, argv.argv, &rev, NULL);
if (prepare_revision_walk(&rev))
die("revision walk setup failed");
@@ -403,7 +582,11 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20])
parent = parent->next;
}
}
- free((char *)argv[1]);
+
+ argv_array_clear(&argv);
+ sha1_array_clear(&ref_tips_before_fetch);
+ sha1_array_clear(&ref_tips_after_fetch);
+ initialized_fetch_ref_tips = 0;
}
int fetch_populated_submodules(int num_options, const char **options,
@@ -437,6 +620,8 @@ int fetch_populated_submodules(int num_options, const char **options,
cp.git_cmd = 1;
cp.no_stdin = 1;
+ calculate_changed_submodule_paths();
+
for (i = 0; i < active_nr; i++) {
struct strbuf submodule_path = STRBUF_INIT;
struct strbuf submodule_git_dir = STRBUF_INIT;
@@ -543,7 +728,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
cp.out = -1;
cp.dir = path;
if (start_command(&cp))
- die("Could not run git status --porcelain");
+ die("Could not run 'git status --porcelain' in submodule %s", path);
len = strbuf_read(&buf, cp.out, 1024);
line = buf.buf;
@@ -568,7 +753,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
close(cp.out);
if (finish_command(&cp))
- die("git status --porcelain failed");
+ die("'git status --porcelain' failed in submodule %s", path);
strbuf_release(&buf);
return dirty_submodule;
@@ -607,6 +792,7 @@ static int find_first_merges(struct object_array *result, const char *path,
if (in_merge_bases(b, &commit, 1))
add_object_array(o, NULL, &merges);
}
+ reset_revision_walk();
/* Now we've got all merges that contain a and b. Prune all
* merges that contain another found merge and save them in
@@ -648,7 +834,7 @@ static void print_commit(struct commit *commit)
int merge_submodule(unsigned char result[20], const char *path,
const unsigned char base[20], const unsigned char a[20],
- const unsigned char b[20])
+ const unsigned char b[20], int search)
{
struct commit *commit_base, *commit_a, *commit_b;
int parent_count;
@@ -703,6 +889,10 @@ int merge_submodule(unsigned char result[20], const char *path,
* user needs to confirm the resolution.
*/
+ /* Skip the search if makes no sense to the calling context. */
+ if (!search)
+ return 0;
+
/* find commit which merges them */
parent_count = find_first_merges(&merges, path, commit_a, commit_b);
switch (parent_count) {
diff --git a/submodule.h b/submodule.h
index 5350b0d..e105b0e 100644
--- a/submodule.h
+++ b/submodule.h
@@ -13,7 +13,7 @@ enum {
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path);
int submodule_config(const char *var, const char *value, void *cb);
-void gitmodules_config();
+void gitmodules_config(void);
int parse_submodule_config_option(const char *var, const char *value);
void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);
@@ -28,6 +28,9 @@ int fetch_populated_submodules(int num_options, const char **options,
int quiet);
unsigned is_submodule_modified(const char *path, int ignore_untracked);
int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
- const unsigned char a[20], const unsigned char b[20]);
+ const unsigned char a[20], const unsigned char b[20], int search);
+int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
+ struct string_list *needs_pushing);
+int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);
#endif
diff --git a/symlinks.c b/symlinks.c
index 034943b..2900367 100644
--- a/symlinks.c
+++ b/symlinks.c
@@ -219,7 +219,20 @@ int has_symlink_leading_path(const char *name, int len)
*/
int check_leading_path(const char *name, int len)
{
- struct cache_def *cache = &default_cache; /* FIXME */
+ return threaded_check_leading_path(&default_cache, name, len);
+}
+
+/*
+ * Return zero if path 'name' has a leading symlink component or
+ * if some leading path component does not exists.
+ *
+ * Return -1 if leading path exists and is a directory.
+ *
+ * Return path length if leading path exists and is neither a
+ * directory nor a symlink.
+ */
+int threaded_check_leading_path(struct cache_def *cache, const char *name, int len)
+{
int flags;
int match_len = lstat_cache_matchlen(cache, name, len, &flags,
FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT);
@@ -240,7 +253,18 @@ int check_leading_path(const char *name, int len)
*/
int has_dirs_only_path(const char *name, int len, int prefix_len)
{
- struct cache_def *cache = &default_cache; /* FIXME */
+ return threaded_has_dirs_only_path(&default_cache, name, len, prefix_len);
+}
+
+/*
+ * Return non-zero if all path components of 'name' exists as a
+ * directory. If prefix_len > 0, we will test with the stat()
+ * function instead of the lstat() function for a prefix length of
+ * 'prefix_len', thus we then allow for symlinks in the prefix part as
+ * long as those points to real existing directories.
+ */
+int threaded_has_dirs_only_path(struct cache_def *cache, const char *name, int len, int prefix_len)
+{
return lstat_cache(cache, name, len,
FL_DIR|FL_FULLPATH, prefix_len) &
FL_DIR;
diff --git a/t/Makefile b/t/Makefile
index 9046ec9..88e289f 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -17,9 +17,9 @@ DEFAULT_TEST_TARGET ?= test
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
-T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
-TSVN = $(wildcard t91[0-9][0-9]-*.sh)
-TGITWEB = $(wildcard t95[0-9][0-9]-*.sh)
+T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
+TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh))
+TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh))
all: $(DEFAULT_TEST_TARGET)
@@ -28,7 +28,7 @@ test: pre-clean $(TEST_LINT)
prove: pre-clean $(TEST_LINT)
@echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
- $(MAKE) clean
+ $(MAKE) clean-except-prove-cache
$(T):
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
@@ -36,9 +36,11 @@ $(T):
pre-clean:
$(RM) -r test-results
-clean:
+clean-except-prove-cache:
$(RM) -r 'trash directory'.* test-results
$(RM) -r valgrind/bin
+
+clean: clean-except-prove-cache
$(RM) .prove
test-lint: test-lint-duplicates test-lint-executable
@@ -73,6 +75,9 @@ gitweb-test:
valgrind:
$(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
+perf:
+ $(MAKE) -C perf/ all
+
# Smoke testing targets
-include ../GIT-VERSION-FILE
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo unknown')
@@ -111,4 +116,4 @@ smoke_report: smoke
http://smoke.git.nix.is/app/projects/process_add_report/1 \
| grep -v ^Redirecting
-.PHONY: pre-clean $(T) aggregate-results clean valgrind smoke smoke_report
+.PHONY: pre-clean $(T) aggregate-results clean valgrind perf
diff --git a/t/README b/t/README
index c85abaf..4c3ea25 100644
--- a/t/README
+++ b/t/README
@@ -307,6 +307,25 @@ Don't:
Use test_done instead if you need to stop the tests early (see
"Skipping tests" below).
+ - use '! git cmd' when you want to make sure the git command exits
+ with failure in a controlled way by calling "die()". Instead,
+ use 'test_must_fail git cmd'. This will signal a failure if git
+ dies in an unexpected way (e.g. segfault).
+
+ - use perl without spelling it as "$PERL_PATH". This is to help our
+ friends on Windows where the platform Perl often adds CR before
+ the end of line, and they bundle Git with a version of Perl that
+ does not do so, whose path is specified with $PERL_PATH.
+
+ - use sh without spelling it as "$SHELL_PATH", when the script can
+ be misinterpreted by broken platform shell (e.g. Solaris).
+
+ - chdir around in tests. It is not sufficient to chdir to
+ somewhere and then chdir back to the original location later in
+ the test, as any intermediate step can fail and abort the test,
+ causing the next test to start in an unexpected directory. Do so
+ inside a subshell if necessary.
+
- Break the TAP output
The raw output from your test may be interpreted by a TAP harness. TAP
@@ -342,9 +361,9 @@ If you need to skip tests you should do so by using the three-arg form
of the test_* functions (see the "Test harness library" section
below), e.g.:
- test_expect_success PERL 'I need Perl' "
- '$PERL_PATH' -e 'hlagh() if unf_unf()'
- "
+ test_expect_success PERL 'I need Perl' '
+ "$PERL_PATH" -e "hlagh() if unf_unf()"
+ '
The advantage of skipping tests like this is that platforms that don't
have the PERL and other optional dependencies get an indication of how
@@ -548,6 +567,19 @@ library for your script to use.
...
'
+ - test_pause
+
+ This command is useful for writing and debugging tests and must be
+ removed before submitting. It halts the execution of the test and
+ spawns a shell in the trash directory. Exit the shell to continue
+ the test. Example:
+
+ test_expect_success 'test' '
+ git do-something >actual &&
+ test_pause &&
+ test_cmp expected actual
+ '
+
Prerequisites
-------------
@@ -658,76 +690,3 @@ Then, at the top-level:
That'll generate a detailed cover report in the "cover_db_html"
directory, which you can then copy to a webserver, or inspect locally
in a browser.
-
-Smoke testing
--------------
-
-The Git test suite has support for smoke testing. Smoke testing is
-when you submit the results of a test run to a central server for
-analysis and aggregation.
-
-Running a smoke tester is an easy and valuable way of contributing to
-Git development, particularly if you have access to an uncommon OS on
-obscure hardware.
-
-After building Git you can generate a smoke report like this in the
-"t" directory:
-
- make clean smoke
-
-You can also pass arguments via the environment. This should make it
-faster:
-
- GIT_TEST_OPTS='--root=/dev/shm' TEST_JOBS=10 make clean smoke
-
-The "smoke" target will run the Git test suite with Perl's
-"TAP::Harness" module, and package up the results in a .tar.gz archive
-with "TAP::Harness::Archive". The former is included with Perl v5.10.1
-or later, but you'll need to install the latter from the CPAN. See the
-"Test coverage" section above for how you might do that.
-
-Once the "smoke" target finishes you'll see a message like this:
-
- TAP Archive created at <path to git>/t/test-results/git-smoke.tar.gz
-
-To upload the smoke report you need to have curl(1) installed, then
-do:
-
- make smoke_report
-
-To upload the report anonymously. Hopefully that'll return something
-like "Reported #7 added.".
-
-If you're going to be uploading reports frequently please request a
-user account by E-Mailing gitsmoke@v.nix.is. Once you have a username
-and password you'll be able to do:
-
- SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> make smoke_report
-
-You can also add an additional comment to attach to the report, and/or
-a comma separated list of tags:
-
- SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> \
- SMOKE_COMMENT=<comment> SMOKE_TAGS=<tags> \
- make smoke_report
-
-Once the report is uploaded it'll be made available at
-http://smoke.git.nix.is, here's an overview of Recent Smoke Reports
-for Git:
-
- http://smoke.git.nix.is/app/projects/smoke_reports/1
-
-The reports will also be mirrored to GitHub every few hours:
-
- http://github.com/gitsmoke/smoke-reports
-
-The Smolder SQLite database is also mirrored and made available for
-download:
-
- http://github.com/gitsmoke/smoke-database
-
-Note that the database includes hashed (with crypt()) user passwords
-and E-Mail addresses. Don't use a valuable password for the smoke
-service if you have an account, or an E-Mail address you don't want to
-be publicly known. The user accounts are just meant to be convenient
-labels, they're not meant to be secure.
diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh
index 292753f..ae2dc46 100644
--- a/t/gitweb-lib.sh
+++ b/t/gitweb-lib.sh
@@ -16,6 +16,7 @@ our \$projectroot = "$safe_pwd";
our \$project_maxdepth = 8;
our \$home_link_str = 'projects';
our \$site_name = '[localhost]';
+our \$site_html_head_string = '';
our \$site_header = '';
our \$site_footer = '';
our \$home_text = 'indextext.html';
@@ -68,7 +69,7 @@ gitweb_run () {
# written to web server logs, so we are not interested in that:
# we are interested only in properly formatted errors/warnings
rm -f gitweb.log &&
- perl -- "$SCRIPT_NAME" \
+ "$PERL_PATH" -- "$SCRIPT_NAME" \
>gitweb.output 2>gitweb.log &&
perl -w -e '
open O, ">gitweb.headers";
diff --git a/t/harness b/t/harness
deleted file mode 100755
index f5c02f4..0000000
--- a/t/harness
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-use Getopt::Long ();
-use TAP::Harness::Archive;
-
-Getopt::Long::Parser->new(
- config => [ qw/ pass_through / ],
-)->getoptions(
- 'jobs:1' => \(my $jobs = $ENV{TEST_JOBS}),
- 'archive=s' => \my $archive,
-) or die "$0: Couldn't getoptions()";
-
-TAP::Harness::Archive->new({
- jobs => $jobs,
- archive => $archive,
- ($ENV{GIT_TEST_OPTS}
- ? (test_args => [ split /\s+/, $ENV{GIT_TEST_OPTS} ])
- : ()),
- extra_properties => {},
-})->runtests(@ARGV);
diff --git a/t/lib-bash.sh b/t/lib-bash.sh
new file mode 100644
index 0000000..11397f7
--- /dev/null
+++ b/t/lib-bash.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Ensures that tests are run under Bash; primarily intended for running tests
+# of the completion script.
+
+if test -n "$BASH" && test -z "$POSIXLY_CORRECT"; then
+ # we are in full-on bash mode
+ true
+elif type bash >/dev/null 2>&1; then
+ # execute in full-on bash mode
+ unset POSIXLY_CORRECT
+ exec bash "$0" "$@"
+else
+ echo '1..0 #SKIP skipping bash completion tests; bash not available'
+ exit 0
+fi
+
+. ./test-lib.sh
diff --git a/t/lib-credential.sh b/t/lib-credential.sh
new file mode 100755
index 0000000..957ae93
--- /dev/null
+++ b/t/lib-credential.sh
@@ -0,0 +1,289 @@
+#!/bin/sh
+
+# Try a set of credential helpers; the expected stdin,
+# stdout and stderr should be provided on stdin,
+# separated by "--".
+check() {
+ credential_opts=
+ credential_cmd=$1
+ shift
+ for arg in "$@"; do
+ credential_opts="$credential_opts -c credential.helper='$arg'"
+ done
+ read_chunk >stdin &&
+ read_chunk >expect-stdout &&
+ read_chunk >expect-stderr &&
+ if ! eval "git $credential_opts credential $credential_cmd <stdin >stdout 2>stderr"; then
+ echo "git credential failed with code $?" &&
+ cat stderr &&
+ false
+ fi &&
+ test_cmp expect-stdout stdout &&
+ test_cmp expect-stderr stderr
+}
+
+read_chunk() {
+ while read line; do
+ case "$line" in
+ --) break ;;
+ *) echo "$line" ;;
+ esac
+ done
+}
+
+# Clear any residual data from previous tests. We only
+# need this when testing third-party helpers which read and
+# write outside of our trash-directory sandbox.
+#
+# Don't bother checking for success here, as it is
+# outside the scope of tests and represents a best effort to
+# clean up after ourselves.
+helper_test_clean() {
+ reject $1 https example.com store-user
+ reject $1 https example.com user1
+ reject $1 https example.com user2
+ reject $1 http path.tld user
+ reject $1 https timeout.tld user
+}
+
+reject() {
+ (
+ echo protocol=$2
+ echo host=$3
+ echo username=$4
+ ) | git -c credential.helper=$1 credential reject
+}
+
+helper_test() {
+ HELPER=$1
+
+ test_expect_success "helper ($HELPER) has no existing data" '
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ --
+ protocol=https
+ host=example.com
+ username=askpass-username
+ password=askpass-password
+ --
+ askpass: Username for '\''https://example.com'\'':
+ askpass: Password for '\''https://askpass-username@example.com'\'':
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) stores password" '
+ check approve $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ username=store-user
+ password=store-pass
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) can retrieve password" '
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ --
+ protocol=https
+ host=example.com
+ username=store-user
+ password=store-pass
+ --
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) requires matching protocol" '
+ check fill $HELPER <<-\EOF
+ protocol=http
+ host=example.com
+ --
+ protocol=http
+ host=example.com
+ username=askpass-username
+ password=askpass-password
+ --
+ askpass: Username for '\''http://example.com'\'':
+ askpass: Password for '\''http://askpass-username@example.com'\'':
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) requires matching host" '
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=other.tld
+ --
+ protocol=https
+ host=other.tld
+ username=askpass-username
+ password=askpass-password
+ --
+ askpass: Username for '\''https://other.tld'\'':
+ askpass: Password for '\''https://askpass-username@other.tld'\'':
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) requires matching username" '
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ username=other
+ --
+ protocol=https
+ host=example.com
+ username=other
+ password=askpass-password
+ --
+ askpass: Password for '\''https://other@example.com'\'':
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) requires matching path" '
+ test_config credential.usehttppath true &&
+ check approve $HELPER <<-\EOF &&
+ protocol=http
+ host=path.tld
+ path=foo.git
+ username=user
+ password=pass
+ EOF
+ check fill $HELPER <<-\EOF
+ protocol=http
+ host=path.tld
+ path=bar.git
+ --
+ protocol=http
+ host=path.tld
+ path=bar.git
+ username=askpass-username
+ password=askpass-password
+ --
+ askpass: Username for '\''http://path.tld/bar.git'\'':
+ askpass: Password for '\''http://askpass-username@path.tld/bar.git'\'':
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) can forget host" '
+ check reject $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ EOF
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ --
+ protocol=https
+ host=example.com
+ username=askpass-username
+ password=askpass-password
+ --
+ askpass: Username for '\''https://example.com'\'':
+ askpass: Password for '\''https://askpass-username@example.com'\'':
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) can store multiple users" '
+ check approve $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=user1
+ password=pass1
+ EOF
+ check approve $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=user2
+ password=pass2
+ EOF
+ check fill $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=user1
+ --
+ protocol=https
+ host=example.com
+ username=user1
+ password=pass1
+ EOF
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ username=user2
+ --
+ protocol=https
+ host=example.com
+ username=user2
+ password=pass2
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) can forget user" '
+ check reject $HELPER <<-\EOF &&
+ protocol=https
+ host=example.com
+ username=user1
+ EOF
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ username=user1
+ --
+ protocol=https
+ host=example.com
+ username=user1
+ password=askpass-password
+ --
+ askpass: Password for '\''https://user1@example.com'\'':
+ EOF
+ '
+
+ test_expect_success "helper ($HELPER) remembers other user" '
+ check fill $HELPER <<-\EOF
+ protocol=https
+ host=example.com
+ username=user2
+ --
+ protocol=https
+ host=example.com
+ username=user2
+ password=pass2
+ EOF
+ '
+}
+
+helper_test_timeout() {
+ HELPER="$*"
+
+ test_expect_success "helper ($HELPER) times out" '
+ check approve "$HELPER" <<-\EOF &&
+ protocol=https
+ host=timeout.tld
+ username=user
+ password=pass
+ EOF
+ sleep 2 &&
+ check fill "$HELPER" <<-\EOF
+ protocol=https
+ host=timeout.tld
+ --
+ protocol=https
+ host=timeout.tld
+ username=askpass-username
+ password=askpass-password
+ --
+ askpass: Username for '\''https://timeout.tld'\'':
+ askpass: Password for '\''https://askpass-username@timeout.tld'\'':
+ EOF
+ '
+}
+
+cat >askpass <<\EOF
+#!/bin/sh
+echo >&2 askpass: $*
+what=`echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z`
+echo "askpass-$what"
+EOF
+chmod +x askpass
+GIT_ASKPASS="$PWD/askpass"
+export GIT_ASKPASS
diff --git a/t/lib-diff-alternative.sh b/t/lib-diff-alternative.sh
new file mode 100644
index 0000000..75ffd91
--- /dev/null
+++ b/t/lib-diff-alternative.sh
@@ -0,0 +1,165 @@
+#!/bin/sh
+
+test_diff_frobnitz() {
+ cat >file1 <<\EOF
+#include <stdio.h>
+
+// Frobs foo heartily
+int frobnitz(int foo)
+{
+ int i;
+ for(i = 0; i < 10; i++)
+ {
+ printf("Your answer is: ");
+ printf("%d\n", foo);
+ }
+}
+
+int fact(int n)
+{
+ if(n > 1)
+ {
+ return fact(n-1) * n;
+ }
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ frobnitz(fact(10));
+}
+EOF
+
+ cat >file2 <<\EOF
+#include <stdio.h>
+
+int fib(int n)
+{
+ if(n > 2)
+ {
+ return fib(n-1) + fib(n-2);
+ }
+ return 1;
+}
+
+// Frobs foo heartily
+int frobnitz(int foo)
+{
+ int i;
+ for(i = 0; i < 10; i++)
+ {
+ printf("%d\n", foo);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ frobnitz(fib(10));
+}
+EOF
+
+ cat >expect <<\EOF
+diff --git a/file1 b/file2
+index 6faa5a3..e3af329 100644
+--- a/file1
++++ b/file2
+@@ -1,26 +1,25 @@
+ #include <stdio.h>
+
++int fib(int n)
++{
++ if(n > 2)
++ {
++ return fib(n-1) + fib(n-2);
++ }
++ return 1;
++}
++
+ // Frobs foo heartily
+ int frobnitz(int foo)
+ {
+ int i;
+ for(i = 0; i < 10; i++)
+ {
+- printf("Your answer is: ");
+ printf("%d\n", foo);
+ }
+ }
+
+-int fact(int n)
+-{
+- if(n > 1)
+- {
+- return fact(n-1) * n;
+- }
+- return 1;
+-}
+-
+ int main(int argc, char **argv)
+ {
+- frobnitz(fact(10));
++ frobnitz(fib(10));
+ }
+EOF
+
+ STRATEGY=$1
+
+ test_expect_success "$STRATEGY diff" '
+ test_must_fail git diff --no-index "--$STRATEGY" file1 file2 > output &&
+ test_cmp expect output
+ '
+
+ test_expect_success "$STRATEGY diff output is valid" '
+ mv file2 expect &&
+ git apply < output &&
+ test_cmp expect file2
+ '
+}
+
+test_diff_unique() {
+ cat >uniq1 <<\EOF
+1
+2
+3
+4
+5
+6
+EOF
+
+ cat >uniq2 <<\EOF
+a
+b
+c
+d
+e
+f
+EOF
+
+ cat >expect <<\EOF
+diff --git a/uniq1 b/uniq2
+index b414108..0fdf397 100644
+--- a/uniq1
++++ b/uniq2
+@@ -1,6 +1,6 @@
+-1
+-2
+-3
+-4
+-5
+-6
++a
++b
++c
++d
++e
++f
+EOF
+
+ STRATEGY=$1
+
+ test_expect_success 'completely different files' '
+ test_must_fail git diff --no-index "--$STRATEGY" uniq1 uniq2 > output &&
+ test_cmp expect output
+ '
+}
+
diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh
new file mode 100644
index 0000000..0f76f6c
--- /dev/null
+++ b/t/lib-gettext.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/po/build/locale"
+GIT_PO_PATH="$GIT_BUILD_DIR/po"
+export GIT_TEXTDOMAINDIR GIT_PO_PATH
+
+. "$GIT_BUILD_DIR"/git-sh-i18n
+
+if test_have_prereq GETTEXT && ! test_have_prereq GETTEXT_POISON
+then
+ # is_IS.UTF-8 on Solaris and FreeBSD, is_IS.utf8 on Debian
+ is_IS_locale=$(locale -a | sed -n '/^is_IS\.[uU][tT][fF]-*8$/{
+ p
+ q
+ }')
+ # is_IS.ISO8859-1 on Solaris and FreeBSD, is_IS.iso88591 on Debian
+ is_IS_iso_locale=$(locale -a | sed -n '/^is_IS\.[iI][sS][oO]8859-*1$/{
+ p
+ q
+ }')
+
+ # Export them as an environment variable so the t0202/test.pl Perl
+ # test can use it too
+ export is_IS_locale is_IS_iso_locale
+
+ if test -n "$is_IS_locale" &&
+ test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
+ then
+ # Some of the tests need the reference Icelandic locale
+ test_set_prereq GETTEXT_LOCALE
+
+ # Exporting for t0202/test.pl
+ GETTEXT_LOCALE=1
+ export GETTEXT_LOCALE
+ say "# lib-gettext: Found '$is_IS_locale' as an is_IS UTF-8 locale"
+ else
+ say "# lib-gettext: No is_IS UTF-8 locale available"
+ fi
+
+ if test -n "$is_IS_iso_locale" &&
+ test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
+ then
+ # Some of the tests need the reference Icelandic locale
+ test_set_prereq GETTEXT_ISO_LOCALE
+
+ say "# lib-gettext: Found '$is_IS_iso_locale' as an is_IS ISO-8859-1 locale"
+ else
+ say "# lib-gettext: No is_IS ISO-8859-1 locale available"
+ fi
+fi
diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh
new file mode 100644
index 0000000..87f0ad8
--- /dev/null
+++ b/t/lib-git-daemon.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+if test -z "$GIT_TEST_GIT_DAEMON"
+then
+ skip_all="git-daemon testing disabled (define GIT_TEST_GIT_DAEMON to enable)"
+ test_done
+fi
+
+LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-'8121'}
+
+GIT_DAEMON_PID=
+GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo
+GIT_DAEMON_URL=git://127.0.0.1:$LIB_GIT_DAEMON_PORT
+
+start_git_daemon() {
+ if test -n "$GIT_DAEMON_PID"
+ then
+ error "start_git_daemon already called"
+ fi
+
+ mkdir -p "$GIT_DAEMON_DOCUMENT_ROOT_PATH"
+
+ trap 'code=$?; stop_git_daemon; (exit $code); die' EXIT
+
+ say >&3 "Starting git daemon ..."
+ mkfifo git_daemon_output
+ git daemon --listen=127.0.0.1 --port="$LIB_GIT_DAEMON_PORT" \
+ --reuseaddr --verbose \
+ --base-path="$GIT_DAEMON_DOCUMENT_ROOT_PATH" \
+ "$@" "$GIT_DAEMON_DOCUMENT_ROOT_PATH" \
+ >&3 2>git_daemon_output &
+ GIT_DAEMON_PID=$!
+ {
+ read line <&7
+ echo >&4 "$line"
+ cat <&7 >&4 &
+ } 7<git_daemon_output &&
+
+ # Check expected output
+ if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble"
+ then
+ kill "$GIT_DAEMON_PID"
+ wait "$GIT_DAEMON_PID"
+ trap 'die' EXIT
+ error "git daemon failed to start"
+ fi
+}
+
+stop_git_daemon() {
+ if test -z "$GIT_DAEMON_PID"
+ then
+ return
+ fi
+
+ trap 'die' EXIT
+
+ # kill git-daemon child of git
+ say >&3 "Stopping git daemon ..."
+ kill "$GIT_DAEMON_PID"
+ wait "$GIT_DAEMON_PID" >&3 2>&4
+ ret=$?
+ # expect exit with status 143 = 128+15 for signal TERM=15
+ if test $ret -ne 143
+ then
+ error "git daemon exited with status: $ret"
+ fi
+ GIT_DAEMON_PID=
+ rm -f git_daemon_output
+}
diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh
new file mode 100644
index 0000000..121e380
--- /dev/null
+++ b/t/lib-git-p4.sh
@@ -0,0 +1,73 @@
+#
+# Library code for git p4 tests
+#
+
+. ./test-lib.sh
+
+if ! test_have_prereq PYTHON; then
+ skip_all='skipping git p4 tests; python not available'
+ test_done
+fi
+( p4 -h && p4d -h ) >/dev/null 2>&1 || {
+ skip_all='skipping git p4 tests; no p4 or p4d'
+ test_done
+}
+
+# Try to pick a unique port: guess a large number, then hope
+# no more than one of each test is running.
+#
+# This does not handle the case where somebody else is running the
+# same tests and has chosen the same ports.
+testid=${this_test#t}
+git_p4_test_start=9800
+P4DPORT=$((10669 + ($testid - $git_p4_test_start)))
+
+export P4PORT=localhost:$P4DPORT
+export P4CLIENT=client
+export P4EDITOR=:
+
+db="$TRASH_DIRECTORY/db"
+cli="$TRASH_DIRECTORY/cli"
+git="$TRASH_DIRECTORY/git"
+pidfile="$TRASH_DIRECTORY/p4d.pid"
+
+start_p4d() {
+ mkdir -p "$db" "$cli" "$git" &&
+ (
+ p4d -q -r "$db" -p $P4DPORT &
+ echo $! >"$pidfile"
+ ) &&
+ for i in 1 2 3 4 5 ; do
+ p4 info >/dev/null 2>&1 && break || true &&
+ echo waiting for p4d to start &&
+ sleep 1
+ done &&
+ # complain if it never started
+ p4 info >/dev/null &&
+ (
+ cd "$cli" &&
+ p4 client -i <<-EOF
+ Client: client
+ Description: client
+ Root: $cli
+ View: //depot/... //client/...
+ EOF
+ )
+}
+
+kill_p4d() {
+ pid=$(cat "$pidfile")
+ # it had better exist for the first kill
+ kill $pid &&
+ for i in 1 2 3 4 5 ; do
+ kill $pid >/dev/null 2>&1 || break
+ sleep 1
+ done &&
+ # complain if it would not die
+ test_must_fail kill $pid >/dev/null 2>&1 &&
+ rm -rf "$db" "$cli" "$pidfile"
+}
+
+cleanup_git() {
+ rm -rf "$git"
+}
diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
new file mode 100755
index 0000000..05824fa
--- /dev/null
+++ b/t/lib-gpg.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+gpg_version=`gpg --version 2>&1`
+if test $? = 127; then
+ say "You do not seem to have gpg installed"
+else
+ # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
+ # the gpg version 1.0.6 didn't parse trust packets correctly, so for
+ # that version, creation of signed tags using the generated key fails.
+ case "$gpg_version" in
+ 'gpg (GnuPG) 1.0.6'*)
+ say "Your version of gpg (1.0.6) is too buggy for testing"
+ ;;
+ *)
+ # key generation info: gpg --homedir t/lib-gpg --gen-key
+ # Type DSA and Elgamal, size 2048 bits, no expiration date.
+ # Name and email: C O Mitter <committer@example.com>
+ # No password given, to enable non-interactive operation.
+ cp -R "$TEST_DIRECTORY"/lib-gpg ./gpghome
+ chmod 0700 gpghome
+ GNUPGHOME="$(pwd)/gpghome"
+ export GNUPGHOME
+ test_set_prereq GPG
+ ;;
+ esac
+fi
+
+sanitize_pgp() {
+ perl -ne '
+ /^-----END PGP/ and $in_pgp = 0;
+ print unless $in_pgp;
+ /^-----BEGIN PGP/ and $in_pgp = 1;
+ '
+}
diff --git a/t/t7004/pubring.gpg b/t/lib-gpg/pubring.gpg
index 83855fa..83855fa 100644
--- a/t/t7004/pubring.gpg
+++ b/t/lib-gpg/pubring.gpg
Binary files differ
diff --git a/t/t7004/random_seed b/t/lib-gpg/random_seed
index 8fed133..8fed133 100644
--- a/t/t7004/random_seed
+++ b/t/lib-gpg/random_seed
Binary files differ
diff --git a/t/t7004/secring.gpg b/t/lib-gpg/secring.gpg
index d831cd9..d831cd9 100644
--- a/t/t7004/secring.gpg
+++ b/t/lib-gpg/secring.gpg
Binary files differ
diff --git a/t/t7004/trustdb.gpg b/t/lib-gpg/trustdb.gpg
index abace96..abace96 100644
--- a/t/t7004/trustdb.gpg
+++ b/t/lib-gpg/trustdb.gpg
Binary files differ
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index b8996a3..094d490 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -81,8 +81,7 @@ prepare_httpd() {
if test -n "$LIB_HTTPD_SSL"
then
- HTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT
- AUTH_HTTPD_URL=https://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
+ HTTPD_PROTO=https
RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \
-config "$TEST_PATH/ssl.cnf" \
@@ -93,9 +92,12 @@ prepare_httpd() {
export GIT_SSL_NO_VERIFY
HTTPD_PARA="$HTTPD_PARA -DSSL"
else
- HTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT
- AUTH_HTTPD_URL=http://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
+ HTTPD_PROTO=http
fi
+ HTTPD_DEST=127.0.0.1:$LIB_HTTPD_PORT
+ HTTPD_URL=$HTTPD_PROTO://$HTTPD_DEST
+ HTTPD_URL_USER=$HTTPD_PROTO://user%40host@$HTTPD_DEST
+ HTTPD_URL_USER_PASS=$HTTPD_PROTO://user%40host:user%40host@$HTTPD_DEST
if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN"
then
@@ -158,6 +160,6 @@ test_http_push_nonff() {
'
test_expect_success 'non-fast-forward push shows help message' '
- test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" output
+ test_i18ngrep "Updates were rejected because" output
'
}
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 0a4cdfa..de3762e 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -52,8 +52,15 @@ Alias /auth/ www/auth/
<Location /smart_noexport/>
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
</Location>
+<Location /smart_custom_env/>
+ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+ SetEnv GIT_HTTP_EXPORT_ALL
+ SetEnv GIT_COMMITTER_NAME "Custom User"
+ SetEnv GIT_COMMITTER_EMAIL custom@example.com
+</Location>
ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
+ScriptAlias /smart_custom_env/ ${GIT_EXEC_PATH}/git-http-backend/
<Directory ${GIT_EXEC_PATH}>
Options None
</Directory>
@@ -92,6 +99,9 @@ SSLEngine On
<Location /dumb/>
Dav on
</Location>
+ <Location /auth/dumb>
+ Dav on
+ </Location>
</IfDefine>
<IfDefine SVN>
diff --git a/t/perf/.gitignore b/t/perf/.gitignore
new file mode 100644
index 0000000..50f5cc1
--- /dev/null
+++ b/t/perf/.gitignore
@@ -0,0 +1,2 @@
+build/
+test-results/
diff --git a/t/perf/Makefile b/t/perf/Makefile
new file mode 100644
index 0000000..8c47155
--- /dev/null
+++ b/t/perf/Makefile
@@ -0,0 +1,15 @@
+-include ../../config.mak
+export GIT_TEST_OPTIONS
+
+all: perf
+
+perf: pre-clean
+ ./run
+
+pre-clean:
+ rm -rf test-results
+
+clean:
+ rm -rf build "trash directory".* test-results
+
+.PHONY: all perf pre-clean clean
diff --git a/t/perf/README b/t/perf/README
new file mode 100644
index 0000000..b2dbad4
--- /dev/null
+++ b/t/perf/README
@@ -0,0 +1,146 @@
+Git performance tests
+=====================
+
+This directory holds performance testing scripts for git tools. The
+first part of this document describes the various ways in which you
+can run them.
+
+When fixing the tools or adding enhancements, you are strongly
+encouraged to add tests in this directory to cover what you are
+trying to fix or enhance. The later part of this short document
+describes how your test scripts should be organized.
+
+
+Running Tests
+-------------
+
+The easiest way to run tests is to say "make". This runs all
+the tests on the current git repository.
+
+ === Running 2 tests in this tree ===
+ [...]
+ Test this tree
+ ---------------------------------------------------------
+ 0001.1: rev-list --all 0.54(0.51+0.02)
+ 0001.2: rev-list --all --objects 6.14(5.99+0.11)
+ 7810.1: grep worktree, cheap regex 0.16(0.16+0.35)
+ 7810.2: grep worktree, expensive regex 7.90(29.75+0.37)
+ 7810.3: grep --cached, cheap regex 3.07(3.02+0.25)
+ 7810.4: grep --cached, expensive regex 9.39(30.57+0.24)
+
+You can compare multiple repositories and even git revisions with the
+'run' script:
+
+ $ ./run . origin/next /path/to/git-tree p0001-rev-list.sh
+
+where . stands for the current git tree. The full invocation is
+
+ ./run [<revision|directory>...] [--] [<test-script>...]
+
+A '.' argument is implied if you do not pass any other
+revisions/directories.
+
+You can also manually test this or another git build tree, and then
+call the aggregation script to summarize the results:
+
+ $ ./p0001-rev-list.sh
+ [...]
+ $ GIT_BUILD_DIR=/path/to/other/git ./p0001-rev-list.sh
+ [...]
+ $ ./aggregate.perl . /path/to/other/git ./p0001-rev-list.sh
+
+aggregate.perl has the same invocation as 'run', it just does not run
+anything beforehand.
+
+You can set the following variables (also in your config.mak):
+
+ GIT_PERF_REPEAT_COUNT
+ Number of times a test should be repeated for best-of-N
+ measurements. Defaults to 5.
+
+ GIT_PERF_MAKE_OPTS
+ Options to use when automatically building a git tree for
+ performance testing. E.g., -j6 would be useful.
+
+ GIT_PERF_REPO
+ GIT_PERF_LARGE_REPO
+ Repositories to copy for the performance tests. The normal
+ repo should be at least git.git size. The large repo should
+ probably be about linux-2.6.git size for optimal results.
+ Both default to the git.git you are running from.
+
+You can also pass the options taken by ordinary git tests; the most
+useful one is:
+
+--root=<directory>::
+ Create "trash" directories used to store all temporary data during
+ testing under <directory>, instead of the t/ directory.
+ Using this option with a RAM-based filesystem (such as tmpfs)
+ can massively speed up the test suite.
+
+
+Naming Tests
+------------
+
+The performance test files are named as:
+
+ pNNNN-commandname-details.sh
+
+where N is a decimal digit. The same conventions for choosing NNNN as
+for normal tests apply.
+
+
+Writing Tests
+-------------
+
+The perf script starts much like a normal test script, except it
+sources perf-lib.sh:
+
+ #!/bin/sh
+ #
+ # Copyright (c) 2005 Junio C Hamano
+ #
+
+ test_description='xxx performance test'
+ . ./perf-lib.sh
+
+After that you will want to use some of the following:
+
+ test_perf_default_repo # sets up a "normal" repository
+ test_perf_large_repo # sets up a "large" repository
+
+ test_perf_default_repo sub # ditto, in a subdir "sub"
+
+ test_checkout_worktree # if you need the worktree too
+
+At least one of the first two is required!
+
+You can use test_expect_success as usual. For actual performance
+tests, use
+
+ test_perf 'descriptive string' '
+ command1 &&
+ command2
+ '
+
+test_perf spawns a subshell, for lack of better options. This means
+that
+
+* you _must_ export all variables that you need in the subshell
+
+* you _must_ flag all variables that you want to persist from the
+ subshell with 'test_export':
+
+ test_perf 'descriptive string' '
+ foo=$(git rev-parse HEAD) &&
+ test_export foo
+ '
+
+ The so-exported variables are automatically marked for export in the
+ shell executing the perf test. For your convenience, test_export is
+ the same as export in the main shell.
+
+ This feature relies on a bit of magic using 'set' and 'source'.
+ While we have tried to make sure that it can cope with embedded
+ whitespace and other special characters, it will not work with
+ multi-line data.
diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl
new file mode 100755
index 0000000..15f7fc1
--- /dev/null
+++ b/t/perf/aggregate.perl
@@ -0,0 +1,166 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Git;
+
+sub get_times {
+ my $name = shift;
+ open my $fh, "<", $name or return undef;
+ my $line = <$fh>;
+ return undef if not defined $line;
+ close $fh or die "cannot close $name: $!";
+ $line =~ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/
+ or die "bad input line: $line";
+ my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3;
+ return ($rt, $4, $5);
+}
+
+sub format_times {
+ my ($r, $u, $s, $firstr) = @_;
+ if (!defined $r) {
+ return "<missing>";
+ }
+ my $out = sprintf "%.2f(%.2f+%.2f)", $r, $u, $s;
+ if (defined $firstr) {
+ if ($firstr > 0) {
+ $out .= sprintf " %+.1f%%", 100.0*($r-$firstr)/$firstr;
+ } elsif ($r == 0) {
+ $out .= " =";
+ } else {
+ $out .= " +inf";
+ }
+ }
+ return $out;
+}
+
+my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests);
+while (scalar @ARGV) {
+ my $arg = $ARGV[0];
+ my $dir;
+ last if -f $arg or $arg eq "--";
+ if (! -d $arg) {
+ my $rev = Git::command_oneline(qw(rev-parse --verify), $arg);
+ $dir = "build/".$rev;
+ } else {
+ $arg =~ s{/*$}{};
+ $dir = $arg;
+ $dirabbrevs{$dir} = $dir;
+ }
+ push @dirs, $dir;
+ $dirnames{$dir} = $arg;
+ my $prefix = $dir;
+ $prefix =~ tr/^a-zA-Z0-9/_/c;
+ $prefixes{$dir} = $prefix . '.';
+ shift @ARGV;
+}
+
+if (not @dirs) {
+ @dirs = ('.');
+}
+$dirnames{'.'} = $dirabbrevs{'.'} = "this tree";
+$prefixes{'.'} = '';
+
+shift @ARGV if scalar @ARGV and $ARGV[0] eq "--";
+
+@tests = @ARGV;
+if (not @tests) {
+ @tests = glob "p????-*.sh";
+}
+
+my @subtests;
+my %shorttests;
+for my $t (@tests) {
+ $t =~ s{(?:.*/)?(p(\d+)-[^/]+)\.sh$}{$1} or die "bad test name: $t";
+ my $n = $2;
+ my $fname = "test-results/$t.subtests";
+ open my $fp, "<", $fname or die "cannot open $fname: $!";
+ for (<$fp>) {
+ chomp;
+ /^(\d+)$/ or die "malformed subtest line: $_";
+ push @subtests, "$t.$1";
+ $shorttests{"$t.$1"} = "$n.$1";
+ }
+ close $fp or die "cannot close $fname: $!";
+}
+
+sub read_descr {
+ my $name = shift;
+ open my $fh, "<", $name or return "<error reading description>";
+ my $line = <$fh>;
+ close $fh or die "cannot close $name";
+ chomp $line;
+ return $line;
+}
+
+my %descrs;
+my $descrlen = 4; # "Test"
+for my $t (@subtests) {
+ $descrs{$t} = $shorttests{$t}.": ".read_descr("test-results/$t.descr");
+ $descrlen = length $descrs{$t} if length $descrs{$t}>$descrlen;
+}
+
+sub have_duplicate {
+ my %seen;
+ for (@_) {
+ return 1 if exists $seen{$_};
+ $seen{$_} = 1;
+ }
+ return 0;
+}
+sub have_slash {
+ for (@_) {
+ return 1 if m{/};
+ }
+ return 0;
+}
+
+my %newdirabbrevs = %dirabbrevs;
+while (!have_duplicate(values %newdirabbrevs)) {
+ %dirabbrevs = %newdirabbrevs;
+ last if !have_slash(values %dirabbrevs);
+ %newdirabbrevs = %dirabbrevs;
+ for (values %newdirabbrevs) {
+ s{^[^/]*/}{};
+ }
+}
+
+my %times;
+my @colwidth = ((0)x@dirs);
+for my $i (0..$#dirs) {
+ my $d = $dirs[$i];
+ my $w = length (exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d});
+ $colwidth[$i] = $w if $w > $colwidth[$i];
+}
+for my $t (@subtests) {
+ my $firstr;
+ for my $i (0..$#dirs) {
+ my $d = $dirs[$i];
+ $times{$prefixes{$d}.$t} = [get_times("test-results/$prefixes{$d}$t.times")];
+ my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}};
+ my $w = length format_times($r,$u,$s,$firstr);
+ $colwidth[$i] = $w if $w > $colwidth[$i];
+ $firstr = $r unless defined $firstr;
+ }
+}
+my $totalwidth = 3*@dirs+$descrlen;
+$totalwidth += $_ for (@colwidth);
+
+printf "%-${descrlen}s", "Test";
+for my $i (0..$#dirs) {
+ my $d = $dirs[$i];
+ printf " %-$colwidth[$i]s", (exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d});
+}
+print "\n";
+print "-"x$totalwidth, "\n";
+for my $t (@subtests) {
+ printf "%-${descrlen}s", $descrs{$t};
+ my $firstr;
+ for my $i (0..$#dirs) {
+ my $d = $dirs[$i];
+ my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}};
+ printf " %-$colwidth[$i]s", format_times($r,$u,$s,$firstr);
+ $firstr = $r unless defined $firstr;
+ }
+ print "\n";
+}
diff --git a/t/perf/min_time.perl b/t/perf/min_time.perl
new file mode 100755
index 0000000..c1a2717
--- /dev/null
+++ b/t/perf/min_time.perl
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+my $minrt = 1e100;
+my $min;
+
+while (<>) {
+ # [h:]m:s.xx U.xx S.xx
+ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/
+ or die "bad input line: $_";
+ my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3;
+ if ($rt < $minrt) {
+ $min = $_;
+ $minrt = $rt;
+ }
+}
+
+if (!defined $min) {
+ die "no input found";
+}
+
+print $min;
diff --git a/t/perf/p0000-perf-lib-sanity.sh b/t/perf/p0000-perf-lib-sanity.sh
new file mode 100755
index 0000000..cf8e1ef
--- /dev/null
+++ b/t/perf/p0000-perf-lib-sanity.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='Tests whether perf-lib facilities work'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_perf 'test_perf_default_repo works' '
+ foo=$(git rev-parse HEAD) &&
+ test_export foo
+'
+
+test_checkout_worktree
+
+test_perf 'test_checkout_worktree works' '
+ wt=$(find . | wc -l) &&
+ idx=$(git ls-files | wc -l) &&
+ test $wt -gt $idx
+'
+
+baz=baz
+test_export baz
+
+test_expect_success 'test_export works' '
+ echo "$foo" &&
+ test "$foo" = "$(git rev-parse HEAD)" &&
+ echo "$baz" &&
+ test "$baz" = baz
+'
+
+test_perf 'export a weird var' '
+ bar="weird # variable" &&
+ test_export bar
+'
+
+test_expect_success 'test_export works with weird vars' '
+ echo "$bar" &&
+ test "$bar" = "weird # variable"
+'
+
+test_perf 'important variables available in subshells' '
+ test -n "$HOME" &&
+ test -n "$TEST_DIRECTORY" &&
+ test -n "$TRASH_DIRECTORY" &&
+ test -n "$GIT_BUILD_DIR"
+'
+
+test_perf 'test-lib-functions correctly loaded in subshells' '
+ : >a &&
+ test_path_is_file a &&
+ : >b &&
+ test_cmp a b
+'
+
+test_done
diff --git a/t/perf/p0001-rev-list.sh b/t/perf/p0001-rev-list.sh
new file mode 100755
index 0000000..4f71a63
--- /dev/null
+++ b/t/perf/p0001-rev-list.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test_description="Tests history walking performance"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_perf 'rev-list --all' '
+ git rev-list --all >/dev/null
+'
+
+test_perf 'rev-list --all --objects' '
+ git rev-list --all --objects >/dev/null
+'
+
+test_done
diff --git a/t/perf/p4000-diff-algorithms.sh b/t/perf/p4000-diff-algorithms.sh
new file mode 100755
index 0000000..7e00c9d
--- /dev/null
+++ b/t/perf/p4000-diff-algorithms.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+test_description="Tests diff generation performance"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_perf 'log -3000 (baseline)' '
+ git log -3000 >/dev/null
+'
+
+test_perf 'log --raw -3000 (tree-only)' '
+ git log --raw -3000 >/dev/null
+'
+
+test_perf 'log -p -3000 (Myers)' '
+ git log -p -3000 >/dev/null
+'
+
+test_perf 'log -p -3000 --histogram' '
+ git log -p -3000 --histogram >/dev/null
+'
+
+test_perf 'log -p -3000 --patience' '
+ git log -p -3000 --patience >/dev/null
+'
+
+test_done
diff --git a/t/perf/p5302-pack-index.sh b/t/perf/p5302-pack-index.sh
new file mode 100755
index 0000000..6cb5b0d
--- /dev/null
+++ b/t/perf/p5302-pack-index.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description="Tests index-pack performance"
+
+. ./perf-lib.sh
+
+test_perf_large_repo
+
+test_expect_success 'repack' '
+ git repack -ad &&
+ PACK=`ls .git/objects/pack/*.pack | head -n1` &&
+ test -f "$PACK" &&
+ export PACK
+'
+
+test_perf 'index-pack 0 threads' '
+ GIT_DIR=t1 git index-pack --threads=1 --stdin < $PACK
+'
+
+test_perf 'index-pack 1 thread ' '
+ GIT_DIR=t2 GIT_FORCE_THREADS=1 git index-pack --threads=1 --stdin < $PACK
+'
+
+test_perf 'index-pack 2 threads' '
+ GIT_DIR=t3 git index-pack --threads=2 --stdin < $PACK
+'
+
+test_perf 'index-pack 4 threads' '
+ GIT_DIR=t4 git index-pack --threads=4 --stdin < $PACK
+'
+
+test_perf 'index-pack 8 threads' '
+ GIT_DIR=t5 git index-pack --threads=8 --stdin < $PACK
+'
+
+test_perf 'index-pack default number of threads' '
+ GIT_DIR=t6 git index-pack --stdin < $PACK
+'
+
+test_done
diff --git a/t/perf/p7810-grep.sh b/t/perf/p7810-grep.sh
new file mode 100755
index 0000000..9f4ade6
--- /dev/null
+++ b/t/perf/p7810-grep.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description="git-grep performance in various modes"
+
+. ./perf-lib.sh
+
+test_perf_large_repo
+test_checkout_worktree
+
+test_perf 'grep worktree, cheap regex' '
+ git grep some_nonexistent_string || :
+'
+test_perf 'grep worktree, expensive regex' '
+ git grep "^.* *some_nonexistent_string$" || :
+'
+test_perf 'grep --cached, cheap regex' '
+ git grep --cached some_nonexistent_string || :
+'
+test_perf 'grep --cached, expensive regex' '
+ git grep --cached "^.* *some_nonexistent_string$" || :
+'
+
+test_done
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh
new file mode 100644
index 0000000..5580c22
--- /dev/null
+++ b/t/perf/perf-lib.sh
@@ -0,0 +1,202 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Thomas Rast
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see http://www.gnu.org/licenses/ .
+
+# do the --tee work early; it otherwise confuses our careful
+# GIT_BUILD_DIR mangling
+case "$GIT_TEST_TEE_STARTED, $* " in
+done,*)
+ # do not redirect again
+ ;;
+*' --tee '*|*' --va'*)
+ mkdir -p test-results
+ BASE=test-results/$(basename "$0" .sh)
+ (GIT_TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1;
+ echo $? > $BASE.exit) | tee $BASE.out
+ test "$(cat $BASE.exit)" = 0
+ exit
+ ;;
+esac
+
+TEST_DIRECTORY=$(pwd)/..
+TEST_OUTPUT_DIRECTORY=$(pwd)
+if test -z "$GIT_TEST_INSTALLED"; then
+ perf_results_prefix=
+else
+ perf_results_prefix=$(printf "%s" "${GIT_TEST_INSTALLED%/bin-wrappers}" | tr -c "[a-zA-Z0-9]" "[_*]")"."
+ # make the tested dir absolute
+ GIT_TEST_INSTALLED=$(cd "$GIT_TEST_INSTALLED" && pwd)
+fi
+
+TEST_NO_CREATE_REPO=t
+
+. ../test-lib.sh
+
+# Variables from test-lib that are normally internal to the tests; we
+# need to export them for test_perf subshells
+export TEST_DIRECTORY TRASH_DIRECTORY GIT_BUILD_DIR GIT_TEST_CMP
+
+perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results
+mkdir -p "$perf_results_dir"
+rm -f "$perf_results_dir"/$(basename "$0" .sh).subtests
+
+if test -z "$GIT_PERF_REPEAT_COUNT"; then
+ GIT_PERF_REPEAT_COUNT=3
+fi
+die_if_build_dir_not_repo () {
+ if ! ( cd "$TEST_DIRECTORY/.." &&
+ git rev-parse --build-dir >/dev/null 2>&1 ); then
+ error "No $1 defined, and your build directory is not a repo"
+ fi
+}
+
+if test -z "$GIT_PERF_REPO"; then
+ die_if_build_dir_not_repo '$GIT_PERF_REPO'
+ GIT_PERF_REPO=$TEST_DIRECTORY/..
+fi
+if test -z "$GIT_PERF_LARGE_REPO"; then
+ die_if_build_dir_not_repo '$GIT_PERF_LARGE_REPO'
+ GIT_PERF_LARGE_REPO=$TEST_DIRECTORY/..
+fi
+
+test_perf_create_repo_from () {
+ test "$#" = 2 ||
+ error "bug in the test script: not 2 parameters to test-create-repo"
+ repo="$1"
+ source="$2"
+ source_git=$source/$(cd "$source" && git rev-parse --git-dir)
+ mkdir -p "$repo/.git"
+ (
+ cd "$repo/.git" &&
+ { cp -Rl "$source_git/objects" . 2>/dev/null ||
+ cp -R "$source_git/objects" .; } &&
+ for stuff in "$source_git"/*; do
+ case "$stuff" in
+ */objects|*/hooks|*/config)
+ ;;
+ *)
+ cp -R "$stuff" . || break
+ ;;
+ esac
+ done &&
+ cd .. &&
+ git init -q &&
+ mv .git/hooks .git/hooks-disabled 2>/dev/null
+ ) || error "failed to copy repository '$source' to '$repo'"
+}
+
+# call at least one of these to establish an appropriately-sized repository
+test_perf_default_repo () {
+ test_perf_create_repo_from "${1:-$TRASH_DIRECTORY}" "$GIT_PERF_REPO"
+}
+test_perf_large_repo () {
+ if test "$GIT_PERF_LARGE_REPO" = "$GIT_BUILD_DIR"; then
+ echo "warning: \$GIT_PERF_LARGE_REPO is \$GIT_BUILD_DIR." >&2
+ echo "warning: This will work, but may not be a sufficiently large repo" >&2
+ echo "warning: for representative measurements." >&2
+ fi
+ test_perf_create_repo_from "${1:-$TRASH_DIRECTORY}" "$GIT_PERF_LARGE_REPO"
+}
+test_checkout_worktree () {
+ git checkout-index -u -a ||
+ error "git checkout-index failed"
+}
+
+# Performance tests should never fail. If they do, stop immediately
+immediate=t
+
+test_run_perf_ () {
+ test_cleanup=:
+ test_export_="test_cleanup"
+ export test_cleanup test_export_
+ /usr/bin/time -f "%E %U %S" -o test_time.$i "$SHELL" -c '
+. '"$TEST_DIRECTORY"/test-lib-functions.sh'
+test_export () {
+ [ $# != 0 ] || return 0
+ test_export_="$test_export_\\|$1"
+ shift
+ test_export "$@"
+}
+'"$1"'
+ret=$?
+set | sed -n "s'"/'/'\\\\''/g"';s/^\\($test_export_\\)/export '"'&'"'/p" >test_vars
+exit $ret' >&3 2>&4
+ eval_ret=$?
+
+ if test $eval_ret = 0 || test -n "$expecting_failure"
+ then
+ test_eval_ "$test_cleanup"
+ . ./test_vars || error "failed to load updated environment"
+ fi
+ if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
+ echo ""
+ fi
+ return "$eval_ret"
+}
+
+
+test_perf () {
+ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
+ test "$#" = 2 ||
+ error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+ export test_prereq
+ if ! test_skip "$@"
+ then
+ base=$(basename "$0" .sh)
+ echo "$test_count" >>"$perf_results_dir"/$base.subtests
+ echo "$1" >"$perf_results_dir"/$base.$test_count.descr
+ if test -z "$verbose"; then
+ echo -n "perf $test_count - $1:"
+ else
+ echo "perf $test_count - $1:"
+ fi
+ for i in $(seq 1 $GIT_PERF_REPEAT_COUNT); do
+ say >&3 "running: $2"
+ if test_run_perf_ "$2"
+ then
+ if test -z "$verbose"; then
+ echo -n " $i"
+ else
+ echo "* timing run $i/$GIT_PERF_REPEAT_COUNT:"
+ fi
+ else
+ test -z "$verbose" && echo
+ test_failure_ "$@"
+ break
+ fi
+ done
+ if test -z "$verbose"; then
+ echo " ok"
+ else
+ test_ok_ "$1"
+ fi
+ base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count"
+ "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times
+ fi
+ echo >&3 ""
+}
+
+# We extend test_done to print timings at the end (./run disables this
+# and does it after running everything)
+test_at_end_hook_ () {
+ if test -z "$GIT_PERF_AGGREGATING_LATER"; then
+ ( cd "$TEST_DIRECTORY"/perf && ./aggregate.perl $(basename "$0") )
+ fi
+}
+
+test_export () {
+ export "$@"
+}
diff --git a/t/perf/run b/t/perf/run
new file mode 100755
index 0000000..cfd7012
--- /dev/null
+++ b/t/perf/run
@@ -0,0 +1,82 @@
+#!/bin/sh
+
+case "$1" in
+ --help)
+ echo "usage: $0 [other_git_tree...] [--] [test_scripts]"
+ exit 0
+ ;;
+esac
+
+die () {
+ echo >&2 "error: $*"
+ exit 1
+}
+
+run_one_dir () {
+ if test $# -eq 0; then
+ set -- p????-*.sh
+ fi
+ echo "=== Running $# tests in ${GIT_TEST_INSTALLED:-this tree} ==="
+ for t in "$@"; do
+ ./$t $GIT_TEST_OPTS
+ done
+}
+
+unpack_git_rev () {
+ rev=$1
+ mkdir -p build/$rev
+ (cd "$(git rev-parse --show-cdup)" && git archive --format=tar $rev) |
+ (cd build/$rev && tar x)
+}
+build_git_rev () {
+ rev=$1
+ cp ../../config.mak build/$rev/config.mak
+ (cd build/$rev && make $GIT_PERF_MAKE_OPTS) ||
+ die "failed to build revision '$mydir'"
+}
+
+run_dirs_helper () {
+ mydir=${1%/}
+ shift
+ while test $# -gt 0 -a "$1" != -- -a ! -f "$1"; do
+ shift
+ done
+ if test $# -gt 0 -a "$1" = --; then
+ shift
+ fi
+ if [ ! -d "$mydir" ]; then
+ rev=$(git rev-parse --verify "$mydir" 2>/dev/null) ||
+ die "'$mydir' is neither a directory nor a valid revision"
+ if [ ! -d build/$rev ]; then
+ unpack_git_rev $rev
+ fi
+ build_git_rev $rev
+ mydir=build/$rev
+ fi
+ if test "$mydir" = .; then
+ unset GIT_TEST_INSTALLED
+ else
+ GIT_TEST_INSTALLED="$mydir/bin-wrappers"
+ export GIT_TEST_INSTALLED
+ fi
+ run_one_dir "$@"
+}
+
+run_dirs () {
+ while test $# -gt 0 -a "$1" != -- -a ! -f "$1"; do
+ run_dirs_helper "$@"
+ shift
+ done
+}
+
+GIT_PERF_AGGREGATING_LATER=t
+export GIT_PERF_AGGREGATING_LATER
+
+cd "$(dirname $0)"
+. ../../GIT-BUILD-OPTIONS
+
+if test $# = 0 -o "$1" = -- -o -f "$1"; then
+ set -- . "$@"
+fi
+run_dirs "$@"
+./aggregate.perl "$@"
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index f4e8f43..ccb5435 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -34,69 +34,69 @@ fi
# git init has been done in an empty repository.
# make sure it is empty.
-find .git/objects -type f -print >should-be-empty
-test_expect_success \
- '.git/objects should be empty after git init in an empty repo.' \
- 'cmp -s /dev/null should-be-empty'
+test_expect_success '.git/objects should be empty after git init in an empty repo' '
+ find .git/objects -type f -print >should-be-empty &&
+ test_line_count = 0 should-be-empty
+'
# also it should have 2 subdirectories; no fan-out anymore, pack, and info.
# 3 is counting "objects" itself
-find .git/objects -type d -print >full-of-directories
-test_expect_success \
- '.git/objects should have 3 subdirectories.' \
- 'test $(wc -l < full-of-directories) = 3'
+test_expect_success '.git/objects should have 3 subdirectories' '
+ find .git/objects -type d -print >full-of-directories &&
+ test_line_count = 3 full-of-directories
+'
################################################################
# Test harness
test_expect_success 'success is reported like this' '
- :
+ :
'
test_expect_failure 'pretend we have a known breakage' '
- false
+ false
'
test_expect_success 'pretend we have fixed a known breakage (run in sub test-lib)' "
- mkdir passing-todo &&
- (cd passing-todo &&
- cat >passing-todo.sh <<EOF &&
-#!$SHELL_PATH
-
-test_description='A passing TODO test
-
-This is run in a sub test-lib so that we do not get incorrect passing
-metrics
-'
-
-# Point to the t/test-lib.sh, which isn't in ../ as usual
-TEST_DIRECTORY=\"$TEST_DIRECTORY\"
-. \"\$TEST_DIRECTORY\"/test-lib.sh
-
-test_expect_failure 'pretend we have fixed a known breakage' '
- :
-'
-
-test_done
-EOF
- chmod +x passing-todo.sh &&
- ./passing-todo.sh >out 2>err &&
- ! test -s err &&
-sed -e 's/^> //' >expect <<EOF &&
-> ok 1 - pretend we have fixed a known breakage # TODO known breakage
-> # fixed 1 known breakage(s)
-> # passed all 1 test(s)
-> 1..1
-EOF
- test_cmp expect out)
+ mkdir passing-todo &&
+ (cd passing-todo &&
+ cat >passing-todo.sh <<-EOF &&
+ #!$SHELL_PATH
+
+ test_description='A passing TODO test
+
+ This is run in a sub test-lib so that we do not get incorrect
+ passing metrics
+ '
+
+ # Point to the t/test-lib.sh, which isn't in ../ as usual
+ TEST_DIRECTORY=\"$TEST_DIRECTORY\"
+ . \"\$TEST_DIRECTORY\"/test-lib.sh
+
+ test_expect_failure 'pretend we have fixed a known breakage' '
+ :
+ '
+
+ test_done
+ EOF
+ chmod +x passing-todo.sh &&
+ ./passing-todo.sh >out 2>err &&
+ ! test -s err &&
+ sed -e 's/^> //' >expect <<-\\EOF &&
+ > ok 1 - pretend we have fixed a known breakage # TODO known breakage
+ > # fixed 1 known breakage(s)
+ > # passed all 1 test(s)
+ > 1..1
+ EOF
+ test_cmp expect out)
"
test_set_prereq HAVEIT
haveit=no
test_expect_success HAVEIT 'test runs if prerequisite is satisfied' '
- test_have_prereq HAVEIT &&
- haveit=yes
+ test_have_prereq HAVEIT &&
+ haveit=yes
'
donthaveit=yes
test_expect_success DONTHAVEIT 'unmet prerequisite causes test to be skipped' '
- donthaveit=no
+ donthaveit=no
'
if test $haveit$donthaveit != yesyes
then
@@ -107,17 +107,17 @@ fi
test_set_prereq HAVETHIS
haveit=no
test_expect_success HAVETHIS,HAVEIT 'test runs if prerequisites are satisfied' '
- test_have_prereq HAVEIT &&
- test_have_prereq HAVETHIS &&
- haveit=yes
+ test_have_prereq HAVEIT &&
+ test_have_prereq HAVETHIS &&
+ haveit=yes
'
donthaveit=yes
test_expect_success HAVEIT,DONTHAVEIT 'unmet prerequisites causes test to be skipped' '
- donthaveit=no
+ donthaveit=no
'
donthaveiteither=yes
test_expect_success DONTHAVEIT,HAVEIT 'unmet prerequisites causes test to be skipped' '
- donthaveiteither=no
+ donthaveiteither=no
'
if test $haveit$donthaveit$donthaveiteither != yesyesyes
then
@@ -127,7 +127,7 @@ fi
clean=no
test_expect_success 'tests clean up after themselves' '
- test_when_finished clean=yes
+ test_when_finished clean=yes
'
if test $clean != yes
@@ -137,106 +137,100 @@ then
fi
test_expect_success 'tests clean up even on failures' "
- mkdir failing-cleanup &&
- (cd failing-cleanup &&
- cat >failing-cleanup.sh <<EOF &&
-#!$SHELL_PATH
-
-test_description='Failing tests with cleanup commands'
-
-# Point to the t/test-lib.sh, which isn't in ../ as usual
-TEST_DIRECTORY=\"$TEST_DIRECTORY\"
-. \"\$TEST_DIRECTORY\"/test-lib.sh
-
-test_expect_success 'tests clean up even after a failure' '
- touch clean-after-failure &&
- test_when_finished rm clean-after-failure &&
- (exit 1)
-'
-
-test_expect_success 'failure to clean up causes the test to fail' '
- test_when_finished \"(exit 2)\"
-'
-
-test_done
-EOF
- chmod +x failing-cleanup.sh &&
- test_must_fail ./failing-cleanup.sh >out 2>err &&
- ! test -s err &&
- ! test -f \"trash directory.failing-cleanup/clean-after-failure\" &&
-sed -e 's/Z$//' -e 's/^> //' >expect <<\EOF &&
-> not ok - 1 tests clean up even after a failure
-> # Z
-> # touch clean-after-failure &&
-> # test_when_finished rm clean-after-failure &&
-> # (exit 1)
-> # Z
-> not ok - 2 failure to clean up causes the test to fail
-> # Z
-> # test_when_finished \"(exit 2)\"
-> # Z
-> # failed 2 among 2 test(s)
-> 1..2
-EOF
- test_cmp expect out)
+ mkdir failing-cleanup &&
+ (
+ cd failing-cleanup &&
+
+ cat >failing-cleanup.sh <<-EOF &&
+ #!$SHELL_PATH
+
+ test_description='Failing tests with cleanup commands'
+
+ # Point to the t/test-lib.sh, which isn't in ../ as usual
+ TEST_DIRECTORY=\"$TEST_DIRECTORY\"
+ . \"\$TEST_DIRECTORY\"/test-lib.sh
+
+ test_expect_success 'tests clean up even after a failure' '
+ touch clean-after-failure &&
+ test_when_finished rm clean-after-failure &&
+ (exit 1)
+ '
+ test_expect_success 'failure to clean up causes the test to fail' '
+ test_when_finished \"(exit 2)\"
+ '
+ test_done
+
+ EOF
+
+ chmod +x failing-cleanup.sh &&
+ test_must_fail ./failing-cleanup.sh >out 2>err &&
+ ! test -s err &&
+ ! test -f \"trash directory.failing-cleanup/clean-after-failure\" &&
+ sed -e 's/Z$//' -e 's/^> //' >expect <<-\\EOF &&
+ > not ok - 1 tests clean up even after a failure
+ > # Z
+ > # touch clean-after-failure &&
+ > # test_when_finished rm clean-after-failure &&
+ > # (exit 1)
+ > # Z
+ > not ok - 2 failure to clean up causes the test to fail
+ > # Z
+ > # test_when_finished \"(exit 2)\"
+ > # Z
+ > # failed 2 among 2 test(s)
+ > 1..2
+ EOF
+ test_cmp expect out
+ )
"
################################################################
# Basics of the basics
# updating a new file without --add should fail.
-test_expect_success 'git update-index without --add should fail adding.' '
- test_must_fail git update-index should-be-empty
+test_expect_success 'git update-index without --add should fail adding' '
+ test_must_fail git update-index should-be-empty
'
# and with --add it should succeed, even if it is empty (it used to fail).
-test_expect_success \
- 'git update-index with --add should succeed.' \
- 'git update-index --add should-be-empty'
+test_expect_success 'git update-index with --add should succeed' '
+ git update-index --add should-be-empty
+'
-test_expect_success \
- 'writing tree out with git write-tree' \
- 'tree=$(git write-tree)'
+test_expect_success 'writing tree out with git write-tree' '
+ tree=$(git write-tree)
+'
# we know the shape and contents of the tree and know the object ID for it.
-test_expect_success \
- 'validate object ID of a known tree.' \
- 'test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a'
+test_expect_success 'validate object ID of a known tree' '
+ test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a
+ '
# Removing paths.
-rm -f should-be-empty full-of-directories
-test_expect_success 'git update-index without --remove should fail removing.' '
- test_must_fail git update-index should-be-empty
+test_expect_success 'git update-index without --remove should fail removing' '
+ rm -f should-be-empty full-of-directories &&
+ test_must_fail git update-index should-be-empty
'
-test_expect_success \
- 'git update-index with --remove should be able to remove.' \
- 'git update-index --remove should-be-empty'
+test_expect_success 'git update-index with --remove should be able to remove' '
+ git update-index --remove should-be-empty
+'
# Empty tree can be written with recent write-tree.
-test_expect_success \
- 'git write-tree should be able to write an empty tree.' \
- 'tree=$(git write-tree)'
+test_expect_success 'git write-tree should be able to write an empty tree' '
+ tree=$(git write-tree)
+'
-test_expect_success \
- 'validate object ID of a known tree.' \
- 'test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904'
+test_expect_success 'validate object ID of a known tree' '
+ test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+'
# Various types of objects
+
# Some filesystems do not support symblic links; on such systems
# some expected values are different
-mkdir path2 path3 path3/subp3
-paths='path0 path2/file2 path3/file3 path3/subp3/file3'
-for p in $paths
-do
- echo "hello $p" >$p
-done
if test_have_prereq SYMLINKS
then
- for p in $paths
- do
- ln -s "hello $p" ${p}sym
- done
expectfilter=cat
expectedtree=087704a96baf1c2d1c869a8b084481e121c88b5b
expectedptree1=21ae8269cacbe57ae09138dcc3a2887f904d02b3
@@ -248,135 +242,154 @@ else
expectedptree2=ce580448f0148b985a513b693fdf7d802cacb44f
fi
-test_expect_success \
- 'adding various types of objects with git update-index --add.' \
- 'find path* ! -type d -print | xargs git update-index --add'
+
+test_expect_success 'adding various types of objects with git update-index --add' '
+ mkdir path2 path3 path3/subp3 &&
+ paths="path0 path2/file2 path3/file3 path3/subp3/file3" &&
+ (
+ for p in $paths
+ do
+ echo "hello $p" >$p || exit 1
+ if test_have_prereq SYMLINKS
+ then
+ ln -s "hello $p" ${p}sym || exit 1
+ fi
+ done
+ ) &&
+ find path* ! -type d -print | xargs git update-index --add
+'
# Show them and see that matches what we expect.
-test_expect_success \
- 'showing stage with git ls-files --stage' \
- 'git ls-files --stage >current'
-
-$expectfilter >expected <<\EOF
-100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0
-120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym
-100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0 path2/file2
-120000 d8ce161addc5173867a3c3c730924388daedbc38 0 path2/file2sym
-100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0 path3/file3
-120000 8599103969b43aff7e430efea79ca4636466794f 0 path3/file3sym
-100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0 path3/subp3/file3
-120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0 path3/subp3/file3sym
-EOF
-test_expect_success \
- 'validate git ls-files output for a known tree.' \
- 'test_cmp expected current'
-
-test_expect_success \
- 'writing tree out with git write-tree.' \
- 'tree=$(git write-tree)'
-test_expect_success \
- 'validate object ID for a known tree.' \
- 'test "$tree" = "$expectedtree"'
-
-test_expect_success \
- 'showing tree with git ls-tree' \
- 'git ls-tree $tree >current'
-cat >expected <<\EOF
-100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
-120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
-040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
-040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
-EOF
-test_expect_success SYMLINKS \
- 'git ls-tree output for a known tree.' \
- 'test_cmp expected current'
+test_expect_success 'showing stage with git ls-files --stage' '
+ git ls-files --stage >current
+'
+
+test_expect_success 'validate git ls-files output for a known tree' '
+ $expectfilter >expected <<-\EOF &&
+ 100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0
+ 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym
+ 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0 path2/file2
+ 120000 d8ce161addc5173867a3c3c730924388daedbc38 0 path2/file2sym
+ 100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0 path3/file3
+ 120000 8599103969b43aff7e430efea79ca4636466794f 0 path3/file3sym
+ 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0 path3/subp3/file3
+ 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0 path3/subp3/file3sym
+ EOF
+ test_cmp expected current
+'
+
+test_expect_success 'writing tree out with git write-tree' '
+ tree=$(git write-tree)
+'
+
+test_expect_success 'validate object ID for a known tree' '
+ test "$tree" = "$expectedtree"
+'
+
+test_expect_success 'showing tree with git ls-tree' '
+ git ls-tree $tree >current
+'
+
+test_expect_success SYMLINKS 'git ls-tree output for a known tree' '
+ cat >expected <<-\EOF &&
+ 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
+ 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
+ 040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
+ 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
+ EOF
+ test_cmp expected current
+'
# This changed in ls-tree pathspec change -- recursive does
# not show tree nodes anymore.
-test_expect_success \
- 'showing tree with git ls-tree -r' \
- 'git ls-tree -r $tree >current'
-$expectfilter >expected <<\EOF
-100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
-120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
-100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
-120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym
-100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3
-120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym
-100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3
-120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
-EOF
-test_expect_success \
- 'git ls-tree -r output for a known tree.' \
- 'test_cmp expected current'
+test_expect_success 'showing tree with git ls-tree -r' '
+ git ls-tree -r $tree >current
+'
+
+test_expect_success 'git ls-tree -r output for a known tree' '
+ $expectfilter >expected <<-\EOF &&
+ 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
+ 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
+ 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
+ 120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym
+ 100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3
+ 120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym
+ 100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3
+ 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
+ EOF
+ test_cmp expected current
+'
# But with -r -t we can have both.
-test_expect_success \
- 'showing tree with git ls-tree -r -t' \
- 'git ls-tree -r -t $tree >current'
-cat >expected <<\EOF
-100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
-120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
-040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
-100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
-120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym
-040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
-100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3
-120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym
-040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3
-100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3
-120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
-EOF
-test_expect_success SYMLINKS \
- 'git ls-tree -r output for a known tree.' \
- 'test_cmp expected current'
-
-test_expect_success \
- 'writing partial tree out with git write-tree --prefix.' \
- 'ptree=$(git write-tree --prefix=path3)'
-test_expect_success \
- 'validate object ID for a known tree.' \
- 'test "$ptree" = "$expectedptree1"'
-
-test_expect_success \
- 'writing partial tree out with git write-tree --prefix.' \
- 'ptree=$(git write-tree --prefix=path3/subp3)'
-test_expect_success \
- 'validate object ID for a known tree.' \
- 'test "$ptree" = "$expectedptree2"'
-
-cat >badobjects <<EOF
-100644 blob 1000000000000000000000000000000000000000 dir/file1
-100644 blob 2000000000000000000000000000000000000000 dir/file2
-100644 blob 3000000000000000000000000000000000000000 dir/file3
-100644 blob 4000000000000000000000000000000000000000 dir/file4
-100644 blob 5000000000000000000000000000000000000000 dir/file5
-EOF
+test_expect_success 'showing tree with git ls-tree -r -t' '
+ git ls-tree -r -t $tree >current
+'
-rm .git/index
-test_expect_success \
- 'put invalid objects into the index.' \
- 'git update-index --index-info < badobjects'
+test_expect_success SYMLINKS 'git ls-tree -r output for a known tree' '
+ cat >expected <<-\EOF &&
+ 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
+ 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
+ 040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
+ 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
+ 120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym
+ 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
+ 100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3
+ 120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym
+ 040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3
+ 100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3
+ 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
+ EOF
+ test_cmp expected current
+'
-test_expect_success 'writing this tree without --missing-ok.' '
- test_must_fail git write-tree
+test_expect_success 'writing partial tree out with git write-tree --prefix' '
+ ptree=$(git write-tree --prefix=path3)
'
-test_expect_success \
- 'writing this tree with --missing-ok.' \
- 'git write-tree --missing-ok'
+test_expect_success 'validate object ID for a known tree' '
+ test "$ptree" = "$expectedptree1"
+'
+
+test_expect_success 'writing partial tree out with git write-tree --prefix' '
+ ptree=$(git write-tree --prefix=path3/subp3)
+'
+
+test_expect_success 'validate object ID for a known tree' '
+ test "$ptree" = "$expectedptree2"
+'
+
+test_expect_success 'put invalid objects into the index' '
+ rm -f .git/index &&
+ cat >badobjects <<-\EOF &&
+ 100644 blob 1000000000000000000000000000000000000000 dir/file1
+ 100644 blob 2000000000000000000000000000000000000000 dir/file2
+ 100644 blob 3000000000000000000000000000000000000000 dir/file3
+ 100644 blob 4000000000000000000000000000000000000000 dir/file4
+ 100644 blob 5000000000000000000000000000000000000000 dir/file5
+ EOF
+ git update-index --index-info <badobjects
+'
+
+test_expect_success 'writing this tree without --missing-ok' '
+ test_must_fail git write-tree
+'
+
+test_expect_success 'writing this tree with --missing-ok' '
+ git write-tree --missing-ok
+'
################################################################
-rm .git/index
-test_expect_success \
- 'git read-tree followed by write-tree should be idempotent.' \
- 'git read-tree $tree &&
- test -f .git/index &&
- newtree=$(git write-tree) &&
- test "$newtree" = "$tree"'
-
-$expectfilter >expected <<\EOF
+test_expect_success 'git read-tree followed by write-tree should be idempotent' '
+ rm -f .git/index
+ git read-tree $tree &&
+ test -f .git/index &&
+ newtree=$(git write-tree) &&
+ test "$newtree" = "$tree"
+'
+
+test_expect_success 'validate git diff-files output for a know cache/work tree state' '
+ $expectfilter >expected <<\EOF &&
:100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M path0
:120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M path0sym
:100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M path2/file2
@@ -386,45 +399,47 @@ $expectfilter >expected <<\EOF
:100644 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0000000000000000000000000000000000000000 M path3/subp3/file3
:120000 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0000000000000000000000000000000000000000 M path3/subp3/file3sym
EOF
-test_expect_success \
- 'validate git diff-files output for a know cache/work tree state.' \
- 'git diff-files >current && test_cmp current expected >/dev/null'
+ git diff-files >current &&
+ test_cmp current expected
+'
-test_expect_success \
- 'git update-index --refresh should succeed.' \
- 'git update-index --refresh'
+test_expect_success 'git update-index --refresh should succeed' '
+ git update-index --refresh
+'
-test_expect_success \
- 'no diff after checkout and git update-index --refresh.' \
- 'git diff-files >current && cmp -s current /dev/null'
+test_expect_success 'no diff after checkout and git update-index --refresh' '
+ git diff-files >current &&
+ cmp -s current /dev/null
+'
################################################################
P=$expectedtree
-test_expect_success \
- 'git commit-tree records the correct tree in a commit.' \
- 'commit0=$(echo NO | git commit-tree $P) &&
- tree=$(git show --pretty=raw $commit0 |
- sed -n -e "s/^tree //p" -e "/^author /q") &&
- test "z$tree" = "z$P"'
-
-test_expect_success \
- 'git commit-tree records the correct parent in a commit.' \
- 'commit1=$(echo NO | git commit-tree $P -p $commit0) &&
- parent=$(git show --pretty=raw $commit1 |
- sed -n -e "s/^parent //p" -e "/^author /q") &&
- test "z$commit0" = "z$parent"'
-
-test_expect_success \
- 'git commit-tree omits duplicated parent in a commit.' \
- 'commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) &&
- parent=$(git show --pretty=raw $commit2 |
- sed -n -e "s/^parent //p" -e "/^author /q" |
- sort -u) &&
- test "z$commit0" = "z$parent" &&
- numparent=$(git show --pretty=raw $commit2 |
- sed -n -e "s/^parent //p" -e "/^author /q" |
- wc -l) &&
- test $numparent = 1'
+
+test_expect_success 'git commit-tree records the correct tree in a commit' '
+ commit0=$(echo NO | git commit-tree $P) &&
+ tree=$(git show --pretty=raw $commit0 |
+ sed -n -e "s/^tree //p" -e "/^author /q") &&
+ test "z$tree" = "z$P"
+'
+
+test_expect_success 'git commit-tree records the correct parent in a commit' '
+ commit1=$(echo NO | git commit-tree $P -p $commit0) &&
+ parent=$(git show --pretty=raw $commit1 |
+ sed -n -e "s/^parent //p" -e "/^author /q") &&
+ test "z$commit0" = "z$parent"
+'
+
+test_expect_success 'git commit-tree omits duplicated parent in a commit' '
+ commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) &&
+ parent=$(git show --pretty=raw $commit2 |
+ sed -n -e "s/^parent //p" -e "/^author /q" |
+ sort -u) &&
+ test "z$commit0" = "z$parent" &&
+ numparent=$(git show --pretty=raw $commit2 |
+ sed -n -e "s/^parent //p" -e "/^author /q" |
+ wc -l) &&
+ test $numparent = 1
+'
test_expect_success 'update-index D/F conflict' '
mv path0 tmp &&
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 61b5a2e..51f3045 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -5,20 +5,17 @@ test_description=gitattributes
. ./test-lib.sh
attr_check () {
+ path="$1" expect="$2"
- path="$1"
- expect="$2"
-
- git check-attr test -- "$path" >actual &&
+ git $3 check-attr test -- "$path" >actual 2>err &&
echo "$path: test: $2" >expect &&
- test_cmp expect actual
-
+ test_cmp expect actual &&
+ test_line_count = 0 err
}
test_expect_success 'setup' '
-
- mkdir -p a/b/d a/c &&
+ mkdir -p a/b/d a/c b &&
(
echo "[attr]notest !test"
echo "f test=f"
@@ -26,6 +23,7 @@ test_expect_success 'setup' '
echo "onoff test -test"
echo "offon -test test"
echo "no notest"
+ echo "A/e/F test=A/e/F"
) >.gitattributes &&
(
echo "g test=a/g" &&
@@ -35,15 +33,43 @@ test_expect_success 'setup' '
echo "h test=a/b/h" &&
echo "d/* test=a/b/d/*"
echo "d/yes notest"
- ) >a/b/.gitattributes
+ ) >a/b/.gitattributes &&
(
echo "global test=global"
- ) >"$HOME"/global-gitattributes
+ ) >"$HOME"/global-gitattributes &&
+ cat <<-EOF >expect-all
+ f: test: f
+ a/f: test: f
+ a/c/f: test: f
+ a/g: test: a/g
+ a/b/g: test: a/b/g
+ b/g: test: unspecified
+ a/b/h: test: a/b/h
+ a/b/d/g: test: a/b/d/*
+ onoff: test: unset
+ offon: test: set
+ no: notest: set
+ no: test: unspecified
+ a/b/d/no: notest: set
+ a/b/d/no: test: a/b/d/*
+ a/b/d/yes: notest: set
+ a/b/d/yes: test: unspecified
+ EOF
+'
+test_expect_success 'command line checks' '
+ test_must_fail git check-attr &&
+ test_must_fail git check-attr -- &&
+ test_must_fail git check-attr test &&
+ test_must_fail git check-attr test -- &&
+ test_must_fail git check-attr -- f &&
+ echo "f" | test_must_fail git check-attr --stdin &&
+ echo "f" | test_must_fail git check-attr --stdin -- f &&
+ echo "f" | test_must_fail git check-attr --stdin test -- f &&
+ test_must_fail git check-attr "" -- f
'
test_expect_success 'attribute test' '
-
attr_check f f &&
attr_check a/f f &&
attr_check a/c/f f &&
@@ -57,7 +83,80 @@ test_expect_success 'attribute test' '
attr_check no unspecified &&
attr_check a/b/d/no "a/b/d/*" &&
attr_check a/b/d/yes unspecified
+'
+
+test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
+
+ test_must_fail attr_check F f "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
+ test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
+ test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+ attr_check NO unspecified "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+ attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
+ test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
+
+'
+
+test_expect_success 'attribute matching is case insensitive when core.ignorecase=1' '
+
+ attr_check F f "-c core.ignorecase=1" &&
+ attr_check a/F f "-c core.ignorecase=1" &&
+ attr_check a/c/F f "-c core.ignorecase=1" &&
+ attr_check a/G a/g "-c core.ignorecase=1" &&
+ attr_check a/B/g a/b/g "-c core.ignorecase=1" &&
+ attr_check a/b/G a/b/g "-c core.ignorecase=1" &&
+ attr_check a/b/H a/b/h "-c core.ignorecase=1" &&
+ attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=1" &&
+ attr_check oNoFf unset "-c core.ignorecase=1" &&
+ attr_check oFfOn set "-c core.ignorecase=1" &&
+ attr_check NO unspecified "-c core.ignorecase=1" &&
+ attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=1" &&
+ attr_check a/b/d/YES unspecified "-c core.ignorecase=1" &&
+ attr_check a/E/f "A/e/F" "-c core.ignorecase=1"
+
+'
+
+test_expect_success 'check whether FS is case-insensitive' '
+ mkdir junk &&
+ echo good >junk/CamelCase &&
+ echo bad >junk/camelcase &&
+ if test "$(cat junk/CamelCase)" != good
+ then
+ test_set_prereq CASE_INSENSITIVE_FS
+ fi
+'
+test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
+ test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
+ test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+ attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
+ attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
+ attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
+'
+
+test_expect_success 'unnormalized paths' '
+ attr_check ./f f &&
+ attr_check ./a/g a/g &&
+ attr_check a/./g a/g &&
+ attr_check a/c/../b/g a/b/g
+'
+
+test_expect_success 'relative paths' '
+ (cd a && attr_check ../f f) &&
+ (cd a && attr_check f f) &&
+ (cd a && attr_check i a/i) &&
+ (cd a && attr_check g a/g) &&
+ (cd a && attr_check b/g a/b/g) &&
+ (cd b && attr_check ../a/f f) &&
+ (cd b && attr_check ../a/g a/g) &&
+ (cd b && attr_check ../a/b/g a/b/g)
'
test_expect_success 'prefixes are not confused with leading directories' '
@@ -76,48 +175,43 @@ test_expect_success 'core.attributesfile' '
attr_check global global &&
git config core.attributesfile "~/global-gitattributes" &&
attr_check global global &&
- echo "global test=precedence" >> .gitattributes &&
+ echo "global test=precedence" >>.gitattributes &&
attr_check global precedence
'
test_expect_success 'attribute test: read paths from stdin' '
-
- cat <<EOF > expect &&
-f: test: f
-a/f: test: f
-a/c/f: test: f
-a/g: test: a/g
-a/b/g: test: a/b/g
-b/g: test: unspecified
-a/b/h: test: a/b/h
-a/b/d/g: test: a/b/d/*
-onoff: test: unset
-offon: test: set
-no: test: unspecified
-a/b/d/no: test: a/b/d/*
-a/b/d/yes: test: unspecified
-EOF
-
- sed -e "s/:.*//" < expect | git check-attr --stdin test > actual &&
+ grep -v notest <expect-all >expect &&
+ sed -e "s/:.*//" <expect | git check-attr --stdin test >actual &&
test_cmp expect actual
'
-test_expect_success 'root subdir attribute test' '
+test_expect_success 'attribute test: --all option' '
+ grep -v unspecified <expect-all | sort >specified-all &&
+ sed -e "s/:.*//" <expect-all | uniq >stdin-all &&
+ git check-attr --stdin --all <stdin-all | sort >actual &&
+ test_cmp specified-all actual
+'
+
+test_expect_success 'attribute test: --cached option' '
+ : >empty &&
+ git check-attr --cached --stdin --all <stdin-all | sort >actual &&
+ test_cmp empty actual &&
+ git add .gitattributes a/.gitattributes a/b/.gitattributes &&
+ git check-attr --cached --stdin --all <stdin-all | sort >actual &&
+ test_cmp specified-all actual
+'
+test_expect_success 'root subdir attribute test' '
attr_check a/i a/i &&
attr_check subdir/a/i unspecified
-
'
test_expect_success 'setup bare' '
-
git clone --bare . bare.git &&
cd bare.git
-
'
test_expect_success 'bare repository: check that .gitattribute is ignored' '
-
(
echo "f test=f"
echo "a/i test=a/i"
@@ -127,11 +221,16 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' '
attr_check a/c/f unspecified &&
attr_check a/i unspecified &&
attr_check subdir/a/i unspecified
+'
+test_expect_success 'bare repository: check that --cached honors index' '
+ GIT_INDEX_FILE=../.git/index \
+ git check-attr --cached --stdin --all <../stdin-all |
+ sort >actual &&
+ test_cmp ../specified-all actual
'
test_expect_success 'bare repository: test info/attributes' '
-
(
echo "f test=f"
echo "a/i test=a/i"
@@ -141,7 +240,6 @@ test_expect_success 'bare repository: test info/attributes' '
attr_check a/c/f f &&
attr_check a/i a/i &&
attr_check subdir/a/i unspecified
-
'
test_done
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index 9078b84..e50f0f7 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -66,31 +66,48 @@ test_expect_success expanded_in_repo '
echo "\$Id:NoSpaceAtEitherEnd\$"
echo "\$Id: NoTerminatingSymbol"
echo "\$Id: Foreign Commit With Spaces \$"
- echo "\$Id: NoTerminatingSymbolAtEOF"
- } > expanded-keywords &&
+ } >expanded-keywords.0 &&
+
+ {
+ cat expanded-keywords.0 &&
+ printf "\$Id: NoTerminatingSymbolAtEOF"
+ } >expanded-keywords &&
+ cat expanded-keywords >expanded-keywords-crlf &&
+ git add expanded-keywords expanded-keywords-crlf &&
+ git commit -m "File with keywords expanded" &&
+ id=$(git rev-parse --verify :expanded-keywords) &&
{
echo "File with expanded keywords"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
echo "\$Id: NoTerminatingSymbol"
echo "\$Id: Foreign Commit With Spaces \$"
- echo "\$Id: NoTerminatingSymbolAtEOF"
- } > expected-output &&
-
- git add expanded-keywords &&
- git commit -m "File with keywords expanded" &&
+ } >expected-output.0 &&
+ {
+ cat expected-output.0 &&
+ printf "\$Id: NoTerminatingSymbolAtEOF"
+ } >expected-output &&
+ {
+ append_cr <expected-output.0 &&
+ printf "\$Id: NoTerminatingSymbolAtEOF"
+ } >expected-output-crlf &&
+ {
+ echo "expanded-keywords ident"
+ echo "expanded-keywords-crlf ident text eol=crlf"
+ } >>.gitattributes &&
- echo "expanded-keywords ident" >> .gitattributes &&
+ rm -f expanded-keywords expanded-keywords-crlf &&
- rm -f expanded-keywords &&
git checkout -- expanded-keywords &&
- cat expanded-keywords &&
- cmp expanded-keywords expected-output
+ test_cmp expanded-keywords expected-output &&
+
+ git checkout -- expanded-keywords-crlf &&
+ test_cmp expanded-keywords-crlf expected-output-crlf
'
# The use of %f in a filter definition is expanded to the path to
@@ -136,4 +153,41 @@ test_expect_success 'filter shell-escaped filenames' '
:
'
+test_expect_success 'required filter success' '
+ git config filter.required.smudge cat &&
+ git config filter.required.clean cat &&
+ git config filter.required.required true &&
+
+ echo "*.r filter=required" >.gitattributes &&
+
+ echo test >test.r &&
+ git add test.r &&
+ rm -f test.r &&
+ git checkout -- test.r
+'
+
+test_expect_success 'required filter smudge failure' '
+ git config filter.failsmudge.smudge false &&
+ git config filter.failsmudge.clean cat &&
+ git config filter.failsmudge.required true &&
+
+ echo "*.fs filter=failsmudge" >.gitattributes &&
+
+ echo test >test.fs &&
+ git add test.fs &&
+ rm -f test.fs &&
+ test_must_fail git checkout -- test.fs
+'
+
+test_expect_success 'required filter clean failure' '
+ git config filter.failclean.smudge cat &&
+ git config filter.failclean.clean false &&
+ git config filter.failclean.required true &&
+
+ echo "*.fc filter=failclean" >.gitattributes &&
+
+ echo test >test.fc &&
+ test_must_fail git add test.fc
+'
+
test_done
diff --git a/t/t0023-crlf-am.sh b/t/t0023-crlf-am.sh
index aaed725..f9bbb91 100755
--- a/t/t0023-crlf-am.sh
+++ b/t/t0023-crlf-am.sh
@@ -11,7 +11,7 @@ Date: Thu, 23 Aug 2007 13:00:00 +0200
Subject: test1
---
- foo | 1 +
+ foo | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 foo
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index ae26614..e3f354a 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -10,7 +10,10 @@ test_description='our own option parser'
cat > expect << EOF
usage: test-parse-options <options>
- -b, --boolean get a boolean
+ --yes get a boolean
+ -D, --no-doubt begins with 'no-'
+ -B, --no-fear be brave
+ -b, --boolean increment by one
-4, --or4 bitwise-or boolean with ...0100
--neg-or4 same as --no-or4
@@ -28,6 +31,7 @@ String options
--st <st> get another string (pervert ordering)
-o <str> get another string
--default-string set string to default
+ --list <str> add str to list
Magic arguments
--quux means --quux
@@ -52,6 +56,59 @@ test_expect_success 'test help' '
mv expect expect.err
+cat >expect.template <<EOF
+boolean: 0
+integer: 0
+timestamp: 0
+string: (not set)
+abbrev: 7
+verbose: 0
+quiet: no
+dry run: no
+file: (not set)
+EOF
+
+check() {
+ what="$1" &&
+ shift &&
+ expect="$1" &&
+ shift &&
+ sed "s/^$what .*/$what $expect/" <expect.template >expect &&
+ test-parse-options $* >output 2>output.err &&
+ test ! -s output.err &&
+ test_cmp expect output
+}
+
+check_unknown() {
+ case "$1" in
+ --*)
+ echo error: unknown option \`${1#--}\' >expect ;;
+ -*)
+ echo error: unknown switch \`${1#-}\' >expect ;;
+ esac &&
+ cat expect.err >>expect &&
+ test_must_fail test-parse-options $* >output 2>output.err &&
+ test ! -s output &&
+ test_cmp expect output.err
+}
+
+test_expect_success 'OPT_BOOL() #1' 'check boolean: 1 --yes'
+test_expect_success 'OPT_BOOL() #2' 'check boolean: 1 --no-doubt'
+test_expect_success 'OPT_BOOL() #3' 'check boolean: 1 -D'
+test_expect_success 'OPT_BOOL() #4' 'check boolean: 1 --no-fear'
+test_expect_success 'OPT_BOOL() #5' 'check boolean: 1 -B'
+
+test_expect_success 'OPT_BOOL() is idempotent #1' 'check boolean: 1 --yes --yes'
+test_expect_success 'OPT_BOOL() is idempotent #2' 'check boolean: 1 -DB'
+
+test_expect_success 'OPT_BOOL() negation #1' 'check boolean: 0 -D --no-yes'
+test_expect_success 'OPT_BOOL() negation #2' 'check boolean: 0 -D --no-no-doubt'
+
+test_expect_success 'OPT_BOOL() no negation #1' 'check_unknown --fear'
+test_expect_success 'OPT_BOOL() no negation #2' 'check_unknown --no-no-fear'
+
+test_expect_success 'OPT_BOOL() positivation' 'check boolean: 0 -D --doubt'
+
cat > expect << EOF
boolean: 2
integer: 1729
@@ -86,7 +143,7 @@ EOF
test_expect_success 'long options' '
test-parse-options --boolean --integer 1729 --boolean --string2=321 \
--verbose --verbose --no-dry-run --abbrev=10 --file fi.le\
- > output 2> output.err &&
+ --obsolete > output 2> output.err &&
test ! -s output.err &&
test_cmp expect output
'
@@ -179,6 +236,16 @@ test_expect_success 'detect possible typos' '
test_cmp typo.err output.err
'
+cat > typo.err << EOF
+error: did you mean \`--ambiguous\` (with two dashes ?)
+EOF
+
+test_expect_success 'detect possible typos' '
+ test_must_fail test-parse-options -ambiguous > output 2> output.err &&
+ test ! -s output &&
+ test_cmp typo.err output.err
+'
+
cat > expect <<EOF
boolean: 0
integer: 0
@@ -295,7 +362,7 @@ test_expect_success 'OPT_NEGBIT() works' '
test_cmp expect output
'
-test_expect_success 'OPT_BOOLEAN() with PARSE_OPT_NODASH works' '
+test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' '
test-parse-options + + + + + + > output 2> output.err &&
test ! -s output.err &&
test_cmp expect output
@@ -337,4 +404,20 @@ test_expect_success 'negation of OPT_NONEG flags is not ambiguous' '
test_cmp expect output
'
+cat >>expect <<'EOF'
+list: foo
+list: bar
+list: baz
+EOF
+test_expect_success '--list keeps list of strings' '
+ test-parse-options --list foo --list=bar --list=baz >output &&
+ test_cmp expect output
+'
+
+test_expect_success '--no-list resets list' '
+ test-parse-options --list=other --list=irrelevant --list=options \
+ --no-list --list=foo --list=bar --list=baz >output &&
+ test_cmp expect output
+'
+
test_done
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
index 8d4938f..17e969d 100755
--- a/t/t0061-run-command.sh
+++ b/t/t0061-run-command.sh
@@ -34,4 +34,17 @@ test_expect_success POSIXPERM 'run_command reports EACCES' '
grep "fatal: cannot exec.*hello.sh" err
'
+test_expect_success POSIXPERM 'unreadable directory in PATH' '
+ mkdir local-command &&
+ test_when_finished "chmod u+rwx local-command && rm -fr local-command" &&
+ git config alias.nitfol "!echo frotz" &&
+ chmod a-rx local-command &&
+ (
+ PATH=./local-command:$PATH &&
+ git nitfol >actual
+ ) &&
+ echo frotz >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t0062-revision-walking.sh b/t/t0062-revision-walking.sh
new file mode 100755
index 0000000..3d98eb8
--- /dev/null
+++ b/t/t0062-revision-walking.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Heiko Voigt
+#
+
+test_description='Test revision walking api'
+
+. ./test-lib.sh
+
+cat >run_twice_expected <<-EOF
+1st
+ > add b
+ > add a
+2nd
+ > add b
+ > add a
+EOF
+
+test_expect_success 'setup' '
+ echo a > a &&
+ git add a &&
+ git commit -m "add a" &&
+ echo b > b &&
+ git add b &&
+ git commit -m "add b"
+'
+
+test_expect_success 'revision walking can be done twice' '
+ test-revision-walking run-twice > run_twice_actual
+ test_cmp run_twice_expected run_twice_actual
+'
+
+test_done
diff --git a/t/t0080-vcs-svn.sh b/t/t0080-vcs-svn.sh
deleted file mode 100755
index 99a314b..0000000
--- a/t/t0080-vcs-svn.sh
+++ /dev/null
@@ -1,117 +0,0 @@
-#!/bin/sh
-
-test_description='check infrastructure for svn importer'
-
-. ./test-lib.sh
-uint32_max=4294967295
-
-test_expect_success 'obj pool: store data' '
- cat <<-\EOF >expected &&
- 0
- 1
- EOF
-
- test-obj-pool <<-\EOF >actual &&
- alloc one 16
- set one 13
- test one 13
- reset one
- EOF
- test_cmp expected actual
-'
-
-test_expect_success 'obj pool: NULL is offset ~0' '
- echo "$uint32_max" >expected &&
- echo null one | test-obj-pool >actual &&
- test_cmp expected actual
-'
-
-test_expect_success 'obj pool: out-of-bounds access' '
- cat <<-EOF >expected &&
- 0
- 0
- $uint32_max
- $uint32_max
- 16
- 20
- $uint32_max
- EOF
-
- test-obj-pool <<-\EOF >actual &&
- alloc one 16
- alloc two 16
- offset one 20
- offset two 20
- alloc one 5
- offset one 20
- free one 1
- offset one 20
- reset one
- reset two
- EOF
- test_cmp expected actual
-'
-
-test_expect_success 'obj pool: high-water mark' '
- cat <<-\EOF >expected &&
- 0
- 0
- 10
- 20
- 20
- 20
- EOF
-
- test-obj-pool <<-\EOF >actual &&
- alloc one 10
- committed one
- alloc one 10
- commit one
- committed one
- alloc one 10
- free one 20
- committed one
- reset one
- EOF
- test_cmp expected actual
-'
-
-test_expect_success 'string pool' '
- echo a does not equal b >expected.differ &&
- echo a equals a >expected.match &&
- echo equals equals equals >expected.matchmore &&
-
- test-string-pool "a,--b" >actual.differ &&
- test-string-pool "a,a" >actual.match &&
- test-string-pool "equals-equals" >actual.matchmore &&
- test_must_fail test-string-pool a,a,a &&
- test_must_fail test-string-pool a &&
-
- test_cmp expected.differ actual.differ &&
- test_cmp expected.match actual.match &&
- test_cmp expected.matchmore actual.matchmore
-'
-
-test_expect_success 'treap sort' '
- cat <<-\EOF >unsorted &&
- 68
- 12
- 13
- 13
- 68
- 13
- 13
- 21
- 10
- 11
- 12
- 13
- 13
- EOF
- sort unsorted >expected &&
-
- test-treap <unsorted >actual &&
- test_cmp expected actual
-'
-
-test_done
diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh
new file mode 100755
index 0000000..6c33e28
--- /dev/null
+++ b/t/t0090-cache-tree.sh
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description="Test whether cache-tree is properly updated
+
+Tests whether various commands properly update and/or rewrite the
+cache-tree extension.
+"
+ . ./test-lib.sh
+
+cmp_cache_tree () {
+ test-dump-cache-tree >actual &&
+ sed "s/$_x40/SHA/" <actual >filtered &&
+ test_cmp "$1" filtered
+}
+
+# We don't bother with actually checking the SHA1:
+# test-dump-cache-tree already verifies that all existing data is
+# correct.
+test_shallow_cache_tree () {
+ printf "SHA (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >expect &&
+ cmp_cache_tree expect
+}
+
+test_invalid_cache_tree () {
+ echo "invalid (0 subtrees)" >expect &&
+ printf "SHA #(ref) (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >>expect &&
+ cmp_cache_tree expect
+}
+
+test_no_cache_tree () {
+ : >expect &&
+ cmp_cache_tree expect
+}
+
+test_expect_failure 'initial commit has cache-tree' '
+ test_commit foo &&
+ test_shallow_cache_tree
+'
+
+test_expect_success 'read-tree HEAD establishes cache-tree' '
+ git read-tree HEAD &&
+ test_shallow_cache_tree
+'
+
+test_expect_success 'git-add invalidates cache-tree' '
+ test_when_finished "git reset --hard; git read-tree HEAD" &&
+ echo "I changed this file" > foo &&
+ git add foo &&
+ test_invalid_cache_tree
+'
+
+test_expect_success 'update-index invalidates cache-tree' '
+ test_when_finished "git reset --hard; git read-tree HEAD" &&
+ echo "I changed this file" > foo &&
+ git update-index --add foo &&
+ test_invalid_cache_tree
+'
+
+test_expect_success 'write-tree establishes cache-tree' '
+ test-scrap-cache-tree &&
+ git write-tree &&
+ test_shallow_cache_tree
+'
+
+test_expect_success 'test-scrap-cache-tree works' '
+ git read-tree HEAD &&
+ test-scrap-cache-tree &&
+ test_no_cache_tree
+'
+
+test_expect_success 'second commit has cache-tree' '
+ test_commit bar &&
+ test_shallow_cache_tree
+'
+
+test_expect_success 'reset --hard gives cache-tree' '
+ test-scrap-cache-tree &&
+ git reset --hard &&
+ test_shallow_cache_tree
+'
+
+test_expect_success 'reset --hard without index gives cache-tree' '
+ rm -f .git/index &&
+ git reset --hard &&
+ test_shallow_cache_tree
+'
+
+test_expect_failure 'checkout gives cache-tree' '
+ git checkout HEAD^ &&
+ test_shallow_cache_tree
+'
+
+test_done
diff --git a/t/t0200-gettext-basic.sh b/t/t0200-gettext-basic.sh
new file mode 100755
index 0000000..8853d8a
--- /dev/null
+++ b/t/t0200-gettext-basic.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext support for Git'
+
+. ./lib-gettext.sh
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+ test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $TEXTDOMAIN is git' '
+ test $TEXTDOMAIN = "git"
+'
+
+test_expect_success 'xgettext sanity: Perl _() strings are not extracted' '
+ ! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '
+ grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l >expect &&
+ grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po | wc -l >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' '
+ ! grep "This is a phony" "$GIT_PO_PATH"/is.po &&
+ ! grep "the above comment" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success GETTEXT 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
+ test -d "$TEXTDOMAINDIR" &&
+ test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+
+test_expect_success GETTEXT 'sanity: Icelandic locale was compiled' '
+ test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
+'
+
+# TODO: When we have more locales, generalize this to test them
+# all. Maybe we'll need a dir->locale map for that.
+test_expect_success GETTEXT_LOCALE 'sanity: gettext("") metadata is OK' '
+ # Return value may be non-zero
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >zero-expect &&
+ grep "Project-Id-Version: Git" zero-expect &&
+ grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
+ grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
+ grep "Content-Transfer-Encoding: 8bit" zero-expect
+'
+
+test_expect_success GETTEXT_LOCALE 'sanity: gettext(unknown) is passed through' '
+ printf "This is not a translation string" >expect &&
+ gettext "This is not a translation string" >actual &&
+ eval_gettext "This is not a translation string" >actual &&
+ test_cmp expect actual
+'
+
+# xgettext from C
+test_expect_success GETTEXT_LOCALE 'xgettext: C extraction of _() and N_() strings' '
+ printf "TILRAUN: C tilraunastrengur" >expect &&
+ printf "\n" >>expect &&
+ printf "Sjá '\''git help SKIPUN'\'' til að sjá hjálp fyrir tiltekna skipun." >>expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string" >actual &&
+ printf "\n" >>actual &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "See '\''git help COMMAND'\'' for more information on a specific command." >>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: C extraction with %s' '
+ printf "TILRAUN: C tilraunastrengur %%s" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string %s" >actual &&
+ test_cmp expect actual
+'
+
+# xgettext from Shell
+test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction' '
+ printf "TILRAUN: Skeljartilraunastrengur" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Shell test string" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction with $variable' '
+ printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" >x-expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" variable="a var i able" eval_gettext "TEST: A Shell test \$variable" >x-actual &&
+ test_cmp x-expect x-actual
+'
+
+# xgettext from Perl
+test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction' '
+ printf "TILRAUN: Perl tilraunastrengur" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test string" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction with %s' '
+ printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test variable %s" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'sanity: Some gettext("") data for real locale' '
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >real-locale &&
+ test -s real-locale
+'
+
+test_done
diff --git a/t/t0200/test.c b/t/t0200/test.c
new file mode 100644
index 0000000..584d45c
--- /dev/null
+++ b/t/t0200/test.c
@@ -0,0 +1,23 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+const char help[] =
+ /* TRANSLATORS: This is a test. You don't need to translate it. */
+ N_("See 'git help COMMAND' for more information on a specific command.");
+
+int main(void)
+{
+ /* TRANSLATORS: This is a test. You don't need to translate it. */
+ puts(_("TEST: A C test string"));
+
+ /* TRANSLATORS: This is a test. You don't need to translate it. */
+ printf(_("TEST: A C test string %s"), "variable");
+
+ /* TRANSLATORS: This is a test. You don't need to translate it. */
+ printf(_("TEST: Hello World!"));
+
+ /* TRANSLATORS: This is a test. You don't need to translate it. */
+ printf(_("TEST: Old English Runes"));
+
+ /* TRANSLATORS: This is a test. You don't need to translate it. */
+ printf(_("TEST: ‘single’ and “double†quotes"));
+}
diff --git a/t/t0200/test.perl b/t/t0200/test.perl
new file mode 100644
index 0000000..36fba34
--- /dev/null
+++ b/t/t0200/test.perl
@@ -0,0 +1,14 @@
+# This is a phony Perl program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+1;
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+print __("TEST: A Perl test string");
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+printf __("TEST: A Perl test variable %s"), "moo";
+
+# TRANSLATORS: If you see this, Git has a bug
+print _"TEST: A Perl string xgettext will not get";
diff --git a/t/t0200/test.sh b/t/t0200/test.sh
new file mode 100644
index 0000000..022d607
--- /dev/null
+++ b/t/t0200/test.sh
@@ -0,0 +1,14 @@
+# This is a phony Shell program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+echo
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+gettext "TEST: A Shell test string"
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+eval_gettext "TEST: A Shell test \$variable"
+
+# TRANSLATORS: If you see this, Git has a bug
+_("TEST: A Shell string xgettext won't get")
diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh
index 54d98b9..52b1c27 100755
--- a/t/t0201-gettext-fallbacks.sh
+++ b/t/t0201-gettext-fallbacks.sh
@@ -5,8 +5,24 @@
test_description='Gettext Shell fallbacks'
-. ./test-lib.sh
-. "$GIT_BUILD_DIR"/git-sh-i18n
+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease
+export GIT_INTERNAL_GETTEXT_TEST_FALLBACKS
+
+. ./lib-gettext.sh
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+ test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '
+ test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+'
+
+test_expect_success C_LOCALE_OUTPUT 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' '
+ echo fallthrough >expect &&
+ echo $GIT_INTERNAL_GETTEXT_SH_SCHEME >actual &&
+ test_cmp expect actual
+'
test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
printf "test" >expect &&
diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh
new file mode 100755
index 0000000..428ebb0
--- /dev/null
+++ b/t/t0202-gettext-perl.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Perl gettext interface (Git::I18N)'
+
+. ./lib-gettext.sh
+
+if ! test_have_prereq PERL; then
+ skip_all='skipping perl interface tests, perl not available'
+ test_done
+fi
+
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+ skip_all="Perl Test::More unavailable, skipping test"
+ test_done
+}
+
+# The external test will outputs its own plan
+test_external_has_tap=1
+
+test_external_without_stderr \
+ 'Perl Git::I18N API' \
+ "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl
+
+test_done
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
new file mode 100644
index 0000000..2c10cb4
--- /dev/null
+++ b/t/t0202/test.pl
@@ -0,0 +1,110 @@
+#!/usr/bin/perl
+use 5.008;
+use lib (split(/:/, $ENV{GITPERLLIB}));
+use strict;
+use warnings;
+use POSIX qw(:locale_h);
+use Test::More tests => 8;
+use Git::I18N;
+
+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
+
+ok(1, "Testing Git::I18N with " .
+ ($has_gettext_library
+ ? (defined $Locale::Messages::VERSION
+ ? "Locale::Messages version $Locale::Messages::VERSION"
+ # Versions of Locale::Messages before 1.17 didn't have a
+ # $VERSION variable.
+ : "Locale::Messages version <1.17")
+ : "NO Perl gettext library"));
+ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
+
+{
+ my $exports = @Git::I18N::EXPORT;
+ ok($exports, "sanity: Git::I18N has $exports export(s)");
+}
+is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default");
+
+# prototypes
+{
+ # Add prototypes here when modifying the public interface to add
+ # more gettext wrapper functions.
+ my %prototypes = (qw(
+ __ $
+ ));
+ while (my ($sub, $proto) = each %prototypes) {
+ is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
+ }
+}
+
+# Test basic passthrough in the C locale
+{
+ local $ENV{LANGUAGE} = 'C';
+ local $ENV{LC_ALL} = 'C';
+ local $ENV{LANG} = 'C';
+
+ my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+ is(__($got), $expect, "Passing a string through __() in the C locale works");
+}
+
+# Test a basic message on different locales
+SKIP: {
+ unless ($ENV{GETTEXT_LOCALE}) {
+ # Can't reliably test __() with a non-C locales because the
+ # required locales may not be installed on the system.
+ #
+ # We test for these anyway as part of the shell
+ # tests. Skipping these here will eliminate failures on odd
+ # platforms with incomplete locale data.
+
+ skip "GETTEXT_LOCALE must be set by lib-gettext.sh for exhaustive Git::I18N tests", 2;
+ }
+
+ # The is_IS UTF-8 locale passed from lib-gettext.sh
+ my $is_IS_locale = $ENV{is_IS_locale};
+
+ my $test = sub {
+ my ($got, $expect, $msg, $locale) = @_;
+ # Maybe this system doesn't have the locale we're trying to
+ # test.
+ my $locale_ok = setlocale(LC_ALL, $locale);
+ is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>");
+ };
+
+ my $env_C = sub {
+ $ENV{LANGUAGE} = 'C';
+ $ENV{LC_ALL} = 'C';
+ };
+
+ my $env_is = sub {
+ $ENV{LANGUAGE} = 'is';
+ $ENV{LC_ALL} = $is_IS_locale;
+ };
+
+ # Translation's the same as the original
+ my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+ if ($has_gettext_library) {
+ {
+ local %ENV; $env_C->();
+ $test->($got, $expect, "With", 'C');
+ }
+
+ {
+ my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');
+ local %ENV; $env_is->();
+ $test->($got, $expect, "With", $is_IS_locale);
+ }
+ } else {
+ {
+ local %ENV; $env_C->();
+ $test->($got, $expect, "Without", 'C');
+ }
+
+ {
+ local %ENV; $env_is->();
+ $test->($got, $expect, "Without", 'is');
+ }
+ }
+}
diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh
new file mode 100755
index 0000000..a212460
--- /dev/null
+++ b/t/t0203-gettext-setlocale-sanity.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description="The Git C functions aren't broken by setlocale(3)"
+
+. ./lib-gettext.sh
+
+test_expect_success 'git show a ISO-8859-1 commit under C locale' '
+ . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+ test_commit "iso-c-commit" iso-under-c &&
+ git show >out 2>err &&
+ ! test -s err &&
+ grep -q "iso-c-commit" out
+'
+
+test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' '
+ . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+ test_commit "iso-utf8-commit" iso-under-utf8 &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err &&
+ ! test -s err &&
+ grep -q "iso-utf8-commit" out
+'
+
+test_done
diff --git a/t/t0204-gettext-reencode-sanity.sh b/t/t0204-gettext-reencode-sanity.sh
new file mode 100755
index 0000000..8437e51
--- /dev/null
+++ b/t/t0204-gettext-reencode-sanity.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description="Gettext reencoding of our *.po/*.mo files works"
+
+. ./lib-gettext.sh
+
+# The constants used in a tricky observation for undefined behaviour
+RUNES="TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛠᚻᛖ ᛒᚢᛞᛖ áš©áš¾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ áš¹á›áš¦ ᚦᚪ ᚹᛖᛥᚫ"
+PUNTS="TILRAUN: ?? ???? ??? ?? ???? ?? ??? ????? ??????????? ??? ?? ????"
+MSGKEY="TEST: Old English Runes"
+
+test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Icelandic' '
+ printf "TILRAUN: Halló Heimur!" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Hello World!" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Runes' '
+ printf "%s" "$RUNES" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "$MSGKEY" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Icelandic' '
+ printf "TILRAUN: Halló Heimur!" | iconv -f UTF-8 -t ISO8859-1 >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Hello World!" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: impossible ISO-8859-1 output' '
+ LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "$MSGKEY" >runes &&
+ case "$(cat runes)" in
+ "$MSGKEY")
+ say "Your system gives back the key to message catalog"
+ ;;
+ "$PUNTS")
+ say "Your system replaces an impossible character with ?"
+ ;;
+ "$RUNES")
+ say "Your system gives back the raw message for an impossible request"
+ ;;
+ *)
+ say "We never saw the error behaviour your system exhibits"
+ false
+ ;;
+ esac
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext: Fetching a UTF-8 msgid -> UTF-8' '
+ printf "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: ‘single’ and “double†quotes" >actual &&
+ test_cmp expect actual
+'
+
+# How these quotes get transliterated depends on the gettext implementation:
+#
+# Debian: ,einfaldar' og ,,tvöfaldar" [GNU libintl]
+# FreeBSD: `einfaldar` og "tvöfaldar" [GNU libintl]
+# Solaris: ?einfaldar? og ?tvöfaldar? [Solaris libintl]
+#
+# Just make sure the contents are transliterated, and don't use grep -q
+# so that these differences are emitted under --verbose for curious
+# eyes.
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Fetching a UTF-8 msgid -> ISO-8859-1' '
+ LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: ‘single’ and “double†quotes" >actual &&
+ grep "einfaldar" actual &&
+ grep "$(echo tvöfaldar | iconv -f UTF-8 -t ISO8859-1)" actual
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext.c: git init UTF-8 -> UTF-8' '
+ printf "Bjó til tóma Git lind" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_locale" git init repo >actual &&
+ test_when_finished "rm -rf repo" &&
+ grep "^$(cat expect) " actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext.c: git init UTF-8 -> ISO-8859-1' '
+ printf "Bjó til tóma Git lind" >expect &&
+ LANGUAGE=is LC_ALL="$is_IS_iso_locale" git init repo >actual &&
+ test_when_finished "rm -rf repo" &&
+ grep "^$(cat expect | iconv -f UTF-8 -t ISO8859-1) " actual
+'
+
+test_done
diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh
new file mode 100755
index 0000000..2361590
--- /dev/null
+++ b/t/t0205-gettext-poison.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext Shell poison'
+
+. ./lib-gettext.sh
+
+test_expect_success GETTEXT_POISON "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+ test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' '
+ test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison"
+'
+
+test_expect_success GETTEXT_POISON 'gettext: our gettext() fallback has poison semantics' '
+ printf "# GETTEXT POISON #" >expect &&
+ gettext "test" >actual &&
+ test_cmp expect actual &&
+ printf "# GETTEXT POISON #" >expect &&
+ gettext "test more words" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GETTEXT_POISON 'eval_gettext: our eval_gettext() fallback has poison semantics' '
+ printf "# GETTEXT POISON #" >expect &&
+ eval_gettext "test" >actual &&
+ test_cmp expect actual &&
+ printf "# GETTEXT POISON #" >expect &&
+ eval_gettext "test more words" >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
new file mode 100755
index 0000000..538ea5f
--- /dev/null
+++ b/t/t0300-credentials.sh
@@ -0,0 +1,292 @@
+#!/bin/sh
+
+test_description='basic credential helper tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+test_expect_success 'setup helper scripts' '
+ cat >dump <<-\EOF &&
+ whoami=`echo $0 | sed s/.*git-credential-//`
+ echo >&2 "$whoami: $*"
+ OIFS=$IFS
+ IFS==
+ while read key value; do
+ echo >&2 "$whoami: $key=$value"
+ eval "$key=$value"
+ done
+ IFS=$OIFS
+ EOF
+
+ write_script git-credential-useless <<-\EOF &&
+ . ./dump
+ exit 0
+ EOF
+
+ write_script git-credential-verbatim <<-\EOF &&
+ user=$1; shift
+ pass=$1; shift
+ . ./dump
+ test -z "$user" || echo username=$user
+ test -z "$pass" || echo password=$pass
+ EOF
+
+ PATH="$PWD:$PATH"
+'
+
+test_expect_success 'credential_fill invokes helper' '
+ check fill "verbatim foo bar" <<-\EOF
+ --
+ username=foo
+ password=bar
+ --
+ verbatim: get
+ EOF
+'
+
+test_expect_success 'credential_fill invokes multiple helpers' '
+ check fill useless "verbatim foo bar" <<-\EOF
+ --
+ username=foo
+ password=bar
+ --
+ useless: get
+ verbatim: get
+ EOF
+'
+
+test_expect_success 'credential_fill stops when we get a full response' '
+ check fill "verbatim one two" "verbatim three four" <<-\EOF
+ --
+ username=one
+ password=two
+ --
+ verbatim: get
+ EOF
+'
+
+test_expect_success 'credential_fill continues through partial response' '
+ check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
+ --
+ username=two
+ password=three
+ --
+ verbatim: get
+ verbatim: get
+ verbatim: username=one
+ EOF
+'
+
+test_expect_success 'credential_fill passes along metadata' '
+ check fill "verbatim one two" <<-\EOF
+ protocol=ftp
+ host=example.com
+ path=foo.git
+ --
+ protocol=ftp
+ host=example.com
+ path=foo.git
+ username=one
+ password=two
+ --
+ verbatim: get
+ verbatim: protocol=ftp
+ verbatim: host=example.com
+ verbatim: path=foo.git
+ EOF
+'
+
+test_expect_success 'credential_approve calls all helpers' '
+ check approve useless "verbatim one two" <<-\EOF
+ username=foo
+ password=bar
+ --
+ --
+ useless: store
+ useless: username=foo
+ useless: password=bar
+ verbatim: store
+ verbatim: username=foo
+ verbatim: password=bar
+ EOF
+'
+
+test_expect_success 'do not bother storing password-less credential' '
+ check approve useless <<-\EOF
+ username=foo
+ --
+ --
+ EOF
+'
+
+
+test_expect_success 'credential_reject calls all helpers' '
+ check reject useless "verbatim one two" <<-\EOF
+ username=foo
+ password=bar
+ --
+ --
+ useless: erase
+ useless: username=foo
+ useless: password=bar
+ verbatim: erase
+ verbatim: username=foo
+ verbatim: password=bar
+ EOF
+'
+
+test_expect_success 'usernames can be preserved' '
+ check fill "verbatim \"\" three" <<-\EOF
+ username=one
+ --
+ username=one
+ password=three
+ --
+ verbatim: get
+ verbatim: username=one
+ EOF
+'
+
+test_expect_success 'usernames can be overridden' '
+ check fill "verbatim two three" <<-\EOF
+ username=one
+ --
+ username=two
+ password=three
+ --
+ verbatim: get
+ verbatim: username=one
+ EOF
+'
+
+test_expect_success 'do not bother completing already-full credential' '
+ check fill "verbatim three four" <<-\EOF
+ username=one
+ password=two
+ --
+ username=one
+ password=two
+ --
+ EOF
+'
+
+# We can't test the basic terminal password prompt here because
+# getpass() tries too hard to find the real terminal. But if our
+# askpass helper is run, we know the internal getpass is working.
+test_expect_success 'empty helper list falls back to internal getpass' '
+ check fill <<-\EOF
+ --
+ username=askpass-username
+ password=askpass-password
+ --
+ askpass: Username:
+ askpass: Password:
+ EOF
+'
+
+test_expect_success 'internal getpass does not ask for known username' '
+ check fill <<-\EOF
+ username=foo
+ --
+ username=foo
+ password=askpass-password
+ --
+ askpass: Password:
+ EOF
+'
+
+HELPER="!f() {
+ cat >/dev/null
+ echo username=foo
+ echo password=bar
+ }; f"
+test_expect_success 'respect configured credentials' '
+ test_config credential.helper "$HELPER" &&
+ check fill <<-\EOF
+ --
+ username=foo
+ password=bar
+ --
+ EOF
+'
+
+test_expect_success 'match configured credential' '
+ test_config credential.https://example.com.helper "$HELPER" &&
+ check fill <<-\EOF
+ protocol=https
+ host=example.com
+ path=repo.git
+ --
+ protocol=https
+ host=example.com
+ username=foo
+ password=bar
+ --
+ EOF
+'
+
+test_expect_success 'do not match configured credential' '
+ test_config credential.https://foo.helper "$HELPER" &&
+ check fill <<-\EOF
+ protocol=https
+ host=bar
+ --
+ protocol=https
+ host=bar
+ username=askpass-username
+ password=askpass-password
+ --
+ askpass: Username for '\''https://bar'\'':
+ askpass: Password for '\''https://askpass-username@bar'\'':
+ EOF
+'
+
+test_expect_success 'pull username from config' '
+ test_config credential.https://example.com.username foo &&
+ check fill <<-\EOF
+ protocol=https
+ host=example.com
+ --
+ protocol=https
+ host=example.com
+ username=foo
+ password=askpass-password
+ --
+ askpass: Password for '\''https://foo@example.com'\'':
+ EOF
+'
+
+test_expect_success 'http paths can be part of context' '
+ check fill "verbatim foo bar" <<-\EOF &&
+ protocol=https
+ host=example.com
+ path=foo.git
+ --
+ protocol=https
+ host=example.com
+ username=foo
+ password=bar
+ --
+ verbatim: get
+ verbatim: protocol=https
+ verbatim: host=example.com
+ EOF
+ test_config credential.https://example.com.useHttpPath true &&
+ check fill "verbatim foo bar" <<-\EOF
+ protocol=https
+ host=example.com
+ path=foo.git
+ --
+ protocol=https
+ host=example.com
+ path=foo.git
+ username=foo
+ password=bar
+ --
+ verbatim: get
+ verbatim: protocol=https
+ verbatim: host=example.com
+ verbatim: path=foo.git
+ EOF
+'
+
+test_done
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
new file mode 100755
index 0000000..82c8411
--- /dev/null
+++ b/t/t0301-credential-cache.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='credential-cache tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+test -z "$NO_UNIX_SOCKETS" || {
+ skip_all='skipping credential-cache tests, unix sockets not available'
+ test_done
+}
+
+# don't leave a stale daemon running
+trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT
+
+helper_test cache
+helper_test_timeout cache --timeout=1
+
+# we can't rely on our "trap" above working after test_done,
+# as test_done will delete the trash directory containing
+# our socket, leaving us with no way to access the daemon.
+git credential-cache exit
+
+test_done
diff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh
new file mode 100755
index 0000000..f61b40c
--- /dev/null
+++ b/t/t0302-credential-store.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='credential-store tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+helper_test store
+
+test_done
diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh
new file mode 100755
index 0000000..f028fd1
--- /dev/null
+++ b/t/t0303-credential-external.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+test_description='external credential helper tests
+
+This is a tool for authors of external helper tools to sanity-check
+their helpers. If you have written the "git-credential-foo" helper,
+you check it with:
+
+ make GIT_TEST_CREDENTIAL_HELPER=foo t0303-credential-external.sh
+
+This assumes that your helper is capable of both storing and
+retrieving credentials (some helpers may be read-only, and they will
+fail these tests).
+
+Please note that the individual tests do not verify all of the
+preconditions themselves, but rather build on each other. A failing
+test means that tests later in the sequence can return false "OK"
+results.
+
+If your helper supports time-based expiration with a configurable
+timeout, you can test that feature with:
+
+ make GIT_TEST_CREDENTIAL_HELPER=foo \
+ GIT_TEST_CREDENTIAL_HELPER_TIMEOUT="foo --timeout=1" \
+ t0303-credential-external.sh
+
+If your helper requires additional setup before the tests are started,
+you can set GIT_TEST_CREDENTIAL_HELPER_SETUP to a sequence of shell
+commands.
+'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+if test -z "$GIT_TEST_CREDENTIAL_HELPER"; then
+ skip_all="used to test external credential helpers"
+ test_done
+fi
+
+test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
+ eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP"
+
+# clean before the test in case there is cruft left
+# over from a previous run that would impact results
+helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+
+helper_test "$GIT_TEST_CREDENTIAL_HELPER"
+
+if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then
+ say "# skipping timeout tests (GIT_TEST_CREDENTIAL_HELPER_TIMEOUT not set)"
+else
+ helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"
+fi
+
+# clean afterwards so that we are good citizens
+# and don't leave cruft in the helper's storage, which
+# might be long-term system storage
+helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+
+test_done
diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index 6d52b82..f83df8e 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -189,7 +189,7 @@ for args in "-w --stdin-paths" "--stdin-paths -w"; do
done
test_expect_success 'corrupt tree' '
- echo abc >malformed-tree
+ echo abc >malformed-tree &&
test_must_fail git hash-object -t tree malformed-tree
'
diff --git a/t/t1010-mktree.sh b/t/t1010-mktree.sh
index b946f87..df573c4 100755
--- a/t/t1010-mktree.sh
+++ b/t/t1010-mktree.sh
@@ -42,13 +42,13 @@ test_expect_success 'ls-tree piped to mktree (2)' '
'
test_expect_success 'ls-tree output in wrong order given to mktree (1)' '
- perl -e "print reverse <>" <top |
+ "$PERL_PATH" -e "print reverse <>" <top |
git mktree >actual &&
test_cmp tree actual
'
test_expect_success 'ls-tree output in wrong order given to mktree (2)' '
- perl -e "print reverse <>" <top.withsub |
+ "$PERL_PATH" -e "print reverse <>" <top.withsub |
git mktree >actual &&
test_cmp tree.withsub actual
'
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index 018c354..5c0053a 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -234,4 +234,20 @@ test_expect_success 'read-tree --reset removes outside worktree' '
test_cmp empty result
'
+test_expect_success 'print errors when failed to update worktree' '
+ echo sub >.git/info/sparse-checkout &&
+ git checkout -f init &&
+ mkdir sub &&
+ touch sub/added sub/addedtoo &&
+ test_must_fail git checkout top 2>actual &&
+ cat >expected <<\EOF &&
+error: The following untracked working tree files would be overwritten by checkout:
+ sub/added
+ sub/addedtoo
+Please move or remove them before you can switch branches.
+Aborting
+EOF
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t1013-loose-object-format.sh b/t/t1013-loose-object-format.sh
new file mode 100755
index 0000000..fbf5f2f
--- /dev/null
+++ b/t/t1013-loose-object-format.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Roberto Tyley
+#
+
+test_description='Correctly identify and parse loose object headers
+
+There are two file formats for loose objects - the original standard
+format, and the experimental format introduced with Git v1.4.3, later
+deprecated with v1.5.3. Although Git no longer writes the
+experimental format, objects in both formats must be read, with the
+format for a given file being determined by the header.
+
+Detecting file format based on header is not entirely trivial, not
+least because the first byte of a zlib-deflated stream will vary
+depending on how much memory was allocated for the deflation window
+buffer when the object was written out (for example 4KB on Android,
+rather that 32KB on a normal PC).
+
+The loose objects used as test vectors have been generated with the
+following Git versions:
+
+standard format: Git v1.7.4.1
+experimental format: Git v1.4.3 (legacyheaders=false)
+standard format, deflated with 4KB window size: Agit/JGit on Android
+'
+
+. ./test-lib.sh
+
+assert_blob_equals() {
+ printf "%s" "$2" >expected &&
+ git cat-file -p "$1" >actual &&
+ test_cmp expected actual
+}
+
+test_expect_success setup '
+ cp -R "$TEST_DIRECTORY/t1013/objects" .git/ &&
+ git --version
+'
+
+test_expect_success 'read standard-format loose objects' '
+ git cat-file tag 8d4e360d6c70fbd72411991c02a09c442cf7a9fa &&
+ git cat-file commit 6baee0540ea990d9761a3eb9ab183003a71c3696 &&
+ git ls-tree 7a37b887a73791d12d26c0d3e39568a8fb0fa6e8 &&
+ assert_blob_equals "257cc5642cb1a054f08cc83f2d943e56fd3ebe99" "foo$LF"
+'
+
+test_expect_success 'read experimental-format loose objects' '
+ git cat-file tag 76e7fa9941f4d5f97f64fea65a2cba436bc79cbb &&
+ git cat-file commit 7875c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 &&
+ git ls-tree 95b1625de3ba8b2214d1e0d0591138aea733f64f &&
+ assert_blob_equals "2e65efe2a145dda7ee51d1741299f848e5bf752e" "a" &&
+ assert_blob_equals "9ae9e86b7bd6cb1472d9373702d8249973da0832" "ab" &&
+ assert_blob_equals "85df50785d62d3b05ab03d9cbf7e4a0b49449730" "abcd" &&
+ assert_blob_equals "1656f9233d999f61ef23ef390b9c71d75399f435" "abcdefgh" &&
+ assert_blob_equals "1e72a6b2c4a577ab0338860fa9fe87f761fc9bbd" "abcdefghi" &&
+ assert_blob_equals "70e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd" "abcdefghijklmnop" &&
+ assert_blob_equals "bd15045f6ce8ff75747562173640456a394412c8" "abcdefghijklmnopqrstuvwx"
+'
+
+test_expect_success 'read standard-format objects deflated with smaller window buffer' '
+ git cat-file tag f816d5255855ac160652ee5253b06cd8ee14165a &&
+ git cat-file tag 149cedb5c46929d18e0f118e9fa31927487af3b6
+'
+
+test_done
diff --git a/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6 b/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6
new file mode 100644
index 0000000..472fd14
--- /dev/null
+++ b/t/t1013/objects/14/9cedb5c46929d18e0f118e9fa31927487af3b6
Binary files differ
diff --git a/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435 b/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435
new file mode 100644
index 0000000..c379d74
--- /dev/null
+++ b/t/t1013/objects/16/56f9233d999f61ef23ef390b9c71d75399f435
Binary files differ
diff --git a/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd b/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd
new file mode 100644
index 0000000..9370630
--- /dev/null
+++ b/t/t1013/objects/1e/72a6b2c4a577ab0338860fa9fe87f761fc9bbd
Binary files differ
diff --git a/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99
new file mode 100644
index 0000000..bdcf704
--- /dev/null
+++ b/t/t1013/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99
Binary files differ
diff --git a/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e b/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e
new file mode 100644
index 0000000..ad62c43
--- /dev/null
+++ b/t/t1013/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e
Binary files differ
diff --git a/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696 b/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696
new file mode 100644
index 0000000..3d2f033
--- /dev/null
+++ b/t/t1013/objects/6b/aee0540ea990d9761a3eb9ab183003a71c3696
Binary files differ
diff --git a/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd b/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd
new file mode 100644
index 0000000..b3f71a6
--- /dev/null
+++ b/t/t1013/objects/70/e6a83d8dcb26fc8bc0cf702e2ddeb6adca18fd
Binary files differ
diff --git a/t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb b/t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb
new file mode 100644
index 0000000..af4e9a7
--- /dev/null
+++ b/t/t1013/objects/76/e7fa9941f4d5f97f64fea65a2cba436bc79cbb
@@ -0,0 +1,2 @@
+ xœ%ÌA‚0@Ñ}O1{cSZ(˜ãνáÃthª”’ZŒÜÞ Ëÿ? ¦m×6dµiÉ9…¤Gå˜h´Ø¨ÁZR'Q¶…RŒ¡ˆ‚ø³p‘ç‚ÓqL9âÏ=g¸§sIÐoopÎÿ”eÏ«_1»€³¤$×ç*Si«ëNwpP•RBôûÅÁú
+³‡[(ð®d-øÁL9á \ No newline at end of file
diff --git a/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09 b/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09
new file mode 100644
index 0000000..3dd28be
--- /dev/null
+++ b/t/t1013/objects/78/75c6237d3fcdd0ac2f0decc7d3fa6a50b66c09
Binary files differ
diff --git a/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8 b/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8
new file mode 100644
index 0000000..2b97b26
--- /dev/null
+++ b/t/t1013/objects/7a/37b887a73791d12d26c0d3e39568a8fb0fa6e8
Binary files differ
diff --git a/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730 b/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730
new file mode 100644
index 0000000..6dff746
--- /dev/null
+++ b/t/t1013/objects/85/df50785d62d3b05ab03d9cbf7e4a0b49449730
Binary files differ
diff --git a/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa b/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa
new file mode 100644
index 0000000..cb41e92
--- /dev/null
+++ b/t/t1013/objects/8d/4e360d6c70fbd72411991c02a09c442cf7a9fa
Binary files differ
diff --git a/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f b/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f
new file mode 100644
index 0000000..7ac46b4
--- /dev/null
+++ b/t/t1013/objects/95/b1625de3ba8b2214d1e0d0591138aea733f64f
Binary files differ
diff --git a/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832 b/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832
new file mode 100644
index 0000000..9d8316d
--- /dev/null
+++ b/t/t1013/objects/9a/e9e86b7bd6cb1472d9373702d8249973da0832
Binary files differ
diff --git a/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8 b/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8
new file mode 100644
index 0000000..eebf239
--- /dev/null
+++ b/t/t1013/objects/bd/15045f6ce8ff75747562173640456a394412c8
Binary files differ
diff --git a/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 0000000..134cf19
--- /dev/null
+++ b/t/t1013/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a b/t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a
new file mode 100644
index 0000000..26b75ae
--- /dev/null
+++ b/t/t1013/objects/f8/16d5255855ac160652ee5253b06cd8ee14165a
@@ -0,0 +1 @@
+H‰ÌÁ‚0 €aÏ{ŠÞ I»e&Æø*¥ˆG°ß^¸ýù¿ËDåÒ†wU‡Ò—¬S±4ªŠÆ­ªž ,fÅ[ðßVAÛºÎüxÈÇö6[wtG§Lu¸?—¦²¼Ú×@‰"gì{†+by¾%M \ No newline at end of file
diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh
index f6a44c9..e23ac0e 100755
--- a/t/t1020-subdirectory.sh
+++ b/t/t1020-subdirectory.sh
@@ -17,8 +17,6 @@ test_expect_success setup '
cp one original.one &&
cp dir/two original.two
'
-LF='
-'
test_expect_success 'update-index and ls-files' '
git update-index --add one &&
@@ -120,7 +118,7 @@ test_expect_success 'alias expansion' '
)
'
-test_expect_success '!alias expansion' '
+test_expect_success NOT_MINGW '!alias expansion' '
pwd >expect &&
(
git config alias.test !pwd &&
@@ -140,6 +138,22 @@ test_expect_success 'GIT_PREFIX for !alias' '
test_cmp expect actual
'
+test_expect_success 'GIT_PREFIX for built-ins' '
+ # Use GIT_EXTERNAL_DIFF to test that the "diff" built-in
+ # receives the GIT_PREFIX variable.
+ printf "dir/" >expect &&
+ printf "#!/bin/sh\n" >diff &&
+ printf "printf \"\$GIT_PREFIX\"" >>diff &&
+ chmod +x diff &&
+ (
+ cd dir &&
+ printf "change" >two &&
+ env GIT_EXTERNAL_DIFF=./diff git diff >../actual
+ git checkout -- two
+ ) &&
+ test_cmp expect actual
+'
+
test_expect_success 'no file/rev ambiguity check inside .git' '
git commit -a -m 1 &&
(
diff --git a/t/t1050-large.sh b/t/t1050-large.sh
index deba111..fd10528 100755
--- a/t/t1050-large.sh
+++ b/t/t1050-large.sh
@@ -6,22 +6,161 @@ test_description='adding and checking out large blobs'
. ./test-lib.sh
test_expect_success setup '
- git config core.bigfilethreshold 200k &&
- echo X | dd of=large bs=1k seek=2000
+ # clone does not allow us to pass core.bigfilethreshold to
+ # new repos, so set core.bigfilethreshold globally
+ git config --global core.bigfilethreshold 200k &&
+ echo X | dd of=large1 bs=1k seek=2000 &&
+ echo X | dd of=large2 bs=1k seek=2000 &&
+ echo X | dd of=large3 bs=1k seek=2000 &&
+ echo Y | dd of=huge bs=1k seek=2500 &&
+ GIT_ALLOC_LIMIT=1500 &&
+ export GIT_ALLOC_LIMIT
'
-test_expect_success 'add a large file' '
- git add large &&
- # make sure we got a packfile and no loose objects
- test -f .git/objects/pack/pack-*.pack &&
- test ! -f .git/objects/??/??????????????????????????????????????
+test_expect_success 'add a large file or two' '
+ git add large1 huge large2 &&
+ # make sure we got a single packfile and no loose objects
+ bad= count=0 idx= &&
+ for p in .git/objects/pack/pack-*.pack
+ do
+ count=$(( $count + 1 ))
+ if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx"
+ then
+ continue
+ fi
+ bad=t
+ done &&
+ test -z "$bad" &&
+ test $count = 1 &&
+ cnt=$(git show-index <"$idx" | wc -l) &&
+ test $cnt = 2 &&
+ for l in .git/objects/??/??????????????????????????????????????
+ do
+ test -f "$l" || continue
+ bad=t
+ done &&
+ test -z "$bad" &&
+
+ # attempt to add another copy of the same
+ git add large3 &&
+ bad= count=0 &&
+ for p in .git/objects/pack/pack-*.pack
+ do
+ count=$(( $count + 1 ))
+ if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx"
+ then
+ continue
+ fi
+ bad=t
+ done &&
+ test -z "$bad" &&
+ test $count = 1
'
test_expect_success 'checkout a large file' '
- large=$(git rev-parse :large) &&
- git update-index --add --cacheinfo 100644 $large another &&
+ large1=$(git rev-parse :large1) &&
+ git update-index --add --cacheinfo 100644 $large1 another &&
git checkout another &&
- cmp large another ;# this must not be test_cmp
+ cmp large1 another ;# this must not be test_cmp
+'
+
+test_expect_success 'packsize limit' '
+ test_create_repo mid &&
+ (
+ cd mid &&
+ git config core.bigfilethreshold 64k &&
+ git config pack.packsizelimit 256k &&
+
+ # mid1 and mid2 will fit within 256k limit but
+ # appending mid3 will bust the limit and will
+ # result in a separate packfile.
+ test-genrandom "a" $(( 66 * 1024 )) >mid1 &&
+ test-genrandom "b" $(( 80 * 1024 )) >mid2 &&
+ test-genrandom "c" $(( 128 * 1024 )) >mid3 &&
+ git add mid1 mid2 mid3 &&
+
+ count=0
+ for pi in .git/objects/pack/pack-*.idx
+ do
+ test -f "$pi" && count=$(( $count + 1 ))
+ done &&
+ test $count = 2 &&
+
+ (
+ git hash-object --stdin <mid1
+ git hash-object --stdin <mid2
+ git hash-object --stdin <mid3
+ ) |
+ sort >expect &&
+
+ for pi in .git/objects/pack/pack-*.idx
+ do
+ git show-index <"$pi"
+ done |
+ sed -e "s/^[0-9]* \([0-9a-f]*\) .*/\1/" |
+ sort >actual &&
+
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'diff --raw' '
+ git commit -q -m initial &&
+ echo modified >>large1 &&
+ git add large1 &&
+ git commit -q -m modified &&
+ git diff --raw HEAD^
+'
+
+test_expect_success 'hash-object' '
+ git hash-object large1
+'
+
+test_expect_success 'cat-file a large file' '
+ git cat-file blob :large1 >/dev/null
+'
+
+test_expect_success 'cat-file a large file from a tag' '
+ git tag -m largefile largefiletag :large1 &&
+ git cat-file blob largefiletag >/dev/null
+'
+
+test_expect_success 'git-show a large file' '
+ git show :large1 >/dev/null
+
+'
+
+test_expect_success 'index-pack' '
+ git clone file://"`pwd`"/.git foo &&
+ GIT_DIR=non-existent git index-pack --strict --verify foo/.git/objects/pack/*.pack
+'
+
+test_expect_success 'repack' '
+ git repack -ad
+'
+
+test_expect_success 'pack-objects with large loose object' '
+ SHA1=`git hash-object huge` &&
+ test_create_repo loose &&
+ echo $SHA1 | git pack-objects --stdout |
+ GIT_ALLOC_LIMIT=0 GIT_DIR=loose/.git git unpack-objects &&
+ echo $SHA1 | GIT_DIR=loose/.git git pack-objects pack &&
+ test_create_repo packed &&
+ mv pack-* packed/.git/objects/pack &&
+ GIT_DIR=packed/.git git cat-file blob $SHA1 >actual &&
+ cmp huge actual
+'
+
+test_expect_success 'tar achiving' '
+ git archive --format=tar HEAD >/dev/null
+'
+
+test_expect_success 'zip achiving, store only' '
+ git archive --format=zip -0 HEAD >/dev/null
+'
+
+test_expect_success 'zip achiving, deflate' '
+ git archive --format=zip HEAD >/dev/null
'
test_done
diff --git a/t/t1051-large-conversion.sh b/t/t1051-large-conversion.sh
new file mode 100755
index 0000000..8b7640b
--- /dev/null
+++ b/t/t1051-large-conversion.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='test conversion filters on large files'
+. ./test-lib.sh
+
+set_attr() {
+ test_when_finished 'rm -f .gitattributes' &&
+ echo "* $*" >.gitattributes
+}
+
+check_input() {
+ git read-tree --empty &&
+ git add small large &&
+ git cat-file blob :small >small.index &&
+ git cat-file blob :large | head -n 1 >large.index &&
+ test_cmp small.index large.index
+}
+
+check_output() {
+ rm -f small large &&
+ git checkout small large &&
+ head -n 1 large >large.head &&
+ test_cmp small large.head
+}
+
+test_expect_success 'setup input tests' '
+ printf "\$Id: foo\$\\r\\n" >small &&
+ cat small small >large &&
+ git config core.bigfilethreshold 20 &&
+ git config filter.test.clean "sed s/.*/CLEAN/"
+'
+
+test_expect_success 'autocrlf=true converts on input' '
+ test_config core.autocrlf true &&
+ check_input
+'
+
+test_expect_success 'eol=crlf converts on input' '
+ set_attr eol=crlf &&
+ check_input
+'
+
+test_expect_success 'ident converts on input' '
+ set_attr ident &&
+ check_input
+'
+
+test_expect_success 'user-defined filters convert on input' '
+ set_attr filter=test &&
+ check_input
+'
+
+test_expect_success 'setup output tests' '
+ echo "\$Id\$" >small &&
+ cat small small >large &&
+ git add small large &&
+ git config core.bigfilethreshold 7 &&
+ git config filter.test.smudge "sed s/.*/SMUDGE/"
+'
+
+test_expect_success 'autocrlf=true converts on output' '
+ test_config core.autocrlf true &&
+ check_output
+'
+
+test_expect_success 'eol=crlf converts on output' '
+ set_attr eol=crlf &&
+ check_output
+'
+
+test_expect_success 'user-defined filters convert on output' '
+ set_attr filter=test &&
+ check_output
+'
+
+test_expect_success 'ident converts on output' '
+ set_attr ident &&
+ rm -f small large &&
+ git checkout small large &&
+ sed -n "s/Id: .*/Id: SHA/p" <small >small.clean &&
+ head -n 1 large >large.head &&
+ sed -n "s/Id: .*/Id: SHA/p" <large.head >large.clean &&
+ test_cmp small.clean large.clean
+'
+
+test_done
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index 5e29e13..397ccb6 100755
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
@@ -154,9 +154,9 @@ test_expect_success 'git show-branch' '
cat > resolve.expect << EOF
Updating VARIABLE..VARIABLE
FASTFORWARD (no commit created; -m option ignored)
- example | 1 +
- hello | 1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+ example | 1 +
+ hello | 1 +
+ 2 files changed, 2 insertions(+)
EOF
test_expect_success 'git resolve' '
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 3e140c1..a477453 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -7,28 +7,28 @@ test_description='Test git config in different settings'
. ./test-lib.sh
-test -f .git/config && rm .git/config
-
-git config core.penguin "little blue"
+test_expect_success 'clear default config' '
+ rm -f .git/config
+'
cat > expect << EOF
[core]
penguin = little blue
EOF
-
-test_expect_success 'initial' 'cmp .git/config expect'
-
-git config Core.Movie BadPhysics
+test_expect_success 'initial' '
+ git config core.penguin "little blue" &&
+ test_cmp expect .git/config
+'
cat > expect << EOF
[core]
penguin = little blue
Movie = BadPhysics
EOF
-
-test_expect_success 'mixed case' 'cmp .git/config expect'
-
-git config Cores.WhatEver Second
+test_expect_success 'mixed case' '
+ git config Core.Movie BadPhysics &&
+ test_cmp expect .git/config
+'
cat > expect << EOF
[core]
@@ -37,10 +37,10 @@ cat > expect << EOF
[Cores]
WhatEver = Second
EOF
-
-test_expect_success 'similar section' 'cmp .git/config expect'
-
-git config CORE.UPPERCASE true
+test_expect_success 'similar section' '
+ git config Cores.WhatEver Second &&
+ test_cmp expect .git/config
+'
cat > expect << EOF
[core]
@@ -50,8 +50,10 @@ cat > expect << EOF
[Cores]
WhatEver = Second
EOF
-
-test_expect_success 'similar section' 'cmp .git/config expect'
+test_expect_success 'uppercase section' '
+ git config CORE.UPPERCASE true &&
+ test_cmp expect .git/config
+'
test_expect_success 'replace with non-match' \
'git config core.penguin kingpin !blue'
@@ -69,7 +71,34 @@ cat > expect << EOF
WhatEver = Second
EOF
-test_expect_success 'non-match result' 'cmp .git/config expect'
+test_expect_success 'non-match result' 'test_cmp expect .git/config'
+
+test_expect_success 'find mixed-case key by canonical name' '
+ echo Second >expect &&
+ git config cores.whatever >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'find mixed-case key by non-canonical name' '
+ echo Second >expect &&
+ git config CoReS.WhAtEvEr >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'subsections are not canonicalized by git-config' '
+ cat >>.git/config <<-\EOF &&
+ [section.SubSection]
+ key = one
+ [section "SubSection"]
+ key = two
+ EOF
+ echo one >expect &&
+ git config section.subsection.key >actual &&
+ test_cmp expect actual &&
+ echo two >expect &&
+ git config section.SubSection.key >actual &&
+ test_cmp expect actual
+'
cat > .git/config <<\EOF
[alpha]
@@ -88,7 +117,7 @@ bar = foo
[beta]
EOF
-test_expect_success 'unset with cont. lines is correct' 'cmp .git/config expect'
+test_expect_success 'unset with cont. lines is correct' 'test_cmp expect .git/config'
cat > .git/config << EOF
[beta] ; silly comment # another comment
@@ -116,7 +145,7 @@ noIndent= sillyValue ; 'nother silly comment
[nextSection] noNewline = ouch
EOF
-test_expect_success 'multiple unset is correct' 'cmp .git/config expect'
+test_expect_success 'multiple unset is correct' 'test_cmp expect .git/config'
cp .git/config2 .git/config
@@ -140,9 +169,7 @@ noIndent= sillyValue ; 'nother silly comment
[nextSection] noNewline = ouch
EOF
-test_expect_success 'all replaced' 'cmp .git/config expect'
-
-git config beta.haha alpha
+test_expect_success 'all replaced' 'test_cmp expect .git/config'
cat > expect << EOF
[beta] ; silly comment # another comment
@@ -153,10 +180,10 @@ noIndent= sillyValue ; 'nother silly comment
haha = alpha
[nextSection] noNewline = ouch
EOF
-
-test_expect_success 'really mean test' 'cmp .git/config expect'
-
-git config nextsection.nonewline wow
+test_expect_success 'really mean test' '
+ git config beta.haha alpha &&
+ test_cmp expect .git/config
+'
cat > expect << EOF
[beta] ; silly comment # another comment
@@ -168,11 +195,12 @@ noIndent= sillyValue ; 'nother silly comment
[nextSection]
nonewline = wow
EOF
-
-test_expect_success 'really really mean test' 'cmp .git/config expect'
+test_expect_success 'really really mean test' '
+ git config nextsection.nonewline wow &&
+ test_cmp expect .git/config
+'
test_expect_success 'get value' 'test alpha = $(git config beta.haha)'
-git config --unset beta.haha
cat > expect << EOF
[beta] ; silly comment # another comment
@@ -183,10 +211,10 @@ noIndent= sillyValue ; 'nother silly comment
[nextSection]
nonewline = wow
EOF
-
-test_expect_success 'unset' 'cmp .git/config expect'
-
-git config nextsection.NoNewLine "wow2 for me" "for me$"
+test_expect_success 'unset' '
+ git config --unset beta.haha &&
+ test_cmp expect .git/config
+'
cat > expect << EOF
[beta] ; silly comment # another comment
@@ -198,8 +226,10 @@ noIndent= sillyValue ; 'nother silly comment
nonewline = wow
NoNewLine = wow2 for me
EOF
-
-test_expect_success 'multivar' 'cmp .git/config expect'
+test_expect_success 'multivar' '
+ git config nextsection.NoNewLine "wow2 for me" "for me$" &&
+ test_cmp expect .git/config
+'
test_expect_success 'non-match' \
'git config --get nextsection.nonewline !for'
@@ -214,8 +244,6 @@ test_expect_success 'ambiguous get' '
test_expect_success 'get multivar' \
'git config --get-all nextsection.nonewline'
-git config nextsection.nonewline "wow3" "wow$"
-
cat > expect << EOF
[beta] ; silly comment # another comment
noIndent= sillyValue ; 'nother silly comment
@@ -226,8 +254,10 @@ noIndent= sillyValue ; 'nother silly comment
nonewline = wow3
NoNewLine = wow2 for me
EOF
-
-test_expect_success 'multivar replace' 'cmp .git/config expect'
+test_expect_success 'multivar replace' '
+ git config nextsection.nonewline "wow3" "wow$" &&
+ test_cmp expect .git/config
+'
test_expect_success 'ambiguous value' '
test_must_fail git config nextsection.nonewline
@@ -241,8 +271,6 @@ test_expect_success 'invalid unset' '
test_must_fail git config --unset somesection.nonewline
'
-git config --unset nextsection.nonewline "wow3$"
-
cat > expect << EOF
[beta] ; silly comment # another comment
noIndent= sillyValue ; 'nother silly comment
@@ -253,7 +281,10 @@ noIndent= sillyValue ; 'nother silly comment
NoNewLine = wow2 for me
EOF
-test_expect_success 'multivar unset' 'cmp .git/config expect'
+test_expect_success 'multivar unset' '
+ git config --unset nextsection.nonewline "wow3$" &&
+ test_cmp expect .git/config
+'
test_expect_success 'invalid key' 'test_must_fail git config inval.2key blabla'
@@ -276,7 +307,7 @@ noIndent= sillyValue ; 'nother silly comment
Alpha = beta
EOF
-test_expect_success 'hierarchical section value' 'cmp .git/config expect'
+test_expect_success 'hierarchical section value' 'test_cmp expect .git/config'
cat > expect << EOF
beta.noindent=sillyValue
@@ -304,15 +335,16 @@ EOF
test_expect_success '--get-regexp' \
'git config --get-regexp in > output && cmp output expect'
-git config --add nextsection.nonewline "wow4 for you"
-
cat > expect << EOF
wow2 for me
wow4 for you
EOF
-test_expect_success '--add' \
- 'git config --get-all nextsection.nonewline > output && cmp output expect'
+test_expect_success '--add' '
+ git config --add nextsection.nonewline "wow4 for you" &&
+ git config --get-all nextsection.nonewline > output &&
+ test_cmp expect output
+'
cat > .git/config << EOF
[novalue]
@@ -333,6 +365,12 @@ test_expect_success 'get-regexp variable with no value' \
'git config --get-regexp novalue > output &&
cmp output expect'
+echo 'novalue.variable true' > expect
+
+test_expect_success 'get-regexp --bool variable with no value' \
+ 'git config --bool --get-regexp novalue > output &&
+ cmp output expect'
+
echo 'emptyvalue.variable ' > expect
test_expect_success 'get-regexp variable with empty value' \
@@ -361,8 +399,6 @@ cat > .git/config << EOF
c = d
EOF
-git config a.x y
-
cat > expect << EOF
[a.b]
c = d
@@ -370,10 +406,10 @@ cat > expect << EOF
x = y
EOF
-test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
-
-git config b.x y
-git config a.b c
+test_expect_success 'new section is partial match of another' '
+ git config a.x y &&
+ test_cmp expect .git/config
+'
cat > expect << EOF
[a.b]
@@ -385,7 +421,11 @@ cat > expect << EOF
x = y
EOF
-test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
+test_expect_success 'new variable inserts into proper section' '
+ git config b.x y &&
+ git config a.b c &&
+ test_cmp expect .git/config
+'
test_expect_success 'alternative GIT_CONFIG (non-existing file should fail)' \
'test_must_fail git config --file non-existing-config -l'
@@ -399,9 +439,10 @@ cat > expect << EOF
ein.bahn=strasse
EOF
-GIT_CONFIG=other-config git config -l > output
-
-test_expect_success 'alternative GIT_CONFIG' 'cmp output expect'
+test_expect_success 'alternative GIT_CONFIG' '
+ GIT_CONFIG=other-config git config -l >output &&
+ test_cmp expect output
+'
test_expect_success 'alternative GIT_CONFIG (--file)' \
'git config --file other-config -l > output && cmp output expect'
@@ -410,14 +451,20 @@ test_expect_success 'refer config from subdirectory' '
mkdir x &&
(
cd x &&
- echo strasse >expect
+ echo strasse >expect &&
git config --get --file ../other-config ein.bahn >actual &&
test_cmp expect actual
)
'
-GIT_CONFIG=other-config git config anwohner.park ausweis
+test_expect_success 'refer config from subdirectory via GIT_CONFIG' '
+ (
+ cd x &&
+ GIT_CONFIG=../other-config git config --get ein.bahn >actual &&
+ test_cmp expect actual
+ )
+'
cat > expect << EOF
[ein]
@@ -426,7 +473,10 @@ cat > expect << EOF
park = ausweis
EOF
-test_expect_success '--set in alternative GIT_CONFIG' 'cmp other-config expect'
+test_expect_success '--set in alternative GIT_CONFIG' '
+ GIT_CONFIG=other-config git config anwohner.park ausweis &&
+ test_cmp expect other-config
+'
cat > .git/config << EOF
# Hallo
@@ -500,6 +550,14 @@ EOF
test_expect_success "rename succeeded" "test_cmp expect .git/config"
+test_expect_success 'renaming empty section name is rejected' '
+ test_must_fail git config --rename-section branch.zwei ""
+'
+
+test_expect_success 'renaming to bogus section is rejected' '
+ test_must_fail git config --rename-section branch.zwei "bogus name"
+'
+
cat >> .git/config << EOF
[branch "zwei"] a = 1 [branch "vier"]
EOF
@@ -516,8 +574,6 @@ EOF
test_expect_success "section was removed properly" \
"test_cmp expect .git/config"
-rm .git/config
-
cat > expect << EOF
[gitcvs]
enabled = true
@@ -528,10 +584,11 @@ EOF
test_expect_success 'section ending' '
+ rm -f .git/config &&
git config gitcvs.enabled true &&
git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
- cmp .git/config expect
+ test_cmp expect .git/config
'
@@ -600,8 +657,6 @@ test_expect_success 'invalid bool (set)' '
test_must_fail git config --bool bool.nobool foobar'
-rm .git/config
-
cat > expect <<\EOF
[bool]
true1 = true
@@ -616,6 +671,7 @@ EOF
test_expect_success 'set --bool' '
+ rm -f .git/config &&
git config --bool bool.true1 01 &&
git config --bool bool.true2 -1 &&
git config --bool bool.true3 YeS &&
@@ -626,8 +682,6 @@ test_expect_success 'set --bool' '
git config --bool bool.false4 FALSE &&
cmp expect .git/config'
-rm .git/config
-
cat > expect <<\EOF
[int]
val1 = 1
@@ -637,13 +691,12 @@ EOF
test_expect_success 'set --int' '
+ rm -f .git/config &&
git config --int int.val1 01 &&
git config --int int.val2 -1 &&
git config --int int.val3 5m &&
cmp expect .git/config'
-rm .git/config
-
cat >expect <<\EOF
[bool]
true1 = true
@@ -657,6 +710,7 @@ cat >expect <<\EOF
EOF
test_expect_success 'get --bool-or-int' '
+ rm -f .git/config &&
(
echo "[bool]"
echo true1
@@ -676,7 +730,6 @@ test_expect_success 'get --bool-or-int' '
'
-rm .git/config
cat >expect <<\EOF
[bool]
true1 = true
@@ -690,6 +743,7 @@ cat >expect <<\EOF
EOF
test_expect_success 'set --bool-or-int' '
+ rm -f .git/config &&
git config --bool-or-int bool.true1 true &&
git config --bool-or-int bool.false1 false &&
git config --bool-or-int bool.true2 yes &&
@@ -700,8 +754,6 @@ test_expect_success 'set --bool-or-int' '
test_cmp expect .git/config
'
-rm .git/config
-
cat >expect <<\EOF
[path]
home = ~/
@@ -710,6 +762,7 @@ cat >expect <<\EOF
EOF
test_expect_success NOT_MINGW 'set --path' '
+ rm -f .git/config &&
git config --path path.home "~/" &&
git config --path path.normal "/dev/null" &&
git config --path path.trailingtilde "foo~" &&
@@ -750,13 +803,6 @@ test_expect_success NOT_MINGW 'get --path copes with unset $HOME' '
test_cmp expect result
'
-rm .git/config
-
-git config quote.leading " test"
-git config quote.ending "test "
-git config quote.semicolon "test;test"
-git config quote.hash "test#test"
-
cat > expect << EOF
[quote]
leading = " test"
@@ -764,8 +810,14 @@ cat > expect << EOF
semicolon = "test;test"
hash = "test#test"
EOF
-
-test_expect_success 'quoting' 'cmp .git/config expect'
+test_expect_success 'quoting' '
+ rm -f .git/config &&
+ git config quote.leading " test" &&
+ git config quote.ending "test " &&
+ git config quote.semicolon "test;test" &&
+ git config quote.hash "test#test" &&
+ test_cmp expect .git/config
+'
test_expect_success 'key with newline' '
test_must_fail git config "key.with
@@ -790,9 +842,10 @@ section.noncont=not continued
section.quotecont=cont;inued
EOF
-git config --list > result
-
-test_expect_success 'value continued on next line' 'cmp result expect'
+test_expect_success 'value continued on next line' '
+ git config --list > result &&
+ cmp result expect
+'
cat > .git/config <<\EOF
[section "sub=section"]
@@ -813,16 +866,17 @@ barQsection.sub=section.val3
Qsection.sub=section.val4
Qsection.sub=section.val5Q
EOF
+test_expect_success '--null --list' '
+ git config --null --list | nul_to_q >result &&
+ echo >>result &&
+ test_cmp expect result
+'
-git config --null --list | perl -pe 'y/\000/Q/' > result
-echo >>result
-
-test_expect_success '--null --list' 'cmp result expect'
-
-git config --null --get-regexp 'val[0-9]' | perl -pe 'y/\000/Q/' > result
-echo >>result
-
-test_expect_success '--null --get-regexp' 'cmp result expect'
+test_expect_success '--null --get-regexp' '
+ git config --null --get-regexp "val[0-9]" | nul_to_q >result &&
+ echo >>result &&
+ test_cmp expect result
+'
test_expect_success 'inner whitespace kept verbatim' '
git config section.val "foo bar" &&
@@ -922,4 +976,52 @@ test_expect_success 'git -c complains about empty key and value' '
test_must_fail git -c "" rev-parse
'
+test_expect_success 'git config --edit works' '
+ git config -f tmp test.value no &&
+ echo test.value=yes >expect &&
+ GIT_EDITOR="echo [test]value=yes >" git config -f tmp --edit &&
+ git config -f tmp --list >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git config --edit respects core.editor' '
+ git config -f tmp test.value no &&
+ echo test.value=yes >expect &&
+ test_config core.editor "echo [test]value=yes >" &&
+ git config -f tmp --edit &&
+ git config -f tmp --list >actual &&
+ test_cmp expect actual
+'
+
+# malformed configuration files
+test_expect_success 'barf on syntax error' '
+ cat >.git/config <<-\EOF &&
+ # broken section line
+ [section]
+ key garbage
+ EOF
+ test_must_fail git config --get section.key >actual 2>error &&
+ grep " line 3 " error
+'
+
+test_expect_success 'barf on incomplete section header' '
+ cat >.git/config <<-\EOF &&
+ # broken section line
+ [section
+ key = value
+ EOF
+ test_must_fail git config --get section.key >actual 2>error &&
+ grep " line 2 " error
+'
+
+test_expect_success 'barf on incomplete string' '
+ cat >.git/config <<-\EOF &&
+ # broken section line
+ [section]
+ key = "value string
+ EOF
+ test_must_fail git config --get section.key >actual 2>error &&
+ grep " line 3 " error
+'
+
test_done
diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh
index b5d89a2..79045ab 100755
--- a/t/t1304-default-acl.sh
+++ b/t/t1304-default-acl.sh
@@ -14,15 +14,19 @@ umask 077
# We need an arbitrary other user give permission to using ACLs. root
# is a good candidate: exists on all unices, and it has permission
# anyway, so we don't create a security hole running the testsuite.
+test_expect_success 'checking for a working acl setup' '
+ if setfacl -m d:m:rwx -m u:root:rwx . &&
+ getfacl . | grep user:root:rwx &&
+ touch should-have-readable-acl &&
+ getfacl should-have-readable-acl | egrep "mask::?rw-"
+ then
+ test_set_prereq SETFACL
+ fi
+'
-setfacl_out="$(setfacl -m u:root:rwx . 2>&1)"
-setfacl_ret=$?
-
-if test $setfacl_ret != 0
+if test -z "$LOGNAME"
then
- say "Unable to use setfacl (output: '$setfacl_out'; return code: '$setfacl_ret')"
-else
- test_set_prereq SETFACL
+ LOGNAME=$USER
fi
check_perms_and_acl () {
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
new file mode 100755
index 0000000..a707076
--- /dev/null
+++ b/t/t1305-config-include.sh
@@ -0,0 +1,142 @@
+#!/bin/sh
+
+test_description='test config file include directives'
+. ./test-lib.sh
+
+test_expect_success 'include file by absolute path' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = \"$(pwd)/one\"" >.gitconfig &&
+ echo 1 >expect &&
+ git config test.one >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'include file by relative path' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = one" >.gitconfig &&
+ echo 1 >expect &&
+ git config test.one >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'chained relative paths' '
+ mkdir subdir &&
+ echo "[test]three = 3" >subdir/three &&
+ echo "[include]path = three" >subdir/two &&
+ echo "[include]path = subdir/two" >.gitconfig &&
+ echo 3 >expect &&
+ git config test.three >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'include paths get tilde-expansion' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = ~/one" >.gitconfig &&
+ echo 1 >expect &&
+ git config test.one >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'include options can still be examined' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = one" >.gitconfig &&
+ echo one >expect &&
+ git config include.path >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'listing includes option and expansion' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = one" >.gitconfig &&
+ cat >expect <<-\EOF &&
+ include.path=one
+ test.one=1
+ EOF
+ git config --list >actual.full &&
+ grep -v ^core actual.full >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'single file lookup does not expand includes by default' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = one" >.gitconfig &&
+ test_must_fail git config -f .gitconfig test.one &&
+ test_must_fail git config --global test.one &&
+ echo 1 >expect &&
+ git config --includes -f .gitconfig test.one >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'single file list does not expand includes by default' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = one" >.gitconfig &&
+ echo "include.path=one" >expect &&
+ git config -f .gitconfig --list >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'writing config file does not expand includes' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = one" >.gitconfig &&
+ git config test.two 2 &&
+ echo 2 >expect &&
+ git config --no-includes test.two >actual &&
+ test_cmp expect actual &&
+ test_must_fail git config --no-includes test.one
+'
+
+test_expect_success 'config modification does not affect includes' '
+ echo "[test]one = 1" >one &&
+ echo "[include]path = one" >.gitconfig &&
+ git config test.one 2 &&
+ echo 1 >expect &&
+ git config -f one test.one >actual &&
+ test_cmp expect actual &&
+ cat >expect <<-\EOF &&
+ 1
+ 2
+ EOF
+ git config --get-all test.one >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'missing include files are ignored' '
+ cat >.gitconfig <<-\EOF &&
+ [include]path = foo
+ [test]value = yes
+ EOF
+ echo yes >expect &&
+ git config test.value >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'absolute includes from command line work' '
+ echo "[test]one = 1" >one &&
+ echo 1 >expect &&
+ git -c include.path="$PWD/one" config test.one >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'relative includes from command line fail' '
+ echo "[test]one = 1" >one &&
+ test_must_fail git -c include.path=one config test.one
+'
+
+test_expect_success 'include cycles are detected' '
+ cat >.gitconfig <<-\EOF &&
+ [test]value = gitconfig
+ [include]path = cycle
+ EOF
+ cat >cycle <<-\EOF &&
+ [test]value = cycle
+ [include]path = .gitconfig
+ EOF
+ cat >expect <<-\EOF &&
+ gitconfig
+ cycle
+ EOF
+ test_must_fail git config --get-all test.value 2>stderr &&
+ grep "exceeded maximum include depth" stderr
+'
+
+test_done
diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh
new file mode 100755
index 0000000..3c75c3f
--- /dev/null
+++ b/t/t1306-xdg-files.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Valentin Duperray, Lucien Kong, Franck Jonas,
+# Thomas Nguy, Khoi Nguyen
+# Grenoble INP Ensimag
+#
+
+test_description='Compatibility with $XDG_CONFIG_HOME/git/ files'
+
+. ./test-lib.sh
+
+test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' '
+ mkdir -p .config/git &&
+ echo "[alias]" >.config/git/config &&
+ echo " myalias = !echo in_config" >>.config/git/config &&
+ echo in_config >expected &&
+ git myalias >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read config: xdg file exists and ~/.gitconfig exists' '
+ >.gitconfig &&
+ echo "[alias]" >.gitconfig &&
+ echo " myalias = !echo in_gitconfig" >>.gitconfig &&
+ echo in_gitconfig >expected &&
+ git myalias >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read with --get: xdg file exists and ~/.gitconfig doesn'\''t' '
+ rm .gitconfig &&
+ echo "[user]" >.config/git/config &&
+ echo " name = read_config" >>.config/git/config &&
+ echo read_config >expected &&
+ git config --get user.name >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read with --get: xdg file exists and ~/.gitconfig exists' '
+ >.gitconfig &&
+ echo "[user]" >.gitconfig &&
+ echo " name = read_gitconfig" >>.gitconfig &&
+ echo read_gitconfig >expected &&
+ git config --get user.name >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read with --list: xdg file exists and ~/.gitconfig doesn'\''t' '
+ rm .gitconfig &&
+ echo user.name=read_config >expected &&
+ git config --global --list >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists' '
+ >.gitconfig &&
+ echo "[user]" >.gitconfig &&
+ echo " name = read_gitconfig" >>.gitconfig &&
+ echo user.name=read_gitconfig >expected &&
+ git config --global --list >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'Setup' '
+ git init git &&
+ cd git &&
+ echo foo >to_be_excluded
+'
+
+
+test_expect_success 'Exclusion of a file in the XDG ignore file' '
+ mkdir -p "$HOME"/.config/git/ &&
+ echo to_be_excluded >"$HOME"/.config/git/ignore &&
+ test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Exclusion in both XDG and local ignore files' '
+ echo to_be_excluded >.gitignore &&
+ test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Exclusion in a non-XDG global ignore file' '
+ rm .gitignore &&
+ echo >"$HOME"/.config/git/ignore &&
+ echo to_be_excluded >"$HOME"/my_gitignore &&
+ git config core.excludesfile "$HOME"/my_gitignore &&
+ test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Checking attributes in the XDG attributes file' '
+ echo foo >f &&
+ git check-attr -a f >actual &&
+ test_line_count -eq 0 actual &&
+ echo "f attr_f" >"$HOME"/.config/git/attributes &&
+ echo "f: attr_f: set" >expected &&
+ git check-attr -a f >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'Checking attributes in both XDG and local attributes files' '
+ echo "f -attr_f" >.gitattributes &&
+ echo "f: attr_f: unset" >expected &&
+ git check-attr -a f >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'Checking attributes in a non-XDG global attributes file' '
+ test_might_fail rm .gitattributes &&
+ echo "f attr_f=test" >"$HOME"/my_gitattributes &&
+ git config core.attributesfile "$HOME"/my_gitattributes &&
+ echo "f: attr_f: test" >expected &&
+ git check-attr -a f >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'write: xdg file exists and ~/.gitconfig doesn'\''t' '
+ mkdir -p "$HOME"/.config/git &&
+ >"$HOME"/.config/git/config &&
+ test_might_fail rm "$HOME"/.gitconfig &&
+ git config --global user.name "write_config" &&
+ echo "[user]" >expected &&
+ echo " name = write_config" >>expected &&
+ test_cmp expected "$HOME"/.config/git/config
+'
+
+
+test_expect_success 'write: xdg file exists and ~/.gitconfig exists' '
+ >"$HOME"/.gitconfig &&
+ git config --global user.name "write_gitconfig" &&
+ echo "[user]" >expected &&
+ echo " name = write_gitconfig" >>expected &&
+ test_cmp expected "$HOME"/.gitconfig
+'
+
+
+test_expect_success 'write: ~/.config/git/ exists and config file doesn'\''t' '
+ test_might_fail rm "$HOME"/.gitconfig &&
+ test_might_fail rm "$HOME"/.config/git/config &&
+ git config --global user.name "write_gitconfig" &&
+ echo "[user]" >expected &&
+ echo " name = write_gitconfig" >>expected &&
+ test_cmp expected "$HOME"/.gitconfig
+'
+
+
+test_done
diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh
index ed4275a..1ae4d87 100755
--- a/t/t1402-check-ref-format.sh
+++ b/t/t1402-check-ref-format.sh
@@ -5,34 +5,126 @@ test_description='Test git check-ref-format'
. ./test-lib.sh
valid_ref() {
- test_expect_success "ref name '$1' is valid" \
- "git check-ref-format '$1'"
+ prereq=
+ case $1 in
+ [A-Z]*)
+ prereq=$1
+ shift
+ esac
+ test_expect_success $prereq "ref name '$1' is valid${2:+ with options $2}" "
+ git check-ref-format $2 '$1'
+ "
}
invalid_ref() {
- test_expect_success "ref name '$1' is not valid" \
- "test_must_fail git check-ref-format '$1'"
+ prereq=
+ case $1 in
+ [A-Z]*)
+ prereq=$1
+ shift
+ esac
+ test_expect_success $prereq "ref name '$1' is invalid${2:+ with options $2}" "
+ test_must_fail git check-ref-format $2 '$1'
+ "
}
-valid_ref 'heads/foo'
-invalid_ref 'foo'
+invalid_ref ''
+invalid_ref NOT_MINGW '/'
+invalid_ref NOT_MINGW '/' --allow-onelevel
+invalid_ref NOT_MINGW '/' --normalize
+invalid_ref NOT_MINGW '/' '--allow-onelevel --normalize'
valid_ref 'foo/bar/baz'
-valid_ref 'refs///heads/foo'
+valid_ref 'foo/bar/baz' --normalize
+invalid_ref 'refs///heads/foo'
+valid_ref 'refs///heads/foo' --normalize
invalid_ref 'heads/foo/'
-valid_ref '/heads/foo'
-valid_ref '///heads/foo'
-invalid_ref '/foo'
+invalid_ref NOT_MINGW '/heads/foo'
+valid_ref NOT_MINGW '/heads/foo' --normalize
+invalid_ref '///heads/foo'
+valid_ref '///heads/foo' --normalize
invalid_ref './foo'
+invalid_ref './foo/bar'
+invalid_ref 'foo/./bar'
+invalid_ref 'foo/bar/.'
invalid_ref '.refs/foo'
invalid_ref 'heads/foo..bar'
invalid_ref 'heads/foo?bar'
valid_ref 'foo./bar'
invalid_ref 'heads/foo.lock'
+invalid_ref 'heads///foo.lock'
+invalid_ref 'foo.lock/bar'
+invalid_ref 'foo.lock///bar'
valid_ref 'heads/foo@bar'
invalid_ref 'heads/v@{ation'
invalid_ref 'heads/foo\bar'
invalid_ref "$(printf 'heads/foo\t')"
invalid_ref "$(printf 'heads/foo\177')"
valid_ref "$(printf 'heads/fu\303\237')"
+invalid_ref 'heads/*foo/bar' --refspec-pattern
+invalid_ref 'heads/foo*/bar' --refspec-pattern
+invalid_ref 'heads/f*o/bar' --refspec-pattern
+
+ref='foo'
+invalid_ref "$ref"
+valid_ref "$ref" --allow-onelevel
+invalid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref "$ref" --normalize
+valid_ref "$ref" '--allow-onelevel --normalize'
+
+ref='foo/bar'
+valid_ref "$ref"
+valid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+valid_ref "$ref" --normalize
+
+ref='foo/*'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/foo'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref "$ref" --normalize
+valid_ref "$ref" '--refspec-pattern --normalize'
+
+ref='foo/*/bar'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+invalid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='foo/*/*'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/foo/*'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/*/foo'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='/foo'
+invalid_ref NOT_MINGW "$ref"
+invalid_ref NOT_MINGW "$ref" --allow-onelevel
+invalid_ref NOT_MINGW "$ref" --refspec-pattern
+invalid_ref NOT_MINGW "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref NOT_MINGW "$ref" --normalize
+valid_ref NOT_MINGW "$ref" '--allow-onelevel --normalize'
+invalid_ref NOT_MINGW "$ref" '--refspec-pattern --normalize'
+valid_ref NOT_MINGW "$ref" '--refspec-pattern --allow-onelevel --normalize'
test_expect_success "check-ref-format --branch @{-1}" '
T=$(git write-tree) &&
@@ -65,23 +157,41 @@ test_expect_success 'check-ref-format --branch from subdir' '
'
valid_ref_normalized() {
- test_expect_success "ref name '$1' simplifies to '$2'" "
- refname=\$(git check-ref-format --print '$1') &&
- test \"\$refname\" = '$2'"
+ prereq=
+ case $1 in
+ [A-Z]*)
+ prereq=$1
+ shift
+ esac
+ test_expect_success $prereq "ref name '$1' simplifies to '$2'" "
+ refname=\$(git check-ref-format --normalize '$1') &&
+ test \"\$refname\" = '$2'
+ "
}
invalid_ref_normalized() {
- test_expect_success "check-ref-format --print rejects '$1'" "
- test_must_fail git check-ref-format --print '$1'"
+ prereq=
+ case $1 in
+ [A-Z]*)
+ prereq=$1
+ shift
+ esac
+ test_expect_success $prereq "check-ref-format --normalize rejects '$1'" "
+ test_must_fail git check-ref-format --normalize '$1'
+ "
}
valid_ref_normalized 'heads/foo' 'heads/foo'
valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo'
-valid_ref_normalized '/heads/foo' 'heads/foo'
+valid_ref_normalized NOT_MINGW '/heads/foo' 'heads/foo'
valid_ref_normalized '///heads/foo' 'heads/foo'
invalid_ref_normalized 'foo'
-invalid_ref_normalized '/foo'
+invalid_ref_normalized NOT_MINGW '/foo'
invalid_ref_normalized 'heads/foo/../bar'
invalid_ref_normalized 'heads/./foo'
invalid_ref_normalized 'heads\foo'
+invalid_ref_normalized 'heads/foo.lock'
+invalid_ref_normalized 'heads///foo.lock'
+invalid_ref_normalized 'foo.lock/bar'
+invalid_ref_normalized 'foo.lock///bar'
test_done
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 252fc82..236b13a 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -100,8 +100,7 @@ test_expect_success setup '
check_fsck &&
- loglen=$(wc -l <.git/logs/refs/heads/master) &&
- test $loglen = 4
+ test_line_count = 4 .git/logs/refs/heads/master
'
test_expect_success rewind '
@@ -117,8 +116,7 @@ test_expect_success rewind '
check_have A B C D E F G H I J K L &&
- loglen=$(wc -l <.git/logs/refs/heads/master) &&
- test $loglen = 5
+ test_line_count = 5 .git/logs/refs/heads/master
'
test_expect_success 'corrupt and check' '
@@ -136,8 +134,7 @@ test_expect_success 'reflog expire --dry-run should not touch reflog' '
--stale-fix \
--all &&
- loglen=$(wc -l <.git/logs/refs/heads/master) &&
- test $loglen = 5 &&
+ test_line_count = 5 .git/logs/refs/heads/master &&
check_fsck "missing blob $F"
'
@@ -150,8 +147,7 @@ test_expect_success 'reflog expire' '
--stale-fix \
--all &&
- loglen=$(wc -l <.git/logs/refs/heads/master) &&
- test $loglen = 2 &&
+ test_line_count = 2 .git/logs/refs/heads/master &&
check_fsck "dangling commit $K"
'
@@ -217,9 +213,7 @@ test_expect_success 'delete' '
test_expect_success 'rewind2' '
test_tick && git reset --hard HEAD~2 &&
- loglen=$(wc -l <.git/logs/refs/heads/master) &&
- test $loglen = 4
-
+ test_line_count = 4 .git/logs/refs/heads/master
'
test_expect_success '--expire=never' '
@@ -228,9 +222,7 @@ test_expect_success '--expire=never' '
--expire=never \
--expire-unreachable=never \
--all &&
- loglen=$(wc -l <.git/logs/refs/heads/master) &&
- test $loglen = 4
-
+ test_line_count = 4 .git/logs/refs/heads/master
'
test_expect_success 'gc.reflogexpire=never' '
@@ -238,8 +230,7 @@ test_expect_success 'gc.reflogexpire=never' '
git config gc.reflogexpire never &&
git config gc.reflogexpireunreachable never &&
git reflog expire --verbose --all &&
- loglen=$(wc -l <.git/logs/refs/heads/master) &&
- test $loglen = 4
+ test_line_count = 4 .git/logs/refs/heads/master
'
test_expect_success 'gc.reflogexpire=false' '
@@ -247,8 +238,7 @@ test_expect_success 'gc.reflogexpire=false' '
git config gc.reflogexpire false &&
git config gc.reflogexpireunreachable false &&
git reflog expire --verbose --all &&
- loglen=$(wc -l <.git/logs/refs/heads/master) &&
- test $loglen = 4 &&
+ test_line_count = 4 .git/logs/refs/heads/master &&
git config --unset gc.reflogexpire &&
git config --unset gc.reflogexpireunreachable
diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh
index caa687b..9a105fe 100755
--- a/t/t1411-reflog-show.sh
+++ b/t/t1411-reflog-show.sh
@@ -65,20 +65,73 @@ test_expect_success 'using @{now} syntax shows reflog date (oneline)' '
'
cat >expect <<'EOF'
-Reflog: HEAD@{1112911993 -0700} (C O Mitter <committer@example.com>)
+HEAD@{Thu Apr 7 15:13:13 2005 -0700}
+EOF
+test_expect_success 'using @{now} syntax shows reflog date (format=%gd)' '
+ git log -g -1 --format=%gd HEAD@{now} >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Reflog: HEAD@{Thu Apr 7 15:13:13 2005 -0700} (C O Mitter <committer@example.com>)
Reflog message: commit (initial): one
EOF
test_expect_success 'using --date= shows reflog date (multiline)' '
- git log -g -1 --date=raw >tmp &&
+ git log -g -1 --date=default >tmp &&
grep ^Reflog <tmp >actual &&
test_cmp expect actual
'
cat >expect <<'EOF'
-e46513e HEAD@{1112911993 -0700}: commit (initial): one
+e46513e HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one
EOF
test_expect_success 'using --date= shows reflog date (oneline)' '
- git log -g -1 --oneline --date=raw >actual &&
+ git log -g -1 --oneline --date=default >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+HEAD@{1112911993 -0700}
+EOF
+test_expect_success 'using --date= shows reflog date (format=%gd)' '
+ git log -g -1 --format=%gd --date=raw >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Reflog: HEAD@{0} (C O Mitter <committer@example.com>)
+Reflog message: commit (initial): one
+EOF
+test_expect_success 'log.date does not invoke "--date" magic (multiline)' '
+ test_config log.date raw &&
+ git log -g -1 >tmp &&
+ grep ^Reflog <tmp >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+e46513e HEAD@{0}: commit (initial): one
+EOF
+test_expect_success 'log.date does not invoke "--date" magic (oneline)' '
+ test_config log.date raw &&
+ git log -g -1 --oneline >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+HEAD@{0}
+EOF
+test_expect_success 'log.date does not invoke "--date" magic (format=%gd)' '
+ test_config log.date raw &&
+ git log -g -1 --format=%gd >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+HEAD@{0}
+EOF
+test_expect_success '--date magic does not override explicit @{0} syntax' '
+ git log -g -1 --format=%gd --date=raw HEAD@{0} >actual &&
test_cmp expect actual
'
diff --git a/t/t1412-reflog-loop.sh b/t/t1412-reflog-loop.sh
index 647d888..3acd895 100755
--- a/t/t1412-reflog-loop.sh
+++ b/t/t1412-reflog-loop.sh
@@ -20,7 +20,7 @@ test_expect_success 'setup reflog with alternating commits' '
'
test_expect_success 'reflog shows all entries' '
- cat >expect <<-\EOF
+ cat >expect <<-\EOF &&
topic@{0} reset: moving to two
topic@{1} reset: moving to one
topic@{2} reset: moving to two
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index bb01d5a..5b79c51 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -27,12 +27,8 @@ test_expect_success 'loose objects borrowed from alternate are not missing' '
git init &&
echo ../../../.git/objects >.git/objects/info/alternates &&
test_commit C fileC one &&
- git fsck >../out 2>&1
+ git fsck --no-dangling >../actual 2>&1
) &&
- {
- grep -v dangling out >actual ||
- :
- } &&
test_cmp empty actual
'
@@ -110,6 +106,42 @@ test_expect_success 'email with embedded > is not okay' '
grep "error in commit $new" out
'
+test_expect_success 'missing < email delimiter is reported nicely' '
+ git cat-file commit HEAD >basis &&
+ sed "s/<//" basis >bad-email-2 &&
+ new=$(git hash-object -t commit -w --stdin <bad-email-2) &&
+ test_when_finished "remove_object $new" &&
+ git update-ref refs/heads/bogus "$new" &&
+ test_when_finished "git update-ref -d refs/heads/bogus" &&
+ git fsck 2>out &&
+ cat out &&
+ grep "error in commit $new.* - bad name" out
+'
+
+test_expect_success 'missing email is reported nicely' '
+ git cat-file commit HEAD >basis &&
+ sed "s/[a-z]* <[^>]*>//" basis >bad-email-3 &&
+ new=$(git hash-object -t commit -w --stdin <bad-email-3) &&
+ test_when_finished "remove_object $new" &&
+ git update-ref refs/heads/bogus "$new" &&
+ test_when_finished "git update-ref -d refs/heads/bogus" &&
+ git fsck 2>out &&
+ cat out &&
+ grep "error in commit $new.* - missing email" out
+'
+
+test_expect_success '> in name is reported' '
+ git cat-file commit HEAD >basis &&
+ sed "s/ </> </" basis >bad-email-4 &&
+ new=$(git hash-object -t commit -w --stdin <bad-email-4) &&
+ test_when_finished "remove_object $new" &&
+ git update-ref refs/heads/bogus "$new" &&
+ test_when_finished "git update-ref -d refs/heads/bogus" &&
+ git fsck 2>out &&
+ cat out &&
+ grep "error in commit $new" out
+'
+
test_expect_success 'tag pointing to nonexistent' '
cat >invalid-tag <<-\EOF &&
object ffffffffffffffffffffffffffffffffffffffff
@@ -155,4 +187,30 @@ test_expect_success 'cleaned up' '
test_cmp empty actual
'
+test_expect_success 'rev-list --verify-objects' '
+ git rev-list --verify-objects --all >/dev/null 2>out &&
+ test_cmp empty out
+'
+
+test_expect_success 'rev-list --verify-objects with bad sha1' '
+ sha=$(echo blob | git hash-object -w --stdin) &&
+ old=$(echo $sha | sed "s+^..+&/+") &&
+ new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff &&
+ sha="$(dirname $new)$(basename $new)" &&
+ mv .git/objects/$old .git/objects/$new &&
+ test_when_finished "remove_object $sha" &&
+ git update-index --add --cacheinfo 100644 $sha foo &&
+ test_when_finished "git read-tree -u --reset HEAD" &&
+ tree=$(git write-tree) &&
+ test_when_finished "remove_object $tree" &&
+ cmt=$(echo bogus | git commit-tree $tree) &&
+ test_when_finished "remove_object $cmt" &&
+ git update-ref refs/heads/bogus $cmt &&
+ test_when_finished "git update-ref -d refs/heads/bogus" &&
+
+ test_might_fail git rev-list --verify-objects refs/heads/bogus >/dev/null 2>out &&
+ cat out &&
+ grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out
+'
+
test_done
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 6384983..8f36aa9 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -48,7 +48,7 @@ test_expect_success 'setup: helper for testing rev-parse' '
'
test_expect_success 'setup: core.worktree = relative path' '
- unset GIT_WORK_TREE;
+ sane_unset GIT_WORK_TREE &&
GIT_DIR=repo.git &&
GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
export GIT_DIR GIT_CONFIG &&
@@ -68,7 +68,7 @@ test_expect_success 'inside work tree' '
)
'
-test_expect_failure 'empty prefix is actually written out' '
+test_expect_success 'empty prefix is actually written out' '
echo >expected &&
(
cd work &&
@@ -89,7 +89,7 @@ test_expect_success 'subdir of work tree' '
'
test_expect_success 'setup: core.worktree = absolute path' '
- unset GIT_WORK_TREE;
+ sane_unset GIT_WORK_TREE &&
GIT_DIR=$(pwd)/repo.git &&
GIT_CONFIG=$GIT_DIR/config &&
export GIT_DIR GIT_CONFIG &&
@@ -334,7 +334,7 @@ test_expect_success 'absolute pathspec should fail gracefully' '
'
test_expect_success 'make_relative_path handles double slashes in GIT_DIR' '
- >dummy_file
+ >dummy_file &&
echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file &&
git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
'
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
index 0843a1c..c5cb77a 100755
--- a/t/t1506-rev-parse-diagnosis.sh
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -171,4 +171,15 @@ test_expect_success 'relative path when startup_info is NULL' '
grep "BUG: startup_info struct is not initialized." error
'
+test_expect_success '<commit>:file correctly diagnosed after a pathname' '
+ test_must_fail git rev-parse file.txt HEAD:file.txt 1>actual 2>error &&
+ test_i18ngrep ! "exists on disk" error &&
+ test_i18ngrep "no such path in the working tree" error &&
+ cat >expect <<-\EOF &&
+ file.txt
+ HEAD:file.txt
+ EOF
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh
index a455551..d6e5761 100755
--- a/t/t1507-rev-parse-upstream.sh
+++ b/t/t1507-rev-parse-upstream.sh
@@ -15,10 +15,18 @@ test_expect_success 'setup' '
test_commit 3 &&
(cd clone &&
test_commit 4 &&
- git branch --track my-side origin/side)
-
+ git branch --track my-side origin/side &&
+ git branch --track local-master master &&
+ git remote add -t master master-only .. &&
+ git fetch master-only &&
+ git branch bad-upstream &&
+ git config branch.bad-upstream.remote master-only &&
+ git config branch.bad-upstream.merge refs/heads/side
+ )
'
+sq="'"
+
full_name () {
(cd clone &&
git rev-parse --symbolic-full-name "$@")
@@ -29,6 +37,11 @@ commit_subject () {
git show -s --pretty=format:%s "$@")
}
+error_message () {
+ (cd clone &&
+ test_must_fail git rev-parse --verify "$@")
+}
+
test_expect_success '@{upstream} resolves to correct full name' '
test refs/remotes/origin/master = "$(full_name @{upstream})"
'
@@ -78,7 +91,6 @@ test_expect_success 'checkout -b new my-side@{u} forks from the same' '
test_expect_success 'merge my-side@{u} records the correct name' '
(
- sq="'\''" &&
cd clone || exit
git checkout master || exit
git branch -D new ;# can fail but is ok
@@ -107,6 +119,69 @@ test_expect_success 'checkout other@{u}' '
test_cmp expect actual
'
+test_expect_success 'branch@{u} works when tracking a local branch' '
+ test refs/heads/master = "$(full_name local-master@{u})"
+'
+
+test_expect_success 'branch@{u} error message when no upstream' '
+ cat >expect <<-EOF &&
+ error: No upstream configured for branch ${sq}non-tracking${sq}
+ fatal: Needed a single revision
+ EOF
+ error_message non-tracking@{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success '@{u} error message when no upstream' '
+ cat >expect <<-EOF &&
+ error: No upstream configured for branch ${sq}master${sq}
+ fatal: Needed a single revision
+ EOF
+ test_must_fail git rev-parse --verify @{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'branch@{u} error message with misspelt branch' '
+ cat >expect <<-EOF &&
+ error: No such branch: ${sq}no-such-branch${sq}
+ fatal: Needed a single revision
+ EOF
+ error_message no-such-branch@{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success '@{u} error message when not on a branch' '
+ cat >expect <<-EOF &&
+ error: HEAD does not point to a branch
+ fatal: Needed a single revision
+ EOF
+ git checkout HEAD^0 &&
+ test_must_fail git rev-parse --verify @{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'branch@{u} error message if upstream branch not fetched' '
+ cat >expect <<-EOF &&
+ error: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
+ fatal: Needed a single revision
+ EOF
+ error_message bad-upstream@{u} 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'pull works when tracking a local branch' '
+(
+ cd clone &&
+ git checkout local-master &&
+ git pull
+)
+'
+
+# makes sense if the previous one succeeded
+test_expect_success '@{u} works when tracking a local branch' '
+ test refs/heads/master = "$(full_name @{u})"
+'
+
cat >expect <<EOF
commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
Reflog: master@{0} (C O Mitter <committer@example.com>)
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
index ec50a9a..80aedfc 100755
--- a/t/t1510-repo-setup.sh
+++ b/t/t1510-repo-setup.sh
@@ -603,7 +603,7 @@ test_expect_success '#22a: core.worktree = GIT_DIR = .git dir' '
# like case #6.
setup_repo 22a "$here/22a/.git" "" unset &&
- setup_repo 22ab . "" unset
+ setup_repo 22ab . "" unset &&
mkdir -p 22a/.git/sub 22a/sub &&
mkdir -p 22ab/.git/sub 22ab/sub &&
try_case 22a/.git unset . \
@@ -742,7 +742,7 @@ test_expect_success '#28: core.worktree and core.bare conflict (gitfile case)' '
# Case #29: GIT_WORK_TREE(+core.worktree) overrides core.bare (gitfile case).
test_expect_success '#29: setup' '
setup_repo 29 non-existent gitfile true &&
- mkdir -p 29/sub/sub 29/wt/sub
+ mkdir -p 29/sub/sub 29/wt/sub &&
(
cd 29 &&
GIT_WORK_TREE="$here/29" &&
diff --git a/t/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh
index e043cb7..eaefc77 100755
--- a/t/t1511-rev-parse-caret.sh
+++ b/t/t1511-rev-parse-caret.sh
@@ -6,7 +6,7 @@ test_description='tests for ref^{stuff}'
test_expect_success 'setup' '
echo blob >a-blob &&
- git tag -a -m blob blob-tag `git hash-object -w a-blob`
+ git tag -a -m blob blob-tag `git hash-object -w a-blob` &&
mkdir a-tree &&
echo moreblobs >a-tree/another-blob &&
git add . &&
diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh
index 36cca14..0f4b289 100755
--- a/t/t2004-checkout-cache-temp.sh
+++ b/t/t2004-checkout-cache-temp.sh
@@ -40,7 +40,7 @@ test_expect_success \
rm -f path* .merge_* out .git/index &&
git read-tree $t1 &&
git checkout-index --temp -- path1 >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
test $(cut "-d " -f2 out) = path1 &&
p=$(cut "-d " -f1 out) &&
test -f $p &&
@@ -51,7 +51,7 @@ test_expect_success \
rm -f path* .merge_* out .git/index &&
git read-tree $t1 &&
git checkout-index -a --temp >out &&
-test $(wc -l <out) = 5 &&
+test_line_count = 5 out &&
for f in path0 path1 path3 path4 asubdir/path5
do
test $(grep $f out | cut "-d " -f2) = $f &&
@@ -69,7 +69,7 @@ test_expect_success \
'checkout one stage 2 to temporary file' '
rm -f path* .merge_* out &&
git checkout-index --stage=2 --temp -- path1 >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
test $(cut "-d " -f2 out) = path1 &&
p=$(cut "-d " -f1 out) &&
test -f $p &&
@@ -79,7 +79,7 @@ test_expect_success \
'checkout all stage 2 to temporary files' '
rm -f path* .merge_* out &&
git checkout-index --all --stage=2 --temp >out &&
-test $(wc -l <out) = 3 &&
+test_line_count = 3 out &&
for f in path1 path2 path4
do
test $(grep $f out | cut "-d " -f2) = $f &&
@@ -92,13 +92,13 @@ test_expect_success \
'checkout all stages/one file to nothing' '
rm -f path* .merge_* out &&
git checkout-index --stage=all --temp -- path0 >out &&
-test $(wc -l <out) = 0'
+test_line_count = 0 out'
test_expect_success \
'checkout all stages/one file to temporary files' '
rm -f path* .merge_* out &&
git checkout-index --stage=all --temp -- path1 >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
test $(cut "-d " -f2 out) = path1 &&
cut "-d " -f1 out | (read s1 s2 s3 &&
test -f $s1 &&
@@ -112,7 +112,7 @@ test_expect_success \
'checkout some stages/one file to temporary files' '
rm -f path* .merge_* out &&
git checkout-index --stage=all --temp -- path2 >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
test $(cut "-d " -f2 out) = path2 &&
cut "-d " -f1 out | (read s1 s2 s3 &&
test $s1 = . &&
@@ -125,7 +125,7 @@ test_expect_success \
'checkout all stages/all files to temporary files' '
rm -f path* .merge_* out &&
git checkout-index -a --stage=all --temp >out &&
-test $(wc -l <out) = 5'
+test_line_count = 5 out'
test_expect_success \
'-- path0: no entry' '
@@ -185,7 +185,7 @@ test_expect_success \
'checkout --temp within subdir' '
(cd asubdir &&
git checkout-index -a --stage=all >out &&
- test $(wc -l <out) = 1 &&
+ test_line_count = 1 out &&
test $(grep path5 out | cut "-d " -f2) = path5 &&
grep path5 out | cut "-d " -f1 | (read s1 s2 s3 &&
test -f ../$s1 &&
@@ -203,7 +203,7 @@ t4=$(git write-tree) &&
rm -f .git/index &&
git read-tree $t4 &&
git checkout-index --temp -a >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
test $(cut "-d " -f2 out) = a &&
p=$(cut "-d " -f1 out) &&
test -f $p &&
diff --git a/t/t2015-checkout-unborn.sh b/t/t2015-checkout-unborn.sh
index 6352b74..37bdced 100755
--- a/t/t2015-checkout-unborn.sh
+++ b/t/t2015-checkout-unborn.sh
@@ -46,4 +46,15 @@ test_expect_success 'checking out another branch from unborn state' '
test_cmp expect actual
'
+test_expect_success 'checking out in a newly created repo' '
+ test_create_repo empty &&
+ (
+ cd empty &&
+ git symbolic-ref HEAD >expect &&
+ test_must_fail git checkout &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh
index a42e039..2741262 100755
--- a/t/t2018-checkout-branch.sh
+++ b/t/t2018-checkout-branch.sh
@@ -118,6 +118,15 @@ test_expect_success 'checkout -b to an existing branch fails' '
test_must_fail do_checkout branch2 $HEAD2
'
+test_expect_success 'checkout -b to @{-1} fails with the right branch name' '
+ git reset --hard HEAD &&
+ git checkout branch1 &&
+ git checkout branch2 &&
+ echo >expect "fatal: A branch named '\''branch1'\'' already exists." &&
+ test_must_fail git checkout -b @{-1} 2>actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'checkout -B to an existing branch resets branch to HEAD' '
git checkout branch1 &&
@@ -180,4 +189,13 @@ test_expect_success 'checkout -b <describe>' '
test_cmp expect actual
'
+test_expect_success 'checkout -B to the current branch works' '
+ git checkout branch1 &&
+ git checkout -B branch1-scratch &&
+
+ setup_dirty_mergeable &&
+ git checkout -B branch1-scratch initial &&
+ test_dirty_mergeable
+'
+
test_done
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 2366f0f..8100537 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -11,12 +11,14 @@ check_not_detached () {
git symbolic-ref -q HEAD >/dev/null
}
-ORPHAN_WARNING='you are leaving .* commit.*behind'
+PREV_HEAD_DESC='Previous HEAD position was'
check_orphan_warning() {
- test_i18ngrep "$ORPHAN_WARNING" "$1"
+ test_i18ngrep "you are leaving $2 behind" "$1" &&
+ test_i18ngrep ! "$PREV_HEAD_DESC" "$1"
}
check_no_orphan_warning() {
- test_i18ngrep ! "$ORPHAN_WARNING" "$1"
+ test_i18ngrep ! "you are leaving .* commit.*behind" "$1" &&
+ test_i18ngrep "$PREV_HEAD_DESC" "$1"
}
reset () {
@@ -107,12 +109,24 @@ test_expect_success 'checkout warns on orphan commits' '
git checkout --detach two &&
echo content >orphan &&
git add orphan &&
- git commit -a -m orphan &&
+ git commit -a -m orphan1 &&
+ echo new content >orphan &&
+ git commit -a -m orphan2 &&
+ orphan2=$(git rev-parse HEAD) &&
git checkout master 2>stderr
'
test_expect_success 'checkout warns on orphan commits: output' '
- check_orphan_warning stderr
+ check_orphan_warning stderr "2 commits"
+'
+
+test_expect_success 'checkout warns orphaning 1 of 2 commits' '
+ git checkout "$orphan2" &&
+ git checkout HEAD^ 2>stderr
+'
+
+test_expect_success 'checkout warns orphaning 1 of 2 commits: output' '
+ check_orphan_warning stderr "1 commit"
'
test_expect_success 'checkout does not warn leaving ref tip' '
@@ -145,7 +159,7 @@ test_expect_success 'tracking count is accurate after orphan check' '
git config branch.child.merge refs/heads/master &&
git checkout child^ &&
git checkout child >stdout &&
- test_cmp expect stdout
+ test_i18ncmp expect stdout
'
test_done
diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh
new file mode 100755
index 0000000..56090d2
--- /dev/null
+++ b/t/t2022-checkout-paths.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='checkout $tree -- $paths'
+. ./test-lib.sh
+
+test_expect_success setup '
+ mkdir dir &&
+ >dir/master &&
+ echo common >dir/common &&
+ git add dir/master dir/common &&
+ test_tick && git commit -m "master has dir/master" &&
+ git checkout -b next &&
+ git mv dir/master dir/next0 &&
+ echo next >dir/next1 &&
+ git add dir &&
+ test_tick && git commit -m "next has dir/next but not dir/master"
+'
+
+test_expect_success 'checking out paths out of a tree does not clobber unrelated paths' '
+ git checkout next &&
+ git reset --hard &&
+ rm dir/next0 &&
+ cat dir/common >expect.common &&
+ echo modified >expect.next1 &&
+ cat expect.next1 >dir/next1 &&
+ echo untracked >expect.next2 &&
+ cat expect.next2 >dir/next2 &&
+
+ git checkout master dir &&
+
+ test_cmp expect.common dir/common &&
+ test_path_is_file dir/master &&
+ git diff --exit-code master dir/master &&
+
+ test_path_is_missing dir/next0 &&
+ test_cmp expect.next1 dir/next1 &&
+ test_path_is_file dir/next2 &&
+ test_must_fail git ls-files --error-unmatch dir/next2 &&
+ test_cmp expect.next2 dir/next2
+'
+
+test_done
diff --git a/t/t2023-checkout-m.sh b/t/t2023-checkout-m.sh
new file mode 100755
index 0000000..7e18985
--- /dev/null
+++ b/t/t2023-checkout-m.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='checkout -m -- <conflicted path>
+
+Ensures that checkout -m on a resolved file restores the conflicted file'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_tick &&
+ test_commit both.txt both.txt initial &&
+ git branch topic &&
+ test_commit modified_in_master both.txt in_master &&
+ test_commit added_in_master each.txt in_master &&
+ git checkout topic &&
+ test_commit modified_in_topic both.txt in_topic &&
+ test_commit added_in_topic each.txt in_topic
+'
+
+test_expect_success 'git merge master' '
+ test_must_fail git merge master
+'
+
+clean_branchnames () {
+ # Remove branch names after conflict lines
+ sed 's/^\([<>]\{5,\}\) .*$/\1/'
+}
+
+test_expect_success '-m restores 2-way conflicted+resolved file' '
+ cp each.txt each.txt.conflicted &&
+ echo resolved >each.txt &&
+ git add each.txt &&
+ git checkout -m -- each.txt &&
+ clean_branchnames <each.txt >each.txt.cleaned &&
+ clean_branchnames <each.txt.conflicted >each.txt.conflicted.cleaned &&
+ test_cmp each.txt.conflicted.cleaned each.txt.cleaned
+'
+
+test_expect_success '-m restores 3-way conflicted+resolved file' '
+ cp both.txt both.txt.conflicted &&
+ echo resolved >both.txt &&
+ git add both.txt &&
+ git checkout -m -- both.txt &&
+ clean_branchnames <both.txt >both.txt.cleaned &&
+ clean_branchnames <both.txt.conflicted >both.txt.conflicted.cleaned &&
+ test_cmp both.txt.conflicted.cleaned both.txt.cleaned
+'
+
+test_done
diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh
index cb7effe..f262065 100755
--- a/t/t2030-unresolve-info.sh
+++ b/t/t2030-unresolve-info.sh
@@ -113,7 +113,7 @@ test_expect_success 'unmerge with plumbing' '
prime_resolve_undo &&
git update-index --unresolve fi/le &&
git ls-files -u >actual &&
- test $(wc -l <actual) = 3
+ test_line_count = 3 actual
'
test_expect_success 'rerere and rerere forget' '
diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh
index 2543529..ec35409 100755
--- a/t/t2203-add-intent.sh
+++ b/t/t2203-add-intent.sh
@@ -32,7 +32,7 @@ test_expect_success 'intent to add does not clobber existing paths' '
! grep "$empty" actual
'
-test_expect_success 'cannot commit with i-t-a entry' '
+test_expect_success 'i-t-a entry is simply ignored' '
test_tick &&
git commit -a -m initial &&
git reset --hard &&
@@ -41,12 +41,14 @@ test_expect_success 'cannot commit with i-t-a entry' '
echo frotz >nitfol &&
git add rezrov &&
git add -N nitfol &&
- test_must_fail git commit -m initial
+ git commit -m second &&
+ test $(git ls-tree HEAD -- nitfol | wc -l) = 0 &&
+ test $(git diff --name-only HEAD -- nitfol | wc -l) = 1
'
test_expect_success 'can commit with an unrelated i-t-a entry in index' '
git reset --hard &&
- echo xyzzy >rezrov &&
+ echo bozbar >rezrov &&
echo frotz >nitfol &&
git add rezrov &&
git add -N nitfol &&
diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh
index 2eec011..88be904 100755
--- a/t/t3000-ls-files-others.sh
+++ b/t/t3000-ls-files-others.sh
@@ -65,4 +65,23 @@ test_expect_success '--no-empty-directory hides empty directory' '
test_cmp expected3 output
'
+test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' '
+ git init super &&
+ git init sub &&
+ (
+ cd sub &&
+ >a &&
+ git add a &&
+ git commit -m sub &&
+ git pack-refs --all
+ ) &&
+ (
+ cd super &&
+ "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" ../sub sub
+ git ls-files --others --exclude-standard >../actual
+ ) &&
+ echo sub/ >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
index 0c02d56..a5e3da7 100755
--- a/t/t3030-merge-recursive.sh
+++ b/t/t3030-merge-recursive.sh
@@ -267,7 +267,8 @@ test_expect_success 'setup 8' '
ln -s e a &&
git add a e &&
test_tick &&
- git commit -m "rename a->e, symlink a->e"
+ git commit -m "rename a->e, symlink a->e" &&
+ oln=`printf e | git hash-object --stdin`
fi
'
@@ -284,17 +285,7 @@ test_expect_success 'merge-recursive simple' '
rm -fr [abcd] &&
git checkout -f "$c2" &&
- git merge-recursive "$c0" -- "$c2" "$c1"
- status=$?
- case "$status" in
- 1)
- : happy
- ;;
- *)
- echo >&2 "why status $status!!!"
- false
- ;;
- esac
+ test_expect_code 1 git merge-recursive "$c0" -- "$c2" "$c1"
'
test_expect_success 'merge-recursive result' '
@@ -333,17 +324,7 @@ test_expect_success 'merge-recursive remove conflict' '
rm -fr [abcd] &&
git checkout -f "$c1" &&
- git merge-recursive "$c0" -- "$c1" "$c5"
- status=$?
- case "$status" in
- 1)
- : happy
- ;;
- *)
- echo >&2 "why status $status!!!"
- false
- ;;
- esac
+ test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c5"
'
test_expect_success 'merge-recursive remove conflict' '
@@ -387,17 +368,7 @@ test_expect_success 'merge-recursive d/f conflict' '
git reset --hard &&
git checkout -f "$c1" &&
- git merge-recursive "$c0" -- "$c1" "$c4"
- status=$?
- case "$status" in
- 1)
- : happy
- ;;
- *)
- echo >&2 "why status $status!!!"
- false
- ;;
- esac
+ test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c4"
'
test_expect_success 'merge-recursive d/f conflict result' '
@@ -421,17 +392,7 @@ test_expect_success 'merge-recursive d/f conflict the other way' '
git reset --hard &&
git checkout -f "$c4" &&
- git merge-recursive "$c0" -- "$c4" "$c1"
- status=$?
- case "$status" in
- 1)
- : happy
- ;;
- *)
- echo >&2 "why status $status!!!"
- false
- ;;
- esac
+ test_expect_code 1 git merge-recursive "$c0" -- "$c4" "$c1"
'
test_expect_success 'merge-recursive d/f conflict result the other way' '
@@ -455,17 +416,7 @@ test_expect_success 'merge-recursive d/f conflict' '
git reset --hard &&
git checkout -f "$c1" &&
- git merge-recursive "$c0" -- "$c1" "$c6"
- status=$?
- case "$status" in
- 1)
- : happy
- ;;
- *)
- echo >&2 "why status $status!!!"
- false
- ;;
- esac
+ test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c6"
'
test_expect_success 'merge-recursive d/f conflict result' '
@@ -489,17 +440,7 @@ test_expect_success 'merge-recursive d/f conflict' '
git reset --hard &&
git checkout -f "$c6" &&
- git merge-recursive "$c0" -- "$c6" "$c1"
- status=$?
- case "$status" in
- 1)
- : happy
- ;;
- *)
- echo >&2 "why status $status!!!"
- false
- ;;
- esac
+ test_expect_code 1 git merge-recursive "$c0" -- "$c6" "$c1"
'
test_expect_success 'merge-recursive d/f conflict result' '
@@ -630,16 +571,18 @@ test_expect_success 'merge-recursive copy vs. rename' '
if test_have_prereq SYMLINKS
then
- test_expect_success 'merge-recursive rename vs. rename/symlink' '
+ test_expect_failure 'merge-recursive rename vs. rename/symlink' '
git checkout -f rename &&
git merge rename-ln &&
( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
(
+ echo "120000 blob $oln a"
echo "100644 blob $o0 b"
echo "100644 blob $o0 c"
echo "100644 blob $o0 d/e"
echo "100644 blob $o0 e"
+ echo "120000 $oln 0 a"
echo "100644 $o0 0 b"
echo "100644 $o0 0 c"
echo "100644 $o0 0 d/e"
diff --git a/t/t3040-subprojects-basic.sh b/t/t3040-subprojects-basic.sh
index f6973e9..0a4ff6d 100755
--- a/t/t3040-subprojects-basic.sh
+++ b/t/t3040-subprojects-basic.sh
@@ -3,81 +3,81 @@
test_description='Basic subproject functionality'
. ./test-lib.sh
-test_expect_success 'Super project creation' \
- ': >Makefile &&
- git add Makefile &&
- git commit -m "Superproject created"'
-
-
-cat >expected <<EOF
-:000000 160000 00000... A sub1
-:000000 160000 00000... A sub2
-EOF
-test_expect_success 'create subprojects' \
- 'mkdir sub1 &&
- ( cd sub1 && git init && : >Makefile && git add * &&
- git commit -q -m "subproject 1" ) &&
- mkdir sub2 &&
- ( cd sub2 && git init && : >Makefile && git add * &&
- git commit -q -m "subproject 2" ) &&
- git update-index --add sub1 &&
- git add sub2 &&
- git commit -q -m "subprojects added" &&
- git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
- test_cmp expected current'
-
-git branch save HEAD
-
-test_expect_success 'check if fsck ignores the subprojects' \
- 'git fsck --full'
-
-test_expect_success 'check if commit in a subproject detected' \
- '( cd sub1 &&
- echo "all:" >>Makefile &&
- echo " true" >>Makefile &&
- git commit -q -a -m "make all" ) && {
- git diff-files --exit-code
- test $? = 1
- }'
-
-test_expect_success 'check if a changed subproject HEAD can be committed' \
- 'git commit -q -a -m "sub1 changed" && {
- git diff-tree --exit-code HEAD^ HEAD
- test $? = 1
- }'
-
-test_expect_success 'check if diff-index works for subproject elements' \
- 'git diff-index --exit-code --cached save -- sub1
- test $? = 1'
-
-test_expect_success 'check if diff-tree works for subproject elements' \
- 'git diff-tree --exit-code HEAD^ HEAD -- sub1
- test $? = 1'
-
-test_expect_success 'check if git diff works for subproject elements' \
- 'git diff --exit-code HEAD^ HEAD
- test $? = 1'
-
-test_expect_success 'check if clone works' \
- 'git ls-files -s >expected &&
- git clone -l -s . cloned &&
- ( cd cloned && git ls-files -s ) >current &&
- test_cmp expected current'
-
-test_expect_success 'removing and adding subproject' \
- 'git update-index --force-remove -- sub2 &&
- mv sub2 sub3 &&
- git add sub3 &&
- git commit -q -m "renaming a subproject" && {
- git diff -M --name-status --exit-code HEAD^ HEAD
- test $? = 1
- }'
+test_expect_success 'setup: create superproject' '
+ : >Makefile &&
+ git add Makefile &&
+ git commit -m "Superproject created"
+'
+
+test_expect_success 'setup: create subprojects' '
+ mkdir sub1 &&
+ ( cd sub1 && git init && : >Makefile && git add * &&
+ git commit -q -m "subproject 1" ) &&
+ mkdir sub2 &&
+ ( cd sub2 && git init && : >Makefile && git add * &&
+ git commit -q -m "subproject 2" ) &&
+ git update-index --add sub1 &&
+ git add sub2 &&
+ git commit -q -m "subprojects added" &&
+ git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
+ git branch save HEAD &&
+ cat >expected <<-\EOF &&
+ :000000 160000 00000... A sub1
+ :000000 160000 00000... A sub2
+ EOF
+ test_cmp expected current
+'
+
+test_expect_success 'check if fsck ignores the subprojects' '
+ git fsck --full
+'
+
+test_expect_success 'check if commit in a subproject detected' '
+ ( cd sub1 &&
+ echo "all:" >>Makefile &&
+ echo " true" >>Makefile &&
+ git commit -q -a -m "make all" ) &&
+ test_expect_code 1 git diff-files --exit-code
+'
+
+test_expect_success 'check if a changed subproject HEAD can be committed' '
+ git commit -q -a -m "sub1 changed" &&
+ test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'check if diff-index works for subproject elements' '
+ test_expect_code 1 git diff-index --exit-code --cached save -- sub1
+'
+
+test_expect_success 'check if diff-tree works for subproject elements' '
+ test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD -- sub1
+'
+
+test_expect_success 'check if git diff works for subproject elements' '
+ test_expect_code 1 git diff --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'check if clone works' '
+ git ls-files -s >expected &&
+ git clone -l -s . cloned &&
+ ( cd cloned && git ls-files -s ) >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'removing and adding subproject' '
+ git update-index --force-remove -- sub2 &&
+ mv sub2 sub3 &&
+ git add sub3 &&
+ git commit -q -m "renaming a subproject" &&
+ test_expect_code 1 git diff -M --name-status --exit-code HEAD^ HEAD
+'
# the index must contain the object name the HEAD of the
# subproject sub1 was at the point "save"
-test_expect_success 'checkout in superproject' \
- 'git checkout save &&
- git diff-index --exit-code --raw --cached save -- sub1'
+test_expect_success 'checkout in superproject' '
+ git checkout save &&
+ git diff-index --exit-code --raw --cached save -- sub1
+'
# just interesting what happened...
# git diff --name-status -M save master
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 9e69c8c..a17f8b2 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -3,11 +3,8 @@
# Copyright (c) 2005 Amos Waterland
#
-test_description='git branch --foo should not create bogus branch
+test_description='git branch assorted tests'
-This test runs git branch --help and checks that the argument is properly
-handled. Specifically, that a bogus branch is not created.
-'
. ./test-lib.sh
test_expect_success \
@@ -22,8 +19,8 @@ test_expect_success \
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_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
+ test_path_is_missing .git/refs/heads/--help
'
test_expect_success 'branch -h in broken repository' '
@@ -39,11 +36,11 @@ test_expect_success 'branch -h in broken repository' '
test_expect_success \
'git branch abc should create a branch' \
- 'git branch abc && test -f .git/refs/heads/abc'
+ 'git branch abc && test_path_is_file .git/refs/heads/abc'
test_expect_success \
'git branch a/b/c should create a branch' \
- 'git branch a/b/c && test -f .git/refs/heads/a/b/c'
+ 'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c'
cat >expect <<EOF
$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
@@ -52,15 +49,15 @@ test_expect_success \
'git branch -l d/e/f should create a branch and a log' \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
git branch -l d/e/f &&
- test -f .git/refs/heads/d/e/f &&
- test -f .git/logs/refs/heads/d/e/f &&
+ test_path_is_file .git/refs/heads/d/e/f &&
+ test_path_is_file .git/logs/refs/heads/d/e/f &&
test_cmp expect .git/logs/refs/heads/d/e/f'
test_expect_success \
'git branch -d d/e/f should delete a branch and a log' \
'git branch -d d/e/f &&
- test ! -f .git/refs/heads/d/e/f &&
- test ! -f .git/logs/refs/heads/d/e/f'
+ test_path_is_missing .git/refs/heads/d/e/f &&
+ test_path_is_missing .git/logs/refs/heads/d/e/f'
test_expect_success \
'git branch j/k should work after branch j has been deleted' \
@@ -75,16 +72,21 @@ test_expect_success \
git branch l'
test_expect_success \
+ 'git branch -m dumps usage' \
+ 'test_expect_code 129 git branch -m 2>err &&
+ grep "[Uu]sage: git branch" err'
+
+test_expect_success \
'git branch -m m m/m should work' \
'git branch -l m &&
git branch -m m m/m &&
- test -f .git/logs/refs/heads/m/m'
+ test_path_is_file .git/logs/refs/heads/m/m'
test_expect_success \
'git branch -m n/n n should work' \
'git branch -l n/n &&
- git branch -m n/n n
- test -f .git/logs/refs/heads/n'
+ git branch -m n/n n &&
+ test_path_is_file .git/logs/refs/heads/n'
test_expect_success 'git branch -m o/o o should fail when o/p exists' '
git branch o/o &&
@@ -98,6 +100,143 @@ test_expect_success 'git branch -m q r/q should fail when r exists' '
test_must_fail git branch -m q r/q
'
+test_expect_success 'git branch -M foo bar should fail when bar is checked out' '
+ git branch bar &&
+ git checkout -b foo &&
+ test_must_fail git branch -M bar foo
+'
+
+test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
+ git checkout -b baz &&
+ git branch bam &&
+ git branch -M baz bam
+'
+
+test_expect_success 'git branch -M master should work when master is checked out' '
+ git checkout master &&
+ git branch -M master
+'
+
+test_expect_success 'git branch -M master master should work when master is checked out' '
+ git checkout master &&
+ git branch -M master master
+'
+
+test_expect_success 'git branch -M master2 master2 should work when master is checked out' '
+ git checkout master &&
+ git branch master2 &&
+ git branch -M master2 master2
+'
+
+test_expect_success 'git branch -v -d t should work' '
+ git branch t &&
+ test_path_is_file .git/refs/heads/t &&
+ git branch -v -d t &&
+ test_path_is_missing .git/refs/heads/t
+'
+
+test_expect_success 'git branch -v -m t s should work' '
+ git branch t &&
+ test_path_is_file .git/refs/heads/t &&
+ git branch -v -m t s &&
+ test_path_is_missing .git/refs/heads/t &&
+ test_path_is_file .git/refs/heads/s &&
+ git branch -d s
+'
+
+test_expect_success 'git branch -m -d t s should fail' '
+ git branch t &&
+ test_path_is_file .git/refs/heads/t &&
+ test_must_fail git branch -m -d t s &&
+ git branch -d t &&
+ test_path_is_missing .git/refs/heads/t
+'
+
+test_expect_success 'git branch --list -d t should fail' '
+ git branch t &&
+ test_path_is_file .git/refs/heads/t &&
+ test_must_fail git branch --list -d t &&
+ git branch -d t &&
+ test_path_is_missing .git/refs/heads/t
+'
+
+test_expect_success 'git branch --column' '
+ COLUMNS=81 git branch --column=column >actual &&
+ cat >expected <<\EOF &&
+ a/b/c bam foo l * master n o/p r
+ abc bar j/k m/m master2 o/o q
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch --column with an extremely long branch name' '
+ long=this/is/a/part/of/long/branch/name &&
+ long=z$long/$long/$long/$long &&
+ test_when_finished "git branch -d $long" &&
+ git branch $long &&
+ COLUMNS=80 git branch --column=column >actual &&
+ cat >expected <<EOF &&
+ a/b/c
+ abc
+ bam
+ bar
+ foo
+ j/k
+ l
+ m/m
+* master
+ master2
+ n
+ o/o
+ o/p
+ q
+ r
+ $long
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch with column.*' '
+ git config column.ui column &&
+ git config column.branch "dense" &&
+ COLUMNS=80 git branch >actual &&
+ git config --unset column.branch &&
+ git config --unset column.ui &&
+ cat >expected <<\EOF &&
+ a/b/c bam foo l * master n o/p r
+ abc bar j/k m/m master2 o/o q
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch --column -v should fail' '
+ test_must_fail git branch --column -v
+'
+
+test_expect_success 'git branch -v with column.ui ignored' '
+ git config column.ui column &&
+ COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual &&
+ git config --unset column.ui &&
+ cat >expected <<\EOF &&
+ a/b/c
+ abc
+ bam
+ bar
+ foo
+ j/k
+ l
+ m/m
+* master
+ master2
+ n
+ o/o
+ o/p
+ q
+ r
+EOF
+ test_cmp expected actual
+'
+
mv .git/config .git/config-saved
test_expect_success 'git branch -m q q2 without config should succeed' '
@@ -112,12 +251,12 @@ git config branch.s/s.dummy Hello
test_expect_success \
'git branch -m s/s s should work when s/t is deleted' \
'git branch -l s/s &&
- test -f .git/logs/refs/heads/s/s &&
+ test_path_is_file .git/logs/refs/heads/s/s &&
git branch -l s/t &&
- test -f .git/logs/refs/heads/s/t &&
+ test_path_is_file .git/logs/refs/heads/s/t &&
git branch -d s/t &&
git branch -m s/s s &&
- test -f .git/logs/refs/heads/s'
+ test_path_is_file .git/logs/refs/heads/s'
test_expect_success 'config information was renamed, too' \
"test $(git config branch.s.dummy) = Hello &&
@@ -128,8 +267,8 @@ test_expect_success 'renaming a symref is not allowed' \
git symbolic-ref refs/heads/master2 refs/heads/master &&
test_must_fail git branch -m master2 master3 &&
git symbolic-ref refs/heads/master2 &&
- test -f .git/refs/heads/master &&
- ! test -f .git/refs/heads/master3
+ test_path_is_file .git/refs/heads/master &&
+ test_path_is_missing .git/refs/heads/master3
'
test_expect_success SYMLINKS \
@@ -238,8 +377,8 @@ test_expect_success \
'git checkout -b g/h/i -l should create a branch and a log' \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
git checkout -b g/h/i -l master &&
- test -f .git/refs/heads/g/h/i &&
- test -f .git/logs/refs/heads/g/h/i &&
+ test_path_is_file .git/refs/heads/g/h/i &&
+ test_path_is_file .git/logs/refs/heads/g/h/i &&
test_cmp expect .git/logs/refs/heads/g/h/i'
test_expect_success 'checkout -b makes reflog by default' '
@@ -542,4 +681,57 @@ test_expect_success 'attempt to delete a branch merged to its base' '
test_must_fail git branch -d my10
'
+test_expect_success 'use set-upstream on the current branch' '
+ git checkout master &&
+ git --bare init myupstream.git &&
+ git push myupstream.git master:refs/heads/frotz &&
+ git remote add origin myupstream.git &&
+ git fetch &&
+ git branch --set-upstream master origin/frotz &&
+
+ test "z$(git config branch.master.remote)" = "zorigin" &&
+ test "z$(git config branch.master.merge)" = "zrefs/heads/frotz"
+
+'
+
+test_expect_success 'use --edit-description' '
+ write_script editor <<-\EOF &&
+ echo "New contents" >"$1"
+ EOF
+ EDITOR=./editor git branch --edit-description &&
+ write_script editor <<-\EOF &&
+ git stripspace -s <"$1" >"EDITOR_OUTPUT"
+ EOF
+ EDITOR=./editor git branch --edit-description &&
+ echo "New contents" >expect &&
+ test_cmp EDITOR_OUTPUT expect
+'
+
+test_expect_success 'detect typo in branch name when using --edit-description' '
+ write_script editor <<-\EOF &&
+ echo "New contents" >"$1"
+ EOF
+ (
+ EDITOR=./editor &&
+ export EDITOR &&
+ test_must_fail git branch --edit-description no-such-branch
+ )
+'
+
+test_expect_success 'refuse --edit-description on unborn branch for now' '
+ write_script editor <<-\EOF &&
+ echo "New contents" >"$1"
+ EOF
+ git checkout --orphan unborn &&
+ (
+ EDITOR=./editor &&
+ export EDITOR &&
+ test_must_fail git branch --edit-description
+ )
+'
+
+test_expect_success '--merged catches invalid object names' '
+ test_must_fail git branch --merged 0000000000000000000000000000000000000000
+'
+
test_done
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
index 6b7c118..76fe7e0 100755
--- a/t/t3203-branch-output.sh
+++ b/t/t3203-branch-output.sh
@@ -32,6 +32,20 @@ test_expect_success 'git branch shows local branches' '
test_cmp expect actual
'
+test_expect_success 'git branch --list shows local branches' '
+ git branch --list >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+ branch-one
+ branch-two
+EOF
+test_expect_success 'git branch --list pattern shows matching local branches' '
+ git branch --list branch* >actual &&
+ test_cmp expect actual
+'
+
cat >expect <<'EOF'
origin/HEAD -> origin/branch-one
origin/branch-one
@@ -67,6 +81,20 @@ test_expect_success 'git branch -v shows branch summaries' '
'
cat >expect <<'EOF'
+two
+one
+EOF
+test_expect_success 'git branch --list -v pattern shows branch summaries' '
+ git branch --list -v branch* >tmp &&
+ awk "{print \$NF}" <tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git branch -v pattern does not show branch summaries' '
+ test_must_fail git branch -v branch*
+'
+
+cat >expect <<'EOF'
* (no branch)
branch-one
branch-two
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
index 5e29a05..1f35e55 100755
--- a/t/t3300-funny-names.sh
+++ b/t/t3300-funny-names.sh
@@ -15,184 +15,204 @@ p0='no-funny'
p1='tabs ," (dq) and spaces'
p2='just space'
-cat >"$p0" <<\EOF
-1. A quick brown fox jumps over the lazy cat, oops dog.
-2. A quick brown fox jumps over the lazy cat, oops dog.
-3. A quick brown fox jumps over the lazy cat, oops dog.
-EOF
-
-cat 2>/dev/null >"$p1" "$p0"
-echo 'Foo Bar Baz' >"$p2"
+test_expect_success 'setup' '
+ cat >"$p0" <<-\EOF &&
+ 1. A quick brown fox jumps over the lazy cat, oops dog.
+ 2. A quick brown fox jumps over the lazy cat, oops dog.
+ 3. A quick brown fox jumps over the lazy cat, oops dog.
+ EOF
+
+ { cat "$p0" >"$p1" || :; } &&
+ { echo "Foo Bar Baz" >"$p2" || :; } &&
+
+ if test -f "$p1" && cmp "$p0" "$p1"
+ then
+ test_set_prereq TABS_IN_FILENAMES
+ fi
+'
-if test -f "$p1" && cmp "$p0" "$p1"
+if ! test_have_prereq TABS_IN_FILENAMES
then
- test_set_prereq TABS_IN_FILENAMES
-else
# since FAT/NTFS does not allow tabs in filenames, skip this test
- say 'Your filesystem does not allow tabs in filenames'
+ skip_all='Your filesystem does not allow tabs in filenames'
+ test_done
fi
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'just space
-no-funny' >expected
-"
+test_expect_success 'setup: populate index and tree' '
+ git update-index --add "$p0" "$p2" &&
+ t0=$(git write-tree)
+'
-test_expect_success TABS_IN_FILENAMES 'git ls-files no-funny' \
- 'git update-index --add "$p0" "$p2" &&
+test_expect_success 'ls-files prints space in filename verbatim' '
+ printf "%s\n" "just space" no-funny >expected &&
git ls-files >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-t0=`git write-tree` &&
-echo "$t0" >t0 &&
+ test_cmp expected current
+'
-cat > expected <<\EOF
-just space
-no-funny
-"tabs\t,\" (dq) and spaces"
-EOF
+test_expect_success 'setup: add funny filename' '
+ git update-index --add "$p1" &&
+ t1=$(git write-tree)
'
-test_expect_success TABS_IN_FILENAMES 'git ls-files with-funny' \
- 'git update-index --add "$p1" &&
+test_expect_success 'ls-files quotes funny filename' '
+ cat >expected <<-\EOF &&
+ just space
+ no-funny
+ "tabs\t,\" (dq) and spaces"
+ EOF
git ls-files >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'just space
-no-funny
-tabs ,\" (dq) and spaces' >expected
-"
-
-test_expect_success TABS_IN_FILENAMES 'git ls-files -z with-funny' \
- 'git ls-files -z | perl -pe y/\\000/\\012/ >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-t1=`git write-tree` &&
-echo "$t1" >t1 &&
-
-cat > expected <<\EOF
-just space
-no-funny
-"tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git ls-tree with funny' \
- 'git ls-tree -r $t1 | sed -e "s/^[^ ]* //" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-A "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-index with-funny' \
- 'git diff-index --name-status $t0 >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree with-funny' \
- 'git diff-tree --name-status $t0 $t1 >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'A
-tabs ,\" (dq) and spaces' >expected
-"
-
-test_expect_success TABS_IN_FILENAMES 'git diff-index -z with-funny' \
- 'git diff-index -z --name-status $t0 | perl -pe y/\\000/\\012/ >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree -z with-funny' \
- 'git diff-tree -z --name-status $t0 $t1 | perl -pe y/\\000/\\012/ >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-CNUM no-funny "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree -C with-funny' \
- 'git diff-tree -C --find-copies-harder --name-status \
- $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-RNUM no-funny "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
- 'git update-index --force-remove "$p0" &&
- git diff-index -M --name-status \
- $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
-similarity index NUM%
-rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
- 'git diff-index -M -p $t0 |
- sed -e "s/index [0-9]*%/index NUM%/" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-chmod +x "$p1" &&
-cat > expected <<\EOF
-diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
-old mode 100644
-new mode 100755
-similarity index NUM%
-rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
- 'git diff-index -M -p $t0 |
- sed -e "s/index [0-9]*%/index NUM%/" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat >expected <<\EOF
- "tabs\t,\" (dq) and spaces"
- 1 files changed, 0 insertions(+), 0 deletions(-)
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree rename with-funny applied' \
- 'git diff-index -M -p $t0 |
- git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
- no-funny
- "tabs\t,\" (dq) and spaces"
- 2 files changed, 3 insertions(+), 3 deletions(-)
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny applied' \
- 'git diff-index -p $t0 |
- git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
- test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git apply non-git diff' \
- 'git diff-index -p $t0 |
- sed -ne "/^[-+@]/p" |
- git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
- test_cmp expected current'
+ test_cmp expected current
+'
+
+test_expect_success 'ls-files -z does not quote funny filename' '
+ cat >expected <<-\EOF &&
+ just space
+ no-funny
+ tabs ," (dq) and spaces
+ EOF
+ git ls-files -z >ls-files.z &&
+ "$PERL_PATH" -pe "y/\000/\012/" <ls-files.z >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'ls-tree quotes funny filename' '
+ cat >expected <<-\EOF &&
+ just space
+ no-funny
+ "tabs\t,\" (dq) and spaces"
+ EOF
+ git ls-tree -r $t1 >ls-tree &&
+ sed -e "s/^[^ ]* //" <ls-tree >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-index --name-status quotes funny filename' '
+ cat >expected <<-\EOF &&
+ A "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index --name-status $t0 >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-tree --name-status quotes funny filename' '
+ cat >expected <<-\EOF &&
+ A "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-tree --name-status $t0 $t1 >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-index -z does not quote funny filename' '
+ cat >expected <<-\EOF &&
+ A
+ tabs ," (dq) and spaces
+ EOF
+ git diff-index -z --name-status $t0 >diff-index.z &&
+ "$PERL_PATH" -pe "y/\000/\012/" <diff-index.z >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-tree -z does not quote funny filename' '
+ cat >expected <<-\EOF &&
+ A
+ tabs ," (dq) and spaces
+ EOF
+ git diff-tree -z --name-status $t0 $t1 >diff-tree.z &&
+ "$PERL_PATH" -pe y/\\000/\\012/ <diff-tree.z >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-tree --find-copies-harder quotes funny filename' '
+ cat >expected <<-\EOF &&
+ CNUM no-funny "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-tree -C --find-copies-harder --name-status $t0 $t1 >out &&
+ sed -e "s/^C[0-9]*/CNUM/" <out >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'setup: remove unfunny index entry' '
+ git update-index --force-remove "$p0"
+'
+
+test_expect_success 'diff-tree -M quotes funny filename' '
+ cat >expected <<-\EOF &&
+ RNUM no-funny "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -M --name-status $t0 >out &&
+ sed -e "s/^R[0-9]*/RNUM/" <out >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diff-index -M -p quotes funny filename' '
+ cat >expected <<-\EOF &&
+ diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+ similarity index NUM%
+ rename from no-funny
+ rename to "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -M -p $t0 >diff &&
+ sed -e "s/index [0-9]*%/index NUM%/" <diff >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'setup: mode change' '
+ chmod +x "$p1"
+'
+
+test_expect_success 'diff-index -M -p with mode change quotes funny filename' '
+ cat >expected <<-\EOF &&
+ diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+ old mode 100644
+ new mode 100755
+ similarity index NUM%
+ rename from no-funny
+ rename to "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -M -p $t0 >diff &&
+ sed -e "s/index [0-9]*%/index NUM%/" <diff >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'diffstat for rename quotes funny filename' '
+ cat >expected <<-\EOF &&
+ "tabs\t,\" (dq) and spaces"
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ EOF
+ git diff-index -M -p $t0 >diff &&
+ git apply --stat <diff >diffstat &&
+ sed -e "s/|.*//" -e "s/ *\$//" <diffstat >current &&
+ test_i18ncmp expected current
+'
+
+test_expect_success 'numstat for rename quotes funny filename' '
+ cat >expected <<-\EOF &&
+ 0 0 "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -M -p $t0 >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'numstat without -M quotes funny filename' '
+ cat >expected <<-\EOF &&
+ 0 3 no-funny
+ 3 0 "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -p $t0 >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expected current
+'
+
+test_expect_success 'numstat for non-git rename diff quotes funny filename' '
+ cat >expected <<-\EOF &&
+ 0 3 no-funny
+ 3 0 "tabs\t,\" (dq) and spaces"
+ EOF
+ git diff-index -p $t0 >git-diff &&
+ sed -ne "/^[-+@]/p" <git-diff >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expected current
+'
test_done
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
index 4ec4d11..195bb97 100755
--- a/t/t3310-notes-merge-manual-resolve.sh
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -324,7 +324,7 @@ y and z notes on 4th commit
EOF
git notes merge --commit &&
# No .git/NOTES_MERGE_* files left
- test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_cmp /dev/null output &&
# Merge commit has pre-merge y and pre-merge z as parents
test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
@@ -386,10 +386,10 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
test_expect_success 'abort notes merge' '
git notes merge --abort &&
# No .git/NOTES_MERGE_* files left
- test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_cmp /dev/null output &&
# m has not moved (still == y)
- test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" &&
# Verify that other notes refs has not changed (w, x, y and z)
verify_notes w &&
verify_notes x &&
@@ -453,7 +453,7 @@ EOF
# Finalize merge
git notes merge --commit &&
# No .git/NOTES_MERGE_* files left
- test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_cmp /dev/null output &&
# Merge commit has pre-merge y and pre-merge z as parents
test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
@@ -525,9 +525,9 @@ EOF
test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
# Refs are unchanged
- test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
- test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
- test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
+ test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
+ test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
# Mention refs/notes/m, and its current and expected value in output
grep -q "refs/notes/m" output &&
grep -q "$(git rev-parse refs/notes/m)" output &&
@@ -542,10 +542,10 @@ EOF
test_expect_success 'resolve situation by aborting the notes merge' '
git notes merge --abort &&
# No .git/NOTES_MERGE_* files left
- test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_cmp /dev/null output &&
# m has not moved (still == w)
- test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
# Verify that other notes refs has not changed (w, x, y and z)
verify_notes w &&
verify_notes x &&
@@ -553,4 +553,23 @@ test_expect_success 'resolve situation by aborting the notes merge' '
verify_notes z
'
+cat >expect_notes <<EOF
+foo
+bar
+EOF
+
+test_expect_success 'switch cwd before committing notes merge' '
+ git notes add -m foo HEAD &&
+ git notes --ref=other add -m bar HEAD &&
+ test_must_fail git notes merge refs/notes/other &&
+ (
+ cd .git/NOTES_MERGE_WORKTREE &&
+ echo "foo" > $(git rev-parse HEAD) &&
+ echo "bar" >> $(git rev-parse HEAD) &&
+ git notes merge --commit
+ ) &&
+ git notes show HEAD > actual_notes &&
+ test_cmp expect_notes actual_notes
+'
+
test_done
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 6eaecec..7788ae0 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -160,20 +160,18 @@ rm -f B
test_expect_success 'fail when upstream arg is missing and not on branch' '
git checkout topic &&
- test_must_fail git rebase >output.out &&
- grep "You are not currently on a branch" output.out
+ test_must_fail git rebase
'
test_expect_success 'fail when upstream arg is missing and not configured' '
git checkout -b no-config topic &&
- test_must_fail git rebase >output.out &&
- grep "branch.no-config.merge" output.out
+ test_must_fail git rebase
'
test_expect_success 'default to @{upstream} when upstream arg is missing' '
git checkout -b default topic &&
- git config branch.default.remote .
- git config branch.default.merge refs/heads/master
+ git config branch.default.remote . &&
+ git config branch.default.merge refs/heads/master &&
git rebase &&
test "$(git rev-parse default~1)" = "$(git rev-parse master)"
'
@@ -218,4 +216,27 @@ test_expect_success 'rebase -m can copy notes' '
test "a note" = "$(git notes show HEAD)"
'
+test_expect_success 'rebase commit with an ancient timestamp' '
+ git reset --hard &&
+
+ >old.one && git add old.one && test_tick &&
+ git commit --date="@12345 +0400" -m "Old one" &&
+ >old.two && git add old.two && test_tick &&
+ git commit --date="@23456 +0500" -m "Old two" &&
+ >old.three && git add old.three && test_tick &&
+ git commit --date="@34567 +0600" -m "Old three" &&
+
+ git cat-file commit HEAD^^ >actual &&
+ grep "author .* 12345 +0400$" actual &&
+ git cat-file commit HEAD^ >actual &&
+ grep "author .* 23456 +0500$" actual &&
+ git cat-file commit HEAD >actual &&
+ grep "author .* 34567 +0600$" actual &&
+
+ git rebase --onto HEAD^^ HEAD^ &&
+
+ git cat-file commit HEAD >actual &&
+ grep "author .* 34567 +0600$" actual
+'
+
test_done
diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh
index aea6685..7ba1797 100755
--- a/t/t3401-rebase-partial.sh
+++ b/t/t3401-rebase-partial.sh
@@ -11,51 +11,35 @@ local branch.
'
. ./test-lib.sh
-test_expect_success \
- 'prepare repository with topic branch' \
- 'echo First > A &&
- git update-index --add A &&
- git commit -m "Add A." &&
-
- git checkout -b my-topic-branch &&
-
- echo Second > B &&
- git update-index --add B &&
- git commit -m "Add B." &&
-
- echo AnotherSecond > C &&
- git update-index --add C &&
- git commit -m "Add C." &&
-
- git checkout -f master &&
-
- echo Third >> A &&
- git update-index A &&
- git commit -m "Modify A."
+test_expect_success 'prepare repository with topic branch' '
+ test_commit A &&
+ git checkout -b my-topic-branch &&
+ test_commit B &&
+ test_commit C &&
+ git checkout -f master &&
+ test_commit A2 A.t
'
-test_expect_success \
- 'pick top patch from topic branch into master' \
- 'git cherry-pick my-topic-branch^0 &&
- git checkout -f my-topic-branch &&
- git branch master-merge master &&
- git branch my-topic-branch-merge my-topic-branch
+test_expect_success 'pick top patch from topic branch into master' '
+ git cherry-pick C &&
+ git checkout -f my-topic-branch
'
-test_debug \
- 'git cherry master &&
- git format-patch -k --stdout --full-index master >/dev/null &&
- gitk --all & sleep 1
+test_debug '
+ git cherry master &&
+ git format-patch -k --stdout --full-index master >/dev/null &&
+ gitk --all & sleep 1
'
-test_expect_success \
- 'rebase topic branch against new master and check git am did not get halted' \
- 'git rebase master && test ! -d .git/rebase-apply'
+test_expect_success 'rebase topic branch against new master and check git am did not get halted' '
+ git rebase master &&
+ test_path_is_missing .git/rebase-apply
+'
-test_expect_success \
- 'rebase --merge topic branch that was partially merged upstream' \
- 'git checkout -f my-topic-branch-merge &&
- git rebase --merge master-merge &&
- test ! -d .git/rebase-merge'
+test_expect_success 'rebase --merge topic branch that was partially merged upstream' '
+ git reset --hard C &&
+ git rebase --merge master &&
+ test_path_is_missing .git/rebase-merge
+'
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 8538813..68d6148 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -247,6 +247,7 @@ test_expect_success '-p handles "no changes" gracefully' '
'
test_expect_failure 'exchange two commits with -p' '
+ git checkout H &&
FAKE_LINES="2 1" git rebase -i -p HEAD~2 &&
test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
test G = $(git cat-file commit HEAD | sed -ne \$p)
@@ -323,7 +324,7 @@ test_expect_success 'verbose flag is heeded, even after --continue' '
echo resolved > file1 &&
git add file1 &&
git rebase --continue > output &&
- grep "^ file1 | 2 +-$" output
+ grep "^ file1 | 2 +-$" output
'
test_expect_success 'multi-squash only fires up editor once' '
@@ -527,6 +528,20 @@ test_expect_success 'auto-amend only edited commits after "edit"' '
git rebase --abort
'
+test_expect_success 'clean error after failed "exec"' '
+ test_tick &&
+ test_when_finished "git rebase --abort || :" &&
+ (
+ FAKE_LINES="1 exec_false" &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i HEAD^
+ ) &&
+ echo "edited again" > file7 &&
+ git add file7 &&
+ test_must_fail git rebase --continue 2>error &&
+ grep "You have staged changes in your working tree." error
+'
+
test_expect_success 'rebase a detached HEAD' '
grandparent=$(git rev-parse HEAD~2) &&
git checkout $(git rev-parse HEAD) &&
@@ -610,8 +625,38 @@ test_expect_success 'submodule rebase -i' '
FAKE_LINES="1 squash 2 3" git rebase -i A
'
+test_expect_success 'submodule conflict setup' '
+ git tag submodule-base &&
+ git checkout HEAD^ &&
+ (
+ cd sub && git checkout HEAD^ && echo 4 >elif &&
+ git add elif && git commit -m "submodule conflict"
+ ) &&
+ git add sub &&
+ test_tick &&
+ git commit -m "Conflict in submodule" &&
+ git tag submodule-topic
+'
+
+test_expect_success 'rebase -i continue with only submodule staged' '
+ test_must_fail git rebase -i submodule-base &&
+ git add sub &&
+ git rebase --continue &&
+ test $(git rev-parse submodule-base) != $(git rev-parse HEAD)
+'
+
+test_expect_success 'rebase -i continue with unstaged submodule' '
+ git checkout submodule-topic &&
+ git reset --hard &&
+ test_must_fail git rebase -i submodule-base &&
+ git reset &&
+ git rebase --continue &&
+ test $(git rev-parse submodule-base) = $(git rev-parse HEAD)
+'
+
test_expect_success 'avoid unnecessary reset' '
git checkout master &&
+ git reset --hard &&
test-chmtime =123456789 file3 &&
git update-index --refresh &&
HEAD=$(git rev-parse HEAD) &&
@@ -710,4 +755,121 @@ test_expect_success 'rebase-i history with funny messages' '
test_cmp expect actual
'
+
+test_expect_success 'prepare for rebase -i --exec' '
+ git checkout master &&
+ git checkout -b execute &&
+ test_commit one_exec main.txt one_exec &&
+ test_commit two_exec main.txt two_exec &&
+ test_commit three_exec main.txt three_exec
+'
+
+
+test_expect_success 'running "git rebase -i --exec git show HEAD"' '
+ git rebase -i --exec "git show HEAD" HEAD~2 >actual &&
+ (
+ FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
+ export FAKE_LINES &&
+ git rebase -i HEAD~2 >expect
+ ) &&
+ sed -e "1,9d" expect >expected &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'running "git rebase --exec git show HEAD -i"' '
+ git reset --hard execute &&
+ git rebase --exec "git show HEAD" -i HEAD~2 >actual &&
+ (
+ FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
+ export FAKE_LINES &&
+ git rebase -i HEAD~2 >expect
+ ) &&
+ sed -e "1,9d" expect >expected &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'running "git rebase -ix git show HEAD"' '
+ git reset --hard execute &&
+ git rebase -ix "git show HEAD" HEAD~2 >actual &&
+ (
+ FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
+ export FAKE_LINES &&
+ git rebase -i HEAD~2 >expect
+ ) &&
+ sed -e "1,9d" expect >expected &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'rebase -ix with several <CMD>' '
+ git reset --hard execute &&
+ git rebase -ix "git show HEAD; pwd" HEAD~2 >actual &&
+ (
+ FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" &&
+ export FAKE_LINES &&
+ git rebase -i HEAD~2 >expect
+ ) &&
+ sed -e "1,9d" expect >expected &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'rebase -ix with several instances of --exec' '
+ git reset --hard execute &&
+ git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual &&
+ (
+ FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2
+ exec_git_show_HEAD exec_pwd" &&
+ export FAKE_LINES &&
+ git rebase -i HEAD~2 >expect
+ ) &&
+ sed -e "1,11d" expect >expected &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'rebase -ix with --autosquash' '
+ git reset --hard execute &&
+ git checkout -b autosquash &&
+ echo second >second.txt &&
+ git add second.txt &&
+ git commit -m "fixup! two_exec" &&
+ echo bis >bis.txt &&
+ git add bis.txt &&
+ git commit -m "fixup! two_exec" &&
+ (
+ git checkout -b autosquash_actual &&
+ git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual
+ ) &&
+ git checkout autosquash &&
+ (
+ git checkout -b autosquash_expected &&
+ FAKE_LINES="1 fixup 3 fixup 4 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
+ export FAKE_LINES &&
+ git rebase -i HEAD~4 >expect
+ ) &&
+ sed -e "1,13d" expect >expected &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'rebase --exec without -i shows error message' '
+ git reset --hard execute &&
+ test_must_fail git rebase --exec "git show HEAD" HEAD~2 2>actual &&
+ echo "--exec option must be used with --interactive option" >expected &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'rebase -i --exec without <CMD>' '
+ git reset --hard execute &&
+ test_must_fail git rebase -i --exec 2>tmp &&
+ sed -e "1d" tmp >actual &&
+ test_must_fail git rebase -h >expected &&
+ test_cmp expected actual &&
+ git checkout master
+'
+
test_done
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index fe5f936..6898377 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -62,4 +62,9 @@ test_expect_success 'rebase -n overrides config rebase.stat config' '
! grep "^ fileX | *1 +$" diffstat.txt
'
+test_expect_success 'rebase --onto outputs the invalid ref' '
+ test_must_fail git rebase --onto invalid-ref HEAD HEAD 2>err &&
+ grep "invalid-ref" err
+'
+
test_done
diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh
index ace8e54..dc81bf2 100755
--- a/t/t3411-rebase-preserve-around-merges.sh
+++ b/t/t3411-rebase-preserve-around-merges.sh
@@ -56,6 +56,7 @@ test_expect_success 'squash F1 into D1' '
# And rebase G1..M1 onto E2
test_expect_success 'rebase two levels of merge' '
+ git checkout A1 &&
test_commit G1 &&
test_commit H1 &&
test_commit I1 &&
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
index b38be8e..a1e86c4 100755
--- a/t/t3415-rebase-autosquash.sh
+++ b/t/t3415-rebase-autosquash.sh
@@ -33,7 +33,7 @@ test_auto_fixup () {
test_tick &&
git rebase $2 -i HEAD^^^ &&
git log --oneline >actual &&
- test 3 = $(wc -l <actual) &&
+ test_line_count = 3 actual &&
git diff --exit-code $1 &&
test 1 = "$(git cat-file blob HEAD^:file1)" &&
test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
@@ -62,7 +62,7 @@ test_auto_squash () {
test_tick &&
git rebase $2 -i HEAD^^^ &&
git log --oneline >actual &&
- test 3 = $(wc -l <actual) &&
+ test_line_count = 3 actual &&
git diff --exit-code $1 &&
test 1 = "$(git cat-file blob HEAD^:file1)" &&
test 2 = $(git cat-file commit HEAD^ | grep first | wc -l)
@@ -90,7 +90,7 @@ test_expect_success 'misspelled auto squash' '
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
git log --oneline >actual &&
- test 4 = $(wc -l <actual) &&
+ test_line_count = 4 actual &&
git diff --exit-code final-missquash &&
test 0 = $(git rev-list final-missquash...HEAD | wc -l)
'
@@ -109,7 +109,7 @@ test_expect_success 'auto squash that matches 2 commits' '
test_tick &&
git rebase --autosquash -i HEAD~4 &&
git log --oneline >actual &&
- test 4 = $(wc -l <actual) &&
+ test_line_count = 4 actual &&
git diff --exit-code final-multisquash &&
test 1 = "$(git cat-file blob HEAD^^:file1)" &&
test 2 = $(git cat-file commit HEAD^^ | grep first | wc -l) &&
@@ -130,7 +130,7 @@ test_expect_success 'auto squash that matches a commit after the squash' '
test_tick &&
git rebase --autosquash -i HEAD~4 &&
git log --oneline >actual &&
- test 5 = $(wc -l <actual) &&
+ test_line_count = 5 actual &&
git diff --exit-code final-presquash &&
test 0 = "$(git cat-file blob HEAD^^:file1)" &&
test 1 = "$(git cat-file blob HEAD^:file1)" &&
@@ -147,7 +147,7 @@ test_expect_success 'auto squash that matches a sha1' '
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
git log --oneline >actual &&
- test 3 = $(wc -l <actual) &&
+ test_line_count = 3 actual &&
git diff --exit-code final-shasquash &&
test 1 = "$(git cat-file blob HEAD^:file1)" &&
test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
@@ -163,7 +163,7 @@ test_expect_success 'auto squash that matches longer sha1' '
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
git log --oneline >actual &&
- test 3 = $(wc -l <actual) &&
+ test_line_count = 3 actual &&
git diff --exit-code final-longshasquash &&
test 1 = "$(git cat-file blob HEAD^:file1)" &&
test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
@@ -179,7 +179,7 @@ test_auto_commit_flags () {
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
git log --oneline >actual &&
- test 3 = $(wc -l <actual) &&
+ test_line_count = 3 actual &&
git diff --exit-code final-commit-$1 &&
test 1 = "$(git cat-file blob HEAD^:file1)" &&
test $2 = $(git cat-file commit HEAD^ | grep first | wc -l)
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index 1e855cd..2680375 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -51,7 +51,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '
test_commit "commit-new-file-F3-on-topic-branch" F3 32 &&
test_when_finished "rm -fr test-bin funny.was.run" &&
mkdir test-bin &&
- cat >test-bin/git-merge-funny <<-EOF
+ cat >test-bin/git-merge-funny <<-EOF &&
#!$SHELL_PATH
case "\$1" in --opt) ;; *) exit 2 ;; esac
shift &&
@@ -77,7 +77,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '
test_expect_success 'rebase --continue remembers --rerere-autoupdate' '
rm -fr .git/rebase-* &&
git reset --hard commit-new-file-F3-on-topic-branch &&
- git checkout master
+ git checkout master &&
test_commit "commit-new-file-F3" F3 3 &&
git config rerere.enabled true &&
test_must_fail git rebase -m master topic &&
diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh
index bd8efaf..e70ac10 100755
--- a/t/t3419-rebase-patch-id.sh
+++ b/t/t3419-rebase-patch-id.sh
@@ -39,7 +39,7 @@ run()
}
test_expect_success 'setup' '
- git commit --allow-empty -m initial
+ git commit --allow-empty -m initial &&
git tag root
'
diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh
index 0ab52da..e37547f 100755
--- a/t/t3502-cherry-pick-merge.sh
+++ b/t/t3502-cherry-pick-merge.sh
@@ -35,7 +35,7 @@ test_expect_success 'cherry-pick a non-merge with -m should fail' '
git reset --hard &&
git checkout a^0 &&
- test_must_fail git cherry-pick -m 1 b &&
+ test_expect_code 128 git cherry-pick -m 1 b &&
git diff --exit-code a --
'
diff --git a/t/t3503-cherry-pick-root.sh b/t/t3503-cherry-pick-root.sh
index 9aefe3a..e27f39d 100755
--- a/t/t3503-cherry-pick-root.sh
+++ b/t/t3503-cherry-pick-root.sh
@@ -16,12 +16,20 @@ test_expect_success setup '
echo second > file2 &&
git add file2 &&
test_tick &&
- git commit -m "second"
+ git commit -m "second" &&
+
+ git symbolic-ref HEAD refs/heads/third &&
+ rm .git/index file2 &&
+ echo third > file3 &&
+ git add file3 &&
+ test_tick &&
+ git commit -m "third"
'
test_expect_success 'cherry-pick a root commit' '
+ git checkout second^0 &&
git cherry-pick master &&
echo first >expect &&
test_cmp expect file1
@@ -50,4 +58,21 @@ test_expect_success 'revert a root commit with an external strategy' '
'
+test_expect_success 'cherry-pick two root commits' '
+
+ echo first >expect.file1 &&
+ echo second >expect.file2 &&
+ echo third >expect.file3 &&
+
+ git checkout second^0 &&
+ git cherry-pick master third &&
+
+ test_cmp expect.file1 file1 &&
+ test_cmp expect.file2 file2 &&
+ test_cmp expect.file3 file3 &&
+ git rev-parse --verify HEAD^^ &&
+ test_must_fail git rev-parse --verify HEAD^^^
+
+'
+
test_done
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index c10b28c..5a1340c 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -18,7 +18,12 @@ test_expect_success setup '
echo third >> file1 &&
git add file1 &&
test_tick &&
- git commit --allow-empty-message -m ""
+ git commit --allow-empty-message -m "" &&
+
+ git checkout master &&
+ git checkout -b empty-branch2 &&
+ test_tick &&
+ git commit --allow-empty -m "empty"
'
@@ -48,4 +53,52 @@ test_expect_success 'index lockfile was removed' '
'
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+ git checkout master &&
+ echo fourth >>file2 &&
+ git add file2 &&
+ git commit -m "fourth" &&
+ test_must_fail git cherry-pick empty-branch2
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+ git checkout master &&
+ git cherry-pick --allow-empty empty-branch2
+'
+
+test_expect_success 'cherry pick with --keep-redundant-commits' '
+ git checkout master &&
+ git cherry-pick --keep-redundant-commits HEAD^
+'
+
+test_expect_success 'cherry-pick a commit that becomes no-op (prep)' '
+ git checkout master &&
+ git branch fork &&
+ echo foo >file2 &&
+ git add file2 &&
+ test_tick &&
+ git commit -m "add file2 on master" &&
+
+ git checkout fork &&
+ echo foo >file2 &&
+ git add file2 &&
+ test_tick &&
+ git commit -m "add file2 on the side"
+'
+
+test_expect_success 'cherry-pick a no-op without --keep-redundant' '
+ git reset --hard &&
+ git checkout fork^0 &&
+ test_must_fail git cherry-pick master
+'
+
+test_expect_success 'cherry-pick a no-op with --keep-redundant' '
+ git reset --hard &&
+ git checkout fork^0 &&
+ git cherry-pick --keep-redundant-commits master &&
+ git show -s --format='%s' >actual &&
+ echo "add file2 on master" >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh
index 212ec54..0c81b3c 100755
--- a/t/t3507-cherry-pick-conflict.sh
+++ b/t/t3507-cherry-pick-conflict.sh
@@ -59,6 +59,20 @@ test_expect_success 'advice from failed cherry-pick' "
test_i18ncmp expected actual
"
+test_expect_success 'advice from failed cherry-pick --no-commit' "
+ pristine_detach initial &&
+
+ picked=\$(git rev-parse --short picked) &&
+ cat <<-EOF >expected &&
+ error: could not apply \$picked... picked
+ hint: after resolving the conflicts, mark the corrected paths
+ hint: with 'git add <paths>' or 'git rm <paths>'
+ EOF
+ test_must_fail git cherry-pick --no-commit picked 2>actual &&
+
+ test_i18ncmp expected actual
+"
+
test_expect_success 'failed cherry-pick sets CHERRY_PICK_HEAD' '
pristine_detach initial &&
test_must_fail git cherry-pick picked &&
@@ -77,6 +91,21 @@ test_expect_success 'cherry-pick --no-commit does not set CHERRY_PICK_HEAD' '
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
'
+test_expect_success 'cherry-pick w/dirty tree does not set CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+ echo foo > foo &&
+ test_must_fail git cherry-pick base &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success \
+ 'cherry-pick --strategy=resolve w/dirty tree does not set CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+ echo foo > foo &&
+ test_must_fail git cherry-pick --strategy=resolve base &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
test_expect_success 'GIT_CHERRY_PICK_HELP suppresses CHERRY_PICK_HEAD' '
pristine_detach initial &&
(
@@ -238,6 +267,60 @@ test_expect_success 'revert also handles conflicts sanely' '
test_cmp expected actual
'
+test_expect_success 'failed revert sets REVERT_HEAD' '
+ pristine_detach initial &&
+ test_must_fail git revert picked &&
+ test_cmp_rev picked REVERT_HEAD
+'
+
+test_expect_success 'successful revert does not set REVERT_HEAD' '
+ pristine_detach base &&
+ git revert base &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+ test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'revert --no-commit sets REVERT_HEAD' '
+ pristine_detach base &&
+ git revert --no-commit base &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+ test_cmp_rev base REVERT_HEAD
+'
+
+test_expect_success 'revert w/dirty tree does not set REVERT_HEAD' '
+ pristine_detach base &&
+ echo foo > foo &&
+ test_must_fail git revert base &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+ test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'GIT_CHERRY_PICK_HELP does not suppress REVERT_HEAD' '
+ pristine_detach initial &&
+ (
+ GIT_CHERRY_PICK_HELP="and then do something else" &&
+ GIT_REVERT_HELP="and then do something else, again" &&
+ export GIT_CHERRY_PICK_HELP GIT_REVERT_HELP &&
+ test_must_fail git revert picked
+ ) &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+ test_cmp_rev picked REVERT_HEAD
+'
+
+test_expect_success 'git reset clears REVERT_HEAD' '
+ pristine_detach initial &&
+ test_must_fail git revert picked &&
+ git reset &&
+ test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'failed commit does not clear REVERT_HEAD' '
+ pristine_detach initial &&
+ test_must_fail git revert picked &&
+ test_must_fail git commit &&
+ test_cmp_rev picked REVERT_HEAD
+'
+
test_expect_success 'revert conflict, diff3 -m style' '
pristine_detach initial &&
git config merge.conflictstyle diff3 &&
diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh
index 8e09fd0..75f7ff4 100755
--- a/t/t3508-cherry-pick-many-commits.sh
+++ b/t/t3508-cherry-pick-many-commits.sh
@@ -35,55 +35,69 @@ test_expect_success setup '
'
test_expect_success 'cherry-pick first..fourth works' '
+ git checkout -f master &&
+ git reset --hard first &&
+ test_tick &&
+ git cherry-pick first..fourth &&
+ git diff --quiet other &&
+ git diff --quiet HEAD other &&
+ check_head_differs_from fourth
+'
+
+test_expect_success 'output to keep user entertained during multi-pick' '
cat <<-\EOF >expected &&
[master OBJID] second
Author: A U Thor <author@example.com>
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
[master OBJID] third
Author: A U Thor <author@example.com>
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
[master OBJID] fourth
Author: A U Thor <author@example.com>
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
EOF
git checkout -f master &&
git reset --hard first &&
test_tick &&
git cherry-pick first..fourth >actual &&
+ sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
+ test_line_count -ge 3 actual.fuzzy &&
+ test_i18ncmp expected actual.fuzzy
+'
+
+test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
+ git checkout -f master &&
+ git reset --hard first &&
+ test_tick &&
+ git cherry-pick --strategy resolve first..fourth &&
git diff --quiet other &&
git diff --quiet HEAD other &&
-
- sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
- test_cmp expected actual.fuzzy &&
check_head_differs_from fourth
'
-test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
+test_expect_success 'output during multi-pick indicates merge strategy' '
cat <<-\EOF >expected &&
Trying simple merge.
[master OBJID] second
Author: A U Thor <author@example.com>
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
Trying simple merge.
[master OBJID] third
Author: A U Thor <author@example.com>
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
Trying simple merge.
[master OBJID] fourth
Author: A U Thor <author@example.com>
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ 1 file changed, 1 insertion(+)
EOF
git checkout -f master &&
git reset --hard first &&
test_tick &&
git cherry-pick --strategy resolve first..fourth >actual &&
- git diff --quiet other &&
- git diff --quiet HEAD other &&
sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
- test_cmp expected actual.fuzzy &&
- check_head_differs_from fourth
+ test_i18ncmp expected actual.fuzzy
'
test_expect_success 'cherry-pick --ff first..fourth works' '
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
new file mode 100755
index 0000000..f4e6450
--- /dev/null
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -0,0 +1,520 @@
+#!/bin/sh
+
+test_description='Test cherry-pick continuation features
+
+ + conflicting: rewrites unrelated to conflicting
+ + yetanotherpick: rewrites foo to e
+ + anotherpick: rewrites foo to d
+ + picked: rewrites foo to c
+ + unrelatedpick: rewrites unrelated to reallyunrelated
+ + base: rewrites foo to b
+ + initial: writes foo as a, unrelated as unrelated
+
+'
+
+. ./test-lib.sh
+
+# Repeat first match 10 times
+_r10='\1\1\1\1\1\1\1\1\1\1'
+
+pristine_detach () {
+ git cherry-pick --quit &&
+ git checkout -f "$1^0" &&
+ git read-tree -u --reset HEAD &&
+ git clean -d -f -f -q -x
+}
+
+test_cmp_rev () {
+ git rev-parse --verify "$1" >expect.rev &&
+ git rev-parse --verify "$2" >actual.rev &&
+ test_cmp expect.rev actual.rev
+}
+
+test_expect_success setup '
+ git config advice.detachedhead false &&
+ echo unrelated >unrelated &&
+ git add unrelated &&
+ test_commit initial foo a &&
+ test_commit base foo b &&
+ test_commit unrelatedpick unrelated reallyunrelated &&
+ test_commit picked foo c &&
+ test_commit anotherpick foo d &&
+ test_commit yetanotherpick foo e &&
+ pristine_detach initial &&
+ test_commit conflicting unrelated
+'
+
+test_expect_success 'cherry-pick persists data on failure' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick -s base..anotherpick &&
+ test_path_is_dir .git/sequencer &&
+ test_path_is_file .git/sequencer/head &&
+ test_path_is_file .git/sequencer/todo &&
+ test_path_is_file .git/sequencer/opts
+'
+
+test_expect_success 'cherry-pick mid-cherry-pick-sequence' '
+ pristine_detach initial &&
+ test_must_fail git cherry-pick base..anotherpick &&
+ test_cmp_rev picked CHERRY_PICK_HEAD &&
+ # "oops, I forgot that these patches rely on the change from base"
+ git checkout HEAD foo &&
+ git cherry-pick base &&
+ git cherry-pick picked &&
+ git cherry-pick --continue &&
+ git diff --exit-code anotherpick
+'
+
+test_expect_success 'cherry-pick persists opts correctly' '
+ pristine_detach initial &&
+ test_expect_code 128 git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours initial..anotherpick &&
+ test_path_is_dir .git/sequencer &&
+ test_path_is_file .git/sequencer/head &&
+ test_path_is_file .git/sequencer/todo &&
+ test_path_is_file .git/sequencer/opts &&
+ echo "true" >expect &&
+ git config --file=.git/sequencer/opts --get-all options.signoff >actual &&
+ test_cmp expect actual &&
+ echo "1" >expect &&
+ git config --file=.git/sequencer/opts --get-all options.mainline >actual &&
+ test_cmp expect actual &&
+ echo "recursive" >expect &&
+ git config --file=.git/sequencer/opts --get-all options.strategy >actual &&
+ test_cmp expect actual &&
+ cat >expect <<-\EOF &&
+ patience
+ ours
+ EOF
+ git config --file=.git/sequencer/opts --get-all options.strategy-option >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick cleans up sequencer state upon success' '
+ pristine_detach initial &&
+ git cherry-pick initial..picked &&
+ test_path_is_missing .git/sequencer
+'
+
+test_expect_success '--quit does not complain when no cherry-pick is in progress' '
+ pristine_detach initial &&
+ git cherry-pick --quit
+'
+
+test_expect_success '--abort requires cherry-pick in progress' '
+ pristine_detach initial &&
+ test_must_fail git cherry-pick --abort
+'
+
+test_expect_success '--quit cleans up sequencer state' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..picked &&
+ git cherry-pick --quit &&
+ test_path_is_missing .git/sequencer
+'
+
+test_expect_success '--quit keeps HEAD and conflicted index intact' '
+ pristine_detach initial &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :100644 100644 OBJID OBJID M unrelated
+ OBJID
+ :000000 100644 OBJID OBJID A foo
+ :000000 100644 OBJID OBJID A unrelated
+ EOF
+ test_expect_code 1 git cherry-pick base..picked &&
+ git cherry-pick --quit &&
+ test_path_is_missing .git/sequencer &&
+ test_must_fail git update-index --refresh &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--abort to cancel multiple cherry-pick' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ git cherry-pick --abort &&
+ test_path_is_missing .git/sequencer &&
+ test_cmp_rev initial HEAD &&
+ git update-index --refresh &&
+ git diff-index --exit-code HEAD
+'
+
+test_expect_success '--abort to cancel single cherry-pick' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick picked &&
+ git cherry-pick --abort &&
+ test_path_is_missing .git/sequencer &&
+ test_cmp_rev initial HEAD &&
+ git update-index --refresh &&
+ git diff-index --exit-code HEAD
+'
+
+test_expect_success 'cherry-pick --abort to cancel multiple revert' '
+ pristine_detach anotherpick &&
+ test_expect_code 1 git revert base..picked &&
+ git cherry-pick --abort &&
+ test_path_is_missing .git/sequencer &&
+ test_cmp_rev anotherpick HEAD &&
+ git update-index --refresh &&
+ git diff-index --exit-code HEAD
+'
+
+test_expect_success 'revert --abort works, too' '
+ pristine_detach anotherpick &&
+ test_expect_code 1 git revert base..picked &&
+ git revert --abort &&
+ test_path_is_missing .git/sequencer &&
+ test_cmp_rev anotherpick HEAD
+'
+
+test_expect_success '--abort to cancel single revert' '
+ pristine_detach anotherpick &&
+ test_expect_code 1 git revert picked &&
+ git revert --abort &&
+ test_path_is_missing .git/sequencer &&
+ test_cmp_rev anotherpick HEAD &&
+ git update-index --refresh &&
+ git diff-index --exit-code HEAD
+'
+
+test_expect_success '--abort keeps unrelated change, easy case' '
+ pristine_detach unrelatedpick &&
+ echo changed >expect &&
+ test_expect_code 1 git cherry-pick picked..yetanotherpick &&
+ echo changed >unrelated &&
+ git cherry-pick --abort &&
+ test_cmp expect unrelated
+'
+
+test_expect_success '--abort refuses to clobber unrelated change, harder case' '
+ pristine_detach initial &&
+ echo changed >expect &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ echo changed >unrelated &&
+ test_must_fail git cherry-pick --abort &&
+ test_cmp expect unrelated &&
+ git rev-list HEAD >log &&
+ test_line_count = 2 log &&
+ test_must_fail git update-index --refresh &&
+
+ git checkout unrelated &&
+ git cherry-pick --abort &&
+ test_cmp_rev initial HEAD
+'
+
+test_expect_success 'cherry-pick still writes sequencer state when one commit is left' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..picked &&
+ test_path_is_dir .git/sequencer &&
+ echo "resolved" >foo &&
+ git add foo &&
+ git commit &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :100644 100644 OBJID OBJID M foo
+ OBJID
+ :100644 100644 OBJID OBJID M unrelated
+ OBJID
+ :000000 100644 OBJID OBJID A foo
+ :000000 100644 OBJID OBJID A unrelated
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success '--abort after last commit in sequence' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..picked &&
+ git cherry-pick --abort &&
+ test_path_is_missing .git/sequencer &&
+ test_cmp_rev initial HEAD &&
+ git update-index --refresh &&
+ git diff-index --exit-code HEAD
+'
+
+test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ test-chmtime -v +0 .git/sequencer >expect &&
+ test_expect_code 128 git cherry-pick unrelatedpick &&
+ test-chmtime -v +0 .git/sequencer >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--continue complains when no cherry-pick is in progress' '
+ pristine_detach initial &&
+ test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success '--continue complains when there are unresolved conflicts' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success '--continue of single cherry-pick' '
+ pristine_detach initial &&
+ echo c >expect &&
+ test_must_fail git cherry-pick picked &&
+ echo c >foo &&
+ git add foo &&
+ git cherry-pick --continue &&
+
+ test_cmp expect foo &&
+ test_cmp_rev initial HEAD^ &&
+ git diff --exit-code HEAD &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success '--continue of single revert' '
+ pristine_detach initial &&
+ echo resolved >expect &&
+ echo "Revert \"picked\"" >expect.msg &&
+ test_must_fail git revert picked &&
+ echo resolved >foo &&
+ git add foo &&
+ git cherry-pick --continue &&
+
+ git diff --exit-code HEAD &&
+ test_cmp expect foo &&
+ test_cmp_rev initial HEAD^ &&
+ git diff-tree -s --pretty=tformat:%s HEAD >msg &&
+ test_cmp expect.msg msg &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+ test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success '--continue after resolving conflicts' '
+ pristine_detach initial &&
+ echo d >expect &&
+ cat >expect.log <<-\EOF &&
+ OBJID
+ :100644 100644 OBJID OBJID M foo
+ OBJID
+ :100644 100644 OBJID OBJID M foo
+ OBJID
+ :100644 100644 OBJID OBJID M unrelated
+ OBJID
+ :000000 100644 OBJID OBJID A foo
+ :000000 100644 OBJID OBJID A unrelated
+ EOF
+ test_must_fail git cherry-pick base..anotherpick &&
+ echo c >foo &&
+ git add foo &&
+ git cherry-pick --continue &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual.log &&
+ test_cmp expect foo &&
+ test_cmp expect.log actual.log
+'
+
+test_expect_success '--continue after resolving conflicts and committing' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ echo "c" >foo &&
+ git add foo &&
+ git commit &&
+ git cherry-pick --continue &&
+ test_path_is_missing .git/sequencer &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :100644 100644 OBJID OBJID M foo
+ OBJID
+ :100644 100644 OBJID OBJID M foo
+ OBJID
+ :100644 100644 OBJID OBJID M unrelated
+ OBJID
+ :000000 100644 OBJID OBJID A foo
+ :000000 100644 OBJID OBJID A unrelated
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success '--continue asks for help after resolving patch to nil' '
+ pristine_detach conflicting &&
+ test_must_fail git cherry-pick initial..picked &&
+
+ test_cmp_rev unrelatedpick CHERRY_PICK_HEAD &&
+ git checkout HEAD -- unrelated &&
+ test_must_fail git cherry-pick --continue 2>msg &&
+ test_i18ngrep "The previous cherry-pick is now empty" msg
+'
+
+test_expect_success 'follow advice and skip nil patch' '
+ pristine_detach conflicting &&
+ test_must_fail git cherry-pick initial..picked &&
+
+ git checkout HEAD -- unrelated &&
+ test_must_fail git cherry-pick --continue &&
+ git reset &&
+ git cherry-pick --continue &&
+
+ git rev-list initial..HEAD >commits &&
+ test_line_count = 3 commits
+'
+
+test_expect_success '--continue respects opts' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick -x base..anotherpick &&
+ echo "c" >foo &&
+ git add foo &&
+ git commit &&
+ git cherry-pick --continue &&
+ test_path_is_missing .git/sequencer &&
+ git cat-file commit HEAD >anotherpick_msg &&
+ git cat-file commit HEAD~1 >picked_msg &&
+ git cat-file commit HEAD~2 >unrelatedpick_msg &&
+ git cat-file commit HEAD~3 >initial_msg &&
+ test_must_fail grep "cherry picked from" initial_msg &&
+ grep "cherry picked from" unrelatedpick_msg &&
+ grep "cherry picked from" picked_msg &&
+ grep "cherry picked from" anotherpick_msg
+'
+
+test_expect_success '--continue of single-pick respects -x' '
+ pristine_detach initial &&
+ test_must_fail git cherry-pick -x picked &&
+ echo c >foo &&
+ git add foo &&
+ git cherry-pick --continue &&
+ test_path_is_missing .git/sequencer &&
+ git cat-file commit HEAD >msg &&
+ grep "cherry picked from" msg
+'
+
+test_expect_success '--continue respects -x in first commit in multi-pick' '
+ pristine_detach initial &&
+ test_must_fail git cherry-pick -x picked anotherpick &&
+ echo c >foo &&
+ git add foo &&
+ git cherry-pick --continue &&
+ test_path_is_missing .git/sequencer &&
+ git cat-file commit HEAD^ >msg &&
+ picked=$(git rev-parse --verify picked) &&
+ grep "cherry picked from.*$picked" msg
+'
+
+test_expect_success '--signoff is not automatically propagated to resolved conflict' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick --signoff base..anotherpick &&
+ echo "c" >foo &&
+ git add foo &&
+ git commit &&
+ git cherry-pick --continue &&
+ test_path_is_missing .git/sequencer &&
+ git cat-file commit HEAD >anotherpick_msg &&
+ git cat-file commit HEAD~1 >picked_msg &&
+ git cat-file commit HEAD~2 >unrelatedpick_msg &&
+ git cat-file commit HEAD~3 >initial_msg &&
+ test_must_fail grep "Signed-off-by:" initial_msg &&
+ grep "Signed-off-by:" unrelatedpick_msg &&
+ test_must_fail grep "Signed-off-by:" picked_msg &&
+ grep "Signed-off-by:" anotherpick_msg
+'
+
+test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' '
+ pristine_detach initial &&
+ test_must_fail git cherry-pick -s picked anotherpick &&
+ echo c >foo &&
+ git add foo &&
+ git cherry-pick --continue &&
+
+ git diff --exit-code HEAD &&
+ test_cmp_rev initial HEAD^^ &&
+ git cat-file commit HEAD^ >msg &&
+ ! grep Signed-off-by: msg
+'
+
+test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' '
+ pristine_detach initial &&
+ test_must_fail git cherry-pick -s picked &&
+ echo c >foo &&
+ git add foo &&
+ git cherry-pick --continue &&
+
+ git diff --exit-code HEAD &&
+ test_cmp_rev initial HEAD^ &&
+ git cat-file commit HEAD >msg &&
+ ! grep Signed-off-by: msg
+'
+
+test_expect_success 'malformed instruction sheet 1' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ echo "resolved" >foo &&
+ git add foo &&
+ git commit &&
+ sed "s/pick /pick/" .git/sequencer/todo >new_sheet &&
+ cp new_sheet .git/sequencer/todo &&
+ test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success 'malformed instruction sheet 2' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ echo "resolved" >foo &&
+ git add foo &&
+ git commit &&
+ sed "s/pick/revert/" .git/sequencer/todo >new_sheet &&
+ cp new_sheet .git/sequencer/todo &&
+ test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success 'empty commit set' '
+ pristine_detach initial &&
+ test_expect_code 128 git cherry-pick base..base
+'
+
+test_expect_success 'malformed instruction sheet 3' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ echo "resolved" >foo &&
+ git add foo &&
+ git commit &&
+ sed "s/pick \([0-9a-f]*\)/pick $_r10/" .git/sequencer/todo >new_sheet &&
+ cp new_sheet .git/sequencer/todo &&
+ test_expect_code 128 git cherry-pick --continue
+'
+
+test_expect_success 'instruction sheet, fat-fingers version' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ echo "c" >foo &&
+ git add foo &&
+ git commit &&
+ sed "s/pick \([0-9a-f]*\)/pick \1 /" .git/sequencer/todo >new_sheet &&
+ cp new_sheet .git/sequencer/todo &&
+ git cherry-pick --continue
+'
+
+test_expect_success 'commit descriptions in insn sheet are optional' '
+ pristine_detach initial &&
+ test_expect_code 1 git cherry-pick base..anotherpick &&
+ echo "c" >foo &&
+ git add foo &&
+ git commit &&
+ cut -d" " -f1,2 .git/sequencer/todo >new_sheet &&
+ cp new_sheet .git/sequencer/todo &&
+ git cherry-pick --continue &&
+ test_path_is_missing .git/sequencer &&
+ git rev-list HEAD >commits &&
+ test_line_count = 4 commits
+'
+
+test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index 575d950..874b3a6 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -179,6 +179,21 @@ test_expect_success 'git add --refresh' '
test -z "`git diff-index HEAD -- foo`"
'
+test_expect_success 'git add --refresh with pathspec' '
+ git reset --hard &&
+ echo >foo && echo >bar && echo >baz &&
+ git add foo bar baz && H=$(git rev-parse :foo) && git rm -f foo &&
+ echo "100644 $H 3 foo" | git update-index --index-info &&
+ test-chmtime -60 bar baz &&
+ >expect &&
+ git add --refresh bar >actual &&
+ test_cmp expect actual &&
+
+ git diff-files --name-only >actual &&
+ ! grep bar actual&&
+ grep baz actual
+'
+
test_expect_success POSIXPERM,SANITY 'git add should fail atomically upon an unreadable file' '
git reset --hard &&
date >foo1 &&
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 9e236f9..098a6ae 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -330,4 +330,30 @@ test_expect_success PERL 'split hunk "add -p (edit)"' '
! grep "^+15" actual
'
+test_expect_success 'patch mode ignores unmerged entries' '
+ git reset --hard &&
+ test_commit conflict &&
+ test_commit non-conflict &&
+ git checkout -b side &&
+ test_commit side conflict.t &&
+ git checkout master &&
+ test_commit master conflict.t &&
+ test_must_fail git merge side &&
+ echo changed >non-conflict.t &&
+ echo y | git add -p >output &&
+ ! grep a/conflict.t output &&
+ cat >expected <<-\EOF &&
+ * Unmerged path conflict.t
+ diff --git a/non-conflict.t b/non-conflict.t
+ index f766221..5ea2ed4 100644
+ --- a/non-conflict.t
+ +++ b/non-conflict.t
+ @@ -1 +1 @@
+ -non-conflict
+ +changed
+ EOF
+ git diff --cached >diff &&
+ test_cmp expected diff
+'
+
test_done
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index c06a5ee..37ddabb 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -34,6 +34,12 @@ test_expect_success 'no encoding header for base case' '
test z = "z$E"
'
+test_expect_failure 'UTF-16 refused because of NULs' '
+ echo UTF-16 >F &&
+ git commit -a -F "$TEST_DIRECTORY"/t3900/UTF-16.txt
+'
+
+
for H in ISO8859-1 eucJP ISO-2022-JP
do
test_expect_success "$H setup" '
@@ -147,19 +153,19 @@ test_commit_autosquash_flags () {
git commit -a -m "intermediate commit" &&
test_tick &&
echo $H $flag >>F &&
- git commit -a --$flag HEAD~1 $3 &&
+ git commit -a --$flag HEAD~1 &&
E=$(git cat-file commit '$H-$flag' |
sed -ne "s/^encoding //p") &&
test "z$E" = "z$H" &&
git config --unset-all i18n.commitencoding &&
git rebase --autosquash -i HEAD^^^ &&
git log --oneline >actual &&
- test 3 = $(wc -l <actual)
+ test_line_count = 3 actual
'
}
test_commit_autosquash_flags eucJP fixup
-test_commit_autosquash_flags ISO-2022-JP squash '-m "squash message"'
+test_commit_autosquash_flags ISO-2022-JP squash
test_done
diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh
index da82b65..534ee08 100755
--- a/t/t3902-quoted.sh
+++ b/t/t3902-quoted.sh
@@ -10,8 +10,6 @@ test_description='quoted output'
FN='濱野'
GN='ç´”'
HT=' '
-LF='
-'
DQ='"'
echo foo 2>/dev/null > "Name and an${HT}HT"
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 7197aae..cd04263 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -432,7 +432,7 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' '
test $(git ls-files --modified | wc -l) -eq 1
'
-test_expect_success 'stash show - stashes on stack, stash-like argument' '
+test_expect_success 'stash show format defaults to --stat' '
git stash clear &&
test_when_finished "git reset --hard HEAD" &&
git reset --hard &&
@@ -443,10 +443,25 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' '
STASH_ID=$(git stash create) &&
git reset --hard &&
cat >expected <<-EOF &&
- file | 1 +
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ file | 1 +
+ 1 file changed, 1 insertion(+)
EOF
git stash show ${STASH_ID} >actual &&
+ test_i18ncmp expected actual
+'
+
+test_expect_success 'stash show - stashes on stack, stash-like argument' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD" &&
+ git reset --hard &&
+ echo foo >> file &&
+ git stash &&
+ test_when_finished "git stash drop" &&
+ echo bar >> file &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ echo "1 0 file" >expected &&
+ git stash show --numstat ${STASH_ID} >actual &&
test_cmp expected actual
'
@@ -480,11 +495,8 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' '
echo foo >> file &&
STASH_ID=$(git stash create) &&
git reset --hard &&
- cat >expected <<-EOF &&
- file | 1 +
- 1 files changed, 1 insertions(+), 0 deletions(-)
- EOF
- git stash show ${STASH_ID} >actual &&
+ echo "1 0 file" >expected &&
+ git stash show --numstat ${STASH_ID} >actual &&
test_cmp expected actual
'
@@ -542,7 +554,7 @@ test_expect_success 'ref with non-existent reflog' '
echo bar6 > file2 &&
git add file2 &&
git stash &&
- ! "git rev-parse --quiet --verify does-not-exist" &&
+ test_must_fail git rev-parse --quiet --verify does-not-exist &&
test_must_fail git stash drop does-not-exist &&
test_must_fail git stash drop does-not-exist@{0} &&
test_must_fail git stash pop does-not-exist &&
@@ -601,4 +613,28 @@ test_expect_success 'stash apply shows status same as git status (relative to cu
test_cmp expect actual
'
+cat > expect << EOF
+diff --git a/HEAD b/HEAD
+new file mode 100644
+index 0000000..fe0cbee
+--- /dev/null
++++ b/HEAD
+@@ -0,0 +1 @@
++file-not-a-ref
+EOF
+
+test_expect_success 'stash where working directory contains "HEAD" file' '
+ git stash clear &&
+ git reset --hard &&
+ echo file-not-a-ref > HEAD &&
+ git add HEAD &&
+ test_tick &&
+ git stash &&
+ git diff-files --quiet &&
+ git diff-index --cached --quiet HEAD &&
+ test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
+ git diff stash^..stash > output &&
+ test_cmp output expect
+'
+
test_done
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
index 781fd71..70655c1 100755
--- a/t/t3904-stash-patch.sh
+++ b/t/t3904-stash-patch.sh
@@ -7,7 +7,8 @@ test_expect_success PERL 'setup' '
mkdir dir &&
echo parent > dir/foo &&
echo dummy > bar &&
- git add bar dir/foo &&
+ echo committed > HEAD &&
+ git add bar dir/foo HEAD &&
git commit -m initial &&
test_tick &&
test_commit second dir/foo head &&
@@ -17,47 +18,57 @@ test_expect_success PERL 'setup' '
save_head
'
-# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
+# note: order of files with unstaged changes: HEAD bar dir/foo
test_expect_success PERL 'saying "n" does nothing' '
+ set_state HEAD HEADfile_work HEADfile_index &&
set_state dir/foo work index &&
- (echo n; echo n) | test_must_fail git stash save -p &&
- verify_state dir/foo work index &&
- verify_saved_state bar
+ (echo n; echo n; echo n) | test_must_fail git stash save -p &&
+ verify_state HEAD HEADfile_work HEADfile_index &&
+ verify_saved_state bar &&
+ verify_state dir/foo work index
'
test_expect_success PERL 'git stash -p' '
- (echo n; echo y) | git stash save -p &&
- verify_state dir/foo head index &&
+ (echo y; echo n; echo y) | git stash save -p &&
+ verify_state HEAD committed HEADfile_index &&
verify_saved_state bar &&
+ verify_state dir/foo head index &&
git reset --hard &&
git stash apply &&
- verify_state dir/foo work head &&
- verify_state bar dummy dummy
+ verify_state HEAD HEADfile_work committed &&
+ verify_state bar dummy dummy &&
+ verify_state dir/foo work head
'
test_expect_success PERL 'git stash -p --no-keep-index' '
- set_state dir/foo work index &&
+ set_state HEAD HEADfile_work HEADfile_index &&
set_state bar bar_work bar_index &&
- (echo n; echo y) | git stash save -p --no-keep-index &&
- verify_state dir/foo head head &&
+ set_state dir/foo work index &&
+ (echo y; echo n; echo y) | git stash save -p --no-keep-index &&
+ verify_state HEAD committed committed &&
verify_state bar bar_work dummy &&
+ verify_state dir/foo head head &&
git reset --hard &&
git stash apply --index &&
- verify_state dir/foo work index &&
- verify_state bar dummy bar_index
+ verify_state HEAD HEADfile_work HEADfile_index &&
+ verify_state bar dummy bar_index &&
+ verify_state dir/foo work index
'
test_expect_success PERL 'git stash --no-keep-index -p' '
- set_state dir/foo work index &&
+ set_state HEAD HEADfile_work HEADfile_index &&
set_state bar bar_work bar_index &&
- (echo n; echo y) | git stash save --no-keep-index -p &&
+ set_state dir/foo work index &&
+ (echo y; echo n; echo y) | git stash save --no-keep-index -p &&
+ verify_state HEAD committed committed &&
verify_state dir/foo head head &&
verify_state bar bar_work dummy &&
git reset --hard &&
git stash apply --index &&
- verify_state dir/foo work index &&
- verify_state bar dummy bar_index
+ verify_state HEAD HEADfile_work HEADfile_index &&
+ verify_state bar dummy bar_index &&
+ verify_state dir/foo work index
'
test_expect_success PERL 'none of this moved HEAD' '
diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh
new file mode 100755
index 0000000..a5e7e6b
--- /dev/null
+++ b/t/t3905-stash-include-untracked.sh
@@ -0,0 +1,188 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 David Caldwell
+#
+
+test_description='Test git stash --include-untracked'
+
+. ./test-lib.sh
+
+test_expect_success 'stash save --include-untracked some dirty working directory' '
+ echo 1 > file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ echo 2 > file &&
+ git add file &&
+ echo 3 > file &&
+ test_tick &&
+ echo 1 > file2 &&
+ echo 1 > HEAD &&
+ mkdir untracked &&
+ echo untracked >untracked/untracked &&
+ git stash --include-untracked &&
+ git diff-files --quiet &&
+ git diff-index --cached --quiet HEAD
+'
+
+cat > expect <<EOF
+?? actual
+?? expect
+EOF
+
+test_expect_success 'stash save --include-untracked cleaned the untracked files' '
+ git status --porcelain >actual &&
+ test_cmp expect actual
+'
+
+cat > expect.diff <<EOF
+diff --git a/HEAD b/HEAD
+new file mode 100644
+index 0000000..d00491f
+--- /dev/null
++++ b/HEAD
+@@ -0,0 +1 @@
++1
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..d00491f
+--- /dev/null
++++ b/file2
+@@ -0,0 +1 @@
++1
+diff --git a/untracked/untracked b/untracked/untracked
+new file mode 100644
+index 0000000..5a72eb2
+--- /dev/null
++++ b/untracked/untracked
+@@ -0,0 +1 @@
++untracked
+EOF
+cat > expect.lstree <<EOF
+HEAD
+file2
+untracked
+EOF
+
+test_expect_success 'stash save --include-untracked stashed the untracked files' '
+ test_path_is_missing file2 &&
+ test_path_is_missing untracked &&
+ test_path_is_missing HEAD &&
+ git diff HEAD stash^3 -- HEAD file2 untracked >actual &&
+ test_cmp expect.diff actual &&
+ git ls-tree --name-only stash^3: >actual &&
+ test_cmp expect.lstree actual
+'
+test_expect_success 'stash save --patch --include-untracked fails' '
+ test_must_fail git stash --patch --include-untracked
+'
+
+test_expect_success 'stash save --patch --all fails' '
+ test_must_fail git stash --patch --all
+'
+
+git clean --force --quiet
+
+cat > expect <<EOF
+ M file
+?? HEAD
+?? actual
+?? expect
+?? file2
+?? untracked/
+EOF
+
+test_expect_success 'stash pop after save --include-untracked leaves files untracked again' '
+ git stash pop &&
+ git status --porcelain >actual &&
+ test_cmp expect actual &&
+ test "1" = "`cat file2`" &&
+ test untracked = "`cat untracked/untracked`"
+'
+
+git clean --force --quiet -d
+
+test_expect_success 'stash save -u dirty index' '
+ echo 4 > file3 &&
+ git add file3 &&
+ test_tick &&
+ git stash -u
+'
+
+cat > expect <<EOF
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..b8626c4
+--- /dev/null
++++ b/file3
+@@ -0,0 +1 @@
++4
+EOF
+
+test_expect_success 'stash save --include-untracked dirty index got stashed' '
+ git stash pop --index &&
+ git diff --cached >actual &&
+ test_cmp expect actual
+'
+
+git reset > /dev/null
+
+# Must direct output somewhere where it won't be considered an untracked file
+test_expect_success 'stash save --include-untracked -q is quiet' '
+ echo 1 > file5 &&
+ git stash save --include-untracked --quiet > .git/stash-output.out 2>&1 &&
+ test_line_count = 0 .git/stash-output.out &&
+ rm -f .git/stash-output.out
+'
+
+test_expect_success 'stash save --include-untracked removed files' '
+ rm -f file &&
+ git stash save --include-untracked &&
+ echo 1 > expect &&
+ test_cmp file expect
+'
+
+rm -f expect
+
+test_expect_success 'stash save --include-untracked removed files got stashed' '
+ git stash pop &&
+ test_path_is_missing file
+'
+
+cat > .gitignore <<EOF
+.gitignore
+ignored
+ignored.d/
+EOF
+
+test_expect_success 'stash save --include-untracked respects .gitignore' '
+ echo ignored > ignored &&
+ mkdir ignored.d &&
+ echo ignored >ignored.d/untracked &&
+ git stash -u &&
+ test -s ignored &&
+ test -s ignored.d/untracked &&
+ test -s .gitignore
+'
+
+test_expect_success 'stash save -u can stash with only untracked files different' '
+ echo 4 > file4 &&
+ git stash -u &&
+ test_path_is_missing file4
+'
+
+test_expect_success 'stash save --all does not respect .gitignore' '
+ git stash -a &&
+ test_path_is_missing ignored &&
+ test_path_is_missing ignored.d &&
+ test_path_is_missing .gitignore
+'
+
+test_expect_success 'stash save --all is stash poppable' '
+ git stash pop &&
+ test -s ignored &&
+ test -s ignored.d/untracked &&
+ test -s .gitignore
+'
+
+test_done
diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh
index ff8c2f7..7a3e1f9 100755
--- a/t/t4006-diff-mode.sh
+++ b/t/t4006-diff-mode.sh
@@ -8,23 +8,52 @@ test_description='Test mode change diffs.
'
. ./test-lib.sh
-test_expect_success \
- 'setup' \
- 'echo frotz >rezrov &&
- git update-index --add rezrov &&
- tree=`git write-tree` &&
- echo $tree'
-
-test_expect_success \
- 'chmod' \
- 'test_chmod +x rezrov &&
- git diff-index $tree >current'
-
-sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
-echo ":100644 100755 X X M rezrov" >expected
-
-test_expect_success \
- 'verify' \
- 'test_cmp expected check'
+sed_script='s/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /'
+
+test_expect_success 'setup' '
+ echo frotz >rezrov &&
+ git update-index --add rezrov &&
+ tree=`git write-tree` &&
+ echo $tree
+'
+
+test_expect_success 'chmod' '
+ test_chmod +x rezrov &&
+ git diff-index $tree >current &&
+ sed -e "$sed_script" <current >check &&
+ echo ":100644 100755 X X M rezrov" >expected &&
+ test_cmp expected check
+'
+
+test_expect_success 'prepare binary file' '
+ git commit -m rezrov &&
+ printf "\00\01\02\03\04\05\06" >binbin &&
+ git add binbin &&
+ git commit -m binbin
+'
+
+test_expect_success '--stat output after text chmod' '
+ test_chmod -x rezrov &&
+ echo " 0 files changed" >expect &&
+ git diff HEAD --stat >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--shortstat output after text chmod' '
+ git diff HEAD --shortstat >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--stat output after binary chmod' '
+ test_chmod +x binbin &&
+ echo " 0 files changed" >expect &&
+ git diff HEAD --stat >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--shortstat output after binary chmod' '
+ git diff HEAD --shortstat >actual &&
+ test_cmp expect actual
+'
test_done
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
index fbc8cd8..af5134b 100755
--- a/t/t4010-diff-pathspec.sh
+++ b/t/t4010-diff-pathspec.sh
@@ -48,6 +48,14 @@ test_expect_success \
compare_diff_raw current expected'
cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M path1/file1
+EOF
+test_expect_success \
+ '"*file1" should show path1/file1' \
+ 'git diff-index --cached $tree -- "*file1" >current &&
+ compare_diff_raw current expected'
+
+cat >expected <<\EOF
:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M file0
EOF
test_expect_success \
diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh
index 408a19c..f0d5041 100755
--- a/t/t4011-diff-symlink.sh
+++ b/t/t4011-diff-symlink.sh
@@ -9,85 +9,110 @@ test_description='Test diff of symlinks.
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh
-cat > expected << EOF
-diff --git a/frotz b/frotz
-new file mode 120000
-index 0000000..7c465af
---- /dev/null
-+++ b/frotz
-@@ -0,0 +1 @@
-+xyzzy
-\ No newline at end of file
-EOF
-
-test_expect_success SYMLINKS \
- 'diff new symlink' \
- 'ln -s xyzzy frotz &&
- git update-index &&
- tree=$(git write-tree) &&
- git update-index --add frotz &&
- GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree > current &&
- compare_diff_patch current expected'
-
-test_expect_success SYMLINKS \
- 'diff unchanged symlink' \
- 'tree=$(git write-tree) &&
- git update-index frotz &&
- test -z "$(git diff-index --name-only $tree)"'
-
-cat > expected << EOF
-diff --git a/frotz b/frotz
-deleted file mode 120000
-index 7c465af..0000000
---- a/frotz
-+++ /dev/null
-@@ -1 +0,0 @@
--xyzzy
-\ No newline at end of file
-EOF
+test_expect_success SYMLINKS 'diff new symlink and file' '
+ cat >expected <<-\EOF &&
+ diff --git a/frotz b/frotz
+ new file mode 120000
+ index 0000000..7c465af
+ --- /dev/null
+ +++ b/frotz
+ @@ -0,0 +1 @@
+ +xyzzy
+ \ No newline at end of file
+ diff --git a/nitfol b/nitfol
+ new file mode 100644
+ index 0000000..7c465af
+ --- /dev/null
+ +++ b/nitfol
+ @@ -0,0 +1 @@
+ +xyzzy
+ EOF
+ ln -s xyzzy frotz &&
+ echo xyzzy >nitfol &&
+ git update-index &&
+ tree=$(git write-tree) &&
+ git update-index --add frotz nitfol &&
+ GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current &&
+ compare_diff_patch expected current
+'
-test_expect_success SYMLINKS \
- 'diff removed symlink' \
- 'mv frotz frotz2 &&
- git diff-index -M -p $tree > current &&
- compare_diff_patch current expected'
+test_expect_success SYMLINKS 'diff unchanged symlink and file' '
+ tree=$(git write-tree) &&
+ git update-index frotz nitfol &&
+ test -z "$(git diff-index --name-only $tree)"
+'
-cat > expected << EOF
-diff --git a/frotz b/frotz
-EOF
+test_expect_success SYMLINKS 'diff removed symlink and file' '
+ cat >expected <<-\EOF &&
+ diff --git a/frotz b/frotz
+ deleted file mode 120000
+ index 7c465af..0000000
+ --- a/frotz
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -xyzzy
+ \ No newline at end of file
+ diff --git a/nitfol b/nitfol
+ deleted file mode 100644
+ index 7c465af..0000000
+ --- a/nitfol
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -xyzzy
+ EOF
+ mv frotz frotz2 &&
+ mv nitfol nitfol2 &&
+ git diff-index -M -p $tree >current &&
+ compare_diff_patch expected current
+'
-test_expect_success SYMLINKS \
- 'diff identical, but newly created symlink' \
- 'ln -s xyzzy frotz &&
- git diff-index -M -p $tree > current &&
- compare_diff_patch current expected'
+test_expect_success SYMLINKS 'diff identical, but newly created symlink and file' '
+ >expected &&
+ rm -f frotz nitfol &&
+ echo xyzzy >nitfol &&
+ test-chmtime +10 nitfol &&
+ ln -s xyzzy frotz &&
+ git diff-index -M -p $tree >current &&
+ compare_diff_patch expected current &&
-cat > expected << EOF
-diff --git a/frotz b/frotz
-index 7c465af..df1db54 120000
---- a/frotz
-+++ b/frotz
-@@ -1 +1 @@
--xyzzy
-\ No newline at end of file
-+yxyyz
-\ No newline at end of file
-EOF
+ >expected &&
+ git diff-index -M -p -w $tree >current &&
+ compare_diff_patch expected current
+'
-test_expect_success SYMLINKS \
- 'diff different symlink' \
- 'rm frotz &&
- ln -s yxyyz frotz &&
- git diff-index -M -p $tree > current &&
- compare_diff_patch current expected'
+test_expect_success SYMLINKS 'diff different symlink and file' '
+ cat >expected <<-\EOF &&
+ diff --git a/frotz b/frotz
+ index 7c465af..df1db54 120000
+ --- a/frotz
+ +++ b/frotz
+ @@ -1 +1 @@
+ -xyzzy
+ \ No newline at end of file
+ +yxyyz
+ \ No newline at end of file
+ diff --git a/nitfol b/nitfol
+ index 7c465af..df1db54 100644
+ --- a/nitfol
+ +++ b/nitfol
+ @@ -1 +1 @@
+ -xyzzy
+ +yxyyz
+ EOF
+ rm -f frotz &&
+ ln -s yxyyz frotz &&
+ echo yxyyz >nitfol &&
+ git diff-index -M -p $tree >current &&
+ compare_diff_patch expected current
+'
-test_expect_success SYMLINKS \
- 'diff symlinks with non-existing targets' \
- 'ln -s narf pinky &&
- ln -s take\ over brain &&
- test_must_fail git diff --no-index pinky brain > output 2> output.err &&
- grep narf output &&
- ! grep error output.err'
+test_expect_success SYMLINKS 'diff symlinks with non-existing targets' '
+ ln -s narf pinky &&
+ ln -s take\ over brain &&
+ test_must_fail git diff --no-index pinky brain >output 2>output.err &&
+ grep narf output &&
+ ! test -s output.err
+'
test_expect_success SYMLINKS 'setup symlinks with attributes' '
echo "*.bin diff=bin" >>.gitattributes &&
@@ -96,19 +121,19 @@ test_expect_success SYMLINKS 'setup symlinks with attributes' '
git add -N file.bin link.bin
'
-cat >expect <<'EOF'
-diff --git a/file.bin b/file.bin
-index e69de29..d95f3ad 100644
-Binary files a/file.bin and b/file.bin differ
-diff --git a/link.bin b/link.bin
-index e69de29..dce41ec 120000
---- a/link.bin
-+++ b/link.bin
-@@ -0,0 +1 @@
-+file.bin
-\ No newline at end of file
-EOF
test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' '
+ cat >expect <<-\EOF &&
+ diff --git a/file.bin b/file.bin
+ index e69de29..d95f3ad 100644
+ Binary files a/file.bin and b/file.bin differ
+ diff --git a/link.bin b/link.bin
+ index e69de29..dce41ec 120000
+ --- a/link.bin
+ +++ b/link.bin
+ @@ -0,0 +1 @@
+ +file.bin
+ \ No newline at end of file
+ EOF
git config diff.bin.binary true &&
git diff file.bin link.bin >actual &&
test_cmp expect actual
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
index 2d9f9a0..6cebb39 100755
--- a/t/t4012-diff-binary.sh
+++ b/t/t4012-diff-binary.sh
@@ -8,6 +8,13 @@ test_description='Binary diff and apply
. ./test-lib.sh
+cat >expect.binary-numstat <<\EOF
+1 1 a
+- - b
+1 1 c
+- - d
+EOF
+
test_expect_success 'prepare repository' \
'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
git update-index --add a b c d &&
@@ -23,13 +30,35 @@ cat > expected <<\EOF
d | Bin
4 files changed, 2 insertions(+), 2 deletions(-)
EOF
-test_expect_success 'diff without --binary' \
- 'git diff | git apply --stat --summary >current &&
- test_cmp expected current'
+test_expect_success '"apply --stat" output for binary file change' '
+ git diff >diff &&
+ git apply --stat --summary <diff >current &&
+ test_i18ncmp expected current
+'
+
+test_expect_success 'diff --shortstat output for binary file change' '
+ echo " 4 files changed, 2 insertions(+), 2 deletions(-)" >expected &&
+ git diff --shortstat >current &&
+ test_i18ncmp expected current
+'
+
+test_expect_success 'diff --shortstat output for binary file change only' '
+ echo " 1 file changed, 0 insertions(+), 0 deletions(-)" >expected &&
+ git diff --shortstat -- b >current &&
+ test_i18ncmp expected current
+'
-test_expect_success 'diff with --binary' \
- 'git diff --binary | git apply --stat --summary >current &&
- test_cmp expected current'
+test_expect_success 'apply --numstat notices binary file change' '
+ git diff >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expect.binary-numstat current
+'
+
+test_expect_success 'apply --numstat understands diff --binary format' '
+ git diff --binary >diff &&
+ git apply --numstat <diff >current &&
+ test_cmp expect.binary-numstat current
+'
# apply needs to be able to skip the binary material correctly
# in order to report the line number of a corrupt patch.
@@ -90,4 +119,23 @@ test_expect_success 'diff --no-index with binary creation' '
test_cmp expected actual
'
+cat >expect <<EOF
+ binfile | Bin 0 -> 1026 bytes
+ textfile | 10000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+
+test_expect_success 'diff --stat with binary files and big change count' '
+ echo X | dd of=binfile bs=1k seek=1 &&
+ git add binfile &&
+ i=0 &&
+ while test $i -lt 10000; do
+ echo $i &&
+ i=$(($i + 1))
+ done >textfile &&
+ git add textfile &&
+ git diff --cached --stat binfile textfile >output &&
+ grep " | " output >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 93a6f20..e77c09c 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -128,7 +128,12 @@ do
} >"$actual" &&
if test -f "$expect"
then
- test_cmp "$expect" "$actual" &&
+ case $cmd in
+ *format-patch* | *-stat*)
+ test_i18ncmp "$expect" "$actual";;
+ *)
+ test_cmp "$expect" "$actual";;
+ esac &&
rm -f "$actual"
else
# this is to help developing new tests.
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
index 3a9f78a..9951e36 100644
--- a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
@@ -1,8 +1,8 @@
$ git diff-tree --cc --patch-with-stat --summary master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --cc dir/sub
index cead32e,7289e35..992913c
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side
index a61ad8c..cec33fa 100644
--- a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side
+++ b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side
@@ -1,9 +1,9 @@
$ git diff-tree --cc --patch-with-stat --summary side
c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_master b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
index 49f23b9..db3c0a7 100644
--- a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
+++ b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
@@ -1,8 +1,8 @@
$ git diff-tree --cc --patch-with-stat master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --cc dir/sub
index cead32e,7289e35..992913c
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_--summary_master b/t/t4013/diff.diff-tree_--cc_--stat_--summary_master
index cc6eb3b..d019867 100644
--- a/t/t4013/diff.diff-tree_--cc_--stat_--summary_master
+++ b/t/t4013/diff.diff-tree_--cc_--stat_--summary_master
@@ -1,6 +1,6 @@
$ git diff-tree --cc --stat --summary master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
$
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_--summary_side b/t/t4013/diff.diff-tree_--cc_--stat_--summary_side
index 50362be..12b2eee 100644
--- a/t/t4013/diff.diff-tree_--cc_--stat_--summary_side
+++ b/t/t4013/diff.diff-tree_--cc_--stat_--summary_side
@@ -1,8 +1,8 @@
$ git diff-tree --cc --stat --summary side
c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
$
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_master b/t/t4013/diff.diff-tree_--cc_--stat_master
index fae7f33..40b9179 100644
--- a/t/t4013/diff.diff-tree_--cc_--stat_master
+++ b/t/t4013/diff.diff-tree_--cc_--stat_master
@@ -1,6 +1,6 @@
$ git diff-tree --cc --stat master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial
index d5c333a..817ed06 100644
--- a/t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial
+++ b/t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial
@@ -1,9 +1,9 @@
$ git diff-tree --pretty=oneline --root --patch-with-stat initial
444ac553ac7612cc88969031b02b3767fb8a353a Initial
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
new file mode 100644
diff --git a/t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side b/t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side
index 4d30e7e..fe3f6b7 100644
--- a/t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side
+++ b/t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side
@@ -5,10 +5,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial
index 7dfa6af..06eb77e 100644
--- a/t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial
+++ b/t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial
@@ -5,10 +5,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
new file mode 100644
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial
index 43bfce2..680eab5 100644
--- a/t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial
+++ b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial
@@ -5,10 +5,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--stat_initial b/t/t4013/diff.diff-tree_--pretty_--root_--stat_initial
index 9154aa4..9722d1b 100644
--- a/t/t4013/diff.diff-tree_--pretty_--root_--stat_initial
+++ b/t/t4013/diff.diff-tree_--pretty_--root_--stat_initial
@@ -5,8 +5,8 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
$
diff --git a/t/t4013/diff.diff-tree_--root_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--root_--patch-with-stat_initial
index 1562b62..ad69ffe 100644
--- a/t/t4013/diff.diff-tree_--root_--patch-with-stat_initial
+++ b/t/t4013/diff.diff-tree_--root_--patch-with-stat_initial
@@ -1,9 +1,9 @@
$ git diff-tree --root --patch-with-stat initial
444ac553ac7612cc88969031b02b3767fb8a353a
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
new file mode 100644
diff --git a/t/t4013/diff.diff-tree_-c_--stat_--summary_master b/t/t4013/diff.diff-tree_-c_--stat_--summary_master
index ac9f641..81c3021 100644
--- a/t/t4013/diff.diff-tree_-c_--stat_--summary_master
+++ b/t/t4013/diff.diff-tree_-c_--stat_--summary_master
@@ -1,6 +1,6 @@
$ git diff-tree -c --stat --summary master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_--summary_side b/t/t4013/diff.diff-tree_-c_--stat_--summary_side
index 2afcca1..e8dc12b 100644
--- a/t/t4013/diff.diff-tree_-c_--stat_--summary_side
+++ b/t/t4013/diff.diff-tree_-c_--stat_--summary_side
@@ -1,8 +1,8 @@
$ git diff-tree -c --stat --summary side
c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_master b/t/t4013/diff.diff-tree_-c_--stat_master
index c2fe6a9..89d59b1 100644
--- a/t/t4013/diff.diff-tree_-c_--stat_master
+++ b/t/t4013/diff.diff-tree_-c_--stat_master
@@ -1,6 +1,6 @@
$ git diff-tree -c --stat master
59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
$
diff --git a/t/t4013/diff.diff_--patch-with-stat_-r_initial..side b/t/t4013/diff.diff_--patch-with-stat_-r_initial..side
index 9ed317a..be8d1ea 100644
--- a/t/t4013/diff.diff_--patch-with-stat_-r_initial..side
+++ b/t/t4013/diff.diff_--patch-with-stat_-r_initial..side
@@ -1,8 +1,8 @@
$ git diff --patch-with-stat -r initial..side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
diff --git a/t/t4013/diff.diff_--patch-with-stat_initial..side b/t/t4013/diff.diff_--patch-with-stat_initial..side
index 8b50629..5424e6d 100644
--- a/t/t4013/diff.diff_--patch-with-stat_initial..side
+++ b/t/t4013/diff.diff_--patch-with-stat_initial..side
@@ -1,8 +1,8 @@
$ git diff --patch-with-stat initial..side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
diff --git a/t/t4013/diff.diff_--stat_initial..side b/t/t4013/diff.diff_--stat_initial..side
index 0517b5d..b7741e2 100644
--- a/t/t4013/diff.diff_--stat_initial..side
+++ b/t/t4013/diff.diff_--stat_initial..side
@@ -1,6 +1,6 @@
$ git diff --stat initial..side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
$
diff --git a/t/t4013/diff.diff_-r_--stat_initial..side b/t/t4013/diff.diff_-r_--stat_initial..side
index 245220d..5d514f5 100644
--- a/t/t4013/diff.diff_-r_--stat_initial..side
+++ b/t/t4013/diff.diff_-r_--stat_initial..side
@@ -1,6 +1,6 @@
$ git diff -r --stat initial..side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
$
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side b/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side
index 52116d3..547ca06 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side
+++ b/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side
@@ -12,10 +12,10 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master b/t/t4013/diff.format-patch_--attach_--stdout_initial..master
index ce49bd6..52fedc1 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_initial..master
+++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -73,9 +73,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
@@ -121,10 +121,10 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
index 5f1b238..1c3cde2 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
+++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -73,9 +73,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..side b/t/t4013/diff.format-patch_--attach_--stdout_initial..side
index 4a2364a..4717bd8 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_initial..side
+++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..side
@@ -12,10 +12,10 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
index 43b81eb..02c4db7 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
+++ b/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -73,9 +73,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
@@ -121,10 +121,10 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
index ca3f60b..c7677c5 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
+++ b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -73,9 +73,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
@@ -121,10 +121,10 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_initial..master
index 08f2301..5b3e34e 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -73,9 +73,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
@@ -121,10 +121,10 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^
index 07f1230..d13f8a8 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -73,9 +73,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^
index 29e00ab..caec553 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..side b/t/t4013/diff.format-patch_--inline_--stdout_initial..side
index 67633d4..d3a6762 100644
--- a/t/t4013/diff.format-patch_--inline_--stdout_initial..side
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..side
@@ -12,10 +12,10 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
index 3b4e113..244d964 100644
--- a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
+++ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
@@ -10,10 +10,10 @@ A U Thor (2):
Second
Third
- dir/sub | 4 ++++
- file0 | 3 +++
- file1 | 3 +++
- file2 | 3 ---
+ dir/sub | 4 ++++
+ file0 | 3 +++
+ file1 | 3 +++
+ file2 | 3 ---
4 files changed, 10 insertions(+), 3 deletions(-)
create mode 100644 file1
delete mode 100644 file2
@@ -28,9 +28,9 @@ Subject: [DIFFERENT_PREFIX 1/2] Second
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -73,9 +73,9 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
Subject: [DIFFERENT_PREFIX 2/2] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master b/t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master
index f7752eb..bfc287a 100644
--- a/t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master
+++ b/t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master
@@ -6,9 +6,9 @@ Subject: [PATCH] Second
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -51,9 +51,9 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
Subject: [PATCH] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -85,10 +85,10 @@ Date: Mon, 26 Jun 2006 00:03:00 +0000
Subject: [PATCH] Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.format-patch_--stdout_--numbered_initial..master b/t/t4013/diff.format-patch_--stdout_--numbered_initial..master
index 8e67dbf..568f6f5 100644
--- a/t/t4013/diff.format-patch_--stdout_--numbered_initial..master
+++ b/t/t4013/diff.format-patch_--stdout_--numbered_initial..master
@@ -6,9 +6,9 @@ Subject: [PATCH 1/3] Second
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -51,9 +51,9 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
Subject: [PATCH 2/3] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -85,10 +85,10 @@ Date: Mon, 26 Jun 2006 00:03:00 +0000
Subject: [PATCH 3/3] Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.format-patch_--stdout_initial..master b/t/t4013/diff.format-patch_--stdout_initial..master
index 7b89978..5f0352f 100644
--- a/t/t4013/diff.format-patch_--stdout_initial..master
+++ b/t/t4013/diff.format-patch_--stdout_initial..master
@@ -6,9 +6,9 @@ Subject: [PATCH 1/3] Second
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -51,9 +51,9 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
Subject: [PATCH 2/3] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -85,10 +85,10 @@ Date: Mon, 26 Jun 2006 00:03:00 +0000
Subject: [PATCH 3/3] Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.format-patch_--stdout_initial..master^ b/t/t4013/diff.format-patch_--stdout_initial..master^
index b7f9725..2ae454d 100644
--- a/t/t4013/diff.format-patch_--stdout_initial..master^
+++ b/t/t4013/diff.format-patch_--stdout_initial..master^
@@ -6,9 +6,9 @@ Subject: [PATCH 1/2] Second
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -51,9 +51,9 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
Subject: [PATCH 2/2] Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.format-patch_--stdout_initial..side b/t/t4013/diff.format-patch_--stdout_initial..side
index e765088..a7d52fb 100644
--- a/t/t4013/diff.format-patch_--stdout_initial..side
+++ b/t/t4013/diff.format-patch_--stdout_initial..side
@@ -5,10 +5,10 @@ Date: Mon, 26 Jun 2006 00:03:00 +0000
Subject: [PATCH] Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
index bd7f5c0..a18f147 100644
--- a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
+++ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
@@ -12,8 +12,8 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
@@ -31,8 +31,8 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
@@ -53,8 +53,8 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..8422d40 100644
diff --git a/t/t4013/diff.log_--patch-with-stat_master b/t/t4013/diff.log_--patch-with-stat_master
index 14595a6..ae425c4 100644
--- a/t/t4013/diff.log_--patch-with-stat_master
+++ b/t/t4013/diff.log_--patch-with-stat_master
@@ -12,10 +12,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
@@ -54,9 +54,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
@@ -86,9 +86,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_
index 5a4e727..d5207ca 100644
--- a/t/t4013/diff.log_--patch-with-stat_master_--_dir_
+++ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_
@@ -12,8 +12,8 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
@@ -31,8 +31,8 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
@@ -53,8 +53,8 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..8422d40 100644
diff --git a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
index df0aaa9..0fc1e8c 100644
--- a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
@@ -6,9 +6,9 @@ Date: Mon Jun 26 00:04:00 2006 +0000
Merge branch 'side'
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --cc dir/sub
index cead32e,7289e35..992913c
@@ -44,10 +44,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
@@ -87,9 +87,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -120,9 +120,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -162,10 +162,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master
index c11b5f2c..dffc09d 100644
--- a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master
@@ -12,10 +12,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
@@ -55,9 +55,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -88,9 +88,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -130,10 +130,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_master b/t/t4013/diff.log_--root_--patch-with-stat_master
index 5f0c98f..55aa980 100644
--- a/t/t4013/diff.log_--root_--patch-with-stat_master
+++ b/t/t4013/diff.log_--root_--patch-with-stat_master
@@ -12,10 +12,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
@@ -54,9 +54,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
@@ -86,9 +86,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dir/sub b/dir/sub
@@ -127,10 +127,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
new file mode 100644
diff --git a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
index e62c368..019d85f 100644
--- a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
@@ -6,9 +6,9 @@ Date: Mon Jun 26 00:04:00 2006 +0000
Merge branch 'side'
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --combined dir/sub
index cead32e,7289e35..992913c
@@ -44,10 +44,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
@@ -87,9 +87,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -120,9 +120,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -162,10 +162,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
diff --git a/t/t4013/diff.show_--patch-with-stat_--summary_side b/t/t4013/diff.show_--patch-with-stat_--summary_side
index 377f2b7..95a474e 100644
--- a/t/t4013/diff.show_--patch-with-stat_--summary_side
+++ b/t/t4013/diff.show_--patch-with-stat_--summary_side
@@ -5,10 +5,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.show_--patch-with-stat_side b/t/t4013/diff.show_--patch-with-stat_side
index fb14c53..974e99b 100644
--- a/t/t4013/diff.show_--patch-with-stat_side
+++ b/t/t4013/diff.show_--patch-with-stat_side
@@ -5,10 +5,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
diff --git a/t/t4013/diff.show_--stat_--summary_side b/t/t4013/diff.show_--stat_--summary_side
index 5bd5977..a71492f 100644
--- a/t/t4013/diff.show_--stat_--summary_side
+++ b/t/t4013/diff.show_--stat_--summary_side
@@ -5,9 +5,9 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
$
diff --git a/t/t4013/diff.show_--stat_side b/t/t4013/diff.show_--stat_side
index 3b22327..9be7124 100644
--- a/t/t4013/diff.show_--stat_side
+++ b/t/t4013/diff.show_--stat_side
@@ -5,8 +5,8 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
$
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
index 6a467cc..c8b6af2 100644
--- a/t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
+++ b/t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
@@ -5,8 +5,8 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
@@ -24,8 +24,8 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
@@ -46,8 +46,8 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..8422d40 100644
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_master b/t/t4013/diff.whatchanged_--patch-with-stat_master
index 1e1bbe1..1ac431b 100644
--- a/t/t4013/diff.whatchanged_--patch-with-stat_master
+++ b/t/t4013/diff.whatchanged_--patch-with-stat_master
@@ -5,10 +5,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
@@ -47,9 +47,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
@@ -79,9 +79,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dir/sub b/dir/sub
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_ b/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
index 13789f1..b30c285 100644
--- a/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
+++ b/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
@@ -5,8 +5,8 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
@@ -24,8 +24,8 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
@@ -46,8 +46,8 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- 1 files changed, 2 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ 1 file changed, 2 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..8422d40 100644
diff --git a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
index e96ff1f..30aae78 100644
--- a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
@@ -6,9 +6,9 @@ Date: Mon Jun 26 00:04:00 2006 +0000
Merge branch 'side'
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --cc dir/sub
index cead32e,7289e35..992913c
@@ -44,10 +44,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
@@ -87,9 +87,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -120,9 +120,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -162,10 +162,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
diff --git a/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
index 0291153..db90e51 100644
--- a/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
@@ -5,10 +5,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
@@ -48,9 +48,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -81,9 +81,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -123,10 +123,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
diff --git a/t/t4013/diff.whatchanged_--root_--patch-with-stat_master b/t/t4013/diff.whatchanged_--root_--patch-with-stat_master
index 9b0349c..9a6cc92 100644
--- a/t/t4013/diff.whatchanged_--root_--patch-with-stat_master
+++ b/t/t4013/diff.whatchanged_--root_--patch-with-stat_master
@@ -5,10 +5,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
diff --git a/dir/sub b/dir/sub
index 35d242b..7289e35 100644
@@ -47,9 +47,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
@@ -79,9 +79,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/dir/sub b/dir/sub
@@ -120,10 +120,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
diff --git a/dir/sub b/dir/sub
new file mode 100644
diff --git a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
index c0aff68..d1d32bd 100644
--- a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
+++ b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
@@ -6,9 +6,9 @@ Date: Mon Jun 26 00:04:00 2006 +0000
Merge branch 'side'
- dir/sub | 2 ++
- file0 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+)
diff --combined dir/sub
index cead32e,7289e35..992913c
@@ -44,10 +44,10 @@ Date: Mon Jun 26 00:03:00 2006 +0000
Side
---
- dir/sub | 2 ++
- file0 | 3 +++
- file3 | 4 ++++
- 3 files changed, 9 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+)
create mode 100644 file3
diff --git a/dir/sub b/dir/sub
@@ -87,9 +87,9 @@ Date: Mon Jun 26 00:02:00 2006 +0000
Third
---
- dir/sub | 2 ++
- file1 | 3 +++
- 2 files changed, 5 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
@@ -120,9 +120,9 @@ Date: Mon Jun 26 00:01:00 2006 +0000
This is the second commit.
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 ---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
@@ -162,10 +162,10 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
---
- dir/sub | 2 ++
- file0 | 3 +++
- file2 | 3 +++
- 3 files changed, 8 insertions(+), 0 deletions(-)
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+)
create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 92248d2..959aa26 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -179,12 +179,21 @@ test_expect_success 'configuration To: header' '
grep "^To: R. E. Cipient <rcipient@example.com>\$" patch9
'
+# check_patch <patch>: Verify that <patch> looks like a half-sane
+# patch email to avoid a false positive with !grep
+check_patch () {
+ grep -e "^From:" "$1" &&
+ grep -e "^Date:" "$1" &&
+ grep -e "^Subject:" "$1"
+}
+
test_expect_success '--no-to overrides config.to' '
git config --replace-all format.to \
"R. E. Cipient <rcipient@example.com>" &&
git format-patch --no-to --stdout master..side |
sed -e "/^\$/q" >patch10 &&
+ check_patch patch10 &&
! grep "^To: R. E. Cipient <rcipient@example.com>\$" patch10
'
@@ -195,6 +204,7 @@ test_expect_success '--no-to and --to replaces config.to' '
git format-patch --no-to --to="Someone Else <else@out.there>" \
--stdout master..side |
sed -e "/^\$/q" >patch11 &&
+ check_patch patch11 &&
! grep "^To: Someone <someone@out.there>\$" patch11 &&
grep "^To: Someone Else <else@out.there>\$" patch11
'
@@ -205,15 +215,17 @@ test_expect_success '--no-cc overrides config.cc' '
"C. E. Cipient <rcipient@example.com>" &&
git format-patch --no-cc --stdout master..side |
sed -e "/^\$/q" >patch12 &&
+ check_patch patch12 &&
! grep "^Cc: C. E. Cipient <rcipient@example.com>\$" patch12
'
-test_expect_success '--no-add-headers overrides config.headers' '
+test_expect_success '--no-add-header overrides config.headers' '
git config --replace-all format.headers \
"Header1: B. E. Cipient <rcipient@example.com>" &&
- git format-patch --no-add-headers --stdout master..side |
+ git format-patch --no-add-header --stdout master..side |
sed -e "/^\$/q" >patch13 &&
+ check_patch patch13 &&
! grep "^Header1: B. E. Cipient <rcipient@example.com>\$" patch13
'
@@ -231,7 +243,7 @@ check_threading () {
(git format-patch --stdout "$@"; echo $? > status.out) |
# Prints everything between the Message-ID and In-Reply-To,
# and replaces all Message-ID-lookalikes by a sequence number
- perl -ne '
+ "$PERL_PATH" -ne '
if (/^(message-id|references|in-reply-to)/i) {
$printing = 1;
} elsif (/^\S/) {
@@ -445,22 +457,22 @@ test_expect_success 'thread deep cover-letter in-reply-to' '
'
test_expect_success 'thread via config' '
- git config format.thread true &&
+ test_config format.thread true &&
check_threading expect.thread master
'
test_expect_success 'thread deep via config' '
- git config format.thread deep &&
+ test_config format.thread deep &&
check_threading expect.deep master
'
test_expect_success 'thread config + override' '
- git config format.thread deep &&
+ test_config format.thread deep &&
check_threading expect.thread --thread master
'
test_expect_success 'thread config + --no-thread' '
- git config format.thread deep &&
+ test_config format.thread deep &&
check_threading expect.no-threading --no-thread master
'
@@ -480,6 +492,7 @@ test_expect_success 'cover-letter inherits diff options' '
git mv file foo &&
git commit -m foo &&
git format-patch --cover-letter -1 &&
+ check_patch 0000-cover-letter.patch &&
! grep "file => foo .* 0 *\$" 0000-cover-letter.patch &&
git format-patch --cover-letter -1 -M &&
grep "file => foo .* 0 *\$" 0000-cover-letter.patch
@@ -505,11 +518,6 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
'
cat > expect << EOF
----
- file | 16 ++++++++++++++++
- 1 files changed, 16 insertions(+), 0 deletions(-)
-
-diff --git a/file b/file
index 40f36c6..2dc5c23 100644
--- a/file
+++ b/file
@@ -524,7 +532,9 @@ EOF
test_expect_success 'format-patch respects -U' '
git format-patch -U4 -2 &&
- sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+ sed -e "1,/^diff/d" -e "/^+5/q" \
+ <0001-This-is-an-excessively-long-subject-line-for-a-messa.patch \
+ >output &&
test_cmp expect output
'
@@ -657,6 +667,7 @@ test_expect_success 'format-patch --no-signature ignores format.signature' '
git config format.signature "config sig" &&
git format-patch --stdout --signature="my sig" --no-signature \
-1 >output &&
+ check_patch output &&
! grep "config sig" output &&
! grep "my sig" output &&
! grep "^-- \$" output
@@ -673,17 +684,20 @@ test_expect_success 'format-patch --signature --cover-letter' '
test_expect_success 'format.signature="" supresses signatures' '
git config format.signature "" &&
git format-patch --stdout -1 >output &&
+ check_patch output &&
! grep "^-- \$" output
'
test_expect_success 'format-patch --no-signature supresses signatures' '
git config --unset-all format.signature &&
git format-patch --stdout --no-signature -1 >output &&
+ check_patch output &&
! grep "^-- \$" output
'
test_expect_success 'format-patch --signature="" supresses signatures' '
- git format-patch --signature="" -1 >output &&
+ git format-patch --stdout --signature="" -1 >output &&
+ check_patch output &&
! grep "^-- \$" output
'
@@ -869,4 +883,12 @@ test_expect_success 'empty subject prefix does not have extra space' '
test_cmp expect actual
'
+test_expect_success 'format patch ignores color.ui' '
+ test_unconfig color.ui &&
+ git format-patch --stdout -1 >expect &&
+ test_config color.ui always &&
+ git format-patch --stdout -1 >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 9059bcd..cc3db13 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -103,7 +103,7 @@ test_expect_success 'another test, with -w --ignore-space-at-eol' 'test_cmp expe
git diff -w -b --ignore-space-at-eol > out
test_expect_success 'another test, with -w -b --ignore-space-at-eol' 'test_cmp expect out'
-tr 'Q' '\015' << EOF > expect
+tr 'Q_' '\015 ' << EOF > expect
diff --git a/x b/x
index d99af23..8b32fb5 100644
--- a/x
@@ -111,19 +111,19 @@ index d99af23..8b32fb5 100644
@@ -1,6 +1,6 @@
-whitespace at beginning
+ whitespace at beginning
- whitespace change
+ whitespace change
-whitespace in the middle
+white space in the middle
- whitespace at end
+ whitespace at end__
unchanged line
- CR at endQ
+ CR at end
EOF
git diff -b > out
test_expect_success 'another test, with -b' 'test_cmp expect out'
git diff -b --ignore-space-at-eol > out
test_expect_success 'another test, with -b --ignore-space-at-eol' 'test_cmp expect out'
-tr 'Q' '\015' << EOF > expect
+tr 'Q_' '\015 ' << EOF > expect
diff --git a/x b/x
index d99af23..8b32fb5 100644
--- a/x
@@ -135,9 +135,9 @@ index d99af23..8b32fb5 100644
+ whitespace at beginning
+whitespace change
+white space in the middle
- whitespace at end
+ whitespace at end__
unchanged line
- CR at endQ
+ CR at end
EOF
git diff --ignore-space-at-eol > out
test_expect_success 'another test, with --ignore-space-at-eol' 'test_cmp expect out'
diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh
index ab0c2f0..97b8177 100755
--- a/t/t4016-diff-quote.sh
+++ b/t/t4016-diff-quote.sh
@@ -57,22 +57,33 @@ test_expect_success TABS_IN_FILENAMES 'git diff --summary -M HEAD' '
test_cmp expect actual
'
-test_expect_success TABS_IN_FILENAMES 'setup expected files' '
-cat >expect <<\EOF
- pathname.1 => "Rpathname\twith HT.0" | 0
- pathname.3 => "Rpathname\nwith LF.0" | 0
- "pathname\twith HT.3" => "Rpathname\nwith LF.1" | 0
- pathname.2 => Rpathname with SP.0 | 0
- "pathname\twith HT.2" => Rpathname with SP.1 | 0
- pathname.0 => Rpathname.0 | 0
- "pathname\twith HT.0" => Rpathname.1 | 0
- 7 files changed, 0 insertions(+), 0 deletions(-)
-EOF
+test_expect_success TABS_IN_FILENAMES 'git diff --numstat -M HEAD' '
+ cat >expect <<-\EOF &&
+ 0 0 pathname.1 => "Rpathname\twith HT.0"
+ 0 0 pathname.3 => "Rpathname\nwith LF.0"
+ 0 0 "pathname\twith HT.3" => "Rpathname\nwith LF.1"
+ 0 0 pathname.2 => Rpathname with SP.0
+ 0 0 "pathname\twith HT.2" => Rpathname with SP.1
+ 0 0 pathname.0 => Rpathname.0
+ 0 0 "pathname\twith HT.0" => Rpathname.1
+ EOF
+ git diff --numstat -M HEAD >actual &&
+ test_cmp expect actual
'
test_expect_success TABS_IN_FILENAMES 'git diff --stat -M HEAD' '
+ cat >expect <<-\EOF &&
+ pathname.1 => "Rpathname\twith HT.0" | 0
+ pathname.3 => "Rpathname\nwith LF.0" | 0
+ "pathname\twith HT.3" => "Rpathname\nwith LF.1" | 0
+ pathname.2 => Rpathname with SP.0 | 0
+ "pathname\twith HT.2" => Rpathname with SP.1 | 0
+ pathname.0 => Rpathname.0 | 0
+ "pathname\twith HT.0" => Rpathname.1 | 0
+ 7 files changed, 0 insertions(+), 0 deletions(-)
+ EOF
git diff --stat -M HEAD >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_done
diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh
index b68c56b..4bd2a1c 100755
--- a/t/t4018-diff-funcname.sh
+++ b/t/t4018-diff-funcname.sh
@@ -105,7 +105,7 @@ test_expect_funcname () {
grep "^@@.*@@ $1" diff
}
-for p in bibtex cpp csharp fortran html java objc pascal perl php python ruby tex
+for p in bibtex cpp csharp fortran html java matlab objc pascal perl php python ruby tex
do
test_expect_success "builtin $p pattern compiles" '
echo "*.java diff=$p" >.gitattributes &&
diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh
index 083f62d..533afc1 100755
--- a/t/t4020-diff-external.sh
+++ b/t/t4020-diff-external.sh
@@ -118,7 +118,7 @@ test_expect_success 'no diff with -diff' '
git diff | grep Binary
'
-echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file
+echo NULZbetweenZwords | "$PERL_PATH" -pe 'y/Z/\000/' > file
test_expect_success 'force diff with "diff"' '
echo >.gitattributes "file diff" &&
diff --git a/t/t4029-diff-trailing-space.sh b/t/t4029-diff-trailing-space.sh
index 3ccc237..36e2f07 100755
--- a/t/t4029-diff-trailing-space.sh
+++ b/t/t4029-diff-trailing-space.sh
@@ -27,7 +27,7 @@ test_expect_success \
git config --bool diff.suppressBlankEmpty true &&
git diff f > actual &&
test_cmp exp actual &&
- perl -i.bak -p -e "s/^\$/ /" exp &&
+ "$PERL_PATH" -i.bak -p -e "s/^\$/ /" exp &&
git config --bool diff.suppressBlankEmpty false &&
git diff f > actual &&
test_cmp exp actual &&
diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh
index 88c5619..eebb1ee 100755
--- a/t/t4030-diff-textconv.sh
+++ b/t/t4030-diff-textconv.sh
@@ -21,7 +21,7 @@ EOF
cat >hexdump <<'EOF'
#!/bin/sh
-perl -e '$/ = undef; $_ = <>; s/./ord($&)/ge; print $_' < "$1"
+"$PERL_PATH" -e '$/ = undef; $_ = <>; s/./ord($&)/ge; print $_' < "$1"
EOF
chmod +x hexdump
@@ -85,13 +85,17 @@ test_expect_success 'status -v produces text' '
'
cat >expect.stat <<'EOF'
- file | Bin 2 -> 4 bytes
- 1 files changed, 0 insertions(+), 0 deletions(-)
+ file | Bin 2 -> 4 bytes
+ 1 file changed, 0 insertions(+), 0 deletions(-)
EOF
test_expect_success 'diffstat does not run textconv' '
echo file diff=fail >.gitattributes &&
git diff --stat HEAD^ HEAD >actual &&
- test_cmp expect.stat actual
+ test_i18ncmp expect.stat actual &&
+
+ head -n1 <expect.stat >expect.line1 &&
+ head -n1 <actual >actual.line1 &&
+ test_cmp expect.line1 actual.line1
'
# restore working setup
echo file diff=foo >.gitattributes
diff --git a/t/t4031-diff-rewrite-binary.sh b/t/t4031-diff-rewrite-binary.sh
index 7d7470f..eacc669 100755
--- a/t/t4031-diff-rewrite-binary.sh
+++ b/t/t4031-diff-rewrite-binary.sh
@@ -44,17 +44,23 @@ test_expect_success 'rewrite diff can show binary patch' '
grep "GIT binary patch" diff
'
-test_expect_success 'rewrite diff --stat shows binary changes' '
+test_expect_success 'rewrite diff --numstat shows binary changes' '
+ git diff -B --numstat --summary >diff &&
+ grep -e "- - " diff &&
+ grep " rewrite file" diff
+'
+
+test_expect_success 'diff --stat counts binary rewrite as 0 lines' '
git diff -B --stat --summary >diff &&
grep "Bin" diff &&
- grep "0 insertions.*0 deletions" diff &&
+ test_i18ngrep "0 insertions.*0 deletions" diff &&
grep " rewrite file" diff
'
{
echo "#!$SHELL_PATH"
cat <<'EOF'
-perl -e '$/ = undef; $_ = <>; s/./ord($&)/ge; print $_' < "$1"
+"$PERL_PATH" -e '$/ = undef; $_ = <>; s/./ord($&)/ge; print $_' < "$1"
EOF
} >dump
chmod +x dump
diff --git a/t/t4033-diff-patience.sh b/t/t4033-diff-patience.sh
index 1eb1498..3c9932e 100755
--- a/t/t4033-diff-patience.sh
+++ b/t/t4033-diff-patience.sh
@@ -3,166 +3,10 @@
test_description='patience diff algorithm'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-diff-alternative.sh
-cat >file1 <<\EOF
-#include <stdio.h>
+test_diff_frobnitz "patience"
-// Frobs foo heartily
-int frobnitz(int foo)
-{
- int i;
- for(i = 0; i < 10; i++)
- {
- printf("Your answer is: ");
- printf("%d\n", foo);
- }
-}
-
-int fact(int n)
-{
- if(n > 1)
- {
- return fact(n-1) * n;
- }
- return 1;
-}
-
-int main(int argc, char **argv)
-{
- frobnitz(fact(10));
-}
-EOF
-
-cat >file2 <<\EOF
-#include <stdio.h>
-
-int fib(int n)
-{
- if(n > 2)
- {
- return fib(n-1) + fib(n-2);
- }
- return 1;
-}
-
-// Frobs foo heartily
-int frobnitz(int foo)
-{
- int i;
- for(i = 0; i < 10; i++)
- {
- printf("%d\n", foo);
- }
-}
-
-int main(int argc, char **argv)
-{
- frobnitz(fib(10));
-}
-EOF
-
-cat >expect <<\EOF
-diff --git a/file1 b/file2
-index 6faa5a3..e3af329 100644
---- a/file1
-+++ b/file2
-@@ -1,26 +1,25 @@
- #include <stdio.h>
-
-+int fib(int n)
-+{
-+ if(n > 2)
-+ {
-+ return fib(n-1) + fib(n-2);
-+ }
-+ return 1;
-+}
-+
- // Frobs foo heartily
- int frobnitz(int foo)
- {
- int i;
- for(i = 0; i < 10; i++)
- {
-- printf("Your answer is: ");
- printf("%d\n", foo);
- }
- }
-
--int fact(int n)
--{
-- if(n > 1)
-- {
-- return fact(n-1) * n;
-- }
-- return 1;
--}
--
- int main(int argc, char **argv)
- {
-- frobnitz(fact(10));
-+ frobnitz(fib(10));
- }
-EOF
-
-test_expect_success 'patience diff' '
-
- test_must_fail git diff --no-index --patience file1 file2 > output &&
- test_cmp expect output
-
-'
-
-test_expect_success 'patience diff output is valid' '
-
- mv file2 expect &&
- git apply < output &&
- test_cmp expect file2
-
-'
-
-cat >uniq1 <<\EOF
-1
-2
-3
-4
-5
-6
-EOF
-
-cat >uniq2 <<\EOF
-a
-b
-c
-d
-e
-f
-EOF
-
-cat >expect <<\EOF
-diff --git a/uniq1 b/uniq2
-index b414108..0fdf397 100644
---- a/uniq1
-+++ b/uniq2
-@@ -1,6 +1,6 @@
--1
--2
--3
--4
--5
--6
-+a
-+b
-+c
-+d
-+e
-+f
-EOF
-
-test_expect_success 'completely different files' '
-
- test_must_fail git diff --no-index --patience uniq1 uniq2 > output &&
- test_cmp expect output
-
-'
+test_diff_unique "patience"
test_done
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index c374aa4..30d42cb 100755
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
@@ -3,6 +3,7 @@
test_description='word diff colors'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
cat >pre.simple <<-\EOF
h(4)
@@ -293,12 +294,17 @@ test_expect_success '--word-diff=none' '
word_diff --word-diff=plain --word-diff=none
'
+test_expect_success 'unset default driver' '
+ test_unconfig diff.wordregex
+'
+
test_language_driver bibtex
test_language_driver cpp
test_language_driver csharp
test_language_driver fortran
test_language_driver html
test_language_driver java
+test_language_driver matlab
test_language_driver objc
test_language_driver pascal
test_language_driver perl
@@ -333,4 +339,49 @@ test_expect_success 'word-diff with diff.sbe' '
word_diff --word-diff=plain
'
+test_expect_success 'word-diff with no newline at EOF' '
+ cat >expect <<-\EOF &&
+ diff --git a/pre b/post
+ index 7bf316e..3dd0303 100644
+ --- a/pre
+ +++ b/post
+ @@ -1 +1 @@
+ a a [-a-]{+ab+} a a
+ EOF
+ printf "%s" "a a a a a" >pre &&
+ printf "%s" "a a ab a a" >post &&
+ word_diff --word-diff=plain
+'
+
+test_expect_success 'setup history with two files' '
+ echo "a b; c" >a.tex &&
+ echo "a b; c" >z.txt &&
+ git add a.tex z.txt &&
+ git commit -minitial &&
+
+ # modify both
+ echo "a bx; c" >a.tex &&
+ echo "a bx; c" >z.txt &&
+ git commit -mmodified -a
+'
+
+test_expect_success 'wordRegex for the first file does not apply to the second' '
+ echo "*.tex diff=tex" >.gitattributes &&
+ git config diff.tex.wordRegex "[a-z]+|." &&
+ cat >expect <<-\EOF &&
+ diff --git a/a.tex b/a.tex
+ --- a/a.tex
+ +++ b/a.tex
+ @@ -1 +1 @@
+ a [-b-]{+bx+}; c
+ diff --git a/z.txt b/z.txt
+ --- a/z.txt
+ +++ b/z.txt
+ @@ -1 +1 @@
+ a [-b;-]{+bx;+} c
+ EOF
+ git diff --word-diff HEAD~ >actual &&
+ compare_diff_patch expect actual
+'
+
test_done
diff --git a/t/t4034/matlab/expect b/t/t4034/matlab/expect
new file mode 100644
index 0000000..72cf3e9
--- /dev/null
+++ b/t/t4034/matlab/expect
@@ -0,0 +1,14 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index dc204db..70e05f0 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,9 +1,9 @@<RESET>
+(<RED>1<RESET><GREEN>0<RESET>) (<RED>-1e10<RESET><GREEN>-0e10<RESET>) '<RED>b<RESET><GREEN>y<RESET>';
+[<RED>a<RESET><GREEN>x<RESET>] {<RED>a<RESET><GREEN>x<RESET>} <RED>a<RESET><GREEN>x<RESET>.<RED>b<RESET><GREEN>y<RESET>;
+~<RED>a<RESET><GREEN>x<RESET>;
+<RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>.*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>./<RED>b a<RESET><GREEN>y x<RESET>^<RED>b a<RESET><GREEN>y x<RESET>.^<RED>b a<RESET><GREEN>y x<RESET>.\<RED>b a<RESET><GREEN>y x<RESET>.';
+<RED>a<RESET><GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET><GREEN>y<RESET>;
+<RED>a<RESET><GREEN>x<RESET>&<RED>b a<RESET><GREEN>y x<RESET>&&<RED>b a<RESET><GREEN>y x<RESET>|<RED>b a<RESET><GREEN>y x<RESET>||<RED>b<RESET><GREEN>y<RESET>;
+<RED>a<RESET><GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET><GREEN>y<RESET>;
+<RED>a<RESET><GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>~=<RED>b<RESET><GREEN>y<RESET>;
+<RED>a<RESET><GREEN>x<RESET>,<RED>b<RESET><GREEN>y<RESET>;
diff --git a/t/t4034/matlab/post b/t/t4034/matlab/post
new file mode 100644
index 0000000..70e05f0
--- /dev/null
+++ b/t/t4034/matlab/post
@@ -0,0 +1,9 @@
+(0) (-0e10) 'y';
+[x] {x} x.y;
+~x;
+x*y x.*y x/y x./y x^y x.^y x.\y x.';
+x+y x-y;
+x&y x&&y x|y x||y;
+x<y x<=y x>y x>=y;
+x==y x~=y;
+x,y;
diff --git a/t/t4034/matlab/pre b/t/t4034/matlab/pre
new file mode 100644
index 0000000..dc204db
--- /dev/null
+++ b/t/t4034/matlab/pre
@@ -0,0 +1,9 @@
+(1) (-1e10) 'b';
+[a] {a} a.b;
+~a;
+a*b a.*b a/b a./b a^b a.^b a.\b a.';
+a+b a-b;
+a&b a&&b a|b a||b;
+a<b a<=b a>b a>=b;
+a==b a~=b;
+a,b;
diff --git a/t/t4035-diff-quiet.sh b/t/t4035-diff-quiet.sh
index e747e84..231412d 100755
--- a/t/t4035-diff-quiet.sh
+++ b/t/t4035-diff-quiet.sh
@@ -10,71 +10,142 @@ test_expect_success 'setup' '
git commit -m first &&
echo 2 >b &&
git add . &&
- git commit -a -m second
+ git commit -a -m second &&
+ mkdir -p test-outside/repo && (
+ cd test-outside/repo &&
+ git init &&
+ echo "1 1" >a &&
+ git add . &&
+ git commit -m 1
+ ) &&
+ mkdir -p test-outside/non/git && (
+ cd test-outside/non/git &&
+ echo "1 1" >a &&
+ echo "1 1" >matching-file &&
+ echo "1 1 " >trailing-space &&
+ echo "1 1" >extra-space &&
+ echo "2" >never-match
+ )
'
test_expect_success 'git diff-tree HEAD^ HEAD' '
git diff-tree --quiet HEAD^ HEAD >cnt
- test $? = 1 && test $(wc -l <cnt) = 0
+ test $? = 1 && test_line_count = 0 cnt
'
test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
git diff-tree --quiet HEAD^ HEAD -- a >cnt
- test $? = 0 && test $(wc -l <cnt) = 0
+ test $? = 0 && test_line_count = 0 cnt
'
test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
git diff-tree --quiet HEAD^ HEAD -- b >cnt
- test $? = 1 && test $(wc -l <cnt) = 0
+ test $? = 1 && test_line_count = 0 cnt
'
# this diff outputs one line: sha1 of the given head
test_expect_success 'echo HEAD | git diff-tree --stdin' '
echo $(git rev-parse HEAD) | git diff-tree --quiet --stdin >cnt
- test $? = 1 && test $(wc -l <cnt) = 1
+ test $? = 1 && test_line_count = 1 cnt
'
test_expect_success 'git diff-tree HEAD HEAD' '
git diff-tree --quiet HEAD HEAD >cnt
- test $? = 0 && test $(wc -l <cnt) = 0
+ test $? = 0 && test_line_count = 0 cnt
'
test_expect_success 'git diff-files' '
git diff-files --quiet >cnt
- test $? = 0 && test $(wc -l <cnt) = 0
+ test $? = 0 && test_line_count = 0 cnt
'
test_expect_success 'git diff-index --cached HEAD' '
git diff-index --quiet --cached HEAD >cnt
- test $? = 0 && test $(wc -l <cnt) = 0
+ test $? = 0 && test_line_count = 0 cnt
'
test_expect_success 'git diff-index --cached HEAD^' '
git diff-index --quiet --cached HEAD^ >cnt
- test $? = 1 && test $(wc -l <cnt) = 0
+ test $? = 1 && test_line_count = 0 cnt
'
test_expect_success 'git diff-index --cached HEAD^' '
echo text >>b &&
echo 3 >c &&
git add . && {
git diff-index --quiet --cached HEAD^ >cnt
- test $? = 1 && test $(wc -l <cnt) = 0
+ test $? = 1 && test_line_count = 0 cnt
}
'
test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
git commit -m "text in b" && {
git diff-tree --quiet -Stext HEAD^ HEAD -- b >cnt
- test $? = 1 && test $(wc -l <cnt) = 0
+ test $? = 1 && test_line_count = 0 cnt
}
'
test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
git diff-tree --quiet -Snot-found HEAD^ HEAD -- b >cnt
- test $? = 0 && test $(wc -l <cnt) = 0
+ test $? = 0 && test_line_count = 0 cnt
'
test_expect_success 'git diff-files' '
echo 3 >>c && {
git diff-files --quiet >cnt
- test $? = 1 && test $(wc -l <cnt) = 0
+ test $? = 1 && test_line_count = 0 cnt
}
'
test_expect_success 'git diff-index --cached HEAD' '
git update-index c && {
git diff-index --quiet --cached HEAD >cnt
- test $? = 1 && test $(wc -l <cnt) = 0
+ test $? = 1 && test_line_count = 0 cnt
}
'
+test_expect_success 'git diff, one file outside repo' '
+ (
+ cd test-outside/repo &&
+ test_expect_code 0 git diff --quiet a ../non/git/matching-file &&
+ test_expect_code 1 git diff --quiet a ../non/git/extra-space
+ )
+'
+
+test_expect_success 'git diff, both files outside repo' '
+ (
+ GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY/test-outside" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd test-outside/non/git &&
+ test_expect_code 0 git diff --quiet a matching-file &&
+ test_expect_code 1 git diff --quiet a extra-space
+ )
+'
+
+test_expect_success 'git diff --ignore-space-at-eol, one file outside repo' '
+ (
+ cd test-outside/repo &&
+ test_expect_code 0 git diff --quiet --ignore-space-at-eol a ../non/git/trailing-space &&
+ test_expect_code 1 git diff --quiet --ignore-space-at-eol a ../non/git/extra-space
+ )
+'
+
+test_expect_success 'git diff --ignore-space-at-eol, both files outside repo' '
+ (
+ GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY/test-outside" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd test-outside/non/git &&
+ test_expect_code 0 git diff --quiet --ignore-space-at-eol a trailing-space &&
+ test_expect_code 1 git diff --quiet --ignore-space-at-eol a extra-space
+ )
+'
+
+test_expect_success 'git diff --ignore-all-space, one file outside repo' '
+ (
+ cd test-outside/repo &&
+ test_expect_code 0 git diff --quiet --ignore-all-space a ../non/git/trailing-space &&
+ test_expect_code 0 git diff --quiet --ignore-all-space a ../non/git/extra-space &&
+ test_expect_code 1 git diff --quiet --ignore-all-space a ../non/git/never-match
+ )
+'
+
+test_expect_success 'git diff --ignore-all-space, both files outside repo' '
+ (
+ GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY/test-outside" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd test-outside/non/git &&
+ test_expect_code 0 git diff --quiet --ignore-all-space a trailing-space &&
+ test_expect_code 0 git diff --quiet --ignore-all-space a extra-space &&
+ test_expect_code 1 git diff --quiet --ignore-all-space a never-match
+ )
+'
+
test_done
diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh
index bf9a752..6c01d0c 100755
--- a/t/t4041-diff-submodule-option.sh
+++ b/t/t4041-diff-submodule-option.sh
@@ -458,4 +458,38 @@ EOF
test_cmp expected actual
'
+test_expect_success 'diff --submodule with objects referenced by alternates' '
+ mkdir sub_alt &&
+ (cd sub_alt &&
+ git init &&
+ echo a >a &&
+ git add a &&
+ git commit -m a
+ ) &&
+ mkdir super &&
+ (cd super &&
+ git clone -s ../sub_alt sub &&
+ git init &&
+ git add sub &&
+ git commit -m "sub a"
+ ) &&
+ (cd sub_alt &&
+ sha1_before=$(git rev-parse --short HEAD)
+ echo b >b &&
+ git add b &&
+ git commit -m b
+ sha1_after=$(git rev-parse --short HEAD)
+ echo "Submodule sub $sha1_before..$sha1_after:
+ > b" >../expected
+ ) &&
+ (cd super &&
+ (cd sub &&
+ git fetch &&
+ git checkout origin/master
+ ) &&
+ git diff --submodule > ../actual
+ )
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t4043-diff-rename-binary.sh b/t/t4043-diff-rename-binary.sh
index 0601281..2a2cf91 100755
--- a/t/t4043-diff-rename-binary.sh
+++ b/t/t4043-diff-rename-binary.sh
@@ -23,9 +23,8 @@ test_expect_success 'move the files into a "sub" directory' '
'
cat > expected <<\EOF
- bar => sub/bar | Bin 5 -> 5 bytes
- foo => sub/foo | 0
- 2 files changed, 0 insertions(+), 0 deletions(-)
+- - bar => sub/bar
+0 0 foo => sub/foo
diff --git a/bar b/sub/bar
similarity index 100%
@@ -38,7 +37,8 @@ rename to sub/foo
EOF
test_expect_success 'git show -C -C report renames' '
- git show -C -C --raw --binary --stat | tail -n 12 > current &&
+ git show -C -C --raw --binary --numstat >patch-with-stat &&
+ tail -n 11 patch-with-stat >current &&
test_cmp expected current
'
diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh
index 8a3c63b..3950f50 100755
--- a/t/t4045-diff-relative.sh
+++ b/t/t4045-diff-relative.sh
@@ -29,15 +29,27 @@ test_expect_success "-p $*" "
"
}
+check_numstat() {
+expect=$1; shift
+cat >expected <<EOF
+1 0 $expect
+EOF
+test_expect_success "--numstat $*" "
+ echo '1 0 $expect' >expected &&
+ git diff --numstat $* HEAD^ >actual &&
+ test_cmp expected actual
+"
+}
+
check_stat() {
expect=$1; shift
cat >expected <<EOF
- $expect | 1 +
- 1 files changed, 1 insertions(+), 0 deletions(-)
+ $expect | 1 +
+ 1 file changed, 1 insertion(+)
EOF
test_expect_success "--stat $*" "
git diff --stat $* HEAD^ >actual &&
- test_cmp expected actual
+ test_i18ncmp expected actual
"
}
@@ -52,7 +64,7 @@ test_expect_success "--raw $*" "
"
}
-for type in diff stat raw; do
+for type in diff numstat stat raw; do
check_$type file2 --relative=subdir/
check_$type file2 --relative=subdir
check_$type dir/file2 --relative=sub
diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh
index 29e80a5..ed7e093 100755
--- a/t/t4047-diff-dirstat.sh
+++ b/t/t4047-diff-dirstat.sh
@@ -252,50 +252,47 @@ EOF
'
cat <<EOF >expect_diff_stat
- changed/text | 2 +-
- dst/copy/changed/text | 10 ++++++++++
- dst/copy/rearranged/text | 10 ++++++++++
- dst/copy/unchanged/text | 10 ++++++++++
- dst/move/changed/text | 10 ++++++++++
- dst/move/rearranged/text | 10 ++++++++++
- dst/move/unchanged/text | 10 ++++++++++
- rearranged/text | 2 +-
- src/move/changed/text | 10 ----------
- src/move/rearranged/text | 10 ----------
- src/move/unchanged/text | 10 ----------
- 11 files changed, 62 insertions(+), 32 deletions(-)
+1 1 changed/text
+10 0 dst/copy/changed/text
+10 0 dst/copy/rearranged/text
+10 0 dst/copy/unchanged/text
+10 0 dst/move/changed/text
+10 0 dst/move/rearranged/text
+10 0 dst/move/unchanged/text
+1 1 rearranged/text
+0 10 src/move/changed/text
+0 10 src/move/rearranged/text
+0 10 src/move/unchanged/text
EOF
cat <<EOF >expect_diff_stat_M
- changed/text | 2 +-
- dst/copy/changed/text | 10 ++++++++++
- dst/copy/rearranged/text | 10 ++++++++++
- dst/copy/unchanged/text | 10 ++++++++++
- {src => dst}/move/changed/text | 2 +-
- {src => dst}/move/rearranged/text | 2 +-
- {src => dst}/move/unchanged/text | 0
- rearranged/text | 2 +-
- 8 files changed, 34 insertions(+), 4 deletions(-)
+1 1 changed/text
+10 0 dst/copy/changed/text
+10 0 dst/copy/rearranged/text
+10 0 dst/copy/unchanged/text
+1 1 {src => dst}/move/changed/text
+1 1 {src => dst}/move/rearranged/text
+0 0 {src => dst}/move/unchanged/text
+1 1 rearranged/text
EOF
cat <<EOF >expect_diff_stat_CC
- changed/text | 2 +-
- {src => dst}/copy/changed/text | 2 +-
- {src => dst}/copy/rearranged/text | 2 +-
- {src => dst}/copy/unchanged/text | 0
- {src => dst}/move/changed/text | 2 +-
- {src => dst}/move/rearranged/text | 2 +-
- {src => dst}/move/unchanged/text | 0
- rearranged/text | 2 +-
- 8 files changed, 6 insertions(+), 6 deletions(-)
-EOF
-
-test_expect_success 'sanity check setup (--stat)' '
- git diff --stat HEAD^..HEAD >actual_diff_stat &&
+1 1 changed/text
+1 1 {src => dst}/copy/changed/text
+1 1 {src => dst}/copy/rearranged/text
+0 0 {src => dst}/copy/unchanged/text
+1 1 {src => dst}/move/changed/text
+1 1 {src => dst}/move/rearranged/text
+0 0 {src => dst}/move/unchanged/text
+1 1 rearranged/text
+EOF
+
+test_expect_success 'sanity check setup (--numstat)' '
+ git diff --numstat HEAD^..HEAD >actual_diff_stat &&
test_cmp expect_diff_stat actual_diff_stat &&
- git diff --stat -M HEAD^..HEAD >actual_diff_stat_M &&
+ git diff --numstat -M HEAD^..HEAD >actual_diff_stat_M &&
test_cmp expect_diff_stat_M actual_diff_stat_M &&
- git diff --stat -C -C HEAD^..HEAD >actual_diff_stat_CC &&
+ git diff --numstat -C -C HEAD^..HEAD >actual_diff_stat_CC &&
test_cmp expect_diff_stat_CC actual_diff_stat_CC
'
diff --git a/t/t4049-diff-stat-count.sh b/t/t4049-diff-stat-count.sh
new file mode 100755
index 0000000..b41eb61
--- /dev/null
+++ b/t/t4049-diff-stat-count.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Copyright (c) 2011, Google Inc.
+
+test_description='diff --stat-count'
+. ./test-lib.sh
+
+test_expect_success setup '
+ >a &&
+ >b &&
+ >c &&
+ >d &&
+ git add a b c d &&
+ chmod +x c d &&
+ echo a >a &&
+ echo b >b &&
+ cat >expect <<-\EOF
+ a | 1 +
+ b | 1 +
+ 2 files changed, 2 insertions(+)
+ EOF
+ git diff --stat --stat-count=2 >actual &&
+ test_i18ncmp expect actual
+'
+
+test_done
diff --git a/t/t4050-diff-histogram.sh b/t/t4050-diff-histogram.sh
new file mode 100755
index 0000000..fd3e86a
--- /dev/null
+++ b/t/t4050-diff-histogram.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+test_description='histogram diff algorithm'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-diff-alternative.sh
+
+test_diff_frobnitz "histogram"
+
+test_diff_unique "histogram"
+
+test_done
diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh
new file mode 100755
index 0000000..001d678
--- /dev/null
+++ b/t/t4051-diff-function-context.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='diff function context'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+
+cat <<\EOF >hello.c
+#include <stdio.h>
+
+static int a(void)
+{
+ /*
+ * Dummy.
+ */
+}
+
+static int hello_world(void)
+{
+ /* Classic. */
+ printf("Hello world.\n");
+
+ /* Success! */
+ return 0;
+}
+static int b(void)
+{
+ /*
+ * Dummy, too.
+ */
+}
+
+int main(int argc, char **argv)
+{
+ a();
+ b();
+ return hello_world();
+}
+EOF
+
+test_expect_success 'setup' '
+ git add hello.c &&
+ test_tick &&
+ git commit -m initial &&
+
+ grep -v Classic <hello.c >hello.c.new &&
+ mv hello.c.new hello.c
+'
+
+cat <<\EOF >expected
+diff --git a/hello.c b/hello.c
+--- a/hello.c
++++ b/hello.c
+@@ -10,8 +10,7 @@ static int a(void)
+ static int hello_world(void)
+ {
+- /* Classic. */
+ printf("Hello world.\n");
+
+ /* Success! */
+ return 0;
+ }
+EOF
+
+test_expect_success 'diff -U0 -W' '
+ git diff -U0 -W >actual &&
+ compare_diff_patch actual expected
+'
+
+cat <<\EOF >expected
+diff --git a/hello.c b/hello.c
+--- a/hello.c
++++ b/hello.c
+@@ -9,9 +9,8 @@ static int a(void)
+
+ static int hello_world(void)
+ {
+- /* Classic. */
+ printf("Hello world.\n");
+
+ /* Success! */
+ return 0;
+ }
+EOF
+
+test_expect_success 'diff -W' '
+ git diff -W >actual &&
+ compare_diff_patch actual expected
+'
+
+test_done
diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh
new file mode 100755
index 0000000..b68afef
--- /dev/null
+++ b/t/t4052-stat-output.sh
@@ -0,0 +1,336 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Zbigniew Jędrzejewski-Szmek
+#
+
+test_description='test --stat output of various commands'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+# 120 character name
+name=aaaaaaaaaa
+name=$name$name$name$name$name$name$name$name$name$name$name$name
+test_expect_success 'preparation' '
+ >"$name" &&
+ git add "$name" &&
+ git commit -m message &&
+ echo a >"$name" &&
+ git commit -m message "$name"
+'
+
+while read cmd args
+do
+ cat >expect <<-'EOF'
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
+ EOF
+ test_expect_success "$cmd: small change with long name gives more space to the name" '
+ git $cmd $args >output &&
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+
+ cat >expect <<-'EOF'
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
+ EOF
+ test_expect_success "$cmd --stat=width: a long name is given more room when the bar is short" '
+ git $cmd $args --stat=40 >output &&
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "$cmd --stat-width=width with long name" '
+ git $cmd $args --stat-width=40 >output &&
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+
+ cat >expect <<-'EOF'
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
+ EOF
+ test_expect_success "$cmd --stat=...,name-width with long name" '
+ git $cmd $args --stat=60,30 >output &&
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "$cmd --stat-name-width with long name" '
+ git $cmd $args --stat-name-width=30 >output &&
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+done <<\EOF
+format-patch -1 --stdout
+diff HEAD^ HEAD --stat
+show --stat
+log -1 --stat
+EOF
+
+
+test_expect_success 'preparation for big change tests' '
+ >abcd &&
+ git add abcd &&
+ git commit -m message &&
+ i=0 &&
+ while test $i -lt 1000
+ do
+ echo $i && i=$(($i + 1))
+ done >abcd &&
+ git commit -m message abcd
+'
+
+cat >expect80 <<'EOF'
+ abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+cat >expect80-graph <<'EOF'
+| abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+cat >expect200 <<'EOF'
+ abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+cat >expect200-graph <<'EOF'
+| abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+while read verb expect cmd args
+do
+ test_expect_success "$cmd $verb COLUMNS (big change)" '
+ COLUMNS=200 git $cmd $args >output
+ grep " | " output >actual &&
+ test_cmp "$expect" actual
+ '
+
+ test "$cmd" != diff || continue
+
+ test_expect_success "$cmd --graph $verb COLUMNS (big change)" '
+ COLUMNS=200 git $cmd $args --graph >output
+ grep " | " output >actual &&
+ test_cmp "$expect-graph" actual
+ '
+done <<\EOF
+ignores expect80 format-patch -1 --stdout
+respects expect200 diff HEAD^ HEAD --stat
+respects expect200 show --stat
+respects expect200 log -1 --stat
+EOF
+
+cat >expect40 <<'EOF'
+ abcd | 1000 ++++++++++++++++++++++++++
+EOF
+cat >expect40-graph <<'EOF'
+| abcd | 1000 ++++++++++++++++++++++++
+EOF
+while read verb expect cmd args
+do
+ test_expect_success "$cmd $verb not enough COLUMNS (big change)" '
+ COLUMNS=40 git $cmd $args >output
+ grep " | " output >actual &&
+ test_cmp "$expect" actual
+ '
+
+ test "$cmd" != diff || continue
+
+ test_expect_success "$cmd --graph $verb not enough COLUMNS (big change)" '
+ COLUMNS=40 git $cmd $args --graph >output
+ grep " | " output >actual &&
+ test_cmp "$expect-graph" actual
+ '
+done <<\EOF
+ignores expect80 format-patch -1 --stdout
+respects expect40 diff HEAD^ HEAD --stat
+respects expect40 show --stat
+respects expect40 log -1 --stat
+EOF
+
+cat >expect40 <<'EOF'
+ abcd | 1000 ++++++++++++++++++++++++++
+EOF
+cat >expect40-graph <<'EOF'
+| abcd | 1000 ++++++++++++++++++++++++++
+EOF
+while read verb expect cmd args
+do
+ test_expect_success "$cmd $verb statGraphWidth config" '
+ git -c diff.statGraphWidth=26 $cmd $args >output
+ grep " | " output >actual &&
+ test_cmp "$expect" actual
+ '
+
+ test "$cmd" != diff || continue
+
+ test_expect_success "$cmd --graph $verb statGraphWidth config" '
+ git -c diff.statGraphWidth=26 $cmd $args --graph >output
+ grep " | " output >actual &&
+ test_cmp "$expect-graph" actual
+ '
+done <<\EOF
+ignores expect80 format-patch -1 --stdout
+respects expect40 diff HEAD^ HEAD --stat
+respects expect40 show --stat
+respects expect40 log -1 --stat
+EOF
+
+
+cat >expect <<'EOF'
+ abcd | 1000 ++++++++++++++++++++++++++
+EOF
+cat >expect-graph <<'EOF'
+| abcd | 1000 ++++++++++++++++++++++++++
+EOF
+while read cmd args
+do
+ test_expect_success "$cmd --stat=width with big change" '
+ git $cmd $args --stat=40 >output
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "$cmd --stat-width=width with big change" '
+ git $cmd $args --stat-width=40 >output
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "$cmd --stat-graph-width with big change" '
+ git $cmd $args --stat-graph-width=26 >output
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+
+ test "$cmd" != diff || continue
+
+ test_expect_success "$cmd --stat-width=width --graph with big change" '
+ git $cmd $args --stat-width=40 --graph >output
+ grep " | " output >actual &&
+ test_cmp expect-graph actual
+ '
+
+ test_expect_success "$cmd --stat-graph-width --graph with big change" '
+ git $cmd $args --stat-graph-width=26 --graph >output
+ grep " | " output >actual &&
+ test_cmp expect-graph actual
+ '
+done <<\EOF
+format-patch -1 --stdout
+diff HEAD^ HEAD --stat
+show --stat
+log -1 --stat
+EOF
+
+test_expect_success 'preparation for long filename tests' '
+ cp abcd aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&
+ git add aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&
+ git commit -m message
+'
+
+cat >expect <<'EOF'
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++
+EOF
+cat >expect-graph <<'EOF'
+| ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++
+EOF
+while read cmd args
+do
+ test_expect_success "$cmd --stat=width with big change is more balanced" '
+ git $cmd $args --stat-width=60 >output &&
+ grep " | " output >actual &&
+ test_cmp expect actual
+ '
+
+ test "$cmd" != diff || continue
+
+ test_expect_success "$cmd --stat=width --graph with big change is balanced" '
+ git $cmd $args --stat-width=60 --graph >output &&
+ grep " | " output >actual &&
+ test_cmp expect-graph actual
+ '
+done <<\EOF
+format-patch -1 --stdout
+diff HEAD^ HEAD --stat
+show --stat
+log -1 --stat
+EOF
+
+cat >expect80 <<'EOF'
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++
+EOF
+cat >expect80-graph <<'EOF'
+| ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++
+EOF
+cat >expect200 <<'EOF'
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+cat >expect200-graph <<'EOF'
+| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+while read verb expect cmd args
+do
+ test_expect_success "$cmd $verb COLUMNS (long filename)" '
+ COLUMNS=200 git $cmd $args >output
+ grep " | " output >actual &&
+ test_cmp "$expect" actual
+ '
+
+ test "$cmd" != diff || continue
+
+ test_expect_success "$cmd --graph $verb COLUMNS (long filename)" '
+ COLUMNS=200 git $cmd $args --graph >output
+ grep " | " output >actual &&
+ test_cmp "$expect-graph" actual
+ '
+done <<\EOF
+ignores expect80 format-patch -1 --stdout
+respects expect200 diff HEAD^ HEAD --stat
+respects expect200 show --stat
+respects expect200 log -1 --stat
+EOF
+
+cat >expect1 <<'EOF'
+ ...aaaaaaa | 1000 ++++++
+EOF
+cat >expect1-graph <<'EOF'
+| ...aaaaaaa | 1000 ++++++
+EOF
+while read verb expect cmd args
+do
+ test_expect_success COLUMNS_CAN_BE_1 \
+ "$cmd $verb prefix greater than COLUMNS (big change)" '
+ COLUMNS=1 git $cmd $args >output
+ grep " | " output >actual &&
+ test_cmp "$expect" actual
+ '
+
+ test "$cmd" != diff || continue
+
+ test_expect_success COLUMNS_CAN_BE_1 \
+ "$cmd --graph $verb prefix greater than COLUMNS (big change)" '
+ COLUMNS=1 git $cmd $args --graph >output
+ grep " | " output >actual &&
+ test_cmp "$expect-graph" actual
+ '
+done <<\EOF
+ignores expect80 format-patch -1 --stdout
+respects expect1 diff HEAD^ HEAD --stat
+respects expect1 show --stat
+respects expect1 log -1 --stat
+EOF
+
+cat >expect <<'EOF'
+ abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+test_expect_success 'merge --stat respects COLUMNS (big change)' '
+ git checkout -b branch HEAD^^ &&
+ COLUMNS=100 git merge --stat --no-ff master^ >output &&
+ grep " | " output >actual
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++
+EOF
+test_expect_success 'merge --stat respects COLUMNS (long filename)' '
+ COLUMNS=100 git merge --stat --no-ff master >output &&
+ grep " | " output >actual
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
new file mode 100755
index 0000000..979e983
--- /dev/null
+++ b/t/t4053-diff-no-index.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='diff --no-index'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ mkdir a &&
+ mkdir b &&
+ echo 1 >a/1 &&
+ echo 2 >a/2 &&
+ git init repo &&
+ echo 1 >repo/a &&
+ mkdir -p non/git &&
+ echo 1 >non/git/a &&
+ echo 1 >non/git/b
+'
+
+test_expect_success 'git diff --no-index directories' '
+ git diff --no-index a b >cnt
+ test $? = 1 && test_line_count = 14 cnt
+'
+
+test_expect_success 'git diff --no-index relative path outside repo' '
+ (
+ cd repo &&
+ test_expect_code 0 git diff --no-index a ../non/git/a &&
+ test_expect_code 0 git diff --no-index ../non/git/a ../non/git/b
+ )
+'
+
+test_done
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index 9b433de..744b8e5 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -17,13 +17,13 @@ do
test_expect_success "$title" '
git apply --stat --summary \
<"$TEST_DIRECTORY/t4100/t-apply-$num.patch" >current &&
- test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
+ test_i18ncmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
'
test_expect_success "$title with recount" '
sed -e "$UNC" <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" |
git apply --recount --stat --summary >current &&
- test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
+ test_i18ncmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
'
done <<\EOF
rename
diff --git a/t/t4100/t-apply-8.expect b/t/t4100/t-apply-8.expect
index eef7f2e..55a55c3 100644
--- a/t/t4100/t-apply-8.expect
+++ b/t/t4100/t-apply-8.expect
@@ -1,2 +1,2 @@
t/t4100-apply-stat.sh | 2 +-
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/t4100/t-apply-9.expect b/t/t4100/t-apply-9.expect
index eef7f2e..55a55c3 100644
--- a/t/t4100/t-apply-9.expect
+++ b/t/t4100/t-apply-9.expect
@@ -1,2 +1,2 @@
t/t4100-apply-stat.sh | 2 +-
- 1 files changed, 1 insertions(+), 1 deletions(-)
+ 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
index dbbf56c..99627bc 100755
--- a/t/t4103-apply-binary.sh
+++ b/t/t4103-apply-binary.sh
@@ -25,10 +25,10 @@ test_expect_success 'setup' "
git commit -m 'Initial Version' 2>/dev/null &&
git checkout -b binary &&
- perl -pe 'y/x/\000/' <file1 >file3 &&
+ "$PERL_PATH" -pe 'y/x/\000/' <file1 >file3 &&
cat file3 >file4 &&
git add file2 &&
- perl -pe 'y/\000/v/' <file3 >file1 &&
+ "$PERL_PATH" -pe 'y/\000/v/' <file3 >file1 &&
rm -f file2 &&
git update-index --add --remove file1 file2 file3 file4 &&
git commit -m 'Second Version' &&
diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh
index 2298ece..fca8153 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 &&
- perl -pe "y/ijk/\\000\\001\\002/" <file1 >file2 &&
+ "$PERL_PATH" -pe "y/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 &&
- perl -pe "y/mon/\\000\\001\\002/" <file1 >file2 &&
+ "$PERL_PATH" -pe "y/mon/\\000\\001\\002/" <file1 >file2 &&
git commit -a -m second &&
git tag second &&
diff --git a/t/t4131-apply-fake-ancestor.sh b/t/t4131-apply-fake-ancestor.sh
index 94373ca..b1361ce 100755
--- a/t/t4131-apply-fake-ancestor.sh
+++ b/t/t4131-apply-fake-ancestor.sh
@@ -11,7 +11,7 @@ test_expect_success 'setup' '
test_commit 1 &&
test_commit 2 &&
mkdir sub &&
- test_commit 3 sub/3 &&
+ test_commit 3 sub/3.t &&
test_commit 4
'
diff --git a/t/t4136-apply-check.sh b/t/t4136-apply-check.sh
new file mode 100755
index 0000000..a321f7c
--- /dev/null
+++ b/t/t4136-apply-check.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test_description='git apply should exit non-zero with unrecognized input.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit 1
+'
+
+test_expect_success 'apply --check exits non-zero with unrecognized input' '
+ test_must_fail git apply --check - <<-\EOF
+ I am not a patch
+ I look nothing like a patch
+ git apply must fail
+ EOF
+'
+
+test_done
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 850fc96..cdafd7e 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -96,6 +96,13 @@ test_expect_success setup '
echo "X-Fake-Field: Line Three" &&
git format-patch --stdout first | sed -e "1d"
} | append_cr >patch1-crlf.eml &&
+ {
+ printf "%255s\\n" ""
+ echo "X-Fake-Field: Line One" &&
+ echo "X-Fake-Field: Line Two" &&
+ echo "X-Fake-Field: Line Three" &&
+ git format-patch --stdout first | sed -e "1d"
+ } > patch1-ws.eml &&
sed -n -e "3,\$p" msg >file &&
git add file &&
@@ -116,6 +123,7 @@ test_expect_success setup '
git commit -m "added another file" &&
git format-patch --stdout master >lorem-move.patch &&
+ git format-patch --no-prefix --stdout master >lorem-zero.patch &&
git checkout -b rename &&
git mv file renamed &&
@@ -129,7 +137,7 @@ test_expect_success setup '
git format-patch -M --stdout lorem^ >rename-add.patch &&
# reset time
- unset test_tick &&
+ sane_unset test_tick &&
test_tick
'
@@ -167,6 +175,17 @@ test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
'
+test_expect_success 'am applies patch e-mail with preceding whitespace' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout first &&
+ git am patch1-ws.eml &&
+ ! test -d .git/rebase-apply &&
+ git diff --exit-code second &&
+ test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
+ test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+'
+
test_expect_success 'setup: new author and committer' '
GIT_AUTHOR_NAME="Another Thor" &&
GIT_AUTHOR_EMAIL="a.thor@example.com" &&
@@ -219,7 +238,7 @@ test_expect_success 'am stays in branch' '
test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
git format-patch --stdout HEAD^ >patch3 &&
- sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4 &&
+ sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
rm -fr .git/rebase-apply &&
git reset --hard &&
git checkout HEAD^ &&
@@ -241,7 +260,17 @@ test_expect_success 'am --keep really keeps the subject' '
git am --keep patch4 &&
! test -d .git/rebase-apply &&
git cat-file commit HEAD >actual &&
- grep "Re: Re: Re: \[PATCH 1/5 v2\] third" actual
+ grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual
+'
+
+test_expect_success 'am --keep-non-patch really keeps the non-patch part' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout HEAD^ &&
+ git am --keep-non-patch patch4 &&
+ ! test -d .git/rebase-apply &&
+ git cat-file commit HEAD >actual &&
+ grep "^\[foo\] third" actual
'
test_expect_success 'am -3 falls back to 3-way merge' '
@@ -258,6 +287,20 @@ test_expect_success 'am -3 falls back to 3-way merge' '
git diff --exit-code lorem
'
+test_expect_success 'am -3 -p0 can read --no-prefix patch' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout -b lorem3 master2 &&
+ sed -n -e "3,\$p" msg >file &&
+ head -n 9 msg >>file &&
+ git add file &&
+ test_tick &&
+ git commit -m "copied stuff" &&
+ git am -3 -p0 lorem-zero.patch &&
+ ! test -d .git/rebase-apply &&
+ git diff --exit-code lorem
+'
+
test_expect_success 'am can rename a file' '
grep "^rename from" rename.patch &&
rm -fr .git/rebase-apply &&
@@ -465,7 +508,7 @@ test_expect_success 'am newline in subject' '
test_tick &&
sed -e "s/second/second \\\n foo/" patch1 >patchnl &&
git am <patchnl >output.out 2>&1 &&
- grep "^Applying: second \\\n foo$" output.out
+ test_i18ngrep "^Applying: second \\\n foo$" output.out
'
test_expect_success 'am -q is quiet' '
@@ -477,4 +520,14 @@ test_expect_success 'am -q is quiet' '
! test -s output.out
'
+test_expect_success 'am empty-file does not infloop' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ touch empty-file &&
+ test_tick &&
+ test_must_fail git am empty-file 2>actual &&
+ echo Patch format detection failed. >expected &&
+ test_i18ncmp expected actual
+'
+
test_done
diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh
index c95c4cc..1176bcc 100755
--- a/t/t4151-am-abort.sh
+++ b/t/t4151-am-abort.sh
@@ -45,8 +45,9 @@ do
test_expect_success "am$with3 --skip continue after failed am$with3" '
test_must_fail git am$with3 --skip >output &&
- test "$(grep "^Applying" output)" = "Applying: 6" &&
- test_cmp file-2-expect file-2 &&
+ test_i18ngrep "^Applying" output >output.applying &&
+ test_i18ngrep "^Applying: 6$" output.applying &&
+ test_i18ncmp file-2-expect file-2 &&
test ! -f .git/MERGE_RR
'
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index 36255d6..3ab670d 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -78,7 +78,7 @@ test_expect_success 'activate rerere, old style (conflicting merge)' '
test_might_fail git config --unset rerere.enabled &&
test_must_fail git merge first &&
- sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) &&
+ sha1=$("$PERL_PATH" -pe "s/ .*//" .git/MERGE_RR) &&
rr=.git/rr-cache/$sha1 &&
grep "^=======\$" $rr/preimage &&
! test -f $rr/postimage &&
@@ -91,7 +91,7 @@ test_expect_success 'rerere.enabled works, too' '
git reset --hard &&
test_must_fail git merge first &&
- sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) &&
+ sha1=$("$PERL_PATH" -pe "s/ .*//" .git/MERGE_RR) &&
rr=.git/rr-cache/$sha1 &&
grep ^=======$ $rr/preimage
'
@@ -101,7 +101,7 @@ test_expect_success 'set up rr-cache' '
git config rerere.enabled true &&
git reset --hard &&
test_must_fail git merge first &&
- sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) &&
+ sha1=$("$PERL_PATH" -pe "s/ .*//" .git/MERGE_RR) &&
rr=.git/rr-cache/$sha1
'
@@ -185,7 +185,7 @@ test_expect_success 'rerere updates postimage timestamp' '
test_expect_success 'rerere clear' '
rm $rr/postimage &&
- echo "$sha1 a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR &&
+ echo "$sha1 a1" | "$PERL_PATH" -pe "y/\012/\000/" >.git/MERGE_RR &&
git rerere clear &&
! test -d $rr
'
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 983e34b..71be59d 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -346,11 +346,11 @@ test_expect_success 'set up more tangled history' '
'
cat > expect <<\EOF
-* Merge commit 'reach'
+* Merge tag 'reach'
|\
| \
| \
-*-. \ Merge commit 'octopus-a'; commit 'octopus-b'
+*-. \ Merge tags 'octopus-a' and 'octopus-b'
|\ \ \
* | | | seventh
| | * | octopus-b
@@ -516,4 +516,294 @@ test_expect_success 'show added path under "--follow -M"' '
)
'
+cat >expect <<\EOF
+* commit COMMIT_OBJECT_NAME
+|\ Merge: MERGE_PARENTS
+| | Author: A U Thor <author@example.com>
+| |
+| | Merge HEADS DESCRIPTION
+| |
+| * commit COMMIT_OBJECT_NAME
+| | Author: A U Thor <author@example.com>
+| |
+| | reach
+| | ---
+| | reach.t | 1 +
+| | 1 file changed, 1 insertion(+)
+| |
+| | diff --git a/reach.t b/reach.t
+| | new file mode 100644
+| | index 0000000..10c9591
+| | --- /dev/null
+| | +++ b/reach.t
+| | @@ -0,0 +1 @@
+| | +reach
+| |
+| \
+*-. \ commit COMMIT_OBJECT_NAME
+|\ \ \ Merge: MERGE_PARENTS
+| | | | Author: A U Thor <author@example.com>
+| | | |
+| | | | Merge HEADS DESCRIPTION
+| | | |
+| | * | commit COMMIT_OBJECT_NAME
+| | |/ Author: A U Thor <author@example.com>
+| | |
+| | | octopus-b
+| | | ---
+| | | octopus-b.t | 1 +
+| | | 1 file changed, 1 insertion(+)
+| | |
+| | | diff --git a/octopus-b.t b/octopus-b.t
+| | | new file mode 100644
+| | | index 0000000..d5fcad0
+| | | --- /dev/null
+| | | +++ b/octopus-b.t
+| | | @@ -0,0 +1 @@
+| | | +octopus-b
+| | |
+| * | commit COMMIT_OBJECT_NAME
+| |/ Author: A U Thor <author@example.com>
+| |
+| | octopus-a
+| | ---
+| | octopus-a.t | 1 +
+| | 1 file changed, 1 insertion(+)
+| |
+| | diff --git a/octopus-a.t b/octopus-a.t
+| | new file mode 100644
+| | index 0000000..11ee015
+| | --- /dev/null
+| | +++ b/octopus-a.t
+| | @@ -0,0 +1 @@
+| | +octopus-a
+| |
+* | commit COMMIT_OBJECT_NAME
+|/ Author: A U Thor <author@example.com>
+|
+| seventh
+| ---
+| seventh.t | 1 +
+| 1 file changed, 1 insertion(+)
+|
+| diff --git a/seventh.t b/seventh.t
+| new file mode 100644
+| index 0000000..9744ffc
+| --- /dev/null
+| +++ b/seventh.t
+| @@ -0,0 +1 @@
+| +seventh
+|
+* commit COMMIT_OBJECT_NAME
+|\ Merge: MERGE_PARENTS
+| | Author: A U Thor <author@example.com>
+| |
+| | Merge branch 'tangle'
+| |
+| * commit COMMIT_OBJECT_NAME
+| |\ Merge: MERGE_PARENTS
+| | | Author: A U Thor <author@example.com>
+| | |
+| | | Merge branch 'side' (early part) into tangle
+| | |
+| * | commit COMMIT_OBJECT_NAME
+| |\ \ Merge: MERGE_PARENTS
+| | | | Author: A U Thor <author@example.com>
+| | | |
+| | | | Merge branch 'master' (early part) into tangle
+| | | |
+| * | | commit COMMIT_OBJECT_NAME
+| | | | Author: A U Thor <author@example.com>
+| | | |
+| | | | tangle-a
+| | | | ---
+| | | | tangle-a | 1 +
+| | | | 1 file changed, 1 insertion(+)
+| | | |
+| | | | diff --git a/tangle-a b/tangle-a
+| | | | new file mode 100644
+| | | | index 0000000..7898192
+| | | | --- /dev/null
+| | | | +++ b/tangle-a
+| | | | @@ -0,0 +1 @@
+| | | | +a
+| | | |
+* | | | commit COMMIT_OBJECT_NAME
+|\ \ \ \ Merge: MERGE_PARENTS
+| | | | | Author: A U Thor <author@example.com>
+| | | | |
+| | | | | Merge branch 'side'
+| | | | |
+| * | | | commit COMMIT_OBJECT_NAME
+| | |_|/ Author: A U Thor <author@example.com>
+| |/| |
+| | | | side-2
+| | | | ---
+| | | | 2 | 1 +
+| | | | 1 file changed, 1 insertion(+)
+| | | |
+| | | | diff --git a/2 b/2
+| | | | new file mode 100644
+| | | | index 0000000..0cfbf08
+| | | | --- /dev/null
+| | | | +++ b/2
+| | | | @@ -0,0 +1 @@
+| | | | +2
+| | | |
+| * | | commit COMMIT_OBJECT_NAME
+| | | | Author: A U Thor <author@example.com>
+| | | |
+| | | | side-1
+| | | | ---
+| | | | 1 | 1 +
+| | | | 1 file changed, 1 insertion(+)
+| | | |
+| | | | diff --git a/1 b/1
+| | | | new file mode 100644
+| | | | index 0000000..d00491f
+| | | | --- /dev/null
+| | | | +++ b/1
+| | | | @@ -0,0 +1 @@
+| | | | +1
+| | | |
+* | | | commit COMMIT_OBJECT_NAME
+| | | | Author: A U Thor <author@example.com>
+| | | |
+| | | | Second
+| | | | ---
+| | | | one | 1 +
+| | | | 1 file changed, 1 insertion(+)
+| | | |
+| | | | diff --git a/one b/one
+| | | | new file mode 100644
+| | | | index 0000000..9a33383
+| | | | --- /dev/null
+| | | | +++ b/one
+| | | | @@ -0,0 +1 @@
+| | | | +case
+| | | |
+* | | | commit COMMIT_OBJECT_NAME
+| |_|/ Author: A U Thor <author@example.com>
+|/| |
+| | | sixth
+| | | ---
+| | | a/two | 1 -
+| | | 1 file changed, 1 deletion(-)
+| | |
+| | | diff --git a/a/two b/a/two
+| | | deleted file mode 100644
+| | | index 9245af5..0000000
+| | | --- a/a/two
+| | | +++ /dev/null
+| | | @@ -1 +0,0 @@
+| | | -ni
+| | |
+* | | commit COMMIT_OBJECT_NAME
+| | | Author: A U Thor <author@example.com>
+| | |
+| | | fifth
+| | | ---
+| | | a/two | 1 +
+| | | 1 file changed, 1 insertion(+)
+| | |
+| | | diff --git a/a/two b/a/two
+| | | new file mode 100644
+| | | index 0000000..9245af5
+| | | --- /dev/null
+| | | +++ b/a/two
+| | | @@ -0,0 +1 @@
+| | | +ni
+| | |
+* | | commit COMMIT_OBJECT_NAME
+|/ / Author: A U Thor <author@example.com>
+| |
+| | fourth
+| | ---
+| | ein | 1 +
+| | 1 file changed, 1 insertion(+)
+| |
+| | diff --git a/ein b/ein
+| | new file mode 100644
+| | index 0000000..9d7e69f
+| | --- /dev/null
+| | +++ b/ein
+| | @@ -0,0 +1 @@
+| | +ichi
+| |
+* | commit COMMIT_OBJECT_NAME
+|/ Author: A U Thor <author@example.com>
+|
+| third
+| ---
+| ichi | 1 +
+| one | 1 -
+| 2 files changed, 1 insertion(+), 1 deletion(-)
+|
+| diff --git a/ichi b/ichi
+| new file mode 100644
+| index 0000000..9d7e69f
+| --- /dev/null
+| +++ b/ichi
+| @@ -0,0 +1 @@
+| +ichi
+| diff --git a/one b/one
+| deleted file mode 100644
+| index 9d7e69f..0000000
+| --- a/one
+| +++ /dev/null
+| @@ -1 +0,0 @@
+| -ichi
+|
+* commit COMMIT_OBJECT_NAME
+| Author: A U Thor <author@example.com>
+|
+| second
+| ---
+| one | 2 +-
+| 1 file changed, 1 insertion(+), 1 deletion(-)
+|
+| diff --git a/one b/one
+| index 5626abf..9d7e69f 100644
+| --- a/one
+| +++ b/one
+| @@ -1 +1 @@
+| -one
+| +ichi
+|
+* commit COMMIT_OBJECT_NAME
+ Author: A U Thor <author@example.com>
+
+ initial
+ ---
+ one | 1 +
+ 1 file changed, 1 insertion(+)
+
+ diff --git a/one b/one
+ new file mode 100644
+ index 0000000..5626abf
+ --- /dev/null
+ +++ b/one
+ @@ -0,0 +1 @@
+ +one
+EOF
+
+sanitize_output () {
+ sed -e 's/ *$//' \
+ -e 's/commit [0-9a-f]*$/commit COMMIT_OBJECT_NAME/' \
+ -e 's/Merge: [ 0-9a-f]*$/Merge: MERGE_PARENTS/' \
+ -e 's/Merge tag.*/Merge HEADS DESCRIPTION/' \
+ -e 's/Merge commit.*/Merge HEADS DESCRIPTION/' \
+ -e 's/, 0 deletions(-)//' \
+ -e 's/, 0 insertions(+)//' \
+ -e 's/ 1 files changed, / 1 file changed, /' \
+ -e 's/, 1 deletions(-)/, 1 deletion(-)/' \
+ -e 's/, 1 insertions(+)/, 1 insertion(+)/'
+}
+
+test_expect_success 'log --graph with diff and stats' '
+ git log --graph --pretty=short --stat -p >actual &&
+ sanitize_output >actual.sanitized <actual &&
+ test_cmp expect actual.sanitized
+'
+
test_done
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index 2ae9faa..4afd778 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -71,4 +71,32 @@ test_expect_success 'alias loop' '
test_must_fail git log --pretty=test-foo
'
+test_expect_success 'NUL separation' '
+ printf "add bar\0initial" >expected &&
+ git log -z --pretty="format:%s" >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'NUL termination' '
+ printf "add bar\0initial\0" >expected &&
+ git log -z --pretty="tformat:%s" >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'NUL separation with --stat' '
+ stat0_part=$(git diff --stat HEAD^ HEAD) &&
+ stat1_part=$(git diff --stat --root HEAD^) &&
+ printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n" >expected &&
+ git log -z --stat --pretty="format:%s" >actual &&
+ test_cmp expected actual
+'
+
+test_expect_failure 'NUL termination with --stat' '
+ stat0_part=$(git diff --stat HEAD^ HEAD) &&
+ stat1_part=$(git diff --stat --root HEAD^) &&
+ printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n\0" >expected &&
+ git log -z --stat --pretty="tformat:%s" >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh
new file mode 100755
index 0000000..eed7273
--- /dev/null
+++ b/t/t4209-log-pickaxe.sh
@@ -0,0 +1,119 @@
+#!/bin/sh
+
+test_description='log --grep/--author/--regexp-ignore-case/-S/-G'
+. ./test-lib.sh
+
+test_expect_success setup '
+ >file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+
+ echo Picked >file &&
+ test_tick &&
+ git commit -a --author="Another Person <another@example.com>" -m second
+'
+
+test_expect_success 'log --grep' '
+ git log --grep=initial --format=%H >actual &&
+ git rev-parse --verify HEAD^ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log --grep --regexp-ignore-case' '
+ git log --regexp-ignore-case --grep=InItial --format=%H >actual &&
+ git rev-parse --verify HEAD^ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log --grep -i' '
+ git log -i --grep=InItial --format=%H >actual &&
+ git rev-parse --verify HEAD^ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log --author --regexp-ignore-case' '
+ git log --regexp-ignore-case --author=person --format=%H >actual &&
+ git rev-parse --verify HEAD >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log --author -i' '
+ git log -i --author=person --format=%H >actual &&
+ git rev-parse --verify HEAD >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -G (nomatch)' '
+ git log -Gpicked --format=%H >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -G (match)' '
+ git log -GPicked --format=%H >actual &&
+ git rev-parse --verify HEAD >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -G --regexp-ignore-case (nomatch)' '
+ git log --regexp-ignore-case -Gpickle --format=%H >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -G -i (nomatch)' '
+ git log -i -Gpickle --format=%H >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -G --regexp-ignore-case (match)' '
+ git log --regexp-ignore-case -Gpicked --format=%H >actual &&
+ git rev-parse --verify HEAD >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -G -i (match)' '
+ git log -i -Gpicked --format=%H >actual &&
+ git rev-parse --verify HEAD >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -S (nomatch)' '
+ git log -Spicked --format=%H >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -S (match)' '
+ git log -SPicked --format=%H >actual &&
+ git rev-parse --verify HEAD >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -S --regexp-ignore-case (match)' '
+ git log --regexp-ignore-case -Spicked --format=%H >actual &&
+ git rev-parse --verify HEAD >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -S -i (match)' '
+ git log -i -Spicked --format=%H >actual &&
+ git rev-parse --verify HEAD >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -S --regexp-ignore-case (nomatch)' '
+ git log --regexp-ignore-case -Spickle --format=%H >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log -S -i (nomatch)' '
+ git log -i -Spickle --format=%H >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4253-am-keep-cr-dos.sh b/t/t4253-am-keep-cr-dos.sh
index 735e55d..553fe3e 100755
--- a/t/t4253-am-keep-cr-dos.sh
+++ b/t/t4253-am-keep-cr-dos.sh
@@ -62,7 +62,7 @@ test_expect_success 'am with dos files config am.keepcr' '
git diff --exit-code master
'
-test_expect_success 'am with dos files config am.keepcr overriden by --no-keep-cr' '
+test_expect_success 'am with dos files config am.keepcr overridden by --no-keep-cr' '
git config am.keepcr 1 &&
git checkout -b dosfiles-conf-keepcr-override initial &&
git format-patch -k initial..master &&
@@ -83,7 +83,7 @@ test_expect_success 'am with dos files with --keep-cr continue' '
git diff --exit-code master
'
-test_expect_success 'am with unix files config am.keepcr overriden by --no-keep-cr' '
+test_expect_success 'am with unix files config am.keepcr overridden by --no-keep-cr' '
git config am.keepcr 1 &&
git checkout -b unixfiles-conf-keepcr-override initial &&
cp -f file1 file &&
diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh
new file mode 100755
index 0000000..b7da95f
--- /dev/null
+++ b/t/t4254-am-corrupt.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='git am with corrupt input'
+. ./test-lib.sh
+
+# Note the missing "+++" line:
+cat > bad-patch.diff <<'EOF'
+From: A U Thor <au.thor@example.com>
+diff --git a/f b/f
+index 7898192..6178079 100644
+--- a/f
+@@ -1 +1 @@
+-a
++b
+EOF
+
+test_expect_success setup '
+ test $? = 0 &&
+ echo a > f &&
+ git add f &&
+ test_tick &&
+ git commit -m initial
+'
+
+# This used to fail before, too, but with a different diagnostic.
+# fatal: unable to write file '(null)' mode 100644: Bad address
+# Also, it had the unwanted side-effect of deleting f.
+test_expect_success 'try to apply corrupted patch' '
+ git am bad-patch.diff 2> actual
+ test $? = 1
+'
+
+cat > expected <<EOF
+fatal: git diff header lacks filename information (line 4)
+EOF
+
+test_expect_success 'compare diagnostic; ensure file is still here' '
+ test $? = 0 &&
+ test -f f &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index cff1b3e..ecf00ed 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -26,9 +26,31 @@ commit id embedding:
. ./test-lib.sh
UNZIP=${UNZIP:-unzip}
+GZIP=${GZIP:-gzip}
+GUNZIP=${GUNZIP:-gzip -d}
SUBSTFORMAT=%H%n
+check_zip() {
+ zipfile=$1.zip
+ listfile=$1.lst
+ dir=$1
+ dir_with_prefix=$dir/$2
+
+ test_expect_success UNZIP " extract ZIP archive" "
+ (mkdir $dir && cd $dir && $UNZIP ../$zipfile)
+ "
+
+ test_expect_success UNZIP " validate filenames" "
+ (cd ${dir_with_prefix}a && find .) | sort >$listfile &&
+ test_cmp a.lst $listfile
+ "
+
+ test_expect_success UNZIP " validate file contents" "
+ diff -r a ${dir_with_prefix}a
+ "
+}
+
test_expect_success \
'populate workdir' \
'mkdir a b c &&
@@ -82,6 +104,12 @@ test_expect_success \
'git archive vs. git tar-tree' \
'test_cmp b.tar b2.tar'
+test_expect_success 'git archive on large files' '
+ test_config core.bigfilethreshold 1 &&
+ git archive HEAD >b3.tar &&
+ test_cmp b.tar b3.tar
+'
+
test_expect_success \
'git archive in a bare repo' \
'(cd bare.git && git archive HEAD) >b3.tar'
@@ -94,7 +122,7 @@ test_expect_success 'git archive with --output' \
'git archive --output=b4.tar HEAD &&
test_cmp b.tar b4.tar'
-test_expect_success NOT_MINGW 'git archive --remote' \
+test_expect_success 'git archive --remote' \
'git archive --remote=. HEAD >b5.tar &&
test_cmp b.tar b5.tar'
@@ -173,10 +201,19 @@ test_expect_success \
test_cmp a/substfile2 g/prefix/a/substfile2
'
+$UNZIP -v >/dev/null 2>&1
+if [ $? -eq 127 ]; then
+ say "Skipping ZIP tests, because unzip was not found"
+else
+ test_set_prereq UNZIP
+fi
+
test_expect_success \
'git archive --format=zip' \
'git archive --format=zip HEAD >d.zip'
+check_zip d
+
test_expect_success \
'git archive --format=zip in a bare repo' \
'(cd bare.git && git archive --format=zip HEAD) >d1.zip'
@@ -199,47 +236,38 @@ test_expect_success 'git archive with --output, override inferred format' '
test_cmp b.tar d4.zip
'
-$UNZIP -v >/dev/null 2>&1
-if [ $? -eq 127 ]; then
- say "Skipping ZIP tests, because unzip was not found"
-else
- test_set_prereq UNZIP
-fi
-
-test_expect_success UNZIP \
- 'extract ZIP archive' \
- '(mkdir d && cd d && $UNZIP ../d.zip)'
-
-test_expect_success UNZIP \
- 'validate filenames' \
- '(cd d/a && find .) | sort >d.lst &&
- test_cmp a.lst d.lst'
-
-test_expect_success UNZIP \
- 'validate file contents' \
- 'diff -r a d/a'
-
test_expect_success \
'git archive --format=zip with prefix' \
'git archive --format=zip --prefix=prefix/ HEAD >e.zip'
-test_expect_success UNZIP \
- 'extract ZIP archive with prefix' \
- '(mkdir e && cd e && $UNZIP ../e.zip)'
+check_zip e prefix/
-test_expect_success UNZIP \
- 'validate filenames with prefix' \
- '(cd e/prefix/a && find .) | sort >e.lst &&
- test_cmp a.lst e.lst'
+test_expect_success 'git archive -0 --format=zip on large files' '
+ test_config core.bigfilethreshold 1 &&
+ git archive -0 --format=zip HEAD >large.zip
+'
-test_expect_success UNZIP \
- 'validate file contents with prefix' \
- 'diff -r a e/prefix/a'
+check_zip large
+
+test_expect_success 'git archive --format=zip on large files' '
+ test_config core.bigfilethreshold 1 &&
+ git archive --format=zip HEAD >large-compressed.zip
+'
+
+check_zip large-compressed
test_expect_success \
'git archive --list outside of a git repo' \
'GIT_DIR=some/non-existing/directory git archive --list'
+test_expect_success 'clients cannot access unreachable commits' '
+ test_commit unreachable &&
+ sha1=`git rev-parse HEAD` &&
+ git reset --hard HEAD^ &&
+ git archive $sha1 >remote.tar &&
+ test_must_fail git archive --remote=. $sha1 >remote.tar
+'
+
test_expect_success 'git-archive --prefix=olde-' '
git archive --prefix=olde- >h.tar HEAD &&
(
@@ -252,4 +280,102 @@ test_expect_success 'git-archive --prefix=olde-' '
test -f h/olde-a/bin/sh
'
+test_expect_success 'setup tar filters' '
+ git config tar.tar.foo.command "tr ab ba" &&
+ git config tar.bar.command "tr ab ba" &&
+ git config tar.bar.remote true
+'
+
+test_expect_success 'archive --list mentions user filter' '
+ git archive --list >output &&
+ grep "^tar\.foo\$" output &&
+ grep "^bar\$" output
+'
+
+test_expect_success 'archive --list shows only enabled remote filters' '
+ git archive --list --remote=. >output &&
+ ! grep "^tar\.foo\$" output &&
+ grep "^bar\$" output
+'
+
+test_expect_success 'invoke tar filter by format' '
+ git archive --format=tar.foo HEAD >config.tar.foo &&
+ tr ab ba <config.tar.foo >config.tar &&
+ test_cmp b.tar config.tar &&
+ git archive --format=bar HEAD >config.bar &&
+ tr ab ba <config.bar >config.tar &&
+ test_cmp b.tar config.tar
+'
+
+test_expect_success 'invoke tar filter by extension' '
+ git archive -o config-implicit.tar.foo HEAD &&
+ test_cmp config.tar.foo config-implicit.tar.foo &&
+ git archive -o config-implicit.bar HEAD &&
+ test_cmp config.tar.foo config-implicit.bar
+'
+
+test_expect_success 'default output format remains tar' '
+ git archive -o config-implicit.baz HEAD &&
+ test_cmp b.tar config-implicit.baz
+'
+
+test_expect_success 'extension matching requires dot' '
+ git archive -o config-implicittar.foo HEAD &&
+ test_cmp b.tar config-implicittar.foo
+'
+
+test_expect_success 'only enabled filters are available remotely' '
+ test_must_fail git archive --remote=. --format=tar.foo HEAD \
+ >remote.tar.foo &&
+ git archive --remote=. --format=bar >remote.bar HEAD &&
+ test_cmp remote.bar config.bar
+'
+
+if $GZIP --version >/dev/null 2>&1; then
+ test_set_prereq GZIP
+else
+ say "Skipping some tar.gz tests because gzip not found"
+fi
+
+test_expect_success GZIP 'git archive --format=tgz' '
+ git archive --format=tgz HEAD >j.tgz
+'
+
+test_expect_success GZIP 'git archive --format=tar.gz' '
+ git archive --format=tar.gz HEAD >j1.tar.gz &&
+ test_cmp j.tgz j1.tar.gz
+'
+
+test_expect_success GZIP 'infer tgz from .tgz filename' '
+ git archive --output=j2.tgz HEAD &&
+ test_cmp j.tgz j2.tgz
+'
+
+test_expect_success GZIP 'infer tgz from .tar.gz filename' '
+ git archive --output=j3.tar.gz HEAD &&
+ test_cmp j.tgz j3.tar.gz
+'
+
+if $GUNZIP --version >/dev/null 2>&1; then
+ test_set_prereq GUNZIP
+else
+ say "Skipping some tar.gz tests because gunzip was not found"
+fi
+
+test_expect_success GZIP,GUNZIP 'extract tgz file' '
+ $GUNZIP -c <j.tgz >j.tar &&
+ test_cmp b.tar j.tar
+'
+
+test_expect_success GZIP 'remote tar.gz is allowed by default' '
+ git archive --remote=. --format=tar.gz HEAD >remote.tar.gz &&
+ test_cmp j.tgz remote.tar.gz
+'
+
+test_expect_success GZIP 'remote tar.gz can be disabled' '
+ git config tar.tar.gz.remote false &&
+ test_must_fail git archive --remote=. --format=tar.gz HEAD \
+ >remote.tar.gz
+'
+
test_done
diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh
index 02d4d22..f47d871 100755
--- a/t/t5001-archive-attr.sh
+++ b/t/t5001-archive-attr.sh
@@ -57,6 +57,15 @@ test_expect_missing worktree/ignored
test_expect_exists worktree/ignored-by-tree
test_expect_missing worktree/ignored-by-worktree
+test_expect_success 'git archive --worktree-attributes option' '
+ git archive --worktree-attributes --worktree-attributes HEAD >worktree.tar &&
+ (mkdir worktree2 && cd worktree2 && "$TAR" xf -) <worktree.tar
+'
+
+test_expect_missing worktree2/ignored
+test_expect_exists worktree2/ignored-by-tree
+test_expect_missing worktree2/ignored-by-worktree
+
test_expect_success 'git archive vs. bare' '
(cd bare && git archive HEAD) >bare-archive.tar &&
test_cmp archive.tar bare-archive.tar
diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh
index ebc36c1..81904d9 100755
--- a/t/t5100-mailinfo.sh
+++ b/t/t5100-mailinfo.sh
@@ -65,7 +65,7 @@ test_expect_success 'respect NULs' '
git mailsplit -d3 -o. "$TEST_DIRECTORY"/t5100/nul-plain &&
test_cmp "$TEST_DIRECTORY"/t5100/nul-plain 001 &&
(cat 001 | git mailinfo msg patch) &&
- test 4 = $(wc -l < patch)
+ test_line_count = 4 patch
'
diff --git a/t/t5100/patch0001 b/t/t5100/patch0001
index 8ce1551..02c9774 100644
--- a/t/t5100/patch0001
+++ b/t/t5100/patch0001
@@ -1,5 +1,5 @@
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
diff --git a/t/t5100/patch0002 b/t/t5100/patch0002
index 8ce1551..02c9774 100644
--- a/t/t5100/patch0002
+++ b/t/t5100/patch0002
@@ -1,5 +1,5 @@
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
diff --git a/t/t5100/patch0003 b/t/t5100/patch0003
index 8ce1551..02c9774 100644
--- a/t/t5100/patch0003
+++ b/t/t5100/patch0003
@@ -1,5 +1,5 @@
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
diff --git a/t/t5100/patch0005 b/t/t5100/patch0005
index 7d24b24..ab7a383 100644
--- a/t/t5100/patch0005
+++ b/t/t5100/patch0005
@@ -1,7 +1,7 @@
---
- Documentation/git-cvsimport-script.txt | 9 ++++++++-
- git-cvsimport-script | 4 ++--
+ Documentation/git-cvsimport-script.txt | 9 ++++++++-
+ git-cvsimport-script | 4 ++--
2 files changed, 10 insertions(+), 3 deletions(-)
50452f9c0c2df1f04d83a26266ba704b13861632
diff --git a/t/t5100/patch0006 b/t/t5100/patch0006
index 8ce1551..02c9774 100644
--- a/t/t5100/patch0006
+++ b/t/t5100/patch0006
@@ -1,5 +1,5 @@
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
diff --git a/t/t5100/patch0010 b/t/t5100/patch0010
index f055481..436821c 100644
--- a/t/t5100/patch0010
+++ b/t/t5100/patch0010
@@ -1,5 +1,5 @@
---
- builtin-mailinfo.c | 2 +-
+ builtin-mailinfo.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
diff --git a/t/t5100/patch0011 b/t/t5100/patch0011
index 8841d3c..0988713 100644
--- a/t/t5100/patch0011
+++ b/t/t5100/patch0011
@@ -1,5 +1,5 @@
---
- builtin-mailinfo.c | 4 ++--
+ builtin-mailinfo.c | 4 ++--
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 3e5fe51..aabfe5c 100644
diff --git a/t/t5100/patch0014 b/t/t5100/patch0014
index 124efd2..3f3825f 100644
--- a/t/t5100/patch0014
+++ b/t/t5100/patch0014
@@ -1,5 +1,5 @@
---
- builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
1 files changed, 36 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
diff --git a/t/t5100/patch0014--scissors b/t/t5100/patch0014--scissors
index 124efd2..3f3825f 100644
--- a/t/t5100/patch0014--scissors
+++ b/t/t5100/patch0014--scissors
@@ -1,5 +1,5 @@
---
- builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
1 files changed, 36 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox
index de10312..34a09a0 100644
--- a/t/t5100/sample.mbox
+++ b/t/t5100/sample.mbox
@@ -12,7 +12,7 @@ Subject: [PATCH] a commit.
Here is a patch from A U Thor.
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
@@ -52,7 +52,7 @@ two truly blank and another full of spaces in between.
Hope this helps.
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
@@ -83,7 +83,7 @@ Message-Id: <nitpicker.12121212@example.net>
Hopefully this would fix the problem stated there.
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
@@ -249,8 +249,8 @@ actual flags.
Signed-off-by: David K=E5gedal <davidk@lysator.liu.se>
---
- Documentation/git-cvsimport-script.txt | 9 ++++++++-
- git-cvsimport-script | 4 ++--
+ Documentation/git-cvsimport-script.txt | 9 ++++++++-
+ git-cvsimport-script | 4 ++--
2 files changed, 10 insertions(+), 3 deletions(-)
50452f9c0c2df1f04d83a26266ba704b13861632
@@ -379,7 +379,7 @@ Subject: [PATCH] a commit.
Here is a patch from A U Thor.
---
- foo | 2 +-
+ foo | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/foo b/foo
@@ -449,7 +449,7 @@ memcmp("Subject: ", header[i], 7) will never match.
Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
- builtin-mailinfo.c | 2 +-
+ builtin-mailinfo.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
@@ -482,7 +482,7 @@ Content-Transfer-Encoding: quoted-printable
Here comes a commit log message, and
its second line is here.
---
- builtin-mailinfo.c | 4 ++--
+ builtin-mailinfo.c | 4 ++--
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 3e5fe51..aabfe5c 100644
@@ -587,7 +587,7 @@ everything before it in the message body.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
- builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
1 files changed, 36 insertions(+), 1 deletions(-)
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh
index 9cc0a42..432f98c 100755
--- a/t/t5150-request-pull.sh
+++ b/t/t5150-request-pull.sh
@@ -67,9 +67,11 @@ test_expect_success 'setup: two scripts for reading pull requests' '
cat <<-\EOT >read-request.sed &&
#!/bin/sed -nf
+ # Note that a request could ask for "tag $tagname"
/ in the git repository at:$/!d
n
/^$/ n
+ s/ tag \([^ ]*\)$/ tag--\1/
s/^[ ]*\(.*\) \([^ ]*\)/please pull\
\1\
\2/p
@@ -86,13 +88,14 @@ test_expect_success 'setup: two scripts for reading pull requests' '
s/$downstream_url_for_sed/URL/g
s/for-upstream/BRANCH/g
s/mnemonic.txt/FILENAME/g
+ s/^version [0-9]/VERSION/
/^ FILENAME | *[0-9]* [-+]*\$/ b diffstat
/^AUTHOR ([0-9]*):\$/ b shortlog
p
b
: diffstat
n
- / [0-9]* files changed/ {
+ / [0-9]* files* changed/ {
a\\
DIFFSTAT
b
@@ -176,10 +179,7 @@ test_expect_success 'request names an appropriate branch' '
read repository &&
read branch
} <digest &&
- {
- test "$branch" = master ||
- test "$branch" = for-upstream
- }
+ test "$branch" = tags/full
'
@@ -193,8 +193,17 @@ test_expect_success 'pull request format' '
SUBJECT (DATE)
are available in the git repository at:
+
URL BRANCH
+ for you to fetch changes up to OBJECT_NAME:
+
+ SUBJECT (DATE)
+
+ ----------------------------------------------------------------
+ VERSION
+
+ ----------------------------------------------------------------
SHORTLOG
DIFFSTAT
@@ -207,7 +216,7 @@ test_expect_success 'pull request format' '
git request-pull initial "$downstream_url" >../request
) &&
<request sed -nf fuzz.sed >request.fuzzy &&
- test_cmp expect request.fuzzy
+ test_i18ncmp expect request.fuzzy
'
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index 602806d..2e52f8b 100755
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
@@ -13,9 +13,9 @@ TRASH=`pwd`
test_expect_success \
'setup' \
'rm -f .git/index* &&
- perl -e "print \"a\" x 4096;" > a &&
- perl -e "print \"b\" x 4096;" > b &&
- perl -e "print \"c\" x 4096;" > c &&
+ "$PERL_PATH" -e "print \"a\" x 4096;" > a &&
+ "$PERL_PATH" -e "print \"b\" x 4096;" > b &&
+ "$PERL_PATH" -e "print \"c\" x 4096;" > c &&
test-genrandom "seed a" 2097152 > a_big &&
test-genrandom "seed b" 2097152 > b_big &&
git update-index --add a a_big b b_big c &&
@@ -38,6 +38,10 @@ test_expect_success \
'pack without delta' \
'packname_1=$(git pack-objects --window=0 test-1 <obj-list)'
+test_expect_success \
+ 'pack-objects with bogus arguments' \
+ 'test_must_fail git pack-objects --window=0 test-1 blah blah <obj-list'
+
rm -fr .git2
mkdir .git2
@@ -125,7 +129,7 @@ test_expect_success \
cd "$TRASH"
test_expect_success 'compare delta flavors' '
- perl -e '\''
+ "$PERL_PATH" -e '\''
defined($_ = -s $_) or die for @ARGV;
exit 1 if $ARGV[0] <= $ARGV[1];
'\'' test-2-$packname_2.pack test-3-$packname_3.pack
@@ -414,4 +418,9 @@ test_expect_success \
'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg &&
grep "SHA1 COLLISION FOUND" msg'
+test_expect_success \
+ 'make sure index-pack detects the SHA1 collision (large blobs)' \
+ 'test_must_fail git -c core.bigfilethreshold=1 index-pack -o bad.idx test-3.pack 2>msg &&
+ grep "SHA1 COLLISION FOUND" msg'
+
test_done
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh
index b34ea93..fe82025 100755
--- a/t/t5302-pack-index.sh
+++ b/t/t5302-pack-index.sh
@@ -65,6 +65,18 @@ test_expect_success \
'cmp "test-1-${pack1}.idx" "1.idx" &&
cmp "test-2-${pack2}.idx" "2.idx"'
+test_expect_success 'index-pack --verify on index version 1' '
+ git index-pack --verify "test-1-${pack1}.pack"
+'
+
+test_expect_success 'index-pack --verify on index version 2' '
+ git index-pack --verify "test-2-${pack2}.pack"
+'
+
+test_expect_success \
+ 'pack-objects --index-version=2, is not accepted' \
+ 'test_must_fail git pack-objects --index-version=2, test-3 <obj-list'
+
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)'
@@ -93,6 +105,16 @@ test_expect_success OFF64_T \
'64-bit offsets: index-pack result should match pack-objects one' \
'cmp "test-3-${pack3}.idx" "3.idx"'
+test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2 (cheat)' '
+ # This cheats by knowing which lower offset should still be encoded
+ # in 64-bit representation.
+ git index-pack --verify --index-version=2,0x40000 "test-3-${pack3}.pack"
+'
+
+test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2' '
+ git index-pack --verify "test-3-${pack3}.pack"
+'
+
# returns the object number for given object in given pack index
index_obj_nr()
{
@@ -208,9 +230,8 @@ test_expect_success \
( while read obj
do git cat-file -p $obj >/dev/null || exit 1
done <obj-list ) &&
- err=$(test_must_fail git verify-pack \
- ".git/objects/pack/pack-${pack1}.pack" 2>&1) &&
- echo "$err" | grep "CRC mismatch"'
+ test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack"
+'
test_expect_success 'running index-pack in the object store' '
rm -f .git/objects/pack/* &&
diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh
index 5f6cd4f..5b1250f 100755
--- a/t/t5303-pack-corruption-resilience.sh
+++ b/t/t5303-pack-corruption-resilience.sh
@@ -98,7 +98,7 @@ test_expect_success \
'create_new_pack &&
git prune-packed &&
chmod +w ${pack}.pack &&
- perl -i.bak -pe "s/ base /abcdef/" ${pack}.pack &&
+ "$PERL_PATH" -i.bak -pe "s/ base /abcdef/" ${pack}.pack &&
test_must_fail git cat-file blob $blob_1 > /dev/null &&
test_must_fail git cat-file blob $blob_2 > /dev/null &&
test_must_fail git cat-file blob $blob_3 > /dev/null'
@@ -155,7 +155,7 @@ test_expect_success \
'create_new_pack &&
git prune-packed &&
chmod +w ${pack}.pack &&
- perl -i.bak -pe "s/ delta1 /abcdefgh/" ${pack}.pack &&
+ "$PERL_PATH" -i.bak -pe "s/ delta1 /abcdefgh/" ${pack}.pack &&
git cat-file blob $blob_1 > /dev/null &&
test_must_fail git cat-file blob $blob_2 > /dev/null &&
test_must_fail git cat-file blob $blob_3 > /dev/null'
diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh
index d05a913..1753ef2 100755
--- a/t/t5403-post-checkout-hook.sh
+++ b/t/t5403-post-checkout-hook.sh
@@ -31,44 +31,44 @@ EOF
done
test_expect_success 'post-checkout runs as expected ' '
- GIT_DIR=clone1/.git git checkout master &&
- test -e clone1/.git/post-checkout.args
+ GIT_DIR=clone1/.git git checkout master &&
+ test -e clone1/.git/post-checkout.args
'
test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' '
- old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
- new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
- flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
- test $old = $new -a $flag = 1
+ old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
+ new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
+ flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+ test $old = $new -a $flag = 1
'
test_expect_success 'post-checkout runs as expected ' '
- GIT_DIR=clone1/.git git checkout master &&
- test -e clone1/.git/post-checkout.args
+ GIT_DIR=clone1/.git git checkout master &&
+ test -e clone1/.git/post-checkout.args
'
test_expect_success 'post-checkout args are correct with git checkout -b ' '
- GIT_DIR=clone1/.git git checkout -b new1 &&
- old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
- new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
- flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
- test $old = $new -a $flag = 1
+ GIT_DIR=clone1/.git git checkout -b new1 &&
+ old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
+ new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
+ flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+ test $old = $new -a $flag = 1
'
test_expect_success 'post-checkout receives the right args with HEAD changed ' '
- GIT_DIR=clone2/.git git checkout new2 &&
- old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
- new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
- flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
- test $old != $new -a $flag = 1
+ GIT_DIR=clone2/.git git checkout new2 &&
+ old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
+ new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
+ flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+ test $old != $new -a $flag = 1
'
test_expect_success 'post-checkout receives the right args when not switching branches ' '
- GIT_DIR=clone2/.git git checkout master b &&
- old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
- new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
- flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
- test $old = $new -a $flag = 0
+ GIT_DIR=clone2/.git git checkout master b &&
+ old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
+ new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
+ flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+ test $old = $new -a $flag = 0
'
if test "$(git config --bool core.filemode)" = true; then
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index bafcca7..e80a2af 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -97,7 +97,7 @@ test_expect_success 'setup' '
git symbolic-ref HEAD refs/heads/B
'
-pull_to_client 1st "B A" $((11*3))
+pull_to_client 1st "refs/heads/B refs/heads/A" $((11*3))
test_expect_success 'post 1st pull setup' '
add A11 $A10 &&
@@ -110,12 +110,28 @@ test_expect_success 'post 1st pull setup' '
done
'
-pull_to_client 2nd "B" $((64*3))
+pull_to_client 2nd "refs/heads/B" $((64*3))
-pull_to_client 3rd "A" $((1*3))
+pull_to_client 3rd "refs/heads/A" $((1*3))
+
+test_expect_success 'single branch clone' '
+ git clone --single-branch "file://$(pwd)/." singlebranch
+'
+
+test_expect_success 'single branch object count' '
+ GIT_DIR=singlebranch/.git git count-objects -v |
+ grep "^in-pack:" > count.singlebranch &&
+ echo "in-pack: 198" >expected &&
+ test_cmp expected count.singlebranch
+'
+
+test_expect_success 'single given branch clone' '
+ git clone --single-branch --branch A "file://$(pwd)/." branch-a &&
+ test_must_fail git --git-dir=branch-a/.git rev-parse origin/B
+'
test_expect_success 'clone shallow' '
- git clone --depth 2 "file://$(pwd)/." shallow
+ git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
'
test_expect_success 'clone shallow object count' '
@@ -248,4 +264,137 @@ test_expect_success 'clone shallow object count' '
grep "^count: 52" count.shallow
'
+test_expect_success 'clone shallow without --no-single-branch' '
+ git clone --depth 1 "file://$(pwd)/." shallow2
+'
+
+test_expect_success 'clone shallow object count' '
+ (
+ cd shallow2 &&
+ git count-objects -v
+ ) > count.shallow2 &&
+ grep "^in-pack: 6" count.shallow2
+'
+
+test_expect_success 'clone shallow with --branch' '
+ git clone --depth 1 --branch A "file://$(pwd)/." shallow3
+'
+
+test_expect_success 'clone shallow object count' '
+ echo "in-pack: 6" > count3.expected &&
+ GIT_DIR=shallow3/.git git count-objects -v |
+ grep "^in-pack" > count3.actual &&
+ test_cmp count3.expected count3.actual
+'
+
+test_expect_success 'clone shallow with detached HEAD' '
+ git checkout HEAD^ &&
+ git clone --depth 1 "file://$(pwd)/." shallow5 &&
+ git checkout - &&
+ GIT_DIR=shallow5/.git git rev-parse HEAD >actual &&
+ git rev-parse HEAD^ >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'shallow clone pulling tags' '
+ git tag -a -m A TAGA1 A &&
+ git tag -a -m B TAGB1 B &&
+ git tag TAGA2 A &&
+ git tag TAGB2 B &&
+ git clone --depth 1 "file://$(pwd)/." shallow6 &&
+
+ cat >taglist.expected <<\EOF &&
+TAGB1
+TAGB2
+EOF
+ GIT_DIR=shallow6/.git git tag -l >taglist.actual &&
+ test_cmp taglist.expected taglist.actual &&
+
+ echo "in-pack: 7" > count6.expected &&
+ GIT_DIR=shallow6/.git git count-objects -v |
+ grep "^in-pack" > count6.actual &&
+ test_cmp count6.expected count6.actual
+'
+
+test_expect_success 'shallow cloning single tag' '
+ git clone --depth 1 --branch=TAGB1 "file://$(pwd)/." shallow7 &&
+ cat >taglist.expected <<\EOF &&
+TAGB1
+TAGB2
+EOF
+ GIT_DIR=shallow7/.git git tag -l >taglist.actual &&
+ test_cmp taglist.expected taglist.actual &&
+
+ echo "in-pack: 7" > count7.expected &&
+ GIT_DIR=shallow7/.git git count-objects -v |
+ grep "^in-pack" > count7.actual &&
+ test_cmp count7.expected count7.actual
+'
+
+test_expect_success 'setup tests for the --stdin parameter' '
+ for head in C D E F
+ do
+ add $head
+ done &&
+ for head in A B C D E F
+ do
+ git tag $head $head
+ done &&
+ cat >input <<-\EOF
+ refs/heads/C
+ refs/heads/A
+ refs/heads/D
+ refs/tags/C
+ refs/heads/B
+ refs/tags/A
+ refs/heads/E
+ refs/tags/B
+ refs/tags/E
+ refs/tags/D
+ EOF
+ sort <input >expect &&
+ (
+ echo refs/heads/E &&
+ echo refs/tags/E &&
+ cat input
+ ) >input.dup
+'
+
+test_expect_success 'fetch refs from cmdline' '
+ (
+ cd client &&
+ git fetch-pack --no-progress .. $(cat ../input)
+ ) >output &&
+ cut -d " " -f 2 <output | sort >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'fetch refs from stdin' '
+ (
+ cd client &&
+ git fetch-pack --stdin --no-progress .. <../input
+ ) >output &&
+ cut -d " " -f 2 <output | sort >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'fetch mixed refs from cmdline and stdin' '
+ (
+ cd client &&
+ tail -n +5 ../input |
+ git fetch-pack --stdin --no-progress .. $(head -n 4 ../input)
+ ) >output &&
+ cut -d " " -f 2 <output | sort >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'test duplicate refs from stdin' '
+ (
+ cd client &&
+ test_must_fail git fetch-pack --stdin --no-progress .. <../input.dup
+ ) >output &&
+ cut -d " " -f 2 <output | sort >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t5501-fetch-push-alternates.sh b/t/t5501-fetch-push-alternates.sh
index b5ced84..1bc57ac 100755
--- a/t/t5501-fetch-push-alternates.sh
+++ b/t/t5501-fetch-push-alternates.sh
@@ -28,7 +28,7 @@ test_expect_success setup '
done
) &&
(
- git clone --reference=original "file:///$(pwd)/original" one &&
+ git clone --reference=original "file://$(pwd)/original" one &&
cd one &&
echo Z >count &&
git add count &&
diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh
new file mode 100755
index 0000000..35ec294
--- /dev/null
+++ b/t/t5504-fetch-receive-strict.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+
+test_description='fetch/receive strict mode'
+. ./test-lib.sh
+
+test_expect_success setup '
+ echo hello >greetings &&
+ git add greetings &&
+ git commit -m greetings &&
+
+ S=$(git rev-parse :greetings | sed -e "s|^..|&/|") &&
+ X=$(echo bye | git hash-object -w --stdin | sed -e "s|^..|&/|") &&
+ mv -f .git/objects/$X .git/objects/$S &&
+
+ test_must_fail git fsck
+'
+
+test_expect_success 'fetch without strict' '
+ rm -rf dst &&
+ git init dst &&
+ (
+ cd dst &&
+ git config fetch.fsckobjects false &&
+ git config transfer.fsckobjects false &&
+ test_must_fail git fetch ../.git master
+ )
+'
+
+test_expect_success 'fetch with !fetch.fsckobjects' '
+ rm -rf dst &&
+ git init dst &&
+ (
+ cd dst &&
+ git config fetch.fsckobjects false &&
+ git config transfer.fsckobjects true &&
+ test_must_fail git fetch ../.git master
+ )
+'
+
+test_expect_success 'fetch with fetch.fsckobjects' '
+ rm -rf dst &&
+ git init dst &&
+ (
+ cd dst &&
+ git config fetch.fsckobjects true &&
+ git config transfer.fsckobjects false &&
+ test_must_fail git fetch ../.git master
+ )
+'
+
+test_expect_success 'fetch with transfer.fsckobjects' '
+ rm -rf dst &&
+ git init dst &&
+ (
+ cd dst &&
+ git config transfer.fsckobjects true &&
+ test_must_fail git fetch ../.git master
+ )
+'
+
+cat >exp <<EOF
+To dst
+! refs/heads/master:refs/heads/test [remote rejected] (missing necessary objects)
+EOF
+
+test_expect_success 'push without strict' '
+ rm -rf dst &&
+ git init dst &&
+ (
+ cd dst &&
+ git config fetch.fsckobjects false &&
+ git config transfer.fsckobjects false
+ ) &&
+ test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+ test_cmp exp act
+'
+
+test_expect_success 'push with !receive.fsckobjects' '
+ rm -rf dst &&
+ git init dst &&
+ (
+ cd dst &&
+ git config receive.fsckobjects false &&
+ git config transfer.fsckobjects true
+ ) &&
+ test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+ test_cmp exp act
+'
+
+cat >exp <<EOF
+To dst
+! refs/heads/master:refs/heads/test [remote rejected] (n/a (unpacker error))
+EOF
+
+test_expect_success 'push with receive.fsckobjects' '
+ rm -rf dst &&
+ git init dst &&
+ (
+ cd dst &&
+ git config receive.fsckobjects true &&
+ git config transfer.fsckobjects false
+ ) &&
+ test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+ test_cmp exp act
+'
+
+test_expect_success 'push with transfer.fsckobjects' '
+ rm -rf dst &&
+ git init dst &&
+ (
+ cd dst &&
+ git config transfer.fsckobjects true
+ ) &&
+ test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+ test_cmp exp act
+'
+
+test_done
diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh
new file mode 100755
index 0000000..cc0b31f
--- /dev/null
+++ b/t/t5509-fetch-push-namespaces.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+test_description='fetch/push involving ref namespaces'
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_tick &&
+ git init original &&
+ (
+ cd original &&
+ echo 0 >count &&
+ git add count &&
+ test_commit 0 &&
+ echo 1 >count &&
+ git add count &&
+ test_commit 1 &&
+ git remote add pushee-namespaced "ext::git --namespace=namespace %s ../pushee" &&
+ git remote add pushee-unnamespaced ../pushee
+ ) &&
+ commit0=$(cd original && git rev-parse HEAD^) &&
+ commit1=$(cd original && git rev-parse HEAD) &&
+ git init pushee &&
+ git init puller
+'
+
+test_expect_success 'pushing into a repository using a ref namespace' '
+ (
+ cd original &&
+ git push pushee-namespaced master &&
+ git ls-remote pushee-namespaced >actual &&
+ printf "$commit1\trefs/heads/master\n" >expected &&
+ test_cmp expected actual &&
+ git push pushee-namespaced --tags &&
+ git ls-remote pushee-namespaced >actual &&
+ printf "$commit0\trefs/tags/0\n" >>expected &&
+ printf "$commit1\trefs/tags/1\n" >>expected &&
+ test_cmp expected actual &&
+ # Verify that the GIT_NAMESPACE environment variable works as well
+ GIT_NAMESPACE=namespace git ls-remote "ext::git %s ../pushee" >actual &&
+ test_cmp expected actual &&
+ # Verify that --namespace overrides GIT_NAMESPACE
+ GIT_NAMESPACE=garbage git ls-remote pushee-namespaced >actual &&
+ test_cmp expected actual &&
+ # Try a namespace with no content
+ git ls-remote "ext::git --namespace=garbage %s ../pushee" >actual &&
+ test_cmp /dev/null actual &&
+ git ls-remote pushee-unnamespaced >actual &&
+ sed -e "s|refs/|refs/namespaces/namespace/refs/|" expected >expected.unnamespaced &&
+ test_cmp expected.unnamespaced actual
+ )
+'
+
+test_expect_success 'pulling from a repository using a ref namespace' '
+ (
+ cd puller &&
+ git remote add -f pushee-namespaced "ext::git --namespace=namespace %s ../pushee" &&
+ git for-each-ref refs/ >actual &&
+ printf "$commit1 commit\trefs/remotes/pushee-namespaced/master\n" >expected &&
+ printf "$commit0 commit\trefs/tags/0\n" >>expected &&
+ printf "$commit1 commit\trefs/tags/1\n" >>expected &&
+ test_cmp expected actual
+ )
+'
+
+# This test with clone --mirror checks for possible regressions in clone
+# or the machinery underneath it. It ensures that no future change
+# causes clone to ignore refs in refs/namespaces/*. In particular, it
+# protects against a regression caused by any future change to the refs
+# machinery that might cause it to ignore refs outside of refs/heads/*
+# or refs/tags/*. More generally, this test also checks the high-level
+# functionality of using clone --mirror to back up a set of repos hosted
+# in the namespaces of a single repo.
+test_expect_success 'mirroring a repository using a ref namespace' '
+ git clone --mirror pushee mirror &&
+ (
+ cd mirror &&
+ git for-each-ref refs/ >actual &&
+ printf "$commit1 commit\trefs/namespaces/namespace/refs/heads/master\n" >expected &&
+ printf "$commit0 commit\trefs/namespaces/namespace/refs/tags/0\n" >>expected &&
+ printf "$commit1 commit\trefs/namespaces/namespace/refs/tags/1\n" >>expected &&
+ test_cmp expected actual
+ )
+'
+
+test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 7e433b1..d7a19a1 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -14,6 +14,14 @@ test_bundle_object_count () {
test "$2" = $(grep '^[0-9a-f]\{40\} ' verify.out | wc -l)
}
+convert_bundle_to_pack () {
+ while read x && test -n "$x"
+ do
+ :;
+ done
+ cat
+}
+
test_expect_success setup '
echo >file original &&
git add file &&
@@ -70,12 +78,62 @@ test_expect_success "fetch test for-merge" '
master_in_two=`cd ../two && git rev-parse master` &&
one_in_two=`cd ../two && git rev-parse one` &&
{
- echo "$master_in_two not-for-merge"
echo "$one_in_two "
+ echo "$master_in_two not-for-merge"
} >expected &&
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success 'fetch --prune on its own works as expected' '
+ cd "$D" &&
+ git clone . prune &&
+ cd prune &&
+ git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
+
+ git fetch --prune origin &&
+ test_must_fail git rev-parse origin/extrabranch
+'
+
+test_expect_success 'fetch --prune with a branch name keeps branches' '
+ cd "$D" &&
+ git clone . prune-branch &&
+ cd prune-branch &&
+ git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
+
+ git fetch --prune origin master &&
+ git rev-parse origin/extrabranch
+'
+
+test_expect_success 'fetch --prune with a namespace keeps other namespaces' '
+ cd "$D" &&
+ git clone . prune-namespace &&
+ cd prune-namespace &&
+
+ git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
+ git rev-parse origin/master
+'
+
+test_expect_success 'fetch --prune --tags does not delete the remote-tracking branches' '
+ cd "$D" &&
+ git clone . prune-tags &&
+ cd prune-tags &&
+ git fetch origin refs/heads/master:refs/tags/sometag &&
+
+ git fetch --prune --tags origin &&
+ git rev-parse origin/master &&
+ test_must_fail git rev-parse somebranch
+'
+
+test_expect_success 'fetch --prune --tags with branch does not delete other remote-tracking branches' '
+ cd "$D" &&
+ git clone . prune-tags-branch &&
+ cd prune-tags-branch &&
+ git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
+
+ git fetch --prune --tags origin master &&
+ git rev-parse origin/extrabranch
+'
+
test_expect_success 'fetch tags when there is no tags' '
cd "$D" &&
@@ -104,6 +162,36 @@ test_expect_success 'fetch following tags' '
'
+test_expect_success 'fetch uses remote ref names to describe new refs' '
+ cd "$D" &&
+ git init descriptive &&
+ (
+ cd descriptive &&
+ git config remote.o.url .. &&
+ git config remote.o.fetch "refs/heads/*:refs/crazyheads/*" &&
+ git config --add remote.o.fetch "refs/others/*:refs/heads/*" &&
+ git fetch o
+ ) &&
+ git tag -a -m "Descriptive tag" descriptive-tag &&
+ git branch descriptive-branch &&
+ git checkout descriptive-branch &&
+ echo "Nuts" >crazy &&
+ git add crazy &&
+ git commit -a -m "descriptive commit" &&
+ git update-ref refs/others/crazy HEAD &&
+ (
+ cd descriptive &&
+ git fetch o 2>actual &&
+ grep " -> refs/crazyheads/descriptive-branch$" actual |
+ test_i18ngrep "new branch" &&
+ grep " -> descriptive-tag$" actual |
+ test_i18ngrep "new tag" &&
+ grep " -> crazy$" actual |
+ test_i18ngrep "new ref"
+ ) &&
+ git checkout master
+'
+
test_expect_success 'fetch must not resolve short tag name' '
cd "$D" &&
@@ -116,7 +204,7 @@ test_expect_success 'fetch must not resolve short tag name' '
'
-test_expect_success 'fetch must not resolve short remote name' '
+test_expect_success 'fetch can now resolve short remote name' '
cd "$D" &&
git update-ref refs/remotes/six/HEAD HEAD &&
@@ -125,8 +213,7 @@ test_expect_success 'fetch must not resolve short remote name' '
cd six &&
git init &&
- test_must_fail git fetch .. six:six
-
+ git fetch .. six:six
'
test_expect_success 'create bundle 1' '
@@ -157,13 +244,7 @@ test_expect_success 'unbundle 1' '
test_expect_success 'bundle 1 has only 3 files ' '
cd "$D" &&
- (
- while read x && test -n "$x"
- do
- :;
- done
- cat
- ) <bundle1 >bundle.pack &&
+ convert_bundle_to_pack <bundle1 >bundle.pack &&
git index-pack bundle.pack &&
test_bundle_object_count bundle.pack 3
'
@@ -180,13 +261,7 @@ test_expect_success 'bundle does not prerequisite objects' '
git add file2 &&
git commit -m add.file2 file2 &&
git bundle create bundle3 -1 HEAD &&
- (
- while read x && test -n "$x"
- do
- :;
- done
- cat
- ) <bundle3 >bundle.pack &&
+ convert_bundle_to_pack <bundle3 >bundle.pack &&
git index-pack bundle.pack &&
test_bundle_object_count bundle.pack 3
'
@@ -384,14 +459,31 @@ test_expect_success 'fetch --dry-run' '
'
test_expect_success "should be able to fetch with duplicate refspecs" '
- mkdir dups &&
- cd dups &&
- git init &&
- git config branch.master.remote three &&
- git config remote.three.url ../three/.git &&
- git config remote.three.fetch +refs/heads/*:refs/remotes/origin/* &&
- git config --add remote.three.fetch +refs/heads/*:refs/remotes/origin/* &&
- git fetch three
+ mkdir dups &&
+ (
+ cd dups &&
+ git init &&
+ git config branch.master.remote three &&
+ git config remote.three.url ../three/.git &&
+ git config remote.three.fetch +refs/heads/*:refs/remotes/origin/* &&
+ git config --add remote.three.fetch +refs/heads/*:refs/remotes/origin/* &&
+ git fetch three
+ )
+'
+
+test_expect_success 'all boundary commits are excluded' '
+ test_commit base &&
+ test_commit oneside &&
+ git checkout HEAD^ &&
+ test_commit otherside &&
+ git checkout master &&
+ test_tick &&
+ git merge otherside &&
+ ad=$(git log --no-walk --format=%ad HEAD) &&
+ git bundle create twoside-boundary.bdl master --since="$ad" &&
+ convert_bundle_to_pack <twoside-boundary.bdl >twoside-boundary.pack &&
+ pack=$(git index-pack --fix-thin --stdin <twoside-boundary.pack) &&
+ test_bundle_object_count .git/objects/pack/pack-${pack##pack }.pack 3
'
test_done
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 5c546c9..d16e5d3 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -5,7 +5,6 @@ test_description='git ls-remote'
. ./test-lib.sh
test_expect_success setup '
-
>file &&
git add file &&
test_tick &&
@@ -18,45 +17,33 @@ test_expect_success setup '
) >expected.all &&
git remote add self "$(pwd)/.git"
-
'
test_expect_success 'ls-remote --tags .git' '
-
git ls-remote --tags .git >actual &&
test_cmp expected.tag actual
-
'
test_expect_success 'ls-remote .git' '
-
git ls-remote .git >actual &&
test_cmp expected.all actual
-
'
test_expect_success 'ls-remote --tags self' '
-
git ls-remote --tags self >actual &&
test_cmp expected.tag actual
-
'
test_expect_success 'ls-remote self' '
-
git ls-remote self >actual &&
test_cmp expected.all actual
-
'
test_expect_success 'dies when no remote specified and no default remotes found' '
-
test_must_fail git ls-remote
-
'
test_expect_success 'use "origin" when no remote specified' '
-
URL="$(pwd)/.git" &&
echo "From $URL" >exp_err &&
@@ -65,18 +52,14 @@ test_expect_success 'use "origin" when no remote specified' '
test_cmp exp_err actual_err &&
test_cmp expected.all actual
-
'
test_expect_success 'suppress "From <url>" with -q' '
-
git ls-remote -q 2>actual_err &&
test_must_fail test_cmp exp_err actual_err
-
'
test_expect_success 'use branch.<name>.remote if possible' '
-
#
# Test that we are indeed using branch.<name>.remote, not "origin", even
# though the "origin" remote has been set.
@@ -99,28 +82,24 @@ test_expect_success 'use branch.<name>.remote if possible' '
git ls-remote 2>actual_err >actual &&
test_cmp exp_err actual_err &&
test_cmp exp actual
-
'
-cat >exp <<EOF
-fatal: 'refs*master' does not appear to be a git repository
-fatal: The remote end hung up unexpectedly
-EOF
test_expect_success 'confuses pattern as remote when no remote specified' '
- #
- # Do not expect "git ls-remote <pattern>" to work; ls-remote, correctly,
- # confuses <pattern> for <remote>. Although ugly, this behaviour is akin
- # to the confusion of refspecs for remotes by git-fetch and git-push,
- # eg:
- #
- # $ git fetch branch
- #
+ cat >exp <<-\EOF &&
+ fatal: '\''refs*master'\'' does not appear to be a git repository
+ fatal: Could not read from remote repository.
+ Please make sure you have the correct access rights
+ and the repository exists.
+ EOF
+ #
+ # Do not expect "git ls-remote <pattern>" to work; ls-remote needs
+ # <remote> if you want to feed <pattern>, just like you cannot say
+ # fetch <branch>.
# We could just as easily have used "master"; the "*" emphasizes its
# role as a pattern.
test_must_fail git ls-remote refs*master >actual 2>&1 &&
test_cmp exp actual
-
'
test_expect_success 'die with non-2 for wrong repository even with --exit-code' '
diff --git a/t/t5515/fetch.br-branches-default b/t/t5515/fetch.br-branches-default
index 2e0414f..a1bc3d5 100644
--- a/t/t5515/fetch.br-branches-default
+++ b/t/t5515/fetch.br-branches-default
@@ -1,8 +1,8 @@
# br-branches-default
754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default-merge b/t/t5515/fetch.br-branches-default-merge
index ca2cc1d..12ab08e 100644
--- a/t/t5515/fetch.br-branches-default-merge
+++ b/t/t5515/fetch.br-branches-default-merge
@@ -1,9 +1,9 @@
# br-branches-default-merge
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default-merge_branches-default b/t/t5515/fetch.br-branches-default-merge_branches-default
index 7d947cd..5442752 100644
--- a/t/t5515/fetch.br-branches-default-merge_branches-default
+++ b/t/t5515/fetch.br-branches-default-merge_branches-default
@@ -1,9 +1,9 @@
# br-branches-default-merge branches-default
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default-octopus b/t/t5515/fetch.br-branches-default-octopus
index ec39c54..498a761 100644
--- a/t/t5515/fetch.br-branches-default-octopus
+++ b/t/t5515/fetch.br-branches-default-octopus
@@ -1,10 +1,10 @@
# br-branches-default-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default-octopus_branches-default b/t/t5515/fetch.br-branches-default-octopus_branches-default
index 6bf42e2..0857f13 100644
--- a/t/t5515/fetch.br-branches-default-octopus_branches-default
+++ b/t/t5515/fetch.br-branches-default-octopus_branches-default
@@ -1,10 +1,10 @@
# br-branches-default-octopus branches-default
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default_branches-default b/t/t5515/fetch.br-branches-default_branches-default
index 4a2bf3c..8cbd718 100644
--- a/t/t5515/fetch.br-branches-default_branches-default
+++ b/t/t5515/fetch.br-branches-default_branches-default
@@ -1,8 +1,8 @@
# br-branches-default branches-default
754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one b/t/t5515/fetch.br-branches-one
index 12ac8d2..c98f670 100644
--- a/t/t5515/fetch.br-branches-one
+++ b/t/t5515/fetch.br-branches-one
@@ -1,8 +1,8 @@
# br-branches-one
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one-merge b/t/t5515/fetch.br-branches-one-merge
index b4b3b35..54a7742 100644
--- a/t/t5515/fetch.br-branches-one-merge
+++ b/t/t5515/fetch.br-branches-one-merge
@@ -1,9 +1,9 @@
# br-branches-one-merge
-8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one-merge_branches-one b/t/t5515/fetch.br-branches-one-merge_branches-one
index 2ecef38..b4d1bb0 100644
--- a/t/t5515/fetch.br-branches-one-merge_branches-one
+++ b/t/t5515/fetch.br-branches-one-merge_branches-one
@@ -1,9 +1,9 @@
# br-branches-one-merge branches-one
-8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one-octopus b/t/t5515/fetch.br-branches-one-octopus
index 96e3029..97c4b54 100644
--- a/t/t5515/fetch.br-branches-one-octopus
+++ b/t/t5515/fetch.br-branches-one-octopus
@@ -1,9 +1,9 @@
# br-branches-one-octopus
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one-octopus_branches-one b/t/t5515/fetch.br-branches-one-octopus_branches-one
index 55e0bad..df705f7 100644
--- a/t/t5515/fetch.br-branches-one-octopus_branches-one
+++ b/t/t5515/fetch.br-branches-one-octopus_branches-one
@@ -1,9 +1,9 @@
# br-branches-one-octopus branches-one
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one_branches-one b/t/t5515/fetch.br-branches-one_branches-one
index 281fa09..96890e5 100644
--- a/t/t5515/fetch.br-branches-one_branches-one
+++ b/t/t5515/fetch.br-branches-one_branches-one
@@ -1,8 +1,8 @@
# br-branches-one branches-one
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit b/t/t5515/fetch.br-config-explicit
index e2fa9c8..68fc927 100644
--- a/t/t5515/fetch.br-config-explicit
+++ b/t/t5515/fetch.br-config-explicit
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit-merge b/t/t5515/fetch.br-config-explicit-merge
index ec1a723..5ce764a 100644
--- a/t/t5515/fetch.br-config-explicit-merge
+++ b/t/t5515/fetch.br-config-explicit-merge
@@ -1,11 +1,11 @@
# br-config-explicit-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit-merge_config-explicit b/t/t5515/fetch.br-config-explicit-merge_config-explicit
index 54f6891..b1152b7 100644
--- a/t/t5515/fetch.br-config-explicit-merge_config-explicit
+++ b/t/t5515/fetch.br-config-explicit-merge_config-explicit
@@ -1,11 +1,11 @@
# br-config-explicit-merge config-explicit
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit-octopus b/t/t5515/fetch.br-config-explicit-octopus
index 7011dfc..110577b 100644
--- a/t/t5515/fetch.br-config-explicit-octopus
+++ b/t/t5515/fetch.br-config-explicit-octopus
@@ -1,11 +1,11 @@
# br-config-explicit-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit-octopus_config-explicit b/t/t5515/fetch.br-config-explicit-octopus_config-explicit
index bdad51f..a29dd8b 100644
--- a/t/t5515/fetch.br-config-explicit-octopus_config-explicit
+++ b/t/t5515/fetch.br-config-explicit-octopus_config-explicit
@@ -1,11 +1,11 @@
# br-config-explicit-octopus config-explicit
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit_config-explicit b/t/t5515/fetch.br-config-explicit_config-explicit
index 1b237dd..b19b016 100644
--- a/t/t5515/fetch.br-config-explicit_config-explicit
+++ b/t/t5515/fetch.br-config-explicit_config-explicit
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob b/t/t5515/fetch.br-config-glob
index e75ec2f..946d70c 100644
--- a/t/t5515/fetch.br-config-glob
+++ b/t/t5515/fetch.br-config-glob
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob-merge b/t/t5515/fetch.br-config-glob-merge
index ce8f739..89f2596 100644
--- a/t/t5515/fetch.br-config-glob-merge
+++ b/t/t5515/fetch.br-config-glob-merge
@@ -1,11 +1,11 @@
# br-config-glob-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob-merge_config-glob b/t/t5515/fetch.br-config-glob-merge_config-glob
index 5817bed..2ba4832 100644
--- a/t/t5515/fetch.br-config-glob-merge_config-glob
+++ b/t/t5515/fetch.br-config-glob-merge_config-glob
@@ -1,11 +1,11 @@
# br-config-glob-merge config-glob
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob-octopus b/t/t5515/fetch.br-config-glob-octopus
index 938e532..64994df 100644
--- a/t/t5515/fetch.br-config-glob-octopus
+++ b/t/t5515/fetch.br-config-glob-octopus
@@ -1,11 +1,11 @@
# br-config-glob-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob-octopus_config-glob b/t/t5515/fetch.br-config-glob-octopus_config-glob
index c9225bf..681a725 100644
--- a/t/t5515/fetch.br-config-glob-octopus_config-glob
+++ b/t/t5515/fetch.br-config-glob-octopus_config-glob
@@ -1,11 +1,11 @@
# br-config-glob-octopus config-glob
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob_config-glob b/t/t5515/fetch.br-config-glob_config-glob
index a6c20f9..19daf0c 100644
--- a/t/t5515/fetch.br-config-glob_config-glob
+++ b/t/t5515/fetch.br-config-glob_config-glob
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit b/t/t5515/fetch.br-remote-explicit
index 83534d2..ab44bc5 100644
--- a/t/t5515/fetch.br-remote-explicit
+++ b/t/t5515/fetch.br-remote-explicit
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit-merge b/t/t5515/fetch.br-remote-explicit-merge
index a9064dd..d018b35 100644
--- a/t/t5515/fetch.br-remote-explicit-merge
+++ b/t/t5515/fetch.br-remote-explicit-merge
@@ -1,11 +1,11 @@
# br-remote-explicit-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit-merge_remote-explicit b/t/t5515/fetch.br-remote-explicit-merge_remote-explicit
index 732a37e..0d3d780 100644
--- a/t/t5515/fetch.br-remote-explicit-merge_remote-explicit
+++ b/t/t5515/fetch.br-remote-explicit-merge_remote-explicit
@@ -1,11 +1,11 @@
# br-remote-explicit-merge remote-explicit
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit-octopus b/t/t5515/fetch.br-remote-explicit-octopus
index ecf020d..6f84304 100644
--- a/t/t5515/fetch.br-remote-explicit-octopus
+++ b/t/t5515/fetch.br-remote-explicit-octopus
@@ -1,11 +1,11 @@
# br-remote-explicit-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit b/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit
index af77531..3546a83 100644
--- a/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit
+++ b/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit
@@ -1,11 +1,11 @@
# br-remote-explicit-octopus remote-explicit
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit_remote-explicit b/t/t5515/fetch.br-remote-explicit_remote-explicit
index 51fae56..01e014e 100644
--- a/t/t5515/fetch.br-remote-explicit_remote-explicit
+++ b/t/t5515/fetch.br-remote-explicit_remote-explicit
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob b/t/t5515/fetch.br-remote-glob
index 94e6ad3..09bfcee 100644
--- a/t/t5515/fetch.br-remote-glob
+++ b/t/t5515/fetch.br-remote-glob
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob-merge b/t/t5515/fetch.br-remote-glob-merge
index 09362e2..7e1a433 100644
--- a/t/t5515/fetch.br-remote-glob-merge
+++ b/t/t5515/fetch.br-remote-glob-merge
@@ -1,11 +1,11 @@
# br-remote-glob-merge
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob-merge_remote-glob b/t/t5515/fetch.br-remote-glob-merge_remote-glob
index e2eabec..53571bb 100644
--- a/t/t5515/fetch.br-remote-glob-merge_remote-glob
+++ b/t/t5515/fetch.br-remote-glob-merge_remote-glob
@@ -1,11 +1,11 @@
# br-remote-glob-merge remote-glob
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob-octopus b/t/t5515/fetch.br-remote-glob-octopus
index b08e046..c7c8b6d 100644
--- a/t/t5515/fetch.br-remote-glob-octopus
+++ b/t/t5515/fetch.br-remote-glob-octopus
@@ -1,11 +1,11 @@
# br-remote-glob-octopus
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob-octopus_remote-glob b/t/t5515/fetch.br-remote-glob-octopus_remote-glob
index d4d547c..36076fb 100644
--- a/t/t5515/fetch.br-remote-glob-octopus_remote-glob
+++ b/t/t5515/fetch.br-remote-glob-octopus_remote-glob
@@ -1,11 +1,11 @@
# br-remote-glob-octopus remote-glob
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge branch 'master' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob_remote-glob b/t/t5515/fetch.br-remote-glob_remote-glob
index 646dbc8..20ba5cb 100644
--- a/t/t5515/fetch.br-remote-glob_remote-glob
+++ b/t/t5515/fetch.br-remote-glob_remote-glob
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig b/t/t5515/fetch.br-unconfig
index 65ce6d9..887ccfc 100644
--- a/t/t5515/fetch.br-unconfig
+++ b/t/t5515/fetch.br-unconfig
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_--tags_.._.git b/t/t5515/fetch.br-unconfig_--tags_.._.git
index 8258c80..1669cc4 100644
--- a/t/t5515/fetch.br-unconfig_--tags_.._.git
+++ b/t/t5515/fetch.br-unconfig_--tags_.._.git
@@ -1,7 +1,7 @@
# br-unconfig --tags ../.git
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file b/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file
index f02bab2..7411536 100644
--- a/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file
+++ b/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file
@@ -2,7 +2,7 @@
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 tag 'tag-one' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file
index 85de411..7726983 100644
--- a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file
+++ b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file
@@ -1,7 +1,7 @@
# br-unconfig ../.git tag tag-one-tree tag tag-three-file
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three
index 0da2337..7b3750c 100644
--- a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three
+++ b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three
@@ -1,7 +1,7 @@
# br-unconfig ../.git tag tag-one tag tag-three
8e32a6d901327a23ef831511badce7bf3bf46689 tag 'tag-one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b tag 'tag-three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 tag 'tag-three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_branches-default b/t/t5515/fetch.br-unconfig_branches-default
index fc7041e..da30e3c 100644
--- a/t/t5515/fetch.br-unconfig_branches-default
+++ b/t/t5515/fetch.br-unconfig_branches-default
@@ -1,8 +1,8 @@
# br-unconfig branches-default
754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_branches-one b/t/t5515/fetch.br-unconfig_branches-one
index e94cde7..e461431 100644
--- a/t/t5515/fetch.br-unconfig_branches-one
+++ b/t/t5515/fetch.br-unconfig_branches-one
@@ -1,8 +1,8 @@
# br-unconfig branches-one
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_config-explicit b/t/t5515/fetch.br-unconfig_config-explicit
index 01a283e..ed323c9 100644
--- a/t/t5515/fetch.br-unconfig_config-explicit
+++ b/t/t5515/fetch.br-unconfig_config-explicit
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_config-glob b/t/t5515/fetch.br-unconfig_config-glob
index 3a556c5..2372ed0 100644
--- a/t/t5515/fetch.br-unconfig_config-glob
+++ b/t/t5515/fetch.br-unconfig_config-glob
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_remote-explicit b/t/t5515/fetch.br-unconfig_remote-explicit
index db216df..6318dd1 100644
--- a/t/t5515/fetch.br-unconfig_remote-explicit
+++ b/t/t5515/fetch.br-unconfig_remote-explicit
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_remote-glob b/t/t5515/fetch.br-unconfig_remote-glob
index aee65c2..1d9afad 100644
--- a/t/t5515/fetch.br-unconfig_remote-glob
+++ b/t/t5515/fetch.br-unconfig_remote-glob
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master b/t/t5515/fetch.master
index 950fd07..9b29d67 100644
--- a/t/t5515/fetch.master
+++ b/t/t5515/fetch.master
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_--tags_.._.git b/t/t5515/fetch.master_--tags_.._.git
index 0e59950..8a74935 100644
--- a/t/t5515/fetch.master_--tags_.._.git
+++ b/t/t5515/fetch.master_--tags_.._.git
@@ -1,7 +1,7 @@
# master --tags ../.git
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file b/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file
index 8286852..0672d12 100644
--- a/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file
+++ b/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file
@@ -2,7 +2,7 @@
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 tag 'tag-one' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file b/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file
index 2e133ef..0fd737c 100644
--- a/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file
+++ b/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file
@@ -1,7 +1,7 @@
# master ../.git tag tag-one-tree tag tag-three-file
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three b/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three
index 92b18b4..e488986 100644
--- a/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three
+++ b/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three
@@ -1,7 +1,7 @@
# master ../.git tag tag-one tag tag-three
8e32a6d901327a23ef831511badce7bf3bf46689 tag 'tag-one' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b tag 'tag-three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 tag 'tag-three' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_branches-default b/t/t5515/fetch.master_branches-default
index 603d6d2..2eedd3b 100644
--- a/t/t5515/fetch.master_branches-default
+++ b/t/t5515/fetch.master_branches-default
@@ -1,8 +1,8 @@
# master branches-default
754b754407bf032e9a2f9d5a9ad05ca79a6b228f branch 'master' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_branches-one b/t/t5515/fetch.master_branches-one
index fe9bb0b..901ce21 100644
--- a/t/t5515/fetch.master_branches-one
+++ b/t/t5515/fetch.master_branches-one
@@ -1,8 +1,8 @@
# master branches-one
8e32a6d901327a23ef831511badce7bf3bf46689 branch 'one' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_config-explicit b/t/t5515/fetch.master_config-explicit
index 4be97c7..251c826 100644
--- a/t/t5515/fetch.master_config-explicit
+++ b/t/t5515/fetch.master_config-explicit
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_config-glob b/t/t5515/fetch.master_config-glob
index cb0726f..27c158e 100644
--- a/t/t5515/fetch.master_config-glob
+++ b/t/t5515/fetch.master_config-glob
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_remote-explicit b/t/t5515/fetch.master_remote-explicit
index 44a1ca8..b3cfe6b 100644
--- a/t/t5515/fetch.master_remote-explicit
+++ b/t/t5515/fetch.master_remote-explicit
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_remote-glob b/t/t5515/fetch.master_remote-glob
index 724e8db..118befd 100644
--- a/t/t5515/fetch.master_remote-glob
+++ b/t/t5515/fetch.master_remote-glob
@@ -3,9 +3,9 @@
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge branch 'one' of ../
0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge branch 'three' of ../
6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge branch 'two' of ../
-754b754407bf032e9a2f9d5a9ad05ca79a6b228f not-for-merge tag 'tag-master' of ../
+6c9dec2b923228c9ff994c6cfe4ae16c12408dc5 not-for-merge tag 'tag-master' of ../
8e32a6d901327a23ef831511badce7bf3bf46689 not-for-merge tag 'tag-one' of ../
22feea448b023a2d864ef94b013735af34d238ba not-for-merge tag 'tag-one-tree' of ../
-0567da4d5edd2ff4bb292a465ba9e64dcad9536b not-for-merge tag 'tag-three' of ../
+c61a82b60967180544e3c19f819ddbd0c9f89899 not-for-merge tag 'tag-three' of ../
0e3b14047d3ee365f4f2a1b673db059c3972589c not-for-merge tag 'tag-three-file' of ../
-6134ee8f857693b96ff1cc98d3e2fd62b199e5a8 not-for-merge tag 'tag-two' of ../
+525b7fb068d59950d185a8779dc957c77eed73ba not-for-merge tag 'tag-two' of ../
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 3abb290..b5417cc 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -40,6 +40,40 @@ mk_test () {
)
}
+mk_test_with_hooks() {
+ mk_test "$@" &&
+ (
+ cd testrepo &&
+ mkdir .git/hooks &&
+ cd .git/hooks &&
+
+ cat >pre-receive <<-'EOF' &&
+ #!/bin/sh
+ cat - >>pre-receive.actual
+ EOF
+
+ cat >update <<-'EOF' &&
+ #!/bin/sh
+ printf "%s %s %s\n" "$@" >>update.actual
+ EOF
+
+ cat >post-receive <<-'EOF' &&
+ #!/bin/sh
+ cat - >>post-receive.actual
+ EOF
+
+ cat >post-update <<-'EOF' &&
+ #!/bin/sh
+ for ref in "$@"
+ do
+ printf "%s\n" "$ref" >>post-update.actual
+ done
+ EOF
+
+ chmod +x pre-receive update post-receive post-update
+ )
+}
+
mk_child() {
rm -rf "$1" &&
git clone testrepo "$1"
@@ -559,6 +593,169 @@ test_expect_success 'allow deleting an invalid remote ref' '
'
+test_expect_success 'pushing valid refs triggers post-receive and post-update hooks' '
+ mk_test_with_hooks heads/master heads/next &&
+ orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+ newmaster=$(git show-ref -s --verify refs/heads/master) &&
+ orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
+ newnext=$_z40 &&
+ git push testrepo refs/heads/master:refs/heads/master :refs/heads/next &&
+ (
+ cd testrepo/.git &&
+ cat >pre-receive.expect <<-EOF &&
+ $orgmaster $newmaster refs/heads/master
+ $orgnext $newnext refs/heads/next
+ EOF
+
+ cat >update.expect <<-EOF &&
+ refs/heads/master $orgmaster $newmaster
+ refs/heads/next $orgnext $newnext
+ EOF
+
+ cat >post-receive.expect <<-EOF &&
+ $orgmaster $newmaster refs/heads/master
+ $orgnext $newnext refs/heads/next
+ EOF
+
+ cat >post-update.expect <<-EOF &&
+ refs/heads/master
+ refs/heads/next
+ EOF
+
+ test_cmp pre-receive.expect pre-receive.actual &&
+ test_cmp update.expect update.actual &&
+ test_cmp post-receive.expect post-receive.actual &&
+ test_cmp post-update.expect post-update.actual
+ )
+'
+
+test_expect_success 'deleting dangling ref triggers hooks with correct args' '
+ mk_test_with_hooks heads/master &&
+ rm -f testrepo/.git/objects/??/* &&
+ git push testrepo :refs/heads/master &&
+ (
+ cd testrepo/.git &&
+ cat >pre-receive.expect <<-EOF &&
+ $_z40 $_z40 refs/heads/master
+ EOF
+
+ cat >update.expect <<-EOF &&
+ refs/heads/master $_z40 $_z40
+ EOF
+
+ cat >post-receive.expect <<-EOF &&
+ $_z40 $_z40 refs/heads/master
+ EOF
+
+ cat >post-update.expect <<-EOF &&
+ refs/heads/master
+ EOF
+
+ test_cmp pre-receive.expect pre-receive.actual &&
+ test_cmp update.expect update.actual &&
+ test_cmp post-receive.expect post-receive.actual &&
+ test_cmp post-update.expect post-update.actual
+ )
+'
+
+test_expect_success 'deletion of a non-existent ref is not fed to post-receive and post-update hooks' '
+ mk_test_with_hooks heads/master &&
+ orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+ newmaster=$(git show-ref -s --verify refs/heads/master) &&
+ git push testrepo master :refs/heads/nonexistent &&
+ (
+ cd testrepo/.git &&
+ cat >pre-receive.expect <<-EOF &&
+ $orgmaster $newmaster refs/heads/master
+ $_z40 $_z40 refs/heads/nonexistent
+ EOF
+
+ cat >update.expect <<-EOF &&
+ refs/heads/master $orgmaster $newmaster
+ refs/heads/nonexistent $_z40 $_z40
+ EOF
+
+ cat >post-receive.expect <<-EOF &&
+ $orgmaster $newmaster refs/heads/master
+ EOF
+
+ cat >post-update.expect <<-EOF &&
+ refs/heads/master
+ EOF
+
+ test_cmp pre-receive.expect pre-receive.actual &&
+ test_cmp update.expect update.actual &&
+ test_cmp post-receive.expect post-receive.actual &&
+ test_cmp post-update.expect post-update.actual
+ )
+'
+
+test_expect_success 'deletion of a non-existent ref alone does trigger post-receive and post-update hooks' '
+ mk_test_with_hooks heads/master &&
+ git push testrepo :refs/heads/nonexistent &&
+ (
+ cd testrepo/.git &&
+ cat >pre-receive.expect <<-EOF &&
+ $_z40 $_z40 refs/heads/nonexistent
+ EOF
+
+ cat >update.expect <<-EOF &&
+ refs/heads/nonexistent $_z40 $_z40
+ EOF
+
+ test_cmp pre-receive.expect pre-receive.actual &&
+ test_cmp update.expect update.actual &&
+ test_path_is_missing post-receive.actual &&
+ test_path_is_missing post-update.actual
+ )
+'
+
+test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks with correct input' '
+ mk_test_with_hooks heads/master heads/next heads/pu &&
+ orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+ newmaster=$(git show-ref -s --verify refs/heads/master) &&
+ orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
+ newnext=$_z40 &&
+ orgpu=$(cd testrepo && git show-ref -s --verify refs/heads/pu) &&
+ newpu=$(git show-ref -s --verify refs/heads/master) &&
+ git push testrepo refs/heads/master:refs/heads/master \
+ refs/heads/master:refs/heads/pu :refs/heads/next \
+ :refs/heads/nonexistent &&
+ (
+ cd testrepo/.git &&
+ cat >pre-receive.expect <<-EOF &&
+ $orgmaster $newmaster refs/heads/master
+ $orgnext $newnext refs/heads/next
+ $orgpu $newpu refs/heads/pu
+ $_z40 $_z40 refs/heads/nonexistent
+ EOF
+
+ cat >update.expect <<-EOF &&
+ refs/heads/master $orgmaster $newmaster
+ refs/heads/next $orgnext $newnext
+ refs/heads/pu $orgpu $newpu
+ refs/heads/nonexistent $_z40 $_z40
+ EOF
+
+ cat >post-receive.expect <<-EOF &&
+ $orgmaster $newmaster refs/heads/master
+ $orgnext $newnext refs/heads/next
+ $orgpu $newpu refs/heads/pu
+ EOF
+
+ cat >post-update.expect <<-EOF &&
+ refs/heads/master
+ refs/heads/next
+ refs/heads/pu
+ EOF
+
+ test_cmp pre-receive.expect pre-receive.actual &&
+ test_cmp update.expect update.actual &&
+ test_cmp post-receive.expect post-receive.actual &&
+ test_cmp post-update.expect post-update.actual
+ )
+'
+
test_expect_success 'allow deleting a ref using --delete' '
mk_test heads/master &&
(cd testrepo && git config receive.denyDeleteCurrent warn) &&
@@ -782,4 +979,20 @@ test_expect_success 'push --porcelain --dry-run rejected' '
test_cmp .git/foo .git/bar
'
+test_expect_success 'push --prune' '
+ mk_test heads/master heads/second heads/foo heads/bar &&
+ git push --prune testrepo &&
+ check_push_result $the_commit heads/master &&
+ check_push_result $the_first_commit heads/second &&
+ ! check_push_result $the_first_commit heads/foo heads/bar
+'
+
+test_expect_success 'push --prune refspec' '
+ mk_test tmp/master tmp/second tmp/foo tmp/bar &&
+ git push --prune testrepo "refs/heads/*:refs/tmp/*" &&
+ check_push_result $the_commit tmp/master &&
+ check_push_result $the_first_commit tmp/second &&
+ ! check_push_result $the_first_commit tmp/foo tmp/bar
+'
+
test_done
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 0e5eb67..35304b4 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -94,16 +94,35 @@ test_expect_success '--rebase' '
test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
test new = $(git show HEAD:file2)
'
+test_expect_success 'pull.rebase' '
+ git reset --hard before-rebase &&
+ git config --bool pull.rebase true &&
+ test_when_finished "git config --unset pull.rebase" &&
+ git pull . 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 config --bool branch.to-rebase.rebase true &&
+ test_when_finished "git config --unset branch.to-rebase.rebase" &&
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 'branch.to-rebase.rebase should override pull.rebase' '
+ git reset --hard before-rebase &&
+ git config --bool pull.rebase true &&
+ test_when_finished "git config --unset pull.rebase" &&
+ git config --bool branch.to-rebase.rebase false &&
+ test_when_finished "git config --unset branch.to-rebase.rebase" &&
+ git pull . copy &&
+ 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 . &&
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
index c229fe6..3683df1 100755
--- a/t/t5523-push-upstream.sh
+++ b/t/t5523-push-upstream.sh
@@ -101,11 +101,19 @@ test_expect_success TTY 'push -q suppresses progress' '
! grep "Writing objects" err
'
-test_expect_failure TTY 'push --no-progress suppresses progress' '
+test_expect_success TTY 'push --no-progress suppresses progress' '
ensure_fresh_upstream &&
test_terminal git push -u --no-progress upstream master >out 2>err &&
+ ! grep "Unpacking objects" err &&
! grep "Writing objects" err
'
+test_expect_success TTY 'quiet push' '
+ ensure_fresh_upstream &&
+
+ test_terminal git push --quiet --no-progress upstream master 2>&1 | tee output &&
+ test_cmp /dev/null output
+'
+
test_done
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
new file mode 100755
index 0000000..edea9f9
--- /dev/null
+++ b/t/t5527-fetch-odd-refs.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+test_description='test fetching of oddly-named refs'
+. ./test-lib.sh
+
+# afterwards we will have:
+# HEAD - two
+# refs/for/refs/heads/master - one
+# refs/heads/master - three
+test_expect_success 'setup repo with odd suffix ref' '
+ echo content >file &&
+ git add . &&
+ git commit -m one &&
+ git update-ref refs/for/refs/heads/master HEAD &&
+ echo content >>file &&
+ git commit -a -m two &&
+ echo content >>file &&
+ git commit -a -m three &&
+ git checkout HEAD^
+'
+
+test_expect_success 'suffix ref is ignored during fetch' '
+ git clone --bare file://"$PWD" suffix &&
+ echo three >expect &&
+ git --git-dir=suffix log -1 --format=%s refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh
new file mode 100755
index 0000000..4736da8
--- /dev/null
+++ b/t/t5528-push-default.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+
+test_description='check various push.default settings'
+. ./test-lib.sh
+
+test_expect_success 'setup bare remotes' '
+ git init --bare repo1 &&
+ git remote add parent1 repo1 &&
+ git init --bare repo2 &&
+ git remote add parent2 repo2 &&
+ test_commit one &&
+ git push parent1 HEAD &&
+ git push parent2 HEAD
+'
+
+# $1 = local revision
+# $2 = remote revision (tested to be equal to the local one)
+check_pushed_commit () {
+ git log -1 --format='%h %s' "$1" >expect &&
+ git --git-dir=repo1 log -1 --format='%h %s' "$2" >actual &&
+ test_cmp expect actual
+}
+
+# $1 = push.default value
+# $2 = expected target branch for the push
+test_push_success () {
+ git -c push.default="$1" push &&
+ check_pushed_commit HEAD "$2"
+}
+
+# $1 = push.default value
+# check that push fails and does not modify any remote branch
+test_push_failure () {
+ git --git-dir=repo1 log --no-walk --format='%h %s' --all >expect &&
+ test_must_fail git -c push.default="$1" push &&
+ git --git-dir=repo1 log --no-walk --format='%h %s' --all >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success '"upstream" pushes to configured upstream' '
+ git checkout master &&
+ test_config branch.master.remote parent1 &&
+ test_config branch.master.merge refs/heads/foo &&
+ test_commit two &&
+ test_push_success upstream foo
+'
+
+test_expect_success '"upstream" does not push on unconfigured remote' '
+ git checkout master &&
+ test_unconfig branch.master.remote &&
+ test_config push.default upstream &&
+ test_commit three &&
+ test_push_failure upstream
+'
+
+test_expect_success '"upstream" does not push on unconfigured branch' '
+ git checkout master &&
+ test_config branch.master.remote parent1 &&
+ test_unconfig branch.master.merge &&
+ test_config push.default upstream
+ test_commit four &&
+ test_push_failure upstream
+'
+
+test_expect_success '"upstream" does not push when remotes do not match' '
+ git checkout master &&
+ test_config branch.master.remote parent1 &&
+ test_config branch.master.merge refs/heads/foo &&
+ test_config push.default upstream &&
+ test_commit five &&
+ test_must_fail git push parent2
+'
+
+test_expect_success 'push from/to new branch with upstream, matching and simple' '
+ git checkout -b new-branch &&
+ test_push_failure simple &&
+ test_push_failure matching &&
+ test_push_failure upstream
+'
+
+test_expect_success 'push from/to new branch with current creates remote branch' '
+ test_config branch.new-branch.remote repo1 &&
+ git checkout new-branch &&
+ test_push_success current new-branch
+'
+
+test_expect_success 'push to existing branch, with no upstream configured' '
+ test_config branch.master.remote repo1 &&
+ git checkout master &&
+ test_push_failure simple &&
+ test_push_failure upstream
+'
+
+test_expect_success 'push to existing branch, upstream configured with same name' '
+ test_config branch.master.remote repo1 &&
+ test_config branch.master.merge refs/heads/master &&
+ git checkout master &&
+ test_commit six &&
+ test_push_success upstream master &&
+ test_commit seven &&
+ test_push_success simple master
+'
+
+test_expect_success 'push to existing branch, upstream configured with different name' '
+ test_config branch.master.remote repo1 &&
+ test_config branch.master.merge refs/heads/other-name &&
+ git checkout master &&
+ test_commit eight &&
+ test_push_success upstream other-name &&
+ test_commit nine &&
+ test_push_failure simple &&
+ git --git-dir=repo1 log -1 --format="%h %s" "other-name" >expect-other-name &&
+ test_push_success current master &&
+ git --git-dir=repo1 log -1 --format="%h %s" "other-name" >actual-other-name &&
+ test_cmp expect-other-name actual-other-name
+'
+
+test_done
diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh
index faa2e96..1947c28 100755
--- a/t/t5531-deep-submodule-push.sh
+++ b/t/t5531-deep-submodule-push.sh
@@ -32,4 +32,185 @@ test_expect_success push '
)
'
+test_expect_success 'push if submodule has no remote' '
+ (
+ cd work/gar/bage &&
+ >junk2 &&
+ git add junk2 &&
+ git commit -m "Second junk"
+ ) &&
+ (
+ cd work &&
+ git add gar/bage &&
+ git commit -m "Second commit for gar/bage" &&
+ git push --recurse-submodules=check ../pub.git master
+ )
+'
+
+test_expect_success 'push fails if submodule commit not on remote' '
+ (
+ cd work/gar &&
+ git clone --bare bage ../../submodule.git &&
+ cd bage &&
+ git remote add origin ../../../submodule.git &&
+ git fetch &&
+ >junk3 &&
+ git add junk3 &&
+ git commit -m "Third junk"
+ ) &&
+ (
+ cd work &&
+ git add gar/bage &&
+ git commit -m "Third commit for gar/bage" &&
+ test_must_fail git push --recurse-submodules=check ../pub.git master
+ )
+'
+
+test_expect_success 'push succeeds after commit was pushed to remote' '
+ (
+ cd work/gar/bage &&
+ git push origin master
+ ) &&
+ (
+ cd work &&
+ git push --recurse-submodules=check ../pub.git master
+ )
+'
+
+test_expect_success 'push fails when commit on multiple branches if one branch has no remote' '
+ (
+ cd work/gar/bage &&
+ >junk4 &&
+ git add junk4 &&
+ git commit -m "Fourth junk"
+ ) &&
+ (
+ cd work &&
+ git branch branch2 &&
+ git add gar/bage &&
+ git commit -m "Fourth commit for gar/bage" &&
+ git checkout branch2 &&
+ (
+ cd gar/bage &&
+ git checkout HEAD~1
+ ) &&
+ >junk1 &&
+ git add junk1 &&
+ git commit -m "First junk" &&
+ test_must_fail git push --recurse-submodules=check ../pub.git
+ )
+'
+
+test_expect_success 'push succeeds if submodule has no remote and is on the first superproject commit' '
+ git init --bare a
+ git clone a a1 &&
+ (
+ cd a1 &&
+ git init b
+ (
+ cd b &&
+ >junk &&
+ git add junk &&
+ git commit -m "initial"
+ ) &&
+ git add b &&
+ git commit -m "added submodule" &&
+ git push --recurse-submodule=check origin master
+ )
+'
+
+test_expect_success 'push unpushed submodules when not needed' '
+ (
+ cd work &&
+ (
+ cd gar/bage &&
+ git checkout master &&
+ >junk5 &&
+ git add junk5 &&
+ git commit -m "Fifth junk" &&
+ git push &&
+ git rev-parse origin/master >../../../expected
+ ) &&
+ git checkout master &&
+ git add gar/bage &&
+ git commit -m "Fifth commit for gar/bage" &&
+ git push --recurse-submodules=on-demand ../pub.git master
+ ) &&
+ (
+ cd submodule.git &&
+ git rev-parse master >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'push unpushed submodules when not needed 2' '
+ (
+ cd submodule.git &&
+ git rev-parse master >../expected
+ ) &&
+ (
+ cd work &&
+ (
+ cd gar/bage &&
+ >junk6 &&
+ git add junk6 &&
+ git commit -m "Sixth junk"
+ ) &&
+ >junk2 &&
+ git add junk2 &&
+ git commit -m "Second junk for work" &&
+ git push --recurse-submodules=on-demand ../pub.git master
+ ) &&
+ (
+ cd submodule.git &&
+ git rev-parse master >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'push unpushed submodules recursively' '
+ (
+ cd work &&
+ (
+ cd gar/bage &&
+ git checkout master &&
+ > junk7 &&
+ git add junk7 &&
+ git commit -m "Seventh junk" &&
+ git rev-parse master >../../../expected
+ ) &&
+ git checkout master &&
+ git add gar/bage &&
+ git commit -m "Seventh commit for gar/bage" &&
+ git push --recurse-submodules=on-demand ../pub.git master
+ ) &&
+ (
+ cd submodule.git &&
+ git rev-parse master >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'push unpushable submodule recursively fails' '
+ (
+ cd work &&
+ (
+ cd gar/bage &&
+ git rev-parse origin/master >../../../expected &&
+ git checkout master~0 &&
+ > junk8 &&
+ git add junk8 &&
+ git commit -m "Eighth junk"
+ ) &&
+ git add gar/bage &&
+ git commit -m "Eighth commit for gar/bage" &&
+ test_must_fail git push --recurse-submodules=on-demand ../pub.git master
+ ) &&
+ (
+ cd submodule.git &&
+ git rev-parse master >../actual
+ ) &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh
index 62f2460..5531bd1 100755
--- a/t/t5532-fetch-proxy.sh
+++ b/t/t5532-fetch-proxy.sh
@@ -15,7 +15,7 @@ test_expect_success 'setup remote repo' '
cat >proxy <<'EOF'
#!/bin/sh
echo >&2 "proxying for $*"
-cmd=`perl -e '
+cmd=`"$PERL_PATH" -e '
read(STDIN, $buf, 4);
my $n = hex($buf) - 4;
read(STDIN, $buf, $n);
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index a266ca5..1eea647 100755
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
@@ -40,6 +40,22 @@ test_expect_success 'setup remote repository' '
mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
'
+test_expect_success 'create password-protected repository' '
+ mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb" &&
+ cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
+ "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git"
+'
+
+test_expect_success 'setup askpass helper' '
+ cat >askpass <<-\EOF &&
+ #!/bin/sh
+ echo user@host
+ EOF
+ chmod +x askpass &&
+ GIT_ASKPASS="$PWD/askpass" &&
+ export GIT_ASKPASS
+'
+
test_expect_success 'clone remote repository' '
cd "$ROOT_PATH" &&
git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone
@@ -132,14 +148,36 @@ x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
x40="$x38$x2"
test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
- sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log |
- grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] "
+ sed \
+ -e "s/PUT /OP /" \
+ -e "s/MOVE /OP /" \
+ -e "s|/objects/$x2/${x38}_$x40|WANTED_PATH_REQUEST|" \
+ "$HTTPD_ROOT_PATH"/access.log |
+ grep -e "\"OP .*WANTED_PATH_REQUEST HTTP/[.0-9]*\" 20[0-9] "
'
test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
"$ROOT_PATH"/test_repo_clone master
+test_expect_success 'push to password-protected repository (user in URL)' '
+ test_commit pw-user &&
+ git push "$HTTPD_URL_USER/auth/dumb/test_repo.git" HEAD &&
+ git rev-parse --verify HEAD >expect &&
+ git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
+ rev-parse --verify HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_failure 'push to password-protected repository (no user in URL)' '
+ test_commit pw-nouser &&
+ git push "$HTTPD_URL/auth/dumb/test_repo.git" HEAD &&
+ git rev-parse --verify HEAD >expect &&
+ git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
+ rev-parse --verify HEAD >actual &&
+ test_cmp expect actual
+'
+
stop_httpd
test_done
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
index a73c826..312e484 100755
--- a/t/t5541-http-push.sh
+++ b/t/t5541-http-push.sh
@@ -14,6 +14,7 @@ fi
ROOT_PATH="$PWD"
LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'}
. "$TEST_DIRECTORY"/lib-httpd.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
start_httpd
test_expect_success 'setup remote repository' '
@@ -29,6 +30,7 @@ test_expect_success 'setup remote repository' '
git clone --bare test_repo test_repo.git &&
cd test_repo.git &&
git config http.receivepack true &&
+ git config core.logallrefupdates true &&
ORIG_HEAD=$(git rev-parse --verify HEAD) &&
cd - &&
mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
@@ -95,6 +97,32 @@ test_expect_success 'create and delete remote branch' '
test_must_fail git show-ref --verify refs/remotes/origin/dev
'
+cat >"$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update" <<EOF
+#!/bin/sh
+exit 1
+EOF
+chmod a+x "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update"
+
+cat >exp <<EOF
+remote: error: hook declined to update refs/heads/dev2
+To http://127.0.0.1:$LIB_HTTPD_PORT/smart/test_repo.git
+ ! [remote rejected] dev2 -> dev2 (hook declined)
+error: failed to push some refs to 'http://127.0.0.1:$LIB_HTTPD_PORT/smart/test_repo.git'
+EOF
+
+test_expect_success 'rejected update prints status' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ git checkout -b dev2 &&
+ : >path4 &&
+ git add path4 &&
+ test_tick &&
+ git commit -m dev2 &&
+ test_must_fail git push origin dev2 2>act &&
+ sed -e "/^remote: /s/ *$//" <act >cmp &&
+ test_cmp exp cmp
+'
+rm -f "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update"
+
cat >exp <<EOF
GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
@@ -106,6 +134,8 @@ GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
EOF
test_expect_success 'used receive-pack service' '
sed -e "
@@ -138,7 +168,7 @@ test_expect_success 'push fails for non-fast-forward refs unmatched by remote he
'
test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: our output' '
- test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" \
+ test_i18ngrep "Updates were rejected because" \
output
'
@@ -154,5 +184,87 @@ test_expect_success 'push (chunked)' '
test $HEAD = $(git rev-parse --verify HEAD))
'
+test_expect_success 'push --all can push to empty repo' '
+ d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
+ git init --bare "$d" &&
+ git --git-dir="$d" config http.receivepack true &&
+ git push --all "$HTTPD_URL"/smart/empty-all.git
+'
+
+test_expect_success 'push --mirror can push to empty repo' '
+ d=$HTTPD_DOCUMENT_ROOT_PATH/empty-mirror.git &&
+ git init --bare "$d" &&
+ git --git-dir="$d" config http.receivepack true &&
+ git push --mirror "$HTTPD_URL"/smart/empty-mirror.git
+'
+
+test_expect_success 'push --all to repo with alternates' '
+ s=$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git &&
+ d=$HTTPD_DOCUMENT_ROOT_PATH/alternates-all.git &&
+ git clone --bare --shared "$s" "$d" &&
+ git --git-dir="$d" config http.receivepack true &&
+ git --git-dir="$d" repack -adl &&
+ git push --all "$HTTPD_URL"/smart/alternates-all.git
+'
+
+test_expect_success 'push --mirror to repo with alternates' '
+ s=$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git &&
+ d=$HTTPD_DOCUMENT_ROOT_PATH/alternates-mirror.git &&
+ git clone --bare --shared "$s" "$d" &&
+ git --git-dir="$d" config http.receivepack true &&
+ git --git-dir="$d" repack -adl &&
+ git push --mirror "$HTTPD_URL"/smart/alternates-mirror.git
+'
+
+test_expect_success TTY 'push shows progress when stderr is a tty' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ test_commit noisy &&
+ test_terminal git push >output 2>&1 &&
+ grep "^Writing objects" output
+'
+
+test_expect_success TTY 'push --quiet silences status and progress' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ test_commit quiet &&
+ test_terminal git push --quiet >output 2>&1 &&
+ test_cmp /dev/null output
+'
+
+test_expect_success TTY 'push --no-progress silences progress but not status' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ test_commit no-progress &&
+ test_terminal git push --no-progress >output 2>&1 &&
+ grep "^To http" output &&
+ ! grep "^Writing objects"
+'
+
+test_expect_success 'push --progress shows progress to non-tty' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ test_commit progress &&
+ git push --progress >output 2>&1 &&
+ grep "^To http" output &&
+ grep "^Writing objects" output
+'
+
+test_expect_success 'http push gives sane defaults to reflog' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ test_commit reflog-test &&
+ git push "$HTTPD_URL"/smart/test_repo.git &&
+ git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
+ log -g -1 --format="%gn <%ge>" >actual &&
+ echo "anonymous <anonymous@http.127.0.0.1>" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'http push respects GIT_COMMITTER_* in reflog' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ test_commit custom-reflog-test &&
+ git push "$HTTPD_URL"/smart_custom_env/test_repo.git &&
+ git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
+ log -g -1 --format="%gn <%ge>" >actual &&
+ echo "Custom User <custom@example.com>" >expect &&
+ test_cmp expect actual
+'
+
stop_httpd
test_done
diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
index a1883ca..b06f817 100755
--- a/t/t5550-http-fetch.sh
+++ b/t/t5550-http-fetch.sh
@@ -8,22 +8,27 @@ if test -n "$NO_CURL"; then
test_done
fi
-. "$TEST_DIRECTORY"/lib-httpd.sh
LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
test_expect_success 'setup repository' '
- echo content >file &&
+ echo content1 >file &&
git add file &&
git commit -m one
+ echo content2 >file &&
+ git add file &&
+ git commit -m two
'
-test_expect_success 'create http-accessible bare repository' '
- mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+test_expect_success 'create http-accessible bare repository with loose objects' '
+ cp -a .git "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git --bare init &&
+ git config core.bare true &&
+ mkdir -p hooks &&
echo "exec git update-server-info" >hooks/post-update &&
- chmod +x hooks/post-update
+ chmod +x hooks/post-update &&
+ hooks/post-update
) &&
git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
git push public master:master
@@ -35,11 +40,98 @@ test_expect_success 'clone http repository' '
test_cmp file clone/file
'
-test_expect_success 'clone http repository with authentication' '
+test_expect_success 'create password-protected repository' '
mkdir "$HTTPD_DOCUMENT_ROOT_PATH/auth/" &&
- cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git" &&
- git clone $AUTH_HTTPD_URL/auth/repo.git clone-auth &&
- test_cmp file clone-auth/file
+ cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
+ "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git"
+'
+
+test_expect_success 'setup askpass helpers' '
+ cat >askpass <<-EOF &&
+ #!/bin/sh
+ echo >>"$PWD/askpass-query" "askpass: \$*" &&
+ cat "$PWD/askpass-response"
+ EOF
+ chmod +x askpass &&
+ GIT_ASKPASS="$PWD/askpass" &&
+ export GIT_ASKPASS
+'
+
+expect_askpass() {
+ dest=$HTTPD_DEST
+ {
+ case "$1" in
+ none)
+ ;;
+ pass)
+ echo "askpass: Password for 'http://$2@$dest': "
+ ;;
+ both)
+ echo "askpass: Username for 'http://$dest': "
+ echo "askpass: Password for 'http://$2@$dest': "
+ ;;
+ *)
+ false
+ ;;
+ esac
+ } >askpass-expect &&
+ test_cmp askpass-expect askpass-query
+}
+
+test_expect_success 'cloning password-protected repository can fail' '
+ >askpass-query &&
+ echo wrong >askpass-response &&
+ test_must_fail git clone "$HTTPD_URL/auth/repo.git" clone-auth-fail &&
+ expect_askpass both wrong
+'
+
+test_expect_success 'http auth can use user/pass in URL' '
+ >askpass-query &&
+ echo wrong >askpass-response &&
+ git clone "$HTTPD_URL_USER_PASS/auth/repo.git" clone-auth-none &&
+ expect_askpass none
+'
+
+test_expect_success 'http auth can use just user in URL' '
+ >askpass-query &&
+ echo user@host >askpass-response &&
+ git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-pass &&
+ expect_askpass pass user@host
+'
+
+test_expect_success 'http auth can request both user and pass' '
+ >askpass-query &&
+ echo user@host >askpass-response &&
+ git clone "$HTTPD_URL/auth/repo.git" clone-auth-both &&
+ expect_askpass both user@host
+'
+
+test_expect_success 'http auth respects credential helper config' '
+ test_config_global credential.helper "!f() {
+ cat >/dev/null
+ echo username=user@host
+ echo password=user@host
+ }; f" &&
+ >askpass-query &&
+ echo wrong >askpass-response &&
+ git clone "$HTTPD_URL/auth/repo.git" clone-auth-helper &&
+ expect_askpass none
+'
+
+test_expect_success 'http auth can get username from config' '
+ test_config_global "credential.$HTTPD_URL.username" user@host &&
+ >askpass-query &&
+ echo user@host >askpass-response &&
+ git clone "$HTTPD_URL/auth/repo.git" clone-auth-user &&
+ expect_askpass pass user@host
+'
+
+test_expect_success 'configured username does not override URL' '
+ test_config_global "credential.$HTTPD_URL.username" wrong &&
+ >askpass-query &&
+ echo user@host >askpass-response &&
+ git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-user2 &&
+ expect_askpass pass user@host
'
test_expect_success 'fetch changes via http' '
@@ -75,8 +167,7 @@ test_expect_success 'http remote detects correct HEAD' '
test_expect_success 'fetch packed objects' '
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
- git --bare repack &&
- git --bare prune-packed
+ git --bare repack -a -d
) &&
git clone $HTTPD_URL/dumb/repo_pack.git
'
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
index 26d3557..fadf2f2 100755
--- a/t/t5551-http-fetch.sh
+++ b/t/t5551-http-fetch.sh
@@ -109,5 +109,36 @@ test_expect_success 'follow redirects (302)' '
git clone $HTTPD_URL/smart-redir-temp/repo.git --quiet repo-t
'
+test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE
+
+test_expect_success EXPENSIVE 'create 50,000 tags in the repo' '
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ for i in `seq 50000`
+ do
+ echo "commit refs/heads/too-many-refs"
+ echo "mark :$i"
+ echo "committer git <git@example.com> $i +0000"
+ echo "data 0"
+ echo "M 644 inline bla.txt"
+ echo "data 4"
+ echo "bla"
+ # make every commit dangling by always
+ # rewinding the branch after each commit
+ echo "reset refs/heads/too-many-refs"
+ echo "from :1"
+ done | git fast-import --export-marks=marks &&
+
+ # now assign tags to all the dangling commits we created above
+ tag=$("$PERL_PATH" -e "print \"bla\" x 30") &&
+ sed -e "s/^:\(.\+\) \(.\+\)$/\2 refs\/tags\/$tag-\1/" <marks >>packed-refs
+ )
+'
+
+test_expect_success EXPENSIVE 'clone the 50,000 tag repo to check OS command line overflow' '
+ git clone $HTTPD_URL/smart/repo.git too-many-refs 2>err &&
+ test_line_count = 0 err
+'
+
stop_httpd
test_done
diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh
index 0ad7ce0..ef98d95 100755
--- a/t/t5560-http-backend-noserver.sh
+++ b/t/t5560-http-backend-noserver.sh
@@ -17,7 +17,7 @@ run_backend() {
GET() {
REQUEST_METHOD="GET" && export REQUEST_METHOD &&
run_backend "/repo.git/$1" &&
- unset REQUEST_METHOD &&
+ sane_unset REQUEST_METHOD &&
if ! grep "Status" act.out >act
then
printf "Status: 200 OK\r\n" >act
@@ -30,8 +30,8 @@ POST() {
REQUEST_METHOD="POST" && export REQUEST_METHOD &&
CONTENT_TYPE="application/x-$1-request" && export CONTENT_TYPE &&
run_backend "/repo.git/$1" "$2" &&
- unset REQUEST_METHOD &&
- unset CONTENT_TYPE &&
+ sane_unset REQUEST_METHOD &&
+ sane_unset CONTENT_TYPE &&
if ! grep "Status" act.out >act
then
printf "Status: 200 OK\r\n" >act
diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh
new file mode 100755
index 0000000..a3a4e47
--- /dev/null
+++ b/t/t5570-git-daemon.sh
@@ -0,0 +1,146 @@
+#!/bin/sh
+
+test_description='test fetching over git protocol'
+. ./test-lib.sh
+
+LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-5570}
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon
+
+test_expect_success 'setup repository' '
+ echo content >file &&
+ git add file &&
+ git commit -m one
+'
+
+test_expect_success 'create git-accessible bare repository' '
+ mkdir "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+ (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+ git --bare init &&
+ : >git-daemon-export-ok
+ ) &&
+ git remote add public "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+ git push public master:master
+'
+
+test_expect_success 'clone git repository' '
+ git clone "$GIT_DAEMON_URL/repo.git" clone &&
+ test_cmp file clone/file
+'
+
+test_expect_success 'fetch changes via git protocol' '
+ echo content >>file &&
+ git commit -a -m two &&
+ git push public &&
+ (cd clone && git pull) &&
+ test_cmp file clone/file
+'
+
+test_expect_failure 'remote detects correct HEAD' '
+ git push public master:other &&
+ (cd clone &&
+ git remote set-head -d origin &&
+ git remote set-head -a origin &&
+ git symbolic-ref refs/remotes/origin/HEAD > output &&
+ echo refs/remotes/origin/master > expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'prepare pack objects' '
+ cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git &&
+ (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git &&
+ git --bare repack -a -d
+ )
+'
+
+test_expect_success 'fetch notices corrupt pack' '
+ cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
+ (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
+ p=`ls objects/pack/pack-*.pack` &&
+ chmod u+w $p &&
+ printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
+ ) &&
+ mkdir repo_bad1.git &&
+ (cd repo_bad1.git &&
+ git --bare init &&
+ test_must_fail git --bare fetch "$GIT_DAEMON_URL/repo_bad1.git" &&
+ test 0 = `ls objects/pack/pack-*.pack | wc -l`
+ )
+'
+
+test_expect_success 'fetch notices corrupt idx' '
+ cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
+ (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
+ p=`ls objects/pack/pack-*.idx` &&
+ chmod u+w $p &&
+ printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
+ ) &&
+ mkdir repo_bad2.git &&
+ (cd repo_bad2.git &&
+ git --bare init &&
+ test_must_fail git --bare fetch "$GIT_DAEMON_URL/repo_bad2.git" &&
+ test 0 = `ls objects/pack | wc -l`
+ )
+'
+
+test_remote_error()
+{
+ do_export=YesPlease
+ while test $# -gt 0
+ do
+ case $1 in
+ -x)
+ shift
+ chmod -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git"
+ ;;
+ -n)
+ shift
+ do_export=
+ ;;
+ *)
+ break
+ esac
+ done
+
+ msg=$1
+ shift
+ cmd=$1
+ shift
+ repo=$1
+ shift || error "invalid number of arguments"
+
+ if test -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo"
+ then
+ if test -n "$do_export"
+ then
+ : >"$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo/git-daemon-export-ok"
+ else
+ rm -f "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo/git-daemon-export-ok"
+ fi
+ fi
+
+ test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" "$@" 2>output &&
+ echo "fatal: remote error: $msg: /$repo" >expect &&
+ test_cmp expect output
+ ret=$?
+ chmod +x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git"
+ (exit $ret)
+}
+
+msg="access denied or repository not exported"
+test_expect_success 'clone non-existent' "test_remote_error '$msg' clone nowhere.git "
+test_expect_success 'push disabled' "test_remote_error '$msg' push repo.git master"
+test_expect_success 'read access denied' "test_remote_error -x '$msg' fetch repo.git "
+test_expect_success 'not exported' "test_remote_error -n '$msg' fetch repo.git "
+
+stop_git_daemon
+start_git_daemon --informative-errors
+
+test_expect_success 'clone non-existent' "test_remote_error 'no such repository' clone nowhere.git "
+test_expect_success 'push disabled' "test_remote_error 'service not enabled' push repo.git master"
+test_expect_success 'read access denied' "test_remote_error -x 'no such repository' fetch repo.git "
+test_expect_success 'not exported' "test_remote_error -n 'repository not exported' fetch repo.git "
+
+stop_git_daemon
+test_done
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index e810314..67869b4 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -9,10 +9,13 @@ test_expect_success setup '
rm -fr .git &&
test_create_repo src &&
(
- cd src
- >file
- git add file
- git commit -m initial
+ cd src &&
+ >file &&
+ git add file &&
+ git commit -m initial &&
+ echo 1 >file &&
+ git add file &&
+ git commit -m updated
)
'
@@ -88,6 +91,26 @@ test_expect_success 'clone --mirror' '
'
+test_expect_success 'clone --mirror with detached HEAD' '
+
+ ( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+ git clone --mirror src mirror.detached &&
+ ( cd src && git checkout - ) &&
+ GIT_DIR=mirror.detached git rev-parse HEAD >actual &&
+ test_cmp expected actual
+
+'
+
+test_expect_success 'clone --bare with detached HEAD' '
+
+ ( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+ git clone --bare src bare.detached &&
+ ( cd src && git checkout - ) &&
+ GIT_DIR=bare.detached git rev-parse HEAD >actual &&
+ test_cmp expected actual
+
+'
+
test_expect_success 'clone --bare names the local repository <name>.git' '
git clone --bare src &&
@@ -206,6 +229,20 @@ test_expect_success 'clone from .git file' '
git clone dst/.git dst2
'
+test_expect_success 'fetch from .git gitfile' '
+ (
+ cd dst2 &&
+ git fetch ../dst/.git
+ )
+'
+
+test_expect_success 'fetch from gitfile parent' '
+ (
+ cd dst2 &&
+ git fetch ../dst
+ )
+'
+
test_expect_success 'clone separate gitdir where target already exists' '
rm -rf dst &&
test_must_fail git clone --separate-git-dir realgitdir src dst
@@ -234,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' '
grep /src/\\.git/objects target-10/objects/info/alternates
'
+test_expect_success 'clone checking out a tag' '
+ git clone --branch=some-tag src dst.tag &&
+ GIT_DIR=src/.git git rev-parse some-tag >expected &&
+ test_cmp expected dst.tag/.git/HEAD &&
+ GIT_DIR=dst.tag/.git git config remote.origin.fetch >fetch.actual &&
+ echo "+refs/heads/*:refs/remotes/origin/*" >fetch.expected &&
+ test_cmp fetch.expected fetch.actual
+'
+
test_done
diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh
index 895f559..c47d450 100755
--- a/t/t5700-clone-reference.sh
+++ b/t/t5700-clone-reference.sh
@@ -34,7 +34,7 @@ test_expect_success 'cloning with reference (-l -s)' \
cd "$base_dir"
test_expect_success 'existence of info/alternates' \
-'test `wc -l <C/.git/objects/info/alternates` = 2'
+'test_line_count = 2 C/.git/objects/info/alternates'
cd "$base_dir"
@@ -52,18 +52,18 @@ test_cmp expected current'
cd "$base_dir"
-rm -f "$U"
+rm -f "$U.D"
test_expect_success 'cloning with reference (no -l -s)' \
-'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U"'
+'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"'
test_expect_success 'fetched no objects' \
-'! grep "^want" "$U"'
+'! grep "^want" "$U.D"'
cd "$base_dir"
test_expect_success 'existence of info/alternates' \
-'test `wc -l <D/.git/objects/info/alternates` = 1'
+'test_line_count = 1 D/.git/objects/info/alternates'
cd "$base_dir"
@@ -146,4 +146,39 @@ test_expect_success 'cloning with reference being subset of source (-l -s)' \
cd "$base_dir"
+test_expect_success 'clone with reference from a tagged repository' '
+ (
+ cd A && git tag -a -m 'tagged' HEAD
+ ) &&
+ git clone --reference=A A I
+'
+
+test_expect_success 'prepare branched repository' '
+ git clone A J &&
+ (
+ cd J &&
+ git checkout -b other master^ &&
+ echo other >otherfile &&
+ git add otherfile &&
+ git commit -m other &&
+ git checkout master
+ )
+'
+
+rm -f "$U.K"
+
+test_expect_success 'fetch with incomplete alternates' '
+ git init K &&
+ echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
+ (
+ cd K &&
+ git remote add J "file://$base_dir/J" &&
+ GIT_DEBUG_SEND_PACK=3 git fetch J 3>"$U.K"
+ ) &&
+ master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
+ ! grep "^want $master_object" "$U.K" &&
+ tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) &&
+ ! grep "^want $tag_object" "$U.K"
+'
+
test_done
diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
index 6972258..7ff6e0e 100755
--- a/t/t5701-clone-local.sh
+++ b/t/t5701-clone-local.sh
@@ -3,7 +3,10 @@
test_description='test local clone'
. ./test-lib.sh
-D=`pwd`
+repo_is_hardlinked() {
+ find "$1/objects" -type f -links 1 >output &&
+ test_line_count = 0 output
+}
test_expect_success 'preparing origin repository' '
: >file && git add . && git commit -m1 &&
@@ -19,105 +22,72 @@ test_expect_success 'preparing origin repository' '
'
test_expect_success 'local clone without .git suffix' '
- cd "$D" &&
git clone -l -s a b &&
- cd b &&
+ (cd b &&
test "$(GIT_CONFIG=.git/config git config --bool core.bare)" = false &&
- git fetch
+ git fetch)
'
test_expect_success 'local clone with .git suffix' '
- cd "$D" &&
git clone -l -s a.git c &&
- cd c &&
- git fetch
+ (cd c && git fetch)
'
test_expect_success 'local clone from x' '
- cd "$D" &&
git clone -l -s x y &&
- cd y &&
- git fetch
+ (cd y && git fetch)
'
test_expect_success 'local clone from x.git that does not exist' '
- cd "$D" &&
- if git clone -l -s x.git z
- then
- echo "Oops, should have failed"
- false
- else
- echo happy
- fi
+ test_must_fail git clone -l -s x.git z
'
test_expect_success 'With -no-hardlinks, local will make a copy' '
- cd "$D" &&
git clone --bare --no-hardlinks x w &&
- cd w &&
- linked=$(find objects -type f ! -links 1 | wc -l) &&
- test 0 = $linked
+ ! repo_is_hardlinked w
'
test_expect_success 'Even without -l, local will make a hardlink' '
- cd "$D" &&
rm -fr w &&
git clone -l --bare x w &&
- cd w &&
- copied=$(find objects -type f -links 1 | wc -l) &&
- test 0 = $copied
+ repo_is_hardlinked w
'
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 &&
+ (cd d &&
git fetch &&
- test ! -e .git/refs/remotes/origin/HEAD'
+ test ! -e .git/refs/remotes/origin/HEAD)
+'
test_expect_success 'bundle clone without .bundle suffix' '
- cd "$D" &&
git clone dir/b3 &&
- cd b3 &&
- git fetch
+ (cd b3 && git fetch)
'
test_expect_success 'bundle clone with .bundle suffix' '
- cd "$D" &&
git clone b1.bundle &&
- cd b1 &&
- git fetch
+ (cd b1 && git fetch)
'
test_expect_success 'bundle clone from b4' '
- cd "$D" &&
git clone b4 bdl &&
- cd bdl &&
- git fetch
+ (cd bdl && git fetch)
'
test_expect_success 'bundle clone from b4.bundle that does not exist' '
- cd "$D" &&
- if git clone b4.bundle bb
- then
- echo "Oops, should have failed"
- false
- else
- echo happy
- fi
+ test_must_fail git clone b4.bundle bb
'
test_expect_success 'bundle clone with nonexistent HEAD' '
- cd "$D" &&
git clone b2.bundle b2 &&
- cd b2 &&
+ (cd b2 &&
git fetch &&
- test ! -e .git/refs/heads/master
+ test_must_fail git rev-parse --verify refs/heads/master)
'
test_expect_success 'clone empty repository' '
- cd "$D" &&
mkdir empty &&
(cd empty &&
git init &&
@@ -135,7 +105,6 @@ test_expect_success 'clone empty repository' '
'
test_expect_success 'clone empty repository, and then push should not segfault.' '
- cd "$D" &&
rm -fr empty/ empty-clone/ &&
mkdir empty &&
(cd empty && git init) &&
@@ -145,16 +114,24 @@ test_expect_success 'clone empty repository, and then push should not segfault.'
'
test_expect_success 'cloning non-existent directory fails' '
- cd "$D" &&
rm -rf does-not-exist &&
test_must_fail git clone does-not-exist
'
test_expect_success 'cloning non-git directory fails' '
- cd "$D" &&
rm -rf not-a-git-repo not-a-git-repo-clone &&
mkdir not-a-git-repo &&
test_must_fail git clone not-a-git-repo not-a-git-repo-clone
'
+test_expect_success 'cloning file:// does not hardlink' '
+ git clone --bare file://"$(pwd)"/a non-local &&
+ ! repo_is_hardlinked non-local
+'
+
+test_expect_success 'cloning a local path with --no-local does not hardlink' '
+ git clone --bare --no-local a force-nonlocal &&
+ ! repo_is_hardlinked force-nonlocal
+'
+
test_done
diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh
index 728ccd8..9e43731 100755
--- a/t/t5704-bundle.sh
+++ b/t/t5704-bundle.sh
@@ -4,53 +4,58 @@ test_description='some bundle related tests'
. ./test-lib.sh
test_expect_success 'setup' '
-
- : > file &&
- git add file &&
- test_tick &&
- git commit -m initial &&
+ test_commit initial &&
test_tick &&
git tag -m tag tag &&
- : > file2 &&
- git add file2 &&
- : > file3 &&
- test_tick &&
- git commit -m second &&
- git add file3 &&
- test_tick &&
- git commit -m third
-
+ test_commit second &&
+ test_commit third &&
+ git tag -d initial &&
+ git tag -d second &&
+ git tag -d third
'
test_expect_success 'tags can be excluded by rev-list options' '
-
git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
git ls-remote bundle > output &&
! grep tag output
-
'
test_expect_success 'die if bundle file cannot be created' '
-
mkdir adir &&
test_must_fail git bundle create adir --all
-
'
test_expect_failure 'bundle --stdin' '
-
echo master | git bundle create stdin-bundle.bdl --stdin &&
git ls-remote stdin-bundle.bdl >output &&
grep master output
-
'
test_expect_failure 'bundle --stdin <rev-list options>' '
-
echo master | git bundle create hybrid-bundle.bdl --stdin tag &&
git ls-remote hybrid-bundle.bdl >output &&
grep master output
+'
+
+test_expect_success 'empty bundle file is rejected' '
+ : >empty-bundle &&
+ test_must_fail git fetch empty-bundle
+'
+# This triggers a bug in older versions where the resulting line (with
+# --pretty=oneline) was longer than a 1024-char buffer.
+test_expect_success 'ridiculously long subject in boundary' '
+ : >file4 &&
+ test_tick &&
+ git add file4 &&
+ printf "%01200d\n" 0 | git commit -F - &&
+ test_commit fifth &&
+ git bundle create long-subject-bundle.bdl HEAD^..HEAD &&
+ git bundle list-heads long-subject-bundle.bdl >heads &&
+ test -s heads &&
+ git fetch long-subject-bundle.bdl &&
+ sed -n "/^-/{p;q;}" long-subject-bundle.bdl >boundary &&
+ grep "^-[0-9a-f]\\{40\\} " boundary
'
test_done
diff --git a/t/t5706-clone-branch.sh b/t/t5706-clone-branch.sh
index f3f9a76..56be67e 100755
--- a/t/t5706-clone-branch.sh
+++ b/t/t5706-clone-branch.sh
@@ -57,12 +57,8 @@ test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
)
'
-test_expect_success 'clone -b with bogus branch chooses HEAD' '
- git clone -b bogus parent clone-bogus &&
- (cd clone-bogus &&
- check_HEAD master &&
- check_file one
- )
+test_expect_success 'clone -b with bogus branch' '
+ test_must_fail git clone -b bogus parent clone-bogus
'
test_done
diff --git a/t/t5707-clone-detached.sh b/t/t5707-clone-detached.sh
new file mode 100755
index 0000000..8b0d607
--- /dev/null
+++ b/t/t5707-clone-detached.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+test_description='test cloning a repository with detached HEAD'
+. ./test-lib.sh
+
+head_is_detached() {
+ git --git-dir=$1/.git rev-parse --verify HEAD &&
+ test_must_fail git --git-dir=$1/.git symbolic-ref HEAD
+}
+
+test_expect_success 'setup' '
+ echo one >file &&
+ git add file &&
+ git commit -m one &&
+ echo two >file &&
+ git commit -a -m two &&
+ git tag two &&
+ echo three >file &&
+ git commit -a -m three
+'
+
+test_expect_success 'clone repo (detached HEAD points to branch)' '
+ git checkout master^0 &&
+ git clone "file://$PWD" detached-branch
+'
+test_expect_success 'cloned HEAD matches' '
+ echo three >expect &&
+ git --git-dir=detached-branch/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_failure 'cloned HEAD is detached' '
+ head_is_detached detached-branch
+'
+
+test_expect_success 'clone repo (detached HEAD points to tag)' '
+ git checkout two^0 &&
+ git clone "file://$PWD" detached-tag
+'
+test_expect_success 'cloned HEAD matches' '
+ echo two >expect &&
+ git --git-dir=detached-tag/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-tag
+'
+
+test_expect_success 'clone repo (detached HEAD points to history)' '
+ git checkout two^ &&
+ git clone "file://$PWD" detached-history
+'
+test_expect_success 'cloned HEAD matches' '
+ echo one >expect &&
+ git --git-dir=detached-history/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-history
+'
+
+test_expect_success 'clone repo (orphan detached HEAD)' '
+ git checkout master^0 &&
+ echo four >file &&
+ git commit -a -m four &&
+ git clone "file://$PWD" detached-orphan
+'
+test_expect_success 'cloned HEAD matches' '
+ echo four >expect &&
+ git --git-dir=detached-orphan/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-orphan
+'
+
+test_done
diff --git a/t/t5708-clone-config.sh b/t/t5708-clone-config.sh
new file mode 100755
index 0000000..27d730c
--- /dev/null
+++ b/t/t5708-clone-config.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description='tests for git clone -c key=value'
+. ./test-lib.sh
+
+test_expect_success 'clone -c sets config in cloned repo' '
+ rm -rf child &&
+ git clone -c core.foo=bar . child &&
+ echo bar >expect &&
+ git --git-dir=child/.git config core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c can set multi-keys' '
+ rm -rf child &&
+ git clone -c core.foo=bar -c core.foo=baz . child &&
+ { echo bar; echo baz; } >expect &&
+ git --git-dir=child/.git config --get-all core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c without a value is boolean true' '
+ rm -rf child &&
+ git clone -c core.foo . child &&
+ echo true >expect &&
+ git --git-dir=child/.git config --bool core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c config is available during clone' '
+ echo content >file &&
+ git add file &&
+ git commit -m one &&
+ rm -rf child &&
+ git clone -c core.autocrlf . child &&
+ printf "content\\r\\n" >expect &&
+ test_cmp expect child/file
+'
+
+test_done
diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh
index ef7127c..aa04529 100755
--- a/t/t5710-info-alternate.sh
+++ b/t/t5710-info-alternate.sh
@@ -18,7 +18,7 @@ reachable_via() {
test_valid_repo() {
git fsck --full > fsck.log &&
- test `wc -l < fsck.log` = 0
+ test_line_count = 0 fsck.log
}
base_dir=`pwd`
diff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh
index 1fb6380..5702334 100755
--- a/t/t5800-remote-helpers.sh
+++ b/t/t5800-remote-helpers.sh
@@ -7,17 +7,27 @@ test_description='Test remote-helper import and export commands'
. ./test-lib.sh
-if test_have_prereq PYTHON && "$PYTHON_PATH" -c '
+if ! test_have_prereq PYTHON ; then
+ skip_all='skipping git-remote-hg tests, python not available'
+ test_done
+fi
+
+"$PYTHON_PATH" -c '
import sys
if sys.hexversion < 0x02040000:
sys.exit(1)
-'
-then
- # Requires Python 2.4 or newer
- test_set_prereq PYTHON_24
-fi
+' || {
+ skip_all='skipping git-remote-hg tests, python version < 2.4'
+ test_done
+}
-test_expect_success PYTHON_24 'setup repository' '
+compare_refs() {
+ git --git-dir="$1/.git" rev-parse --verify $2 >expect &&
+ git --git-dir="$3/.git" rev-parse --verify $4 >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'setup repository' '
git init --bare server/.git &&
git clone server public &&
(cd public &&
@@ -27,54 +37,112 @@ test_expect_success PYTHON_24 'setup repository' '
git push origin master)
'
-test_expect_success PYTHON_24 'cloning from local repo' '
+test_expect_success 'cloning from local repo' '
git clone "testgit::${PWD}/server" localclone &&
test_cmp public/file localclone/file
'
-test_expect_success PYTHON_24 'cloning from remote repo' '
+test_expect_success 'cloning from remote repo' '
git clone "testgit::file://${PWD}/server" clone &&
test_cmp public/file clone/file
'
-test_expect_success PYTHON_24 'create new commit on remote' '
+test_expect_success 'create new commit on remote' '
(cd public &&
echo content >>file &&
git commit -a -m two &&
git push)
'
-test_expect_success PYTHON_24 'pulling from local repo' '
+test_expect_success 'pulling from local repo' '
(cd localclone && git pull) &&
test_cmp public/file localclone/file
'
-test_expect_success PYTHON_24 'pulling from remote remote' '
+test_expect_success 'pulling from remote remote' '
(cd clone && git pull) &&
test_cmp public/file clone/file
'
-test_expect_success PYTHON_24 'pushing to local repo' '
+test_expect_success 'pushing to local repo' '
(cd localclone &&
echo content >>file &&
git commit -a -m three &&
git push) &&
- HEAD=$(git --git-dir=localclone/.git rev-parse --verify HEAD) &&
- test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
+ compare_refs localclone HEAD server HEAD
+'
+
+# Generally, skip this test. It demonstrates a now-fixed race in
+# git-remote-testgit, but is too slow to leave in for general use.
+: test_expect_success 'racily pushing to local repo' '
+ test_when_finished "rm -rf server2 localclone2" &&
+ cp -a server server2 &&
+ git clone "testgit::${PWD}/server2" localclone2 &&
+ (cd localclone2 &&
+ echo content >>file &&
+ git commit -a -m three &&
+ GIT_REMOTE_TESTGIT_SLEEPY=2 git push) &&
+ compare_refs localclone2 HEAD server2 HEAD
'
-test_expect_success PYTHON_24 'synch with changes from localclone' '
+test_expect_success 'synch with changes from localclone' '
(cd clone &&
git pull)
'
-test_expect_success PYTHON_24 'pushing remote local repo' '
+test_expect_success 'pushing remote local repo' '
(cd clone &&
echo content >>file &&
git commit -a -m four &&
git push) &&
- HEAD=$(git --git-dir=clone/.git rev-parse --verify HEAD) &&
- test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
+ compare_refs clone HEAD server HEAD
+'
+
+test_expect_success 'fetch new branch' '
+ (cd public &&
+ git checkout -b new &&
+ echo content >>file &&
+ git commit -a -m five &&
+ git push origin new
+ ) &&
+ (cd localclone &&
+ git fetch origin new
+ ) &&
+ compare_refs public HEAD localclone FETCH_HEAD
+'
+
+test_expect_success 'fetch multiple branches' '
+ (cd localclone &&
+ git fetch
+ ) &&
+ compare_refs server master localclone refs/remotes/origin/master &&
+ compare_refs server new localclone refs/remotes/origin/new
+'
+
+test_expect_success 'push when remote has extra refs' '
+ (cd clone &&
+ echo content >>file &&
+ git commit -a -m six &&
+ git push
+ ) &&
+ compare_refs clone master server master
+'
+
+test_expect_success 'push new branch by name' '
+ (cd clone &&
+ git checkout -b new-name &&
+ echo content >>file &&
+ git commit -a -m seven &&
+ git push origin new-name
+ ) &&
+ compare_refs clone HEAD server refs/heads/new-name
+'
+
+test_expect_failure 'push new branch with old:new refspec' '
+ (cd clone &&
+ git push origin new-name:new-refspec
+ ) &&
+ compare_refs clone HEAD server refs/heads/new-refspec
'
test_done
diff --git a/t/t5900-repo-selection.sh b/t/t5900-repo-selection.sh
new file mode 100755
index 0000000..3d5b418
--- /dev/null
+++ b/t/t5900-repo-selection.sh
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='selecting remote repo in ambiguous cases'
+. ./test-lib.sh
+
+reset() {
+ rm -rf foo foo.git fetch clone
+}
+
+make_tree() {
+ git init "$1" &&
+ (cd "$1" && test_commit "$1")
+}
+
+make_bare() {
+ git init --bare "$1" &&
+ (cd "$1" &&
+ tree=`git hash-object -w -t tree /dev/null` &&
+ commit=$(echo "$1" | git commit-tree $tree) &&
+ git update-ref HEAD $commit
+ )
+}
+
+get() {
+ git init --bare fetch &&
+ (cd fetch && git fetch "../$1") &&
+ git clone "$1" clone
+}
+
+check() {
+ echo "$1" >expect &&
+ (cd fetch && git log -1 --format=%s FETCH_HEAD) >actual.fetch &&
+ (cd clone && git log -1 --format=%s HEAD) >actual.clone &&
+ test_cmp expect actual.fetch &&
+ test_cmp expect actual.clone
+}
+
+test_expect_success 'find .git dir in worktree' '
+ reset &&
+ make_tree foo &&
+ get foo &&
+ check foo
+'
+
+test_expect_success 'automagically add .git suffix' '
+ reset &&
+ make_bare foo.git &&
+ get foo &&
+ check foo.git
+'
+
+test_expect_success 'automagically add .git suffix to worktree' '
+ reset &&
+ make_tree foo.git &&
+ get foo &&
+ check foo.git
+'
+
+test_expect_success 'prefer worktree foo over bare foo.git' '
+ reset &&
+ make_tree foo &&
+ make_bare foo.git &&
+ get foo &&
+ check foo
+'
+
+test_expect_success 'prefer bare foo over bare foo.git' '
+ reset &&
+ make_bare foo &&
+ make_bare foo.git &&
+ get foo &&
+ check foo
+'
+
+test_expect_success 'disambiguate with full foo.git' '
+ reset &&
+ make_bare foo &&
+ make_bare foo.git &&
+ get foo.git &&
+ check foo.git
+'
+
+test_expect_success 'we are not fooled by non-git foo directory' '
+ reset &&
+ make_bare foo.git &&
+ mkdir foo &&
+ get foo &&
+ check foo.git
+'
+
+test_expect_success 'prefer inner .git over outer bare' '
+ reset &&
+ make_tree foo &&
+ make_bare foo.git &&
+ mv foo/.git foo.git &&
+ get foo.git &&
+ check foo
+'
+
+test_done
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
index d918cc0..f94f0c4 100755
--- a/t/t6006-rev-list-format.sh
+++ b/t/t6006-rev-list-format.sh
@@ -188,23 +188,23 @@ test_expect_success 'empty email' '
test_expect_success 'del LF before empty (1)' '
git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD^^ >actual &&
- test $(wc -l <actual) = 2
+ test_line_count = 2 actual
'
test_expect_success 'del LF before empty (2)' '
git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD >actual &&
- test $(wc -l <actual) = 6 &&
+ test_line_count = 6 actual &&
grep "^$" actual
'
test_expect_success 'add LF before non-empty (1)' '
git show -s --pretty=format:"%s%+b%nThanks%n" HEAD^^ >actual &&
- test $(wc -l <actual) = 2
+ test_line_count = 2 actual
'
test_expect_success 'add LF before non-empty (2)' '
git show -s --pretty=format:"%s%+b%nThanks%n" HEAD >actual &&
- test $(wc -l <actual) = 6 &&
+ test_line_count = 6 actual &&
grep "^$" actual
'
@@ -267,13 +267,27 @@ test_expect_success '%gd shortens ref name' '
test_cmp expect.gd-short actual.gd-short
'
+test_expect_success 'reflog identity' '
+ echo "C O Mitter:committer@example.com" >expect &&
+ git log -g -1 --format="%gn:%ge" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'oneline with empty message' '
git commit -m "dummy" --allow-empty &&
git commit -m "dummy" --allow-empty &&
git filter-branch --msg-filter "sed -e s/dummy//" HEAD^^.. &&
git rev-list --oneline HEAD >test.txt &&
- test $(git rev-list --oneline HEAD | wc -l) -eq 5 &&
- test $(git rev-list --oneline --graph HEAD | wc -l) -eq 5
+ test_line_count = 5 test.txt &&
+ git rev-list --oneline --graph HEAD >testg.txt &&
+ test_line_count = 5 testg.txt
+'
+
+test_expect_success 'single-character name is parsed correctly' '
+ git commit --author="a <a@example.com>" --allow-empty -m foo &&
+ echo "a <a@example.com>" >expect &&
+ git log -1 --format="%an <%ae>" >actual &&
+ test_cmp expect actual
'
test_done
diff --git a/t/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh
index e51eb41..bbb0581 100755
--- a/t/t6011-rev-list-with-bad-commit.sh
+++ b/t/t6011-rev-list-with-bad-commit.sh
@@ -37,7 +37,7 @@ test_expect_success 'verify number of revisions' \
test_expect_success 'corrupt second commit object' \
'
- perl -i.bak -pe "s/second commit/socond commit/" .git/objects/pack/*.pack &&
+ "$PERL_PATH" -i.bak -pe "s/second commit/socond commit/" .git/objects/pack/*.pack &&
test_must_fail git fsck --full
'
diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh
index af34a1e..839ad97 100755
--- a/t/t6012-rev-list-simplify.sh
+++ b/t/t6012-rev-list-simplify.sh
@@ -86,5 +86,6 @@ check_result 'I H E C B A' --full-history --date-order -- file
check_result 'I E C B A' --simplify-merges -- file
check_result 'I B A' -- file
check_result 'I B A' --topo-order -- file
+check_result 'H' --first-parent -- another-file
test_done
diff --git a/t/t6013-rev-list-reverse-parents.sh b/t/t6013-rev-list-reverse-parents.sh
index 59fc2f0..892a537 100755
--- a/t/t6013-rev-list-reverse-parents.sh
+++ b/t/t6013-rev-list-reverse-parents.sh
@@ -25,7 +25,7 @@ test_expect_success 'set up --reverse example' '
test_expect_success '--reverse --parents --full-history combines correctly' '
git rev-list --parents --full-history master -- foo |
- perl -e "print reverse <>" > expected &&
+ "$PERL_PATH" -e "print reverse <>" > expected &&
git rev-list --reverse --parents --full-history master -- foo \
> actual &&
test_cmp actual expected
@@ -33,7 +33,7 @@ test_expect_success '--reverse --parents --full-history combines correctly' '
test_expect_success '--boundary does too' '
git rev-list --boundary --parents --full-history master ^root -- foo |
- perl -e "print reverse <>" > expected &&
+ "$PERL_PATH" -e "print reverse <>" > expected &&
git rev-list --boundary --reverse --parents --full-history \
master ^root -- foo > actual &&
test_cmp actual expected
diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh
index 7641029..39b4cb0 100755
--- a/t/t6019-rev-list-ancestry-path.sh
+++ b/t/t6019-rev-list-ancestry-path.sh
@@ -70,4 +70,42 @@ test_expect_success 'rev-list --ancestry-patch D..M -- M.t' '
test_cmp expect actual
'
+# b---bc
+# / \ /
+# a X
+# \ / \
+# c---cb
+#
+# All refnames prefixed with 'x' to avoid confusion with the tags
+# generated by test_commit on case-insensitive systems.
+test_expect_success 'setup criss-cross' '
+ mkdir criss-cross &&
+ (cd criss-cross &&
+ git init &&
+ test_commit A &&
+ git checkout -b xb master &&
+ test_commit B &&
+ git checkout -b xc master &&
+ test_commit C &&
+ git checkout -b xbc xb -- &&
+ git merge xc &&
+ git checkout -b xcb xc -- &&
+ git merge xb &&
+ git checkout master)
+'
+
+# no commits in bc descend from cb
+test_expect_success 'criss-cross: rev-list --ancestry-path cb..bc' '
+ (cd criss-cross &&
+ git rev-list --ancestry-path xcb..xbc > actual &&
+ test -z "$(cat actual)")
+'
+
+# no commits in repository descend from cb
+test_expect_success 'criss-cross: rev-list --ancestry-path --all ^cb' '
+ (cd criss-cross &&
+ git rev-list --ancestry-path --all ^xcb > actual &&
+ test -z "$(cat actual)")
+'
+
test_done
diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh
index eec8f4e..27c3d73 100755
--- a/t/t6020-merge-df.sh
+++ b/t/t6020-merge-df.sh
@@ -59,15 +59,19 @@ test_expect_success 'setup modify/delete + directory/file conflict' '
git add letters &&
git commit -m initial &&
+ # Throw in letters.txt for sorting order fun
+ # ("letters.txt" sorts between "letters" and "letters/file")
echo i >>letters &&
- git add letters &&
+ echo "version 2" >letters.txt &&
+ git add letters letters.txt &&
git commit -m modified &&
git checkout -b delete HEAD^ &&
git rm letters &&
mkdir letters &&
>letters/file &&
- git add letters &&
+ echo "version 1" >letters.txt &&
+ git add letters letters.txt &&
git commit -m deleted
'
@@ -75,25 +79,31 @@ test_expect_success 'modify/delete + directory/file conflict' '
git checkout delete^0 &&
test_must_fail git merge modify &&
- test 3 = $(git ls-files -s | wc -l) &&
- test 2 = $(git ls-files -u | wc -l) &&
- test 1 = $(git ls-files -o | wc -l) &&
+ test 5 -eq $(git ls-files -s | wc -l) &&
+ test 4 -eq $(git ls-files -u | wc -l) &&
+ test 1 -eq $(git ls-files -o | wc -l) &&
test -f letters/file &&
+ test -f letters.txt &&
test -f letters~modify
'
test_expect_success 'modify/delete + directory/file conflict; other way' '
+ # Yes, we really need the double reset since "letters" appears as
+ # both a file and a directory.
+ git reset --hard &&
git reset --hard &&
git clean -f &&
git checkout modify^0 &&
+
test_must_fail git merge delete &&
- test 3 = $(git ls-files -s | wc -l) &&
- test 2 = $(git ls-files -u | wc -l) &&
- test 1 = $(git ls-files -o | wc -l) &&
+ test 5 -eq $(git ls-files -s | wc -l) &&
+ test 4 -eq $(git ls-files -u | wc -l) &&
+ test 1 -eq $(git ls-files -o | wc -l) &&
test -f letters/file &&
+ test -f letters.txt &&
test -f letters~HEAD
'
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index 1ed259d..1104249 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -252,6 +252,7 @@ test_expect_success 'setup for rename + d/f conflicts' '
git reset --hard &&
git checkout --orphan dir-in-way &&
git rm -rf . &&
+ git clean -fdqx &&
mkdir sub &&
mkdir dir &&
@@ -302,11 +303,11 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '
git checkout -q renamed-file-has-no-conflicts^0 &&
test_must_fail git merge --strategy=recursive dir-in-way >output &&
- grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+ grep "CONFLICT (modify/delete): dir/file-in-the-way" output &&
grep "Auto-merging dir" output &&
grep "Adding as dir~HEAD instead" output &&
- test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 3 -eq "$(git ls-files -u | wc -l)" &&
test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
test_must_fail git diff --quiet &&
@@ -324,11 +325,11 @@ test_expect_success 'Same as previous, but merged other way' '
test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors &&
! grep "error: refusing to lose untracked file at" errors &&
- grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+ grep "CONFLICT (modify/delete): dir/file-in-the-way" output &&
grep "Auto-merging dir" output &&
grep "Adding as dir~renamed-file-has-no-conflicts instead" output &&
- test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 3 -eq "$(git ls-files -u | wc -l)" &&
test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
test_must_fail git diff --quiet &&
@@ -350,11 +351,11 @@ cat >expected <<\EOF &&
8
9
10
-<<<<<<< HEAD
+<<<<<<< HEAD:dir
12
=======
11
->>>>>>> dir-not-in-way
+>>>>>>> dir-not-in-way:sub/file
EOF
test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '
@@ -404,11 +405,11 @@ cat >expected <<\EOF &&
8
9
10
-<<<<<<< HEAD
+<<<<<<< HEAD:sub/file
11
=======
12
->>>>>>> renamed-file-has-conflicts
+>>>>>>> renamed-file-has-conflicts:dir
EOF
test_expect_success 'Same as previous, but merged other way' '
@@ -609,4 +610,294 @@ test_expect_success 'check handling of differently renamed file with D/F conflic
! test -f original
'
+test_expect_success 'setup avoid unnecessary update, normal rename' '
+ git reset --hard &&
+ git checkout --orphan avoid-unnecessary-update-1 &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git mv original rename &&
+ echo 11 >>rename &&
+ git add -u &&
+ git commit -m "Renamed and modified" &&
+
+ git checkout -b merge-branch-1 HEAD~1 &&
+ echo "random content" >random-file &&
+ git add -A &&
+ git commit -m "Random, unrelated changes"
+'
+
+test_expect_success 'avoid unnecessary update, normal rename' '
+ git checkout -q avoid-unnecessary-update-1^0 &&
+ test-chmtime =1000000000 rename &&
+ test-chmtime -v +0 rename >expect &&
+ git merge merge-branch-1 &&
+ test-chmtime -v +0 rename >actual &&
+ test_cmp expect actual # "rename" should have stayed intact
+'
+
+test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' '
+ git reset --hard &&
+ git checkout --orphan avoid-unnecessary-update-2 &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ mkdir df &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git mv df/file temp &&
+ rm -rf df &&
+ git mv temp df &&
+ echo 11 >>df &&
+ git add -u &&
+ git commit -m "Renamed and modified" &&
+
+ git checkout -b merge-branch-2 HEAD~1 &&
+ >unrelated-change &&
+ git add unrelated-change &&
+ git commit -m "Only unrelated changes"
+'
+
+test_expect_success 'avoid unnecessary update, with D/F conflict' '
+ git checkout -q avoid-unnecessary-update-2^0 &&
+ test-chmtime =1000000000 df &&
+ test-chmtime -v +0 df >expect &&
+ git merge merge-branch-2 &&
+ test-chmtime -v +0 df >actual &&
+ test_cmp expect actual # "df" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ >irrelevant &&
+ mkdir df &&
+ >df/file &&
+ git add -A &&
+ git commit -mA &&
+
+ git checkout -b side
+ git rm -rf df &&
+ git commit -mB &&
+
+ git checkout master &&
+ git rm -rf df &&
+ echo bla >df &&
+ git add -A &&
+ git commit -m "Add a newfile"
+'
+
+test_expect_success 'avoid unnecessary update, dir->(file,nothing)' '
+ git checkout -q master^0 &&
+ test-chmtime =1000000000 df &&
+ test-chmtime -v +0 df >expect &&
+ git merge side &&
+ test-chmtime -v +0 df >actual &&
+ test_cmp expect actual # "df" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, modify/delete' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ >irrelevant &&
+ >file &&
+ git add -A &&
+ git commit -mA &&
+
+ git checkout -b side
+ git rm -f file &&
+ git commit -m "Delete file" &&
+
+ git checkout master &&
+ echo bla >file &&
+ git add -A &&
+ git commit -m "Modify file"
+'
+
+test_expect_success 'avoid unnecessary update, modify/delete' '
+ git checkout -q master^0 &&
+ test-chmtime =1000000000 file &&
+ test-chmtime -v +0 file >expect &&
+ test_must_fail git merge side &&
+ test-chmtime -v +0 file >actual &&
+ test_cmp expect actual # "file" should have stayed intact
+'
+
+test_expect_success 'setup avoid unnecessary update, rename/add-dest' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n6\n7\n8\n" >file &&
+ git add -A &&
+ git commit -mA &&
+
+ git checkout -b side
+ cp file newfile &&
+ git add -A &&
+ git commit -m "Add file copy" &&
+
+ git checkout master &&
+ git mv file newfile &&
+ git commit -m "Rename file"
+'
+
+test_expect_success 'avoid unnecessary update, rename/add-dest' '
+ git checkout -q master^0 &&
+ test-chmtime =1000000000 newfile &&
+ test-chmtime -v +0 newfile >expect &&
+ git merge side &&
+ test-chmtime -v +0 newfile >actual &&
+ test_cmp expect actual # "file" should have stayed intact
+'
+
+test_expect_success 'setup merge of rename + small change' '
+ git reset --hard &&
+ git checkout --orphan rename-plus-small-change &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ echo ORIGINAL >file &&
+ git add file &&
+
+ test_tick &&
+ git commit -m Initial &&
+ git checkout -b rename_branch &&
+ git mv file renamed_file &&
+ git commit -m Rename &&
+ git checkout rename-plus-small-change &&
+ echo NEW-VERSION >file &&
+ git commit -a -m Reformat
+'
+
+test_expect_success 'merge rename + small change' '
+ git merge rename_branch &&
+
+ test 1 -eq $(git ls-files -s | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+ test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file)
+'
+
+test_expect_success 'setup for use of extended merge markers' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file &&
+ git add original_file &&
+ git commit -mA &&
+
+ git checkout -b rename &&
+ echo 9 >>original_file &&
+ git add original_file &&
+ git mv original_file renamed_file &&
+ git commit -mB &&
+
+ git checkout master &&
+ echo 8.5 >>original_file &&
+ git add original_file &&
+ git commit -mC
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+<<<<<<< HEAD:renamed_file
+9
+=======
+8.5
+>>>>>>> master^0:original_file
+EOF
+
+test_expect_success 'merge master into rename has correct extended markers' '
+ git checkout rename^0 &&
+ test_must_fail git merge -s recursive master^0 &&
+ test_cmp expected renamed_file
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+<<<<<<< HEAD:original_file
+8.5
+=======
+9
+>>>>>>> rename^0:renamed_file
+EOF
+
+test_expect_success 'merge rename into master has correct extended markers' '
+ git reset --hard &&
+ git checkout master^0 &&
+ test_must_fail git merge -s recursive rename^0 &&
+ test_cmp expected renamed_file
+'
+
+test_expect_success 'setup spurious "refusing to lose untracked" message' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ > irrelevant_file &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file &&
+ git add irrelevant_file original_file &&
+ git commit -mA &&
+
+ git checkout -b rename &&
+ git mv original_file renamed_file &&
+ git commit -mB &&
+
+ git checkout master &&
+ git rm original_file &&
+ git commit -mC
+'
+
+test_expect_success 'no spurious "refusing to lose untracked" message' '
+ git checkout master^0 &&
+ test_must_fail git merge rename^0 2>errors.txt &&
+ ! grep "refusing to lose untracked file" errors.txt
+'
+
+test_expect_success 'do not follow renames for empty files' '
+ git checkout -f -b empty-base &&
+ >empty1 &&
+ git add empty1 &&
+ git commit -m base &&
+ echo content >empty1 &&
+ git add empty1 &&
+ git commit -m fill &&
+ git checkout -b empty-topic HEAD^ &&
+ git mv empty1 empty2 &&
+ git commit -m rename &&
+ test_must_fail git merge empty-base &&
+ >expect &&
+ test_cmp expect empty2
+'
+
test_done
diff --git a/t/t6028-merge-up-to-date.sh b/t/t6028-merge-up-to-date.sh
index a91644e..c518e9c 100755
--- a/t/t6028-merge-up-to-date.sh
+++ b/t/t6028-merge-up-to-date.sh
@@ -16,7 +16,12 @@ test_expect_success setup '
test_tick &&
git commit -m second &&
git tag c1 &&
- git branch test
+ git branch test &&
+ echo third >file &&
+ git add file &&
+ test_tick &&
+ git commit -m third &&
+ git tag c2
'
test_expect_success 'merge -s recursive up-to-date' '
@@ -74,4 +79,14 @@ test_expect_success 'merge -s subtree up-to-date' '
'
+test_expect_success 'merge fast-forward octopus' '
+
+ git reset --hard c0 &&
+ test_tick &&
+ git merge c1 c2
+ expect=$(git rev-parse c2) &&
+ current=$(git rev-parse HEAD) &&
+ test "$expect" = "$current"
+'
+
test_done
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index b5063b6..72e28ee 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -126,6 +126,18 @@ test_expect_success 'bisect reset removes packed refs' '
test -z "$(git for-each-ref "refs/heads/bisect")"
'
+test_expect_success 'bisect reset removes bisect state after --no-checkout' '
+ git bisect reset &&
+ git bisect start --no-checkout &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH3 &&
+ git bisect next &&
+ git bisect reset &&
+ test -z "$(git for-each-ref "refs/bisect/*")" &&
+ test -z "$(git for-each-ref "refs/heads/bisect")" &&
+ test -z "$(git for-each-ref "BISECT_HEAD")"
+'
+
test_expect_success 'bisect start: back in good branch' '
git branch > branch.output &&
grep "* other" branch.output > /dev/null &&
@@ -138,15 +150,23 @@ test_expect_success 'bisect start: back in good branch' '
grep "* other" branch.output > /dev/null
'
-test_expect_success 'bisect start: no ".git/BISECT_START" if junk rev' '
- git bisect start $HASH4 $HASH1 -- &&
- git bisect good &&
+test_expect_success 'bisect start: no ".git/BISECT_START" created if junk rev' '
+ git bisect reset &&
test_must_fail git bisect start $HASH4 foo -- &&
git branch > branch.output &&
grep "* other" branch.output > /dev/null &&
test_must_fail test -e .git/BISECT_START
'
+test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if junk rev' '
+ git bisect start $HASH4 $HASH1 -- &&
+ git bisect good &&
+ cp .git/BISECT_START saved &&
+ test_must_fail git bisect start $HASH4 foo -- &&
+ git branch > branch.output &&
+ test_i18ngrep "* (no branch)" branch.output > /dev/null &&
+ test_cmp saved .git/BISECT_START
+'
test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
git bisect start $HASH4 $HASH1 -- &&
git bisect good &&
@@ -460,7 +480,7 @@ test_expect_success 'many merge bases creation' '
git merge -m "merge HASH7 and SIDE_HASH7" "$HASH7" &&
B_HASH=$(git rev-parse --verify HEAD) &&
git merge-base --all "$A_HASH" "$B_HASH" > merge_bases.txt &&
- test $(wc -l < merge_bases.txt) = "2" &&
+ test_line_count = 2 merge_bases.txt &&
grep "$HASH5" merge_bases.txt &&
grep "$SIDE_HASH5" merge_bases.txt
'
@@ -572,6 +592,155 @@ test_expect_success 'erroring out when using bad path parameters' '
grep "bad path parameters" error.txt
'
+test_expect_success 'test bisection on bare repo - --no-checkout specified' '
+ git clone --bare . bare.nocheckout &&
+ (
+ cd bare.nocheckout &&
+ git bisect start --no-checkout &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH4 &&
+ git bisect run eval \
+ "test \$(git rev-list BISECT_HEAD ^$HASH2 --max-count=1 | wc -l) = 0" \
+ >../nocheckout.log &&
+ git bisect reset
+ ) &&
+ grep "$HASH3 is the first bad commit" nocheckout.log
+'
+
+
+test_expect_success 'test bisection on bare repo - --no-checkout defaulted' '
+ git clone --bare . bare.defaulted &&
+ (
+ cd bare.defaulted &&
+ git bisect start &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH4 &&
+ git bisect run eval \
+ "test \$(git rev-list BISECT_HEAD ^$HASH2 --max-count=1 | wc -l) = 0" \
+ >../defaulted.log &&
+ git bisect reset
+ ) &&
+ grep "$HASH3 is the first bad commit" defaulted.log
+'
+
#
+# This creates a broken branch which cannot be checked out because
+# the tree created has been deleted.
#
+# H1-H2-H3-H4-H5-H6-H7 <--other
+# \
+# S5-S6'-S7'-S8'-S9 <--broken
+#
+# Commits marked with ' have a missing tree.
+#
+test_expect_success 'broken branch creation' '
+ git bisect reset &&
+ git checkout -b broken $HASH4 &&
+ git tag BROKEN_HASH4 $HASH4 &&
+ add_line_into_file "5(broken): first line on a broken branch" hello2 &&
+ git tag BROKEN_HASH5 &&
+ mkdir missing &&
+ :> missing/MISSING &&
+ git add missing/MISSING &&
+ git commit -m "6(broken): Added file that will be deleted"
+ git tag BROKEN_HASH6 &&
+ add_line_into_file "7(broken): second line on a broken branch" hello2 &&
+ git tag BROKEN_HASH7 &&
+ add_line_into_file "8(broken): third line on a broken branch" hello2 &&
+ git tag BROKEN_HASH8 &&
+ git rm missing/MISSING &&
+ git commit -m "9(broken): Remove missing file"
+ git tag BROKEN_HASH9 &&
+ rm .git/objects/39/f7e61a724187ab767d2e08442d9b6b9dab587d
+'
+
+echo "" > expected.ok
+cat > expected.missing-tree.default <<EOF
+fatal: unable to read tree 39f7e61a724187ab767d2e08442d9b6b9dab587d
+EOF
+
+test_expect_success 'bisect fails if tree is broken on start commit' '
+ git bisect reset &&
+ test_must_fail git bisect start BROKEN_HASH7 BROKEN_HASH4 2>error.txt &&
+ test_cmp expected.missing-tree.default error.txt
+'
+
+test_expect_success 'bisect fails if tree is broken on trial commit' '
+ git bisect reset &&
+ test_must_fail git bisect start BROKEN_HASH9 BROKEN_HASH4 2>error.txt &&
+ git reset --hard broken &&
+ git checkout broken &&
+ test_cmp expected.missing-tree.default error.txt
+'
+
+check_same()
+{
+ echo "Checking $1 is the same as $2" &&
+ git rev-parse "$1" > expected.same &&
+ git rev-parse "$2" > expected.actual &&
+ test_cmp expected.same expected.actual
+}
+
+test_expect_success 'bisect: --no-checkout - start commit bad' '
+ git bisect reset &&
+ git bisect start BROKEN_HASH7 BROKEN_HASH4 --no-checkout &&
+ check_same BROKEN_HASH6 BISECT_HEAD &&
+ git bisect reset
+'
+
+test_expect_success 'bisect: --no-checkout - trial commit bad' '
+ git bisect reset &&
+ git bisect start broken BROKEN_HASH4 --no-checkout &&
+ check_same BROKEN_HASH6 BISECT_HEAD &&
+ git bisect reset
+'
+
+test_expect_success 'bisect: --no-checkout - target before breakage' '
+ git bisect reset &&
+ git bisect start broken BROKEN_HASH4 --no-checkout &&
+ check_same BROKEN_HASH6 BISECT_HEAD &&
+ git bisect bad BISECT_HEAD &&
+ check_same BROKEN_HASH5 BISECT_HEAD &&
+ git bisect bad BISECT_HEAD &&
+ check_same BROKEN_HASH5 bisect/bad &&
+ git bisect reset
+'
+
+test_expect_success 'bisect: --no-checkout - target in breakage' '
+ git bisect reset &&
+ git bisect start broken BROKEN_HASH4 --no-checkout &&
+ check_same BROKEN_HASH6 BISECT_HEAD &&
+ git bisect bad BISECT_HEAD &&
+ check_same BROKEN_HASH5 BISECT_HEAD &&
+ git bisect good BISECT_HEAD &&
+ check_same BROKEN_HASH6 bisect/bad &&
+ git bisect reset
+'
+
+test_expect_success 'bisect: --no-checkout - target after breakage' '
+ git bisect reset &&
+ git bisect start broken BROKEN_HASH4 --no-checkout &&
+ check_same BROKEN_HASH6 BISECT_HEAD &&
+ git bisect good BISECT_HEAD &&
+ check_same BROKEN_HASH8 BISECT_HEAD &&
+ git bisect good BISECT_HEAD &&
+ check_same BROKEN_HASH9 bisect/bad &&
+ git bisect reset
+'
+
+test_expect_success 'bisect: demonstrate identification of damage boundary' "
+ git bisect reset &&
+ git checkout broken &&
+ git bisect start broken master --no-checkout &&
+ git bisect run \"\$SHELL_PATH\" -c '
+ GOOD=\$(git for-each-ref \"--format=%(objectname)\" refs/bisect/good-*) &&
+ git rev-list --objects BISECT_HEAD --not \$GOOD >tmp.\$\$ &&
+ git pack-objects --stdout >/dev/null < tmp.\$\$
+ rc=\$?
+ rm -f tmp.\$\$
+ test \$rc = 0' &&
+ check_same BROKEN_HASH6 bisect/bad &&
+ git bisect reset
+"
+
test_done
diff --git a/t/t6032-merge-large-rename.sh b/t/t6032-merge-large-rename.sh
index fdb6c25..15beecc 100755
--- a/t/t6032-merge-large-rename.sh
+++ b/t/t6032-merge-large-rename.sh
@@ -95,9 +95,9 @@ test_expect_success 'setup large simple rename' '
'
test_expect_success 'massive simple rename does not spam added files' '
- unset GIT_MERGE_VERBOSITY &&
+ sane_unset GIT_MERGE_VERBOSITY &&
git merge --no-stat simple-rename | grep -v Removing >output &&
- test 5 -gt "$(wc -l < output)"
+ test_line_count -lt 5 output
'
test_done
diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh
index 871577d..dfee7d1 100755
--- a/t/t6036-recursive-corner-cases.sh
+++ b/t/t6036-recursive-corner-cases.sh
@@ -1,9 +1,15 @@
#!/bin/sh
-test_description='recursive merge corner cases'
+test_description='recursive merge corner cases involving criss-cross merges'
. ./test-lib.sh
+get_clean_checkout () {
+ git reset --hard &&
+ git clean -fdqx &&
+ git checkout "$1"
+}
+
#
# L1 L2
# o---o
@@ -51,23 +57,15 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
test_must_fail git merge -s recursive R2^0 &&
- test 5 = $(git ls-files -s | wc -l) &&
- test 3 = $(git ls-files -u | wc -l) &&
- test 0 = $(git ls-files -o | wc -l) &&
+ test 2 = $(git ls-files -s | wc -l) &&
+ test 2 = $(git ls-files -u | wc -l) &&
+ test 2 = $(git ls-files -o | wc -l) &&
- test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
- test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
- cp two merged &&
- >empty &&
- test_must_fail git merge-file \
- -L "Temporary merge branch 2" \
- -L "" \
- -L "Temporary merge branch 1" \
- merged empty one &&
- test $(git rev-parse :1:three) = $(git hash-object merged)
+ test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
+ test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
'
#
@@ -126,24 +124,15 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
test_must_fail git merge -s recursive R2^0 &&
- test 5 = $(git ls-files -s | wc -l) &&
- test 3 = $(git ls-files -u | wc -l) &&
- test 0 = $(git ls-files -o | wc -l) &&
+ test 2 = $(git ls-files -s | wc -l) &&
+ test 2 = $(git ls-files -u | wc -l) &&
+ test 2 = $(git ls-files -o | wc -l) &&
- test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
- test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
- head -n 10 two >merged &&
- cp one merge-me &&
- >empty &&
- test_must_fail git merge-file \
- -L "Temporary merge branch 2" \
- -L "" \
- -L "Temporary merge branch 1" \
- merged empty merge-me &&
- test $(git rev-parse :1:three) = $(git hash-object merged)
+ test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
+ test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
'
#
@@ -231,4 +220,557 @@ test_expect_success 'git detects differently handled merges conflict' '
test $(git rev-parse :1:new_a) = $(git hash-object merged)
'
+#
+# criss-cross + modify/delete:
+#
+# B D
+# o---o
+# / \ / \
+# A o X ? F
+# \ / \ /
+# o---o
+# C E
+#
+# Commit A: file with contents 'A\n'
+# Commit B: file with contents 'B\n'
+# Commit C: file not present
+# Commit D: file with contents 'B\n'
+# Commit E: file not present
+#
+# Merging commits D & E should result in modify/delete conflict.
+
+test_expect_success 'setup criss-cross + modify/delete resolved differently' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ echo A >file &&
+ git add file &&
+ test_tick &&
+ git commit -m A &&
+
+ git branch B &&
+ git checkout -b C &&
+ git rm file &&
+ test_tick &&
+ git commit -m C &&
+
+ git checkout B &&
+ echo B >file &&
+ git add file &&
+ test_tick &&
+ git commit -m B &&
+
+ git checkout B^0 &&
+ test_must_fail git merge C &&
+ echo B >file &&
+ git add file &&
+ test_tick &&
+ git commit -m D &&
+ git tag D &&
+
+ git checkout C^0 &&
+ test_must_fail git merge B &&
+ git rm file &&
+ test_tick &&
+ git commit -m E &&
+ git tag E
+'
+
+test_expect_success 'git detects conflict merging criss-cross+modify/delete' '
+ git checkout D^0 &&
+
+ test_must_fail git merge -s recursive E^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 2 -eq $(git ls-files -u | wc -l) &&
+
+ test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
+ test $(git rev-parse :2:file) = $(git rev-parse B:file)
+'
+
+test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '
+ git reset --hard &&
+ git checkout E^0 &&
+
+ test_must_fail git merge -s recursive D^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 2 -eq $(git ls-files -u | wc -l) &&
+
+ test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
+ test $(git rev-parse :3:file) = $(git rev-parse B:file)
+'
+
+#
+# criss-cross + modify/modify with very contrived file contents:
+#
+# B D
+# o---o
+# / \ / \
+# A o X ? F
+# \ / \ /
+# o---o
+# C E
+#
+# Commit A: file with contents 'A\n'
+# Commit B: file with contents 'B\n'
+# Commit C: file with contents 'C\n'
+# Commit D: file with contents 'D\n'
+# Commit E: file with contents:
+# <<<<<<< Temporary merge branch 1
+# C
+# =======
+# B
+# >>>>>>> Temporary merge branch 2
+#
+# Now, when we merge commits D & E, does git detect the conflict?
+
+test_expect_success 'setup differently handled merges of content conflict' '
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ echo A >file &&
+ git add file &&
+ test_tick &&
+ git commit -m A &&
+
+ git branch B &&
+ git checkout -b C &&
+ echo C >file &&
+ git add file &&
+ test_tick &&
+ git commit -m C &&
+
+ git checkout B &&
+ echo B >file &&
+ git add file &&
+ test_tick &&
+ git commit -m B &&
+
+ git checkout B^0 &&
+ test_must_fail git merge C &&
+ echo D >file &&
+ git add file &&
+ test_tick &&
+ git commit -m D &&
+ git tag D &&
+
+ git checkout C^0 &&
+ test_must_fail git merge B &&
+ cat <<EOF >file &&
+<<<<<<< Temporary merge branch 1
+C
+=======
+B
+>>>>>>> Temporary merge branch 2
+EOF
+ git add file &&
+ test_tick &&
+ git commit -m E &&
+ git tag E
+'
+
+test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '
+ git checkout D^0 &&
+
+ test_must_fail git merge -s recursive E^0 &&
+
+ test 3 -eq $(git ls-files -s | wc -l) &&
+ test 3 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :2:file) = $(git rev-parse D:file) &&
+ test $(git rev-parse :3:file) = $(git rev-parse E:file)
+'
+
+#
+# criss-cross + d/f conflict via add/add:
+# Commit A: Neither file 'a' nor directory 'a/' exist.
+# Commit B: Introduce 'a'
+# Commit C: Introduce 'a/file'
+# Commit D: Merge B & C, keeping 'a' and deleting 'a/'
+#
+# Two different later cases:
+# Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'
+# Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file'
+#
+# B D
+# o---o
+# / \ / \
+# A o X ? F
+# \ / \ /
+# o---o
+# C E1 or E2
+#
+# Merging D & E1 requires we first create a virtual merge base X from
+# merging A & B in memory. Now, if X could keep both 'a' and 'a/file' in
+# the index, then the merge of D & E1 could be resolved cleanly with both
+# 'a' and 'a/file' removed. Since git does not currently allow creating
+# such a tree, the best we can do is have X contain both 'a~<unique>' and
+# 'a/file' resulting in the merge of D and E1 having a rename/delete
+# conflict for 'a'. (Although this merge appears to be unsolvable with git
+# currently, git could do a lot better than it currently does with these
+# d/f conflicts, which is the purpose of this test.)
+#
+# Merge of D & E2 has similar issues for path 'a', but should always result
+# in a modify/delete conflict for path 'a/file'.
+#
+# We run each merge in both directions, to check for directional issues
+# with D/F conflict handling.
+#
+
+test_expect_success 'setup differently handled merges of directory/file conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ >ignore-me &&
+ git add ignore-me &&
+ test_tick &&
+ git commit -m A &&
+ git tag A &&
+
+ git branch B &&
+ git checkout -b C &&
+ mkdir a &&
+ echo 10 >a/file &&
+ git add a/file &&
+ test_tick &&
+ git commit -m C &&
+
+ git checkout B &&
+ echo 5 >a &&
+ git add a &&
+ test_tick &&
+ git commit -m B &&
+
+ git checkout B^0 &&
+ test_must_fail git merge C &&
+ git clean -f &&
+ rm -rf a/ &&
+ echo 5 >a &&
+ git add a &&
+ test_tick &&
+ git commit -m D &&
+ git tag D &&
+
+ git checkout C^0 &&
+ test_must_fail git merge B &&
+ git clean -f &&
+ git rm --cached a &&
+ echo 10 >a/file &&
+ git add a/file &&
+ test_tick &&
+ git commit -m E1 &&
+ git tag E1 &&
+
+ git checkout C^0 &&
+ test_must_fail git merge B &&
+ git clean -f &&
+ git rm --cached a &&
+ printf "10\n11\n" >a/file &&
+ git add a/file &&
+ test_tick &&
+ git commit -m E2 &&
+ git tag E2
+'
+
+test_expect_success 'merge of D & E1 fails but has appropriate contents' '
+ get_clean_checkout D^0 &&
+
+ test_must_fail git merge -s recursive E1^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 1 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+ test $(git rev-parse :2:a) = $(git rev-parse B:a)
+'
+
+test_expect_success 'merge of E1 & D fails but has appropriate contents' '
+ get_clean_checkout E1^0 &&
+
+ test_must_fail git merge -s recursive D^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 1 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+ test $(git rev-parse :3:a) = $(git rev-parse B:a)
+'
+
+test_expect_success 'merge of D & E2 fails but has appropriate contents' '
+ get_clean_checkout D^0 &&
+
+ test_must_fail git merge -s recursive E2^0 &&
+
+ test 4 -eq $(git ls-files -s | wc -l) &&
+ test 3 -eq $(git ls-files -u | wc -l) &&
+ test 1 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :2:a) = $(git rev-parse B:a) &&
+ test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) &&
+ test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
+ test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+
+ test -f a~HEAD
+'
+
+test_expect_success 'merge of E2 & D fails but has appropriate contents' '
+ get_clean_checkout E2^0 &&
+
+ test_must_fail git merge -s recursive D^0 &&
+
+ test 4 -eq $(git ls-files -s | wc -l) &&
+ test 3 -eq $(git ls-files -u | wc -l) &&
+ test 1 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :3:a) = $(git rev-parse B:a) &&
+ test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) &&
+ test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)
+ test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+
+ test -f a~D^0
+'
+
+#
+# criss-cross with rename/rename(1to2)/modify followed by
+# rename/rename(2to1)/modify:
+#
+# B D
+# o---o
+# / \ / \
+# A o X ? F
+# \ / \ /
+# o---o
+# C E
+#
+# Commit A: new file: a
+# Commit B: rename a->b, modifying by adding a line
+# Commit C: rename a->c
+# Commit D: merge B&C, resolving conflict by keeping contents in newname
+# Commit E: merge B&C, resolving conflict similar to D but adding another line
+#
+# There is a conflict merging B & C, but one of filename not of file
+# content. Whoever created D and E chose specific resolutions for that
+# conflict resolution. Now, since: (1) there is no content conflict
+# merging B & C, (2) D does not modify that merged content further, and (3)
+# both D & E resolve the name conflict in the same way, the modification to
+# newname in E should not cause any conflicts when it is merged with D.
+# (Note that this can be accomplished by having the virtual merge base have
+# the merged contents of b and c stored in a file named a, which seems like
+# the most logical choice anyway.)
+#
+# Comment from Junio: I do not necessarily agree with the choice "a", but
+# it feels sound to say "B and C do not agree what the final pathname
+# should be, but we know this content was derived from the common A:a so we
+# use one path whose name is arbitrary in the virtual merge base X between
+# D and E" and then further let the rename detection to notice that that
+# arbitrary path gets renamed between X-D to "newname" and X-E also to
+# "newname" to resolve it as both sides renaming it to the same new
+# name. It is akin to what we do at the content level, i.e. "B and C do not
+# agree what the final contents should be, so we leave the conflict marker
+# but that may cancel out at the final merge stage".
+
+test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
+ git reset --hard &&
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n6\n" >a &&
+ git add a &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a b &&
+ echo 7 >>b &&
+ git add -u &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ git mv a c &&
+ git commit -m C &&
+
+ git checkout -q B^0 &&
+ git merge --no-commit -s ours C^0 &&
+ git mv b newname &&
+ git commit -m "Merge commit C^0 into HEAD" &&
+ git tag D &&
+
+ git checkout -q C^0 &&
+ git merge --no-commit -s ours B^0 &&
+ git mv c newname &&
+ printf "7\n8\n" >>newname &&
+ git add -u &&
+ git commit -m "Merge commit B^0 into HEAD" &&
+ git tag E
+'
+
+test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
+ git checkout D^0 &&
+
+ git merge -s recursive E^0 &&
+
+ test 1 -eq $(git ls-files -s | wc -l) &&
+ test 0 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)
+'
+
+#
+# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:
+#
+# B D
+# o---o
+# / \ / \
+# A o X ? F
+# \ / \ /
+# o---o
+# C E
+#
+# Commit A: new file: a
+# Commit B: rename a->b
+# Commit C: rename a->c, add different a
+# Commit D: merge B&C, keeping b&c and (new) a modified at beginning
+# Commit E: merge B&C, keeping b&c and (new) a modified at end
+#
+# Merging commits D & E should result in no conflict; doing so correctly
+# requires getting the virtual merge base (from merging B&C) right, handling
+# renaming carefully (both in the virtual merge base and later), and getting
+# content merge handled.
+
+test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "lots\nof\nwords\nand\ncontent\n" >a &&
+ git add a &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a b &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ git mv a c &&
+ printf "2\n3\n4\n5\n6\n7\n" >a &&
+ git add a &&
+ git commit -m C &&
+
+ git checkout B^0 &&
+ git merge --no-commit -s ours C^0 &&
+ git checkout C -- a c &&
+ mv a old_a &&
+ echo 1 >a &&
+ cat old_a >>a &&
+ rm old_a &&
+ git add -u &&
+ git commit -m "Merge commit C^0 into HEAD" &&
+ git tag D &&
+
+ git checkout C^0 &&
+ git merge --no-commit -s ours B^0 &&
+ git checkout B -- b &&
+ echo 8 >>a &&
+ git add -u &&
+ git commit -m "Merge commit B^0 into HEAD" &&
+ git tag E
+'
+
+test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '
+ git checkout D^0 &&
+
+ git merge -s recursive E^0 &&
+
+ test 3 -eq $(git ls-files -s | wc -l) &&
+ test 0 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+ test $(git rev-parse HEAD:c) = $(git rev-parse A:a) &&
+ test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")"
+'
+
+#
+# criss-cross with rename/rename(1to2)/add-dest + simple modify:
+#
+# B D
+# o---o
+# / \ / \
+# A o X ? F
+# \ / \ /
+# o---o
+# C E
+#
+# Commit A: new file: a
+# Commit B: rename a->b, add c
+# Commit C: rename a->c
+# Commit D: merge B&C, keeping A:a and B:c
+# Commit E: merge B&C, keeping A:a and slightly modified c from B
+#
+# Merging commits D & E should result in no conflict. The virtual merge
+# base of B & C needs to not delete B:c for that to work, though...
+
+test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ >a &&
+ git add a &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a b &&
+ printf "1\n2\n3\n4\n5\n6\n7\n" >c &&
+ git add c &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ git mv a c &&
+ git commit -m C &&
+
+ git checkout B^0 &&
+ git merge --no-commit -s ours C^0 &&
+ git mv b a &&
+ git commit -m "D is like B but renames b back to a" &&
+ git tag D &&
+
+ git checkout B^0 &&
+ git merge --no-commit -s ours C^0 &&
+ git mv b a &&
+ echo 8 >>c &&
+ git add c &&
+ git commit -m "E like D but has mod in c" &&
+ git tag E
+'
+
+test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '
+ git checkout D^0 &&
+
+ git merge -s recursive E^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 0 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse HEAD:a) = $(git rev-parse A:a) &&
+ test $(git rev-parse HEAD:c) = $(git rev-parse E:c)
+'
+
test_done
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 19de5b1..ec2b516 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -51,17 +51,33 @@ test_expect_success 'branch -v' '
test_i18ncmp expect actual
'
+cat >expect <<\EOF
+b1 origin/master: ahead 1, behind 1
+b2 origin/master: ahead 1, behind 1
+b3 origin/master: behind 1
+b4 origin/master: ahead 2
+EOF
+
+test_expect_success 'branch -vv' '
+ (
+ cd test &&
+ git branch -vv
+ ) |
+ sed -n -e "$script" >actual &&
+ test_i18ncmp expect actual
+'
+
test_expect_success 'checkout' '
(
cd test && git checkout b1
) >actual &&
- grep "have 1 and 1 different" actual
+ test_i18ngrep "have 1 and 1 different" actual
'
test_expect_success 'checkout with local tracked branch' '
git checkout master &&
git checkout follower >actual &&
- grep "is ahead of" actual
+ test_i18ngrep "is ahead of" actual
'
test_expect_success 'status' '
@@ -71,14 +87,14 @@ test_expect_success 'status' '
# reports nothing to commit
test_must_fail git commit --dry-run
) >actual &&
- grep "have 1 and 1 different" actual
+ test_i18ngrep "have 1 and 1 different" actual
'
test_expect_success 'fail to track lightweight tags' '
git checkout master &&
git tag light &&
test_must_fail git branch --track lighttrack light >actual &&
- test_must_fail grep "set up to track" actual &&
+ test_i18ngrep ! "set up to track" actual &&
test_must_fail git checkout lighttrack
'
@@ -86,7 +102,7 @@ test_expect_success 'fail to track annotated tags' '
git checkout master &&
git tag -m heavy heavy &&
test_must_fail git branch --track heavytrack heavy >actual &&
- test_must_fail grep "set up to track" actual &&
+ test_i18ngrep ! "set up to track" actual &&
test_must_fail git checkout heavytrack
'
diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh
new file mode 100755
index 0000000..466fa38
--- /dev/null
+++ b/t/t6042-merge-rename-corner-cases.sh
@@ -0,0 +1,578 @@
+#!/bin/sh
+
+test_description="recursive merge corner cases w/ renames but not criss-crosses"
+# t6036 has corner cases that involve both criss-cross merges and renames
+
+. ./test-lib.sh
+
+test_expect_success 'setup rename/delete + untracked file' '
+ echo "A pretty inscription" >ring &&
+ git add ring &&
+ test_tick &&
+ git commit -m beginning &&
+
+ git branch people &&
+ git checkout -b rename-the-ring &&
+ git mv ring one-ring-to-rule-them-all &&
+ test_tick &&
+ git commit -m fullname &&
+
+ git checkout people &&
+ git rm ring &&
+ echo gollum >owner &&
+ git add owner &&
+ test_tick &&
+ git commit -m track-people-instead-of-objects &&
+ echo "Myyy PRECIOUSSS" >ring
+'
+
+test_expect_success "Does git preserve Gollum's precious artifact?" '
+ test_must_fail git merge -s recursive rename-the-ring &&
+
+ # Make sure git did not delete an untracked file
+ test -f ring
+'
+
+# Testcase setup for rename/modify/add-source:
+# Commit A: new file: a
+# Commit B: modify a slightly
+# Commit C: rename a->b, add completely different a
+#
+# We should be able to merge B & C cleanly
+
+test_expect_success 'setup rename/modify/add-source conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
+ git add a &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ echo 8 >>a &&
+ git add a &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ git mv a b &&
+ echo something completely different >a &&
+ git add a &&
+ git commit -m C
+'
+
+test_expect_failure 'rename/modify/add-source conflict resolvable' '
+ git checkout B^0 &&
+
+ git merge -s recursive C^0 &&
+
+ test $(git rev-parse B:a) = $(git rev-parse b) &&
+ test $(git rev-parse C:a) = $(git rev-parse a)
+'
+
+test_expect_success 'setup resolvable conflict missed if rename missed' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n" >a &&
+ echo foo >b &&
+ git add a b &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a c &&
+ echo "Completely different content" >a &&
+ git add a &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ echo 6 >>a &&
+ git add a &&
+ git commit -m C
+'
+
+test_expect_failure 'conflict caused if rename not detected' '
+ git checkout -q C^0 &&
+ git merge -s recursive B^0 &&
+
+ test 3 -eq $(git ls-files -s | wc -l) &&
+ test 0 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test_line_count = 6 c &&
+ test $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&
+ test $(git rev-parse HEAD:b) = $(git rev-parse A:b)
+'
+
+test_expect_success 'setup conflict resolved wrong if rename missed' '
+ git reset --hard &&
+ git clean -f &&
+
+ git checkout -b D A &&
+ echo 7 >>a &&
+ git add a &&
+ git mv a c &&
+ echo "Completely different content" >a &&
+ git add a &&
+ git commit -m D &&
+
+ git checkout -b E A &&
+ git rm a &&
+ echo "Completely different content" >>a &&
+ git add a &&
+ git commit -m E
+'
+
+test_expect_failure 'missed conflict if rename not detected' '
+ git checkout -q E^0 &&
+ test_must_fail git merge -s recursive D^0
+'
+
+# Tests for undetected rename/add-source causing a file to erroneously be
+# deleted (and for mishandled rename/rename(1to1) causing the same issue).
+#
+# This test uses a rename/rename(1to1)+add-source conflict (1to1 means the
+# same file is renamed on both sides to the same thing; it should trigger
+# the 1to2 logic, which it would do if the add-source didn't cause issues
+# for git's rename detection):
+# Commit A: new file: a
+# Commit B: rename a->b
+# Commit C: rename a->b, add unrelated a
+
+test_expect_success 'setup undetected rename/add-source causes data loss' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n" >a &&
+ git add a &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a b &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ git mv a b &&
+ echo foobar >a &&
+ git add a &&
+ git commit -m C
+'
+
+test_expect_failure 'detect rename/add-source and preserve all data' '
+ git checkout B^0 &&
+
+ git merge -s recursive C^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 2 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test -f a &&
+ test -f b &&
+
+ test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+ test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
+'
+
+test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
+ git checkout C^0 &&
+
+ git merge -s recursive B^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 2 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test -f a &&
+ test -f b &&
+
+ test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
+ test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
+'
+
+test_expect_success 'setup content merge + rename/directory conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n6\n" >file &&
+ git add file &&
+ test_tick &&
+ git commit -m base &&
+ git tag base &&
+
+ git checkout -b right &&
+ echo 7 >>file &&
+ mkdir newfile &&
+ echo junk >newfile/realfile &&
+ git add file newfile/realfile &&
+ test_tick &&
+ git commit -m right &&
+
+ git checkout -b left-conflict base &&
+ echo 8 >>file &&
+ git add file &&
+ git mv file newfile &&
+ test_tick &&
+ git commit -m left &&
+
+ git checkout -b left-clean base &&
+ echo 0 >newfile &&
+ cat file >>newfile &&
+ git add newfile &&
+ git rm file &&
+ test_tick &&
+ git commit -m left
+'
+
+test_expect_success 'rename/directory conflict + clean content merge' '
+ git reset --hard &&
+ git reset --hard &&
+ git clean -fdqx &&
+
+ git checkout left-clean^0 &&
+
+ test_must_fail git merge -s recursive right^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 1 -eq $(git ls-files -u | wc -l) &&
+ test 1 -eq $(git ls-files -o | wc -l) &&
+
+ echo 0 >expect &&
+ git cat-file -p base:file >>expect &&
+ echo 7 >>expect &&
+ test_cmp expect newfile~HEAD &&
+
+ test $(git rev-parse :2:newfile) = $(git hash-object expect) &&
+
+ test -f newfile/realfile &&
+ test -f newfile~HEAD
+'
+
+test_expect_success 'rename/directory conflict + content merge conflict' '
+ git reset --hard &&
+ git reset --hard &&
+ git clean -fdqx &&
+
+ git checkout left-conflict^0 &&
+
+ test_must_fail git merge -s recursive right^0 &&
+
+ test 4 -eq $(git ls-files -s | wc -l) &&
+ test 3 -eq $(git ls-files -u | wc -l) &&
+ test 1 -eq $(git ls-files -o | wc -l) &&
+
+ git cat-file -p left-conflict:newfile >left &&
+ git cat-file -p base:file >base &&
+ git cat-file -p right:file >right &&
+ test_must_fail git merge-file \
+ -L "HEAD:newfile" \
+ -L "" \
+ -L "right^0:file" \
+ left base right &&
+ test_cmp left newfile~HEAD &&
+
+ test $(git rev-parse :1:newfile) = $(git rev-parse base:file) &&
+ test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) &&
+ test $(git rev-parse :3:newfile) = $(git rev-parse right:file) &&
+
+ test -f newfile/realfile &&
+ test -f newfile~HEAD
+'
+
+test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' '
+ git reset --hard &&
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ mkdir sub &&
+ printf "1\n2\n3\n4\n5\n6\n" >sub/file &&
+ git add sub/file &&
+ test_tick &&
+ git commit -m base &&
+ git tag base &&
+
+ git checkout -b right &&
+ echo 7 >>sub/file &&
+ git add sub/file &&
+ test_tick &&
+ git commit -m right &&
+
+ git checkout -b left base &&
+ echo 0 >newfile &&
+ cat sub/file >>newfile &&
+ git rm sub/file &&
+ mv newfile sub &&
+ git add sub &&
+ test_tick &&
+ git commit -m left
+'
+
+test_expect_success 'disappearing dir in rename/directory conflict handled' '
+ git reset --hard &&
+ git clean -fdqx &&
+
+ git checkout left^0 &&
+
+ git merge -s recursive right^0 &&
+
+ test 1 -eq $(git ls-files -s | wc -l) &&
+ test 0 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ echo 0 >expect &&
+ git cat-file -p base:sub/file >>expect &&
+ echo 7 >>expect &&
+ test_cmp expect sub &&
+
+ test -f sub
+'
+
+# Test for all kinds of things that can go wrong with rename/rename (2to1):
+# Commit A: new files: a & b
+# Commit B: rename a->c, modify b
+# Commit C: rename b->c, modify a
+#
+# Merging of B & C should NOT be clean. Questions:
+# * Both a & b should be removed by the merge; are they?
+# * The two c's should contain modifications to a & b; do they?
+# * The index should contain two files, both for c; does it?
+# * The working copy should have two files, both of form c~<unique>; does it?
+# * Nothing else should be present. Is anything?
+
+test_expect_success 'setup rename/rename (2to1) + modify/modify' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n" >a &&
+ printf "5\n4\n3\n2\n1\n" >b &&
+ git add a b &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a c &&
+ echo 0 >>b &&
+ git add b &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ git mv b c &&
+ echo 6 >>a &&
+ git add a &&
+ git commit -m C
+'
+
+test_expect_success 'handle rename/rename (2to1) conflict correctly' '
+ git checkout B^0 &&
+
+ test_must_fail git merge -s recursive C^0 >out &&
+ grep "CONFLICT (rename/rename)" out &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 2 -eq $(git ls-files -u | wc -l) &&
+ test 2 -eq $(git ls-files -u c | wc -l) &&
+ test 3 -eq $(git ls-files -o | wc -l) &&
+
+ test ! -f a &&
+ test ! -f b &&
+ test -f c~HEAD &&
+ test -f c~C^0 &&
+
+ test $(git hash-object c~HEAD) = $(git rev-parse C:a) &&
+ test $(git hash-object c~C^0) = $(git rev-parse B:b)
+'
+
+# Testcase setup for simple rename/rename (1to2) conflict:
+# Commit A: new file: a
+# Commit B: rename a->b
+# Commit C: rename a->c
+test_expect_success 'setup simple rename/rename (1to2) conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ echo stuff >a &&
+ git add a &&
+ test_tick &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a b &&
+ test_tick &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ git mv a c &&
+ test_tick &&
+ git commit -m C
+'
+
+test_expect_success 'merge has correct working tree contents' '
+ git checkout C^0 &&
+
+ test_must_fail git merge -s recursive B^0 &&
+
+ test 3 -eq $(git ls-files -s | wc -l) &&
+ test 3 -eq $(git ls-files -u | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
+ test $(git rev-parse :3:b) = $(git rev-parse A:a) &&
+ test $(git rev-parse :2:c) = $(git rev-parse A:a) &&
+
+ test ! -f a &&
+ test $(git hash-object b) = $(git rev-parse A:a) &&
+ test $(git hash-object c) = $(git rev-parse A:a)
+'
+
+# Testcase setup for rename/rename(1to2)/add-source conflict:
+# Commit A: new file: a
+# Commit B: rename a->b
+# Commit C: rename a->c, add completely different a
+#
+# Merging of B & C should NOT be clean; there's a rename/rename conflict
+
+test_expect_success 'setup rename/rename(1to2)/add-source conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
+ git add a &&
+ git commit -m A &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a b &&
+ git commit -m B &&
+
+ git checkout -b C A &&
+ git mv a c &&
+ echo something completely different >a &&
+ git add a &&
+ git commit -m C
+'
+
+test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' '
+ git checkout B^0 &&
+
+ test_must_fail git merge -s recursive C^0 &&
+
+ test 4 -eq $(git ls-files -s | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse 3:a) = $(git rev-parse C:a) &&
+ test $(git rev-parse 1:a) = $(git rev-parse A:a) &&
+ test $(git rev-parse 2:b) = $(git rev-parse B:b) &&
+ test $(git rev-parse 3:c) = $(git rev-parse C:c) &&
+
+ test -f a &&
+ test -f b &&
+ test -f c
+'
+
+test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ >a &&
+ git add a &&
+ test_tick &&
+ git commit -m base &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a b &&
+ test_tick &&
+ git commit -m one &&
+
+ git checkout -b C A &&
+ git mv a b &&
+ echo important-info >a &&
+ git add a &&
+ test_tick &&
+ git commit -m two
+'
+
+test_expect_failure 'rename/rename/add-source still tracks new a file' '
+ git checkout C^0 &&
+ git merge -s recursive B^0 &&
+
+ test 2 -eq $(git ls-files -s | wc -l) &&
+ test 0 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse HEAD:a) = $(git rev-parse C:a) &&
+ test $(git rev-parse HEAD:b) = $(git rev-parse A:a)
+'
+
+test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ echo stuff >a &&
+ git add a &&
+ test_tick &&
+ git commit -m base &&
+ git tag A &&
+
+ git checkout -b B A &&
+ git mv a b &&
+ echo precious-data >c &&
+ git add c &&
+ test_tick &&
+ git commit -m one &&
+
+ git checkout -b C A &&
+ git mv a c &&
+ echo important-info >b &&
+ git add b &&
+ test_tick &&
+ git commit -m two
+'
+
+test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' '
+ git checkout C^0 &&
+ test_must_fail git merge -s recursive B^0 &&
+
+ test 5 -eq $(git ls-files -s | wc -l) &&
+ test 2 -eq $(git ls-files -u b | wc -l) &&
+ test 2 -eq $(git ls-files -u c | wc -l) &&
+ test 4 -eq $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
+ test $(git rev-parse :2:b) = $(git rev-parse C:b) &&
+ test $(git rev-parse :3:b) = $(git rev-parse B:b) &&
+ test $(git rev-parse :2:c) = $(git rev-parse C:c) &&
+ test $(git rev-parse :3:c) = $(git rev-parse B:c) &&
+
+ test $(git hash-object c~HEAD) = $(git rev-parse C:c) &&
+ test $(git hash-object c~B\^0) = $(git rev-parse B:c) &&
+ test $(git hash-object b~HEAD) = $(git rev-parse C:b) &&
+ test $(git hash-object b~B\^0) = $(git rev-parse B:b) &&
+
+ test ! -f b &&
+ test ! -f c
+'
+
+test_done
diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh
index 9a16806..992c2a0 100755
--- a/t/t6200-fmt-merge-msg.sh
+++ b/t/t6200-fmt-merge-msg.sh
@@ -35,15 +35,18 @@ test_expect_success setup '
echo "l3" >two &&
test_tick &&
- git commit -a -m "Left #3" &&
+ GIT_COMMITTER_NAME="Another Committer" \
+ GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #3" &&
echo "l4" >two &&
test_tick &&
- git commit -a -m "Left #4" &&
+ GIT_COMMITTER_NAME="Another Committer" \
+ GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #4" &&
echo "l5" >two &&
test_tick &&
- git commit -a -m "Left #5" &&
+ GIT_COMMITTER_NAME="Another Committer" \
+ GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #5" &&
git tag tag-l5 &&
git checkout right &&
@@ -99,6 +102,8 @@ test_expect_success '[merge] summary/log configuration' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* left:
Left #5
Left #4
@@ -144,6 +149,8 @@ test_expect_success 'merge.log=3 limits shortlog length' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* left: (5 commits)
Left #5
Left #4
@@ -159,6 +166,8 @@ test_expect_success 'merge.log=5 shows all 5 commits' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* left:
Left #5
Left #4
@@ -181,6 +190,8 @@ test_expect_success '--log=3 limits shortlog length' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* left: (5 commits)
Left #5
Left #4
@@ -196,6 +207,8 @@ test_expect_success '--log=5 shows all 5 commits' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* left:
Left #5
Left #4
@@ -225,6 +238,8 @@ test_expect_success 'fmt-merge-msg -m' '
cat >expected.log <<-EOF &&
Sync with left
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* ${apos}left${apos} of $(pwd):
Left #5
Left #4
@@ -256,6 +271,8 @@ test_expect_success 'setup: expected shortlog for two branches' '
cat >expected <<-EOF
Merge branches ${apos}left${apos} and ${apos}right${apos}
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* left:
Left #5
Left #4
@@ -379,6 +396,8 @@ test_expect_success 'merge-msg two tags' '
Common #2
Common #1
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* tag ${apos}tag-l5${apos}:
Left #5
Left #4
@@ -407,6 +426,8 @@ test_expect_success 'merge-msg tag and branch' '
Common #2
Common #1
+ # By Another Author (3) and A U Thor (2)
+ # Via Another Committer
* left:
Left #5
Left #4
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 7dc8a51..1721784 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -6,6 +6,7 @@
test_description='for-each-ref test'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
# Mon Jul 3 15:18:43 2006 +0000
datestamp=1151939923
@@ -37,11 +38,13 @@ test_atom() {
case "$1" in
head) ref=refs/heads/master ;;
tag) ref=refs/tags/testtag ;;
+ *) ref=$1 ;;
esac
printf '%s\n' "$3" >expected
- test_expect_${4:-success} "basic atom: $1 $2" "
+ test_expect_${4:-success} $PREREQ "basic atom: $1 $2" "
git for-each-ref --format='%($2)' $ref >actual &&
- test_cmp expected actual
+ sanitize_pgp <actual >actual.clean &&
+ test_cmp expected actual.clean
"
}
@@ -71,7 +74,10 @@ test_atom head taggerdate ''
test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200'
test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200'
test_atom head subject 'Initial'
+test_atom head contents:subject 'Initial'
test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
test_atom head contents 'Initial
'
@@ -101,7 +107,10 @@ test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200'
test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200'
test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200'
test_atom tag subject 'Tagging at 1151939927'
+test_atom tag contents:subject 'Tagging at 1151939927'
test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
test_atom tag contents 'Tagging at 1151939927
'
@@ -359,4 +368,92 @@ test_expect_success 'an unusual tag with an incomplete line' '
'
+test_expect_success 'create tag with subject and body content' '
+ cat >>msg <<-\EOF &&
+ the subject line
+
+ first body line
+ second body line
+ EOF
+ git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+ cat >msg <<-\EOF &&
+ first subject line
+ second subject line
+
+ first body line
+ second body line
+ EOF
+ git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+ git tag -s -m "" signed-empty &&
+ git tag -s -m "subject line" signed-short &&
+ cat >msg <<-\EOF &&
+ subject line
+
+ body contents
+ EOF
+ git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 097ce2b..5189446 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -8,6 +8,7 @@ test_description='git tag
Tests for operations with tags.'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
# creating and listing lightweight tags:
@@ -262,6 +263,50 @@ test_expect_success 'tag -l can accept multiple patterns' '
test_cmp expect actual
'
+test_expect_success 'listing tags in column' '
+ COLUMNS=40 git tag -l --column=row >actual &&
+ cat >expected <<\EOF &&
+a1 aa1 cba t210 t211
+v0.2.1 v1.0 v1.0.1 v1.1.3
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'listing tags in column with column.*' '
+ git config column.tag row &&
+ git config column.ui dense &&
+ COLUMNS=40 git tag -l >actual &&
+ git config --unset column.ui &&
+ git config --unset column.tag &&
+ cat >expected <<\EOF &&
+a1 aa1 cba t210 t211
+v0.2.1 v1.0 v1.0.1 v1.1.3
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'listing tag with -n --column should fail' '
+ test_must_fail git tag --column -n
+'
+
+test_expect_success 'listing tags -n in column with column.ui ignored' '
+ git config column.ui "row dense" &&
+ COLUMNS=40 git tag -l -n >actual &&
+ git config --unset column.ui &&
+ cat >expected <<\EOF &&
+a1 Foo
+aa1 Foo
+cba Foo
+t210 Foo
+t211 Foo
+v0.2.1 Foo
+v1.0 Foo
+v1.0.1 Foo
+v1.1.3 Foo
+EOF
+ test_cmp expected actual
+'
+
# creating and verifying lightweight tags:
test_expect_success \
@@ -585,23 +630,18 @@ test_expect_success \
test_cmp expect actual
'
-# subsequent tests require gpg; check if it is available
-gpg --version >/dev/null 2>/dev/null
-if [ $? -eq 127 ]; then
- say "# gpg not found - skipping tag signing and verification tests"
-else
- # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
- # the gpg version 1.0.6 didn't parse trust packets correctly, so for
- # that version, creation of signed tags using the generated key fails.
- case "$(gpg --version)" in
- 'gpg (GnuPG) 1.0.6'*)
- say "Skipping signed tag tests, because a bug in 1.0.6 version"
- ;;
- *)
- test_set_prereq GPG
- ;;
- esac
-fi
+test_expect_success 'annotations for blobs are empty' '
+ blob=$(git hash-object -w --stdin <<-\EOF
+ Blob paragraph 1.
+
+ Blob paragraph 2.
+ EOF
+ ) &&
+ git tag tag-blob $blob &&
+ echo "tag-blob " >expect &&
+ git tag -n1 -l tag-blob >actual &&
+ test_cmp expect actual
+'
# trying to verify annotated non-signed tags:
@@ -625,16 +665,6 @@ test_expect_success GPG \
# creating and verifying signed tags:
-# key generation info: gpg --homedir t/t7004 --gen-key
-# Type DSA and Elgamal, size 2048 bits, no expiration date.
-# Name and email: C O Mitter <committer@example.com>
-# No password given, to enable non-interactive operation.
-
-cp -R "$TEST_DIRECTORY"/t7004 ./gpghome
-chmod 0700 gpghome
-GNUPGHOME="$(pwd)/gpghome"
-export GNUPGHOME
-
get_tag_header signed-tag $commit commit $time >expect
echo 'A signed tag message' >>expect
echo '-----BEGIN PGP SIGNATURE-----' >>expect
@@ -1296,4 +1326,43 @@ test_expect_success 'mixing incompatibles modes and options is forbidden' '
test_must_fail git tag -v -s
'
+# check points-at
+
+test_expect_success '--points-at cannot be used in non-list mode' '
+ test_must_fail git tag --points-at=v4.0 foo
+'
+
+test_expect_success '--points-at finds lightweight tags' '
+ echo v4.0 >expect &&
+ git tag --points-at v4.0 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--points-at finds annotated tags of commits' '
+ git tag -m "v4.0, annotated" annotated-v4.0 v4.0 &&
+ echo annotated-v4.0 >expect &&
+ git tag -l --points-at v4.0 "annotated*" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--points-at finds annotated tags of tags' '
+ git tag -m "describing the v4.0 tag object" \
+ annotated-again-v4.0 annotated-v4.0 &&
+ cat >expect <<-\EOF &&
+ annotated-again-v4.0
+ annotated-v4.0
+ EOF
+ git tag --points-at=annotated-v4.0 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'multiple --points-at are OR-ed together' '
+ cat >expect <<-\EOF &&
+ v2.0
+ v3.0
+ EOF
+ git tag --points-at=v2.0 --points-at=v3.0 >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
index ed7575d..ff25908 100755
--- a/t/t7006-pager.sh
+++ b/t/t7006-pager.sh
@@ -6,14 +6,9 @@ test_description='Test automatic use of a pager.'
. "$TEST_DIRECTORY"/lib-pager.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
-cleanup_fail() {
- echo >&2 cleanup failed
- (exit 1)
-}
-
test_expect_success 'setup' '
sane_unset GIT_PAGER GIT_PAGER_IN_USE &&
- test_might_fail git config --unset core.pager &&
+ test_unconfig core.pager &&
PAGER="cat >paginated.out" &&
export PAGER &&
@@ -22,9 +17,7 @@ test_expect_success 'setup' '
'
test_expect_success TTY 'some commands use a pager' '
- rm -f paginated.out ||
- cleanup_fail &&
-
+ rm -f paginated.out &&
test_terminal git log &&
test -e paginated.out
'
@@ -45,70 +38,56 @@ test_expect_failure TTY 'pager runs from subdir' '
'
test_expect_success TTY 'some commands do not use a pager' '
- rm -f paginated.out ||
- cleanup_fail &&
-
+ rm -f paginated.out &&
test_terminal git rev-list HEAD &&
! test -e paginated.out
'
test_expect_success 'no pager when stdout is a pipe' '
- rm -f paginated.out ||
- cleanup_fail &&
-
+ rm -f paginated.out &&
git log | cat &&
! test -e paginated.out
'
test_expect_success 'no pager when stdout is a regular file' '
- rm -f paginated.out ||
- cleanup_fail &&
-
+ rm -f paginated.out &&
git log >file &&
! test -e paginated.out
'
test_expect_success TTY 'git --paginate rev-list uses a pager' '
- rm -f paginated.out ||
- cleanup_fail &&
-
+ rm -f paginated.out &&
test_terminal git --paginate rev-list HEAD &&
test -e paginated.out
'
test_expect_success 'no pager even with --paginate when stdout is a pipe' '
- rm -f file paginated.out ||
- cleanup_fail &&
-
+ rm -f file paginated.out &&
git --paginate log | cat &&
! test -e paginated.out
'
test_expect_success TTY 'no pager with --no-pager' '
- rm -f paginated.out ||
- cleanup_fail &&
-
+ rm -f paginated.out &&
test_terminal git --no-pager log &&
! test -e paginated.out
'
test_expect_success TTY 'configuration can disable pager' '
rm -f paginated.out &&
- test_might_fail git config --unset pager.grep &&
+ test_unconfig pager.grep &&
test_terminal git grep initial &&
test -e paginated.out &&
rm -f paginated.out &&
- git config pager.grep false &&
- test_when_finished "git config --unset pager.grep" &&
+ test_config pager.grep false &&
test_terminal git grep initial &&
! test -e paginated.out
'
test_expect_success TTY 'git config uses a pager if configured to' '
rm -f paginated.out &&
- git config pager.config true &&
- test_when_finished "git config --unset pager.config" &&
+ test_config pager.config true &&
test_terminal git config --list &&
test -e paginated.out
'
@@ -116,8 +95,7 @@ test_expect_success TTY 'git config uses a pager if configured to' '
test_expect_success TTY 'configuration can enable pager (from subdir)' '
rm -f paginated.out &&
mkdir -p subdir &&
- git config pager.bundle true &&
- test_when_finished "git config --unset pager.bundle" &&
+ test_config pager.bundle true &&
git bundle create test.bundle --all &&
rm -f paginated.out subdir/paginated.out &&
@@ -139,9 +117,7 @@ colorful() {
}
test_expect_success 'tests can detect color' '
- rm -f colorful.log colorless.log ||
- cleanup_fail &&
-
+ rm -f colorful.log colorless.log &&
git log --no-color >colorless.log &&
git log --color >colorful.log &&
! colorful colorless.log &&
@@ -150,18 +126,14 @@ test_expect_success 'tests can detect color' '
test_expect_success 'no color when stdout is a regular file' '
rm -f colorless.log &&
- git config color.ui auto ||
- cleanup_fail &&
-
+ test_config color.ui auto &&
git log >colorless.log &&
! colorful colorless.log
'
test_expect_success TTY 'color when writing to a pager' '
rm -f paginated.out &&
- git config color.ui auto ||
- cleanup_fail &&
-
+ test_config color.ui auto &&
(
TERM=vt100 &&
export TERM &&
@@ -170,11 +142,21 @@ test_expect_success TTY 'color when writing to a pager' '
colorful paginated.out
'
+test_expect_success TTY 'colors are suppressed by color.pager' '
+ rm -f paginated.out &&
+ test_config color.ui auto &&
+ test_config color.pager false &&
+ (
+ TERM=vt100 &&
+ export TERM &&
+ test_terminal git log
+ ) &&
+ ! colorful paginated.out
+'
+
test_expect_success 'color when writing to a file intended for a pager' '
rm -f colorful.log &&
- git config color.ui auto ||
- cleanup_fail &&
-
+ test_config color.ui auto &&
(
TERM=vt100 &&
GIT_PAGER_IN_USE=true &&
@@ -184,6 +166,17 @@ test_expect_success 'color when writing to a file intended for a pager' '
colorful colorful.log
'
+test_expect_success TTY 'colors are sent to pager for external commands' '
+ test_config alias.externallog "!git log" &&
+ test_config color.ui auto &&
+ (
+ TERM=vt100 &&
+ export TERM &&
+ test_terminal git -p externallog
+ ) &&
+ colorful paginated.out
+'
+
# Use this helper to make it easy for the caller of your
# terminal-using function to specify whether it should fail.
# If you write
@@ -221,10 +214,8 @@ test_default_pager() {
$test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" "
sane_unset PAGER GIT_PAGER &&
- test_might_fail git config --unset core.pager &&
- rm -f default_pager_used ||
- cleanup_fail &&
-
+ test_unconfig core.pager &&
+ rm -f default_pager_used &&
cat >\$less <<-\EOF &&
#!/bin/sh
wc >default_pager_used
@@ -244,10 +235,8 @@ test_PAGER_overrides() {
$test_expectation TTY "$cmd - PAGER overrides default pager" "
sane_unset GIT_PAGER &&
- test_might_fail git config --unset core.pager &&
- rm -f PAGER_used ||
- cleanup_fail &&
-
+ test_unconfig core.pager &&
+ rm -f PAGER_used &&
PAGER='wc >PAGER_used' &&
export PAGER &&
$full_command &&
@@ -272,12 +261,10 @@ test_core_pager() {
$test_expectation TTY "$cmd - repository-local core.pager setting $used_if_wanted" "
sane_unset GIT_PAGER &&
- rm -f core.pager_used ||
- cleanup_fail &&
-
+ rm -f core.pager_used &&
PAGER=wc &&
export PAGER &&
- git config core.pager 'wc >core.pager_used' &&
+ test_config core.pager 'wc >core.pager_used' &&
$full_command &&
${if_local_config}test -e core.pager_used
"
@@ -301,13 +288,11 @@ test_pager_subdir_helper() {
$test_expectation TTY "$cmd - core.pager $used_if_wanted from subdirectory" "
sane_unset GIT_PAGER &&
rm -f core.pager_used &&
- rm -fr sub ||
- cleanup_fail &&
-
+ rm -fr sub &&
PAGER=wc &&
stampname=\$(pwd)/core.pager_used &&
export PAGER stampname &&
- git config core.pager 'wc >\"\$stampname\"' &&
+ test_config core.pager 'wc >\"\$stampname\"' &&
mkdir sub &&
(
cd sub &&
@@ -321,10 +306,8 @@ test_GIT_PAGER_overrides() {
parse_args "$@"
$test_expectation TTY "$cmd - GIT_PAGER overrides core.pager" "
- rm -f GIT_PAGER_used ||
- cleanup_fail &&
-
- git config core.pager wc &&
+ rm -f GIT_PAGER_used &&
+ test_config core.pager wc &&
GIT_PAGER='wc >GIT_PAGER_used' &&
export GIT_PAGER &&
$full_command &&
@@ -336,9 +319,7 @@ test_doesnt_paginate() {
parse_args "$@"
$test_expectation TTY "no pager for '$cmd'" "
- rm -f GIT_PAGER_used ||
- cleanup_fail &&
-
+ rm -f GIT_PAGER_used &&
GIT_PAGER='wc >GIT_PAGER_used' &&
export GIT_PAGER &&
$full_command &&
@@ -402,21 +383,21 @@ test_core_pager_subdir expect_success test_must_fail \
'git -p apply </dev/null'
test_expect_success TTY 'command-specific pager' '
- unset PAGER GIT_PAGER;
+ sane_unset PAGER GIT_PAGER &&
echo "foo:initial" >expect &&
>actual &&
- git config --unset core.pager &&
- git config pager.log "sed s/^/foo:/ >actual" &&
+ test_unconfig core.pager &&
+ test_config pager.log "sed s/^/foo:/ >actual" &&
test_terminal git log --format=%s -1 &&
test_cmp expect actual
'
test_expect_success TTY 'command-specific pager overrides core.pager' '
- unset PAGER GIT_PAGER;
+ sane_unset PAGER GIT_PAGER &&
echo "foo:initial" >expect &&
>actual &&
- git config core.pager "exit 1"
- git config pager.log "sed s/^/foo:/ >actual" &&
+ test_config core.pager "exit 1"
+ test_config pager.log "sed s/^/foo:/ >actual" &&
test_terminal git log --format=%s -1 &&
test_cmp expect actual
'
@@ -425,9 +406,45 @@ test_expect_success TTY 'command-specific pager overridden by environment' '
GIT_PAGER="sed s/^/foo:/ >actual" && export GIT_PAGER &&
>actual &&
echo "foo:initial" >expect &&
- git config pager.log "exit 1" &&
+ test_config pager.log "exit 1" &&
test_terminal git log --format=%s -1 &&
test_cmp expect actual
'
+test_expect_success 'setup external command' '
+ cat >git-external <<-\EOF &&
+ #!/bin/sh
+ git "$@"
+ EOF
+ chmod +x git-external
+'
+
+test_expect_success TTY 'command-specific pager works for external commands' '
+ sane_unset PAGER GIT_PAGER &&
+ echo "foo:initial" >expect &&
+ >actual &&
+ test_config pager.external "sed s/^/foo:/ >actual" &&
+ test_terminal git --exec-path="`pwd`" external log --format=%s -1 &&
+ test_cmp expect actual
+'
+
+test_expect_success TTY 'sub-commands of externals use their own pager' '
+ sane_unset PAGER GIT_PAGER &&
+ echo "foo:initial" >expect &&
+ >actual &&
+ test_config pager.log "sed s/^/foo:/ >actual" &&
+ test_terminal git --exec-path=. external log --format=%s -1 &&
+ test_cmp expect actual
+'
+
+test_expect_success TTY 'external command pagers override sub-commands' '
+ sane_unset PAGER GIT_PAGER &&
+ >expect &&
+ >actual &&
+ test_config pager.external false &&
+ test_config pager.log "sed s/^/log:/ >actual" &&
+ test_terminal git --exec-path=. external log --format=%s -1 &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh
index e058d18..fd6410f 100755
--- a/t/t7008-grep-binary.sh
+++ b/t/t7008-grep-binary.sh
@@ -84,7 +84,7 @@ test_expect_success 'git grep -Fi Y<NUL>f a' "
git grep -f f -Fi a
"
-test_expect_failure 'git grep -Fi Y<NUL>x a' "
+test_expect_success 'git grep -Fi Y<NUL>x a' "
printf 'YQx' | q_to_nul >f &&
test_must_fail git grep -f f -Fi a
"
@@ -94,9 +94,33 @@ test_expect_success 'git grep y<NUL>f a' "
git grep -f f a
"
-test_expect_failure 'git grep y<NUL>x a' "
+test_expect_success 'git grep y<NUL>x a' "
printf 'yQx' | q_to_nul >f &&
test_must_fail git grep -f f a
"
+test_expect_success 'grep respects binary diff attribute' '
+ echo text >t &&
+ git add t &&
+ echo t:text >expect &&
+ git grep text t >actual &&
+ test_cmp expect actual &&
+ echo "t -diff" >.gitattributes &&
+ echo "Binary file t matches" >expect &&
+ git grep text t >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'grep respects not-binary diff attribute' '
+ echo binQary | q_to_nul >b &&
+ git add b &&
+ echo "Binary file b matches" >expect &&
+ git grep bin b >actual &&
+ test_cmp expect actual &&
+ echo "b diff" >.gitattributes &&
+ echo "b:binQary" >expect &&
+ git grep bin b | nul_to_q >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh
index b8cb490..f4f38a5 100755
--- a/t/t7060-wtstatus.sh
+++ b/t/t7060-wtstatus.sh
@@ -30,6 +30,9 @@ test_expect_success 'Report new path with conflict' '
cat >expect <<EOF
# On branch side
+# You have unmerged paths.
+# (fix conflicts and run "git commit")
+#
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
@@ -118,4 +121,97 @@ test_expect_success 'git diff-index --cached -C shows 2 copies + 1 unmerged' '
test_cmp expected actual
'
+
+test_expect_success 'status when conflicts with add and rm advice (deleted by them)' '
+ git reset --hard &&
+ git checkout master &&
+ test_commit init main.txt init &&
+ git checkout -b second_branch &&
+ git rm main.txt &&
+ git commit -m "main.txt deleted on second_branch" &&
+ test_commit second conflict.txt second &&
+ git checkout master &&
+ test_commit on_second main.txt on_second &&
+ test_commit master conflict.txt master &&
+ test_must_fail git merge second_branch &&
+ cat >expected <<-\EOF &&
+ # On branch master
+ # You have unmerged paths.
+ # (fix conflicts and run "git commit")
+ #
+ # Unmerged paths:
+ # (use "git add/rm <file>..." as appropriate to mark resolution)
+ #
+ # both added: conflict.txt
+ # deleted by them: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for conflicts' '
+ git reset --hard &&
+ git checkout -b conflict &&
+ test_commit one main.txt one &&
+ git branch conflict_second &&
+ git mv main.txt sub_master.txt &&
+ git commit -m "main.txt renamed in sub_master.txt" &&
+ git checkout conflict_second &&
+ git mv main.txt sub_second.txt &&
+ git commit -m "main.txt renamed in sub_second.txt"
+'
+
+
+test_expect_success 'status when conflicts with add and rm advice (both deleted)' '
+ test_must_fail git merge conflict &&
+ cat >expected <<-\EOF &&
+ # On branch conflict_second
+ # You have unmerged paths.
+ # (fix conflicts and run "git commit")
+ #
+ # Unmerged paths:
+ # (use "git add/rm <file>..." as appropriate to mark resolution)
+ #
+ # both deleted: main.txt
+ # added by them: sub_master.txt
+ # added by us: sub_second.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when conflicts with only rm advice (both deleted)' '
+ git reset --hard conflict_second &&
+ test_must_fail git merge conflict &&
+ git add sub_master.txt &&
+ git add sub_second.txt &&
+ cat >expected <<-\EOF &&
+ # On branch conflict_second
+ # You have unmerged paths.
+ # (fix conflicts and run "git commit")
+ #
+ # Changes to be committed:
+ #
+ # new file: sub_master.txt
+ #
+ # Unmerged paths:
+ # (use "git rm <file>..." to mark resolution)
+ #
+ # both deleted: main.txt
+ #
+ # Untracked files not listed (use -u option to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual &&
+ git reset --hard &&
+ git checkout master
+'
+
+
test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 07fb53a..be9672e 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -229,7 +229,7 @@ test_expect_success 'checkout to detach HEAD (with advice declined)' '
git checkout -f renamer && git clean -f &&
git checkout renamer^ 2>messages &&
test_i18ngrep "HEAD is now at 7329388" messages &&
- test 1 -eq $(wc -l <messages) &&
+ test_line_count = 1 messages &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
test "z$H" = "z$M" &&
@@ -247,7 +247,7 @@ test_expect_success 'checkout to detach HEAD' '
git checkout -f renamer && git clean -f &&
git checkout renamer^ 2>messages &&
test_i18ngrep "HEAD is now at 7329388" messages &&
- test 1 -lt $(wc -l <messages) &&
+ test_line_count -gt 1 messages &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
test "z$H" = "z$M" &&
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
index 800b536..ccfb54d 100755
--- a/t/t7300-clean.sh
+++ b/t/t7300-clean.sh
@@ -399,8 +399,8 @@ test_expect_success SANITY 'removal failure' '
'
test_expect_success 'nested git work tree' '
- rm -fr foo bar &&
- mkdir foo bar &&
+ rm -fr foo bar baz &&
+ mkdir -p foo bar baz/boo &&
(
cd foo &&
git init &&
@@ -412,15 +412,24 @@ test_expect_success 'nested git work tree' '
cd bar &&
>goodbye.people
) &&
+ (
+ cd baz/boo &&
+ git init &&
+ >deeper.world
+ git add . &&
+ git commit -a -m deeply.nested
+ ) &&
git clean -f -d &&
test -f foo/.git/index &&
test -f foo/hello.world &&
+ test -f baz/boo/.git/index &&
+ test -f baz/boo/deeper.world &&
! test -d bar
'
test_expect_success 'force removal of nested git work tree' '
- rm -fr foo bar &&
- mkdir foo bar &&
+ rm -fr foo bar baz &&
+ mkdir -p foo bar baz/boo &&
(
cd foo &&
git init &&
@@ -432,9 +441,17 @@ test_expect_success 'force removal of nested git work tree' '
cd bar &&
>goodbye.people
) &&
+ (
+ cd baz/boo &&
+ git init &&
+ >deeper.world
+ git add . &&
+ git commit -a -m deeply.nested
+ ) &&
git clean -f -f -d &&
! test -d foo &&
- ! test -d bar
+ ! test -d bar &&
+ ! test -d baz
'
test_expect_success 'git clean -e' '
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index c22916d..c73bec9 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -79,6 +79,15 @@ test_expect_success 'submodule add' '
cd addtest &&
git submodule add -q "$submodurl" submod >actual &&
test ! -s actual &&
+ echo "gitdir: ../.git/modules/submod" >expect &&
+ test_cmp expect submod/.git &&
+ (
+ cd submod &&
+ git config core.worktree >actual &&
+ echo "../../../submod" >expect &&
+ test_cmp expect actual &&
+ rm -f actual expect
+ ) &&
git submodule init
) &&
@@ -102,7 +111,7 @@ test_expect_success 'submodule add to .gitignored path fails' '
git add --force .gitignore &&
git commit -m"Ignore everything" &&
! git submodule add "$submodurl" submod >actual 2>&1 &&
- test_cmp expect actual
+ test_i18ncmp expect actual
)
'
@@ -225,7 +234,7 @@ EOF
test_expect_success 'status should only print one line' '
git submodule status >lines &&
- test $(wc -l <lines) = 1
+ test_line_count = 1 lines
'
test_expect_success 'setup - fetch commit name from submodule' '
@@ -361,11 +370,11 @@ test_expect_success 'update --init' '
git submodule update init > update.out &&
cat update.out &&
- grep "not initialized" update.out &&
- ! test -d init/.git &&
+ test_i18ngrep "not initialized" update.out &&
+ test_must_fail git rev-parse --resolve-git-dir init/.git &&
git submodule update --init init &&
- test -d init/.git
+ git rev-parse --resolve-git-dir init/.git
'
test_expect_success 'do not add files from a submodule' '
@@ -474,21 +483,72 @@ test_expect_success 'set up for relative path tests' '
git add sub &&
git config -f .gitmodules submodule.sub.path sub &&
git config -f .gitmodules submodule.sub.url ../subrepo &&
- cp .git/config pristine-.git-config
+ cp .git/config pristine-.git-config &&
+ cp .gitmodules pristine-.gitmodules
)
'
-test_expect_success 'relative path works with URL' '
+test_expect_success '../subrepo works with URL - ssh://hostname/repo' '
(
cd reltest &&
cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
git config remote.origin.url ssh://hostname/repo &&
git submodule init &&
test "$(git config submodule.sub.url)" = ssh://hostname/subrepo
)
'
-test_expect_success 'relative path works with user@host:path' '
+test_expect_success '../subrepo works with port-qualified URL - ssh://hostname:22/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ssh://hostname:22/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = ssh://hostname:22/subrepo
+ )
+'
+
+# About the choice of the path in the next test:
+# - double-slash side-steps path mangling issues on Windows
+# - it is still an absolute local path
+# - there cannot be a server with a blank in its name just in case the
+# path is used erroneously to access a //server/share style path
+test_expect_success '../subrepo path works with local path - //somewhere else/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url "//somewhere else/repo" &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = "//somewhere else/subrepo"
+ )
+'
+
+test_expect_success '../subrepo works with file URL - file:///tmp/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url file:///tmp/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = file:///tmp/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with helper URL- helper:://hostname/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url helper:://hostname/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = helper:://hostname/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with scp-style URL - user@host:repo' '
(
cd reltest &&
cp pristine-.git-config .git/config &&
@@ -498,4 +558,109 @@ test_expect_success 'relative path works with user@host:path' '
)
'
+test_expect_success '../subrepo works with scp-style URL - user@host:path/to/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url user@host:path/to/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = user@host:path/to/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - foo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url foo &&
+ # actual: fails with an error
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - foo/bar' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url foo/bar &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = foo/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - ./foo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ./foo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - ./foo/bar' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ./foo/bar &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = foo/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - ../foo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ../foo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = ../subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - ../foo/bar' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ../foo/bar &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = ../foo/subrepo
+ )
+'
+
+test_expect_success '../bar/a/b/c works with relative local path - ../foo/bar.git' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ mkdir -p a/b/c &&
+ (cd a/b/c; git init) &&
+ git config remote.origin.url ../foo/bar.git &&
+ git submodule add ../bar/a/b/c ./a/b/c &&
+ git submodule init &&
+ test "$(git config submodule.a/b/c.url)" = ../foo/bar/a/b/c
+ )
+'
+
+test_expect_success 'moving the superproject does not break submodules' '
+ (
+ cd addtest &&
+ git submodule status >expect
+ )
+ mv addtest addtest2 &&
+ (
+ cd addtest2 &&
+ git submodule status >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh
index 7d7fde0..30b429e 100755
--- a/t/t7401-submodule-summary.sh
+++ b/t/t7401-submodule-summary.sh
@@ -128,7 +128,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --cached' "
< Add foo5
EOF
- test_cmp actual expected
+ test_i18ncmp actual expected
"
test_expect_success 'typechanged submodule(submodule->blob), --files' "
@@ -138,7 +138,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --files' "
> Add foo5
EOF
- test_cmp actual expected
+ test_i18ncmp actual expected
"
rm -rf sm1 &&
@@ -149,7 +149,7 @@ test_expect_success 'typechanged submodule(submodule->blob)' "
* sm1 $head4(submodule)->$head5(blob):
EOF
- test_cmp actual expected
+ test_i18ncmp actual expected
"
rm -f sm1 &&
@@ -162,7 +162,7 @@ test_expect_success 'nonexistent commit' "
Warn: sm1 doesn't contain commit $head4_full
EOF
- test_cmp actual expected
+ test_i18ncmp actual expected
"
commit_file
@@ -173,7 +173,7 @@ test_expect_success 'typechanged submodule(blob->submodule)' "
> Add foo7
EOF
- test_cmp expected actual
+ test_i18ncmp expected actual
"
commit_file sm1 &&
@@ -228,7 +228,7 @@ EOF
test_expect_success '--for-status' "
git submodule summary --for-status HEAD^ >actual &&
- test_cmp actual - <<EOF
+ test_i18ncmp actual - <<EOF
# Submodule changes to be committed:
#
# * sm1 $head6...0000000:
diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh
index 95ffe34..524d5c1 100755
--- a/t/t7403-submodule-sync.sh
+++ b/t/t7403-submodule-sync.sh
@@ -26,7 +26,9 @@ test_expect_success setup '
(cd super-clone && git submodule update --init) &&
git clone super empty-clone &&
(cd empty-clone && git submodule init) &&
- git clone super top-only-clone
+ git clone super top-only-clone &&
+ git clone super relative-clone &&
+ (cd relative-clone && git submodule update --init)
'
test_expect_success 'change submodule' '
@@ -56,8 +58,9 @@ test_expect_success '"git submodule sync" should update submodule URLs' '
git pull --no-recurse-submodules &&
git submodule sync
) &&
- test -d "$(git config -f super-clone/submodule/.git/config \
- remote.origin.url)" &&
+ test -d "$(cd super-clone/submodule &&
+ git config remote.origin.url
+ )" &&
(cd super-clone/submodule &&
git checkout master &&
git pull
@@ -85,4 +88,90 @@ test_expect_success '"git submodule sync" should not vivify uninteresting submod
)
'
+test_expect_success '"git submodule sync" handles origin URL of the form foo' '
+ (cd relative-clone &&
+ git remote set-url origin foo &&
+ git submodule sync &&
+ (cd submodule &&
+ #actual fails with: "cannot strip off url foo
+ test "$(git config remote.origin.url)" = "../submodule"
+ )
+ )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form foo/bar' '
+ (cd relative-clone &&
+ git remote set-url origin foo/bar &&
+ git submodule sync &&
+ (cd submodule &&
+ #actual foo/submodule
+ test "$(git config remote.origin.url)" = "../foo/submodule"
+ )
+ )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ./foo' '
+ (cd relative-clone &&
+ git remote set-url origin ./foo &&
+ git submodule sync &&
+ (cd submodule &&
+ #actual ./submodule
+ test "$(git config remote.origin.url)" = "../submodule"
+ )
+ )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ./foo/bar' '
+ (cd relative-clone &&
+ git remote set-url origin ./foo/bar &&
+ git submodule sync &&
+ (cd submodule &&
+ #actual ./foo/submodule
+ test "$(git config remote.origin.url)" = "../foo/submodule"
+ )
+ )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ../foo' '
+ (cd relative-clone &&
+ git remote set-url origin ../foo &&
+ git submodule sync &&
+ (cd submodule &&
+ #actual ../submodule
+ test "$(git config remote.origin.url)" = "../../submodule"
+ )
+ )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar' '
+ (cd relative-clone &&
+ git remote set-url origin ../foo/bar &&
+ git submodule sync &&
+ (cd submodule &&
+ #actual ../foo/submodule
+ test "$(git config remote.origin.url)" = "../../foo/submodule"
+ )
+ )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar with deeply nested submodule' '
+ (cd relative-clone &&
+ git remote set-url origin ../foo/bar &&
+ mkdir -p a/b/c &&
+ ( cd a/b/c &&
+ git init &&
+ :> .gitignore &&
+ git add .gitignore &&
+ test_tick &&
+ git commit -m "initial commit" ) &&
+ git submodule add ../bar/a/b/c ./a/b/c &&
+ git submodule sync &&
+ (cd a/b/c &&
+ #actual ../foo/bar/a/b/c
+ test "$(git config remote.origin.url)" = "../../../../foo/bar/a/b/c"
+ )
+ )
+'
+
+
test_done
diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh
index a8fb30b..0d5b42a 100755
--- a/t/t7405-submodule-merge.sh
+++ b/t/t7405-submodule-merge.sh
@@ -228,4 +228,55 @@ test_expect_success 'merging with a modify/modify conflict between merge bases'
git merge d
'
+# canonical criss-cross history in top and submodule
+test_expect_success 'setup for recursive merge with submodule' '
+ mkdir merge-recursive &&
+ (cd merge-recursive &&
+ git init &&
+ mkdir sub &&
+ (cd sub &&
+ git init &&
+ test_commit a &&
+ git checkout -b sub-b master &&
+ test_commit b &&
+ git checkout -b sub-c master &&
+ test_commit c &&
+ git checkout -b sub-bc sub-b &&
+ git merge sub-c &&
+ git checkout -b sub-cb sub-c &&
+ git merge sub-b &&
+ git checkout master) &&
+ git add sub &&
+ git commit -m a &&
+ git checkout -b top-b master &&
+ (cd sub && git checkout sub-b) &&
+ git add sub &&
+ git commit -m b &&
+ git checkout -b top-c master &&
+ (cd sub && git checkout sub-c) &&
+ git add sub &&
+ git commit -m c &&
+ git checkout -b top-bc top-b &&
+ git merge -s ours --no-commit top-c &&
+ (cd sub && git checkout sub-bc) &&
+ git add sub &&
+ git commit -m bc &&
+ git checkout -b top-cb top-c &&
+ git merge -s ours --no-commit top-b &&
+ (cd sub && git checkout sub-cb) &&
+ git add sub &&
+ git commit -m cb)
+'
+
+# merge should leave submodule unmerged in index
+test_expect_success 'recursive merge with submodule' '
+ (cd merge-recursive &&
+ test_must_fail git merge top-bc &&
+ echo "160000 $(git rev-parse top-cb:sub) 2 sub" > expect2 &&
+ echo "160000 $(git rev-parse top-bc:sub) 3 sub" > expect3 &&
+ git ls-files -u > actual &&
+ grep "$(cat expect2)" actual > /dev/null &&
+ grep "$(cat expect3)" actual > /dev/null)
+'
+
test_done
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 4f16fcc..dcb195b 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -30,6 +30,7 @@ test_expect_success 'setup a submodule tree' '
git clone super submodule &&
git clone super rebasing &&
git clone super merging &&
+ git clone super none &&
(cd super &&
git submodule add ../submodule submodule &&
test_tick &&
@@ -58,6 +59,11 @@ test_expect_success 'setup a submodule tree' '
test_tick &&
git commit -m "rebasing"
)
+ (cd super &&
+ git submodule add ../none none &&
+ test_tick &&
+ git commit -m "none"
+ )
'
test_expect_success 'submodule update detaching the HEAD ' '
@@ -90,7 +96,7 @@ test_expect_success 'submodule update does not fetch already present commits' '
(cd super &&
git submodule update > ../actual 2> ../actual.err
) &&
- test_cmp expected actual &&
+ test_i18ncmp expected actual &&
! test -s actual.err
'
@@ -298,4 +304,336 @@ test_expect_success 'submodule update ignores update=rebase config for new submo
)
'
+test_expect_success 'submodule init picks up update=none' '
+ (cd super &&
+ git config -f .gitmodules submodule.none.update none &&
+ git submodule init none &&
+ test "none" = "$(git config submodule.none.update)"
+ )
+'
+
+test_expect_success 'submodule update - update=none in .git/config' '
+ (cd super &&
+ git config submodule.submodule.update none &&
+ (cd submodule &&
+ git checkout master &&
+ compare_head
+ ) &&
+ git diff --raw | grep " submodule" &&
+ git submodule update &&
+ git diff --raw | grep " submodule" &&
+ (cd submodule &&
+ compare_head
+ ) &&
+ git config --unset submodule.submodule.update &&
+ git submodule update submodule
+ )
+'
+
+test_expect_success 'submodule update - update=none in .git/config but --checkout given' '
+ (cd super &&
+ git config submodule.submodule.update none &&
+ (cd submodule &&
+ git checkout master &&
+ compare_head
+ ) &&
+ git diff --raw | grep " submodule" &&
+ git submodule update --checkout &&
+ test_must_fail git diff --raw \| grep " submodule" &&
+ (cd submodule &&
+ test_must_fail compare_head
+ ) &&
+ git config --unset submodule.submodule.update
+ )
+'
+
+test_expect_success 'submodule update --init skips submodule with update=none' '
+ (cd super &&
+ git add .gitmodules &&
+ git commit -m ".gitmodules"
+ ) &&
+ git clone super cloned &&
+ (cd cloned &&
+ git submodule update --init &&
+ test -e submodule/.git &&
+ test_must_fail test -e none/.git
+ )
+'
+
+test_expect_success 'submodule update continues after checkout error' '
+ (cd super &&
+ git reset --hard HEAD &&
+ git submodule add ../submodule submodule2 &&
+ git submodule init &&
+ git commit -am "new_submodule" &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../expect
+ ) &&
+ (cd submodule &&
+ test_commit "update_submodule" file
+ ) &&
+ (cd submodule2 &&
+ test_commit "update_submodule2" file
+ ) &&
+ git add submodule &&
+ git add submodule2 &&
+ git commit -m "two_new_submodule_commits" &&
+ (cd submodule &&
+ echo "" > file
+ ) &&
+ git checkout HEAD^ &&
+ test_must_fail git submodule update &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../actual
+ ) &&
+ test_cmp expect actual
+ )
+'
+test_expect_success 'submodule update continues after recursive checkout error' '
+ (cd super &&
+ git reset --hard HEAD &&
+ git checkout master &&
+ git submodule update &&
+ (cd submodule &&
+ git submodule add ../submodule subsubmodule &&
+ git submodule init &&
+ git commit -m "new_subsubmodule"
+ ) &&
+ git add submodule &&
+ git commit -m "update_submodule" &&
+ (cd submodule &&
+ (cd subsubmodule &&
+ test_commit "update_subsubmodule" file
+ ) &&
+ git add subsubmodule &&
+ test_commit "update_submodule_again" file &&
+ (cd subsubmodule &&
+ test_commit "update_subsubmodule_again" file
+ ) &&
+ test_commit "update_submodule_again_again" file
+ ) &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../expect &&
+ test_commit "update_submodule2_again" file
+ ) &&
+ git add submodule &&
+ git add submodule2 &&
+ git commit -m "new_commits" &&
+ git checkout HEAD^ &&
+ (cd submodule &&
+ git checkout HEAD^ &&
+ (cd subsubmodule &&
+ echo "" > file
+ )
+ ) &&
+ test_must_fail git submodule update --recursive &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../actual
+ ) &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'submodule update exit immediately in case of merge conflict' '
+ (cd super &&
+ git checkout master &&
+ git reset --hard HEAD &&
+ (cd submodule &&
+ (cd subsubmodule &&
+ git reset --hard HEAD
+ )
+ ) &&
+ git submodule update --recursive &&
+ (cd submodule &&
+ test_commit "update_submodule_2" file
+ ) &&
+ (cd submodule2 &&
+ test_commit "update_submodule2_2" file
+ ) &&
+ git add submodule &&
+ git add submodule2 &&
+ git commit -m "two_new_submodule_commits" &&
+ (cd submodule &&
+ git checkout master &&
+ test_commit "conflict" file &&
+ echo "conflict" > file
+ ) &&
+ git checkout HEAD^ &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../expect
+ ) &&
+ git config submodule.submodule.update merge &&
+ test_must_fail git submodule update &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../actual
+ ) &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'submodule update exit immediately after recursive rebase error' '
+ (cd super &&
+ git checkout master &&
+ git reset --hard HEAD &&
+ (cd submodule &&
+ git reset --hard HEAD &&
+ git submodule update --recursive
+ ) &&
+ (cd submodule &&
+ test_commit "update_submodule_3" file
+ ) &&
+ (cd submodule2 &&
+ test_commit "update_submodule2_3" file
+ ) &&
+ git add submodule &&
+ git add submodule2 &&
+ git commit -m "two_new_submodule_commits" &&
+ (cd submodule &&
+ git checkout master &&
+ test_commit "conflict2" file &&
+ echo "conflict" > file
+ ) &&
+ git checkout HEAD^ &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../expect
+ ) &&
+ git config submodule.submodule.update rebase &&
+ test_must_fail git submodule update &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../actual
+ ) &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'add different submodules to the same path' '
+ (cd super &&
+ git submodule add ../submodule s1 &&
+ test_must_fail git submodule add ../merging s1
+ )
+'
+
+test_expect_success 'submodule add places git-dir in superprojects git-dir' '
+ (cd super &&
+ mkdir deeper &&
+ git submodule add ../submodule deeper/submodule &&
+ (cd deeper/submodule &&
+ git log > ../../expected
+ ) &&
+ (cd .git/modules/deeper/submodule &&
+ git log > ../../../../actual
+ ) &&
+ test_cmp actual expected
+ )
+'
+
+test_expect_success 'submodule update places git-dir in superprojects git-dir' '
+ (cd super &&
+ git commit -m "added submodule"
+ ) &&
+ git clone super super2 &&
+ (cd super2 &&
+ git submodule init deeper/submodule &&
+ git submodule update &&
+ (cd deeper/submodule &&
+ git log > ../../expected
+ ) &&
+ (cd .git/modules/deeper/submodule &&
+ git log > ../../../../actual
+ ) &&
+ test_cmp actual expected
+ )
+'
+
+test_expect_success 'submodule add places git-dir in superprojects git-dir recursive' '
+ (cd super2 &&
+ (cd deeper/submodule &&
+ git submodule add ../submodule subsubmodule &&
+ (cd subsubmodule &&
+ git log > ../../../expected
+ ) &&
+ git commit -m "added subsubmodule" &&
+ git push
+ ) &&
+ (cd .git/modules/deeper/submodule/modules/subsubmodule &&
+ git log > ../../../../../actual
+ ) &&
+ git add deeper/submodule &&
+ git commit -m "update submodule" &&
+ git push &&
+ test_cmp actual expected
+ )
+'
+
+test_expect_success 'submodule update places git-dir in superprojects git-dir recursive' '
+ mkdir super_update_r &&
+ (cd super_update_r &&
+ git init --bare
+ ) &&
+ mkdir subsuper_update_r &&
+ (cd subsuper_update_r &&
+ git init --bare
+ ) &&
+ mkdir subsubsuper_update_r &&
+ (cd subsubsuper_update_r &&
+ git init --bare
+ ) &&
+ git clone subsubsuper_update_r subsubsuper_update_r2 &&
+ (cd subsubsuper_update_r2 &&
+ test_commit "update_subsubsuper" file &&
+ git push origin master
+ ) &&
+ git clone subsuper_update_r subsuper_update_r2 &&
+ (cd subsuper_update_r2 &&
+ test_commit "update_subsuper" file &&
+ git submodule add ../subsubsuper_update_r subsubmodule &&
+ git commit -am "subsubmodule" &&
+ git push origin master
+ ) &&
+ git clone super_update_r super_update_r2 &&
+ (cd super_update_r2 &&
+ test_commit "update_super" file &&
+ git submodule add ../subsuper_update_r submodule &&
+ git commit -am "submodule" &&
+ git push origin master
+ ) &&
+ rm -rf super_update_r2 &&
+ git clone super_update_r super_update_r2 &&
+ (cd super_update_r2 &&
+ git submodule update --init --recursive &&
+ (cd submodule/subsubmodule &&
+ git log > ../../expected
+ ) &&
+ (cd .git/modules/submodule/modules/subsubmodule
+ git log > ../../../../../actual
+ )
+ test_cmp actual expected
+ )
+'
+
+test_expect_success 'submodule add properly re-creates deeper level submodules' '
+ (cd super &&
+ git reset --hard master &&
+ rm -rf deeper/ &&
+ git submodule add ../submodule deeper/submodule
+ )
+'
+
+test_expect_success 'submodule update properly revives a moved submodule' '
+ (cd super &&
+ git commit -am "pre move" &&
+ git status >expect&&
+ H=$(cd submodule2; git rev-parse HEAD) &&
+ git rm --cached submodule2 &&
+ rm -rf submodule2 &&
+ mkdir -p "moved/sub module" &&
+ git update-index --add --cacheinfo 160000 $H "moved/sub module" &&
+ git config -f .gitmodules submodule.submodule2.path "moved/sub module"
+ git commit -am "post move" &&
+ git submodule update &&
+ git status >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh
index 835a506..9b69fe2 100755
--- a/t/t7407-submodule-foreach.sh
+++ b/t/t7407-submodule-foreach.sh
@@ -77,7 +77,7 @@ test_expect_success 'test basic "submodule foreach" usage' '
git config foo.bar zar &&
git submodule foreach "git config --file \"\$toplevel/.git/config\" foo.bar"
) &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_expect_success 'setup nested submodules' '
@@ -118,19 +118,19 @@ test_expect_success 'use "submodule foreach" to checkout 2nd level submodule' '
git clone super clone2 &&
(
cd clone2 &&
- test ! -d sub1/.git &&
- test ! -d sub2/.git &&
- test ! -d sub3/.git &&
- test ! -d nested1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+ test_must_fail git rev-parse --resolve-git-dir nested1/.git &&
git submodule update --init &&
- test -d sub1/.git &&
- test -d sub2/.git &&
- test -d sub3/.git &&
- test -d nested1/.git &&
- test ! -d nested1/nested2/.git &&
+ git rev-parse --resolve-git-dir sub1/.git &&
+ git rev-parse --resolve-git-dir sub2/.git &&
+ git rev-parse --resolve-git-dir sub3/.git &&
+ git rev-parse --resolve-git-dir nested1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir nested1/nested2/.git &&
git submodule foreach "git submodule update --init" &&
- test -d nested1/nested2/.git &&
- test ! -d nested1/nested2/nested3/.git
+ git rev-parse --resolve-git-dir nested1/nested1/nested2/.git
+ test_must_fail git rev-parse --resolve-git-dir nested1/nested2/nested3/.git
)
'
@@ -138,8 +138,8 @@ test_expect_success 'use "foreach --recursive" to checkout all submodules' '
(
cd clone2 &&
git submodule foreach --recursive "git submodule update --init" &&
- test -d nested1/nested2/nested3/.git &&
- test -d nested1/nested2/nested3/submodule/.git
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
)
'
@@ -158,7 +158,7 @@ test_expect_success 'test messages from "foreach --recursive"' '
cd clone2 &&
git submodule foreach --recursive "true" > ../actual
) &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
cat > expect <<EOF
@@ -183,18 +183,18 @@ test_expect_success 'use "update --recursive" to checkout all submodules' '
git clone super clone3 &&
(
cd clone3 &&
- test ! -d sub1/.git &&
- test ! -d sub2/.git &&
- test ! -d sub3/.git &&
- test ! -d nested1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+ test_must_fail git rev-parse --resolve-git-dir nested1/.git &&
git submodule update --init --recursive &&
- test -d sub1/.git &&
- test -d sub2/.git &&
- test -d sub3/.git &&
- test -d nested1/.git &&
- test -d nested1/nested2/.git &&
- test -d nested1/nested2/nested3/.git &&
- test -d nested1/nested2/nested3/submodule/.git
+ git rev-parse --resolve-git-dir sub1/.git &&
+ git rev-parse --resolve-git-dir sub2/.git &&
+ git rev-parse --resolve-git-dir sub3/.git &&
+ git rev-parse --resolve-git-dir nested1/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
)
'
@@ -247,14 +247,17 @@ test_expect_success 'ensure "status --cached --recursive" preserves the --cached
test_expect_success 'use "git clone --recursive" to checkout all submodules' '
git clone --recursive super clone4 &&
- test -d clone4/.git &&
- test -d clone4/sub1/.git &&
- test -d clone4/sub2/.git &&
- test -d clone4/sub3/.git &&
- test -d clone4/nested1/.git &&
- test -d clone4/nested1/nested2/.git &&
- test -d clone4/nested1/nested2/nested3/.git &&
- test -d clone4/nested1/nested2/nested3/submodule/.git
+ (
+ cd clone4 &&
+ git rev-parse --resolve-git-dir .git &&
+ git rev-parse --resolve-git-dir sub1/.git &&
+ git rev-parse --resolve-git-dir sub2/.git &&
+ git rev-parse --resolve-git-dir sub3/.git &&
+ git rev-parse --resolve-git-dir nested1/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
+ )
'
test_expect_success 'test "update --recursive" with a flag with spaces' '
@@ -262,14 +265,14 @@ test_expect_success 'test "update --recursive" with a flag with spaces' '
git clone super clone5 &&
(
cd clone5 &&
- test ! -d nested1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir d nested1/.git &&
git submodule update --init --recursive --reference="$(dirname "$PWD")/common objects" &&
- test -d nested1/.git &&
- test -d nested1/nested2/.git &&
- test -d nested1/nested2/nested3/.git &&
- test -f nested1/.git/objects/info/alternates &&
- test -f nested1/nested2/.git/objects/info/alternates &&
- test -f nested1/nested2/nested3/.git/objects/info/alternates
+ git rev-parse --resolve-git-dir nested1/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+ test -f .git/modules/nested1/objects/info/alternates &&
+ test -f .git/modules/nested1/modules/nested2/objects/info/alternates &&
+ test -f .git/modules/nested1/modules/nested2/modules/nested3/objects/info/alternates
)
'
@@ -277,18 +280,18 @@ test_expect_success 'use "update --recursive nested1" to checkout all submodules
git clone super clone6 &&
(
cd clone6 &&
- test ! -d sub1/.git &&
- test ! -d sub2/.git &&
- test ! -d sub3/.git &&
- test ! -d nested1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+ test_must_fail git rev-parse --resolve-git-dir nested1/.git &&
git submodule update --init --recursive -- nested1 &&
- test ! -d sub1/.git &&
- test ! -d sub2/.git &&
- test ! -d sub3/.git &&
- test -d nested1/.git &&
- test -d nested1/nested2/.git &&
- test -d nested1/nested2/nested3/.git &&
- test -d nested1/nested2/nested3/submodule/.git
+ test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+ test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+ git rev-parse --resolve-git-dir nested1/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+ git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
)
'
diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh
index cc16d3f..b770b2f 100755
--- a/t/t7408-submodule-reference.sh
+++ b/t/t7408-submodule-reference.sh
@@ -28,7 +28,7 @@ git prune'
cd "$base_dir"
-test_expect_success 'preparing supermodule' \
+test_expect_success 'preparing superproject' \
'test_create_repo super && cd super &&
echo file > file &&
git add file &&
@@ -43,7 +43,7 @@ git commit -m B-super-added'
cd "$base_dir"
test_expect_success 'after add: existence of info/alternates' \
-'test `wc -l <super/sub/.git/objects/info/alternates` = 1'
+'test_line_count = 1 super/.git/modules/sub/objects/info/alternates'
cd "$base_dir"
@@ -55,7 +55,7 @@ diff expected current'
cd "$base_dir"
-test_expect_success 'cloning supermodule' \
+test_expect_success 'cloning superproject' \
'git clone super super-clone'
cd "$base_dir"
@@ -66,7 +66,7 @@ test_expect_success 'update with reference' \
cd "$base_dir"
test_expect_success 'after update: existence of info/alternates' \
-'test `wc -l <super-clone/sub/.git/objects/info/alternates` = 1'
+'test_line_count = 1 super-clone/.git/modules/sub/objects/info/alternates'
cd "$base_dir"
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 3ad0436..b20ca0e 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -8,39 +8,41 @@
test_description='git commit'
. ./test-lib.sh
+. "$TEST_DIRECTORY/diff-lib.sh"
-test_tick
+author='The Real Author <someguy@his.email.org>'
-test_expect_success \
- "initial status" \
- "echo 'bongo bongo' >file &&
- git add file"
+test_tick
-test_expect_success "Constructing initial commit" '
+test_expect_success 'initial status' '
+ echo bongo bongo >file &&
+ git add file &&
git status >actual &&
test_i18ngrep "Initial commit" actual
'
-test_expect_success \
- "fail initial amend" \
- "test_must_fail git commit --amend"
+test_expect_success 'fail initial amend' '
+ test_must_fail git commit --amend
+'
-test_expect_success \
- "initial commit" \
- "git commit -m initial"
+test_expect_success 'setup: initial commit' '
+ git commit -m initial
+'
-test_expect_success \
- "invalid options 1" \
- "test_must_fail git commit -m foo -m bar -F file"
+test_expect_success '-m and -F do not mix' '
+ git checkout HEAD file && echo >>file && git add file &&
+ test_must_fail git commit -m foo -m bar -F file
+'
-test_expect_success \
- "invalid options 2" \
- "test_must_fail git commit -C HEAD -m illegal"
+test_expect_success '-m and -C do not mix' '
+ git checkout HEAD file && echo >>file && git add file &&
+ test_must_fail git commit -C HEAD -m illegal
+'
-test_expect_success \
- "using paths with -a" \
- "echo King of the bongo >file &&
- test_must_fail git commit -m foo -a file"
+test_expect_success 'paths and -a do not mix' '
+ echo King of the bongo >file &&
+ test_must_fail git commit -m foo -a file
+'
test_expect_success PERL 'can use paths with --interactive' '
echo bong-o-bong >file &&
@@ -50,139 +52,175 @@ test_expect_success PERL 'can use paths with --interactive' '
git reset --hard HEAD^
'
-test_expect_success \
- "using invalid commit with -C" \
- "test_must_fail git commit -C bogus"
+test_expect_success 'using invalid commit with -C' '
+ test_must_fail git commit -C bogus
+'
-test_expect_success \
- "testing nothing to commit" \
- "test_must_fail git commit -m initial"
+test_expect_success 'nothing to commit' '
+ test_must_fail git commit -m initial
+'
+
+test_expect_success 'setup: non-initial commit' '
+ echo bongo bongo bongo >file &&
+ git commit -m next -a
+'
-test_expect_success \
- "next commit" \
- "echo 'bongo bongo bongo' >file \
- git commit -m next -a"
+test_expect_success 'commit message from non-existing file' '
+ echo more bongo: bongo bongo bongo bongo >file &&
+ test_must_fail git commit -F gah -a
+'
-test_expect_success \
- "commit message from non-existing file" \
- "echo 'more bongo: bongo bongo bongo bongo' >file && \
- test_must_fail git commit -F gah -a"
+test_expect_success 'empty commit message' '
+ # Empty except stray tabs and spaces on a few lines.
+ sed -e "s/@//g" >msg <<-\EOF &&
+ @ @
+ @@
+ @ @
+ @Signed-off-by: hula@
+ EOF
+ test_must_fail git commit -F msg -a
+'
-# Empty except stray tabs and spaces on a few lines.
-sed -e 's/@$//' >msg <<EOF
- @
+test_expect_success 'template "emptyness" check does not kick in with -F' '
+ git checkout HEAD file && echo >>file && git add file &&
+ git commit -t file -F file
+'
- @
-Signed-off-by: hula
-EOF
-test_expect_success \
- "empty commit message" \
- "test_must_fail git commit -F msg -a"
+test_expect_success 'template "emptyness" check' '
+ git checkout HEAD file && echo >>file && git add file &&
+ test_must_fail git commit -t file 2>err &&
+ test_i18ngrep "did not edit" err
+'
-test_expect_success \
- "commit message from file" \
- "echo 'this is the commit message, coming from a file' >msg && \
- git commit -F msg -a"
+test_expect_success 'setup: commit message from file' '
+ git checkout HEAD file && echo >>file && git add file &&
+ echo this is the commit message, coming from a file >msg &&
+ git commit -F msg -a
+'
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
+test_expect_success 'amend commit' '
+ cat >editor <<-\EOF &&
+ #!/bin/sh
+ sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
+ mv "$1-" "$1"
+ EOF
+ chmod 755 editor &&
+ EDITOR=./editor git commit --amend
+'
-test_expect_success \
- "amend commit" \
- "EDITOR=./editor git commit --amend"
+test_expect_success 'set up editor' '
+ cat >editor <<-\EOF &&
+ #!/bin/sh
+ sed -e "s/unamended/amended/g" <"$1" >"$1-"
+ mv "$1-" "$1"
+ EOF
+ chmod 755 editor
+'
-test_expect_success \
- "passing -m and -F" \
- "echo 'enough with the bongos' >file && \
- test_must_fail git commit -F msg -m amending ."
+test_expect_success 'amend without launching editor' '
+ echo unamended >expect &&
+ git commit --allow-empty -m "unamended" &&
+ echo needs more bongo >file &&
+ git add file &&
+ EDITOR=./editor git commit --no-edit --amend &&
+ git diff --exit-code HEAD -- file &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ test_cmp expect msg
+'
-test_expect_success \
- "using message from other commit" \
- "git commit -C HEAD^ ."
+test_expect_success '--amend --edit' '
+ echo amended >expect &&
+ git commit --allow-empty -m "unamended" &&
+ echo bongo again >file &&
+ git add file &&
+ EDITOR=./editor git commit --edit --amend &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ test_cmp expect msg
+'
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/amend/older/g" < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
-
-test_expect_success \
- "editing message from other commit" \
- "echo 'hula hula' >file && \
- EDITOR=./editor git commit -c HEAD^ -a"
-
-test_expect_success \
- "message from stdin" \
- "echo 'silly new contents' >file && \
- echo commit message from stdin | git commit -F - -a"
-
-test_expect_success \
- "overriding author from command line" \
- "echo 'gak' >file && \
- git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a >output 2>&1"
-
-test_expect_success \
- "commit --author output mentions author" \
- "grep Rubber.Duck output"
-
-test_expect_success PERL \
- "interactive add" \
- "echo 7 | git commit --interactive | grep 'What now'"
-
-test_expect_success PERL \
- "commit --interactive doesn't change index if editor aborts" \
- "echo zoo >file &&
+test_expect_success '-m --edit' '
+ echo amended >expect &&
+ git commit --allow-empty -m buffer &&
+ echo bongo bongo >file &&
+ git add file &&
+ EDITOR=./editor git commit -m unamended --edit &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ test_cmp expect msg
+'
+
+test_expect_success '-m and -F do not mix' '
+ echo enough with the bongos >file &&
+ test_must_fail git commit -F msg -m amending .
+'
+
+test_expect_success 'using message from other commit' '
+ git commit -C HEAD^ .
+'
+
+test_expect_success 'editing message from other commit' '
+ cat >editor <<-\EOF &&
+ #!/bin/sh
+ sed -e "s/amend/older/g" < "$1" > "$1-"
+ mv "$1-" "$1"
+ EOF
+ chmod 755 editor &&
+ echo hula hula >file &&
+ EDITOR=./editor git commit -c HEAD^ -a
+'
+
+test_expect_success 'message from stdin' '
+ echo silly new contents >file &&
+ echo commit message from stdin |
+ git commit -F - -a
+'
+
+test_expect_success 'overriding author from command line' '
+ echo gak >file &&
+ git commit -m author \
+ --author "Rubber Duck <rduck@convoy.org>" -a >output 2>&1 &&
+ grep Rubber.Duck output
+'
+
+test_expect_success PERL 'interactive add' '
+ echo 7 |
+ git commit --interactive |
+ grep "What now"
+'
+
+test_expect_success PERL "commit --interactive doesn't change index if editor aborts" '
+ echo zoo >file &&
test_must_fail git diff --exit-code >diff1 &&
- (echo u ; echo '*' ; echo q) |
- (EDITOR=: && export EDITOR &&
- test_must_fail git commit --interactive) &&
+ (echo u ; echo "*" ; echo q) |
+ (
+ EDITOR=: &&
+ export EDITOR &&
+ test_must_fail git commit --interactive
+ ) &&
git diff >diff2 &&
- test_cmp diff1 diff2"
-
-test_expect_success \
- "showing committed revisions" \
- "git rev-list HEAD >current"
+ compare_diff_patch diff1 diff2
+'
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/good/bad/g" < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
-
-cat >msg <<EOF
-A good commit message.
-EOF
-
-test_expect_success \
- 'editor not invoked if -F is given' '
- echo "moo" >file &&
- EDITOR=./editor git commit -a -F msg &&
- git show -s --pretty=format:"%s" | grep -q good &&
- echo "quack" >file &&
- echo "Another good message." | EDITOR=./editor git commit -a -F - &&
- git show -s --pretty=format:"%s" | grep -q good
- '
-# We could just check the head sha1, but checking each commit makes it
-# easier to isolate bugs.
-
-cat >expected <<\EOF
-72c0dc9855b0c9dadcbfd5a31cab072e0cb774ca
-9b88fc14ce6b32e3d9ee021531a54f18a5cf38a2
-3536bbb352c3a1ef9a420f5b4242d48578b92aa7
-d381ac431806e53f3dd7ac2f1ae0534f36d738b9
-4fd44095ad6334f3ef72e4c5ec8ddf108174b54a
-402702b49136e7587daa9280e91e4bb7cb2179f7
-EOF
-
-test_expect_success \
- 'validate git rev-list output.' \
- 'test_cmp expected current'
+test_expect_success 'editor not invoked if -F is given' '
+ cat >editor <<-\EOF &&
+ #!/bin/sh
+ sed -e s/good/bad/g <"$1" >"$1-"
+ mv "$1-" "$1"
+ EOF
+ chmod 755 editor &&
+
+ echo A good commit message. >msg &&
+ echo moo >file &&
+
+ EDITOR=./editor git commit -a -F msg &&
+ git show -s --pretty=format:%s >subject &&
+ grep -q good subject &&
+
+ echo quack >file &&
+ echo Another good message. |
+ EDITOR=./editor git commit -a -F - &&
+ git show -s --pretty=format:%s >subject &&
+ grep -q good subject
+'
test_expect_success 'partial commit that involves removal (1)' '
@@ -216,7 +254,6 @@ 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 &&
@@ -345,7 +382,6 @@ test_expect_success 'multiple -m' '
'
-author="The Real Author <someguy@his.email.org>"
test_expect_success 'amend commit to fix author' '
oldtick=$GIT_AUTHOR_DATE &&
@@ -372,15 +408,8 @@ test_expect_success 'git commit <file> with dirty index' '
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
+ git reset --hard &&
+ test_must_fail git commit -m empty
'
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index 3f3adc3..181456a 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
@@ -335,7 +335,7 @@ test_expect_success 'A single-liner subject with a token plus colon is not a foo
git reset --hard &&
git commit -s -m "hello: kitty" --allow-empty &&
git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
- test $(wc -l <actual) = 3
+ test_line_count = 3 actual
'
diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh
index 8528f64..984889b 100755
--- a/t/t7503-pre-commit-hook.sh
+++ b/t/t7503-pre-commit-hook.sh
@@ -84,5 +84,56 @@ test_expect_success POSIXPERM '--no-verify with non-executable hook' '
git commit --no-verify -m "more content"
'
+chmod +x "$HOOK"
+
+# a hook that checks $GIT_PREFIX and succeeds inside the
+# success/ subdirectory only
+cat > "$HOOK" <<EOF
+#!/bin/sh
+test \$GIT_PREFIX = success/
+EOF
+
+test_expect_success 'with hook requiring GIT_PREFIX' '
+
+ echo "more content" >> file &&
+ git add file &&
+ mkdir success &&
+ (
+ cd success &&
+ git commit -m "hook requires GIT_PREFIX = success/"
+ ) &&
+ rmdir success
+'
+
+test_expect_success 'with failing hook requiring GIT_PREFIX' '
+
+ echo "more content" >> file &&
+ git add file &&
+ mkdir fail &&
+ (
+ cd fail &&
+ test_must_fail git commit -m "hook must fail"
+ ) &&
+ rmdir fail &&
+ git checkout -- file
+'
+
+test_expect_success 'check the author in hook' '
+ write_script "$HOOK" <<-\EOF &&
+ test "$GIT_AUTHOR_NAME" = "New Author" &&
+ test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com"
+ EOF
+ test_must_fail git commit --allow-empty -m "by a.u.thor" &&
+ (
+ GIT_AUTHOR_NAME="New Author" &&
+ GIT_AUTHOR_EMAIL="newauthor@example.com" &&
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
+ git commit --allow-empty -m "by new.author via env" &&
+ git show -s
+ ) &&
+ git commit --author="New Author <newauthor@example.com>" \
+ --allow-empty -m "by new.author via command line" &&
+ git show -s
+'
test_done
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 905255a..c206f47 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -59,6 +59,30 @@ test_expect_success 'status (1)' '
test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output
'
+test_expect_success 'status --column' '
+ COLUMNS=50 git status --column="column dense" >output &&
+ cat >expect <<\EOF &&
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# new file: dir2/added
+#
+# Changes not staged for commit:
+# (use "git add <file>..." to update what will be committed)
+# (use "git checkout -- <file>..." to discard changes in working directory)
+#
+# modified: dir1/modified
+#
+# Untracked files:
+# (use "git add <file>..." to include in what will be committed)
+#
+# dir1/untracked dir2/untracked untracked
+# dir2/modified output
+EOF
+ test_cmp expect output
+'
+
cat >expect <<\EOF
# On branch master
# Changes to be committed:
@@ -189,7 +213,7 @@ test_expect_success 'status with gitignore' '
# untracked
EOF
git status --ignored >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
test_expect_success 'status with gitignore (nothing untracked)' '
@@ -247,7 +271,7 @@ test_expect_success 'status with gitignore (nothing untracked)' '
# untracked
EOF
git status --ignored >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
rm -f .gitignore
@@ -271,6 +295,15 @@ test_expect_success 'status -s -b' '
'
+test_expect_success 'status -s -z -b' '
+ tr "\\n" Q <expect >expect.q &&
+ mv expect.q expect &&
+ git status -s -z -b >output &&
+ nul_to_q <output >output.q &&
+ mv output.q output &&
+ test_cmp expect output
+'
+
test_expect_success 'setup dir3' '
mkdir dir3 &&
: >dir3/untracked1 &&
@@ -647,9 +680,14 @@ test_expect_success 'status --porcelain ignores color.status' '
git config --unset color.status
git config --unset color.ui
-test_expect_success 'status --porcelain ignores -b' '
+test_expect_success 'status --porcelain respects -b' '
git status --porcelain -b >output &&
+ {
+ echo "## master" &&
+ cat expect
+ } >tmp &&
+ mv tmp expect &&
test_cmp expect output
'
@@ -903,7 +941,7 @@ test_expect_success 'status -s submodule summary (clean submodule)' '
test_expect_success 'status -z implies porcelain' '
git status --porcelain |
- perl -pe "s/\012/\000/g" >expect &&
+ "$PERL_PATH" -pe "s/\012/\000/g" >expect &&
git status -z >output &&
test_cmp expect output
'
diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
new file mode 100755
index 0000000..1d3c56f
--- /dev/null
+++ b/t/t7510-signed-commit.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='signed commit tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPG 'create signed commits' '
+ echo 1 >file && git add file &&
+ test_tick && git commit -S -m initial &&
+ git tag initial &&
+ git branch side &&
+
+ echo 2 >file && test_tick && git commit -a -S -m second &&
+ git tag second &&
+
+ git checkout side &&
+ echo 3 >elif && git add elif &&
+ test_tick && git commit -m "third on side" &&
+
+ git checkout master &&
+ test_tick && git merge -S side &&
+ git tag merge &&
+
+ echo 4 >file && test_tick && git commit -a -m "fourth unsigned" &&
+ git tag fourth-unsigned &&
+
+ test_tick && git commit --amend -S -m "fourth signed" &&
+ git tag fourth-signed
+'
+
+test_expect_success GPG 'show signatures' '
+ (
+ for commit in initial second merge master
+ do
+ git show --pretty=short --show-signature $commit >actual &&
+ grep "Good signature from" actual || exit 1
+ ! grep "BAD signature from" actual || exit 1
+ echo $commit OK
+ done
+ ) &&
+ (
+ for commit in merge^2 fourth-unsigned
+ do
+ git show --pretty=short --show-signature $commit >actual &&
+ grep "Good signature from" actual && exit 1
+ ! grep "BAD signature from" actual || exit 1
+ echo $commit OK
+ done
+ )
+'
+
+test_expect_success GPG 'detect fudged signature' '
+ git cat-file commit master >raw &&
+
+ sed -e "s/fourth signed/4th forged/" raw >forged1 &&
+ git hash-object -w -t commit forged1 >forged1.commit &&
+ git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
+ grep "BAD signature from" actual1 &&
+ ! grep "Good signature from" actual1
+'
+
+test_expect_success GPG 'detect fudged signature with NUL' '
+ git cat-file commit master >raw &&
+ cat raw >forged2 &&
+ echo Qwik | tr "Q" "\000" >>forged2 &&
+ git hash-object -w -t commit forged2 >forged2.commit &&
+ git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
+ grep "BAD signature from" actual2 &&
+ ! grep "Good signature from" actual2
+'
+
+test_expect_success GPG 'amending already signed commit' '
+ git checkout fourth-signed^0 &&
+ git commit --amend -S --no-edit &&
+ git show -s --show-signature HEAD >actual &&
+ grep "Good signature from" actual &&
+ ! grep "BAD signature from" actual
+'
+
+test_done
diff --git a/t/t7511-status-index.sh b/t/t7511-status-index.sh
new file mode 100755
index 0000000..b5fdc04
--- /dev/null
+++ b/t/t7511-status-index.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='git status with certain file name lengths'
+
+. ./test-lib.sh
+
+files="0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z"
+
+check() {
+ len=$1
+ prefix=$2
+
+ for i in $files
+ do
+ : >$prefix$i
+ done
+
+ test_expect_success "status, filename length $len" "
+ git add $prefix* &&
+ git status
+ "
+ rm $prefix* .git/index
+}
+
+check 1
+check 2 p
+check 3 px
+check 4 pre
+check 5 pref
+check 6 prefi
+check 7 prefix
+check 8 prefix-
+check 9 prefix-p
+check 10 prefix-pr
+check 11 prefix-pre
+check 12 prefix-pref
+check 13 prefix-prefi
+check 14 prefix-prefix
+check 15 prefix-prefix-
+check 16 prefix-prefix-p
+check 17 prefix-prefix-pr
+check 18 prefix-prefix-pre
+check 19 prefix-prefix-pref
+check 20 prefix-prefix-prefi
+check 21 prefix-prefix-prefix
+check 22 prefix-prefix-prefix-
+check 23 prefix-prefix-prefix-p
+check 24 prefix-prefix-prefix-pr
+
+test_done
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
new file mode 100755
index 0000000..b3f6eb9
--- /dev/null
+++ b/t/t7512-status-help.sh
@@ -0,0 +1,649 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Valentin Duperray, Lucien Kong, Franck Jonas,
+# Thomas Nguy, Khoi Nguyen
+# Grenoble INP Ensimag
+#
+
+test_description='git status advices'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'prepare for conflicts' '
+ test_commit init main.txt init &&
+ git branch conflicts &&
+ test_commit on_master main.txt on_master &&
+ git checkout conflicts &&
+ test_commit on_conflicts main.txt on_conflicts
+'
+
+
+test_expect_success 'status when conflicts unresolved' '
+ test_must_fail git merge master &&
+ cat >expected <<-\EOF &&
+ # On branch conflicts
+ # You have unmerged paths.
+ # (fix conflicts and run "git commit")
+ #
+ # Unmerged paths:
+ # (use "git add <file>..." to mark resolution)
+ #
+ # both modified: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when conflicts resolved before commit' '
+ git reset --hard conflicts &&
+ test_must_fail git merge master &&
+ echo one >main.txt &&
+ git add main.txt &&
+ cat >expected <<-\EOF &&
+ # On branch conflicts
+ # All conflicts fixed but you are still merging.
+ # (use "git commit" to conclude merge)
+ #
+ # Changes to be committed:
+ #
+ # modified: main.txt
+ #
+ # Untracked files not listed (use -u option to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for rebase conflicts' '
+ git reset --hard master &&
+ git checkout -b rebase_conflicts &&
+ test_commit one_rebase main.txt one &&
+ test_commit two_rebase main.txt two &&
+ test_commit three_rebase main.txt three
+'
+
+
+test_expect_success 'status when rebase in progress before resolving conflicts' '
+ test_when_finished "git rebase --abort" &&
+ test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently rebasing.
+ # (fix conflicts and then run "git rebase --continue")
+ # (use "git rebase --skip" to skip this patch)
+ # (use "git rebase --abort" to check out the original branch)
+ #
+ # Unmerged paths:
+ # (use "git reset HEAD <file>..." to unstage)
+ # (use "git add <file>..." to mark resolution)
+ #
+ # both modified: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when rebase in progress before rebase --continue' '
+ git reset --hard rebase_conflicts &&
+ test_when_finished "git rebase --abort" &&
+ test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+ echo three >main.txt &&
+ git add main.txt &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently rebasing.
+ # (all conflicts fixed: run "git rebase --continue")
+ #
+ # Changes to be committed:
+ # (use "git reset HEAD <file>..." to unstage)
+ #
+ # modified: main.txt
+ #
+ # Untracked files not listed (use -u option to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for rebase_i_conflicts' '
+ git reset --hard master &&
+ git checkout -b rebase_i_conflicts &&
+ test_commit one_unmerge main.txt one_unmerge &&
+ git branch rebase_i_conflicts_second &&
+ test_commit one_master main.txt one_master &&
+ git checkout rebase_i_conflicts_second &&
+ test_commit one_second main.txt one_second
+'
+
+
+test_expect_success 'status during rebase -i when conflicts unresolved' '
+ test_when_finished "git rebase --abort" &&
+ test_must_fail git rebase -i rebase_i_conflicts &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently rebasing.
+ # (fix conflicts and then run "git rebase --continue")
+ # (use "git rebase --skip" to skip this patch)
+ # (use "git rebase --abort" to check out the original branch)
+ #
+ # Unmerged paths:
+ # (use "git reset HEAD <file>..." to unstage)
+ # (use "git add <file>..." to mark resolution)
+ #
+ # both modified: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status during rebase -i after resolving conflicts' '
+ git reset --hard rebase_i_conflicts_second &&
+ test_when_finished "git rebase --abort" &&
+ test_must_fail git rebase -i rebase_i_conflicts &&
+ git add main.txt &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently rebasing.
+ # (all conflicts fixed: run "git rebase --continue")
+ #
+ # Changes to be committed:
+ # (use "git reset HEAD <file>..." to unstage)
+ #
+ # modified: main.txt
+ #
+ # Untracked files not listed (use -u option to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when rebasing -i in edit mode' '
+ git reset --hard master &&
+ git checkout -b rebase_i_edit &&
+ test_commit one_rebase_i main.txt one &&
+ test_commit two_rebase_i main.txt two &&
+ test_commit three_rebase_i main.txt three &&
+ FAKE_LINES="1 edit 2" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~2 &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently editing a commit during a rebase.
+ # (use "git commit --amend" to amend the current commit)
+ # (use "git rebase --continue" once you are satisfied with your changes)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when splitting a commit' '
+ git reset --hard master &&
+ git checkout -b split_commit &&
+ test_commit one_split main.txt one &&
+ test_commit two_split main.txt two &&
+ test_commit three_split main.txt three &&
+ test_commit four_split main.txt four &&
+ FAKE_LINES="1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git reset HEAD^ &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently splitting a commit during a rebase.
+ # (Once your working directory is clean, run "git rebase --continue")
+ #
+ # Changes not staged for commit:
+ # (use "git add <file>..." to update what will be committed)
+ # (use "git checkout -- <file>..." to discard changes in working directory)
+ #
+ # modified: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status after editing the last commit with --amend during a rebase -i' '
+ git reset --hard master &&
+ git checkout -b amend_last &&
+ test_commit one_amend main.txt one &&
+ test_commit two_amend main.txt two &&
+ test_commit three_amend main.txt three &&
+ test_commit four_amend main.txt four &&
+ FAKE_LINES="1 2 edit 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git commit --amend -m "foo" &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently editing a commit during a rebase.
+ # (use "git commit --amend" to amend the current commit)
+ # (use "git rebase --continue" once you are satisfied with your changes)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for several edits' '
+ git reset --hard master &&
+ git checkout -b several_edits &&
+ test_commit one_edits main.txt one &&
+ test_commit two_edits main.txt two &&
+ test_commit three_edits main.txt three &&
+ test_commit four_edits main.txt four
+'
+
+
+test_expect_success 'status: (continue first edit) second edit' '
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git rebase --continue &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently editing a commit during a rebase.
+ # (use "git commit --amend" to amend the current commit)
+ # (use "git rebase --continue" once you are satisfied with your changes)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (continue first edit) second edit and split' '
+ git reset --hard several_edits &&
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git rebase --continue &&
+ git reset HEAD^ &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently splitting a commit during a rebase.
+ # (Once your working directory is clean, run "git rebase --continue")
+ #
+ # Changes not staged for commit:
+ # (use "git add <file>..." to update what will be committed)
+ # (use "git checkout -- <file>..." to discard changes in working directory)
+ #
+ # modified: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (continue first edit) second edit and amend' '
+ git reset --hard several_edits &&
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git rebase --continue &&
+ git commit --amend -m "foo" &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently editing a commit during a rebase.
+ # (use "git commit --amend" to amend the current commit)
+ # (use "git rebase --continue" once you are satisfied with your changes)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (amend first edit) second edit' '
+ git reset --hard several_edits &&
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git commit --amend -m "a" &&
+ git rebase --continue &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently editing a commit during a rebase.
+ # (use "git commit --amend" to amend the current commit)
+ # (use "git rebase --continue" once you are satisfied with your changes)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (amend first edit) second edit and split' '
+ git reset --hard several_edits &&
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git commit --amend -m "b" &&
+ git rebase --continue &&
+ git reset HEAD^ &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently splitting a commit during a rebase.
+ # (Once your working directory is clean, run "git rebase --continue")
+ #
+ # Changes not staged for commit:
+ # (use "git add <file>..." to update what will be committed)
+ # (use "git checkout -- <file>..." to discard changes in working directory)
+ #
+ # modified: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (amend first edit) second edit and amend' '
+ git reset --hard several_edits &&
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git commit --amend -m "c" &&
+ git rebase --continue &&
+ git commit --amend -m "d" &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently editing a commit during a rebase.
+ # (use "git commit --amend" to amend the current commit)
+ # (use "git rebase --continue" once you are satisfied with your changes)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (split first edit) second edit' '
+ git reset --hard several_edits &&
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git reset HEAD^ &&
+ git add main.txt &&
+ git commit -m "e" &&
+ git rebase --continue &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently editing a commit during a rebase.
+ # (use "git commit --amend" to amend the current commit)
+ # (use "git rebase --continue" once you are satisfied with your changes)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (split first edit) second edit and split' '
+ git reset --hard several_edits &&
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git reset HEAD^ &&
+ git add main.txt &&
+ git commit --amend -m "f" &&
+ git rebase --continue &&
+ git reset HEAD^ &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently splitting a commit during a rebase.
+ # (Once your working directory is clean, run "git rebase --continue")
+ #
+ # Changes not staged for commit:
+ # (use "git add <file>..." to update what will be committed)
+ # (use "git checkout -- <file>..." to discard changes in working directory)
+ #
+ # modified: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (split first edit) second edit and amend' '
+ git reset --hard several_edits &&
+ FAKE_LINES="edit 1 edit 2 3" &&
+ export FAKE_LINES &&
+ test_when_finished "git rebase --abort" &&
+ git rebase -i HEAD~3 &&
+ git reset HEAD^ &&
+ git add main.txt &&
+ git commit --amend -m "g" &&
+ git rebase --continue &&
+ git commit --amend -m "h" &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently editing a commit during a rebase.
+ # (use "git commit --amend" to amend the current commit)
+ # (use "git rebase --continue" once you are satisfied with your changes)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare am_session' '
+ git reset --hard master &&
+ git checkout -b am_session &&
+ test_commit one_am one.txt "one" &&
+ test_commit two_am two.txt "two" &&
+ test_commit three_am three.txt "three"
+'
+
+
+test_expect_success 'status in an am session: file already exists' '
+ git checkout -b am_already_exists &&
+ test_when_finished "rm Maildir/* && git am --abort" &&
+ git format-patch -1 -oMaildir &&
+ test_must_fail git am Maildir/*.patch &&
+ cat >expected <<-\EOF &&
+ # On branch am_already_exists
+ # You are in the middle of an am session.
+ # (fix conflicts and then run "git am --resolved")
+ # (use "git am --skip" to skip this patch)
+ # (use "git am --abort" to restore the original branch)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status in an am session: file does not exist' '
+ git reset --hard am_session &&
+ git checkout -b am_not_exists &&
+ git rm three.txt &&
+ git commit -m "delete three.txt" &&
+ test_when_finished "rm Maildir/* && git am --abort" &&
+ git format-patch -1 -oMaildir &&
+ test_must_fail git am Maildir/*.patch &&
+ cat >expected <<-\EOF &&
+ # On branch am_not_exists
+ # You are in the middle of an am session.
+ # (fix conflicts and then run "git am --resolved")
+ # (use "git am --skip" to skip this patch)
+ # (use "git am --abort" to restore the original branch)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status in an am session: empty patch' '
+ git reset --hard am_session &&
+ git checkout -b am_empty &&
+ test_when_finished "rm Maildir/* && git am --abort" &&
+ git format-patch -3 -oMaildir &&
+ git rm one.txt two.txt three.txt &&
+ git commit -m "delete all am_empty" &&
+ echo error >Maildir/0002-two_am.patch &&
+ test_must_fail git am Maildir/*.patch &&
+ cat >expected <<-\EOF &&
+ # On branch am_empty
+ # You are in the middle of an am session.
+ # The current patch is empty.
+ # (use "git am --skip" to skip this patch)
+ # (use "git am --abort" to restore the original branch)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when bisecting' '
+ git reset --hard master &&
+ git checkout -b bisect &&
+ test_commit one_bisect main.txt one &&
+ test_commit two_bisect main.txt two &&
+ test_commit three_bisect main.txt three &&
+ test_when_finished "git bisect reset" &&
+ git bisect start &&
+ git bisect bad &&
+ git bisect good one_bisect &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently bisecting.
+ # (use "git bisect reset" to get back to the original branch)
+ #
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when rebase conflicts with statushints disabled' '
+ git reset --hard master &&
+ git checkout -b statushints_disabled &&
+ test_when_finished "git config --local advice.statushints true" &&
+ git config --local advice.statushints false &&
+ test_commit one_statushints main.txt one &&
+ test_commit two_statushints main.txt two &&
+ test_commit three_statushints main.txt three &&
+ test_when_finished "git rebase --abort" &&
+ test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+ cat >expected <<-\EOF &&
+ # Not currently on any branch.
+ # You are currently rebasing.
+ #
+ # Unmerged paths:
+ # both modified: main.txt
+ #
+ no changes added to commit
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for cherry-pick conflicts' '
+ git reset --hard master &&
+ git checkout -b cherry_branch &&
+ test_commit one_cherry main.txt one &&
+ test_commit two_cherries main.txt two &&
+ git checkout -b cherry_branch_second &&
+ test_commit second_cherry main.txt second &&
+ git checkout cherry_branch &&
+ test_commit three_cherries main.txt three
+'
+
+
+test_expect_success 'status when cherry-picking before resolving conflicts' '
+ test_when_finished "git cherry-pick --abort" &&
+ test_must_fail git cherry-pick cherry_branch_second &&
+ cat >expected <<-\EOF &&
+ # On branch cherry_branch
+ # You are currently cherry-picking.
+ # (fix conflicts and run "git commit")
+ #
+ # Unmerged paths:
+ # (use "git add <file>..." to mark resolution)
+ #
+ # both modified: main.txt
+ #
+ no changes added to commit (use "git add" and/or "git commit -a")
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when cherry-picking after resolving conflicts' '
+ git reset --hard cherry_branch &&
+ test_when_finished "git cherry-pick --abort" &&
+ test_must_fail git cherry-pick cherry_branch_second &&
+ echo end >main.txt &&
+ git add main.txt &&
+ cat >expected <<-\EOF &&
+ # On branch cherry_branch
+ # You are currently cherry-picking.
+ # (all conflicts fixed: run "git commit")
+ #
+ # Changes to be committed:
+ #
+ # modified: main.txt
+ #
+ # Untracked files not listed (use -u option to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
+
+
+test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 87aac83..9e27bbf 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -27,6 +27,7 @@ Testing basic merge operations/option parsing.
'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
printf '%s\n' 1 2 3 4 5 6 7 8 9 >file
printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1
@@ -38,8 +39,8 @@ printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
>empty
create_merge_msgs () {
- echo "Merge commit 'c2'" >msg.1-5 &&
- echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 &&
+ echo "Merge tag 'c2'" >msg.1-5 &&
+ echo "Merge tags 'c2' and 'c3'" >msg.1-5-9 &&
{
echo "Squashed commit of the following:" &&
echo &&
@@ -57,7 +58,7 @@ create_merge_msgs () {
} >squash.1-5-9 &&
echo >msg.nolog &&
{
- echo "* commit 'c3':" &&
+ echo "* tag 'c3':" &&
echo " commit 3" &&
echo
} >msg.log
@@ -96,7 +97,11 @@ verify_parents () {
verify_mergeheads () {
printf '%s\n' "$@" >mergehead.expected &&
- test_cmp mergehead.expected .git/MERGE_HEAD
+ while read sha1 rest
+ do
+ git rev-parse $sha1
+ done <.git/MERGE_HEAD >mergehead.actual &&
+ test_cmp mergehead.expected mergehead.actual
}
verify_no_mergehead () {
@@ -643,4 +648,51 @@ test_expect_success 'amending no-ff merge commit' '
test_debug 'git log --graph --decorate --oneline --all'
+cat >editor <<\EOF
+#!/bin/sh
+# Add a new message string that was not in the template
+(
+ echo "Merge work done on the side branch c1"
+ echo
+ cat <"$1"
+) >"$1.tmp" && mv "$1.tmp" "$1"
+# strip comments and blank lines from end of message
+sed -e '/^#/d' < "$1" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > expected
+EOF
+chmod 755 editor
+
+test_expect_success 'merge --no-ff --edit' '
+ git reset --hard c0 &&
+ EDITOR=./editor git merge --no-ff --edit c1 &&
+ verify_parents $c0 $c1 &&
+ git cat-file commit HEAD >raw &&
+ grep "work done on the side branch" raw &&
+ sed "1,/^$/d" >actual raw &&
+ test_cmp actual expected
+'
+
+test_expect_success GPG 'merge --ff-only tag' '
+ git reset --hard c0 &&
+ git commit --allow-empty -m "A newer commit" &&
+ git tag -s -m "A newer commit" signed &&
+ git reset --hard c0 &&
+
+ git merge --ff-only signed &&
+ git rev-parse signed^0 >expect &&
+ git rev-parse HEAD >actual &&
+ test_cmp actual expect
+'
+
+test_expect_success GPG 'merge --no-edit tag should skip editor' '
+ git reset --hard c0 &&
+ git commit --allow-empty -m "A newer commit" &&
+ git tag -f -s -m "A newer commit" signed &&
+ git reset --hard c0 &&
+
+ EDITOR=false git merge --no-edit signed &&
+ git rev-parse signed^0 >expect &&
+ git rev-parse HEAD^2 >actual &&
+ test_cmp actual expect
+'
+
test_done
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index 0a46795..955f09f 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -53,11 +53,11 @@ cat >expected <<\EOF
Trying simple merge with c2
Trying simple merge with c3
Trying simple merge with c4
-Merge made by octopus.
- c2.c | 1 +
- c3.c | 1 +
- c4.c | 1 +
- 3 files changed, 3 insertions(+), 0 deletions(-)
+Merge made by the 'octopus' strategy.
+ c2.c | 1 +
+ c3.c | 1 +
+ c4.c | 1 +
+ 3 files changed, 3 insertions(+)
create mode 100644 c2.c
create mode 100644 c3.c
create mode 100644 c4.c
@@ -66,30 +66,28 @@ EOF
test_expect_success 'merge output uses pretty names' '
git reset --hard c1 &&
git merge c2 c3 c4 >actual &&
- test_cmp actual expected
+ test_i18ncmp expected actual
'
cat >expected <<\EOF
-Already up-to-date with c4
-Trying simple merge with c5
-Merge made by octopus.
- c5.c | 1 +
- 1 files changed, 1 insertions(+), 0 deletions(-)
+Merge made by the 'recursive' strategy.
+ c5.c | 1 +
+ 1 file changed, 1 insertion(+)
create mode 100644 c5.c
EOF
-test_expect_success 'merge up-to-date output uses pretty names' '
- git merge c4 c5 >actual &&
- test_cmp actual expected
+test_expect_success 'merge reduces irrelevant remote heads' '
+ GIT_MERGE_VERBOSITY=0 git merge c4 c5 >actual &&
+ test_i18ncmp expected actual
'
cat >expected <<\EOF
Fast-forwarding to: c1
Trying simple merge with c2
-Merge made by octopus.
- c1.c | 1 +
- c2.c | 1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
+Merge made by the 'octopus' strategy.
+ c1.c | 1 +
+ c2.c | 1 +
+ 2 files changed, 2 insertions(+)
create mode 100644 c1.c
create mode 100644 c2.c
EOF
@@ -97,7 +95,7 @@ EOF
test_expect_success 'merge fast-forward output uses pretty names' '
git reset --hard c0 &&
git merge c1 c2 >actual &&
- test_cmp actual expected
+ test_i18ncmp expected actual
'
test_done
diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
index 7e17eb4..9894895 100755
--- a/t/t7603-merge-reduce-heads.sh
+++ b/t/t7603-merge-reduce-heads.sh
@@ -57,7 +57,36 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '
test -f c2.c &&
test -f c3.c &&
test -f c4.c &&
- test -f c5.c
+ test -f c5.c &&
+ git show --format=%s -s >actual &&
+ ! grep c1 actual &&
+ grep c2 actual &&
+ grep c3 actual &&
+ ! grep c4 actual &&
+ grep c5 actual
+'
+
+test_expect_success 'pull c2, c3, c4, c5 into c1' '
+ git reset --hard c1 &&
+ git pull . c2 c3 c4 c5 &&
+ test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+ test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+ test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+ test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+ test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
+ git diff --exit-code &&
+ test -f c0.c &&
+ test -f c1.c &&
+ test -f c2.c &&
+ test -f c3.c &&
+ test -f c4.c &&
+ test -f c5.c &&
+ git show --format=%s -s >actual &&
+ ! grep c1 actual &&
+ grep c2 actual &&
+ grep c3 actual &&
+ ! grep c4 actual &&
+ grep c5 actual
'
test_expect_success 'setup' '
@@ -113,4 +142,23 @@ test_expect_success 'verify merge result' '
test $(git rev-parse HEAD^1) = $(git rev-parse E2) &&
test $(git rev-parse HEAD^2) = $(git rev-parse I2)
'
+
+test_expect_success 'fast-forward to redundant refs' '
+ git reset --hard c0 &&
+ git merge c4 c5
+'
+
+test_expect_success 'verify merge result' '
+ test $(git rev-parse HEAD) = $(git rev-parse c5)
+'
+
+test_expect_success 'merge up-to-date redundant refs' '
+ git reset --hard c5 &&
+ git merge c0 c4
+'
+
+test_expect_success 'verify merge result' '
+ test $(git rev-parse HEAD) = $(git rev-parse c5)
+'
+
test_done
diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh
index 9114785..89619cf 100755
--- a/t/t7604-merge-custom-message.sh
+++ b/t/t7604-merge-custom-message.sh
@@ -11,7 +11,7 @@ create_merge_msgs() {
cp exp.subject exp.log &&
echo >>exp.log "" &&
- echo >>exp.log "* commit 'c2':" &&
+ echo >>exp.log "* tag 'c2':" &&
echo >>exp.log " c2"
}
diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh
index aa74184..6547eb8 100755
--- a/t/t7607-merge-overwrite.sh
+++ b/t/t7607-merge-overwrite.sh
@@ -92,6 +92,15 @@ test_expect_success 'will not overwrite removed file with staged changes' '
test_cmp important c1.c
'
+test_expect_failure 'will not overwrite unstaged changes in renamed file' '
+ git reset --hard c1 &&
+ git mv c1.c other.c &&
+ git commit -m rename &&
+ cp important other.c &&
+ git merge c1a &&
+ test_cmp important other.c
+'
+
test_expect_success 'will not overwrite untracked subtree' '
git reset --hard c0 &&
rm -rf sub &&
diff --git a/t/t7608-merge-messages.sh b/t/t7608-merge-messages.sh
index 9225fa6..8e7e0a5 100755
--- a/t/t7608-merge-messages.sh
+++ b/t/t7608-merge-messages.sh
@@ -35,7 +35,7 @@ test_expect_success 'merge tag' '
git checkout master &&
test_commit master-3 &&
git merge tag-1 &&
- check_oneline "Merge commit Qtag-1Q"
+ check_oneline "Merge tag Qtag-1Q"
'
test_expect_success 'ambiguous tag' '
@@ -44,7 +44,7 @@ test_expect_success 'ambiguous tag' '
git checkout master &&
test_commit master-4 &&
git merge ambiguous &&
- check_oneline "Merge commit QambiguousQ"
+ check_oneline "Merge tag QambiguousQ"
'
test_expect_success 'remote-tracking branch' '
diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh
index 4aab2a7..f5e16fc 100755
--- a/t/t7610-mergetool.sh
+++ b/t/t7610-mergetool.sh
@@ -39,6 +39,7 @@ test_expect_success 'setup' '
echo branch1 change >file1 &&
echo branch1 newfile >file2 &&
echo branch1 spaced >"spaced name" &&
+ echo branch1 both added >both &&
echo branch1 change file11 >file11 &&
echo branch1 change file13 >file13 &&
echo branch1 sub >subdir/file3 &&
@@ -50,6 +51,7 @@ test_expect_success 'setup' '
git checkout -b submod-branch1
) &&
git add file1 "spaced name" file11 file13 file2 subdir/file3 submod &&
+ git add both &&
git rm file12 &&
git commit -m "branch1 changes" &&
@@ -58,6 +60,7 @@ test_expect_success 'setup' '
echo master updated >file1 &&
echo master new >file2 &&
echo master updated spaced >"spaced name" &&
+ echo master both added >both &&
echo master updated file12 >file12 &&
echo master updated file14 >file14 &&
echo master new sub >subdir/file3 &&
@@ -69,18 +72,22 @@ test_expect_success 'setup' '
git checkout -b submod-master
) &&
git add file1 "spaced name" file12 file14 file2 subdir/file3 submod &&
+ git add both &&
git rm file11 &&
git commit -m "master updates" &&
git config merge.tool mytool &&
git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
- git config mergetool.mytool.trustExitCode true
+ git config mergetool.mytool.trustExitCode true &&
+ git config mergetool.mybase.cmd "cat \"\$BASE\" >\"\$MERGED\"" &&
+ git config mergetool.mybase.trustExitCode true
'
test_expect_success 'custom mergetool' '
git checkout -b test1 branch1 &&
git submodule update -N &&
test_must_fail git merge master >/dev/null 2>&1 &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "" | git mergetool file1 file1 ) &&
( yes "" | git mergetool file2 "spaced name" >/dev/null 2>&1 ) &&
( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
@@ -101,6 +108,7 @@ test_expect_success 'mergetool crlf' '
( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
( yes "" | git mergetool "spaced name" >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
@@ -131,6 +139,7 @@ test_expect_success 'mergetool on file in parent dir' '
cd subdir &&
( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool ../both >/dev/null 2>&1 ) &&
( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) &&
( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) &&
( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) &&
@@ -212,6 +221,7 @@ test_expect_success 'deleted vs modified submodule' '
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
( yes "r" | git mergetool submod ) &&
rmdir submod && mv submod-movedaside submod &&
@@ -228,6 +238,7 @@ test_expect_success 'deleted vs modified submodule' '
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
( yes "l" | git mergetool submod ) &&
test ! -e submod &&
@@ -241,6 +252,7 @@ test_expect_success 'deleted vs modified submodule' '
test_must_fail git merge test6 &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
( yes "r" | git mergetool submod ) &&
test ! -e submod &&
@@ -256,6 +268,7 @@ test_expect_success 'deleted vs modified submodule' '
test_must_fail git merge test6 &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
( yes "l" | git mergetool submod ) &&
test "$(cat submod/bar)" = "master submodule" &&
@@ -279,6 +292,7 @@ test_expect_success 'file vs modified submodule' '
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
( yes "r" | git mergetool submod ) &&
rmdir submod && mv submod-movedaside submod &&
@@ -294,6 +308,7 @@ test_expect_success 'file vs modified submodule' '
test_must_fail git merge master &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
( yes "l" | git mergetool submod ) &&
git submodule update -N &&
@@ -309,6 +324,7 @@ test_expect_success 'file vs modified submodule' '
test_must_fail git merge test7 &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both >/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
( yes "r" | git mergetool submod ) &&
test -d submod.orig &&
@@ -324,6 +340,7 @@ test_expect_success 'file vs modified submodule' '
test_must_fail git merge test7 &&
test -n "$(git ls-files -u)" &&
( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool both>/dev/null 2>&1 ) &&
( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
( yes "l" | git mergetool submod ) &&
test "$(cat submod/bar)" = "master submodule" &&
@@ -445,4 +462,13 @@ test_expect_success 'directory vs modified submodule' '
git submodule update -N
'
+test_expect_success 'file with no base' '
+ git checkout -b test13 branch1 &&
+ test_must_fail git merge master &&
+ git mergetool --no-prompt --tool mybase -- both &&
+ >expected &&
+ test_cmp both expected &&
+ git reset --hard master >/dev/null 2>&1
+'
+
test_done
diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh
index 200ab61..b8d4cde 100755
--- a/t/t7701-repack-unpack-unreachable.sh
+++ b/t/t7701-repack-unpack-unreachable.sh
@@ -95,4 +95,18 @@ test_expect_success 'unpacked objects receive timestamp of pack file' '
compare_mtimes < mtimes
'
+test_expect_success 'do not bother loosening old objects' '
+ obj1=$(echo one | git hash-object -w --stdin) &&
+ obj2=$(echo two | git hash-object -w --stdin) &&
+ pack1=$(echo $obj1 | git pack-objects .git/objects/pack/pack) &&
+ pack2=$(echo $obj2 | git pack-objects .git/objects/pack/pack) &&
+ git prune-packed &&
+ git cat-file -p $obj1 &&
+ git cat-file -p $obj2 &&
+ test-chmtime =-86400 .git/objects/pack/pack-$pack2.pack &&
+ git repack -A -d --unpack-unreachable=1.hour.ago &&
+ git cat-file -p $obj1 &&
+ test_must_fail git cat-file -p $obj2
+'
+
test_done
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 4048d10..9c3e997 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -10,9 +10,6 @@ Testing basic diff tool invocation
. ./test-lib.sh
-LF='
-'
-
remove_config_vars()
{
# Unset all config variables used by git-difftool
@@ -41,7 +38,17 @@ restore_test_defaults()
prompt_given()
{
prompt="$1"
- test "$prompt" = "Hit return to launch 'test-tool': branch"
+ test "$prompt" = "Launch 'test-tool' [Y/n]: branch"
+}
+
+stdin_contains()
+{
+ grep >/dev/null "$1"
+}
+
+stdin_doesnot_contain()
+{
+ ! stdin_contains "$1"
}
# Create a file on master and change it on branch
@@ -76,6 +83,17 @@ test_expect_success PERL 'difftool ignores bad --tool values' '
test "$diff" = ""
'
+test_expect_success PERL 'difftool forwards arguments to diff' '
+ >for-diff &&
+ git add for-diff &&
+ echo changes>for-diff &&
+ git add for-diff &&
+ diff=$(git difftool --cached --no-prompt -- for-diff) &&
+ test "$diff" = "" &&
+ git reset -- for-diff &&
+ rm for-diff
+'
+
test_expect_success PERL 'difftool honors --gui' '
git config merge.tool bogus-tool &&
git config diff.tool bogus-tool &&
@@ -87,6 +105,19 @@ test_expect_success PERL 'difftool honors --gui' '
restore_test_defaults
'
+test_expect_success PERL 'difftool --gui last setting wins' '
+ git config diff.guitool bogus-tool &&
+ git difftool --no-prompt --gui --no-gui &&
+
+ git config merge.tool bogus-tool &&
+ git config diff.tool bogus-tool &&
+ git config diff.guitool test-tool &&
+ diff=$(git difftool --no-prompt --no-gui --gui branch) &&
+ test "$diff" = "branch" &&
+
+ restore_test_defaults
+'
+
test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
git config diff.tool test-tool &&
@@ -268,4 +299,79 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '
test "$diff" = branch
'
+# Create a second file on master and a different version on branch
+test_expect_success PERL 'setup with 2 files different' '
+ echo m2 >file2 &&
+ git add file2 &&
+ git commit -m "added file2" &&
+
+ git checkout branch &&
+ echo br2 >file2 &&
+ git add file2 &&
+ git commit -a -m "branch changed file2" &&
+ git checkout master
+'
+
+test_expect_success PERL 'say no to the first file' '
+ diff=$( (echo n; echo) | git difftool -x cat branch ) &&
+
+ echo "$diff" | stdin_contains m2 &&
+ echo "$diff" | stdin_contains br2 &&
+ echo "$diff" | stdin_doesnot_contain master &&
+ echo "$diff" | stdin_doesnot_contain branch
+'
+
+test_expect_success PERL 'say no to the second file' '
+ diff=$( (echo; echo n) | git difftool -x cat branch ) &&
+
+ echo "$diff" | stdin_contains master &&
+ echo "$diff" | stdin_contains branch &&
+ echo "$diff" | stdin_doesnot_contain m2 &&
+ echo "$diff" | stdin_doesnot_contain br2
+'
+
+test_expect_success PERL 'difftool --tool-help' '
+ tool_help=$(git difftool --tool-help) &&
+ echo "$tool_help" | stdin_contains tool
+'
+
+test_expect_success PERL 'setup change in subdirectory' '
+ git checkout master &&
+ mkdir sub &&
+ echo master >sub/sub &&
+ git add sub/sub &&
+ git commit -m "added sub/sub" &&
+ echo test >>file &&
+ echo test >>sub/sub &&
+ git add . &&
+ git commit -m "modified both"
+'
+
+test_expect_success PERL 'difftool -d' '
+ diff=$(git difftool -d --extcmd ls branch) &&
+ echo "$diff" | stdin_contains sub &&
+ echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff' '
+ diff=$(git difftool --dir-diff --extcmd ls branch) &&
+ echo "$diff" | stdin_contains sub &&
+ echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff ignores --prompt' '
+ diff=$(git difftool --dir-diff --prompt --extcmd ls branch) &&
+ echo "$diff" | stdin_contains sub &&
+ echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff from subdirectory' '
+ (
+ cd sub &&
+ diff=$(git difftool --dir-diff --extcmd ls branch) &&
+ echo "$diff" | stdin_contains sub &&
+ echo "$diff" | stdin_contains file
+ )
+'
+
test_done
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index 6379ad6..24e9b19 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -47,6 +47,13 @@ test_expect_success setup '
echo vvv >t/v &&
mkdir t/a &&
echo vvv >t/a/v &&
+ {
+ echo "line without leading space1"
+ echo " line with leading space1"
+ echo " line with leading space2"
+ echo " line with leading space3"
+ echo "line without leading space2"
+ } >space &&
git add . &&
test_tick &&
git commit -m initial
@@ -246,6 +253,28 @@ do
done
cat >expected <<EOF
+file
+EOF
+test_expect_success 'grep -l -C' '
+ git grep -l -C1 foo >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:5
+EOF
+test_expect_success 'grep -l -C' '
+ git grep -c -C1 foo >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep -L -C' '
+ git ls-files >expected &&
+ git grep -L -C1 nonexistent_string >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
file:foo mmap bar_mmap
EOF
@@ -322,6 +351,11 @@ test_expect_success 'grep -f, multiple patterns' '
test_cmp expected actual
'
+test_expect_success 'grep, multiple patterns' '
+ git grep "$(cat patterns)" >actual &&
+ test_cmp expected actual
+'
+
cat >expected <<EOF
file:foo mmap bar
file:foo_mmap bar
@@ -509,6 +543,34 @@ test_expect_success 'grep -p -B5' '
test_cmp expected actual
'
+cat >expected <<EOF
+hello.c=int main(int argc, const char **argv)
+hello.c-{
+hello.c- printf("Hello world.\n");
+hello.c: return 0;
+hello.c- /* char ?? */
+hello.c-}
+EOF
+
+test_expect_success 'grep -W' '
+ git grep -W return >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c= printf("Hello world.\n");
+hello.c: return 0;
+hello.c- /* char ?? */
+EOF
+
+test_expect_success 'grep -W with userdiff' '
+ test_when_finished "rm -f .gitattributes" &&
+ git config diff.custom.xfuncname "(printf.*|})$" &&
+ echo "hello.c diff=custom" >.gitattributes &&
+ git grep -W return >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'grep from a subdirectory to search wider area (1)' '
mkdir -p s &&
(
@@ -540,7 +602,6 @@ test_expect_success 'outside of git repository' '
mkdir -p non/git/sub &&
echo hello >non/git/file1 &&
echo world >non/git/sub/file2 &&
- echo ".*o*" >non/git/.gitignore &&
{
echo file1:hello &&
echo sub/file2:world
@@ -557,6 +618,23 @@ test_expect_success 'outside of git repository' '
test_must_fail git grep o &&
git grep --no-index o >../../actual.sub &&
test_cmp ../../expect.sub ../../actual.sub
+ ) &&
+
+ echo ".*o*" >non/git/.gitignore &&
+ (
+ GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd non/git &&
+ test_must_fail git grep o &&
+ git grep --no-index --exclude-standard o >../actual.full &&
+ test_cmp ../expect.full ../actual.full &&
+
+ {
+ echo ".gitignore:.*o*"
+ cat ../expect.full
+ } >../expect.with.ignored &&
+ git grep --no-index --no-exclude o >../actual.full &&
+ test_cmp ../expect.with.ignored ../actual.full
)
'
@@ -569,6 +647,10 @@ test_expect_success 'inside git repository but with --no-index' '
{
echo file1:hello &&
echo sub/file2:world
+ } >is/expect.unignored &&
+ {
+ echo ".gitignore:.*o*" &&
+ cat is/expect.unignored
} >is/expect.full &&
: >is/expect.empty &&
echo file2:world >is/expect.sub &&
@@ -577,12 +659,24 @@ test_expect_success 'inside git repository but with --no-index' '
git init &&
test_must_fail git grep o >../actual.full &&
test_cmp ../expect.empty ../actual.full &&
+
+ git grep --untracked o >../actual.unignored &&
+ test_cmp ../expect.unignored ../actual.unignored &&
+
git grep --no-index o >../actual.full &&
test_cmp ../expect.full ../actual.full &&
+
+ git grep --no-index --exclude-standard o >../actual.unignored &&
+ test_cmp ../expect.unignored ../actual.unignored &&
+
cd sub &&
test_must_fail git grep o >../../actual.sub &&
test_cmp ../../expect.empty ../../actual.sub &&
+
git grep --no-index o >../../actual.sub &&
+ test_cmp ../../expect.sub ../../actual.sub &&
+
+ git grep --untracked o >../../actual.sub &&
test_cmp ../../expect.sub ../../actual.sub
)
'
@@ -716,4 +810,115 @@ test_expect_success LIBPCRE 'grep -G -F -E -P pattern' '
test_cmp expected actual
'
+test_config() {
+ git config "$1" "$2" &&
+ test_when_finished "git config --unset $1"
+}
+
+cat >expected <<EOF
+hello.c<RED>:<RESET>int main(int argc, const char **argv)
+hello.c<RED>-<RESET>{
+<RED>--<RESET>
+hello.c<RED>:<RESET> /* char ?? */
+hello.c<RED>-<RESET>}
+<RED>--<RESET>
+hello_world<RED>:<RESET>Hello_world
+hello_world<RED>-<RESET>HeLLo_world
+EOF
+
+test_expect_success 'grep --color, separator' '
+ test_config color.grep.context normal &&
+ test_config color.grep.filename normal &&
+ test_config color.grep.function normal &&
+ test_config color.grep.linenumber normal &&
+ test_config color.grep.match normal &&
+ test_config color.grep.selected normal &&
+ test_config color.grep.separator red &&
+
+ git grep --color=always -A1 -e char -e lo_w hello.c hello_world |
+ test_decode_color >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c: /* char ?? */
+
+hello_world:Hello_world
+EOF
+
+test_expect_success 'grep --break' '
+ git grep --break -e char -e lo_w hello.c hello_world >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c-{
+--
+hello.c: /* char ?? */
+hello.c-}
+
+hello_world:Hello_world
+hello_world-HeLLo_world
+EOF
+
+test_expect_success 'grep --break with context' '
+ git grep --break -A1 -e char -e lo_w hello.c hello_world >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c
+int main(int argc, const char **argv)
+ /* char ?? */
+hello_world
+Hello_world
+EOF
+
+test_expect_success 'grep --heading' '
+ git grep --heading -e char -e lo_w hello.c hello_world >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+<BOLD;GREEN>hello.c<RESET>
+2:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)
+6: /* <BLACK;BYELLOW>char<RESET> ?? */
+
+<BOLD;GREEN>hello_world<RESET>
+3:Hel<BLACK;BYELLOW>lo_w<RESET>orld
+EOF
+
+test_expect_success 'mimic ack-grep --group' '
+ test_config color.grep.context normal &&
+ test_config color.grep.filename "bold green" &&
+ test_config color.grep.function normal &&
+ test_config color.grep.linenumber normal &&
+ test_config color.grep.match "black yellow" &&
+ test_config color.grep.selected normal &&
+ test_config color.grep.separator normal &&
+
+ git grep --break --heading -n --color \
+ -e char -e lo_w hello.c hello_world |
+ test_decode_color >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+space: line with leading space1
+space: line with leading space2
+space: line with leading space3
+EOF
+
+test_expect_success LIBPCRE 'grep -E "^ "' '
+ git grep -E "^ " space >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P "^ "' '
+ git grep -P "^ " space >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh
index 32ec82a..bf6caa4 100755
--- a/t/t8006-blame-textconv.sh
+++ b/t/t8006-blame-textconv.sh
@@ -10,11 +10,12 @@ find_blame() {
cat >helper <<'EOF'
#!/bin/sh
grep -q '^bin: ' "$1" || { echo "E: $1 is not \"binary\" file" 1>&2; exit 1; }
-sed 's/^bin: /converted: /' "$1"
+"$PERL_PATH" -p -e 's/^bin: /converted: /' "$1"
EOF
chmod +x helper
test_expect_success 'setup ' '
+ echo "bin: test number 0" >zero.bin &&
echo "bin: test 1" >one.bin &&
echo "bin: test number 2" >two.bin &&
if test_have_prereq SYMLINKS; then
@@ -43,6 +44,7 @@ test_expect_success 'no filter specified' '
test_expect_success 'setup textconv filters' '
echo "*.bin diff=test" >.gitattributes &&
+ echo "zero.bin eol=crlf" >>.gitattributes &&
git config diff.test.textconv ./helper &&
git config diff.test.cachetextconv false
'
@@ -74,6 +76,15 @@ test_expect_success 'blame --textconv going through revisions' '
test_cmp expected result
'
+test_expect_success 'blame --textconv with local changes' '
+ test_when_finished "git checkout zero.bin" &&
+ printf "bin: updated number 0\015" >zero.bin &&
+ git blame --textconv zero.bin >blame &&
+ expect="(Not Committed Yet ....-..-.. ..:..:.. +0000 1)" &&
+ expect="$expect converted: updated number 0" &&
+ expr "$(find_blame <blame)" : "^$expect"
+'
+
test_expect_success 'setup +cachetextconv' '
git config diff.test.cachetextconv true
'
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 579ddb7..8c12c65 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -23,6 +23,7 @@ test_expect_success $PREREQ \
echo do
echo " echo \"!\$a!\""
echo "done >commandline\$output"
+ test_have_prereq MINGW && echo "dos2unix commandline\$output"
echo "cat > msgtxt\$output"
) >fake.sendmail &&
chmod +x ./fake.sendmail &&
@@ -1168,4 +1169,32 @@ test_expect_success $PREREQ '--force sends cover letter template anyway' '
test -n "$(ls msgtxt*)"
'
+test_expect_success $PREREQ 'sendemail.aliasfiletype=mailrc' '
+ clean_fake_sendmail &&
+ echo "alias sbd somebody@example.org" >.mailrc &&
+ git config --replace-all sendemail.aliasesfile "$(pwd)/.mailrc" &&
+ git config sendemail.aliasfiletype mailrc &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=sbd \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ outdir/0001-*.patch \
+ 2>errors >out &&
+ grep "^!somebody@example\.org!$" commandline1
+'
+
+test_expect_success $PREREQ 'sendemail.aliasfile=~/.mailrc' '
+ clean_fake_sendmail &&
+ echo "alias sbd someone@example.org" >~/.mailrc &&
+ git config --replace-all sendemail.aliasesfile "~/.mailrc" &&
+ git config sendemail.aliasfiletype mailrc &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=sbd \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ outdir/0001-*.patch \
+ 2>errors >out &&
+ grep "^!someone@example\.org!$" commandline1
+'
+
test_done
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
new file mode 100755
index 0000000..8998352
--- /dev/null
+++ b/t/t9002-column.sh
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+test_description='git column'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ cat >lista <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+'
+
+test_expect_success 'never' '
+ git column --indent=Z --mode=never <lista >actual &&
+ test_cmp lista actual
+'
+
+test_expect_success 'always' '
+ cat >expected <<\EOF &&
+Zone
+Ztwo
+Zthree
+Zfour
+Zfive
+Zsix
+Zseven
+Zeight
+Znine
+Zten
+Zeleven
+EOF
+ git column --indent=Z --mode=plain <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '80 columns' '
+ cat >expected <<\EOF &&
+one two three four five six seven eight nine ten eleven
+EOF
+ COLUMNS=80 git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+
+test_expect_success COLUMNS_CAN_BE_1 'COLUMNS = 1' '
+ COLUMNS=1 git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'width = 1' '
+ git column --mode=column --width=1 <lista >actual &&
+ test_cmp expected actual
+'
+
+COLUMNS=20
+export COLUMNS
+
+test_expect_success '20 columns' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, nodense' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column,nodense < lista > actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, dense' '
+ cat >expected <<\EOF &&
+one five nine
+two six ten
+three seven eleven
+four eight
+EOF
+ git column --mode=column,dense < lista > actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, padding 2' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column --padding 2 <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, indented' '
+ cat >expected <<\EOF &&
+ one seven
+ two eight
+ three nine
+ four ten
+ five eleven
+ six
+EOF
+ git column --mode=column --indent=" " <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first' '
+ cat >expected <<\EOF &&
+one two
+three four
+five six
+seven eight
+nine ten
+eleven
+EOF
+ git column --mode=row <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, nodense' '
+ cat >expected <<\EOF &&
+one two
+three four
+five six
+seven eight
+nine ten
+eleven
+EOF
+ git column --mode=row,nodense <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, dense' '
+ cat >expected <<\EOF &&
+one two three
+four five six
+seven eight nine
+ten eleven
+EOF
+ git column --mode=row,dense <lista >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh
index 6f6175a..b7eed24 100755
--- a/t/t9010-svn-fe.sh
+++ b/t/t9010-svn-fe.sh
@@ -5,8 +5,27 @@ test_description='check svn dumpfile importer'
. ./test-lib.sh
reinit_git () {
+ if ! test_declared_prereq PIPE
+ then
+ echo >&4 "reinit_git: need to declare PIPE prerequisite"
+ return 127
+ fi
rm -fr .git &&
- git init
+ rm -f stream backflow &&
+ git init &&
+ mkfifo stream backflow
+}
+
+try_dump () {
+ input=$1 &&
+ maybe_fail_svnfe=${2:+test_$2} &&
+ maybe_fail_fi=${3:+test_$3} &&
+
+ {
+ $maybe_fail_svnfe test-svn-fe "$input" >stream 3<backflow &
+ } &&
+ $maybe_fail_fi git fast-import --cat-blob-fd=3 <stream 3>backflow &&
+ wait $!
}
properties () {
@@ -35,21 +54,27 @@ text_no_props () {
>empty
-test_expect_success 'empty dump' '
+test_expect_success 'setup: have pipes?' '
+ rm -f frob &&
+ if mkfifo frob
+ then
+ test_set_prereq PIPE
+ fi
+'
+
+test_expect_success PIPE 'empty dump' '
reinit_git &&
echo "SVN-fs-dump-format-version: 2" >input &&
- test-svn-fe input >stream &&
- git fast-import <stream
+ try_dump input
'
-test_expect_success 'v4 dumps not supported' '
+test_expect_success PIPE 'v4 dumps not supported' '
reinit_git &&
echo "SVN-fs-dump-format-version: 4" >v4.dump &&
- test_must_fail test-svn-fe v4.dump >stream &&
- test_cmp empty stream
+ try_dump v4.dump must_fail
'
-test_expect_failure 'empty revision' '
+test_expect_failure PIPE 'empty revision' '
reinit_git &&
printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
cat >emptyrev.dump <<-\EOF &&
@@ -64,13 +89,12 @@ test_expect_failure 'empty revision' '
Content-length: 0
EOF
- test-svn-fe emptyrev.dump >stream &&
- git fast-import <stream &&
+ try_dump emptyrev.dump &&
git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
test_cmp expect actual
'
-test_expect_success 'empty properties' '
+test_expect_success PIPE 'empty properties' '
reinit_git &&
printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
cat >emptyprop.dump <<-\EOF &&
@@ -88,13 +112,12 @@ test_expect_success 'empty properties' '
PROPS-END
EOF
- test-svn-fe emptyprop.dump >stream &&
- git fast-import <stream &&
+ try_dump emptyprop.dump &&
git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
test_cmp expect actual
'
-test_expect_success 'author name and commit message' '
+test_expect_success PIPE 'author name and commit message' '
reinit_git &&
echo "<author@example.com, author@example.com@local>" >expect.author &&
cat >message <<-\EOF &&
@@ -121,15 +144,14 @@ test_expect_success 'author name and commit message' '
echo &&
cat props
} >log.dump &&
- test-svn-fe log.dump >stream &&
- git fast-import <stream &&
+ try_dump log.dump &&
git log -p --format="%B" HEAD >actual.log &&
git log --format="<%an, %ae>" >actual.author &&
test_cmp message actual.log &&
test_cmp expect.author actual.author
'
-test_expect_success 'unsupported properties are ignored' '
+test_expect_success PIPE 'unsupported properties are ignored' '
reinit_git &&
echo author >expect &&
cat >extraprop.dump <<-\EOF &&
@@ -149,13 +171,12 @@ test_expect_success 'unsupported properties are ignored' '
author
PROPS-END
EOF
- test-svn-fe extraprop.dump >stream &&
- git fast-import <stream &&
+ try_dump extraprop.dump &&
git log -p --format=%an HEAD >actual &&
test_cmp expect actual
'
-test_expect_failure 'timestamp and empty file' '
+test_expect_failure PIPE 'timestamp and empty file' '
echo author@example.com >expect.author &&
echo 1999-01-01 >expect.date &&
echo file >expect.files &&
@@ -186,8 +207,7 @@ test_expect_failure 'timestamp and empty file' '
EOF
} >emptyfile.dump &&
- test-svn-fe emptyfile.dump >stream &&
- git fast-import <stream &&
+ try_dump emptyfile.dump &&
git log --format=%an HEAD >actual.author &&
git log --date=short --format=%ad HEAD >actual.date &&
git ls-tree -r --name-only HEAD >actual.files &&
@@ -198,7 +218,7 @@ test_expect_failure 'timestamp and empty file' '
test_cmp empty file
'
-test_expect_success 'directory with files' '
+test_expect_success PIPE 'directory with files' '
reinit_git &&
printf "%s\n" directory/file1 directory/file2 >expect.files &&
echo hi >hi &&
@@ -242,8 +262,7 @@ test_expect_success 'directory with files' '
EOF
text_no_props hi
} >directory.dump &&
- test-svn-fe directory.dump >stream &&
- git fast-import <stream &&
+ try_dump directory.dump &&
git ls-tree -r --name-only HEAD >actual.files &&
git checkout HEAD directory &&
@@ -252,7 +271,107 @@ test_expect_success 'directory with files' '
test_cmp hi directory/file2
'
-test_expect_success 'node without action' '
+test_expect_success PIPE 'branch name with backslash' '
+ reinit_git &&
+ sort <<-\EOF >expect.branch-files &&
+ trunk/file1
+ trunk/file2
+ "branches/UpdateFOPto094\\/file1"
+ "branches/UpdateFOPto094\\/file2"
+ EOF
+
+ echo hi >hi &&
+ echo hello >hello &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-02-02T00:01:02.000000Z" \
+ svn:log "add directory with some files in it" &&
+ echo PROPS-END
+ } >props.setup &&
+ {
+ properties \
+ svn:author brancher@example.com \
+ svn:date "2007-12-06T21:38:34.000000Z" \
+ svn:log "Updating fop to .94 and adjust fo-stylesheets" &&
+ echo PROPS-END
+ } >props.branch &&
+ {
+ cat <<-EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ EOF
+ echo Prop-content-length: $(wc -c <props.setup) &&
+ echo Content-length: $(wc -c <props.setup) &&
+ echo &&
+ cat props.setup &&
+ cat <<-\EOF &&
+
+ Node-path: trunk
+ Node-kind: dir
+ Node-action: add
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: branches
+ Node-kind: dir
+ Node-action: add
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: trunk/file1
+ Node-kind: file
+ Node-action: add
+ EOF
+ text_no_props hello &&
+ cat <<-\EOF &&
+ Node-path: trunk/file2
+ Node-kind: file
+ Node-action: add
+ EOF
+ text_no_props hi &&
+ cat <<-\EOF &&
+
+ Revision-number: 2
+ EOF
+ echo Prop-content-length: $(wc -c <props.branch) &&
+ echo Content-length: $(wc -c <props.branch) &&
+ echo &&
+ cat props.branch &&
+ cat <<-\EOF
+
+ Node-path: branches/UpdateFOPto094\
+ Node-kind: dir
+ Node-action: add
+ Node-copyfrom-rev: 1
+ Node-copyfrom-path: trunk
+
+ Node-kind: dir
+ Node-action: add
+ Prop-content-length: 34
+ Content-length: 34
+
+ K 13
+ svn:mergeinfo
+ V 0
+
+ PROPS-END
+ EOF
+ } >branch.dump &&
+ try_dump branch.dump &&
+
+ git ls-tree -r --name-only HEAD |
+ sort >actual.branch-files &&
+ test_cmp expect.branch-files actual.branch-files
+'
+
+test_expect_success PIPE 'node without action' '
+ reinit_git &&
cat >inaction.dump <<-\EOF &&
SVN-fs-dump-format-version: 3
@@ -269,10 +388,11 @@ test_expect_success 'node without action' '
PROPS-END
EOF
- test_must_fail test-svn-fe inaction.dump
+ try_dump inaction.dump must_fail
'
-test_expect_success 'action: add node without text' '
+test_expect_success PIPE 'action: add node without text' '
+ reinit_git &&
cat >textless.dump <<-\EOF &&
SVN-fs-dump-format-version: 3
@@ -290,10 +410,10 @@ test_expect_success 'action: add node without text' '
PROPS-END
EOF
- test_must_fail test-svn-fe textless.dump
+ try_dump textless.dump must_fail
'
-test_expect_failure 'change file mode but keep old content' '
+test_expect_failure PIPE 'change file mode but keep old content' '
reinit_git &&
cat >expect <<-\EOF &&
OBJID
@@ -356,8 +476,7 @@ test_expect_failure 'change file mode but keep old content' '
PROPS-END
EOF
- test-svn-fe filemode.dump >stream &&
- git fast-import <stream &&
+ try_dump filemode.dump &&
{
git rev-list HEAD |
git diff-tree --root --stdin |
@@ -370,7 +489,7 @@ test_expect_failure 'change file mode but keep old content' '
test_cmp hello actual.target
'
-test_expect_success 'NUL in property value' '
+test_expect_success PIPE 'NUL in property value' '
reinit_git &&
echo "commit message" >expect.message &&
{
@@ -391,13 +510,12 @@ test_expect_success 'NUL in property value' '
echo &&
cat props
} >nulprop.dump &&
- test-svn-fe nulprop.dump >stream &&
- git fast-import <stream &&
+ try_dump nulprop.dump &&
git diff-tree --always -s --format=%s HEAD >actual.message &&
test_cmp expect.message actual.message
'
-test_expect_success 'NUL in log message, file content, and property name' '
+test_expect_success PIPE 'NUL in log message, file content, and property name' '
# Caveat: svnadmin 1.6.16 (r1073529) truncates at \0 in the
# svn:specialQnotreally example.
reinit_git &&
@@ -458,8 +576,7 @@ test_expect_success 'NUL in log message, file content, and property name' '
link hello
EOF
} >8bitclean.dump &&
- test-svn-fe 8bitclean.dump >stream &&
- git fast-import <stream &&
+ try_dump 8bitclean.dump &&
{
git rev-list HEAD |
git diff-tree --root --stdin |
@@ -478,7 +595,7 @@ test_expect_success 'NUL in log message, file content, and property name' '
test_cmp expect.hello2 actual.hello2
'
-test_expect_success 'change file mode and reiterate content' '
+test_expect_success PIPE 'change file mode and reiterate content' '
reinit_git &&
cat >expect <<-\EOF &&
OBJID
@@ -490,7 +607,7 @@ test_expect_success 'change file mode and reiterate content' '
EOF
echo "link hello" >expect.blob &&
echo hello >hello &&
- cat >filemode.dump <<-\EOF &&
+ cat >filemode2.dump <<-\EOF &&
SVN-fs-dump-format-version: 3
Revision-number: 1
@@ -545,8 +662,7 @@ test_expect_success 'change file mode and reiterate content' '
PROPS-END
link hello
EOF
- test-svn-fe filemode.dump >stream &&
- git fast-import <stream &&
+ try_dump filemode2.dump &&
{
git rev-list HEAD |
git diff-tree --root --stdin |
@@ -559,7 +675,8 @@ test_expect_success 'change file mode and reiterate content' '
test_cmp hello actual.target
'
-test_expect_success 'deltas not supported' '
+test_expect_success PIPE 'deltas supported' '
+ reinit_git &&
{
# (old) h + (inline) ello + (old) \n
printf "SVNQ%b%b%s" "Q\003\006\005\004" "\001Q\0204\001\002" "ello" |
@@ -619,10 +736,10 @@ test_expect_success 'deltas not supported' '
echo PROPS-END &&
cat delta
} >delta.dump &&
- test_must_fail test-svn-fe delta.dump
+ try_dump delta.dump
'
-test_expect_success 'property deltas supported' '
+test_expect_success PIPE 'property deltas supported' '
reinit_git &&
cat >expect <<-\EOF &&
OBJID
@@ -678,8 +795,7 @@ test_expect_success 'property deltas supported' '
PROPS-END
EOF
} >propdelta.dump &&
- test-svn-fe propdelta.dump >stream &&
- git fast-import <stream &&
+ try_dump propdelta.dump &&
{
git rev-list HEAD |
git diff-tree --stdin |
@@ -688,7 +804,7 @@ test_expect_success 'property deltas supported' '
test_cmp expect actual
'
-test_expect_success 'properties on /' '
+test_expect_success PIPE 'properties on /' '
reinit_git &&
cat <<-\EOF >expect &&
OBJID
@@ -733,8 +849,7 @@ test_expect_success 'properties on /' '
PROPS-END
EOF
- test-svn-fe changeroot.dump >stream &&
- git fast-import <stream &&
+ try_dump changeroot.dump &&
{
git rev-list HEAD |
git diff-tree --root --always --stdin |
@@ -743,7 +858,7 @@ test_expect_success 'properties on /' '
test_cmp expect actual
'
-test_expect_success 'deltas for typechange' '
+test_expect_success PIPE 'deltas for typechange' '
reinit_git &&
cat >expect <<-\EOF &&
OBJID
@@ -819,8 +934,7 @@ test_expect_success 'deltas for typechange' '
PROPS-END
link testing 321
EOF
- test-svn-fe deleteprop.dump >stream &&
- git fast-import <stream &&
+ try_dump deleteprop.dump &&
{
git rev-list HEAD |
git diff-tree --root --stdin |
@@ -829,6 +943,143 @@ test_expect_success 'deltas for typechange' '
test_cmp expect actual
'
+test_expect_success PIPE 'deltas need not consume the whole preimage' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :120000 100644 OBJID OBJID T postimage
+ OBJID
+ :100644 120000 OBJID OBJID T postimage
+ OBJID
+ :000000 100644 OBJID OBJID A postimage
+ EOF
+ echo "first preimage" >expect.1 &&
+ printf target >expect.2 &&
+ printf lnk >expect.3 &&
+ {
+ printf "SVNQ%b%b%b" "QQ\017\001\017" "\0217" "first preimage\n" |
+ q_to_nul
+ } >delta.1 &&
+ {
+ properties svn:special "*" &&
+ echo PROPS-END
+ } >symlink.props &&
+ {
+ printf "SVNQ%b%b%b" "Q\002\013\004\012" "\0201\001\001\0211" "lnk target" |
+ q_to_nul
+ } >delta.2 &&
+ {
+ printf "SVNQ%b%b" "Q\004\003\004Q" "\001Q\002\002" |
+ q_to_nul
+ } >delta.3 &&
+ {
+ cat <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: postimage
+ Node-kind: file
+ Node-action: add
+ Text-delta: true
+ Prop-content-length: 10
+ EOF
+ echo Text-content-length: $(wc -c <delta.1) &&
+ echo Content-length: $((10 + $(wc -c <delta.1))) &&
+ echo &&
+ echo PROPS-END &&
+ cat delta.1 &&
+ cat <<-\EOF &&
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: postimage
+ Node-kind: file
+ Node-action: change
+ Text-delta: true
+ EOF
+ echo Prop-content-length: $(wc -c <symlink.props) &&
+ echo Text-content-length: $(wc -c <delta.2) &&
+ echo Content-length: $(($(wc -c <symlink.props) + $(wc -c <delta.2))) &&
+ echo &&
+ cat symlink.props &&
+ cat delta.2 &&
+ cat <<-\EOF &&
+
+ Revision-number: 3
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: postimage
+ Node-kind: file
+ Node-action: change
+ Text-delta: true
+ Prop-content-length: 10
+ EOF
+ echo Text-content-length: $(wc -c <delta.3) &&
+ echo Content-length: $((10 + $(wc -c <delta.3))) &&
+ echo &&
+ echo PROPS-END &&
+ cat delta.3 &&
+ echo
+ } >deltapartial.dump &&
+ try_dump deltapartial.dump &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ test_cmp expect actual &&
+ git show HEAD:postimage >actual.3 &&
+ git show HEAD^:postimage >actual.2 &&
+ git show HEAD^^:postimage >actual.1 &&
+ test_cmp expect.1 actual.1 &&
+ test_cmp expect.2 actual.2 &&
+ test_cmp expect.3 actual.3
+'
+
+test_expect_success PIPE 'no hang for delta trying to read past end of preimage' '
+ reinit_git &&
+ {
+ # COPY 1
+ printf "SVNQ%b%b" "Q\001\001\002Q" "\001Q" |
+ q_to_nul
+ } >greedy.delta &&
+ {
+ cat <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: bootstrap
+ Node-kind: file
+ Node-action: add
+ Text-delta: true
+ Prop-content-length: 10
+ EOF
+ echo Text-content-length: $(wc -c <greedy.delta) &&
+ echo Content-length: $((10 + $(wc -c <greedy.delta))) &&
+ echo &&
+ echo PROPS-END &&
+ cat greedy.delta &&
+ echo
+ } >greedydelta.dump &&
+ try_dump greedydelta.dump must_fail might_fail
+'
test_expect_success 'set up svn repo' '
svnconf=$PWD/svnconf &&
@@ -844,12 +1095,12 @@ test_expect_success 'set up svn repo' '
fi
'
-test_expect_success SVNREPO 't9135/svn.dump' '
- git init simple-git &&
- test-svn-fe "$TEST_DIRECTORY/t9135/svn.dump" >simple.fe &&
+test_expect_success SVNREPO,PIPE 't9135/svn.dump' '
+ mkdir -p simple-git &&
(
cd simple-git &&
- git fast-import <../simple.fe
+ reinit_git &&
+ try_dump "$TEST_DIRECTORY/t9135/svn.dump"
) &&
(
cd simple-svnco &&
diff --git a/t/t9011-svn-da.sh b/t/t9011-svn-da.sh
new file mode 100755
index 0000000..b38d16f
--- /dev/null
+++ b/t/t9011-svn-da.sh
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+test_description='test parsing of svndiff0 files
+
+Using the "test-svn-fe -d" helper, check that svn-fe correctly
+interprets deltas using various facilities (some from the spec,
+some only learned from practice).
+'
+. ./test-lib.sh
+
+>empty
+printf foo >preimage
+
+test_expect_success 'reject empty delta' '
+ test_must_fail test-svn-fe -d preimage empty 0
+'
+
+test_expect_success 'delta can empty file' '
+ printf "SVNQ" | q_to_nul >clear.delta &&
+ test-svn-fe -d preimage clear.delta 4 >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success 'reject svndiff2' '
+ printf "SVN\002" >bad.filetype &&
+ test_must_fail test-svn-fe -d preimage bad.filetype 4
+'
+
+test_expect_success 'one-window empty delta' '
+ printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
+ test-svn-fe -d preimage clear.onewindow 9 >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success 'reject incomplete window header' '
+ printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
+ printf "SVNQ%s" "QQ" | q_to_nul >clear.partialwindow &&
+ test_must_fail test-svn-fe -d preimage clear.onewindow 6 &&
+ test_must_fail test-svn-fe -d preimage clear.partialwindow 6
+'
+
+test_expect_success 'reject declared delta longer than actual delta' '
+ printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
+ printf "SVNQ%s" "QQ" | q_to_nul >clear.partialwindow &&
+ test_must_fail test-svn-fe -d preimage clear.onewindow 14 &&
+ test_must_fail test-svn-fe -d preimage clear.partialwindow 9
+'
+
+test_expect_success 'two-window empty delta' '
+ printf "SVNQ%s%s" "QQQQQ" "QQQQQ" | q_to_nul >clear.twowindow &&
+ test-svn-fe -d preimage clear.twowindow 14 >actual &&
+ test_must_fail test-svn-fe -d preimage clear.twowindow 13 &&
+ test_cmp empty actual
+'
+
+test_expect_success 'noisy zeroes' '
+ printf "SVNQ%s" \
+ "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQ" |
+ tr R "\200" |
+ q_to_nul >clear.noisy &&
+ len=$(wc -c <clear.noisy) &&
+ test-svn-fe -d preimage clear.noisy $len &&
+ test_cmp empty actual
+'
+
+test_expect_success 'reject variable-length int in magic' '
+ printf "SVNRQ" | tr R "\200" | q_to_nul >clear.badmagic &&
+ test_must_fail test-svn-fe -d preimage clear.badmagic 5
+'
+
+test_expect_success 'reject truncated integer' '
+ printf "SVNQ%s%s" "QQQQQ" "QQQQRRQ" |
+ tr R "\200" |
+ q_to_nul >clear.fullint &&
+ printf "SVNQ%s%s" "QQQQQ" "QQQQRR" |
+ tr RT "\201" |
+ q_to_nul >clear.partialint &&
+ test_must_fail test-svn-fe -d preimage clear.fullint 15 &&
+ test-svn-fe -d preimage clear.fullint 16 &&
+ test_must_fail test-svn-fe -d preimage clear.partialint 15
+'
+
+test_expect_success 'nonempty (but unused) preimage view' '
+ printf "SVNQ%b" "Q\003QQQ" | q_to_nul >clear.readpreimage &&
+ test-svn-fe -d preimage clear.readpreimage 9 >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success 'preimage view: right endpoint cannot backtrack' '
+ printf "SVNQ%b%b" "Q\003QQQ" "Q\002QQQ" |
+ q_to_nul >clear.backtrack &&
+ test_must_fail test-svn-fe -d preimage clear.backtrack 14
+'
+
+test_expect_success 'preimage view: left endpoint can advance' '
+ printf "SVNQ%b%b" "Q\003QQQ" "\001\002QQQ" |
+ q_to_nul >clear.preshrink &&
+ printf "SVNQ%b%b" "Q\003QQQ" "\001\001QQQ" |
+ q_to_nul >clear.shrinkbacktrack &&
+ test-svn-fe -d preimage clear.preshrink 14 >actual &&
+ test_must_fail test-svn-fe -d preimage clear.shrinkbacktrack 14 &&
+ test_cmp empty actual
+'
+
+test_expect_success 'preimage view: offsets compared by value' '
+ printf "SVNQ%b%b" "\001\001QQQ" "\0200Q\003QQQ" |
+ q_to_nul >clear.noisybacktrack &&
+ printf "SVNQ%b%b" "\001\001QQQ" "\0200\001\002QQQ" |
+ q_to_nul >clear.noisyadvance &&
+ test_must_fail test-svn-fe -d preimage clear.noisybacktrack 15 &&
+ test-svn-fe -d preimage clear.noisyadvance 15 &&
+ test_cmp empty actual
+'
+
+test_expect_success 'preimage view: reject truncated preimage' '
+ printf "SVNQ%b" "\010QQQQ" | q_to_nul >clear.lateemptyread &&
+ printf "SVNQ%b" "\010\001QQQ" | q_to_nul >clear.latenonemptyread &&
+ printf "SVNQ%b" "\001\010QQQ" | q_to_nul >clear.longread &&
+ test_must_fail test-svn-fe -d preimage clear.lateemptyread 9 &&
+ test_must_fail test-svn-fe -d preimage clear.latenonemptyread 9 &&
+ test_must_fail test-svn-fe -d preimage clear.longread 9
+'
+
+test_expect_success 'forbid unconsumed inline data' '
+ printf "SVNQ%b%s%b%s" "QQQQ\003" "bar" "QQQQ\001" "x" |
+ q_to_nul >inline.clear &&
+ test_must_fail test-svn-fe -d preimage inline.clear 18 >actual
+'
+
+test_expect_success 'reject truncated inline data' '
+ printf "SVNQ%b%s" "QQQQ\003" "b" | q_to_nul >inline.trunc &&
+ test_must_fail test-svn-fe -d preimage inline.trunc 10
+'
+
+test_expect_success 'reject truncated inline data (after instruction section)' '
+ printf "SVNQ%b%b%s" "QQ\001\001\003" "\0201" "b" | q_to_nul >insn.trunc &&
+ test_must_fail test-svn-fe -d preimage insn.trunc 11
+'
+
+test_expect_success 'copyfrom_data' '
+ echo hi >expect &&
+ printf "SVNQ%b%b%b" "QQ\003\001\003" "\0203" "hi\n" | q_to_nul >copydat &&
+ test-svn-fe -d preimage copydat 13 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'multiple copyfrom_data' '
+ echo hi >expect &&
+ printf "SVNQ%b%b%b%b%b" "QQ\003\002\003" "\0201\0202" "hi\n" \
+ "QQQ\002Q" "\0200Q" | q_to_nul >copy.multi &&
+ len=$(wc -c <copy.multi) &&
+ test-svn-fe -d preimage copy.multi $len >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'incomplete multiple insn' '
+ printf "SVNQ%b%b%b" "QQ\003\002\003" "\0203\0200" "hi\n" |
+ q_to_nul >copy.partial &&
+ len=$(wc -c <copy.partial) &&
+ test_must_fail test-svn-fe -d preimage copy.partial $len
+'
+
+test_expect_success 'catch attempt to copy missing data' '
+ printf "SVNQ%b%b%s%b%s" "QQ\002\002\001" "\0201\0201" "X" \
+ "QQQQ\002" "YZ" |
+ q_to_nul >copy.incomplete &&
+ len=$(wc -c <copy.incomplete) &&
+ test_must_fail test-svn-fe -d preimage copy.incomplete $len
+'
+
+test_expect_success 'copyfrom target to repeat data' '
+ printf foofoo >expect &&
+ printf "SVNQ%b%b%s" "QQ\006\004\003" "\0203\0100\003Q" "foo" |
+ q_to_nul >copytarget.repeat &&
+ len=$(wc -c <copytarget.repeat) &&
+ test-svn-fe -d preimage copytarget.repeat $len >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'copyfrom target out of order' '
+ printf foooof >expect &&
+ printf "SVNQ%b%b%s" \
+ "QQ\006\007\003" "\0203\0101\002\0101\001\0101Q" "foo" |
+ q_to_nul >copytarget.reverse &&
+ len=$(wc -c <copytarget.reverse) &&
+ test-svn-fe -d preimage copytarget.reverse $len >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'catch copyfrom future' '
+ printf "SVNQ%b%b%s" "QQ\004\004\003" "\0202\0101\002\0201" "XYZ" |
+ q_to_nul >copytarget.infuture &&
+ len=$(wc -c <copytarget.infuture) &&
+ test_must_fail test-svn-fe -d preimage copytarget.infuture $len
+'
+
+test_expect_success 'copy to sustain' '
+ printf XYXYXYXYXYXZ >expect &&
+ printf "SVNQ%b%b%s" "QQ\014\004\003" "\0202\0111Q\0201" "XYZ" |
+ q_to_nul >copytarget.sustain &&
+ len=$(wc -c <copytarget.sustain) &&
+ test-svn-fe -d preimage copytarget.sustain $len >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'catch copy that overflows' '
+ printf "SVNQ%b%b%s" "QQ\003\003\001" "\0201\0177Q" X |
+ q_to_nul >copytarget.overflow &&
+ len=$(wc -c <copytarget.overflow) &&
+ test_must_fail test-svn-fe -d preimage copytarget.overflow $len
+'
+
+test_expect_success 'copyfrom source' '
+ printf foo >expect &&
+ printf "SVNQ%b%b" "Q\003\003\002Q" "\003Q" | q_to_nul >copysource.all &&
+ test-svn-fe -d preimage copysource.all 11 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'copy backwards' '
+ printf oof >expect &&
+ printf "SVNQ%b%b" "Q\003\003\006Q" "\001\002\001\001\001Q" |
+ q_to_nul >copysource.rev &&
+ test-svn-fe -d preimage copysource.rev 15 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'offsets are relative to window' '
+ printf fo >expect &&
+ printf "SVNQ%b%b%b%b" "Q\003\001\002Q" "\001Q" \
+ "\002\001\001\002Q" "\001Q" |
+ q_to_nul >copysource.two &&
+ test-svn-fe -d preimage copysource.two 18 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'example from notes/svndiff' '
+ printf aaaaccccdddddddd >expect &&
+ printf aaaabbbbcccc >source &&
+ printf "SVNQ%b%b%s" "Q\014\020\007\001" \
+ "\004Q\004\010\0201\0107\010" d |
+ q_to_nul >delta.example &&
+ len=$(wc -c <delta.example) &&
+ test-svn-fe -d source delta.example $len >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh
index b041516..749b75e 100755
--- a/t/t9100-git-svn-basic.sh
+++ b/t/t9100-git-svn-basic.sh
@@ -65,7 +65,8 @@ test_expect_success "$name" "
git update-index --add dir/file/file &&
git commit -m '$name' &&
test_must_fail git svn set-tree --find-copies-harder --rmdir \
- ${remotes_git_svn}..mybranch" || true
+ ${remotes_git_svn}..mybranch
+"
name='detect node change from directory to file #1'
@@ -79,7 +80,8 @@ test_expect_success "$name" '
git update-index --add -- bar &&
git commit -m "$name" &&
test_must_fail git svn set-tree --find-copies-harder --rmdir \
- ${remotes_git_svn}..mybranch2' || true
+ ${remotes_git_svn}..mybranch2
+'
name='detect node change from file to directory #2'
@@ -92,9 +94,12 @@ test_expect_success "$name" '
echo yyy > bar/zzz/yyy &&
git update-index --add bar/zzz/yyy &&
git commit -m "$name" &&
- test_must_fail git svn set-tree --find-copies-harder --rmdir \
- ${remotes_git_svn}..mybranch3' || true
-
+ git svn set-tree --find-copies-harder --rmdir \
+ ${remotes_git_svn}..mybranch3 &&
+ svn_cmd up "$SVN_TREE" &&
+ test -d "$SVN_TREE"/bar/zzz &&
+ test -e "$SVN_TREE"/bar/zzz/yyy
+'
name='detect node change from directory to file #2'
test_expect_success "$name" '
@@ -107,7 +112,8 @@ test_expect_success "$name" '
git update-index --add -- dir &&
git commit -m "$name" &&
test_must_fail git svn set-tree --find-copies-harder --rmdir \
- ${remotes_git_svn}..mybranch4' || true
+ ${remotes_git_svn}..mybranch4
+'
name='remove executable bit from a file'
@@ -134,10 +140,10 @@ test_expect_success "$name" '
test -x "$SVN_TREE"/exec.sh'
-name='executable file becomes a symlink to bar/zzz (file)'
+name='executable file becomes a symlink to file'
test_expect_success "$name" '
rm exec.sh &&
- ln -s bar/zzz exec.sh &&
+ ln -s file exec.sh &&
git update-index exec.sh &&
git commit -m "$name" &&
git svn set-tree --find-copies-harder --rmdir \
@@ -148,19 +154,19 @@ test_expect_success "$name" '
name='new symlink is added to a file that was also just made executable'
test_expect_success "$name" '
- chmod +x bar/zzz &&
- ln -s bar/zzz exec-2.sh &&
- git update-index --add bar/zzz exec-2.sh &&
+ chmod +x file &&
+ ln -s file exec-2.sh &&
+ git update-index --add file exec-2.sh &&
git commit -m "$name" &&
git svn set-tree --find-copies-harder --rmdir \
${remotes_git_svn}..mybranch5 &&
svn_cmd up "$SVN_TREE" &&
- test -x "$SVN_TREE"/bar/zzz &&
+ test -x "$SVN_TREE"/file &&
test -h "$SVN_TREE"/exec-2.sh'
name='modify a symlink to become a file'
test_expect_success "$name" '
- echo git help > help || true &&
+ echo git help >help &&
rm exec-2.sh &&
cp help exec-2.sh &&
git update-index exec-2.sh &&
@@ -195,14 +201,15 @@ name='check imported tree checksums expected tree checksums'
rm -f expected
if test_have_prereq UTF8
then
- echo tree bf522353586b1b883488f2bc73dab0d9f774b9a9 > expected
+ echo tree dc68b14b733e4ec85b04ab6f712340edc5dc936e > expected
fi
cat >> expected <<\EOF
-tree 83654bb36f019ae4fe77a0171f81075972087624
-tree 031b8d557afc6fea52894eaebb45bec52f1ba6d1
-tree 0b094cbff17168f24c302e297f55bfac65eb8bd3
-tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
-tree 56a30b966619b863674f5978696f4a3594f2fca9
+tree c3322890dcf74901f32d216f05c5044f670ce632
+tree d3ccd5035feafd17b030c5732e7808cc49122853
+tree d03e1630363d4881e68929d532746b20b0986b83
+tree 149d63cd5878155c846e8c55d7d8487de283f89e
+tree 312b76e4f64ce14893aeac8591eb3960b065e247
+tree 149d63cd5878155c846e8c55d7d8487de283f89e
tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
EOF
diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh
index 8cfdfe7..9a40f1e 100755
--- a/t/t9129-git-svn-i18n-commitencoding.sh
+++ b/t/t9129-git-svn-i18n-commitencoding.sh
@@ -29,7 +29,7 @@ fi
compare_svn_head_with () {
# extract just the log message and strip out committer info.
# don't use --limit here since svn 1.1.x doesn't have it,
- LC_ALL="$a_utf8_locale" svn log `git svn info --url` | perl -w -e '
+ LC_ALL="$a_utf8_locale" svn log `git svn info --url` | "$PERL_PATH" -w -e '
use bytes;
$/ = ("-"x72) . "\n";
my @x = <STDIN>;
diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh
index b324c49..c3443ce 100755
--- a/t/t9130-git-svn-authors-file.sh
+++ b/t/t9130-git-svn-authors-file.sh
@@ -96,8 +96,8 @@ test_expect_success 'fresh clone with svn.authors-file in config' '
rm -r "$GIT_DIR" &&
test x = x"$(git config svn.authorsfile)" &&
test_config="$HOME"/.gitconfig &&
- unset GIT_DIR &&
- unset GIT_CONFIG &&
+ sane_unset GIT_DIR &&
+ sane_unset GIT_CONFIG &&
git config --global \
svn.authorsfile "$HOME"/svn-authors &&
test x"$HOME"/svn-authors = x"$(git config svn.authorsfile)" &&
diff --git a/t/t9137-git-svn-dcommit-clobber-series.sh b/t/t9137-git-svn-dcommit-clobber-series.sh
index d60da63..c17aa31 100755
--- a/t/t9137-git-svn-dcommit-clobber-series.sh
+++ b/t/t9137-git-svn-dcommit-clobber-series.sh
@@ -20,8 +20,8 @@ test_expect_success '(supposedly) non-conflicting change from SVN' '
test x"`sed -n -e 61p < file`" = x61 &&
svn_cmd co "$svnrepo" tmp &&
(cd tmp &&
- perl -i.bak -p -e "s/^58$/5588/" file &&
- perl -i.bak -p -e "s/^61$/6611/" file &&
+ "$PERL_PATH" -i.bak -p -e "s/^58$/5588/" file &&
+ "$PERL_PATH" -i.bak -p -e "s/^61$/6611/" file &&
poke file &&
test x"`sed -n -e 58p < file`" = x5588 &&
test x"`sed -n -e 61p < file`" = x6611 &&
@@ -40,8 +40,8 @@ test_expect_success 'some unrelated changes to git' "
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.bak -p -e 's/^4\$/4444/' file &&
- perl -i.bak -p -e 's/^7\$/7777/' file &&
+ "$PERL_PATH" -i.bak -p -e 's/^4\$/4444/' file &&
+ "$PERL_PATH" -i.bak -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 &&
diff --git a/t/t9158-git-svn-mergeinfo.sh b/t/t9158-git-svn-mergeinfo.sh
index 3ab4390..8c9539e 100755
--- a/t/t9158-git-svn-mergeinfo.sh
+++ b/t/t9158-git-svn-mergeinfo.sh
@@ -38,4 +38,17 @@ test_expect_success 'verify svn:mergeinfo' '
test "$mergeinfo" = "/branches/foo:1-10"
'
+test_expect_success 'change svn:mergeinfo multiline' '
+ touch baz &&
+ git add baz &&
+ git commit -m "baz" &&
+ git svn dcommit --mergeinfo="/branches/bar:1-10 /branches/other:3-5,8,10-11"
+'
+
+test_expect_success 'verify svn:mergeinfo multiline' '
+ mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/trunk)
+ test "$mergeinfo" = "/branches/bar:1-10
+/branches/other:3-5,8,10-11"
+'
+
test_done
diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh
new file mode 100755
index 0000000..b4a4434
--- /dev/null
+++ b/t/t9160-git-svn-preserve-empty-dirs.sh
@@ -0,0 +1,153 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Ray Chen
+#
+
+test_description='git svn test (option --preserve-empty-dirs)
+
+This test uses git to clone a Subversion repository that contains empty
+directories, and checks that corresponding directories are created in the
+local Git repository with placeholder files.'
+
+. ./lib-git-svn.sh
+
+say 'define NO_SVN_TESTS to skip git svn tests'
+GIT_REPO=git-svn-repo
+
+test_expect_success 'initialize source svn repo containing empty dirs' '
+ svn_cmd mkdir -m x "$svnrepo"/trunk &&
+ svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+ (
+ cd "$SVN_TREE" &&
+ mkdir -p 1 2 3/a 3/b 4 5 6 &&
+ echo "First non-empty file" > 2/file1.txt &&
+ echo "Second non-empty file" > 2/file2.txt &&
+ echo "Third non-empty file" > 3/a/file1.txt &&
+ echo "Fourth non-empty file" > 3/b/file1.txt &&
+ svn_cmd add 1 2 3 4 5 6 &&
+ svn_cmd commit -m "initial commit" &&
+
+ mkdir 4/a &&
+ svn_cmd add 4/a &&
+ svn_cmd commit -m "nested empty directory" &&
+ mkdir 4/a/b &&
+ svn_cmd add 4/a/b &&
+ svn_cmd commit -m "deeply nested empty directory" &&
+ mkdir 4/a/b/c &&
+ svn_cmd add 4/a/b/c &&
+ svn_cmd commit -m "really deeply nested empty directory" &&
+ echo "Kill the placeholder file" > 4/a/b/c/foo &&
+ svn_cmd add 4/a/b/c/foo &&
+ svn_cmd commit -m "Regular file to remove placeholder" &&
+
+ svn_cmd del 2/file2.txt &&
+ svn_cmd del 3/b &&
+ svn_cmd commit -m "delete non-last entry in directory" &&
+
+ svn_cmd del 2/file1.txt &&
+ svn_cmd del 3/a &&
+ svn_cmd commit -m "delete last entry in directory" &&
+
+ echo "Conflict file" > 5/.placeholder &&
+ mkdir 6/.placeholder &&
+ svn_cmd add 5/.placeholder 6/.placeholder &&
+ svn_cmd commit -m "Placeholder Namespace conflict"
+ ) &&
+ rm -rf "$SVN_TREE"
+'
+
+test_expect_success 'clone svn repo with --preserve-empty-dirs' '
+ git svn clone "$svnrepo"/trunk --preserve-empty-dirs "$GIT_REPO"
+'
+
+# "$GIT_REPO"/1 should only contain the placeholder file.
+test_expect_success 'directory empty from inception' '
+ test -f "$GIT_REPO"/1/.gitignore &&
+ test $(find "$GIT_REPO"/1 -type f | wc -l) = "1"
+'
+
+# "$GIT_REPO"/2 and "$GIT_REPO"/3 should only contain the placeholder file.
+test_expect_success 'directory empty from subsequent svn commit' '
+ test -f "$GIT_REPO"/2/.gitignore &&
+ test $(find "$GIT_REPO"/2 -type f | wc -l) = "1" &&
+ test -f "$GIT_REPO"/3/.gitignore &&
+ test $(find "$GIT_REPO"/3 -type f | wc -l) = "1"
+'
+
+# No placeholder files should exist in "$GIT_REPO"/4, even though one was
+# generated for every sub-directory at some point in the repo's history.
+test_expect_success 'add entry to previously empty directory' '
+ test $(find "$GIT_REPO"/4 -type f | wc -l) = "1" &&
+ test -f "$GIT_REPO"/4/a/b/c/foo
+'
+
+# The HEAD~2 commit should not have introduced .gitignore placeholder files.
+test_expect_success 'remove non-last entry from directory' '
+ (
+ cd "$GIT_REPO" &&
+ git checkout HEAD~2
+ ) &&
+ test_must_fail test -f "$GIT_REPO"/2/.gitignore &&
+ test_must_fail test -f "$GIT_REPO"/3/.gitignore
+'
+
+# After re-cloning the repository with --placeholder-file specified, there
+# should be 5 files named ".placeholder" in the local Git repo.
+test_expect_success 'clone svn repo with --placeholder-file specified' '
+ rm -rf "$GIT_REPO" &&
+ git svn clone "$svnrepo"/trunk --preserve-empty-dirs \
+ --placeholder-file=.placeholder "$GIT_REPO" &&
+ find "$GIT_REPO" -type f -name ".placeholder" &&
+ test $(find "$GIT_REPO" -type f -name ".placeholder" | wc -l) = "5"
+'
+
+# "$GIT_REPO"/5/.placeholder should be a file, and non-empty.
+test_expect_success 'placeholder namespace conflict with file' '
+ test -s "$GIT_REPO"/5/.placeholder
+'
+
+# "$GIT_REPO"/6/.placeholder should be a directory, and the "$GIT_REPO"/6 tree
+# should only contain one file: the placeholder.
+test_expect_success 'placeholder namespace conflict with directory' '
+ test -d "$GIT_REPO"/6/.placeholder &&
+ test -f "$GIT_REPO"/6/.placeholder/.placeholder &&
+ test $(find "$GIT_REPO"/6 -type f | wc -l) = "1"
+'
+
+# Prepare a second set of svn commits to test persistence during rebase.
+test_expect_success 'second set of svn commits and rebase' '
+ svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+ (
+ cd "$SVN_TREE" &&
+ mkdir -p 7 &&
+ echo "This should remove placeholder" > 1/file1.txt &&
+ echo "This should not remove placeholder" > 5/file1.txt &&
+ svn_cmd add 7 1/file1.txt 5/file1.txt &&
+ svn_cmd commit -m "subsequent svn commit for persistence tests"
+ ) &&
+ rm -rf "$SVN_TREE" &&
+ (
+ cd "$GIT_REPO" &&
+ git svn rebase
+ )
+'
+
+# Check that --preserve-empty-dirs and --placeholder-file flag state
+# stays persistent over multiple invocations.
+test_expect_success 'flag persistence during subsqeuent rebase' '
+ test -f "$GIT_REPO"/7/.placeholder &&
+ test $(find "$GIT_REPO"/7 -type f | wc -l) = "1"
+'
+
+# Check that placeholder files are properly removed when unnecessary,
+# even across multiple invocations.
+test_expect_success 'placeholder list persistence during subsqeuent rebase' '
+ test -f "$GIT_REPO"/1/file1.txt &&
+ test $(find "$GIT_REPO"/1 -type f | wc -l) = "1" &&
+
+ test -f "$GIT_REPO"/5/file1.txt &&
+ test -f "$GIT_REPO"/5/.placeholder &&
+ test $(find "$GIT_REPO"/5 -type f | wc -l) = "2"
+'
+
+test_done
diff --git a/t/t9161-git-svn-mergeinfo-push.sh b/t/t9161-git-svn-mergeinfo-push.sh
new file mode 100755
index 0000000..6ef0c0b
--- /dev/null
+++ b/t/t9161-git-svn-mergeinfo-push.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# Portions copyright (c) 2007, 2009 Sam Vilain
+# Portions copyright (c) 2011 Bryan Jacobs
+#
+
+test_description='git-svn svn mergeinfo propagation'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svn dump' "
+ svnadmin load -q '$rawsvnrepo' \
+ < '$TEST_DIRECTORY/t9161/branches.dump' &&
+ git svn init --minimize-url -R svnmerge \
+ -T trunk -b branches '$svnrepo' &&
+ git svn fetch --all
+ "
+
+test_expect_success 'propagate merge information' '
+ git config svn.pushmergeinfo yes &&
+ git checkout svnb1 &&
+ git merge --no-ff svnb2 &&
+ git svn dcommit
+ '
+
+test_expect_success 'check svn:mergeinfo' '
+ mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+ test "$mergeinfo" = "/branches/svnb2:3,8"
+ '
+
+test_expect_success 'merge another branch' '
+ git merge --no-ff svnb3 &&
+ git svn dcommit
+ '
+
+test_expect_success 'check primary parent mergeinfo respected' '
+ mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+ test "$mergeinfo" = "/branches/svnb2:3,8
+/branches/svnb3:4,9"
+ '
+
+test_expect_success 'merge existing merge' '
+ git merge --no-ff svnb4 &&
+ git svn dcommit
+ '
+
+test_expect_success "check both parents' mergeinfo respected" '
+ mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+ test "$mergeinfo" = "/branches/svnb2:3,8
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+ '
+
+test_expect_success 'make further commits to branch' '
+ git checkout svnb2 &&
+ touch newb2file &&
+ git add newb2file &&
+ git commit -m "later b2 commit" &&
+ touch newb2file-2 &&
+ git add newb2file-2 &&
+ git commit -m "later b2 commit 2" &&
+ git svn dcommit
+ '
+
+test_expect_success 'second forward merge' '
+ git checkout svnb1 &&
+ git merge --no-ff svnb2 &&
+ git svn dcommit
+ '
+
+test_expect_success 'check new mergeinfo added' '
+ mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb1)
+ test "$mergeinfo" = "/branches/svnb2:3,8,16-17
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+ '
+
+test_expect_success 'reintegration merge' '
+ git checkout svnb4 &&
+ git merge --no-ff svnb1 &&
+ git svn dcommit
+ '
+
+test_expect_success 'check reintegration mergeinfo' '
+ mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/branches/svnb4)
+ test "$mergeinfo" = "/branches/svnb1:2-4,7-9,13-18
+/branches/svnb2:3,8,16-17
+/branches/svnb3:4,9
+/branches/svnb4:5-6,10-12
+/branches/svnb5:6,11"
+ '
+
+test_expect_success 'dcommit a merge at the top of a stack' '
+ git checkout svnb1 &&
+ touch anotherfile &&
+ git add anotherfile &&
+ git commit -m "a commit" &&
+ git merge svnb4 &&
+ git svn dcommit
+ '
+
+test_done
diff --git a/t/t9161/branches.dump b/t/t9161/branches.dump
new file mode 100644
index 0000000..e61c3e7
--- /dev/null
+++ b/t/t9161/branches.dump
@@ -0,0 +1,374 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1ef08553-f2d1-45df-b38c-19af6b7c926d
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2011-09-02T16:08:02.941384Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+Base commit
+
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:08:27.205062Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb1
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:43.628137Z
+PROPS-END
+
+Node-path: branches/svnb1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb2
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:46.339930Z
+PROPS-END
+
+Node-path: branches/svnb2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb3
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:49.394515Z
+PROPS-END
+
+Node-path: branches/svnb3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb4
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:54.114607Z
+PROPS-END
+
+Node-path: branches/svnb4
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 6
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+Create branch svnb5
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:09:58.602623Z
+PROPS-END
+
+Node-path: branches/svnb5
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 7
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b1 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:20.292369Z
+PROPS-END
+
+Node-path: branches/svnb1/b1file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 8
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b2 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:38.429199Z
+PROPS-END
+
+Node-path: branches/svnb2/b2file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 9
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b3 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:10:52.843023Z
+PROPS-END
+
+Node-path: branches/svnb3/b3file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 10
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b4 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:17.489870Z
+PROPS-END
+
+Node-path: branches/svnb4/b4file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 11
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+b5 commit
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:32.277404Z
+PROPS-END
+
+Node-path: branches/svnb5/b5file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 12
+Prop-content-length: 192
+Content-length: 192
+
+K 7
+svn:log
+V 90
+Merge remote-tracking branch 'svnb5' into HEAD
+
+* svnb5:
+ b5 commit
+ Create branch svnb5
+K 10
+svn:author
+V 7
+bjacobs
+K 8
+svn:date
+V 27
+2011-09-02T16:11:54.274722Z
+PROPS-END
+
+Node-path: branches/svnb4
+Node-kind: dir
+Node-action: change
+Prop-content-length: 56
+Content-length: 56
+
+K 13
+svn:mergeinfo
+V 21
+/branches/svnb5:6,11
+
+PROPS-END
+
+
+Node-path: branches/svnb4/b5file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
diff --git a/t/t9162-git-svn-dcommit-interactive.sh b/t/t9162-git-svn-dcommit-interactive.sh
new file mode 100755
index 0000000..e38d9fa
--- /dev/null
+++ b/t/t9162-git-svn-dcommit-interactive.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Frédéric Heitzmann
+
+test_description='git svn dcommit --interactive series'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' '
+ svn_cmd mkdir -m"mkdir test-interactive" "$svnrepo/test-interactive" &&
+ git svn clone "$svnrepo/test-interactive" test-interactive &&
+ cd test-interactive &&
+ touch foo && git add foo && git commit -m"foo: first commit" &&
+ git svn dcommit
+ '
+
+test_expect_success 'answers: y [\n] yes' '
+ (
+ echo "change #1" >> foo && git commit -a -m"change #1" &&
+ echo "change #2" >> foo && git commit -a -m"change #2" &&
+ echo "change #3" >> foo && git commit -a -m"change #3" &&
+ ( echo "y
+
+y" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+ test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn)
+ )
+ '
+
+test_expect_success 'answers: yes yes no' '
+ (
+ echo "change #1" >> foo && git commit -a -m"change #1" &&
+ echo "change #2" >> foo && git commit -a -m"change #2" &&
+ echo "change #3" >> foo && git commit -a -m"change #3" &&
+ ( echo "yes
+yes
+no" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+ test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
+ git reset --hard remotes/git-svn
+ )
+ '
+
+test_expect_success 'answers: yes quit' '
+ (
+ echo "change #1" >> foo && git commit -a -m"change #1" &&
+ echo "change #2" >> foo && git commit -a -m"change #2" &&
+ echo "change #3" >> foo && git commit -a -m"change #3" &&
+ ( echo "yes
+quit" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+ test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
+ git reset --hard remotes/git-svn
+ )
+ '
+
+test_expect_success 'answers: all' '
+ (
+ echo "change #1" >> foo && git commit -a -m"change #1" &&
+ echo "change #2" >> foo && git commit -a -m"change #2" &&
+ echo "change #3" >> foo && git commit -a -m"change #3" &&
+ ( echo "all" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+ test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn) &&
+ git reset --hard remotes/git-svn
+ )
+ '
+
+test_done
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index 41db05c..b59be9a 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -19,9 +19,9 @@ then
test_done
fi
-CVSROOT=$(pwd)/cvsroot
-CVSWORK=$(pwd)/cvswork
-GIT_DIR=$(pwd)/.git
+CVSROOT=$PWD/cvsroot
+CVSWORK=$PWD/cvswork
+GIT_DIR=$PWD/.git
export CVSROOT CVSWORK GIT_DIR
rm -rf "$CVSROOT" "$CVSWORK"
@@ -321,7 +321,7 @@ test_expect_success 'use the same checkout for Git and CVS' '
(mkdir shared &&
cd shared &&
- unset GIT_DIR &&
+ sane_unset GIT_DIR &&
cvs co . &&
git init &&
git add " space" &&
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 2a53640..2fcf269 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -12,7 +12,7 @@ test_description='test git fast-import utility'
# This could be written as "head -c $1", but IRIX "head" does not
# support the -c option.
head_c () {
- perl -e '
+ "$PERL_PATH" -e '
my $len = $ARGV[1];
while ($len > 0) {
my $s;
@@ -24,6 +24,13 @@ head_c () {
' - "$1"
}
+verify_packs () {
+ for p in .git/objects/pack/*.pack
+ do
+ git verify-pack "$@" "$p" || return
+ done
+}
+
file2_data='file2
second line of EOF'
@@ -94,14 +101,21 @@ data <<EOF
An annotated tag without a tagger
EOF
+tag series-A-blob
+from :3
+data <<EOF
+An annotated tag that annotates a blob.
+EOF
+
INPUT_END
test_expect_success \
'A: create pack from stdin' \
'git fast-import --export-marks=marks.out <input &&
git whatchanged master'
-test_expect_success \
- 'A: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'A: verify pack' '
+ verify_packs
+'
cat >expect <<EOF
author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
@@ -152,6 +166,18 @@ test_expect_success 'A: verify tag/series-A' '
'
cat >expect <<EOF
+object $(git rev-parse refs/heads/master:file3)
+type blob
+tag series-A-blob
+
+An annotated tag that annotates a blob.
+EOF
+test_expect_success 'A: verify tag/series-A-blob' '
+ git cat-file tag tags/series-A-blob >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<EOF
:2 `git rev-parse --verify master:file2`
:3 `git rev-parse --verify master:file3`
:4 `git rev-parse --verify master:file4`
@@ -170,6 +196,55 @@ test_expect_success \
test_cmp expect marks.new'
test_tick
+new_blob=$(echo testing | git hash-object --stdin)
+cat >input <<INPUT_END
+tag series-A-blob-2
+from $(git rev-parse refs/heads/master:file3)
+data <<EOF
+Tag blob by sha1.
+EOF
+
+blob
+mark :6
+data <<EOF
+testing
+EOF
+
+commit refs/heads/new_blob
+committer <> 0 +0000
+data 0
+M 644 :6 new_blob
+#pretend we got sha1 from fast-import
+ls "new_blob"
+
+tag series-A-blob-3
+from $new_blob
+data <<EOF
+Tag new_blob.
+EOF
+INPUT_END
+
+cat >expect <<EOF
+object $(git rev-parse refs/heads/master:file3)
+type blob
+tag series-A-blob-2
+
+Tag blob by sha1.
+object $new_blob
+type blob
+tag series-A-blob-3
+
+Tag new_blob.
+EOF
+
+test_expect_success \
+ 'A: tag blob by sha1' \
+ 'git fast-import <input &&
+ git cat-file tag tags/series-A-blob-2 >actual &&
+ git cat-file tag tags/series-A-blob-3 >>actual &&
+ test_cmp expect actual'
+
+test_tick
cat >input <<INPUT_END
commit refs/heads/verify--import-marks
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
@@ -185,9 +260,11 @@ test_expect_success \
'A: verify marks import does not crash' \
'git fast-import --import-marks=marks.out <input &&
git whatchanged verify--import-marks'
-test_expect_success \
- 'A: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'A: verify pack' '
+ verify_packs
+'
+
cat >expect <<EOF
:000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A copy-of-file2
EOF
@@ -324,6 +401,105 @@ test_expect_success \
test `git rev-parse master` = `git rev-parse TEMP_TAG^`'
rm -f .git/TEMP_TAG
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/empty-committer-1
+committer <> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: accept empty committer' '
+ git fast-import <input &&
+ out=$(git fsck) &&
+ echo "$out" &&
+ test -z "$out"
+'
+git update-ref -d refs/heads/empty-committer-1 || true
+
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/empty-committer-2
+committer <a@b.com> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: accept and fixup committer with no name' '
+ git fast-import <input &&
+ out=$(git fsck) &&
+ echo "$out" &&
+ test -z "$out"
+'
+git update-ref -d refs/heads/empty-committer-2 || true
+
+git gc 2>/dev/null >/dev/null
+git prune 2>/dev/null >/dev/null
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name email> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (1)' '
+ test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <e<mail> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (2)' '
+ test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <email>> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (3)' '
+ test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name <email $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (4)' '
+ test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
+cat >input <<INPUT_END
+commit refs/heads/invalid-committer
+committer Name<email> $GIT_COMMITTER_DATE
+data <<COMMIT
+empty commit
+COMMIT
+INPUT_END
+test_expect_success 'B: fail on invalid committer (5)' '
+ test_must_fail git fast-import <input
+'
+git update-ref -d refs/heads/invalid-committer || true
+
###
### series C
###
@@ -348,9 +524,11 @@ test_expect_success \
'C: incremental import create pack from stdin' \
'git fast-import <input &&
git whatchanged branch'
-test_expect_success \
- 'C: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'C: verify pack' '
+ verify_packs
+'
+
test_expect_success \
'C: validate reuse existing blob' \
'test $newf = `git rev-parse --verify branch:file2/newf` &&
@@ -406,9 +584,10 @@ test_expect_success \
'D: inline data in commit' \
'git fast-import <input &&
git whatchanged branch'
-test_expect_success \
- 'D: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'D: verify pack' '
+ verify_packs
+'
cat >expect <<EOF
:000000 100755 0000000000000000000000000000000000000000 35a59026a33beac1569b1c7f66f3090ce9c09afc A newdir/exec.sh
@@ -452,9 +631,10 @@ test_expect_success 'E: rfc2822 date, --date-format=raw' '
test_expect_success \
'E: rfc2822 date, --date-format=rfc2822' \
'git fast-import --date-format=rfc2822 <input'
-test_expect_success \
- 'E: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'E: verify pack' '
+ verify_packs
+'
cat >expect <<EOF
author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500
@@ -503,9 +683,10 @@ test_expect_success \
fi
fi
'
-test_expect_success \
- 'F: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'F: verify pack' '
+ verify_packs
+'
cat >expect <<EOF
tree `git rev-parse branch~1^{tree}`
@@ -539,9 +720,11 @@ INPUT_END
test_expect_success \
'G: non-fast-forward update forced' \
'git fast-import --force <input'
-test_expect_success \
- 'G: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'G: verify pack' '
+ verify_packs
+'
+
test_expect_success \
'G: branch changed, but logged' \
'test $old_branch != `git rev-parse --verify branch^0` &&
@@ -576,9 +759,10 @@ test_expect_success \
'H: deletall, add 1' \
'git fast-import <input &&
git whatchanged H'
-test_expect_success \
- 'H: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'H: verify pack' '
+ verify_packs
+'
cat >expect <<EOF
:100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file2/newf
@@ -654,6 +838,18 @@ test_expect_success \
'test 1 = `git rev-list J | wc -l` &&
test 0 = `git ls-tree J | wc -l`'
+cat >input <<INPUT_END
+reset refs/heads/J2
+
+tag wrong_tag
+from refs/heads/J2
+data <<EOF
+Tag branch that was reset.
+EOF
+INPUT_END
+test_expect_success \
+ 'J: tag must fail on empty branch' \
+ 'test_must_fail git fast-import <input'
###
### series K
###
@@ -734,6 +930,47 @@ test_expect_success \
git diff-tree --abbrev --raw L^ L >output &&
test_cmp expect output'
+cat >input <<INPUT_END
+blob
+mark :1
+data <<EOF
+the data
+EOF
+
+commit refs/heads/L2
+committer C O Mitter <committer@example.com> 1112912473 -0700
+data <<COMMIT
+init L2
+COMMIT
+M 644 :1 a/b/c
+M 644 :1 a/b/d
+M 644 :1 a/e/f
+
+commit refs/heads/L2
+committer C O Mitter <committer@example.com> 1112912473 -0700
+data <<COMMIT
+update L2
+COMMIT
+C a g
+C a/e g/b
+M 644 :1 g/b/h
+INPUT_END
+
+cat <<EOF >expect
+g/b/f
+g/b/h
+EOF
+
+test_expect_success \
+ 'L: nested tree copy does not corrupt deltas' \
+ 'git fast-import <input &&
+ git ls-tree L2 g/b/ >tmp &&
+ cat tmp | cut -f 2 >actual &&
+ test_cmp expect actual &&
+ git fsck `git rev-parse L2`'
+
+git update-ref -d refs/heads/L2
+
###
### series M
###
@@ -1088,6 +1325,45 @@ test_expect_success \
INPUT_END'
test_expect_success \
+ 'N: reject foo/ syntax in copy source' \
+ 'test_must_fail git fast-import <<-INPUT_END
+ commit refs/heads/N5C
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy with invalid syntax
+ COMMIT
+
+ from refs/heads/branch^0
+ C file2/ file3
+ INPUT_END'
+
+test_expect_success \
+ 'N: reject foo/ syntax in rename source' \
+ 'test_must_fail git fast-import <<-INPUT_END
+ commit refs/heads/N5D
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ rename with invalid syntax
+ COMMIT
+
+ from refs/heads/branch^0
+ R file2/ file3
+ INPUT_END'
+
+test_expect_success \
+ 'N: reject foo/ syntax in ls argument' \
+ 'test_must_fail git fast-import <<-INPUT_END
+ commit refs/heads/N5E
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy with invalid syntax
+ COMMIT
+
+ from refs/heads/branch^0
+ ls "file2/"
+ INPUT_END'
+
+test_expect_success \
'N: copy to root by id and modify' \
'echo "hello, world" >expect.foo &&
echo hello >expect.bar &&
@@ -1381,7 +1657,7 @@ M 160000 :6 sub
INPUT_END
test_expect_success \
- 'P: supermodule & submodule mix' \
+ 'P: superproject & submodule mix' \
'git fast-import <input &&
git checkout subuse1 &&
rm -rf sub && mkdir sub && (cd sub &&
@@ -1599,9 +1875,10 @@ test_expect_success \
'Q: commit notes' \
'git fast-import <input &&
git whatchanged notes-test'
-test_expect_success \
- 'Q: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'Q: verify pack' '
+ verify_packs
+'
commit1=$(git rev-parse notes-test~2)
commit2=$(git rev-parse notes-test^)
@@ -1768,6 +2045,23 @@ test_expect_success \
'Q: verify second note for second commit' \
'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
+cat >input <<EOF
+reset refs/heads/Q0
+
+commit refs/heads/note-Q0
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+Note for an empty branch.
+COMMIT
+
+N inline refs/heads/Q0
+data <<NOTE
+some note
+NOTE
+EOF
+test_expect_success \
+ 'Q: deny note on empty branch' \
+ 'test_must_fail git fast-import <input'
###
### series R (feature and option)
###
@@ -1823,7 +2117,7 @@ test_expect_success \
grep :1 git.marks'
test_expect_success \
- 'R: export-marks options can be overriden by commandline options' \
+ 'R: export-marks options can be overridden by commandline options' \
'cat input | git fast-import --export-marks=other.marks &&
grep :1 other.marks'
@@ -1882,6 +2176,53 @@ test_expect_success 'R: --import-marks-if-exists' '
test_cmp expect io.marks
'
+test_expect_success 'R: feature import-marks-if-exists' '
+ rm -f io.marks &&
+ >expect &&
+
+ git fast-import --export-marks=io.marks <<-\EOF &&
+ feature import-marks-if-exists=not_io.marks
+ EOF
+ test_cmp expect io.marks &&
+
+ blob=$(echo hi | git hash-object --stdin) &&
+
+ echo ":1 $blob" >io.marks &&
+ echo ":1 $blob" >expect &&
+ echo ":2 $blob" >>expect &&
+
+ git fast-import --export-marks=io.marks <<-\EOF &&
+ feature import-marks-if-exists=io.marks
+ blob
+ mark :2
+ data 3
+ hi
+
+ EOF
+ test_cmp expect io.marks &&
+
+ echo ":3 $blob" >>expect &&
+
+ git fast-import --import-marks=io.marks \
+ --export-marks=io.marks <<-\EOF &&
+ feature import-marks-if-exists=not_io.marks
+ blob
+ mark :3
+ data 3
+ hi
+
+ EOF
+ test_cmp expect io.marks &&
+
+ >expect &&
+
+ git fast-import --import-marks-if-exists=not_io.marks \
+ --export-marks=io.marks <<-\EOF
+ feature import-marks-if-exists=io.marks
+ EOF
+ test_cmp expect io.marks
+'
+
cat >input << EOF
feature import-marks=marks.out
feature export-marks=marks.new
@@ -1954,7 +2295,7 @@ test_expect_success 'R: cat-blob-fd must be a nonnegative integer' '
test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
'
-test_expect_success 'R: print old blob' '
+test_expect_success NOT_MINGW 'R: print old blob' '
blob=$(echo "yes it can" | git hash-object -w --stdin) &&
cat >expect <<-EOF &&
${blob} blob 11
@@ -1966,7 +2307,7 @@ test_expect_success 'R: print old blob' '
test_cmp expect actual
'
-test_expect_success 'R: in-stream cat-blob-fd not respected' '
+test_expect_success NOT_MINGW 'R: in-stream cat-blob-fd not respected' '
echo hello >greeting &&
blob=$(git hash-object -w greeting) &&
cat >expect <<-EOF &&
@@ -1987,7 +2328,7 @@ test_expect_success 'R: in-stream cat-blob-fd not respected' '
test_cmp expect actual.1
'
-test_expect_success 'R: print new blob' '
+test_expect_success NOT_MINGW 'R: print new blob' '
blob=$(echo "yep yep yep" | git hash-object --stdin) &&
cat >expect <<-EOF &&
${blob} blob 12
@@ -2005,7 +2346,7 @@ test_expect_success 'R: print new blob' '
test_cmp expect actual
'
-test_expect_success 'R: print new blob by sha1' '
+test_expect_success NOT_MINGW 'R: print new blob by sha1' '
blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
cat >expect <<-EOF &&
${blob} blob 25
@@ -2197,6 +2538,48 @@ test_expect_success 'R: quiet option results in no stats being output' '
test_cmp empty output
'
+test_expect_success 'R: feature done means terminating "done" is mandatory' '
+ echo feature done | test_must_fail git fast-import &&
+ test_must_fail git fast-import --done </dev/null
+'
+
+test_expect_success 'R: terminating "done" with trailing gibberish is ok' '
+ git fast-import <<-\EOF &&
+ feature done
+ done
+ trailing gibberish
+ EOF
+ git fast-import <<-\EOF
+ done
+ more trailing gibberish
+ EOF
+'
+
+test_expect_success 'R: terminating "done" within commit' '
+ cat >expect <<-\EOF &&
+ OBJID
+ :000000 100644 OBJID OBJID A hello.c
+ :000000 100644 OBJID OBJID A hello2.c
+ EOF
+ git fast-import <<-EOF &&
+ commit refs/heads/done-ends
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<EOT
+ Commit terminated by "done" command
+ EOT
+ M 100644 inline hello.c
+ data <<EOT
+ Hello, world.
+ EOT
+ C hello.c hello2.c
+ done
+ EOF
+ git rev-list done-ends |
+ git diff-tree -r --stdin --root --always |
+ sed -e "s/$_x40/OBJID/g" >actual &&
+ test_cmp expect actual
+'
+
cat >input <<EOF
option git non-existing-option
EOF
@@ -2252,13 +2635,14 @@ test_expect_success \
'R: blob bigger than threshold' \
'test_create_repo R &&
git --git-dir=R/.git fast-import --big-file-threshold=1 <input'
-test_expect_success \
- 'R: verify created pack' \
- ': >verify &&
- for p in R/.git/objects/pack/*.pack;
- do
- git verify-pack -v $p >>verify || exit;
- done'
+
+test_expect_success 'R: verify created pack' '
+ (
+ cd R &&
+ verify_packs -v > ../verify
+ )
+'
+
test_expect_success \
'R: verify written objects' \
'git --git-dir=R/.git cat-file blob big-file:big1 >actual &&
@@ -2271,4 +2655,291 @@ test_expect_success \
'n=$(grep $a verify | wc -l) &&
test 1 = $n'
+###
+### series S
+###
+#
+# Make sure missing spaces and EOLs after mark references
+# cause errors.
+#
+# Setup:
+#
+# 1--2--4
+# \ /
+# -3-
+#
+# commit marks: 301, 302, 303, 304
+# blob marks: 403, 404, resp.
+# note mark: 202
+#
+# The error message when a space is missing not at the
+# end of the line is:
+#
+# Missing space after ..
+#
+# or when extra characters come after the mark at the end
+# of the line:
+#
+# Garbage after ..
+#
+# or when the dataref is neither "inline " or a known SHA1,
+#
+# Invalid dataref ..
+#
+test_tick
+
+cat >input <<INPUT_END
+commit refs/heads/S
+mark :301
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit 1
+COMMIT
+M 100644 inline hello.c
+data <<BLOB
+blob 1
+BLOB
+
+commit refs/heads/S
+mark :302
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit 2
+COMMIT
+from :301
+M 100644 inline hello.c
+data <<BLOB
+blob 2
+BLOB
+
+blob
+mark :403
+data <<BLOB
+blob 3
+BLOB
+
+blob
+mark :202
+data <<BLOB
+note 2
+BLOB
+INPUT_END
+
+test_expect_success 'S: initialize for S tests' '
+ git fast-import --export-marks=marks <input
+'
+
+#
+# filemodify, three datarefs
+#
+test_expect_success 'S: filemodify with garbage after mark must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ commit refs/heads/S
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit N
+ COMMIT
+ M 100644 :403x hello.c
+ EOF
+ cat err &&
+ test_i18ngrep "space after mark" err
+'
+
+# inline is misspelled; fast-import thinks it is some unknown dataref
+test_expect_success 'S: filemodify with garbage after inline must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ commit refs/heads/S
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit N
+ COMMIT
+ M 100644 inlineX hello.c
+ data <<BLOB
+ inline
+ BLOB
+ EOF
+ cat err &&
+ test_i18ngrep "nvalid dataref" err
+'
+
+test_expect_success 'S: filemodify with garbage after sha1 must fail' '
+ sha1=$(grep :403 marks | cut -d\ -f2) &&
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ commit refs/heads/S
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit N
+ COMMIT
+ M 100644 ${sha1}x hello.c
+ EOF
+ cat err &&
+ test_i18ngrep "space after SHA1" err
+'
+
+#
+# notemodify, three ways to say dataref
+#
+test_expect_success 'S: notemodify with garabge after mark dataref must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ commit refs/heads/S
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit S note dataref markref
+ COMMIT
+ N :202x :302
+ EOF
+ cat err &&
+ test_i18ngrep "space after mark" err
+'
+
+test_expect_success 'S: notemodify with garbage after inline dataref must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ commit refs/heads/S
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit S note dataref inline
+ COMMIT
+ N inlineX :302
+ data <<BLOB
+ note blob
+ BLOB
+ EOF
+ cat err &&
+ test_i18ngrep "nvalid dataref" err
+'
+
+test_expect_success 'S: notemodify with garbage after sha1 dataref must fail' '
+ sha1=$(grep :202 marks | cut -d\ -f2) &&
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ commit refs/heads/S
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit S note dataref sha1
+ COMMIT
+ N ${sha1}x :302
+ EOF
+ cat err &&
+ test_i18ngrep "space after SHA1" err
+'
+
+#
+# notemodify, mark in committish
+#
+test_expect_success 'S: notemodify with garbarge after mark committish must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ commit refs/heads/Snotes
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit S note committish
+ COMMIT
+ N :202 :302x
+ EOF
+ cat err &&
+ test_i18ngrep "after mark" err
+'
+
+#
+# from
+#
+test_expect_success 'S: from with garbage after mark must fail' '
+ # no &&
+ git fast-import --import-marks=marks --export-marks=marks <<-EOF 2>err
+ commit refs/heads/S2
+ mark :303
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit 3
+ COMMIT
+ from :301x
+ M 100644 :403 hello.c
+ EOF
+
+ ret=$? &&
+ echo returned $ret &&
+ test $ret -ne 0 && # failed, but it created the commit
+
+ # go create the commit, need it for merge test
+ git fast-import --import-marks=marks --export-marks=marks <<-EOF &&
+ commit refs/heads/S2
+ mark :303
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ commit 3
+ COMMIT
+ from :301
+ M 100644 :403 hello.c
+ EOF
+
+ # now evaluate the error
+ cat err &&
+ test_i18ngrep "after mark" err
+'
+
+
+#
+# merge
+#
+test_expect_success 'S: merge with garbage after mark must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ commit refs/heads/S
+ mark :304
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ merge 4
+ COMMIT
+ from :302
+ merge :303x
+ M 100644 :403 hello.c
+ EOF
+ cat err &&
+ test_i18ngrep "after mark" err
+'
+
+#
+# tag, from markref
+#
+test_expect_success 'S: tag with garbage after mark must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ tag refs/tags/Stag
+ from :302x
+ tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<TAG
+ tag S
+ TAG
+ EOF
+ cat err &&
+ test_i18ngrep "after mark" err
+'
+
+#
+# cat-blob markref
+#
+test_expect_success 'S: cat-blob with garbage after mark must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ cat-blob :403x
+ EOF
+ cat err &&
+ test_i18ngrep "after mark" err
+'
+
+#
+# ls markref
+#
+test_expect_success 'S: ls with garbage after mark must fail' '
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ ls :302x hello.c
+ EOF
+ cat err &&
+ test_i18ngrep "space after mark" err
+'
+
+test_expect_success 'S: ls with garbage after sha1 must fail' '
+ sha1=$(grep :302 marks | cut -d\ -f2) &&
+ test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
+ ls ${sha1}x hello.c
+ EOF
+ cat err &&
+ test_i18ngrep "space after tree-ish" err
+'
+
test_done
diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh
index 463254c..83acf68 100755
--- a/t/t9301-fast-import-notes.sh
+++ b/t/t9301-fast-import-notes.sh
@@ -505,9 +505,63 @@ test_expect_success 'verify that non-notes are untouched by a fanout change' '
test_cmp expect_non-note3 actual
'
+
+# Change the notes for the three top commits
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+changing notes for the top three commits
+COMMIT
+from refs/notes/many_notes^0
+INPUT_END
+
+rm expect
+i=$num_commits
+j=0
+while test $j -lt 3
+do
+ cat >>input <<INPUT_END
+N inline refs/heads/many_commits~$j
+data <<EOF
+changed note for commit #$i
+EOF
+INPUT_END
+ cat >>expect <<EXPECT_END
+ commit #$i
+ changed note for commit #$i
+EXPECT_END
+ i=$(($i - 1))
+ j=$(($j + 1))
+done
+
+test_expect_success 'change a few existing notes' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/many_notes git log -n3 refs/heads/many_commits |
+ grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'verify that changing notes respect existing fanout' '
+
+ # None of the entries in the top-level notes tree should be a full SHA1
+ git ls-tree --name-only refs/notes/many_notes |
+ while read path
+ do
+ if test $(expr length "$path") -ge 40
+ then
+ return 1
+ fi
+ done
+
+'
+
remaining_notes=10
test_tick
-cat >>input <<INPUT_END
+cat >input <<INPUT_END
commit refs/notes/many_notes
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT
@@ -516,12 +570,11 @@ COMMIT
from refs/notes/many_notes^0
INPUT_END
-i=$remaining_notes
-while test $i -lt $num_commits
+i=$(($num_commits - $remaining_notes))
+for sha1 in $(git rev-list -n $i refs/heads/many_commits)
do
- i=$(($i + 1))
cat >>input <<INPUT_END
-N 0000000000000000000000000000000000000000 :$i
+N 0000000000000000000000000000000000000000 $sha1
INPUT_END
done
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index 950d0ff..77447b7 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -86,7 +86,7 @@ test_expect_success 'import/export-marks' '
git checkout -b marks master &&
git fast-export --export-marks=tmp-marks HEAD &&
test -s tmp-marks &&
- test $(wc -l < tmp-marks) -eq 3 &&
+ test_line_count = 3 tmp-marks &&
test $(
git fast-export --import-marks=tmp-marks\
--export-marks=tmp-marks HEAD |
@@ -101,7 +101,7 @@ test_expect_success 'import/export-marks' '
grep ^commit\ |
wc -l) \
-eq 1 &&
- test $(wc -l < tmp-marks) -eq 4
+ test_line_count = 4 tmp-marks
'
@@ -424,7 +424,7 @@ test_expect_success 'fast-export quotes pathnames' '
--cacheinfo 100644 $blob "path with \\backslash" \
--cacheinfo 100644 $blob "path with space" &&
git commit -m addition &&
- git ls-files -z -s | perl -0pe "s{\\t}{$&subdir/}" >index &&
+ git ls-files -z -s | "$PERL_PATH" -0pe "s{\\t}{$&subdir/}" >index &&
git read-tree --empty &&
git update-index -z --index-info <index &&
git commit -m rename &&
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index 9199550..806623e 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -476,14 +476,14 @@ test_expect_success 'cvs status' '
cd cvswork &&
GIT_CONFIG="$git_config" cvs update &&
GIT_CONFIG="$git_config" cvs status | grep "^File: status.file" >../out &&
- test $(wc -l <../out) = 2
+ test_line_count = 2 ../out
'
cd "$WORKDIR"
test_expect_success 'cvs status (nonrecursive)' '
cd cvswork &&
GIT_CONFIG="$git_config" cvs status -l | grep "^File: status.file" >../out &&
- test $(wc -l <../out) = 1
+ test_line_count = 1 ../out
'
cd "$WORKDIR"
@@ -500,8 +500,8 @@ test_expect_success 'cvs status (no subdirs in header)' '
cd "$WORKDIR"
test_expect_success 'cvs co -c (shows module database)' '
GIT_CONFIG="$git_config" cvs co -c > out &&
- grep "^master[ ]\+master$" < out &&
- ! grep -v "^master[ ]\+master$" < out
+ grep "^master[ ][ ]*master$" <out &&
+ ! grep -v "^master[ ][ ]*master$" <out
'
#------------
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
index 5329715..90bb605 100755
--- a/t/t9500-gitweb-standalone-no-errors.sh
+++ b/t/t9500-gitweb-standalone-no-errors.sh
@@ -274,6 +274,53 @@ test_expect_success \
'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"'
# ----------------------------------------------------------------------
+# commitdiff testing (incomplete lines)
+
+test_expect_success 'setup incomplete lines' '
+ cat >file<<-\EOF &&
+ Dominus regit me,
+ et nihil mihi deerit.
+ In loco pascuae ibi me collocavit,
+ super aquam refectionis educavit me;
+ animam meam convertit,
+ deduxit me super semitas jusitiae,
+ propter nomen suum.
+ CHANGE_ME
+ EOF
+ git commit -a -m "Preparing for incomplete lines" &&
+ echo "incomplete" | tr -d "\\012" >>file &&
+ git commit -a -m "Add incomplete line" &&
+ git tag incomplete_lines_add &&
+ sed -e s/CHANGE_ME/change_me/ <file >file+ &&
+ mv -f file+ file &&
+ git commit -a -m "Incomplete context line" &&
+ git tag incomplete_lines_ctx &&
+ echo "Dominus regit me," >file &&
+ echo "incomplete line" | tr -d "\\012" >>file &&
+ git commit -a -m "Change incomplete line" &&
+ git tag incomplete_lines_chg
+ echo "Dominus regit me," >file &&
+ git commit -a -m "Remove incomplete line" &&
+ git tag incomplete_lines_rem
+'
+
+test_expect_success 'commitdiff(1): addition of incomplete line' '
+ gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add"
+'
+
+test_expect_success 'commitdiff(1): incomplete line as context line' '
+ gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx"
+'
+
+test_expect_success 'commitdiff(1): change incomplete line' '
+ gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg"
+'
+
+test_expect_success 'commitdiff(1): removal of incomplete line' '
+ gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem"
+'
+
+# ----------------------------------------------------------------------
# commit, commitdiff: merge, large
test_expect_success \
'Create a merge' \
@@ -282,7 +329,8 @@ test_expect_success \
git add b &&
git commit -a -m "On branch" &&
git checkout master &&
- git pull . b'
+ git pull . b &&
+ git tag merge_commit'
test_expect_success \
'commit(0): merge commit' \
@@ -332,6 +380,29 @@ test_expect_success \
'gitweb_run "p=.git;a=commitdiff;h=b"'
# ----------------------------------------------------------------------
+# side-by-side diff
+
+test_expect_success 'side-by-side: addition of incomplete line' '
+ gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: incomplete line as context line' '
+ gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: changed incomplete line' '
+ gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: removal of incomplete line' '
+ gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: merge commit' '
+ gitweb_run "p=.git;a=commitdiff;h=merge_commit;ds=sidebyside"
+'
+
+# ----------------------------------------------------------------------
# tags testing
test_expect_success \
@@ -404,6 +475,14 @@ test_expect_success \
'gitweb_run "" "/.git/master:foo/"'
test_expect_success \
+ 'path_info: project/branch (non-existent)' \
+ 'gitweb_run "" "/.git/non-existent"'
+
+test_expect_success \
+ 'path_info: project/branch:filename (non-existent branch)' \
+ 'gitweb_run "" "/.git/non-existent:non-existent"'
+
+test_expect_success \
'path_info: project/branch:file (non-existent)' \
'gitweb_run "" "/.git/master:non-existent"'
@@ -559,6 +638,45 @@ test_expect_success \
'gitweb_run "p=.git;a=tree"'
# ----------------------------------------------------------------------
+# searching
+
+cat >>gitweb_config.perl <<\EOF
+
+# enable search
+$feature{'search'}{'default'} = [1];
+$feature{'grep'}{'default'} = [1];
+$feature{'pickaxe'}{'default'} = [1];
+EOF
+
+test_expect_success \
+ 'search: preparation' \
+ 'echo "1st MATCH" >>file &&
+ echo "2nd MATCH" >>file &&
+ echo "MATCH" >>bar &&
+ git add file bar &&
+ git commit -m "Added MATCH word"'
+
+test_expect_success \
+ 'search: commit author' \
+ 'gitweb_run "p=.git;a=search;h=HEAD;st=author;s=A+U+Thor"'
+
+test_expect_success \
+ 'search: commit message' \
+ 'gitweb_run "p=.git;a=search;h=HEAD;st=commitr;s=MATCH"'
+
+test_expect_success \
+ 'search: grep' \
+ 'gitweb_run "p=.git;a=search;h=HEAD;st=grep;s=MATCH"'
+
+test_expect_success \
+ 'search: pickaxe' \
+ 'gitweb_run "p=.git;a=search;h=HEAD;st=pickaxe;s=MATCH"'
+
+test_expect_success \
+ 'search: projects' \
+ 'gitweb_run "a=project_list;s=.git"'
+
+# ----------------------------------------------------------------------
# non-ASCII in README.html
test_expect_success \
@@ -660,4 +778,13 @@ test_expect_success \
'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl &&
gitweb_run'
+# ----------------------------------------------------------------------
+# unborn branches
+
+test_expect_success \
+ 'unborn HEAD: "summary" page (with "heads" subview)' \
+ 'git checkout orphan_branch || git checkout --orphan orphan_branch &&
+ test_when_finished "git checkout master" &&
+ gitweb_run "p=.git;a=summary"'
+
test_done
diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh
index 26102ee..ef86948 100755
--- a/t/t9501-gitweb-standalone-http-status.sh
+++ b/t/t9501-gitweb-standalone-http-status.sh
@@ -12,6 +12,13 @@ code and message.'
. ./gitweb-lib.sh
+#
+# Gitweb only provides the functionality tested by the 'modification times'
+# tests if it can access a date parser from one of these modules:
+#
+perl -MHTTP::Date -e 0 >/dev/null 2>&1 && test_set_prereq DATE_PARSER
+perl -MTime::ParseDate -e 0 >/dev/null 2>&1 && test_set_prereq DATE_PARSER
+
# ----------------------------------------------------------------------
# snapshot settings
@@ -92,7 +99,7 @@ test_debug 'cat gitweb.output'
test_expect_success 'snapshots: bad tree-ish id (tagged object)' '
echo object > tag-object &&
git add tag-object &&
- git commit -m "Object to be tagged" &&
+ test_tick && git commit -m "Object to be tagged" &&
git tag tagged-object `git hash-object tag-object` &&
gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" &&
grep "400 - Object is not a tree-ish" gitweb.output
@@ -112,6 +119,64 @@ test_expect_success 'snapshots: bad object id' '
'
test_debug 'cat gitweb.output'
+# ----------------------------------------------------------------------
+# modification times (Last-Modified and If-Modified-Since)
+
+test_expect_success DATE_PARSER 'modification: feed last-modified' '
+ gitweb_run "p=.git;a=atom;h=master" &&
+ grep "Status: 200 OK" gitweb.headers &&
+ grep "Last-modified: Thu, 7 Apr 2005 22:14:13 +0000" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success DATE_PARSER 'modification: feed if-modified-since (modified)' '
+ export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=atom;h=master" &&
+ grep "Status: 200 OK" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success DATE_PARSER 'modification: feed if-modified-since (unmodified)' '
+ export HTTP_IF_MODIFIED_SINCE="Thu, 7 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=atom;h=master" &&
+ grep "Status: 304 Not Modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success DATE_PARSER 'modification: snapshot last-modified' '
+ gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+ grep "Status: 200 OK" gitweb.headers &&
+ grep "Last-modified: Thu, 7 Apr 2005 22:14:13 +0000" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success DATE_PARSER 'modification: snapshot if-modified-since (modified)' '
+ export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+ grep "Status: 200 OK" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success DATE_PARSER 'modification: snapshot if-modified-since (unmodified)' '
+ export HTTP_IF_MODIFIED_SINCE="Thu, 7 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+ grep "Status: 304 Not Modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success DATE_PARSER 'modification: tree snapshot' '
+ ID=`git rev-parse --verify HEAD^{tree}` &&
+ export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+ test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+ gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" &&
+ grep "Status: 200 OK" gitweb.headers &&
+ ! grep -i "last-modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
# ----------------------------------------------------------------------
# load checking
@@ -134,4 +199,14 @@ our $maxload = undef;
EOF
+# ----------------------------------------------------------------------
+# invalid arguments
+
+test_expect_success 'invalid arguments: invalid regexp (in project search)' '
+ gitweb_run "a=project_list;s=*\.git;sr=1" &&
+ grep "Status: 400" gitweb.headers &&
+ grep "400 - Invalid.*regexp" gitweb.body
+'
+test_debug 'cat gitweb.headers'
+
test_done
diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh
index 3787186..435d896 100755
--- a/t/t9700-perl-git.sh
+++ b/t/t9700-perl-git.sh
@@ -43,7 +43,11 @@ test_expect_success \
git config --add test.booltrue true &&
git config --add test.boolfalse no &&
git config --add test.boolother other &&
- git config --add test.int 2k
+ git config --add test.int 2k &&
+ git config --add test.path "~/foo" &&
+ git config --add test.pathexpanded "$HOME/foo" &&
+ git config --add test.pathmulti foo &&
+ git config --add test.pathmulti bar
'
# The external test will outputs its own plan
diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index 13ba96e..3b9b484 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -33,6 +33,10 @@ is($r->config_int("test.int"), 2048, "config_int: integer");
is($r->config_int("test.nonexistent"), undef, "config_int: nonexistent");
ok($r->config_bool("test.booltrue"), "config_bool: true");
ok(!$r->config_bool("test.boolfalse"), "config_bool: false");
+is($r->config_path("test.path"), $r->config("test.pathexpanded"),
+ "config_path: ~/foo expansion");
+is_deeply([$r->config_path("test.pathmulti")], ["foo", "bar"],
+ "config_path: multiple values");
our $ansi_green = "\x1b[32m";
is($r->get_color("color.test.slot1", "red"), $ansi_green, "get_color");
# Cannot test $r->get_colorbool("color.foo")) because we do not
diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh
new file mode 100755
index 0000000..0f410c4
--- /dev/null
+++ b/t/t9800-git-p4-basic.sh
@@ -0,0 +1,576 @@
+#!/bin/sh
+
+test_description='git p4 tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'add p4 files' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "file1" &&
+ echo file2 >file2 &&
+ p4 add file2 &&
+ p4 submit -d "file2"
+ )
+'
+
+test_expect_success 'basic git p4 clone' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git log --oneline >lines &&
+ test_line_count = 1 lines
+ )
+'
+
+test_expect_success 'git p4 clone @all' '
+ git p4 clone --dest="$git" //depot@all &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git log --oneline >lines &&
+ test_line_count = 2 lines
+ )
+'
+
+test_expect_success 'git p4 sync uninitialized repo' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_must_fail git p4 sync
+ )
+'
+
+#
+# Create a git repo by hand. Add a commit so that HEAD is valid.
+# Test imports a new p4 repository into a new git branch.
+#
+test_expect_success 'git p4 sync new branch' '
+ test_create_repo "$git" &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_commit head &&
+ git p4 sync --branch=refs/remotes/p4/depot //depot@all &&
+ git log --oneline p4/depot >lines &&
+ test_line_count = 2 lines
+ )
+'
+
+test_expect_success 'clone two dirs' '
+ (
+ cd "$cli" &&
+ mkdir sub1 sub2 &&
+ echo sub1/f1 >sub1/f1 &&
+ echo sub2/f2 >sub2/f2 &&
+ p4 add sub1/f1 &&
+ p4 submit -d "sub1/f1" &&
+ p4 add sub2/f2 &&
+ p4 submit -d "sub2/f2"
+ ) &&
+ git p4 clone --dest="$git" //depot/sub1 //depot/sub2 &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git ls-files >lines &&
+ test_line_count = 2 lines &&
+ git log --oneline p4/master >lines &&
+ test_line_count = 1 lines
+ )
+'
+
+test_expect_success 'clone two dirs, @all' '
+ (
+ cd "$cli" &&
+ echo sub1/f3 >sub1/f3 &&
+ p4 add sub1/f3 &&
+ p4 submit -d "sub1/f3"
+ ) &&
+ git p4 clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git ls-files >lines &&
+ test_line_count = 3 lines &&
+ git log --oneline p4/master >lines &&
+ test_line_count = 3 lines
+ )
+'
+
+test_expect_success 'clone two dirs, @all, conflicting files' '
+ (
+ cd "$cli" &&
+ echo sub2/f3 >sub2/f3 &&
+ p4 add sub2/f3 &&
+ p4 submit -d "sub2/f3"
+ ) &&
+ git p4 clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git ls-files >lines &&
+ test_line_count = 3 lines &&
+ git log --oneline p4/master >lines &&
+ test_line_count = 4 lines &&
+ echo sub2/f3 >expected &&
+ test_cmp expected f3
+ )
+'
+
+test_expect_success 'exit when p4 fails to produce marshaled output' '
+ badp4dir="$TRASH_DIRECTORY/badp4dir" &&
+ mkdir "$badp4dir" &&
+ test_when_finished "rm \"$badp4dir/p4\" && rmdir \"$badp4dir\"" &&
+ cat >"$badp4dir"/p4 <<-EOF &&
+ #!$SHELL_PATH
+ exit 1
+ EOF
+ chmod 755 "$badp4dir"/p4 &&
+ PATH="$badp4dir:$PATH" git p4 clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
+ test $retval -eq 1 &&
+ test_must_fail grep -q Traceback errs
+'
+
+test_expect_success 'add p4 files with wildcards in the names' '
+ (
+ cd "$cli" &&
+ echo file-wild-hash >file-wild#hash &&
+ echo file-wild-star >file-wild\*star &&
+ echo file-wild-at >file-wild@at &&
+ echo file-wild-percent >file-wild%percent &&
+ p4 add -f file-wild* &&
+ p4 submit -d "file wildcards"
+ )
+'
+
+test_expect_success 'wildcard files git p4 clone' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test -f file-wild#hash &&
+ test -f file-wild\*star &&
+ test -f file-wild@at &&
+ test -f file-wild%percent
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, add' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ echo git-wild-hash >git-wild#hash &&
+ echo git-wild-star >git-wild\*star &&
+ echo git-wild-at >git-wild@at &&
+ echo git-wild-percent >git-wild%percent &&
+ git add git-wild* &&
+ git commit -m "add some wildcard filenames" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file git-wild#hash &&
+ test_path_is_file git-wild\*star &&
+ test_path_is_file git-wild@at &&
+ test_path_is_file git-wild%percent
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, modify' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ echo new-line >>git-wild#hash &&
+ echo new-line >>git-wild\*star &&
+ echo new-line >>git-wild@at &&
+ echo new-line >>git-wild%percent &&
+ git add git-wild* &&
+ git commit -m "modify the wildcard files" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_line_count = 2 git-wild#hash &&
+ test_line_count = 2 git-wild\*star &&
+ test_line_count = 2 git-wild@at &&
+ test_line_count = 2 git-wild%percent
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, copy' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ cp file2 git-wild-cp#hash &&
+ git add git-wild-cp#hash &&
+ cp git-wild\*star file-wild-3 &&
+ git add file-wild-3 &&
+ git commit -m "wildcard copies" &&
+ git config git-p4.detectCopies true &&
+ git config git-p4.detectCopiesHarder true &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file git-wild-cp#hash &&
+ test_path_is_file file-wild-3
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, rename' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git mv git-wild@at file-wild-4 &&
+ git mv file-wild-3 git-wild-cp%percent &&
+ git commit -m "wildcard renames" &&
+ git config git-p4.detectRenames true &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing git-wild@at &&
+ test_path_is_file git-wild-cp%percent
+ )
+'
+
+test_expect_success 'wildcard files submit back to p4, delete' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git rm git-wild* &&
+ git commit -m "delete the wildcard files" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing git-wild#hash &&
+ test_path_is_missing git-wild\*star &&
+ test_path_is_missing git-wild@at &&
+ test_path_is_missing git-wild%percent
+ )
+'
+
+test_expect_success 'clone bare' '
+ git p4 clone --dest="$git" --bare //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test ! -d .git &&
+ bare=`git config --get core.bare` &&
+ test "$bare" = true
+ )
+'
+
+p4_add_user() {
+ name=$1 fullname=$2 &&
+ p4 user -f -i <<-EOF &&
+ User: $name
+ Email: $name@localhost
+ FullName: $fullname
+ EOF
+ p4 passwd -P secret $name
+}
+
+p4_grant_admin() {
+ name=$1 &&
+ {
+ p4 protect -o &&
+ echo " admin user $name * //depot/..."
+ } | p4 protect -i
+}
+
+p4_check_commit_author() {
+ file=$1 user=$2 &&
+ p4 changes -m 1 //depot/$file | grep -q $user
+}
+
+make_change_by_user() {
+ file=$1 name=$2 email=$3 &&
+ echo "username: a change by $name" >>"$file" &&
+ git add "$file" &&
+ git commit --author "$name <$email>" -m "a change by $name"
+}
+
+# Test username support, submitting as user 'alice'
+test_expect_success 'preserve users' '
+ p4_add_user alice Alice &&
+ p4_add_user bob Bob &&
+ p4_grant_admin alice &&
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ echo "username: a change by alice" >>file1 &&
+ echo "username: a change by bob" >>file2 &&
+ git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
+ git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
+ git config git-p4.skipSubmitEditCheck true &&
+ P4EDITOR=touch P4USER=alice P4PASSWD=secret git p4 commit --preserve-user &&
+ p4_check_commit_author file1 alice &&
+ p4_check_commit_author file2 bob
+ )
+'
+
+# Test username support, submitting as bob, who lacks admin rights. Should
+# not submit change to p4 (git diff should show deltas).
+test_expect_success 'refuse to preserve users without perms' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ echo "username-noperms: a change by alice" >>file1 &&
+ git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
+ P4EDITOR=touch P4USER=bob P4PASSWD=secret &&
+ export P4EDITOR P4USER P4PASSWD &&
+ test_must_fail git p4 commit --preserve-user &&
+ ! git diff --exit-code HEAD..p4/master
+ )
+'
+
+# What happens with unknown author? Without allowMissingP4Users it should fail.
+test_expect_success 'preserve user where author is unknown to p4' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ echo "username-bob: a change by bob" >>file1 &&
+ git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
+ echo "username-unknown: a change by charlie" >>file1 &&
+ git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
+ P4EDITOR=touch P4USER=alice P4PASSWD=secret &&
+ export P4EDITOR P4USER P4PASSWD &&
+ test_must_fail git p4 commit --preserve-user &&
+ ! git diff --exit-code HEAD..p4/master &&
+
+ echo "$0: repeat with allowMissingP4Users enabled" &&
+ git config git-p4.allowMissingP4Users true &&
+ git config git-p4.preserveUser true &&
+ git p4 commit &&
+ git diff --exit-code HEAD..p4/master &&
+ p4_check_commit_author file1 alice
+ )
+'
+
+# If we're *not* using --preserve-user, git p4 should warn if we're submitting
+# changes that are not all ours.
+# Test: user in p4 and user unknown to p4.
+# Test: warning disabled and user is the same.
+test_expect_success 'not preserving user with mixed authorship' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ p4_add_user derek Derek &&
+
+ make_change_by_user usernamefile3 Derek derek@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
+ export P4EDITOR P4USER P4PASSWD &&
+ git p4 commit |\
+ grep "git author derek@localhost does not match" &&
+
+ make_change_by_user usernamefile3 Charlie charlie@localhost &&
+ git p4 commit |\
+ grep "git author charlie@localhost does not match" &&
+
+ make_change_by_user usernamefile3 alice alice@localhost &&
+ git p4 commit |\
+ test_must_fail grep "git author.*does not match" &&
+
+ git config git-p4.skipUserNameCheck true &&
+ make_change_by_user usernamefile3 Charlie charlie@localhost &&
+ git p4 commit |\
+ test_must_fail grep "git author.*does not match" &&
+
+ p4_check_commit_author usernamefile3 alice
+ )
+'
+
+marshal_dump() {
+ what=$1
+ "$PYTHON_PATH" -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]'
+}
+
+# Sleep a bit so that the top-most p4 change did not happen "now". Then
+# import the repo and make sure that the initial import has the same time
+# as the top-most change.
+test_expect_success 'initial import time from top change time' '
+ p4change=$(p4 -G changes -m 1 //depot/... | marshal_dump change) &&
+ p4time=$(p4 -G changes -m 1 //depot/... | marshal_dump time) &&
+ sleep 3 &&
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ gittime=$(git show -s --raw --pretty=format:%at HEAD) &&
+ echo $p4time $gittime &&
+ test $p4time = $gittime
+ )
+'
+
+# Rename a file and confirm that rename is not detected in P4.
+# Rename the new file again with detectRenames option enabled and confirm that
+# this is detected in P4.
+# Rename the new file again adding an extra line, configure a big threshold in
+# detectRenames and confirm that rename is not detected in P4.
+# Repeat, this time with a smaller threshold and confirm that the rename is
+# detected in P4.
+test_expect_success 'detect renames' '
+ git p4 clone --dest="$git" //depot@all &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+
+ git mv file1 file4 &&
+ git commit -a -m "Rename file1 to file4" &&
+ git diff-tree -r -M HEAD &&
+ git p4 submit &&
+ p4 filelog //depot/file4 &&
+ p4 filelog //depot/file4 | test_must_fail grep -q "branch from" &&
+
+ git mv file4 file5 &&
+ git commit -a -m "Rename file4 to file5" &&
+ git diff-tree -r -M HEAD &&
+ git config git-p4.detectRenames true &&
+ git p4 submit &&
+ p4 filelog //depot/file5 &&
+ p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
+
+ git mv file5 file6 &&
+ echo update >>file6 &&
+ git add file6 &&
+ git commit -a -m "Rename file5 to file6 with changes" &&
+ git diff-tree -r -M HEAD &&
+ level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+ test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+ git config git-p4.detectRenames $(($level + 2)) &&
+ git p4 submit &&
+ p4 filelog //depot/file6 &&
+ p4 filelog //depot/file6 | test_must_fail grep -q "branch from" &&
+
+ git mv file6 file7 &&
+ echo update >>file7 &&
+ git add file7 &&
+ git commit -a -m "Rename file6 to file7 with changes" &&
+ git diff-tree -r -M HEAD &&
+ level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+ test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+ git config git-p4.detectRenames $(($level - 2)) &&
+ git p4 submit &&
+ p4 filelog //depot/file7 &&
+ p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
+ )
+'
+
+# Copy a file and confirm that copy is not detected in P4.
+# Copy a file with detectCopies option enabled and confirm that copy is not
+# detected in P4.
+# Modify and copy a file with detectCopies option enabled and confirm that copy
+# is detected in P4.
+# Copy a file with detectCopies and detectCopiesHarder options enabled and
+# confirm that copy is detected in P4.
+# Modify and copy a file, configure a bigger threshold in detectCopies and
+# confirm that copy is not detected in P4.
+# Modify and copy a file, configure a smaller threshold in detectCopies and
+# confirm that copy is detected in P4.
+test_expect_success 'detect copies' '
+ git p4 clone --dest="$git" //depot@all &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+
+ cp file2 file8 &&
+ git add file8 &&
+ git commit -a -m "Copy file2 to file8" &&
+ git diff-tree -r -C HEAD &&
+ git p4 submit &&
+ p4 filelog //depot/file8 &&
+ p4 filelog //depot/file8 | test_must_fail grep -q "branch from" &&
+
+ cp file2 file9 &&
+ git add file9 &&
+ git commit -a -m "Copy file2 to file9" &&
+ git diff-tree -r -C HEAD &&
+ git config git-p4.detectCopies true &&
+ git p4 submit &&
+ p4 filelog //depot/file9 &&
+ p4 filelog //depot/file9 | test_must_fail grep -q "branch from" &&
+
+ echo "file2" >>file2 &&
+ cp file2 file10 &&
+ git add file2 file10 &&
+ git commit -a -m "Modify and copy file2 to file10" &&
+ git diff-tree -r -C HEAD &&
+ git p4 submit &&
+ p4 filelog //depot/file10 &&
+ p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
+
+ cp file2 file11 &&
+ git add file11 &&
+ git commit -a -m "Copy file2 to file11" &&
+ git diff-tree -r -C --find-copies-harder HEAD &&
+ src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+ test "$src" = file10 &&
+ git config git-p4.detectCopiesHarder true &&
+ git p4 submit &&
+ p4 filelog //depot/file11 &&
+ p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
+
+ cp file2 file12 &&
+ echo "some text" >>file12 &&
+ git add file12 &&
+ git commit -a -m "Copy file2 to file12 with changes" &&
+ git diff-tree -r -C --find-copies-harder HEAD &&
+ level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+ test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+ src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+ test "$src" = file10 &&
+ git config git-p4.detectCopies $(($level + 2)) &&
+ git p4 submit &&
+ p4 filelog //depot/file12 &&
+ p4 filelog //depot/file12 | test_must_fail grep -q "branch from" &&
+
+ cp file2 file13 &&
+ echo "different text" >>file13 &&
+ git add file13 &&
+ git commit -a -m "Copy file2 to file13 with changes" &&
+ git diff-tree -r -C --find-copies-harder HEAD &&
+ level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+ test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+ src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+ test "$src" = file10 &&
+ git config git-p4.detectCopies $(($level - 2)) &&
+ git p4 submit &&
+ p4 filelog //depot/file13 &&
+ p4 filelog //depot/file13 | grep -q "branch from //depot/file"
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh
deleted file mode 100755
index 33b0127..0000000
--- a/t/t9800-git-p4.sh
+++ /dev/null
@@ -1,264 +0,0 @@
-#!/bin/sh
-
-test_description='git-p4 tests'
-
-. ./test-lib.sh
-
-( p4 -h && p4d -h ) >/dev/null 2>&1 || {
- skip_all='skipping git-p4 tests; no p4 or p4d'
- test_done
-}
-
-GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4
-P4DPORT=10669
-
-export P4PORT=localhost:$P4DPORT
-
-db="$TRASH_DIRECTORY/db"
-cli="$TRASH_DIRECTORY/cli"
-git="$TRASH_DIRECTORY/git"
-
-test_debug 'echo p4d -q -d -r "$db" -p $P4DPORT'
-test_expect_success setup '
- mkdir -p "$db" &&
- p4d -q -d -r "$db" -p $P4DPORT &&
- mkdir -p "$cli" &&
- mkdir -p "$git" &&
- export P4PORT=localhost:$P4DPORT
-'
-
-test_expect_success 'add p4 files' '
- cd "$cli" &&
- p4 client -i <<-EOF &&
- Client: client
- Description: client
- Root: $cli
- View: //depot/... //client/...
- EOF
- export P4CLIENT=client &&
- echo file1 >file1 &&
- p4 add file1 &&
- p4 submit -d "file1" &&
- echo file2 >file2 &&
- p4 add file2 &&
- p4 submit -d "file2" &&
- cd "$TRASH_DIRECTORY"
-'
-
-test_expect_success 'basic git-p4 clone' '
- "$GITP4" clone --dest="$git" //depot &&
- cd "$git" &&
- git log --oneline >lines &&
- test_line_count = 1 lines &&
- cd .. &&
- rm -rf "$git" && mkdir "$git"
-'
-
-test_expect_success 'git-p4 clone @all' '
- "$GITP4" clone --dest="$git" //depot@all &&
- cd "$git" &&
- git log --oneline >lines &&
- test_line_count = 2 lines &&
- cd .. &&
- rm -rf "$git" && mkdir "$git"
-'
-
-test_expect_success 'git-p4 sync uninitialized repo' '
- test_create_repo "$git" &&
- cd "$git" &&
- test_must_fail "$GITP4" sync &&
- rm -rf "$git" && mkdir "$git"
-'
-
-#
-# Create a git repo by hand. Add a commit so that HEAD is valid.
-# Test imports a new p4 repository into a new git branch.
-#
-test_expect_success 'git-p4 sync new branch' '
- test_create_repo "$git" &&
- cd "$git" &&
- test_commit head &&
- "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
- git log --oneline p4/depot >lines &&
- cat lines &&
- test_line_count = 2 lines &&
- cd .. &&
- rm -rf "$git" && mkdir "$git"
-'
-
-test_expect_success 'exit when p4 fails to produce marshaled output' '
- badp4dir="$TRASH_DIRECTORY/badp4dir" &&
- mkdir -p "$badp4dir" &&
- cat >"$badp4dir"/p4 <<-EOF &&
- #!$SHELL_PATH
- exit 1
- EOF
- chmod 755 "$badp4dir"/p4 &&
- PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
- test $retval -eq 1 &&
- test_must_fail grep -q Traceback errs
-'
-
-test_expect_success 'add p4 files with wildcards in the names' '
- cd "$cli" &&
- echo file-wild-hash >file-wild#hash &&
- echo file-wild-star >file-wild\*star &&
- echo file-wild-at >file-wild@at &&
- echo file-wild-percent >file-wild%percent &&
- p4 add -f file-wild* &&
- p4 submit -d "file wildcards" &&
- cd "$TRASH_DIRECTORY"
-'
-
-test_expect_success 'wildcard files git-p4 clone' '
- "$GITP4" clone --dest="$git" //depot &&
- cd "$git" &&
- test -f file-wild#hash &&
- test -f file-wild\*star &&
- test -f file-wild@at &&
- test -f file-wild%percent &&
- cd "$TRASH_DIRECTORY" &&
- rm -rf "$git" && mkdir "$git"
-'
-
-test_expect_success 'clone bare' '
- "$GITP4" clone --dest="$git" --bare //depot &&
- cd "$git" &&
- test ! -d .git &&
- bare=`git config --get core.bare` &&
- test "$bare" = true &&
- cd "$TRASH_DIRECTORY" &&
- rm -rf "$git" && mkdir "$git"
-'
-
-p4_add_user() {
- name=$1
- fullname=$2
- p4 user -f -i <<EOF &&
-User: $name
-Email: $name@localhost
-FullName: $fullname
-EOF
- p4 passwd -P secret $name
-}
-
-p4_grant_admin() {
- name=$1
- p4 protect -o |\
- awk "{print}END{print \" admin user $name * //depot/...\"}" |\
- p4 protect -i
-}
-
-p4_check_commit_author() {
- file=$1
- user=$2
- if p4 changes -m 1 //depot/$file | grep $user > /dev/null ; then
- return 0
- else
- echo "file $file not modified by user $user" 1>&2
- return 1
- fi
-}
-
-make_change_by_user() {
- file=$1 name=$2 email=$3 &&
- echo "username: a change by $name" >>"$file" &&
- git add "$file" &&
- git commit --author "$name <$email>" -m "a change by $name"
-}
-
-# Test username support, submitting as user 'alice'
-test_expect_success 'preserve users' '
- p4_add_user alice Alice &&
- p4_add_user bob Bob &&
- p4_grant_admin alice &&
- "$GITP4" clone --dest="$git" //depot &&
- cd "$git" &&
- echo "username: a change by alice" >> file1 &&
- echo "username: a change by bob" >> file2 &&
- git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
- git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
- git config git-p4.skipSubmitEditCheck true &&
- P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
- p4_check_commit_author file1 alice &&
- p4_check_commit_author file2 bob &&
- cd "$TRASH_DIRECTORY" &&
- rm -rf "$git" && mkdir "$git"
-'
-
-# Test username support, submitting as bob, who lacks admin rights. Should
-# not submit change to p4 (git diff should show deltas).
-test_expect_success 'refuse to preserve users without perms' '
- "$GITP4" clone --dest="$git" //depot &&
- cd "$git" &&
- echo "username-noperms: a change by alice" >> file1 &&
- git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
- ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user &&
- ! git diff --exit-code HEAD..p4/master > /dev/null &&
- cd "$TRASH_DIRECTORY" &&
- rm -rf "$git" && mkdir "$git"
-'
-
-# What happens with unknown author? Without allowMissingP4Users it should fail.
-test_expect_success 'preserve user where author is unknown to p4' '
- "$GITP4" clone --dest="$git" //depot &&
- cd "$git" &&
- git config git-p4.skipSubmitEditCheck true
- echo "username-bob: a change by bob" >> file1 &&
- git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
- echo "username-unknown: a change by charlie" >> file1 &&
- git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
- ! P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
- ! git diff --exit-code HEAD..p4/master > /dev/null &&
- echo "$0: repeat with allowMissingP4Users enabled" &&
- git config git-p4.allowMissingP4Users true &&
- git config git-p4.preserveUser true &&
- P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
- git diff --exit-code HEAD..p4/master > /dev/null &&
- p4_check_commit_author file1 alice &&
- cd "$TRASH_DIRECTORY" &&
- rm -rf "$git" && mkdir "$git"
-'
-
-# If we're *not* using --preserve-user, git-p4 should warn if we're submitting
-# changes that are not all ours.
-# Test: user in p4 and user unknown to p4.
-# Test: warning disabled and user is the same.
-test_expect_success 'not preserving user with mixed authorship' '
- "$GITP4" clone --dest="$git" //depot &&
- (
- cd "$git" &&
- git config git-p4.skipSubmitEditCheck true &&
- p4_add_user derek Derek &&
-
- make_change_by_user usernamefile3 Derek derek@localhost &&
- P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
- grep "git author derek@localhost does not match" actual &&
-
- make_change_by_user usernamefile3 Charlie charlie@localhost &&
- P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
- grep "git author charlie@localhost does not match" actual &&
-
- make_change_by_user usernamefile3 alice alice@localhost &&
- P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
- ! grep "git author.*does not match" actual &&
-
- git config git-p4.skipUserNameCheck true &&
- make_change_by_user usernamefile3 Charlie charlie@localhost &&
- P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
- ! grep "git author.*does not match" actual &&
-
- p4_check_commit_author usernamefile3 alice
- ) &&
- rm -rf "$git" && mkdir "$git"
-'
-
-
-test_expect_success 'shutdown' '
- pid=`pgrep -f p4d` &&
- test -n "$pid" &&
- test_debug "ps wl `echo $pid`" &&
- kill $pid
-'
-
-test_done
diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh
new file mode 100755
index 0000000..99fe16b
--- /dev/null
+++ b/t/t9801-git-p4-branch.sh
@@ -0,0 +1,417 @@
+#!/bin/sh
+
+test_description='git p4 tests for p4 branches'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+#
+# 1: //depot/main/f1
+# 2: //depot/main/f2
+# 3: integrate //depot/main/... -> //depot/branch1/...
+# 4: //depot/main/f4
+# 5: //depot/branch1/f5
+# .: named branch branch2
+# 6: integrate -b branch2
+# 7: //depot/branch2/f7
+# 8: //depot/main/f8
+#
+test_expect_success 'basic p4 branches' '
+ (
+ cd "$cli" &&
+ mkdir -p main &&
+
+ echo f1 >main/f1 &&
+ p4 add main/f1 &&
+ p4 submit -d "main/f1" &&
+
+ echo f2 >main/f2 &&
+ p4 add main/f2 &&
+ p4 submit -d "main/f2" &&
+
+ p4 integrate //depot/main/... //depot/branch1/... &&
+ p4 submit -d "integrate main to branch1" &&
+
+ echo f4 >main/f4 &&
+ p4 add main/f4 &&
+ p4 submit -d "main/f4" &&
+
+ echo f5 >branch1/f5 &&
+ p4 add branch1/f5 &&
+ p4 submit -d "branch1/f5" &&
+
+ p4 branch -i <<-EOF &&
+ Branch: branch2
+ View: //depot/main/... //depot/branch2/...
+ EOF
+
+ p4 integrate -b branch2 &&
+ p4 submit -d "integrate main to branch2" &&
+
+ echo f7 >branch2/f7 &&
+ p4 add branch2/f7 &&
+ p4 submit -d "branch2/f7" &&
+
+ echo f8 >main/f8 &&
+ p4 add main/f8 &&
+ p4 submit -d "main/f8"
+ )
+'
+
+test_expect_success 'import main, no branch detection' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot/main@all &&
+ (
+ cd "$git" &&
+ git log --oneline --graph --decorate --all &&
+ git rev-list master >wc &&
+ test_line_count = 4 wc
+ )
+'
+
+test_expect_success 'import branch1, no branch detection' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot/branch1@all &&
+ (
+ cd "$git" &&
+ git log --oneline --graph --decorate --all &&
+ git rev-list master >wc &&
+ test_line_count = 2 wc
+ )
+'
+
+test_expect_success 'import branch2, no branch detection' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot/branch2@all &&
+ (
+ cd "$git" &&
+ git log --oneline --graph --decorate --all &&
+ git rev-list master >wc &&
+ test_line_count = 2 wc
+ )
+'
+
+test_expect_success 'import depot, no branch detection' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git log --oneline --graph --decorate --all &&
+ git rev-list master >wc &&
+ test_line_count = 8 wc
+ )
+'
+
+test_expect_success 'import depot, branch detection' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" --detect-branches //depot@all &&
+ (
+ cd "$git" &&
+
+ git log --oneline --graph --decorate --all &&
+
+ # 4 main commits
+ git rev-list master >wc &&
+ test_line_count = 4 wc &&
+
+ # 3 main, 1 integrate, 1 on branch2
+ git rev-list p4/depot/branch2 >wc &&
+ test_line_count = 5 wc &&
+
+ # no branch1, since no p4 branch created for it
+ test_must_fail git show-ref p4/depot/branch1
+ )
+'
+
+test_expect_success 'import depot, branch detection, branchList branch definition' '
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
+ (
+ cd "$git" &&
+ git config git-p4.branchList main:branch1 &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
+
+ git log --oneline --graph --decorate --all &&
+
+ # 4 main commits
+ git rev-list master >wc &&
+ test_line_count = 4 wc &&
+
+ # 3 main, 1 integrate, 1 on branch2
+ git rev-list p4/depot/branch2 >wc &&
+ test_line_count = 5 wc &&
+
+ # 2 main, 1 integrate, 1 on branch1
+ git rev-list p4/depot/branch1 >wc &&
+ test_line_count = 4 wc
+ )
+'
+
+test_expect_success 'restart p4d' '
+ kill_p4d &&
+ start_p4d
+'
+
+#
+# 1: //depot/branch1/file1
+# //depot/branch1/file2
+# 2: integrate //depot/branch1/... -> //depot/branch2/...
+# 3: //depot/branch1/file3
+# 4: //depot/branch1/file2 (edit)
+# 5: integrate //depot/branch1/... -> //depot/branch3/...
+#
+## Create a simple branch structure in P4 depot.
+test_expect_success 'add simple p4 branches' '
+ (
+ cd "$cli" &&
+ mkdir branch1 &&
+ cd branch1 &&
+ echo file1 >file1 &&
+ echo file2 >file2 &&
+ p4 add file1 file2 &&
+ p4 submit -d "Create branch1" &&
+ p4 integrate //depot/branch1/... //depot/branch2/... &&
+ p4 submit -d "Integrate branch2 from branch1" &&
+ echo file3 >file3 &&
+ p4 add file3 &&
+ p4 submit -d "add file3 in branch1" &&
+ p4 open file2 &&
+ echo update >>file2 &&
+ p4 submit -d "update file2 in branch1" &&
+ p4 integrate //depot/branch1/... //depot/branch3/... &&
+ p4 submit -d "Integrate branch3 from branch1"
+ )
+'
+
+# Configure branches through git-config and clone them.
+# All files are tested to make sure branches were cloned correctly.
+# Finally, make an update to branch1 on P4 side to check if it is imported
+# correctly by git p4.
+test_expect_success 'git p4 clone simple branches' '
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
+ (
+ cd "$git" &&
+ git config git-p4.branchList branch1:branch2 &&
+ git config --add git-p4.branchList branch1:branch3 &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
+ git log --all --graph --decorate --stat &&
+ git reset --hard p4/depot/branch1 &&
+ test -f file1 &&
+ test -f file2 &&
+ test -f file3 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch2 &&
+ test -f file1 &&
+ test -f file2 &&
+ test ! -f file3 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch3 &&
+ test -f file1 &&
+ test -f file2 &&
+ test -f file3 &&
+ grep update file2 &&
+ cd "$cli" &&
+ cd branch1 &&
+ p4 edit file2 &&
+ echo file2_ >>file2 &&
+ p4 submit -d "update file2 in branch1" &&
+ cd "$git" &&
+ git reset --hard p4/depot/branch1 &&
+ git p4 rebase &&
+ grep file2_ file2
+ )
+'
+
+# Create a complex branch structure in P4 depot to check if they are correctly
+# cloned. The branches are created from older changelists to check if git p4 is
+# able to correctly detect them.
+# The final expected structure is:
+# `branch1
+# | `- file1
+# | `- file2 (updated)
+# | `- file3
+# `branch2
+# | `- file1
+# | `- file2
+# `branch3
+# | `- file1
+# | `- file2 (updated)
+# | `- file3
+# `branch4
+# | `- file1
+# | `- file2
+# `branch5
+# `- file1
+# `- file2
+# `- file3
+test_expect_success 'git p4 add complex branches' '
+ (
+ cd "$cli" &&
+ changelist=$(p4 changes -m1 //depot/... | cut -d" " -f2) &&
+ changelist=$(($changelist - 5)) &&
+ p4 integrate //depot/branch1/...@$changelist //depot/branch4/... &&
+ p4 submit -d "Integrate branch4 from branch1@${changelist}" &&
+ changelist=$(($changelist + 2)) &&
+ p4 integrate //depot/branch1/...@$changelist //depot/branch5/... &&
+ p4 submit -d "Integrate branch5 from branch1@${changelist}"
+ )
+'
+
+# Configure branches through git-config and clone them. git p4 will only be able
+# to clone the original structure if it is able to detect the origin changelist
+# of each branch.
+test_expect_success 'git p4 clone complex branches' '
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
+ (
+ cd "$git" &&
+ git config git-p4.branchList branch1:branch2 &&
+ git config --add git-p4.branchList branch1:branch3 &&
+ git config --add git-p4.branchList branch1:branch4 &&
+ git config --add git-p4.branchList branch1:branch5 &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
+ git log --all --graph --decorate --stat &&
+ git reset --hard p4/depot/branch1 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch2 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_missing file3 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch3 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch4 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_missing file3 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch5 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ ! grep update file2 &&
+ test_path_is_missing .git/git-p4-tmp
+ )
+'
+
+# Move branch3/file3 to branch4/file3 in a single changelist
+test_expect_success 'git p4 submit to two branches in a single changelist' '
+ (
+ cd "$cli" &&
+ p4 integrate //depot/branch3/file3 //depot/branch4/file3 &&
+ p4 delete //depot/branch3/file3 &&
+ p4 submit -d "Move branch3/file3 to branch4/file3"
+ )
+'
+
+# Confirm that changes to two branches done in a single changelist
+# are correctly imported by git p4
+test_expect_success 'git p4 sync changes to two branches in the same changelist' '
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
+ (
+ cd "$git" &&
+ git config git-p4.branchList branch1:branch2 &&
+ git config --add git-p4.branchList branch1:branch3 &&
+ git config --add git-p4.branchList branch1:branch4 &&
+ git config --add git-p4.branchList branch1:branch5 &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
+ git log --all --graph --decorate --stat &&
+ git reset --hard p4/depot/branch1 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch2 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_missing file3 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch3 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_missing file3 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch4 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch5 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ ! grep update file2 &&
+ test_path_is_missing .git/git-p4-tmp
+ )
+'
+
+# Create a branch by integrating a single file
+test_expect_success 'git p4 file subset branch' '
+ (
+ cd "$cli" &&
+ p4 integrate //depot/branch1/file1 //depot/branch6/file1 &&
+ p4 submit -d "Integrate file1 alone from branch1 to branch6"
+ )
+'
+
+# Check if git p4 creates a new branch containing a single file,
+# instead of keeping the old files from the original branch
+test_expect_failure 'git p4 clone file subset branch' '
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
+ (
+ cd "$git" &&
+ git config git-p4.branchList branch1:branch2 &&
+ git config --add git-p4.branchList branch1:branch3 &&
+ git config --add git-p4.branchList branch1:branch4 &&
+ git config --add git-p4.branchList branch1:branch5 &&
+ git config --add git-p4.branchList branch1:branch6 &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
+ git log --all --graph --decorate --stat &&
+ git reset --hard p4/depot/branch1 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch2 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_missing file3 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch3 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_missing file3 &&
+ grep update file2 &&
+ git reset --hard p4/depot/branch4 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch5 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ ! grep update file2 &&
+ git reset --hard p4/depot/branch6 &&
+ test_path_is_file file1 &&
+ test_path_is_missing file2 &&
+ test_path_is_missing file3
+ )
+'
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh
new file mode 100755
index 0000000..21924df
--- /dev/null
+++ b/t/t9802-git-p4-filetype.sh
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+test_description='git p4 filetype tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'utf-16 file create' '
+ (
+ cd "$cli" &&
+
+ # p4 saves this verbatim
+ printf "three\nline\ntext\n" >f-ascii &&
+ p4 add -t text f-ascii &&
+
+ # p4 adds \377\376 header
+ cp f-ascii f-ascii-as-utf16 &&
+ p4 add -t utf16 f-ascii-as-utf16 &&
+
+ # p4 saves this exactly as iconv produced it
+ printf "three\nline\ntext\n" | iconv -f ascii -t utf-16 >f-utf16 &&
+ p4 add -t utf16 f-utf16 &&
+
+ # this also is unchanged
+ cp f-utf16 f-utf16-as-text &&
+ p4 add -t text f-utf16-as-text &&
+
+ p4 submit -d "f files" &&
+
+ # force update of client files
+ p4 sync -f
+ )
+'
+
+test_expect_success 'utf-16 file test' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+
+ test_cmp "$cli/f-ascii" f-ascii &&
+ test_cmp "$cli/f-ascii-as-utf16" f-ascii-as-utf16 &&
+ test_cmp "$cli/f-utf16" f-utf16 &&
+ test_cmp "$cli/f-utf16-as-text" f-utf16-as-text
+ )
+'
+
+test_expect_success 'keyword file create' '
+ (
+ cd "$cli" &&
+
+ printf "id\n\$Id\$\n\$Author\$\ntext\n" >k-text-k &&
+ p4 add -t text+k k-text-k &&
+
+ cp k-text-k k-text-ko &&
+ p4 add -t text+ko k-text-ko &&
+
+ cat k-text-k | iconv -f ascii -t utf-16 >k-utf16-k &&
+ p4 add -t utf16+k k-utf16-k &&
+
+ cp k-utf16-k k-utf16-ko &&
+ p4 add -t utf16+ko k-utf16-ko &&
+
+ p4 submit -d "k files" &&
+ p4 sync -f
+ )
+'
+
+build_smush() {
+ cat >k_smush.py <<-\EOF &&
+ import re, sys
+ sys.stdout.write(re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', sys.stdin.read()))
+ EOF
+ cat >ko_smush.py <<-\EOF
+ import re, sys
+ sys.stdout.write(re.sub(r'(?i)\$(Id|Header):[^$]*\$', r'$\1$', sys.stdin.read()))
+ EOF
+}
+
+test_expect_success 'keyword file test' '
+ build_smush &&
+ test_when_finished rm -f k_smush.py ko_smush.py &&
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+
+ # text, ensure unexpanded
+ "$PYTHON_PATH" "$TRASH_DIRECTORY/k_smush.py" <"$cli/k-text-k" >cli-k-text-k-smush &&
+ test_cmp cli-k-text-k-smush k-text-k &&
+ "$PYTHON_PATH" "$TRASH_DIRECTORY/ko_smush.py" <"$cli/k-text-ko" >cli-k-text-ko-smush &&
+ test_cmp cli-k-text-ko-smush k-text-ko &&
+
+ # utf16, even though p4 expands keywords, git p4 does not
+ # try to undo that
+ test_cmp "$cli/k-utf16-k" k-utf16-k &&
+ test_cmp "$cli/k-utf16-ko" k-utf16-ko
+ )
+'
+
+build_gendouble() {
+ cat >gendouble.py <<-\EOF
+ import sys
+ import struct
+ import array
+
+ s = array.array("c", '\0' * 26)
+ struct.pack_into(">L", s, 0, 0x00051607) # AppleDouble
+ struct.pack_into(">L", s, 4, 0x00020000) # version 2
+ s.tofile(sys.stdout)
+ EOF
+}
+
+test_expect_success 'ignore apple' '
+ test_when_finished rm -f gendouble.py &&
+ build_gendouble &&
+ (
+ cd "$cli" &&
+ test-genrandom apple 1024 >double.png &&
+ "$PYTHON_PATH" "$TRASH_DIRECTORY/gendouble.py" >%double.png &&
+ p4 add -t apple double.png &&
+ p4 submit -d appledouble
+ ) &&
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ test ! -f double.png
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9803-git-p4-shell-metachars.sh b/t/t9803-git-p4-shell-metachars.sh
new file mode 100755
index 0000000..fbacff3
--- /dev/null
+++ b/t/t9803-git-p4-shell-metachars.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+
+test_description='git p4 transparency to shell metachars in filenames'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "file1"
+ )
+'
+
+test_expect_success 'shell metachars in filenames' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ echo f1 >foo\$bar &&
+ git add foo\$bar &&
+ echo f2 >"file with spaces" &&
+ git add "file with spaces" &&
+ git commit -m "add files" &&
+ P4EDITOR=touch git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync ... &&
+ test -e "file with spaces" &&
+ test -e "foo\$bar"
+ )
+'
+
+test_expect_success 'deleting with shell metachars' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ git rm foo\$bar &&
+ git rm file\ with\ spaces &&
+ git commit -m "remove files" &&
+ P4EDITOR=touch git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync ... &&
+ test ! -e "file with spaces" &&
+ test ! -e foo\$bar
+ )
+'
+
+# Create a branch with a shell metachar in its name
+#
+# 1. //depot/main
+# 2. //depot/branch$3
+
+test_expect_success 'branch with shell char' '
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
+ (
+ cd "$cli" &&
+
+ mkdir -p main &&
+
+ echo f1 >main/f1 &&
+ p4 add main/f1 &&
+ p4 submit -d "main/f1" &&
+
+ p4 integrate //depot/main/... //depot/branch\$3/... &&
+ p4 submit -d "integrate main to branch\$3" &&
+
+ echo f1 >branch\$3/shell_char_branch_file &&
+ p4 add branch\$3/shell_char_branch_file &&
+ p4 submit -d "branch\$3/shell_char_branch_file" &&
+
+ p4 branch -i <<-EOF &&
+ Branch: branch\$3
+ View: //depot/main/... //depot/branch\$3/...
+ EOF
+
+ p4 edit main/f1 &&
+ echo "a change" >> main/f1 &&
+ p4 submit -d "a change" main/f1 &&
+
+ p4 integrate -b branch\$3 &&
+ p4 resolve -am branch\$3/... &&
+ p4 submit -d "integrate main to branch\$3" &&
+
+ cd "$git" &&
+
+ git config git-p4.branchList main:branch\$3 &&
+ git p4 clone --dest=. --detect-branches //depot@all &&
+ git log --all --graph --decorate --stat &&
+ git reset --hard p4/depot/branch\$3 &&
+ test -f shell_char_branch_file &&
+ test -f f1
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9804-git-p4-label.sh b/t/t9804-git-p4-label.sh
new file mode 100755
index 0000000..e30f80e
--- /dev/null
+++ b/t/t9804-git-p4-label.sh
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+test_description='git p4 label tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+# Basic p4 label tests.
+#
+# Note: can't have more than one label per commit - others
+# are silently discarded.
+#
+test_expect_success 'basic p4 labels' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ mkdir -p main &&
+
+ echo f1 >main/f1 &&
+ p4 add main/f1 &&
+ p4 submit -d "main/f1" &&
+
+ echo f2 >main/f2 &&
+ p4 add main/f2 &&
+ p4 submit -d "main/f2" &&
+
+ echo f3 >main/file_with_\$metachar &&
+ p4 add main/file_with_\$metachar &&
+ p4 submit -d "file with metachar" &&
+
+ p4 tag -l tag_f1_only main/f1 &&
+ p4 tag -l tag_with\$_shell_char main/... &&
+
+ echo f4 >main/f4 &&
+ p4 add main/f4 &&
+ p4 submit -d "main/f4" &&
+
+ p4 label -i <<-EOF &&
+ Label: long_label
+ Description:
+ A Label first line
+ A Label second line
+ View: //depot/...
+ EOF
+
+ p4 tag -l long_label ... &&
+
+ p4 labels ... &&
+
+ git p4 clone --dest="$git" --detect-labels //depot@all &&
+ cd "$git" &&
+
+ git tag &&
+ git tag >taglist &&
+ test_line_count = 3 taglist &&
+
+ cd main &&
+ git checkout tag_tag_f1_only &&
+ ! test -f f2 &&
+ git checkout tag_tag_with\$_shell_char &&
+ test -f f1 && test -f f2 && test -f file_with_\$metachar &&
+
+ git show tag_long_label | grep -q "A Label second line"
+ )
+'
+
+# Test some label corner cases:
+#
+# - two tags on the same file; both should be available
+# - a tag that is only on one file; this kind of tag
+# cannot be imported (at least not easily).
+
+test_expect_failure 'two labels on the same changelist' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ mkdir -p main &&
+
+ p4 edit main/f1 main/f2 &&
+ echo "hello world" >main/f1 &&
+ echo "not in the tag" >main/f2 &&
+ p4 submit -d "main/f[12]: testing two labels" &&
+
+ p4 tag -l tag_f1_1 main/... &&
+ p4 tag -l tag_f1_2 main/... &&
+
+ p4 labels ... &&
+
+ git p4 clone --dest="$git" --detect-labels //depot@all &&
+ cd "$git" &&
+
+ git tag | grep tag_f1 &&
+ git tag | grep -q tag_f1_1 &&
+ git tag | grep -q tag_f1_2 &&
+
+ cd main &&
+
+ git checkout tag_tag_f1_1 &&
+ ls &&
+ test -f f1 &&
+
+ git checkout tag_tag_f1_2 &&
+ ls &&
+ test -f f1
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9805-git-p4-skip-submit-edit.sh b/t/t9805-git-p4-skip-submit-edit.sh
new file mode 100755
index 0000000..353dcfb
--- /dev/null
+++ b/t/t9805-git-p4-skip-submit-edit.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+test_description='git p4 skipSubmitEdit config variables'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "change 1"
+ )
+'
+
+# this works because EDITOR is set to :
+test_expect_success 'no config, unedited, say yes' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ echo line >>file1 &&
+ git commit -a -m "change 2" &&
+ echo y | git p4 submit &&
+ p4 changes //depot/... >wc &&
+ test_line_count = 2 wc
+ )
+'
+
+test_expect_success 'no config, unedited, say no' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ echo line >>file1 &&
+ git commit -a -m "change 3 (not really)" &&
+ printf "bad response\nn\n" | git p4 submit &&
+ p4 changes //depot/... >wc &&
+ test_line_count = 2 wc
+ )
+'
+
+test_expect_success 'skipSubmitEdit' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ # will fail if editor is even invoked
+ git config core.editor /bin/false &&
+ echo line >>file1 &&
+ git commit -a -m "change 3" &&
+ git p4 submit &&
+ p4 changes //depot/... >wc &&
+ test_line_count = 3 wc
+ )
+'
+
+test_expect_success 'skipSubmitEditCheck' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ echo line >>file1 &&
+ git commit -a -m "change 4" &&
+ git p4 submit &&
+ p4 changes //depot/... >wc &&
+ test_line_count = 4 wc
+ )
+'
+
+# check the normal case, where the template really is edited
+test_expect_success 'no config, edited' '
+ git p4 clone --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ ed="$TRASH_DIRECTORY/ed.sh" &&
+ test_when_finished "rm \"$ed\"" &&
+ cat >"$ed" <<-EOF &&
+ #!$SHELL_PATH
+ sleep 1
+ touch "\$1"
+ exit 0
+ EOF
+ chmod 755 "$ed" &&
+ (
+ cd "$git" &&
+ echo line >>file1 &&
+ git commit -a -m "change 5" &&
+ P4EDITOR="" EDITOR="\"$ed\"" git p4 submit &&
+ p4 changes //depot/... >wc &&
+ test_line_count = 5 wc
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh
new file mode 100755
index 0000000..2892367
--- /dev/null
+++ b/t/t9806-git-p4-options.sh
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+test_description='git p4 options'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "change 1" &&
+ echo file2 >file2 &&
+ p4 add file2 &&
+ p4 submit -d "change 2" &&
+ echo file3 >file3 &&
+ p4 add file3 &&
+ p4 submit -d "change 3"
+ )
+'
+
+test_expect_success 'clone no --git-dir' '
+ test_must_fail git p4 clone --git-dir=xx //depot
+'
+
+test_expect_success 'clone --branch' '
+ git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git ls-files >files &&
+ test_line_count = 0 files &&
+ test_path_is_file .git/refs/remotes/p4/sb
+ )
+'
+
+test_expect_success 'clone --changesfile' '
+ cf="$TRASH_DIRECTORY/cf" &&
+ test_when_finished "rm \"$cf\"" &&
+ printf "1\n3\n" >"$cf" &&
+ git p4 clone --changesfile="$cf" --dest="$git" //depot &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git log --oneline p4/master >lines &&
+ test_line_count = 2 lines
+ test_path_is_file file1 &&
+ test_path_is_missing file2 &&
+ test_path_is_file file3
+ )
+'
+
+test_expect_success 'clone --changesfile, @all' '
+ cf="$TRASH_DIRECTORY/cf" &&
+ test_when_finished "rm \"$cf\"" &&
+ printf "1\n3\n" >"$cf" &&
+ test_must_fail git p4 clone --changesfile="$cf" --dest="$git" //depot@all
+'
+
+# imports both master and p4/master in refs/heads
+# requires --import-local on sync to find p4 refs/heads
+# does not update master on sync, just p4/master
+test_expect_success 'clone/sync --import-local' '
+ git p4 clone --import-local --dest="$git" //depot@1,2 &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git log --oneline refs/heads/master >lines &&
+ test_line_count = 2 lines &&
+ git log --oneline refs/heads/p4/master >lines &&
+ test_line_count = 2 lines &&
+ test_must_fail git p4 sync &&
+
+ git p4 sync --import-local &&
+ git log --oneline refs/heads/master >lines &&
+ test_line_count = 2 lines &&
+ git log --oneline refs/heads/p4/master >lines &&
+ test_line_count = 3 lines
+ )
+'
+
+test_expect_success 'clone --max-changes' '
+ git p4 clone --dest="$git" --max-changes 2 //depot@all &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git log --oneline refs/heads/master >lines &&
+ test_line_count = 2 lines
+ )
+'
+
+test_expect_success 'clone --keep-path' '
+ (
+ cd "$cli" &&
+ mkdir -p sub/dir &&
+ echo f4 >sub/dir/f4 &&
+ p4 add sub/dir/f4 &&
+ p4 submit -d "change 4"
+ ) &&
+ git p4 clone --dest="$git" --keep-path //depot/sub/dir@all &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ test_path_is_missing f4 &&
+ test_path_is_file sub/dir/f4
+ ) &&
+ cleanup_git &&
+ git p4 clone --dest="$git" //depot/sub/dir@all &&
+ (
+ cd "$git" &&
+ test_path_is_file f4 &&
+ test_path_is_missing sub/dir/f4
+ )
+'
+
+# clone --use-client-spec must still specify a depot path
+# if given, it should rearrange files according to client spec
+# when it has view lines that match the depot path
+# XXX: should clone/sync just use the client spec exactly, rather
+# than needing depot paths?
+test_expect_success 'clone --use-client-spec' '
+ (
+ # big usage message
+ exec >/dev/null &&
+ test_must_fail git p4 clone --dest="$git" --use-client-spec
+ ) &&
+ cli2="$TRASH_DIRECTORY/cli2" &&
+ mkdir -p "$cli2" &&
+ test_when_finished "rmdir \"$cli2\"" &&
+ (
+ cd "$cli2" &&
+ p4 client -i <<-EOF
+ Client: client2
+ Description: client2
+ Root: $cli2
+ View: //depot/sub/... //client2/bus/...
+ EOF
+ ) &&
+ P4CLIENT=client2 &&
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" --use-client-spec //depot/... &&
+ (
+ cd "$git" &&
+ test_path_is_file bus/dir/f4 &&
+ test_path_is_missing file1
+ ) &&
+ cleanup_git &&
+
+ # same thing again, this time with variable instead of option
+ mkdir "$git" &&
+ (
+ cd "$git" &&
+ git init &&
+ git config git-p4.useClientSpec true &&
+ git p4 sync //depot/... &&
+ git checkout -b master p4/master &&
+ test_path_is_file bus/dir/f4 &&
+ test_path_is_missing file1
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh
new file mode 100755
index 0000000..f23b4c3
--- /dev/null
+++ b/t/t9807-git-p4-submit.sh
@@ -0,0 +1,189 @@
+#!/bin/sh
+
+test_description='git p4 submit'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "change 1"
+ )
+'
+
+test_expect_success 'submit with no client dir' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ echo file2 >file2 &&
+ git add file2 &&
+ git commit -m "git commit 2" &&
+ rm -rf "$cli" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file file1 &&
+ test_path_is_file file2
+ )
+'
+
+# make two commits, but tell it to apply only from HEAD^
+test_expect_success 'submit --origin' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ test_commit "file3" &&
+ test_commit "file4" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit --origin=HEAD^
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing "file3.t" &&
+ test_path_is_file "file4.t"
+ )
+'
+
+test_expect_success 'submit with allowSubmit' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ test_commit "file5" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.allowSubmit "nobranch" &&
+ test_must_fail git p4 submit &&
+ git config git-p4.allowSubmit "nobranch,master" &&
+ git p4 submit
+ )
+'
+
+test_expect_success 'submit with master branch name from argv' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ test_commit "file6" &&
+ git config git-p4.skipSubmitEdit true &&
+ test_must_fail git p4 submit nobranch &&
+ git branch otherbranch &&
+ git reset --hard HEAD^ &&
+ test_commit "file7" &&
+ git p4 submit otherbranch
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file "file6.t" &&
+ test_path_is_missing "file7.t"
+ )
+'
+
+#
+# Basic submit tests, the five handled cases
+#
+
+test_expect_success 'submit modify' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ echo line >>file1 &&
+ git add file1 &&
+ git commit -m file1 &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file file1 &&
+ test_line_count = 2 file1
+ )
+'
+
+test_expect_success 'submit add' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ echo file13 >file13 &&
+ git add file13 &&
+ git commit -m file13 &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file file13
+ )
+'
+
+test_expect_success 'submit delete' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git rm file4.t &&
+ git commit -m "delete file4.t" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing file4.t
+ )
+'
+
+test_expect_success 'submit copy' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.detectCopies true &&
+ git config git-p4.detectCopiesHarder true &&
+ cp file5.t file5.ta &&
+ git add file5.ta &&
+ git commit -m "copy to file5.ta" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file file5.ta &&
+ test ! -w file5.ta
+ )
+'
+
+test_expect_success 'submit rename' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.detectRenames true &&
+ git mv file6.t file6.ta &&
+ git commit -m "rename file6.t to file6.ta" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing file6.t &&
+ test_path_is_file file6.ta &&
+ test ! -w file6.ta
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9808-git-p4-chdir.sh b/t/t9808-git-p4-chdir.sh
new file mode 100755
index 0000000..2f8014a
--- /dev/null
+++ b/t/t9808-git-p4-chdir.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='git p4 relative chdir'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "change 1"
+ )
+'
+
+# P4 reads from P4CONFIG file to find its server params, if the
+# environment variable is set
+test_expect_success 'P4CONFIG and absolute dir clone' '
+ printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config &&
+ test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" &&
+ test_when_finished cleanup_git &&
+ (
+ P4CONFIG=p4config && export P4CONFIG &&
+ sane_unset P4PORT P4CLIENT &&
+ git p4 clone --verbose --dest="$git" //depot
+ )
+'
+
+# same thing, but with relative directory name, note missing $ on --dest
+test_expect_success 'P4CONFIG and relative dir clone' '
+ printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config &&
+ test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" &&
+ test_when_finished cleanup_git &&
+ (
+ P4CONFIG=p4config && export P4CONFIG &&
+ sane_unset P4PORT P4CLIENT &&
+ git p4 clone --verbose --dest="git" //depot
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh
new file mode 100755
index 0000000..7d993ef
--- /dev/null
+++ b/t/t9809-git-p4-client-view.sh
@@ -0,0 +1,854 @@
+#!/bin/sh
+
+test_description='git p4 client view'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+#
+# Construct a client with this list of View lines
+#
+client_view() {
+ (
+ cat <<-EOF &&
+ Client: client
+ Description: client
+ Root: $cli
+ View:
+ EOF
+ for arg ; do
+ printf "\t$arg\n"
+ done
+ ) | p4 client -i
+}
+
+#
+# Verify these files exist, exactly. Caller creates
+# a list of files in file "files".
+#
+check_files_exist() {
+ ok=0 &&
+ num=$# &&
+ for arg ; do
+ test_path_is_file "$arg" &&
+ ok=$(($ok + 1))
+ done &&
+ test $ok -eq $num &&
+ test_line_count = $num files
+}
+
+#
+# Sync up the p4 client, make sure the given files (and only
+# those) exist.
+#
+client_verify() {
+ (
+ cd "$cli" &&
+ p4 sync &&
+ find . -type f ! -name files >files &&
+ check_files_exist "$@"
+ )
+}
+
+#
+# Make sure the named files, exactly, exist.
+#
+git_verify() {
+ (
+ cd "$git" &&
+ git ls-files >files &&
+ check_files_exist "$@"
+ )
+}
+
+# //depot
+# - dir1
+# - file11
+# - file12
+# - dir2
+# - file21
+# - file22
+init_depot() {
+ for d in 1 2 ; do
+ mkdir -p dir$d &&
+ for f in 1 2 ; do
+ echo dir$d/file$d$f >dir$d/file$d$f &&
+ p4 add dir$d/file$d$f &&
+ p4 submit -d "dir$d/file$d$f"
+ done
+ done &&
+ find . -type f ! -name files >files &&
+ check_files_exist dir1/file11 dir1/file12 \
+ dir2/file21 dir2/file22
+}
+
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+ init_depot
+ )
+'
+
+# double % for printf
+test_expect_success 'unsupported view wildcard %%n' '
+ client_view "//depot/%%%%1/sub/... //client/sub/%%%%1/..." &&
+ test_when_finished cleanup_git &&
+ test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'unsupported view wildcard *' '
+ client_view "//depot/*/bar/... //client/*/bar/..." &&
+ test_when_finished cleanup_git &&
+ test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'wildcard ... only supported at end of spec 1' '
+ client_view "//depot/.../file11 //client/.../file11" &&
+ test_when_finished cleanup_git &&
+ test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'wildcard ... only supported at end of spec 2' '
+ client_view "//depot/.../a/... //client/.../a/..." &&
+ test_when_finished cleanup_git &&
+ test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
+'
+
+test_expect_success 'basic map' '
+ client_view "//depot/dir1/... //client/cli1/..." &&
+ files="cli1/file11 cli1/file12" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'client view with no mappings' '
+ client_view &&
+ client_verify &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify
+'
+
+test_expect_success 'single file map' '
+ client_view "//depot/dir1/file11 //client/file11" &&
+ files="file11" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'later mapping takes precedence (entire repo)' '
+ client_view "//depot/dir1/... //client/cli1/..." \
+ "//depot/... //client/cli2/..." &&
+ files="cli2/dir1/file11 cli2/dir1/file12
+ cli2/dir2/file21 cli2/dir2/file22" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'later mapping takes precedence (partial repo)' '
+ client_view "//depot/dir1/... //client/..." \
+ "//depot/dir2/... //client/..." &&
+ files="file21 file22" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+# Reading the view backwards,
+# dir2 goes to cli12
+# dir1 cannot go to cli12 since it was filled by dir2
+# dir1 also does not go to cli3, since the second rule
+# noticed that it matched, but was already filled
+test_expect_success 'depot path matching rejected client path' '
+ client_view "//depot/dir1/... //client/cli3/..." \
+ "//depot/dir1/... //client/cli12/..." \
+ "//depot/dir2/... //client/cli12/..." &&
+ files="cli12/file21 cli12/file22" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+# since both have the same //client/..., the exclusion
+# rule keeps everything out
+test_expect_success 'exclusion wildcard, client rhs same (odd)' '
+ client_view "//depot/... //client/..." \
+ "-//depot/dir2/... //client/..." &&
+ client_verify &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify
+'
+
+test_expect_success 'exclusion wildcard, client rhs different (normal)' '
+ client_view "//depot/... //client/..." \
+ "-//depot/dir2/... //client/dir2/..." &&
+ files="dir1/file11 dir1/file12" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'exclusion single file' '
+ client_view "//depot/... //client/..." \
+ "-//depot/dir2/file22 //client/file22" &&
+ files="dir1/file11 dir1/file12 dir2/file21" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'overlay wildcard' '
+ client_view "//depot/dir1/... //client/cli/..." \
+ "+//depot/dir2/... //client/cli/...\n" &&
+ files="cli/file11 cli/file12 cli/file21 cli/file22" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'overlay single file' '
+ client_view "//depot/dir1/... //client/cli/..." \
+ "+//depot/dir2/file21 //client/cli/file21" &&
+ files="cli/file11 cli/file12 cli/file21" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'exclusion with later inclusion' '
+ client_view "//depot/... //client/..." \
+ "-//depot/dir2/... //client/dir2/..." \
+ "//depot/dir2/... //client/dir2incl/..." &&
+ files="dir1/file11 dir1/file12 dir2incl/file21 dir2incl/file22" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'quotes on rhs only' '
+ client_view "//depot/dir1/... \"//client/cdir 1/...\"" &&
+ client_verify "cdir 1/file11" "cdir 1/file12" &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify "cdir 1/file11" "cdir 1/file12"
+'
+
+#
+# Submit tests
+#
+
+# clone sets variable
+test_expect_success 'clone --use-client-spec sets useClientSpec' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config --bool git-p4.useClientSpec >actual &&
+ echo true >true &&
+ test_cmp actual true
+ )
+'
+
+# clone just a subdir of the client spec
+test_expect_success 'subdir clone' '
+ client_view "//depot/... //client/..." &&
+ files="dir1/file11 dir1/file12 dir2/file21 dir2/file22" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+ git_verify dir1/file11 dir1/file12
+'
+
+#
+# submit back, see what happens: five cases
+#
+test_expect_success 'subdir clone, submit modify' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ echo line >>dir1/file12 &&
+ git add dir1/file12 &&
+ git commit -m dir1/file12 &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file dir1/file12 &&
+ test_line_count = 2 dir1/file12
+ )
+'
+
+test_expect_success 'subdir clone, submit add' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ echo file13 >dir1/file13 &&
+ git add dir1/file13 &&
+ git commit -m dir1/file13 &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file dir1/file13
+ )
+'
+
+test_expect_success 'subdir clone, submit delete' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git rm dir1/file12 &&
+ git commit -m "delete dir1/file12" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing dir1/file12
+ )
+'
+
+test_expect_success 'subdir clone, submit copy' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.detectCopies true &&
+ cp dir1/file11 dir1/file11a &&
+ git add dir1/file11a &&
+ git commit -m "copy to dir1/file11a" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file dir1/file11a &&
+ test ! -w dir1/file11a
+ )
+'
+
+test_expect_success 'subdir clone, submit rename' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.detectRenames true &&
+ git mv dir1/file13 dir1/file13a &&
+ git commit -m "rename dir1/file13 to dir1/file13a" &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_missing dir1/file13 &&
+ test_path_is_file dir1/file13a &&
+ test ! -w dir1/file13a
+ )
+'
+
+# see t9800 for the non-client-spec case, and the rest of the wildcard tests
+test_expect_success 'wildcard files submit back to p4, client-spec case' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+ (
+ cd "$git" &&
+ echo git-wild-hash >dir1/git-wild#hash &&
+ echo git-wild-star >dir1/git-wild\*star &&
+ echo git-wild-at >dir1/git-wild@at &&
+ echo git-wild-percent >dir1/git-wild%percent &&
+ git add dir1/git-wild* &&
+ git commit -m "add some wildcard filenames" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ test_path_is_file dir1/git-wild#hash &&
+ test_path_is_file dir1/git-wild\*star &&
+ test_path_is_file dir1/git-wild@at &&
+ test_path_is_file dir1/git-wild%percent
+ ) &&
+ (
+ # delete these carefully, cannot just do "p4 delete"
+ # on files with wildcards; but git-p4 knows how
+ cd "$git" &&
+ git rm dir1/git-wild* &&
+ git commit -m "clean up the wildcards" &&
+ git p4 submit
+ )
+'
+
+test_expect_success 'reinit depot' '
+ (
+ cd "$cli" &&
+ rm files &&
+ p4 delete */* &&
+ p4 submit -d "delete all files" &&
+ init_depot
+ )
+'
+
+#
+# What happens when two files of the same name are overlayed together?
+# The last-listed file should take preference.
+#
+# //depot
+# - dir1
+# - file11
+# - file12
+# - filecollide
+# - dir2
+# - file21
+# - file22
+# - filecollide
+#
+test_expect_success 'overlay collision setup' '
+ client_view "//depot/... //client/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir1/filecollide >dir1/filecollide &&
+ p4 add dir1/filecollide &&
+ p4 submit -d dir1/filecollide &&
+ echo dir2/filecollide >dir2/filecollide &&
+ p4 add dir2/filecollide &&
+ p4 submit -d dir2/filecollide
+ )
+'
+
+test_expect_success 'overlay collision 1 to 2' '
+ client_view "//depot/dir1/... //client/..." \
+ "+//depot/dir2/... //client/..." &&
+ files="file11 file12 file21 file22 filecollide" &&
+ echo dir2/filecollide >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/filecollide &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files &&
+ test_cmp actual "$git"/filecollide
+'
+
+test_expect_failure 'overlay collision 2 to 1' '
+ client_view "//depot/dir2/... //client/..." \
+ "+//depot/dir1/... //client/..." &&
+ files="file11 file12 file21 file22 filecollide" &&
+ echo dir1/filecollide >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/filecollide &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files &&
+ test_cmp actual "$git"/filecollide
+'
+
+test_expect_success 'overlay collision delete 2' '
+ client_view "//depot/... //client/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ p4 delete dir2/filecollide &&
+ p4 submit -d "remove dir2/filecollide"
+ )
+'
+
+# no filecollide, got deleted with dir2
+test_expect_failure 'overlay collision 1 to 2, but 2 deleted' '
+ client_view "//depot/dir1/... //client/..." \
+ "+//depot/dir2/... //client/..." &&
+ files="file11 file12 file21 file22" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'overlay collision update 1' '
+ client_view "//depot/dir1/... //client/dir1/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ p4 open dir1/filecollide &&
+ echo dir1/filecollide update >dir1/filecollide &&
+ p4 submit -d "update dir1/filecollide"
+ )
+'
+
+# still no filecollide, dir2 still wins with the deletion even though the
+# change to dir1 is more recent
+test_expect_failure 'overlay collision 1 to 2, but 2 deleted, then 1 updated' '
+ client_view "//depot/dir1/... //client/..." \
+ "+//depot/dir2/... //client/..." &&
+ files="file11 file12 file21 file22" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files
+'
+
+test_expect_success 'overlay collision delete filecollides' '
+ client_view "//depot/... //client/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ p4 delete dir1/filecollide dir2/filecollide &&
+ p4 submit -d "remove filecollides"
+ )
+'
+
+#
+# Overlays as part of sync, rather than initial checkout:
+# 1. add a file in dir1
+# 2. sync to include it
+# 3. add same file in dir2
+# 4. sync, make sure content switches as dir2 has priority
+# 5. add another file in dir1
+# 6. sync
+# 7. add/delete same file in dir2
+# 8. sync, make sure it disappears, again dir2 wins
+# 9. cleanup
+#
+# //depot
+# - dir1
+# - file11
+# - file12
+# - colA
+# - colB
+# - dir2
+# - file21
+# - file22
+# - colA
+# - colB
+#
+test_expect_success 'overlay sync: add colA in dir1' '
+ client_view "//depot/dir1/... //client/dir1/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir1/colA >dir1/colA &&
+ p4 add dir1/colA &&
+ p4 submit -d dir1/colA
+ )
+'
+
+test_expect_success 'overlay sync: initial git checkout' '
+ client_view "//depot/dir1/... //client/..." \
+ "+//depot/dir2/... //client/..." &&
+ files="file11 file12 file21 file22 colA" &&
+ echo dir1/colA >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/colA &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files &&
+ test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync: add colA in dir2' '
+ client_view "//depot/dir2/... //client/dir2/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir2/colA >dir2/colA &&
+ p4 add dir2/colA &&
+ p4 submit -d dir2/colA
+ )
+'
+
+test_expect_success 'overlay sync: colA content switch' '
+ client_view "//depot/dir1/... //client/..." \
+ "+//depot/dir2/... //client/..." &&
+ files="file11 file12 file21 file22 colA" &&
+ echo dir2/colA >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/colA &&
+ (
+ cd "$git" &&
+ git p4 sync --use-client-spec &&
+ git merge --ff-only p4/master
+ ) &&
+ git_verify $files &&
+ test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync: add colB in dir1' '
+ client_view "//depot/dir1/... //client/dir1/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir1/colB >dir1/colB &&
+ p4 add dir1/colB &&
+ p4 submit -d dir1/colB
+ )
+'
+
+test_expect_success 'overlay sync: colB appears' '
+ client_view "//depot/dir1/... //client/..." \
+ "+//depot/dir2/... //client/..." &&
+ files="file11 file12 file21 file22 colA colB" &&
+ echo dir1/colB >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/colB &&
+ (
+ cd "$git" &&
+ git p4 sync --use-client-spec &&
+ git merge --ff-only p4/master
+ ) &&
+ git_verify $files &&
+ test_cmp actual "$git"/colB
+'
+
+test_expect_success 'overlay sync: add/delete colB in dir2' '
+ client_view "//depot/dir2/... //client/dir2/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir2/colB >dir2/colB &&
+ p4 add dir2/colB &&
+ p4 submit -d dir2/colB &&
+ p4 delete dir2/colB &&
+ p4 submit -d "delete dir2/colB"
+ )
+'
+
+test_expect_success 'overlay sync: colB disappears' '
+ client_view "//depot/dir1/... //client/..." \
+ "+//depot/dir2/... //client/..." &&
+ files="file11 file12 file21 file22 colA" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git p4 sync --use-client-spec &&
+ git merge --ff-only p4/master
+ ) &&
+ git_verify $files
+'
+
+test_expect_success 'overlay sync: cleanup' '
+ client_view "//depot/... //client/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ p4 delete dir1/colA dir2/colA dir1/colB &&
+ p4 submit -d "remove overlay sync files"
+ )
+'
+
+#
+# Overlay tests again, but swapped so dir1 has priority.
+# 1. add a file in dir1
+# 2. sync to include it
+# 3. add same file in dir2
+# 4. sync, make sure content does not switch
+# 5. add another file in dir1
+# 6. sync
+# 7. add/delete same file in dir2
+# 8. sync, make sure it is still there
+# 9. cleanup
+#
+# //depot
+# - dir1
+# - file11
+# - file12
+# - colA
+# - colB
+# - dir2
+# - file21
+# - file22
+# - colA
+# - colB
+#
+test_expect_success 'overlay sync swap: add colA in dir1' '
+ client_view "//depot/dir1/... //client/dir1/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir1/colA >dir1/colA &&
+ p4 add dir1/colA &&
+ p4 submit -d dir1/colA
+ )
+'
+
+test_expect_success 'overlay sync swap: initial git checkout' '
+ client_view "//depot/dir2/... //client/..." \
+ "+//depot/dir1/... //client/..." &&
+ files="file11 file12 file21 file22 colA" &&
+ echo dir1/colA >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/colA &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify $files &&
+ test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync swap: add colA in dir2' '
+ client_view "//depot/dir2/... //client/dir2/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir2/colA >dir2/colA &&
+ p4 add dir2/colA &&
+ p4 submit -d dir2/colA
+ )
+'
+
+test_expect_failure 'overlay sync swap: colA no content switch' '
+ client_view "//depot/dir2/... //client/..." \
+ "+//depot/dir1/... //client/..." &&
+ files="file11 file12 file21 file22 colA" &&
+ echo dir1/colA >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/colA &&
+ (
+ cd "$git" &&
+ git p4 sync --use-client-spec &&
+ git merge --ff-only p4/master
+ ) &&
+ git_verify $files &&
+ test_cmp actual "$git"/colA
+'
+
+test_expect_success 'overlay sync swap: add colB in dir1' '
+ client_view "//depot/dir1/... //client/dir1/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir1/colB >dir1/colB &&
+ p4 add dir1/colB &&
+ p4 submit -d dir1/colB
+ )
+'
+
+test_expect_success 'overlay sync swap: colB appears' '
+ client_view "//depot/dir2/... //client/..." \
+ "+//depot/dir1/... //client/..." &&
+ files="file11 file12 file21 file22 colA colB" &&
+ echo dir1/colB >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/colB &&
+ (
+ cd "$git" &&
+ git p4 sync --use-client-spec &&
+ git merge --ff-only p4/master
+ ) &&
+ git_verify $files &&
+ test_cmp actual "$git"/colB
+'
+
+test_expect_success 'overlay sync swap: add/delete colB in dir2' '
+ client_view "//depot/dir2/... //client/dir2/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ echo dir2/colB >dir2/colB &&
+ p4 add dir2/colB &&
+ p4 submit -d dir2/colB &&
+ p4 delete dir2/colB &&
+ p4 submit -d "delete dir2/colB"
+ )
+'
+
+test_expect_failure 'overlay sync swap: colB no change' '
+ client_view "//depot/dir2/... //client/..." \
+ "+//depot/dir1/... //client/..." &&
+ files="file11 file12 file21 file22 colA colB" &&
+ echo dir1/colB >actual &&
+ client_verify $files &&
+ test_cmp actual "$cli"/colB &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git p4 sync --use-client-spec &&
+ git merge --ff-only p4/master
+ ) &&
+ git_verify $files &&
+ test_cmp actual "$cli"/colB
+'
+
+test_expect_success 'overlay sync swap: cleanup' '
+ client_view "//depot/... //client/..." &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ p4 delete dir1/colA dir2/colA dir1/colB &&
+ p4 submit -d "remove overlay sync files"
+ )
+'
+
+#
+# Rename directories to test quoting in depot-side mappings
+# //depot
+# - "dir 1"
+# - file11
+# - file12
+# - "dir 2"
+# - file21
+# - file22
+#
+test_expect_success 'rename files to introduce spaces' '
+ client_view "//depot/... //client/..." &&
+ client_verify dir1/file11 dir1/file12 \
+ dir2/file21 dir2/file22 &&
+ (
+ cd "$cli" &&
+ p4 open dir1/... &&
+ p4 move dir1/... "dir 1"/... &&
+ p4 open dir2/... &&
+ p4 move dir2/... "dir 2"/... &&
+ p4 submit -d "rename with spaces"
+ ) &&
+ client_verify "dir 1/file11" "dir 1/file12" \
+ "dir 2/file21" "dir 2/file22"
+'
+
+test_expect_success 'quotes on lhs only' '
+ client_view "\"//depot/dir 1/...\" //client/cdir1/..." &&
+ files="cdir1/file11 cdir1/file12" &&
+ client_verify $files &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ client_verify $files
+'
+
+test_expect_success 'quotes on both sides' '
+ client_view "\"//depot/dir 1/...\" \"//client/cdir 1/...\"" &&
+ client_verify "cdir 1/file11" "cdir 1/file12" &&
+ test_when_finished cleanup_git &&
+ git p4 clone --use-client-spec --dest="$git" //depot &&
+ git_verify "cdir 1/file11" "cdir 1/file12"
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9810-git-p4-rcs.sh b/t/t9810-git-p4-rcs.sh
new file mode 100755
index 0000000..b00ad09
--- /dev/null
+++ b/t/t9810-git-p4-rcs.sh
@@ -0,0 +1,388 @@
+#!/bin/sh
+
+test_description='git-p4 rcs keywords'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+#
+# Make one file with keyword lines at the top, and
+# enough plain text to be able to test modifications
+# far away from the keywords.
+#
+test_expect_success 'init depot' '
+ (
+ cd "$cli" &&
+ cat <<-\EOF >filek &&
+ $Id$
+ /* $Revision$ */
+ # $Change$
+ line4
+ line5
+ line6
+ line7
+ line8
+ EOF
+ cp filek fileko &&
+ sed -i "s/Revision/Revision: do not scrub me/" fileko
+ cp fileko file_text &&
+ sed -i "s/Id/Id: do not scrub me/" file_text
+ p4 add -t text+k filek &&
+ p4 submit -d "filek" &&
+ p4 add -t text+ko fileko &&
+ p4 submit -d "fileko" &&
+ p4 add -t text file_text &&
+ p4 submit -d "file_text"
+ )
+'
+
+#
+# Generate these in a function to make it easy to use single quote marks.
+#
+write_scrub_scripts () {
+ cat >"$TRASH_DIRECTORY/scrub_k.py" <<-\EOF &&
+ import re, sys
+ sys.stdout.write(re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', sys.stdin.read()))
+ EOF
+ cat >"$TRASH_DIRECTORY/scrub_ko.py" <<-\EOF
+ import re, sys
+ sys.stdout.write(re.sub(r'(?i)\$(Id|Header):[^$]*\$', r'$\1$', sys.stdin.read()))
+ EOF
+}
+
+test_expect_success 'scrub scripts' '
+ write_scrub_scripts
+'
+
+#
+# Compare $cli/file to its scrubbed version, should be different.
+# Compare scrubbed $cli/file to $git/file, should be same.
+#
+scrub_k_check () {
+ file="$1" &&
+ scrub="$TRASH_DIRECTORY/$file" &&
+ "$PYTHON_PATH" "$TRASH_DIRECTORY/scrub_k.py" <"$git/$file" >"$scrub" &&
+ ! test_cmp "$cli/$file" "$scrub" &&
+ test_cmp "$git/$file" "$scrub" &&
+ rm "$scrub"
+}
+scrub_ko_check () {
+ file="$1" &&
+ scrub="$TRASH_DIRECTORY/$file" &&
+ "$PYTHON_PATH" "$TRASH_DIRECTORY/scrub_ko.py" <"$git/$file" >"$scrub" &&
+ ! test_cmp "$cli/$file" "$scrub" &&
+ test_cmp "$git/$file" "$scrub" &&
+ rm "$scrub"
+}
+
+#
+# Modify far away from keywords. If no RCS lines show up
+# in the diff, there is no conflict.
+#
+test_expect_success 'edit far away from RCS lines' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ sed -i "s/^line7/line7 edit/" filek &&
+ git commit -m "filek line7 edit" filek &&
+ git p4 submit &&
+ scrub_k_check filek
+ )
+'
+
+#
+# Modify near the keywords. This will require RCS scrubbing.
+#
+test_expect_success 'edit near RCS lines' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ sed -i "s/^line4/line4 edit/" filek &&
+ git commit -m "filek line4 edit" filek &&
+ git p4 submit &&
+ scrub_k_check filek
+ )
+'
+
+#
+# Modify the keywords themselves. This also will require RCS scrubbing.
+#
+test_expect_success 'edit keyword lines' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ sed -i "/Revision/d" filek &&
+ git commit -m "filek remove Revision line" filek &&
+ git p4 submit &&
+ scrub_k_check filek
+ )
+'
+
+#
+# Scrubbing text+ko files should not alter all keywords, just Id, Header.
+#
+test_expect_success 'scrub ko files differently' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ sed -i "s/^line4/line4 edit/" fileko &&
+ git commit -m "fileko line4 edit" fileko &&
+ git p4 submit &&
+ scrub_ko_check fileko &&
+ ! scrub_k_check fileko
+ )
+'
+
+# hack; git-p4 submit should do it on its own
+test_expect_success 'cleanup after failure' '
+ (
+ cd "$cli" &&
+ p4 revert ...
+ )
+'
+
+#
+# Do not scrub anything but +k or +ko files. Sneak a change into
+# the cli file so that submit will get a conflict. Make sure that
+# scrubbing doesn't make a mess of things.
+#
+# Assumes that git-p4 exits leaving the p4 file open, with the
+# conflict-generating patch unapplied.
+#
+# This might happen only if the git repo is behind the p4 repo at
+# submit time, and there is a conflict.
+#
+test_expect_success 'do not scrub plain text' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ sed -i "s/^line4/line4 edit/" file_text &&
+ git commit -m "file_text line4 edit" file_text &&
+ (
+ cd "$cli" &&
+ p4 open file_text &&
+ sed -i "s/^line5/line5 p4 edit/" file_text &&
+ p4 submit -d "file5 p4 edit"
+ ) &&
+ ! git p4 submit &&
+ (
+ # exepct something like:
+ # file_text - file(s) not opened on this client
+ # but not copious diff output
+ cd "$cli" &&
+ p4 diff file_text >wc &&
+ test_line_count = 1 wc
+ )
+ )
+'
+
+# hack; git-p4 submit should do it on its own
+test_expect_success 'cleanup after failure 2' '
+ (
+ cd "$cli" &&
+ p4 revert ...
+ )
+'
+
+create_kw_file () {
+ cat <<\EOF >"$1"
+/* A file
+ Id: $Id$
+ Revision: $Revision$
+ File: $File$
+ */
+int main(int argc, const char **argv) {
+ return 0;
+}
+EOF
+}
+
+test_expect_success 'add kwfile' '
+ (
+ cd "$cli" &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "file 1" &&
+ create_kw_file kwfile1.c &&
+ p4 add kwfile1.c &&
+ p4 submit -d "Add rcw kw file" kwfile1.c
+ )
+'
+
+p4_append_to_file () {
+ f="$1" &&
+ p4 edit -t ktext "$f" &&
+ echo "/* $(date) */" >>"$f" &&
+ p4 submit -d "appending a line in p4"
+}
+
+# Create some files with RCS keywords. If they get modified
+# elsewhere then the version number gets bumped which then
+# results in a merge conflict if we touch the RCS kw lines,
+# even though the change itself would otherwise apply cleanly.
+test_expect_success 'cope with rcs keyword expansion damage' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ (cd ../cli && p4_append_to_file kwfile1.c) &&
+ old_lines=$(wc -l <kwfile1.c) &&
+ "$PERL_PATH" -n -i -e "print unless m/Revision:/" kwfile1.c &&
+ new_lines=$(wc -l <kwfile1.c) &&
+ test $new_lines = $(($old_lines - 1)) &&
+
+ git add kwfile1.c &&
+ git commit -m "Zap an RCS kw line" &&
+ git p4 submit &&
+ git p4 rebase &&
+ git diff p4/master &&
+ git p4 commit &&
+ echo "try modifying in both" &&
+ cd "$cli" &&
+ p4 edit kwfile1.c &&
+ echo "line from p4" >>kwfile1.c &&
+ p4 submit -d "add a line in p4" kwfile1.c &&
+ cd "$git" &&
+ echo "line from git at the top" | cat - kwfile1.c >kwfile1.c.new &&
+ mv kwfile1.c.new kwfile1.c &&
+ git commit -m "Add line in git at the top" kwfile1.c &&
+ git p4 rebase &&
+ git p4 submit
+ )
+'
+
+test_expect_success 'cope with rcs keyword file deletion' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ echo "\$Revision\$" >kwdelfile.c &&
+ p4 add -t ktext kwdelfile.c &&
+ p4 submit -d "Add file to be deleted" &&
+ cat kwdelfile.c &&
+ grep 1 kwdelfile.c
+ ) &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ grep Revision kwdelfile.c &&
+ git rm -f kwdelfile.c &&
+ git commit -m "Delete a file containing RCS keywords" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ ! test -f kwdelfile.c
+ )
+'
+
+# If you add keywords in git of the form $Header$ then everything should
+# work fine without any special handling.
+test_expect_success 'Add keywords in git which match the default p4 values' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ echo "NewKW: \$Revision\$" >>kwfile1.c &&
+ git add kwfile1.c &&
+ git commit -m "Adding RCS keywords in git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ test -f kwfile1.c &&
+ grep "NewKW.*Revision.*[0-9]" kwfile1.c
+
+ )
+'
+
+# If you add keywords in git of the form $Header:#1$ then things will fail
+# unless git-p4 takes steps to scrub the *git* commit.
+#
+test_expect_failure 'Add keywords in git which do not match the default p4 values' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ echo "NewKW2: \$Revision:1\$" >>kwfile1.c &&
+ git add kwfile1.c &&
+ git commit -m "Adding RCS keywords in git" &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ git p4 submit
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync &&
+ grep "NewKW2.*Revision.*[0-9]" kwfile1.c
+
+ )
+'
+
+# Check that the existing merge conflict handling still works.
+# Modify kwfile1.c in git, and delete in p4. We should be able
+# to skip the git commit.
+#
+test_expect_success 'merge conflict handling still works' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ echo "Hello:\$Id\$" >merge2.c &&
+ echo "World" >>merge2.c &&
+ p4 add -t ktext merge2.c &&
+ p4 submit -d "add merge test file"
+ ) &&
+ git p4 clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ sed -e "/Hello/d" merge2.c >merge2.c.tmp &&
+ mv merge2.c.tmp merge2.c &&
+ git add merge2.c &&
+ git commit -m "Modifying merge2.c"
+ ) &&
+ (
+ cd "$cli" &&
+ p4 delete merge2.c &&
+ p4 submit -d "remove merge test file"
+ ) &&
+ (
+ cd "$git" &&
+ test -f merge2.c &&
+ git config git-p4.skipSubmitEdit true &&
+ git config git-p4.attemptRCSCleanup true &&
+ !(echo "s" | git p4 submit) &&
+ git rebase --skip &&
+ ! test -f merge2.c
+ )
+'
+
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh
new file mode 100755
index 0000000..095238f
--- /dev/null
+++ b/t/t9811-git-p4-label-import.sh
@@ -0,0 +1,222 @@
+#!/bin/sh
+
+test_description='git p4 label tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+# Basic p4 label import tests.
+#
+test_expect_success 'basic p4 labels' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ mkdir -p main &&
+
+ echo f1 >main/f1 &&
+ p4 add main/f1 &&
+ p4 submit -d "main/f1" &&
+
+ echo f2 >main/f2 &&
+ p4 add main/f2 &&
+ p4 submit -d "main/f2" &&
+
+ echo f3 >main/file_with_\$metachar &&
+ p4 add main/file_with_\$metachar &&
+ p4 submit -d "file with metachar" &&
+
+ p4 tag -l TAG_F1_ONLY main/f1 &&
+ p4 tag -l TAG_WITH\$_SHELL_CHAR main/... &&
+ p4 tag -l this_tag_will_be\ skipped main/... &&
+
+ echo f4 >main/f4 &&
+ p4 add main/f4 &&
+ p4 submit -d "main/f4" &&
+
+ p4 label -i <<-EOF &&
+ Label: TAG_LONG_LABEL
+ Description:
+ A Label first line
+ A Label second line
+ View: //depot/...
+ EOF
+
+ p4 tag -l TAG_LONG_LABEL ... &&
+
+ p4 labels ... &&
+
+ git p4 clone --dest="$git" //depot@all &&
+ cd "$git" &&
+ git config git-p4.labelImportRegexp ".*TAG.*" &&
+ git p4 sync --import-labels --verbose &&
+
+ git tag &&
+ git tag >taglist &&
+ test_line_count = 3 taglist &&
+
+ cd main &&
+ git checkout TAG_F1_ONLY &&
+ ! test -f f2 &&
+ git checkout TAG_WITH\$_SHELL_CHAR &&
+ test -f f1 && test -f f2 && test -f file_with_\$metachar &&
+
+ git show TAG_LONG_LABEL | grep -q "A Label second line"
+ )
+'
+# Test some label corner cases:
+#
+# - two tags on the same file; both should be available
+# - a tag that is only on one file; this kind of tag
+# cannot be imported (at least not easily).
+
+test_expect_success 'two labels on the same changelist' '
+ test_when_finished cleanup_git &&
+ (
+ cd "$cli" &&
+ mkdir -p main &&
+
+ p4 edit main/f1 main/f2 &&
+ echo "hello world" >main/f1 &&
+ echo "not in the tag" >main/f2 &&
+ p4 submit -d "main/f[12]: testing two labels" &&
+
+ p4 tag -l TAG_F1_1 main/... &&
+ p4 tag -l TAG_F1_2 main/... &&
+
+ p4 labels ... &&
+
+ git p4 clone --dest="$git" //depot@all &&
+ cd "$git" &&
+ git p4 sync --import-labels &&
+
+ git tag | grep TAG_F1 &&
+ git tag | grep -q TAG_F1_1 &&
+ git tag | grep -q TAG_F1_2 &&
+
+ cd main &&
+
+ git checkout TAG_F1_1 &&
+ ls &&
+ test -f f1 &&
+
+ git checkout TAG_F1_2 &&
+ ls &&
+ test -f f1
+ )
+'
+
+# Export some git tags to p4
+test_expect_success 'export git tags to p4' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git tag -m "A tag created in git:xyzzy" GIT_TAG_1 &&
+ echo "hello world" >main/f10 &&
+ git add main/f10 &&
+ git commit -m "Adding file for export test" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit &&
+ git tag -m "Another git tag" GIT_TAG_2 &&
+ git tag LIGHTWEIGHT_TAG &&
+ git p4 rebase --import-labels --verbose &&
+ git p4 submit --export-labels --verbose
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync ... &&
+ p4 labels ... | grep GIT_TAG_1 &&
+ p4 labels ... | grep GIT_TAG_2 &&
+ p4 labels ... | grep LIGHTWEIGHT_TAG &&
+ p4 label -o GIT_TAG_1 | grep "tag created in git:xyzzy" &&
+ p4 sync ...@GIT_TAG_1 &&
+ ! test -f main/f10
+ p4 sync ...@GIT_TAG_2 &&
+ test -f main/f10
+ )
+'
+
+# Export a tag from git where an affected file is deleted later on
+# Need to create git tags after rebase, since only then can the
+# git commits be mapped to p4 changelists.
+test_expect_success 'export git tags to p4 with deletion' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git p4 sync --import-labels &&
+ echo "deleted file" >main/deleted_file &&
+ git add main/deleted_file &&
+ git commit -m "create deleted file" &&
+ git rm main/deleted_file &&
+ echo "new file" >main/f11 &&
+ git add main/f11 &&
+ git commit -m "delete the deleted file" &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit &&
+ git p4 rebase --import-labels --verbose &&
+ git tag -m "tag on deleted file" GIT_TAG_ON_DELETED HEAD~1 &&
+ git tag -m "tag after deletion" GIT_TAG_AFTER_DELETION HEAD &&
+ git p4 submit --export-labels --verbose
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync ... &&
+ p4 sync ...@GIT_TAG_ON_DELETED &&
+ test -f main/deleted_file &&
+ p4 sync ...@GIT_TAG_AFTER_DELETION &&
+ ! test -f main/deleted_file &&
+ echo "checking label contents" &&
+ p4 label -o GIT_TAG_ON_DELETED | grep "tag on deleted file"
+ )
+'
+
+# Create a tag in git that cannot be exported to p4
+test_expect_success 'tag that cannot be exported' '
+ test_when_finished cleanup_git &&
+ git p4 clone --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git checkout -b a_branch &&
+ echo "hello" >main/f12 &&
+ git add main/f12 &&
+ git commit -m "adding f12" &&
+ git tag -m "tag on a_branch" GIT_TAG_ON_A_BRANCH &&
+ git checkout master &&
+ git p4 submit --export-labels
+ ) &&
+ (
+ cd "$cli" &&
+ p4 sync ... &&
+ !(p4 labels | grep GIT_TAG_ON_A_BRANCH)
+ )
+'
+
+test_expect_success 'use git config to enable import/export of tags' '
+ git p4 clone --verbose --dest="$git" //depot@all &&
+ (
+ cd "$git" &&
+ git config git-p4.exportLabels true &&
+ git config git-p4.importLabels true &&
+ git tag CFG_A_GIT_TAG &&
+ git p4 rebase --verbose &&
+ git p4 submit --verbose &&
+ git tag &&
+ git tag | grep TAG_F1_1
+ ) &&
+ (
+ cd "$cli" &&
+ p4 labels &&
+ p4 labels | grep CFG_A_GIT_TAG
+ )
+'
+
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
diff --git a/t/t9901-git-web--browse.sh b/t/t9901-git-web--browse.sh
new file mode 100755
index 0000000..b0a6bad
--- /dev/null
+++ b/t/t9901-git-web--browse.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+
+test_description='git web--browse basic tests
+
+This test checks that git web--browse can handle various valid URLs.'
+
+. ./test-lib.sh
+
+test_web_browse () {
+ # browser=$1 url=$2
+ git web--browse --browser="$1" "$2" >actual &&
+ tr -d '\015' <actual >text &&
+ test_cmp expect text
+}
+
+test_expect_success \
+ 'URL with an ampersand in it' '
+ echo http://example.com/foo\&bar >expect &&
+ git config browser.custom.cmd echo &&
+ test_web_browse custom http://example.com/foo\&bar
+'
+
+test_expect_success \
+ 'URL with a semi-colon in it' '
+ echo http://example.com/foo\;bar >expect &&
+ git config browser.custom.cmd echo &&
+ test_web_browse custom http://example.com/foo\;bar
+'
+
+test_expect_success \
+ 'URL with a hash in it' '
+ echo http://example.com/foo#bar >expect &&
+ git config browser.custom.cmd echo &&
+ test_web_browse custom http://example.com/foo#bar
+'
+
+test_expect_success \
+ 'browser paths are properly quoted' '
+ echo fake: http://example.com/foo >expect &&
+ cat >"fake browser" <<-\EOF &&
+ #!/bin/sh
+ echo fake: "$@"
+ EOF
+ chmod +x "fake browser" &&
+ git config browser.w3m.path "`pwd`/fake browser" &&
+ test_web_browse w3m http://example.com/foo
+'
+
+test_expect_success \
+ 'browser command allows arbitrary shell code' '
+ echo "arg: http://example.com/foo" >expect &&
+ git config browser.custom.cmd "
+ f() {
+ for i in \"\$@\"; do
+ echo arg: \$i
+ done
+ }
+ f" &&
+ test_web_browse custom http://example.com/foo
+'
+
+test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
new file mode 100755
index 0000000..92d7eb4
--- /dev/null
+++ b/t/t9902-completion.sh
@@ -0,0 +1,231 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Felipe Contreras
+#
+
+test_description='test bash completion'
+
+. ./lib-bash.sh
+
+complete ()
+{
+ # do nothing
+ return 0
+}
+
+. "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
+
+# We don't need this function to actually join words or do anything special.
+# Also, it's cleaner to avoid touching bash's internal completion variables.
+# So let's override it with a minimal version for testing purposes.
+_get_comp_words_by_ref ()
+{
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=${_words[_cword]}
+ ;;
+ prev)
+ prev=${_words[_cword-1]}
+ ;;
+ words)
+ words=("${_words[@]}")
+ ;;
+ cword)
+ cword=$_cword
+ ;;
+ esac
+ shift
+ done
+}
+
+print_comp ()
+{
+ local IFS=$'\n'
+ echo "${COMPREPLY[*]}" > out
+}
+
+run_completion ()
+{
+ local -a COMPREPLY _words
+ local _cword
+ _words=( $1 )
+ (( _cword = ${#_words[@]} - 1 ))
+ __git_wrap__git_main && print_comp
+}
+
+test_completion ()
+{
+ test $# -gt 1 && echo "$2" > expected
+ run_completion "$@" &&
+ test_cmp expected out
+}
+
+newline=$'\n'
+
+test_expect_success '__gitcomp - trailing space - options' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ --reuse-message=Z
+ --reedit-message=Z
+ --reset-author Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="--re" &&
+ __gitcomp "--dry-run --reuse-message= --reedit-message=
+ --reset-author" &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success '__gitcomp - trailing space - config keys' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ branch.Z
+ branch.autosetupmerge Z
+ branch.autosetuprebase Z
+ browser.Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="br" &&
+ __gitcomp "branch. branch.autosetupmerge
+ branch.autosetuprebase browser." &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success '__gitcomp - option parameter' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ recursive Z
+ resolve Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="--strategy=re" &&
+ __gitcomp "octopus ours recursive resolve subtree
+ " "" "re" &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success '__gitcomp - prefix' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ branch.maint.merge Z
+ branch.maint.mergeoptions Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="branch.me" &&
+ __gitcomp "remote merge mergeoptions rebase
+ " "branch.maint." "me" &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success '__gitcomp - suffix' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ branch.master.Z
+ branch.maint.Z
+ EOF
+ (
+ local -a COMPREPLY &&
+ cur="branch.me" &&
+ __gitcomp "master maint next pu
+ " "branch." "ma" "." &&
+ IFS="$newline" &&
+ echo "${COMPREPLY[*]}" > out
+ ) &&
+ test_cmp expected out
+'
+
+test_expect_success 'basic' '
+ run_completion "git \"\"" &&
+ # built-in
+ grep -q "^add \$" out &&
+ # script
+ grep -q "^filter-branch \$" out &&
+ # plumbing
+ ! grep -q "^ls-files \$" out &&
+
+ run_completion "git f" &&
+ ! grep -q -v "^f" out
+'
+
+test_expect_success 'double dash "git" itself' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ --paginate Z
+ --no-pager Z
+ --git-dir=
+ --bare Z
+ --version Z
+ --exec-path Z
+ --exec-path=
+ --html-path Z
+ --info-path Z
+ --work-tree=
+ --namespace=
+ --no-replace-objects Z
+ --help Z
+ EOF
+ test_completion "git --"
+'
+
+test_expect_success 'double dash "git checkout"' '
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ --quiet Z
+ --ours Z
+ --theirs Z
+ --track Z
+ --no-track Z
+ --merge Z
+ --conflict=
+ --orphan Z
+ --patch Z
+ EOF
+ test_completion "git checkout --"
+'
+
+test_expect_success 'general options' '
+ test_completion "git --ver" "--version " &&
+ test_completion "git --hel" "--help " &&
+ sed -e "s/Z$//" >expected <<-\EOF &&
+ --exec-path Z
+ --exec-path=
+ EOF
+ test_completion "git --exe" &&
+ test_completion "git --htm" "--html-path " &&
+ test_completion "git --pag" "--paginate " &&
+ test_completion "git --no-p" "--no-pager " &&
+ test_completion "git --git" "--git-dir=" &&
+ test_completion "git --wor" "--work-tree=" &&
+ test_completion "git --nam" "--namespace=" &&
+ test_completion "git --bar" "--bare " &&
+ test_completion "git --inf" "--info-path " &&
+ test_completion "git --no-r" "--no-replace-objects "
+'
+
+test_expect_success 'general options plus command' '
+ test_completion "git --version check" "checkout " &&
+ test_completion "git --paginate check" "checkout " &&
+ test_completion "git --git-dir=foo check" "checkout " &&
+ test_completion "git --bare check" "checkout " &&
+ test_completion "git --help des" "describe " &&
+ test_completion "git --exec-path=foo check" "checkout " &&
+ test_completion "git --html-path check" "checkout " &&
+ test_completion "git --no-pager check" "checkout " &&
+ test_completion "git --work-tree=foo check" "checkout " &&
+ test_completion "git --namespace=foo check" "checkout " &&
+ test_completion "git --paginate check" "checkout " &&
+ test_completion "git --info-path check" "checkout " &&
+ test_completion "git --no-replace-objects check" "checkout "
+'
+
+test_done
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
new file mode 100755
index 0000000..f17c1f8
--- /dev/null
+++ b/t/t9903-bash-prompt.sh
@@ -0,0 +1,456 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 SZEDER Gábor
+#
+
+test_description='test git-specific bash prompt functions'
+
+. ./lib-bash.sh
+
+. "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh"
+
+actual="$TRASH_DIRECTORY/actual"
+
+test_expect_success 'setup for prompt tests' '
+ mkdir -p subdir/subsubdir &&
+ git init otherrepo &&
+ echo 1 > file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ git tag -a -m msg1 t1 &&
+ git checkout -b b1 &&
+ echo 2 > file &&
+ git commit -m "second b1" file &&
+ echo 3 > file &&
+ git commit -m "third b1" file &&
+ git tag -a -m msg2 t2 &&
+ git checkout -b b2 master &&
+ echo 0 > file &&
+ git commit -m "second b2" file &&
+ git checkout master
+'
+
+test_expect_success 'gitdir - from command line (through $__git_dir)' '
+ echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+ (
+ __git_dir="$TRASH_DIRECTORY/otherrepo/.git" &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - repo as argument' '
+ echo "otherrepo/.git" > expected &&
+ __gitdir "otherrepo" > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - remote as argument' '
+ echo "remote" > expected &&
+ __gitdir "remote" > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - .git directory in cwd' '
+ echo ".git" > expected &&
+ __gitdir > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - .git directory in parent' '
+ echo "$TRASH_DIRECTORY/.git" > expected &&
+ (
+ cd subdir/subsubdir &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - cwd is a .git directory' '
+ echo "." > expected &&
+ (
+ cd .git &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - parent is a .git directory' '
+ echo "$TRASH_DIRECTORY/.git" > expected &&
+ (
+ cd .git/refs/heads &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - $GIT_DIR set while .git directory in cwd' '
+ echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+ (
+ GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+ export GIT_DIR &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - $GIT_DIR set while .git directory in parent' '
+ echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+ (
+ GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+ export GIT_DIR &&
+ cd subdir &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - non-existing $GIT_DIR' '
+ (
+ GIT_DIR="$TRASH_DIRECTORY/non-existing" &&
+ export GIT_DIR &&
+ test_must_fail __gitdir
+ )
+'
+
+test_expect_success 'gitdir - gitfile in cwd' '
+ echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+ echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
+ test_when_finished "rm -f subdir/.git" &&
+ (
+ cd subdir &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - gitfile in parent' '
+ echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+ echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
+ test_when_finished "rm -f subdir/.git" &&
+ (
+ cd subdir/subsubdir &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' '
+ echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+ mkdir otherrepo/dir &&
+ test_when_finished "rm -rf otherrepo/dir" &&
+ ln -s otherrepo/dir link &&
+ test_when_finished "rm -f link" &&
+ (
+ cd link &&
+ __gitdir > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - not a git repository' '
+ (
+ cd subdir/subsubdir &&
+ GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" &&
+ export GIT_CEILING_DIRECTORIES &&
+ test_must_fail __gitdir
+ )
+'
+
+test_expect_success 'prompt - branch name' '
+ printf " (master)" > expected &&
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - detached head' '
+ printf " ((%s...))" $(git log -1 --format="%h" b1^) > expected &&
+ git checkout b1^ &&
+ test_when_finished "git checkout master" &&
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - describe detached head - contains' '
+ printf " ((t2~1))" > expected &&
+ git checkout b1^ &&
+ test_when_finished "git checkout master" &&
+ (
+ GIT_PS1_DESCRIBE_STYLE=contains &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - describe detached head - branch' '
+ printf " ((b1~1))" > expected &&
+ git checkout b1^ &&
+ test_when_finished "git checkout master" &&
+ (
+ GIT_PS1_DESCRIBE_STYLE=branch &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - describe detached head - describe' '
+ printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) > expected &&
+ git checkout b1^ &&
+ test_when_finished "git checkout master" &&
+ (
+ GIT_PS1_DESCRIBE_STYLE=describe &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - describe detached head - default' '
+ printf " ((t2))" > expected &&
+ git checkout --detach b1 &&
+ test_when_finished "git checkout master" &&
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - inside .git directory' '
+ printf " (GIT_DIR!)" > expected &&
+ (
+ cd .git &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - deep inside .git directory' '
+ printf " (GIT_DIR!)" > expected &&
+ (
+ cd .git/refs/heads &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - inside bare repository' '
+ printf " (BARE:master)" > expected &&
+ git init --bare bare.git &&
+ test_when_finished "rm -rf bare.git" &&
+ (
+ cd bare.git &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - interactive rebase' '
+ printf " (b1|REBASE-i)" > expected
+ echo "#!$SHELL_PATH" >fake_editor.sh &&
+ cat >>fake_editor.sh <<\EOF &&
+echo "edit $(git log -1 --format="%h")" > "$1"
+EOF
+ test_when_finished "rm -f fake_editor.sh" &&
+ chmod a+x fake_editor.sh &&
+ test_set_editor "$TRASH_DIRECTORY/fake_editor.sh" &&
+ git checkout b1 &&
+ test_when_finished "git checkout master" &&
+ git rebase -i HEAD^ &&
+ test_when_finished "git rebase --abort"
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - rebase merge' '
+ printf " (b2|REBASE-m)" > expected &&
+ git checkout b2 &&
+ test_when_finished "git checkout master" &&
+ test_must_fail git rebase --merge b1 b2 &&
+ test_when_finished "git rebase --abort" &&
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - rebase' '
+ printf " ((t2)|REBASE)" > expected &&
+ git checkout b2 &&
+ test_when_finished "git checkout master" &&
+ test_must_fail git rebase b1 b2 &&
+ test_when_finished "git rebase --abort" &&
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - merge' '
+ printf " (b1|MERGING)" > expected &&
+ git checkout b1 &&
+ test_when_finished "git checkout master" &&
+ test_must_fail git merge b2 &&
+ test_when_finished "git reset --hard" &&
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - cherry-pick' '
+ printf " (master|CHERRY-PICKING)" > expected &&
+ test_must_fail git cherry-pick b1 &&
+ test_when_finished "git reset --hard" &&
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bisect' '
+ printf " (master|BISECTING)" > expected &&
+ git bisect start &&
+ test_when_finished "git bisect reset" &&
+ __git_ps1 > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - clean' '
+ printf " (master)" > expected &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - dirty worktree' '
+ printf " (master *)" > expected &&
+ echo "dirty" > file &&
+ test_when_finished "git reset --hard" &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - dirty index' '
+ printf " (master +)" > expected &&
+ echo "dirty" > file &&
+ test_when_finished "git reset --hard" &&
+ git add -u &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - dirty index and worktree' '
+ printf " (master *+)" > expected &&
+ echo "dirty index" > file &&
+ test_when_finished "git reset --hard" &&
+ git add -u &&
+ echo "dirty worktree" > file &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - before root commit' '
+ printf " (master #)" > expected &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ cd otherrepo &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - disabled by config' '
+ printf " (master)" > expected &&
+ echo "dirty" > file &&
+ test_when_finished "git reset --hard" &&
+ test_config bash.showDirtyState false &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - not shown inside .git directory' '
+ printf " (GIT_DIR!)" > expected &&
+ echo "dirty" > file &&
+ test_when_finished "git reset --hard" &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ cd .git &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - stash status indicator - no stash' '
+ printf " (master)" > expected &&
+ (
+ GIT_PS1_SHOWSTASHSTATE=y &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - stash status indicator - stash' '
+ printf " (master $)" > expected &&
+ echo 2 >file &&
+ git stash &&
+ test_when_finished "git stash drop" &&
+ (
+ GIT_PS1_SHOWSTASHSTATE=y &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - stash status indicator - not shown inside .git directory' '
+ printf " (GIT_DIR!)" > expected &&
+ echo 2 >file &&
+ git stash &&
+ test_when_finished "git stash drop" &&
+ (
+ GIT_PS1_SHOWSTASHSTATE=y &&
+ cd .git &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - untracked files status indicator - no untracked files' '
+ printf " (master)" > expected &&
+ (
+ GIT_PS1_SHOWUNTRACKEDFILES=y &&
+ cd otherrepo &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - untracked files status indicator - untracked files' '
+ printf " (master %%)" > expected &&
+ (
+ GIT_PS1_SHOWUNTRACKEDFILES=y &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - untracked files status indicator - not shown inside .git directory' '
+ printf " (GIT_DIR!)" > expected &&
+ (
+ GIT_PS1_SHOWUNTRACKEDFILES=y &&
+ cd .git &&
+ __git_ps1 > "$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - format string starting with dash' '
+ printf -- "-master" > expected &&
+ __git_ps1 "-%s" > "$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_done
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
new file mode 100644
index 0000000..1639769
--- /dev/null
+++ b/t/test-lib-functions.sh
@@ -0,0 +1,565 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see http://www.gnu.org/licenses/ .
+
+# The semantics of the editor variables are that of invoking
+# sh -c "$EDITOR \"$@\"" files ...
+#
+# If our trash directory contains shell metacharacters, they will be
+# interpreted if we just set $EDITOR directly, so do a little dance with
+# environment variables to work around this.
+#
+# In particular, quoting isn't enough, as the path may contain the same quote
+# that we're using.
+test_set_editor () {
+ FAKE_EDITOR="$1"
+ export FAKE_EDITOR
+ EDITOR='"$FAKE_EDITOR"'
+ export EDITOR
+}
+
+test_decode_color () {
+ awk '
+ function name(n) {
+ if (n == 0) return "RESET";
+ if (n == 1) return "BOLD";
+ if (n == 30) return "BLACK";
+ if (n == 31) return "RED";
+ if (n == 32) return "GREEN";
+ if (n == 33) return "YELLOW";
+ if (n == 34) return "BLUE";
+ if (n == 35) return "MAGENTA";
+ if (n == 36) return "CYAN";
+ if (n == 37) return "WHITE";
+ if (n == 40) return "BLACK";
+ if (n == 41) return "BRED";
+ if (n == 42) return "BGREEN";
+ if (n == 43) return "BYELLOW";
+ if (n == 44) return "BBLUE";
+ if (n == 45) return "BMAGENTA";
+ if (n == 46) return "BCYAN";
+ if (n == 47) return "BWHITE";
+ }
+ {
+ while (match($0, /\033\[[0-9;]*m/) != 0) {
+ printf "%s<", substr($0, 1, RSTART-1);
+ codes = substr($0, RSTART+2, RLENGTH-3);
+ if (length(codes) == 0)
+ printf "%s", name(0)
+ else {
+ n = split(codes, ary, ";");
+ sep = "";
+ for (i = 1; i <= n; i++) {
+ printf "%s%s", sep, name(ary[i]);
+ sep = ";"
+ }
+ }
+ printf ">";
+ $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1);
+ }
+ print
+ }
+ '
+}
+
+nul_to_q () {
+ "$PERL_PATH" -pe 'y/\000/Q/'
+}
+
+q_to_nul () {
+ "$PERL_PATH" -pe 'y/Q/\000/'
+}
+
+q_to_cr () {
+ tr Q '\015'
+}
+
+q_to_tab () {
+ tr Q '\011'
+}
+
+append_cr () {
+ sed -e 's/$/Q/' | tr Q '\015'
+}
+
+remove_cr () {
+ tr '\015' Q | sed -e 's/Q$//'
+}
+
+# In some bourne shell implementations, the "unset" builtin returns
+# nonzero status when a variable to be unset was not set in the first
+# place.
+#
+# Use sane_unset when that should not be considered an error.
+
+sane_unset () {
+ unset "$@"
+ return 0
+}
+
+test_tick () {
+ if test -z "${test_tick+set}"
+ then
+ test_tick=1112911993
+ else
+ test_tick=$(($test_tick + 60))
+ fi
+ GIT_COMMITTER_DATE="$test_tick -0700"
+ GIT_AUTHOR_DATE="$test_tick -0700"
+ export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
+
+# Stop execution and start a shell. This is useful for debugging tests and
+# only makes sense together with "-v".
+#
+# Be sure to remove all invocations of this command before submitting.
+
+test_pause () {
+ if test "$verbose" = t; then
+ "$SHELL_PATH" <&6 >&3 2>&4
+ else
+ error >&5 "test_pause requires --verbose"
+ fi
+}
+
+# Call test_commit with the arguments "<message> [<file> [<contents>]]"
+#
+# This will commit a file with the given contents and the given commit
+# message. It will also add a tag with <message> as name.
+#
+# Both <file> and <contents> default to <message>.
+
+test_commit () {
+ file=${2:-"$1.t"}
+ echo "${3-$1}" > "$file" &&
+ git add "$file" &&
+ test_tick &&
+ git commit -m "$1" &&
+ git tag "$1"
+}
+
+# Call test_merge with the arguments "<message> <commit>", where <commit>
+# can be a tag pointing to the commit-to-merge.
+
+test_merge () {
+ test_tick &&
+ git merge -m "$1" "$2" &&
+ git tag "$1"
+}
+
+# This function helps systems where core.filemode=false is set.
+# Use it instead of plain 'chmod +x' to set or unset the executable bit
+# of a file in the working directory and add it to the index.
+
+test_chmod () {
+ chmod "$@" &&
+ git update-index --add "--chmod=$@"
+}
+
+# Unset a configuration variable, but don't fail if it doesn't exist.
+test_unconfig () {
+ git config --unset-all "$@"
+ config_status=$?
+ case "$config_status" in
+ 5) # ok, nothing to unset
+ config_status=0
+ ;;
+ esac
+ return $config_status
+}
+
+# Set git config, automatically unsetting it after the test is over.
+test_config () {
+ test_when_finished "test_unconfig '$1'" &&
+ git config "$@"
+}
+
+test_config_global () {
+ test_when_finished "test_unconfig --global '$1'" &&
+ git config --global "$@"
+}
+
+write_script () {
+ {
+ echo "#!${2-"$SHELL_PATH"}" &&
+ cat
+ } >"$1" &&
+ chmod +x "$1"
+}
+
+# Use test_set_prereq to tell that a particular prerequisite is available.
+# The prerequisite can later be checked for in two ways:
+#
+# - Explicitly using test_have_prereq.
+#
+# - Implicitly by specifying the prerequisite tag in the calls to
+# test_expect_{success,failure,code}.
+#
+# The single parameter is the prerequisite tag (a simple word, in all
+# capital letters by convention).
+
+test_set_prereq () {
+ satisfied="$satisfied$1 "
+}
+satisfied=" "
+
+test_have_prereq () {
+ # prerequisites can be concatenated with ','
+ save_IFS=$IFS
+ IFS=,
+ set -- $*
+ IFS=$save_IFS
+
+ total_prereq=0
+ ok_prereq=0
+ missing_prereq=
+
+ for prerequisite
+ do
+ total_prereq=$(($total_prereq + 1))
+ case $satisfied in
+ *" $prerequisite "*)
+ ok_prereq=$(($ok_prereq + 1))
+ ;;
+ *)
+ # Keep a list of missing prerequisites
+ if test -z "$missing_prereq"
+ then
+ missing_prereq=$prerequisite
+ else
+ missing_prereq="$prerequisite,$missing_prereq"
+ fi
+ esac
+ done
+
+ test $total_prereq = $ok_prereq
+}
+
+test_declared_prereq () {
+ case ",$test_prereq," in
+ *,$1,*)
+ return 0
+ ;;
+ esac
+ return 1
+}
+
+test_expect_failure () {
+ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
+ test "$#" = 2 ||
+ error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
+ export test_prereq
+ if ! test_skip "$@"
+ then
+ say >&3 "checking known breakage: $2"
+ if test_run_ "$2" expecting_failure
+ then
+ test_known_broken_ok_ "$1"
+ else
+ test_known_broken_failure_ "$1"
+ fi
+ fi
+ echo >&3 ""
+}
+
+test_expect_success () {
+ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
+ test "$#" = 2 ||
+ error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+ export test_prereq
+ if ! test_skip "$@"
+ then
+ say >&3 "expecting success: $2"
+ if test_run_ "$2"
+ then
+ test_ok_ "$1"
+ else
+ test_failure_ "$@"
+ fi
+ fi
+ echo >&3 ""
+}
+
+# test_external runs external test scripts that provide continuous
+# test output about their progress, and succeeds/fails on
+# zero/non-zero exit code. It outputs the test output on stdout even
+# in non-verbose mode, and announces the external script with "# run
+# <n>: ..." before running it. When providing relative paths, keep in
+# mind that all scripts run in "trash directory".
+# Usage: test_external description command arguments...
+# Example: test_external 'Perl API' perl ../path/to/test.pl
+test_external () {
+ test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
+ test "$#" = 3 ||
+ error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
+ descr="$1"
+ shift
+ export test_prereq
+ if ! test_skip "$descr" "$@"
+ then
+ # Announce the script to reduce confusion about the
+ # test output that follows.
+ say_color "" "# run $test_count: $descr ($*)"
+ # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG
+ # to be able to use them in script
+ export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG
+ # Run command; redirect its stderr to &4 as in
+ # test_run_, but keep its stdout on our stdout even in
+ # non-verbose mode.
+ "$@" 2>&4
+ if [ "$?" = 0 ]
+ then
+ if test $test_external_has_tap -eq 0; then
+ test_ok_ "$descr"
+ else
+ say_color "" "# test_external test $descr was ok"
+ test_success=$(($test_success + 1))
+ fi
+ else
+ if test $test_external_has_tap -eq 0; then
+ test_failure_ "$descr" "$@"
+ else
+ say_color error "# test_external test $descr failed: $@"
+ test_failure=$(($test_failure + 1))
+ fi
+ fi
+ fi
+}
+
+# Like test_external, but in addition tests that the command generated
+# no output on stderr.
+test_external_without_stderr () {
+ # The temporary file has no (and must have no) security
+ # implications.
+ tmp=${TMPDIR:-/tmp}
+ stderr="$tmp/git-external-stderr.$$.tmp"
+ test_external "$@" 4> "$stderr"
+ [ -f "$stderr" ] || error "Internal error: $stderr disappeared."
+ descr="no stderr: $1"
+ shift
+ say >&3 "# expecting no stderr from previous command"
+ if [ ! -s "$stderr" ]; then
+ rm "$stderr"
+
+ if test $test_external_has_tap -eq 0; then
+ test_ok_ "$descr"
+ else
+ say_color "" "# test_external_without_stderr test $descr was ok"
+ test_success=$(($test_success + 1))
+ fi
+ else
+ if [ "$verbose" = t ]; then
+ output=`echo; echo "# Stderr is:"; cat "$stderr"`
+ else
+ output=
+ fi
+ # rm first in case test_failure exits.
+ rm "$stderr"
+ if test $test_external_has_tap -eq 0; then
+ test_failure_ "$descr" "$@" "$output"
+ else
+ say_color error "# test_external_without_stderr test $descr failed: $@: $output"
+ test_failure=$(($test_failure + 1))
+ fi
+ fi
+}
+
+# debugging-friendly alternatives to "test [-f|-d|-e]"
+# The commands test the existence or non-existence of $1. $2 can be
+# given to provide a more precise diagnosis.
+test_path_is_file () {
+ if ! [ -f "$1" ]
+ then
+ echo "File $1 doesn't exist. $*"
+ false
+ fi
+}
+
+test_path_is_dir () {
+ if ! [ -d "$1" ]
+ then
+ echo "Directory $1 doesn't exist. $*"
+ false
+ fi
+}
+
+test_path_is_missing () {
+ if [ -e "$1" ]
+ then
+ echo "Path exists:"
+ ls -ld "$1"
+ if [ $# -ge 1 ]; then
+ echo "$*"
+ fi
+ false
+ fi
+}
+
+# test_line_count checks that a file has the number of lines it
+# ought to. For example:
+#
+# test_expect_success 'produce exactly one line of output' '
+# do something >output &&
+# test_line_count = 1 output
+# '
+#
+# is like "test $(wc -l <output) = 1" except that it passes the
+# output through when the number of lines is wrong.
+
+test_line_count () {
+ if test $# != 3
+ then
+ error "bug in the test script: not 3 parameters to test_line_count"
+ elif ! test $(wc -l <"$3") "$1" "$2"
+ then
+ echo "test_line_count: line count for $3 !$1 $2"
+ cat "$3"
+ return 1
+ fi
+}
+
+# This is not among top-level (test_expect_success | test_expect_failure)
+# but is a prefix that can be used in the test script, like:
+#
+# test_expect_success 'complain and die' '
+# do something &&
+# do something else &&
+# test_must_fail git checkout ../outerspace
+# '
+#
+# Writing this as "! git checkout ../outerspace" is wrong, because
+# the failure could be due to a segv. We want a controlled failure.
+
+test_must_fail () {
+ "$@"
+ exit_code=$?
+ if test $exit_code = 0; then
+ echo >&2 "test_must_fail: command succeeded: $*"
+ return 1
+ elif test $exit_code -gt 129 -a $exit_code -le 192; then
+ echo >&2 "test_must_fail: died by signal: $*"
+ return 1
+ elif test $exit_code = 127; then
+ echo >&2 "test_must_fail: command not found: $*"
+ return 1
+ fi
+ return 0
+}
+
+# Similar to test_must_fail, but tolerates success, too. This is
+# meant to be used in contexts like:
+#
+# test_expect_success 'some command works without configuration' '
+# test_might_fail git config --unset all.configuration &&
+# do something
+# '
+#
+# Writing "git config --unset all.configuration || :" would be wrong,
+# because we want to notice if it fails due to segv.
+
+test_might_fail () {
+ "$@"
+ exit_code=$?
+ if test $exit_code -gt 129 -a $exit_code -le 192; then
+ echo >&2 "test_might_fail: died by signal: $*"
+ return 1
+ elif test $exit_code = 127; then
+ echo >&2 "test_might_fail: command not found: $*"
+ return 1
+ fi
+ return 0
+}
+
+# Similar to test_must_fail and test_might_fail, but check that a
+# given command exited with a given exit code. Meant to be used as:
+#
+# test_expect_success 'Merge with d/f conflicts' '
+# test_expect_code 1 git merge "merge msg" B master
+# '
+
+test_expect_code () {
+ want_code=$1
+ shift
+ "$@"
+ exit_code=$?
+ if test $exit_code = $want_code
+ then
+ return 0
+ fi
+
+ echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
+ return 1
+}
+
+# test_cmp is a helper function to compare actual and expected output.
+# You can use it like:
+#
+# test_expect_success 'foo works' '
+# echo expected >expected &&
+# foo >actual &&
+# test_cmp expected actual
+# '
+#
+# This could be written as either "cmp" or "diff -u", but:
+# - cmp's output is not nearly as easy to read as diff -u
+# - not all diff versions understand "-u"
+
+test_cmp() {
+ $GIT_TEST_CMP "$@"
+}
+
+# This function can be used to schedule some commands to be run
+# unconditionally at the end of the test to restore sanity:
+#
+# test_expect_success 'test core.capslock' '
+# git config core.capslock true &&
+# test_when_finished "git config --unset core.capslock" &&
+# hello world
+# '
+#
+# That would be roughly equivalent to
+#
+# test_expect_success 'test core.capslock' '
+# git config core.capslock true &&
+# hello world
+# git config --unset core.capslock
+# '
+#
+# except that the greeting and config --unset must both succeed for
+# the test to pass.
+#
+# Note that under --immediate mode, no clean-up is done to help diagnose
+# what went wrong.
+
+test_when_finished () {
+ test_cleanup="{ $*
+ } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
+}
+
+# Most tests can use the created repository, but some may need to create more.
+# Usage: test_create_repo <directory>
+test_create_repo () {
+ test "$#" = 1 ||
+ error "bug in the test script: not 1 parameter to test-create-repo"
+ repo="$1"
+ mkdir -p "$repo"
+ (
+ cd "$repo" || error "Cannot setup test environment"
+ "$GIT_EXEC_PATH/git-init" "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 ||
+ error "cannot run git init -- have you built things yet?"
+ mv .git/hooks .git/hooks-disabled
+ ) || exit
+}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index df25f17..acda33d 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -42,9 +42,11 @@ TZ=UTC
TERM=dumb
export LANG LC_ALL PAGER TERM TZ
EDITOR=:
-unset VISUAL
-unset EMAIL
-unset $(perl -e '
+# A call to "unset" with no arguments causes at least Solaris 10
+# /usr/xpg4/bin/sh and /bin/ksh to bail out. So keep the unsets
+# deriving from the command substitution clustered with the other
+# ones.
+unset VISUAL EMAIL LANGUAGE COLUMNS $(perl -e '
my @env = keys %ENV;
my $ok = join("|", qw(
TRACE
@@ -54,6 +56,7 @@ unset $(perl -e '
.*_TEST
PROVE
VALGRIND
+ PERF_AGGREGATING_LATER
));
my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
print join("\n", @vars);
@@ -63,7 +66,8 @@ GIT_AUTHOR_NAME='A U Thor'
GIT_COMMITTER_EMAIL=committer@example.com
GIT_COMMITTER_NAME='C O Mitter'
GIT_MERGE_VERBOSITY=5
-export GIT_MERGE_VERBOSITY
+GIT_MERGE_AUTOEDIT=no
+export GIT_MERGE_VERBOSITY GIT_MERGE_AUTOEDIT
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
export EDITOR
@@ -92,6 +96,12 @@ _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
# Zero SHA-1
_z40=0000000000000000000000000000000000000000
+# Line feed
+LF='
+'
+
+export _x05 _x40 _z40 LF
+
# Each test should start with something like this, after copyright notices:
#
# test_description='Description of this test...
@@ -187,6 +197,7 @@ then
fi
exec 5>&1
+exec 6<&0
if test "$verbose" = "t"
then
exec 4>&2 3>&1
@@ -216,203 +227,9 @@ die () {
GIT_EXIT_OK=
trap 'die' EXIT
-# The semantics of the editor variables are that of invoking
-# sh -c "$EDITOR \"$@\"" files ...
-#
-# If our trash directory contains shell metacharacters, they will be
-# interpreted if we just set $EDITOR directly, so do a little dance with
-# environment variables to work around this.
-#
-# In particular, quoting isn't enough, as the path may contain the same quote
-# that we're using.
-test_set_editor () {
- FAKE_EDITOR="$1"
- export FAKE_EDITOR
- EDITOR='"$FAKE_EDITOR"'
- export EDITOR
-}
-
-test_decode_color () {
- awk '
- function name(n) {
- if (n == 0) return "RESET";
- if (n == 1) return "BOLD";
- if (n == 30) return "BLACK";
- if (n == 31) return "RED";
- if (n == 32) return "GREEN";
- if (n == 33) return "YELLOW";
- if (n == 34) return "BLUE";
- if (n == 35) return "MAGENTA";
- if (n == 36) return "CYAN";
- if (n == 37) return "WHITE";
- if (n == 40) return "BLACK";
- if (n == 41) return "BRED";
- if (n == 42) return "BGREEN";
- if (n == 43) return "BYELLOW";
- if (n == 44) return "BBLUE";
- if (n == 45) return "BMAGENTA";
- if (n == 46) return "BCYAN";
- if (n == 47) return "BWHITE";
- }
- {
- while (match($0, /\033\[[0-9;]*m/) != 0) {
- printf "%s<", substr($0, 1, RSTART-1);
- codes = substr($0, RSTART+2, RLENGTH-3);
- if (length(codes) == 0)
- printf "%s", name(0)
- else {
- n = split(codes, ary, ";");
- sep = "";
- for (i = 1; i <= n; i++) {
- printf "%s%s", sep, name(ary[i]);
- sep = ";"
- }
- }
- printf ">";
- $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1);
- }
- print
- }
- '
-}
-
-nul_to_q () {
- perl -pe 'y/\000/Q/'
-}
-
-q_to_nul () {
- perl -pe 'y/Q/\000/'
-}
-
-q_to_cr () {
- tr Q '\015'
-}
-
-q_to_tab () {
- tr Q '\011'
-}
-
-append_cr () {
- sed -e 's/$/Q/' | tr Q '\015'
-}
-
-remove_cr () {
- tr '\015' Q | sed -e 's/Q$//'
-}
-
-# In some bourne shell implementations, the "unset" builtin returns
-# nonzero status when a variable to be unset was not set in the first
-# place.
-#
-# Use sane_unset when that should not be considered an error.
-
-sane_unset () {
- unset "$@"
- return 0
-}
-
-test_tick () {
- if test -z "${test_tick+set}"
- then
- test_tick=1112911993
- else
- test_tick=$(($test_tick + 60))
- fi
- GIT_COMMITTER_DATE="$test_tick -0700"
- GIT_AUTHOR_DATE="$test_tick -0700"
- export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
-# Call test_commit with the arguments "<message> [<file> [<contents>]]"
-#
-# This will commit a file with the given contents and the given commit
-# message. It will also add a tag with <message> as name.
-#
-# Both <file> and <contents> default to <message>.
-
-test_commit () {
- file=${2:-"$1.t"}
- echo "${3-$1}" > "$file" &&
- git add "$file" &&
- test_tick &&
- git commit -m "$1" &&
- git tag "$1"
-}
-
-# Call test_merge with the arguments "<message> <commit>", where <commit>
-# can be a tag pointing to the commit-to-merge.
-
-test_merge () {
- test_tick &&
- git merge -m "$1" "$2" &&
- git tag "$1"
-}
-
-# This function helps systems where core.filemode=false is set.
-# Use it instead of plain 'chmod +x' to set or unset the executable bit
-# of a file in the working directory and add it to the index.
-
-test_chmod () {
- chmod "$@" &&
- git update-index --add "--chmod=$@"
-}
-
-# Use test_set_prereq to tell that a particular prerequisite is available.
-# The prerequisite can later be checked for in two ways:
-#
-# - Explicitly using test_have_prereq.
-#
-# - Implicitly by specifying the prerequisite tag in the calls to
-# test_expect_{success,failure,code}.
-#
-# The single parameter is the prerequisite tag (a simple word, in all
-# capital letters by convention).
-
-test_set_prereq () {
- satisfied="$satisfied$1 "
-}
-satisfied=" "
-
-test_have_prereq () {
- # prerequisites can be concatenated with ','
- save_IFS=$IFS
- IFS=,
- set -- $*
- IFS=$save_IFS
-
- total_prereq=0
- ok_prereq=0
- missing_prereq=
-
- for prerequisite
- do
- total_prereq=$(($total_prereq + 1))
- case $satisfied in
- *" $prerequisite "*)
- ok_prereq=$(($ok_prereq + 1))
- ;;
- *)
- # Keep a list of missing prerequisites
- if test -z "$missing_prereq"
- then
- missing_prereq=$prerequisite
- else
- missing_prereq="$prerequisite,$missing_prereq"
- fi
- esac
- done
-
- test $total_prereq = $ok_prereq
-}
-
-test_declared_prereq () {
- case ",$test_prereq," in
- *,$1,*)
- return 0
- ;;
- esac
- return 1
-}
+# The user-facing functions are loaded from a separate file so that
+# test_perf subshells can have them too
+. "${TEST_DIRECTORY:-.}"/test-lib-functions.sh
# You are not expected to call test_ok_ and test_failure_ directly, use
# the text_expect_* functions instead.
@@ -444,20 +261,26 @@ test_debug () {
test "$debug" = "" || eval "$1"
}
+test_eval_ () {
+ # This is a separate function because some tests use
+ # "return" to end a test_expect_success block early.
+ eval </dev/null >&3 2>&4 "$*"
+}
+
test_run_ () {
test_cleanup=:
expecting_failure=$2
- eval >&3 2>&4 "$1"
+ test_eval_ "$1"
eval_ret=$?
if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"
then
- eval >&3 2>&4 "$test_cleanup"
+ test_eval_ "$test_cleanup"
fi
if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
echo ""
fi
- return 0
+ return "$eval_ret"
}
test_skip () {
@@ -494,320 +317,16 @@ test_skip () {
esac
}
-test_expect_failure () {
- test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
- test "$#" = 2 ||
- error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
- export test_prereq
- if ! test_skip "$@"
- then
- say >&3 "checking known breakage: $2"
- test_run_ "$2" expecting_failure
- if [ "$?" = 0 -a "$eval_ret" = 0 ]
- then
- test_known_broken_ok_ "$1"
- else
- test_known_broken_failure_ "$1"
- fi
- fi
- echo >&3 ""
-}
-
-test_expect_success () {
- test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
- test "$#" = 2 ||
- error "bug in the test script: not 2 or 3 parameters to test-expect-success"
- export test_prereq
- if ! test_skip "$@"
- then
- say >&3 "expecting success: $2"
- test_run_ "$2"
- if [ "$?" = 0 -a "$eval_ret" = 0 ]
- then
- test_ok_ "$1"
- else
- test_failure_ "$@"
- fi
- fi
- echo >&3 ""
-}
-
-# test_external runs external test scripts that provide continuous
-# test output about their progress, and succeeds/fails on
-# zero/non-zero exit code. It outputs the test output on stdout even
-# in non-verbose mode, and announces the external script with "# run
-# <n>: ..." before running it. When providing relative paths, keep in
-# mind that all scripts run in "trash directory".
-# Usage: test_external description command arguments...
-# Example: test_external 'Perl API' perl ../path/to/test.pl
-test_external () {
- test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
- test "$#" = 3 ||
- error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
- descr="$1"
- shift
- export test_prereq
- if ! test_skip "$descr" "$@"
- then
- # Announce the script to reduce confusion about the
- # test output that follows.
- say_color "" "# run $test_count: $descr ($*)"
- # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG
- # to be able to use them in script
- export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG
- # Run command; redirect its stderr to &4 as in
- # test_run_, but keep its stdout on our stdout even in
- # non-verbose mode.
- "$@" 2>&4
- if [ "$?" = 0 ]
- then
- if test $test_external_has_tap -eq 0; then
- test_ok_ "$descr"
- else
- say_color "" "# test_external test $descr was ok"
- test_success=$(($test_success + 1))
- fi
- else
- if test $test_external_has_tap -eq 0; then
- test_failure_ "$descr" "$@"
- else
- say_color error "# test_external test $descr failed: $@"
- test_failure=$(($test_failure + 1))
- fi
- fi
- fi
-}
-
-# Like test_external, but in addition tests that the command generated
-# no output on stderr.
-test_external_without_stderr () {
- # The temporary file has no (and must have no) security
- # implications.
- tmp=${TMPDIR:-/tmp}
- stderr="$tmp/git-external-stderr.$$.tmp"
- test_external "$@" 4> "$stderr"
- [ -f "$stderr" ] || error "Internal error: $stderr disappeared."
- descr="no stderr: $1"
- shift
- say >&3 "# expecting no stderr from previous command"
- if [ ! -s "$stderr" ]; then
- rm "$stderr"
-
- if test $test_external_has_tap -eq 0; then
- test_ok_ "$descr"
- else
- say_color "" "# test_external_without_stderr test $descr was ok"
- test_success=$(($test_success + 1))
- fi
- else
- if [ "$verbose" = t ]; then
- output=`echo; echo "# Stderr is:"; cat "$stderr"`
- else
- output=
- fi
- # rm first in case test_failure exits.
- rm "$stderr"
- if test $test_external_has_tap -eq 0; then
- test_failure_ "$descr" "$@" "$output"
- else
- say_color error "# test_external_without_stderr test $descr failed: $@: $output"
- test_failure=$(($test_failure + 1))
- fi
- fi
-}
-
-# debugging-friendly alternatives to "test [-f|-d|-e]"
-# The commands test the existence or non-existence of $1. $2 can be
-# given to provide a more precise diagnosis.
-test_path_is_file () {
- if ! [ -f "$1" ]
- then
- echo "File $1 doesn't exist. $*"
- false
- fi
-}
-
-test_path_is_dir () {
- if ! [ -d "$1" ]
- then
- echo "Directory $1 doesn't exist. $*"
- false
- fi
-}
-
-test_path_is_missing () {
- if [ -e "$1" ]
- then
- echo "Path exists:"
- ls -ld "$1"
- if [ $# -ge 1 ]; then
- echo "$*"
- fi
- false
- fi
-}
-
-# test_line_count checks that a file has the number of lines it
-# ought to. For example:
-#
-# test_expect_success 'produce exactly one line of output' '
-# do something >output &&
-# test_line_count = 1 output
-# '
-#
-# is like "test $(wc -l <output) = 1" except that it passes the
-# output through when the number of lines is wrong.
-
-test_line_count () {
- if test $# != 3
- then
- error "bug in the test script: not 3 parameters to test_line_count"
- elif ! test $(wc -l <"$3") "$1" "$2"
- then
- echo "test_line_count: line count for $3 !$1 $2"
- cat "$3"
- return 1
- fi
-}
-
-# This is not among top-level (test_expect_success | test_expect_failure)
-# but is a prefix that can be used in the test script, like:
-#
-# test_expect_success 'complain and die' '
-# do something &&
-# do something else &&
-# test_must_fail git checkout ../outerspace
-# '
-#
-# Writing this as "! git checkout ../outerspace" is wrong, because
-# the failure could be due to a segv. We want a controlled failure.
-
-test_must_fail () {
- "$@"
- exit_code=$?
- if test $exit_code = 0; then
- echo >&2 "test_must_fail: command succeeded: $*"
- return 1
- elif test $exit_code -gt 129 -a $exit_code -le 192; then
- echo >&2 "test_must_fail: died by signal: $*"
- return 1
- elif test $exit_code = 127; then
- echo >&2 "test_must_fail: command not found: $*"
- return 1
- fi
- return 0
-}
-
-# Similar to test_must_fail, but tolerates success, too. This is
-# meant to be used in contexts like:
-#
-# test_expect_success 'some command works without configuration' '
-# test_might_fail git config --unset all.configuration &&
-# do something
-# '
-#
-# Writing "git config --unset all.configuration || :" would be wrong,
-# because we want to notice if it fails due to segv.
-
-test_might_fail () {
- "$@"
- exit_code=$?
- if test $exit_code -gt 129 -a $exit_code -le 192; then
- echo >&2 "test_might_fail: died by signal: $*"
- return 1
- elif test $exit_code = 127; then
- echo >&2 "test_might_fail: command not found: $*"
- return 1
- fi
- return 0
-}
-
-# Similar to test_must_fail and test_might_fail, but check that a
-# given command exited with a given exit code. Meant to be used as:
-#
-# test_expect_success 'Merge with d/f conflicts' '
-# test_expect_code 1 git merge "merge msg" B master
-# '
-
-test_expect_code () {
- want_code=$1
- shift
- "$@"
- exit_code=$?
- if test $exit_code = $want_code
- then
- return 0
- fi
-
- echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
- return 1
-}
-
-# test_cmp is a helper function to compare actual and expected output.
-# You can use it like:
-#
-# test_expect_success 'foo works' '
-# echo expected >expected &&
-# foo >actual &&
-# test_cmp expected actual
-# '
-#
-# This could be written as either "cmp" or "diff -u", but:
-# - cmp's output is not nearly as easy to read as diff -u
-# - not all diff versions understand "-u"
-
-test_cmp() {
- $GIT_TEST_CMP "$@"
-}
-
-# This function can be used to schedule some commands to be run
-# unconditionally at the end of the test to restore sanity:
-#
-# test_expect_success 'test core.capslock' '
-# git config core.capslock true &&
-# test_when_finished "git config --unset core.capslock" &&
-# hello world
-# '
-#
-# That would be roughly equivalent to
-#
-# test_expect_success 'test core.capslock' '
-# git config core.capslock true &&
-# hello world
-# git config --unset core.capslock
-# '
-#
-# except that the greeting and config --unset must both succeed for
-# the test to pass.
-#
-# Note that under --immediate mode, no clean-up is done to help diagnose
-# what went wrong.
-
-test_when_finished () {
- test_cleanup="{ $*
- } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
-}
-
-# Most tests can use the created repository, but some may need to create more.
-# Usage: test_create_repo <directory>
-test_create_repo () {
- test "$#" = 1 ||
- error "bug in the test script: not 1 parameter to test-create-repo"
- repo="$1"
- mkdir -p "$repo"
- (
- cd "$repo" || error "Cannot setup test environment"
- "$GIT_EXEC_PATH/git-init" "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 ||
- error "cannot run git init -- have you built things yet?"
- mv .git/hooks .git/hooks-disabled
- ) || exit
+# stub; perf-lib overrides it
+test_at_end_hook_ () {
+ :
}
test_done () {
GIT_EXIT_OK=t
if test -z "$HARNESS_ACTIVE"; then
- test_results_dir="$TEST_DIRECTORY/test-results"
+ test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results"
mkdir -p "$test_results_dir"
test_results_path="$test_results_dir/${0%.sh}-$$.counts"
@@ -846,6 +365,8 @@ test_done () {
cd "$(dirname "$remove_trash")" &&
rm -rf "$(basename "$remove_trash")"
+ test_at_end_hook_
+
exit 0 ;;
*)
@@ -868,6 +389,12 @@ then
# itself.
TEST_DIRECTORY=$(pwd)
fi
+if test -z "$TEST_OUTPUT_DIRECTORY"
+then
+ # Similarly, override this to store the test-results subdir
+ # elsewhere
+ TEST_OUTPUT_DIRECTORY=$TEST_DIRECTORY
+fi
GIT_BUILD_DIR="$TEST_DIRECTORY"/..
if test -n "$valgrind"
@@ -924,6 +451,8 @@ then
do
make_valgrind_symlink $file
done
+ # special-case the mergetools loadables
+ make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools"
OLDIFS=$IFS
IFS=:
for path in $PATH
@@ -965,6 +494,8 @@ export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM
. "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS
+export PERL_PATH
+
if test -z "$GIT_TEST_CMP"
then
if test -n "$GIT_TEST_CMP_USE_COPIED_CONTEXT"
@@ -1001,7 +532,7 @@ test="trash directory.$(basename "$0" .sh)"
test -n "$root" && test="$root/$test"
case "$test" in
/*) TRASH_DIRECTORY="$test" ;;
- *) TRASH_DIRECTORY="$TEST_DIRECTORY/$test" ;;
+ *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$test" ;;
esac
test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY
rm -fr "$test" || {
@@ -1013,7 +544,11 @@ rm -fr "$test" || {
HOME="$TRASH_DIRECTORY"
export HOME
-test_create_repo "$test"
+if test -z "$TEST_NO_CREATE_REPO"; then
+ test_create_repo "$test"
+else
+ mkdir -p "$test"
+fi
# Use -P to resolve symlinks in our working directory so that the cwd
# in subprocesses like git equals our $PWD (for pathname comparisons).
cd -P "$test" || exit 1
@@ -1082,15 +617,18 @@ case $(uname -s) in
;;
esac
+( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
test -z "$NO_PERL" && test_set_prereq PERL
test -z "$NO_PYTHON" && test_set_prereq PYTHON
test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
+test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
# Can we rely on git's output in the C locale?
if test -n "$GETTEXT_POISON"
then
GIT_GETTEXT_POISON=YesPlease
export GIT_GETTEXT_POISON
+ test_set_prereq GETTEXT_POISON
else
test_set_prereq C_LOCALE_OUTPUT
fi
diff --git a/t/test-terminal.perl b/t/test-terminal.perl
index ee01eb9..10172ae 100755
--- a/t/test-terminal.perl
+++ b/t/test-terminal.perl
@@ -69,6 +69,10 @@ if ($#ARGV < 1) {
}
my $master_out = new IO::Pty;
my $master_err = new IO::Pty;
+$master_out->set_raw();
+$master_err->set_raw();
+$master_out->slave->set_raw();
+$master_err->slave->set_raw();
my $pid = start_child(\@ARGV, $master_out->slave, $master_err->slave);
close $master_out->slave;
close $master_err->slave;
diff --git a/tag.c b/tag.c
index 7d38cc0..78d272b 100644
--- a/tag.c
+++ b/tag.c
@@ -24,6 +24,18 @@ struct object *deref_tag(struct object *o, const char *warn, int warnlen)
return o;
}
+struct object *deref_tag_noverify(struct object *o)
+{
+ while (o && o->type == OBJ_TAG) {
+ o = parse_object(o->sha1);
+ if (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged)
+ o = ((struct tag *)o)->tagged;
+ else
+ o = NULL;
+ }
+ return o;
+}
+
struct tag *lookup_tag(const unsigned char *sha1)
{
struct object *obj = lookup_object(sha1);
@@ -139,6 +151,11 @@ int parse_tag(struct tag *item)
return ret;
}
+/*
+ * Look at a signed tag object, and return the offset where
+ * the embedded detached signature begins, or the end of the
+ * data when there is no such signature.
+ */
size_t parse_signature(const char *buf, unsigned long size)
{
char *eol;
diff --git a/tag.h b/tag.h
index 5ee88e6..bc8a1e4 100644
--- a/tag.h
+++ b/tag.h
@@ -16,6 +16,7 @@ extern struct tag *lookup_tag(const unsigned char *sha1);
extern int parse_tag_buffer(struct tag *item, const void *data, unsigned long size);
extern int parse_tag(struct tag *item);
extern struct object *deref_tag(struct object *, const char *, int);
+extern struct object *deref_tag_noverify(struct object *);
extern size_t parse_signature(const char *buf, unsigned long size);
#endif /* TAG_H */
diff --git a/templates/hooks--post-commit.sample b/templates/hooks--post-commit.sample
deleted file mode 100755
index 2266821..0000000
--- a/templates/hooks--post-commit.sample
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-#
-# An example hook script that is called after a successful
-# commit is made.
-#
-# To enable this hook, rename this file to "post-commit".
-
-: Nothing
diff --git a/templates/hooks--post-receive.sample b/templates/hooks--post-receive.sample
deleted file mode 100755
index 7a83e17..0000000
--- a/templates/hooks--post-receive.sample
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-#
-# An example hook script for the "post-receive" event.
-#
-# The "post-receive" script is run after receive-pack has accepted a pack
-# and the repository has been updated. It is passed arguments in through
-# stdin in the form
-# <oldrev> <newrev> <refname>
-# For example:
-# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
-#
-# see contrib/hooks/ for a sample, or uncomment the next line and
-# rename the file to "post-receive".
-
-#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
diff --git a/test-ctype.c b/test-ctype.c
index 033c749..707a821 100644
--- a/test-ctype.c
+++ b/test-ctype.c
@@ -1,78 +1,42 @@
#include "cache.h"
+static int rc;
-static int test_isdigit(int c)
+static void report_error(const char *class, int ch)
{
- return isdigit(c);
+ printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch);
+ rc = 1;
}
-static int test_isspace(int c)
+static int is_in(const char *s, int ch)
{
- return isspace(c);
+ /* We can't find NUL using strchr. It's classless anyway. */
+ if (ch == '\0')
+ return 0;
+ return !!strchr(s, ch);
}
-static int test_isalpha(int c)
-{
- return isalpha(c);
-}
-
-static int test_isalnum(int c)
-{
- return isalnum(c);
-}
-
-static int test_is_glob_special(int c)
-{
- return is_glob_special(c);
-}
-
-static int test_is_regex_special(int c)
-{
- return is_regex_special(c);
+#define TEST_CLASS(t,s) { \
+ int i; \
+ for (i = 0; i < 256; i++) { \
+ if (is_in(s, i) != t(i)) \
+ report_error(#t, i); \
+ } \
}
#define DIGIT "0123456789"
#define LOWER "abcdefghijklmnopqrstuvwxyz"
#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-static const struct ctype_class {
- const char *name;
- int (*test_fn)(int);
- const char *members;
-} classes[] = {
- { "isdigit", test_isdigit, DIGIT },
- { "isspace", test_isspace, " \n\r\t" },
- { "isalpha", test_isalpha, LOWER UPPER },
- { "isalnum", test_isalnum, LOWER UPPER DIGIT },
- { "is_glob_special", test_is_glob_special, "*?[\\" },
- { "is_regex_special", test_is_regex_special, "$()*+.?[\\^{|" },
- { NULL }
-};
-
-static int test_class(const struct ctype_class *test)
-{
- int i, rc = 0;
-
- for (i = 0; i < 256; i++) {
- int expected = i ? !!strchr(test->members, i) : 0;
- int actual = test->test_fn(i);
-
- if (actual != expected) {
- rc = 1;
- printf("%s classifies char %d (0x%02x) wrongly\n",
- test->name, i, i);
- }
- }
- return rc;
-}
-
int main(int argc, char **argv)
{
- const struct ctype_class *test;
- int rc = 0;
-
- for (test = classes; test->name; test++)
- rc |= test_class(test);
+ TEST_CLASS(isdigit, DIGIT);
+ TEST_CLASS(isspace, " \n\r\t");
+ TEST_CLASS(isalpha, LOWER UPPER);
+ TEST_CLASS(isalnum, LOWER UPPER DIGIT);
+ TEST_CLASS(is_glob_special, "*?[\\");
+ TEST_CLASS(is_regex_special, "$()*+.?[\\^{|");
+ TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
return rc;
}
diff --git a/test-date.c b/test-date.c
index 6bcd5b0..10afaab 100644
--- a/test-date.c
+++ b/test-date.c
@@ -7,13 +7,14 @@ static const char *usage_msg = "\n"
static void show_dates(char **argv, struct timeval *now)
{
- char buf[128];
+ struct strbuf buf = STRBUF_INIT;
for (; *argv; argv++) {
time_t t = atoi(*argv);
- show_date_relative(t, 0, now, buf, sizeof(buf));
- printf("%s -> %s\n", *argv, buf);
+ show_date_relative(t, 0, now, &buf);
+ printf("%s -> %s\n", *argv, buf.buf);
}
+ strbuf_release(&buf);
}
static void parse_dates(char **argv, struct timeval *now)
diff --git a/test-dump-cache-tree.c b/test-dump-cache-tree.c
index 1f73f1e..a6ffdf3 100644
--- a/test-dump-cache-tree.c
+++ b/test-dump-cache-tree.c
@@ -59,6 +59,6 @@ int main(int ac, char **av)
struct cache_tree *another = cache_tree();
if (read_cache() < 0)
die("unable to read index file");
- cache_tree_update(another, active_cache, active_nr, 0, 1);
+ cache_tree_update(another, active_cache, active_nr, WRITE_TREE_DRY_RUN);
return dump_cache_tree(active_cache_tree, another, "");
}
diff --git a/test-mergesort.c b/test-mergesort.c
new file mode 100644
index 0000000..3f388b4
--- /dev/null
+++ b/test-mergesort.c
@@ -0,0 +1,52 @@
+#include "cache.h"
+#include "mergesort.h"
+
+struct line {
+ char *text;
+ struct line *next;
+};
+
+static void *get_next(const void *a)
+{
+ return ((const struct line *)a)->next;
+}
+
+static void set_next(void *a, void *b)
+{
+ ((struct line *)a)->next = b;
+}
+
+static int compare_strings(const void *a, const void *b)
+{
+ const struct line *x = a, *y = b;
+ return strcmp(x->text, y->text);
+}
+
+int main(int argc, const char **argv)
+{
+ struct line *line, *p = NULL, *lines = NULL;
+ struct strbuf sb = STRBUF_INIT;
+
+ for (;;) {
+ if (strbuf_getwholeline(&sb, stdin, '\n'))
+ break;
+ line = xmalloc(sizeof(struct line));
+ line->text = strbuf_detach(&sb, NULL);
+ if (p) {
+ line->next = p->next;
+ p->next = line;
+ } else {
+ line->next = NULL;
+ lines = line;
+ }
+ p = line;
+ }
+
+ lines = llist_mergesort(lines, get_next, set_next, compare_strings);
+
+ while (lines) {
+ printf("%s", lines->text);
+ lines = lines->next;
+ }
+ return 0;
+}
diff --git a/test-obj-pool.c b/test-obj-pool.c
deleted file mode 100644
index 5018863..0000000
--- a/test-obj-pool.c
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * test-obj-pool.c: code to exercise the svn importer's object pool
- */
-
-#include "cache.h"
-#include "vcs-svn/obj_pool.h"
-
-enum pool { POOL_ONE, POOL_TWO };
-obj_pool_gen(one, int, 1)
-obj_pool_gen(two, int, 4096)
-
-static uint32_t strtouint32(const char *s)
-{
- char *end;
- uintmax_t n = strtoumax(s, &end, 10);
- if (*s == '\0' || (*end != '\n' && *end != '\0'))
- die("invalid offset: %s", s);
- return (uint32_t) n;
-}
-
-static void handle_command(const char *command, enum pool pool, const char *arg)
-{
- switch (*command) {
- case 'a':
- if (!prefixcmp(command, "alloc ")) {
- uint32_t n = strtouint32(arg);
- printf("%"PRIu32"\n",
- pool == POOL_ONE ?
- one_alloc(n) : two_alloc(n));
- return;
- }
- case 'c':
- if (!prefixcmp(command, "commit ")) {
- pool == POOL_ONE ? one_commit() : two_commit();
- return;
- }
- if (!prefixcmp(command, "committed ")) {
- printf("%"PRIu32"\n",
- pool == POOL_ONE ?
- one_pool.committed : two_pool.committed);
- return;
- }
- case 'f':
- if (!prefixcmp(command, "free ")) {
- uint32_t n = strtouint32(arg);
- pool == POOL_ONE ? one_free(n) : two_free(n);
- return;
- }
- case 'n':
- if (!prefixcmp(command, "null ")) {
- printf("%"PRIu32"\n",
- pool == POOL_ONE ?
- one_offset(NULL) : two_offset(NULL));
- return;
- }
- case 'o':
- if (!prefixcmp(command, "offset ")) {
- uint32_t n = strtouint32(arg);
- printf("%"PRIu32"\n",
- pool == POOL_ONE ?
- one_offset(one_pointer(n)) :
- two_offset(two_pointer(n)));
- return;
- }
- case 'r':
- if (!prefixcmp(command, "reset ")) {
- pool == POOL_ONE ? one_reset() : two_reset();
- return;
- }
- case 's':
- if (!prefixcmp(command, "set ")) {
- uint32_t n = strtouint32(arg);
- if (pool == POOL_ONE)
- *one_pointer(n) = 1;
- else
- *two_pointer(n) = 1;
- return;
- }
- case 't':
- if (!prefixcmp(command, "test ")) {
- uint32_t n = strtouint32(arg);
- printf("%d\n", pool == POOL_ONE ?
- *one_pointer(n) : *two_pointer(n));
- return;
- }
- default:
- die("unrecognized command: %s", command);
- }
-}
-
-static void handle_line(const char *line)
-{
- const char *arg = strchr(line, ' ');
- enum pool pool;
-
- if (arg && !prefixcmp(arg + 1, "one"))
- pool = POOL_ONE;
- else if (arg && !prefixcmp(arg + 1, "two"))
- pool = POOL_TWO;
- else
- die("no pool specified: %s", line);
-
- handle_command(line, pool, arg + strlen("one "));
-}
-
-int main(int argc, char *argv[])
-{
- struct strbuf sb = STRBUF_INIT;
- if (argc != 1)
- usage("test-obj-str < script");
-
- while (strbuf_getline(&sb, stdin, '\n') != EOF)
- handle_line(sb.buf);
- strbuf_release(&sb);
- return 0;
-}
diff --git a/test-parse-options.c b/test-parse-options.c
index 4e3710b..3c9510a 100644
--- a/test-parse-options.c
+++ b/test-parse-options.c
@@ -1,5 +1,6 @@
#include "cache.h"
#include "parse-options.h"
+#include "string-list.h"
static int boolean = 0;
static int integer = 0;
@@ -9,6 +10,7 @@ static int verbose = 0, dry_run = 0, quiet = 0;
static char *string = NULL;
static char *file = NULL;
static int ambiguous;
+static struct string_list list;
static int length_callback(const struct option *opt, const char *arg, int unset)
{
@@ -35,7 +37,11 @@ int main(int argc, const char **argv)
NULL
};
struct option options[] = {
- OPT_BOOLEAN('b', "boolean", &boolean, "get a boolean"),
+ OPT_BOOL(0, "yes", &boolean, "get a boolean"),
+ OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"),
+ { OPTION_SET_INT, 'B', "no-fear", &boolean, NULL,
+ "be brave", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ OPT_COUNTUP('b', "boolean", &boolean, "increment by one"),
OPT_BIT('4', "or4", &boolean,
"bitwise-or boolean with ...0100", 4),
OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4),
@@ -52,17 +58,19 @@ int main(int argc, const char **argv)
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_NOOP_NOARG(0, "obsolete"),
OPT_SET_PTR(0, "default-string", &string,
"set string to default", (unsigned long)"default"),
+ OPT_STRING_LIST(0, "list", &list, "str", "add str to list"),
OPT_GROUP("Magic arguments"),
OPT_ARGUMENT("quux", "means --quux"),
OPT_NUMBER_CALLBACK(&integer, "set integer to NUM",
number_callback),
- { OPTION_BOOLEAN, '+', NULL, &boolean, NULL, "same as -b",
+ { OPTION_COUNTUP, '+', NULL, &boolean, NULL, "same as -b",
PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH },
- { OPTION_BOOLEAN, 0, "ambiguous", &ambiguous, NULL,
+ { OPTION_COUNTUP, 0, "ambiguous", &ambiguous, NULL,
"positive ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG },
- { OPTION_BOOLEAN, 0, "no-ambiguous", &ambiguous, NULL,
+ { OPTION_COUNTUP, 0, "no-ambiguous", &ambiguous, NULL,
"negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG },
OPT_GROUP("Standard options"),
OPT__ABBREV(&abbrev),
@@ -85,6 +93,9 @@ int main(int argc, const char **argv)
printf("dry run: %s\n", dry_run ? "yes" : "no");
printf("file: %s\n", file ? file : "(not set)");
+ for (i = 0; i < list.nr; i++)
+ printf("list: %s\n", list.items[i].string);
+
for (i = 0; i < argc; i++)
printf("arg %02d: %s\n", i, argv[i]);
diff --git a/test-path-utils.c b/test-path-utils.c
index e767159..3bc20e9 100644
--- a/test-path-utils.c
+++ b/test-path-utils.c
@@ -20,12 +20,34 @@ int main(int argc, char **argv)
return 0;
}
+ if (argc >= 2 && !strcmp(argv[1], "absolute_path")) {
+ while (argc > 2) {
+ puts(absolute_path(argv[2]));
+ argc--;
+ argv++;
+ }
+ return 0;
+ }
+
if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
int len = longest_ancestor_length(argv[2], argv[3]);
printf("%d\n", len);
return 0;
}
+ if (argc >= 4 && !strcmp(argv[1], "prefix_path")) {
+ char *prefix = argv[2];
+ int prefix_len = strlen(prefix);
+ int nongit_ok;
+ setup_git_directory_gently(&nongit_ok);
+ while (argc > 3) {
+ puts(prefix_path(prefix, prefix_len, argv[3]));
+ argc--;
+ argv++;
+ }
+ return 0;
+ }
+
if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) {
char *prefix = strip_path_suffix(argv[2], argv[3]);
printf("%s\n", prefix ? prefix : "(null)");
diff --git a/test-revision-walking.c b/test-revision-walking.c
new file mode 100644
index 0000000..3ade02c
--- /dev/null
+++ b/test-revision-walking.c
@@ -0,0 +1,66 @@
+/*
+ * test-revision-walking.c: test revision walking API.
+ *
+ * (C) 2012 Heiko Voigt <hvoigt@hvoigt.net>
+ *
+ * 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 "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+
+static void print_commit(struct commit *commit)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = DATE_NORMAL;
+ format_commit_message(commit, " %m %s", &sb, &ctx);
+ printf("%s\n", sb.buf);
+ strbuf_release(&sb);
+}
+
+static int run_revision_walk(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ const char *argv[] = {NULL, "--all", NULL};
+ int argc = ARRAY_SIZE(argv) - 1;
+ int got_revision = 0;
+
+ init_revisions(&rev, NULL);
+ setup_revisions(argc, argv, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ while ((commit = get_revision(&rev)) != NULL) {
+ print_commit(commit);
+ got_revision = 1;
+ }
+
+ reset_revision_walk();
+ return got_revision;
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2)
+ return 1;
+
+ if (!strcmp(argv[1], "run-twice")) {
+ printf("1st\n");
+ if (!run_revision_walk())
+ return 1;
+ printf("2nd\n");
+ if (!run_revision_walk())
+ return 1;
+
+ return 0;
+ }
+
+ fprintf(stderr, "check usage\n");
+ return 1;
+}
diff --git a/test-scrap-cache-tree.c b/test-scrap-cache-tree.c
new file mode 100644
index 0000000..4728013
--- /dev/null
+++ b/test-scrap-cache-tree.c
@@ -0,0 +1,17 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+static struct lock_file index_lock;
+
+int main(int ac, char **av)
+{
+ int fd = hold_locked_index(&index_lock, 1);
+ if (read_cache() < 0)
+ die("unable to read index file");
+ active_cache_tree = NULL;
+ if (write_cache(fd, active_cache, active_nr)
+ || commit_lock_file(&index_lock))
+ die("unable to write index file");
+ return 0;
+}
diff --git a/test-string-pool.c b/test-string-pool.c
deleted file mode 100644
index c5782e6..0000000
--- a/test-string-pool.c
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * test-string-pool.c: code to exercise the svn importer's string pool
- */
-
-#include "git-compat-util.h"
-#include "vcs-svn/string_pool.h"
-
-int main(int argc, char *argv[])
-{
- const uint32_t unequal = pool_intern("does not equal");
- const uint32_t equal = pool_intern("equals");
- uint32_t buf[3];
- uint32_t n;
-
- if (argc != 2)
- usage("test-string-pool <string>,<string>");
-
- n = pool_tok_seq(3, buf, ",-", argv[1]);
- if (n >= 3)
- die("too many strings");
- if (n <= 1)
- die("too few strings");
-
- buf[2] = buf[1];
- buf[1] = (buf[0] == buf[2]) ? equal : unequal;
- pool_print_seq(3, buf, ' ', stdout);
- fputc('\n', stdout);
-
- pool_reset();
- return 0;
-}
diff --git a/test-subprocess.c b/test-subprocess.c
index 8926bc5..f2d4c0d 100644
--- a/test-subprocess.c
+++ b/test-subprocess.c
@@ -1,7 +1,7 @@
#include "cache.h"
#include "run-command.h"
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
{
struct child_process cp;
int nogit = 0;
@@ -9,12 +9,12 @@ int main(int argc, char **argv)
setup_git_directory_gently(&nogit);
if (nogit)
die("No git repo found");
- if (!strcmp(argv[1], "--setup-work-tree")) {
+ if (argc > 1 && !strcmp(argv[1], "--setup-work-tree")) {
setup_work_tree();
argv++;
}
memset(&cp, 0, sizeof(cp));
cp.git_cmd = 1;
- cp.argv = (const char **)argv+1;
+ cp.argv = argv + 1;
return run_command(&cp);
}
diff --git a/test-svn-fe.c b/test-svn-fe.c
index b42ba78..332a5f7 100644
--- a/test-svn-fe.c
+++ b/test-svn-fe.c
@@ -4,15 +4,51 @@
#include "git-compat-util.h"
#include "vcs-svn/svndump.h"
+#include "vcs-svn/svndiff.h"
+#include "vcs-svn/sliding_window.h"
+#include "vcs-svn/line_buffer.h"
-int main(int argc, char *argv[])
+static const char test_svnfe_usage[] =
+ "test-svn-fe (<dumpfile> | [-d] <preimage> <delta> <len>)";
+
+static int apply_delta(int argc, char *argv[])
{
- if (argc != 2)
- usage("test-svn-fe <file>");
- if (svndump_init(argv[1]))
+ struct line_buffer preimage = LINE_BUFFER_INIT;
+ struct line_buffer delta = LINE_BUFFER_INIT;
+ struct sliding_view preimage_view = SLIDING_VIEW_INIT(&preimage, -1);
+
+ if (argc != 5)
+ usage(test_svnfe_usage);
+
+ if (buffer_init(&preimage, argv[2]))
+ die_errno("cannot open preimage");
+ if (buffer_init(&delta, argv[3]))
+ die_errno("cannot open delta");
+ if (svndiff0_apply(&delta, (off_t) strtoull(argv[4], NULL, 0),
+ &preimage_view, stdout))
return 1;
- svndump_read(NULL);
- svndump_deinit();
- svndump_reset();
+ if (buffer_deinit(&preimage))
+ die_errno("cannot close preimage");
+ if (buffer_deinit(&delta))
+ die_errno("cannot close delta");
+ buffer_reset(&preimage);
+ strbuf_release(&preimage_view.buf);
+ buffer_reset(&delta);
return 0;
}
+
+int main(int argc, char *argv[])
+{
+ if (argc == 2) {
+ if (svndump_init(argv[1]))
+ return 1;
+ svndump_read(NULL);
+ svndump_deinit();
+ svndump_reset();
+ return 0;
+ }
+
+ if (argc >= 2 && !strcmp(argv[1], "-d"))
+ return apply_delta(argc, argv);
+ usage(test_svnfe_usage);
+}
diff --git a/test-treap.c b/test-treap.c
deleted file mode 100644
index ab8c951..0000000
--- a/test-treap.c
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * test-treap.c: code to exercise the svn importer's treap structure
- */
-
-#include "cache.h"
-#include "vcs-svn/obj_pool.h"
-#include "vcs-svn/trp.h"
-
-struct int_node {
- uintmax_t n;
- struct trp_node children;
-};
-
-obj_pool_gen(node, struct int_node, 3)
-
-static int node_cmp(struct int_node *a, struct int_node *b)
-{
- return (a->n > b->n) - (a->n < b->n);
-}
-
-trp_gen(static, treap_, struct int_node, children, node, node_cmp)
-
-static void strtonode(struct int_node *item, const char *s)
-{
- char *end;
- item->n = strtoumax(s, &end, 10);
- if (*s == '\0' || (*end != '\n' && *end != '\0'))
- die("invalid integer: %s", s);
-}
-
-int main(int argc, char *argv[])
-{
- struct strbuf sb = STRBUF_INIT;
- struct trp_root root = { ~0 };
- uint32_t item;
-
- if (argc != 1)
- usage("test-treap < ints");
-
- while (strbuf_getline(&sb, stdin, '\n') != EOF) {
- struct int_node *node = node_pointer(node_alloc(1));
-
- item = node_offset(node);
- strtonode(node, sb.buf);
- node = treap_insert(&root, node_pointer(item));
- if (node_offset(node) != item)
- die("inserted %"PRIu32" in place of %"PRIu32"",
- node_offset(node), item);
- }
-
- item = node_offset(treap_first(&root));
- while (~item) {
- uint32_t next;
- struct int_node *tmp = node_pointer(node_alloc(1));
-
- tmp->n = node_pointer(item)->n;
- next = node_offset(treap_next(&root, node_pointer(item)));
-
- treap_remove(&root, node_pointer(item));
- item = node_offset(treap_nsearch(&root, tmp));
-
- if (item != next && (!~item || node_pointer(item)->n != tmp->n))
- die("found %"PRIuMAX" in place of %"PRIuMAX"",
- ~item ? node_pointer(item)->n : ~(uintmax_t) 0,
- ~next ? node_pointer(next)->n : ~(uintmax_t) 0);
- printf("%"PRIuMAX"\n", tmp->n);
- }
- node_reset();
- return 0;
-}
diff --git a/transport-helper.c b/transport-helper.c
index 660147f..61c928f 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -9,6 +9,7 @@
#include "remote.h"
#include "string-list.h"
#include "thread-utils.h"
+#include "sigchain.h"
static int debug;
@@ -23,6 +24,8 @@ struct helper_data {
push : 1,
connect : 1,
no_disconnect_req : 1;
+ char *export_marks;
+ char *import_marks;
/* These go from remote name (as in "list") to private name */
struct refspec *refspecs;
int refspec_nr;
@@ -105,6 +108,12 @@ static struct child_process *get_helper(struct transport *transport)
int refspec_alloc = 0;
int duped;
int code;
+ char git_dir_buf[sizeof(GIT_DIR_ENVIRONMENT) + PATH_MAX + 1];
+ const char *helper_env[] = {
+ git_dir_buf,
+ NULL
+ };
+
if (data->helper)
return data->helper;
@@ -120,6 +129,10 @@ static struct child_process *get_helper(struct transport *transport)
helper->argv[2] = remove_ext_force(transport->url);
helper->git_cmd = 0;
helper->silent_exec_failure = 1;
+
+ snprintf(git_dir_buf, sizeof(git_dir_buf), "%s=%s", GIT_DIR_ENVIRONMENT, get_git_dir());
+ helper->env = helper_env;
+
code = start_command(helper);
if (code < 0 && errno == ENOENT)
die("Unable to find remote helper for '%s'", data->name);
@@ -171,17 +184,22 @@ static struct child_process *get_helper(struct transport *transport)
ALLOC_GROW(refspecs,
refspec_nr + 1,
refspec_alloc);
- refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
+ refspecs[refspec_nr++] = xstrdup(capname + strlen("refspec "));
} else if (!strcmp(capname, "connect")) {
data->connect = 1;
- } else if (!strcmp(buf.buf, "gitdir")) {
- struct strbuf gitdir = STRBUF_INIT;
- strbuf_addf(&gitdir, "gitdir %s\n", get_git_dir());
- sendline(data, &gitdir);
- strbuf_release(&gitdir);
+ } else if (!prefixcmp(capname, "export-marks ")) {
+ struct strbuf arg = STRBUF_INIT;
+ strbuf_addstr(&arg, "--export-marks=");
+ strbuf_addstr(&arg, capname + strlen("export-marks "));
+ data->export_marks = strbuf_detach(&arg, NULL);
+ } else if (!prefixcmp(capname, "import-marks")) {
+ struct strbuf arg = STRBUF_INIT;
+ strbuf_addstr(&arg, "--import-marks=");
+ strbuf_addstr(&arg, capname + strlen("import-marks "));
+ data->import_marks = strbuf_detach(&arg, NULL);
} else if (mandatory) {
die("Unknown mandatory capability %s. This remote "
- "helper probably needs newer version of Git.\n",
+ "helper probably needs newer version of Git.",
capname);
}
}
@@ -203,25 +221,32 @@ static struct child_process *get_helper(struct transport *transport)
static int disconnect_helper(struct transport *transport)
{
struct helper_data *data = transport->data;
- struct strbuf buf = STRBUF_INIT;
+ int res = 0;
if (data->helper) {
if (debug)
fprintf(stderr, "Debug: Disconnecting.\n");
if (!data->no_disconnect_req) {
- strbuf_addf(&buf, "\n");
- sendline(data, &buf);
+ /*
+ * Ignore write errors; there's nothing we can do,
+ * since we're about to close the pipe anyway. And the
+ * most likely error is EPIPE due to the helper dying
+ * to report an error itself.
+ */
+ sigchain_push(SIGPIPE, SIG_IGN);
+ xwrite(data->helper->in, "\n", 1);
+ sigchain_pop(SIGPIPE);
}
close(data->helper->in);
close(data->helper->out);
fclose(data->out);
- finish_command(data->helper);
+ res = finish_command(data->helper);
free((char *)data->helper->argv[0]);
free(data->helper->argv);
free(data->helper);
data->helper = NULL;
}
- return 0;
+ return res;
}
static const char *unsupported_options[] = {
@@ -299,12 +324,13 @@ static void standard_options(struct transport *t)
static int release_helper(struct transport *transport)
{
+ int res = 0;
struct helper_data *data = transport->data;
free_refspec(data->refspec_nr, data->refspecs);
data->refspecs = NULL;
- disconnect_helper(transport);
+ res = disconnect_helper(transport);
free(transport->data);
- return 0;
+ return res;
}
static int fetch_with_fetch(struct transport *transport,
@@ -362,10 +388,9 @@ static int get_importer(struct transport *transport, struct child_process *fasti
static int get_exporter(struct transport *transport,
struct child_process *fastexport,
- const char *export_marks,
- const char *import_marks,
struct string_list *revlist_args)
{
+ struct helper_data *data = transport->data;
struct child_process *helper = get_helper(transport);
int argc = 0, i;
memset(fastexport, 0, sizeof(*fastexport));
@@ -373,12 +398,13 @@ static int get_exporter(struct transport *transport,
/* we need to duplicate helper->in because we want to use it after
* fastexport is done with it. */
fastexport->out = dup(helper->in);
- fastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv));
+ fastexport->argv = xcalloc(5 + revlist_args->nr, sizeof(*fastexport->argv));
fastexport->argv[argc++] = "fast-export";
- if (export_marks)
- fastexport->argv[argc++] = export_marks;
- if (import_marks)
- fastexport->argv[argc++] = import_marks;
+ fastexport->argv[argc++] = "--use-done-feature";
+ if (data->export_marks)
+ fastexport->argv[argc++] = data->export_marks;
+ if (data->import_marks)
+ fastexport->argv[argc++] = data->import_marks;
for (i = 0; i < revlist_args->nr; i++)
fastexport->argv[argc++] = revlist_args->items[i].string;
@@ -410,8 +436,11 @@ static int fetch_with_import(struct transport *transport,
sendline(data, &buf);
strbuf_reset(&buf);
}
- disconnect_helper(transport);
- finish_command(&fastimport);
+
+ write_constant(data->helper->in, "\n");
+
+ if (finish_command(&fastimport))
+ die("Error while running fast-import");
free(fastimport.argv);
fastimport.argv = NULL;
@@ -423,9 +452,11 @@ static int fetch_with_import(struct transport *transport,
if (data->refspecs)
private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
else
- private = strdup(posn->name);
- read_ref(private, posn->old_sha1);
- free(private);
+ private = xstrdup(posn->name);
+ if (private) {
+ read_ref(private, posn->old_sha1);
+ free(private);
+ }
}
strbuf_release(&buf);
return 0;
@@ -554,6 +585,88 @@ static int fetch(struct transport *transport,
return -1;
}
+static void push_update_ref_status(struct strbuf *buf,
+ struct ref **ref,
+ struct ref *remote_refs)
+{
+ char *refname, *msg;
+ int status;
+
+ if (!prefixcmp(buf->buf, "ok ")) {
+ status = REF_STATUS_OK;
+ refname = buf->buf + 3;
+ } else if (!prefixcmp(buf->buf, "error ")) {
+ status = REF_STATUS_REMOTE_REJECT;
+ refname = buf->buf + 6;
+ } else
+ die("expected ok/error, helper said '%s'", buf->buf);
+
+ msg = strchr(refname, ' ');
+ if (msg) {
+ struct strbuf msg_buf = STRBUF_INIT;
+ const char *end;
+
+ *msg++ = '\0';
+ if (!unquote_c_style(&msg_buf, msg, &end))
+ msg = strbuf_detach(&msg_buf, NULL);
+ else
+ msg = xstrdup(msg);
+ strbuf_release(&msg_buf);
+
+ if (!strcmp(msg, "no match")) {
+ status = REF_STATUS_NONE;
+ free(msg);
+ msg = NULL;
+ }
+ else if (!strcmp(msg, "up to date")) {
+ status = REF_STATUS_UPTODATE;
+ free(msg);
+ msg = NULL;
+ }
+ else if (!strcmp(msg, "non-fast forward")) {
+ status = REF_STATUS_REJECT_NONFASTFORWARD;
+ free(msg);
+ msg = NULL;
+ }
+ }
+
+ if (*ref)
+ *ref = find_ref_by_name(*ref, refname);
+ if (!*ref)
+ *ref = find_ref_by_name(remote_refs, refname);
+ if (!*ref) {
+ warning("helper reported unexpected status of %s", refname);
+ return;
+ }
+
+ if ((*ref)->status != REF_STATUS_NONE) {
+ /*
+ * Earlier, the ref was marked not to be pushed, so ignore the ref
+ * status reported by the remote helper if the latter is 'no match'.
+ */
+ if (status == REF_STATUS_NONE)
+ return;
+ }
+
+ (*ref)->status = status;
+ (*ref)->remote_status = msg;
+}
+
+static void push_update_refs_status(struct helper_data *data,
+ struct ref *remote_refs)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct ref *ref = remote_refs;
+ for (;;) {
+ recvline(data, &buf);
+ if (!buf.len)
+ break;
+
+ push_update_ref_status(&buf, &ref, remote_refs);
+ }
+ strbuf_release(&buf);
+}
+
static int push_refs_with_push(struct transport *transport,
struct ref *remote_refs, int flags)
{
@@ -608,76 +721,9 @@ static int push_refs_with_push(struct transport *transport,
strbuf_addch(&buf, '\n');
sendline(data, &buf);
-
- ref = remote_refs;
- while (1) {
- char *refname, *msg;
- int status;
-
- recvline(data, &buf);
- if (!buf.len)
- break;
-
- if (!prefixcmp(buf.buf, "ok ")) {
- status = REF_STATUS_OK;
- refname = buf.buf + 3;
- } else if (!prefixcmp(buf.buf, "error ")) {
- status = REF_STATUS_REMOTE_REJECT;
- refname = buf.buf + 6;
- } else
- die("expected ok/error, helper said '%s'\n", buf.buf);
-
- msg = strchr(refname, ' ');
- if (msg) {
- struct strbuf msg_buf = STRBUF_INIT;
- const char *end;
-
- *msg++ = '\0';
- if (!unquote_c_style(&msg_buf, msg, &end))
- msg = strbuf_detach(&msg_buf, NULL);
- else
- msg = xstrdup(msg);
- strbuf_release(&msg_buf);
-
- if (!strcmp(msg, "no match")) {
- status = REF_STATUS_NONE;
- free(msg);
- msg = NULL;
- }
- else if (!strcmp(msg, "up to date")) {
- status = REF_STATUS_UPTODATE;
- free(msg);
- msg = NULL;
- }
- else if (!strcmp(msg, "non-fast forward")) {
- status = REF_STATUS_REJECT_NONFASTFORWARD;
- free(msg);
- msg = NULL;
- }
- }
-
- if (ref)
- ref = find_ref_by_name(ref, refname);
- if (!ref)
- ref = find_ref_by_name(remote_refs, refname);
- if (!ref) {
- warning("helper reported unexpected status of %s", refname);
- continue;
- }
-
- if (ref->status != REF_STATUS_NONE) {
- /*
- * Earlier, the ref was marked not to be pushed, so ignore the ref
- * status reported by the remote helper if the latter is 'no match'.
- */
- if (status == REF_STATUS_NONE)
- continue;
- }
-
- ref->status = status;
- ref->remote_status = msg;
- }
strbuf_release(&buf);
+
+ push_update_refs_status(data, remote_refs);
return 0;
}
@@ -687,7 +733,6 @@ static int push_refs_with_export(struct transport *transport,
struct ref *ref;
struct child_process *helper, exporter;
struct helper_data *data = transport->data;
- char *export_marks = NULL, *import_marks = NULL;
struct string_list revlist_args = STRING_LIST_INIT_NODUP;
struct strbuf buf = STRBUF_INIT;
@@ -695,26 +740,6 @@ static int push_refs_with_export(struct transport *transport,
write_constant(helper->in, "export\n");
- recvline(data, &buf);
- if (debug)
- fprintf(stderr, "Debug: Got export_marks '%s'\n", buf.buf);
- if (buf.len) {
- struct strbuf arg = STRBUF_INIT;
- strbuf_addstr(&arg, "--export-marks=");
- strbuf_addbuf(&arg, &buf);
- export_marks = strbuf_detach(&arg, NULL);
- }
-
- recvline(data, &buf);
- if (debug)
- fprintf(stderr, "Debug: Got import_marks '%s'\n", buf.buf);
- if (buf.len) {
- struct strbuf arg = STRBUF_INIT;
- strbuf_addstr(&arg, "--import-marks=");
- strbuf_addbuf(&arg, &buf);
- import_marks = strbuf_detach(&arg, NULL);
- }
-
strbuf_reset(&buf);
for (ref = remote_refs; ref; ref = ref->next) {
@@ -728,18 +753,23 @@ static int push_refs_with_export(struct transport *transport,
strbuf_addf(&buf, "^%s", private);
string_list_append(&revlist_args, strbuf_detach(&buf, NULL));
}
+ free(private);
+
+ if (ref->deletion) {
+ die("remote-helpers do not support ref deletion");
+ }
- string_list_append(&revlist_args, ref->name);
+ if (ref->peer_ref)
+ string_list_append(&revlist_args, ref->peer_ref->name);
}
- if (get_exporter(transport, &exporter,
- export_marks, import_marks, &revlist_args))
+ if (get_exporter(transport, &exporter, &revlist_args))
die("Couldn't run fast-export");
- data->no_disconnect_req = 1;
- finish_command(&exporter);
- disconnect_helper(transport);
+ if (finish_command(&exporter))
+ die("Error while running fast-export");
+ push_update_refs_status(data, remote_refs);
return 0;
}
diff --git a/transport.c b/transport.c
index c9c8056..1811b50 100644
--- a/transport.c
+++ b/transport.c
@@ -10,6 +10,8 @@
#include "refs.h"
#include "branch.h"
#include "url.h"
+#include "submodule.h"
+#include "string-list.h"
/* rsync support */
@@ -162,7 +164,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
/* Follow symbolic refs (mainly for HEAD). */
localname = ref->peer_ref->name;
remotename = ref->name;
- tmp = resolve_ref(localname, sha, 1, &flag);
+ tmp = resolve_ref_unsafe(localname, sha, 1, &flag);
if (tmp && flag & REF_ISSYMREF &&
!prefixcmp(tmp, "refs/heads/"))
localname = tmp;
@@ -214,7 +216,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
rsync.argv = args;
rsync.stdout_to_stderr = 1;
args[0] = "rsync";
- args[1] = (transport->verbose > 0) ? "-rv" : "-r";
+ args[1] = (transport->verbose > 1) ? "-rv" : "-r";
args[2] = buf.buf;
args[3] = temp_dir.buf;
args[4] = NULL;
@@ -267,7 +269,7 @@ static int fetch_objs_via_rsync(struct transport *transport,
rsync.argv = args;
rsync.stdout_to_stderr = 1;
args[0] = "rsync";
- args[1] = (transport->verbose > 0) ? "-rv" : "-r";
+ args[1] = (transport->verbose > 1) ? "-rv" : "-r";
args[2] = "--ignore-existing";
args[3] = "--exclude";
args[4] = "info";
@@ -350,7 +352,7 @@ static int rsync_transport_push(struct transport *transport,
args[i++] = "-a";
if (flags & TRANSPORT_PUSH_DRY_RUN)
args[i++] = "--dry-run";
- if (transport->verbose > 0)
+ if (transport->verbose > 1)
args[i++] = "-v";
args[i++] = "--ignore-existing";
args[i++] = "--exclude";
@@ -431,7 +433,8 @@ 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);
+ return unbundle(&data->header, data->fd,
+ transport->progress ? BUNDLE_VERBOSE : 0);
}
static int close_bundle(struct transport *transport)
@@ -472,8 +475,12 @@ static int set_git_option(struct git_transport_options *opts,
} else if (!strcmp(name, TRANS_OPT_DEPTH)) {
if (!value)
opts->depth = 0;
- else
- opts->depth = atoi(value);
+ else {
+ char *end;
+ opts->depth = strtol(value, &end, 0);
+ if (*end)
+ die("transport: invalid depth option '%s'", value);
+ }
return 0;
}
return 1;
@@ -500,7 +507,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
struct ref *refs;
connect_setup(transport, for_push, 0);
- get_remote_heads(data->fd[0], &refs, 0, NULL,
+ get_remote_heads(data->fd[0], &refs,
for_push ? REF_NORMAL : 0, &data->extra_have);
data->got_remote_heads = 1;
@@ -525,7 +532,7 @@ static int fetch_refs_via_pack(struct transport *transport,
args.lock_pack = 1;
args.use_thin_pack = data->options.thin;
args.include_tag = data->options.followtags;
- args.verbose = (transport->verbose > 0);
+ args.verbose = (transport->verbose > 1);
args.quiet = (transport->verbose < 0);
args.no_progress = !transport->progress;
args.depth = data->options.depth;
@@ -535,7 +542,7 @@ static int fetch_refs_via_pack(struct transport *transport,
if (!data->got_remote_heads) {
connect_setup(transport, 0, 0);
- get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
+ get_remote_heads(data->fd[0], &refs_tmp, 0, NULL);
data->got_remote_heads = 1;
}
@@ -715,6 +722,10 @@ void transport_print_push_status(const char *dest, struct ref *refs,
{
struct ref *ref;
int n = 0;
+ unsigned char head_sha1[20];
+ char *head;
+
+ head = resolve_refdup("HEAD", head_sha1, 1, NULL);
if (verbose) {
for (ref = refs; ref; ref = ref->next)
@@ -732,8 +743,13 @@ void transport_print_push_status(const char *dest, struct ref *refs,
ref->status != REF_STATUS_UPTODATE &&
ref->status != REF_STATUS_OK)
n += print_one_push_status(ref, dest, n, porcelain);
- if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
- *nonfastforward = 1;
+ if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD &&
+ *nonfastforward != NON_FF_HEAD) {
+ if (!strcmp(head, ref->name))
+ *nonfastforward = NON_FF_HEAD;
+ else
+ *nonfastforward = NON_FF_OTHER;
+ }
}
}
@@ -753,18 +769,10 @@ void transport_verify_remote_names(int nr_heads, const char **heads)
continue;
remote = remote ? (remote + 1) : local;
- 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]);
+ if (check_refname_format(remote,
+ REFNAME_ALLOW_ONELEVEL|REFNAME_REFSPEC_PATTERN))
+ die("remote part of refspec is not a valid name in %s",
+ heads[i]);
}
}
@@ -778,8 +786,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
struct ref *tmp_refs;
connect_setup(transport, 1, 0);
- get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
- NULL);
+ get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL);
data->got_remote_heads = 1;
}
@@ -913,7 +920,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
ret->fetch = fetch_objs_via_rsync;
ret->push = rsync_transport_push;
ret->smart_options = NULL;
- } else if (is_local(url) && is_file(url)) {
+ } else if (is_local(url) && is_file(url) && is_bundle(url, 1)) {
struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
ret->data = data;
ret->get_refs_list = get_refs_from_bundle;
@@ -987,7 +994,7 @@ int transport_set_option(struct transport *transport,
void transport_set_verbosity(struct transport *transport, int verbosity,
int force_progress)
{
- if (verbosity >= 2)
+ if (verbosity >= 1)
transport->verbose = verbosity <= 3 ? verbosity : 3;
if (verbosity < 0)
transport->verbose = -1;
@@ -996,11 +1003,34 @@ void transport_set_verbosity(struct transport *transport, int verbosity,
* Rules used to determine whether to report progress (processing aborts
* when a rule is satisfied):
*
- * 1. Report progress, if force_progress is 1 (ie. --progress).
- * 2. Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
- * 3. Report progress if isatty(2) is 1.
+ * . Report progress, if force_progress is 1 (ie. --progress).
+ * . Don't report progress, if force_progress is 0 (ie. --no-progress).
+ * . Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
+ * . Report progress if isatty(2) is 1.
**/
- transport->progress = force_progress || (verbosity >= 0 && isatty(2));
+ if (force_progress >= 0)
+ transport->progress = !!force_progress;
+ else
+ transport->progress = verbosity >= 0 && isatty(2);
+}
+
+static void die_with_unpushed_submodules(struct string_list *needs_pushing)
+{
+ int i;
+
+ fprintf(stderr, "The following submodule paths contain changes that can\n"
+ "not be found on any remote:\n");
+ for (i = 0; i < needs_pushing->nr; i++)
+ printf(" %s\n", needs_pushing->items[i].string);
+ fprintf(stderr, "\nPlease try\n\n"
+ " git push --recurse-submodules=on-demand\n\n"
+ "or cd to the path and use\n\n"
+ " git push\n\n"
+ "to push them to a remote.\n\n");
+
+ string_list_clear(needs_pushing, 0);
+
+ die("Aborting.");
}
int transport_push(struct transport *transport,
@@ -1031,9 +1061,11 @@ int transport_push(struct transport *transport,
match_flags |= MATCH_REFS_ALL;
if (flags & TRANSPORT_PUSH_MIRROR)
match_flags |= MATCH_REFS_MIRROR;
+ if (flags & TRANSPORT_PUSH_PRUNE)
+ match_flags |= MATCH_REFS_PRUNE;
- if (match_refs(local_refs, &remote_refs,
- refspec_nr, refspec, match_flags)) {
+ if (match_push_refs(local_refs, &remote_refs,
+ refspec_nr, refspec, match_flags)) {
return -1;
}
@@ -1041,6 +1073,29 @@ int transport_push(struct transport *transport,
flags & TRANSPORT_PUSH_MIRROR,
flags & TRANSPORT_PUSH_FORCE);
+ if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
+ struct ref *ref = remote_refs;
+ for (; ref; ref = ref->next)
+ if (!is_null_sha1(ref->new_sha1) &&
+ !push_unpushed_submodules(ref->new_sha1,
+ transport->remote->name))
+ die ("Failed to push all needed submodules!");
+ }
+
+ if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
+ TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) {
+ struct ref *ref = remote_refs;
+ struct string_list needs_pushing;
+
+ memset(&needs_pushing, 0, sizeof(struct string_list));
+ needs_pushing.strdup_strings = 1;
+ for (; ref; ref = ref->next)
+ if (!is_null_sha1(ref->new_sha1) &&
+ find_unpushed_submodules(ref->new_sha1,
+ transport->remote->name, &needs_pushing))
+ die_with_unpushed_submodules(&needs_pushing);
+ }
+
push_ret = transport->push_refs(transport, remote_refs, flags);
err = push_had_errors(remote_refs);
ret = push_ret | err;
@@ -1143,7 +1198,7 @@ int transport_disconnect(struct transport *transport)
}
/*
- * Strip username (and password) from an url and return
+ * Strip username (and password) from a URL and return
* it in a newly allocated string.
*/
char *transport_anonymize_url(const char *url)
diff --git a/transport.h b/transport.h
index 161d724..b866c12 100644
--- a/transport.h
+++ b/transport.h
@@ -101,6 +101,9 @@ struct transport {
#define TRANSPORT_PUSH_MIRROR 8
#define TRANSPORT_PUSH_PORCELAIN 16
#define TRANSPORT_PUSH_SET_UPSTREAM 32
+#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
+#define TRANSPORT_PUSH_PRUNE 128
+#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
@@ -136,6 +139,8 @@ int transport_set_option(struct transport *transport, const char *name,
void transport_set_verbosity(struct transport *transport, int verbosity,
int force_progress);
+#define NON_FF_HEAD 1
+#define NON_FF_OTHER 2
int transport_push(struct transport *connection,
int refspec_nr, const char **refspec, int flags,
int * nonfastforward);
diff --git a/tree-diff.c b/tree-diff.c
index b3cc2e4..28ad6db 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -21,8 +21,8 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
sha1 = tree_entry_extract(t1, &path1, &mode1);
sha2 = tree_entry_extract(t2, &path2, &mode2);
- pathlen1 = tree_entry_len(path1, sha1);
- pathlen2 = tree_entry_len(path2, sha2);
+ pathlen1 = tree_entry_len(&t1->entry);
+ pathlen2 = tree_entry_len(&t2->entry);
cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
if (cmp < 0) {
show_entry(opt, "-", t1, base);
@@ -64,14 +64,14 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
static void show_tree(struct diff_options *opt, const char *prefix,
struct tree_desc *desc, struct strbuf *base)
{
- int match = 0;
+ enum interesting match = entry_not_interesting;
for (; desc->size; update_tree_entry(desc)) {
- if (match != 2) {
+ if (match != all_entries_interesting) {
match = tree_entry_interesting(&desc->entry, base, 0,
&opt->pathspec);
- if (match < 0)
+ if (match == all_entries_not_interesting)
break;
- if (match == 0)
+ if (match == entry_not_interesting)
continue;
}
show_entry(opt, prefix, desc, base);
@@ -85,7 +85,7 @@ static void show_entry(struct diff_options *opt, const char *prefix,
unsigned mode;
const char *path;
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
- int pathlen = tree_entry_len(path, sha1);
+ int pathlen = tree_entry_len(&desc->entry);
int old_baselen = base->len;
strbuf_add(base, path, pathlen);
@@ -114,12 +114,13 @@ static void show_entry(struct diff_options *opt, const char *prefix,
}
static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
- struct diff_options *opt, int *match)
+ struct diff_options *opt,
+ enum interesting *match)
{
while (t->size) {
*match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
if (*match) {
- if (*match < 0)
+ if (*match == all_entries_not_interesting)
t->size = 0;
break;
}
@@ -132,7 +133,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
{
struct strbuf base;
int baselen = strlen(base_str);
- int t1_match = 0, t2_match = 0;
+ enum interesting t1_match = entry_not_interesting;
+ enum interesting t2_match = entry_not_interesting;
/* Enable recursion indefinitely */
opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
@@ -207,6 +209,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = opt->pathspec.raw[0];
diff_opts.break_opt = opt->break_opt;
+ diff_opts.rename_score = opt->rename_score;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
if (diff_setup_done(&diff_opts) < 0)
diff --git a/tree-walk.c b/tree-walk.c
index 33f749e..492c7cd 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -116,7 +116,7 @@ void setup_traverse_info(struct traverse_info *info, const char *base)
char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n)
{
- int len = tree_entry_len(n->path, n->sha1);
+ int len = tree_entry_len(n);
int pathlen = info->pathlen;
path[pathlen + len] = 0;
@@ -126,7 +126,7 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str
break;
path[--pathlen] = '/';
n = &info->name;
- len = tree_entry_len(n->path, n->sha1);
+ len = tree_entry_len(n);
info = info->prev;
pathlen -= len;
}
@@ -253,7 +253,7 @@ static void extended_entry_extract(struct tree_desc_x *t,
* The caller wants "first" from this tree, or nothing.
*/
path = a->path;
- len = tree_entry_len(a->path, a->sha1);
+ len = tree_entry_len(a);
switch (check_entry_match(first, first_len, path, len)) {
case -1:
entry_clear(a);
@@ -271,7 +271,7 @@ static void extended_entry_extract(struct tree_desc_x *t,
while (probe.size) {
entry_extract(&probe, a);
path = a->path;
- len = tree_entry_len(a->path, a->sha1);
+ len = tree_entry_len(a);
switch (check_entry_match(first, first_len, path, len)) {
case -1:
entry_clear(a);
@@ -309,6 +309,18 @@ static void free_extended_entry(struct tree_desc_x *t)
}
}
+static inline int prune_traversal(struct name_entry *e,
+ struct traverse_info *info,
+ struct strbuf *base,
+ int still_interesting)
+{
+ if (!info->pathspec || still_interesting == 2)
+ return 2;
+ if (still_interesting < 0)
+ return still_interesting;
+ return tree_entry_interesting(e, base, 0, info->pathspec);
+}
+
int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
{
int ret = 0;
@@ -316,15 +328,23 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
struct name_entry *entry = xmalloc(n*sizeof(*entry));
int i;
struct tree_desc_x *tx = xcalloc(n, sizeof(*tx));
+ struct strbuf base = STRBUF_INIT;
+ int interesting = 1;
for (i = 0; i < n; i++)
tx[i].d = t[i];
+ if (info->prev) {
+ strbuf_grow(&base, info->pathlen);
+ make_traverse_path(base.buf, info->prev, &info->name);
+ base.buf[info->pathlen-1] = '/';
+ strbuf_setlen(&base, info->pathlen);
+ }
for (;;) {
unsigned long mask, dirmask;
const char *first = NULL;
int first_len = 0;
- struct name_entry *e;
+ struct name_entry *e = NULL;
int len;
for (i = 0; i < n; i++) {
@@ -342,7 +362,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
e = entry + i;
if (!e->path)
continue;
- len = tree_entry_len(e->path, e->sha1);
+ len = tree_entry_len(e);
if (!first) {
first = e->path;
first_len = len;
@@ -361,7 +381,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
/* Cull the ones that are not the earliest */
if (!e->path)
continue;
- len = tree_entry_len(e->path, e->sha1);
+ len = tree_entry_len(e);
if (name_compare(e->path, len, first, first_len))
entry_clear(e);
}
@@ -376,16 +396,22 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
mask |= 1ul << i;
if (S_ISDIR(entry[i].mode))
dirmask |= 1ul << i;
+ e = &entry[i];
}
if (!mask)
break;
- ret = info->fn(n, mask, dirmask, entry, info);
- if (ret < 0) {
- error = ret;
- if (!info->show_all_errors)
- break;
+ interesting = prune_traversal(e, info, &base, interesting);
+ if (interesting < 0)
+ break;
+ if (interesting) {
+ ret = info->fn(n, mask, dirmask, entry, info);
+ if (ret < 0) {
+ error = ret;
+ if (!info->show_all_errors)
+ break;
+ }
+ mask &= ret;
}
- mask &= ret;
ret = 0;
for (i = 0; i < n; i++)
if (mask & (1ul << i))
@@ -395,6 +421,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
for (i = 0; i < n; i++)
free_extended_entry(tx + i);
free(tx);
+ strbuf_release(&base);
return error;
}
@@ -407,8 +434,8 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char
int entrylen, cmp;
sha1 = tree_entry_extract(t, &entry, mode);
+ entrylen = tree_entry_len(&t->entry);
update_tree_entry(t);
- entrylen = tree_entry_len(entry, sha1);
if (entrylen > namelen)
continue;
cmp = memcmp(name, entry, entrylen);
@@ -438,7 +465,6 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
int retval;
void *tree;
unsigned long size;
- struct tree_desc t;
unsigned char root[20];
tree = read_object_with_reference(tree_sha1, tree_type, &size, root);
@@ -451,8 +477,13 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
return 0;
}
- init_tree_desc(&t, tree, size);
- retval = find_tree_entry(&t, name, sha1, mode);
+ if (!size) {
+ retval = -1;
+ } else {
+ struct tree_desc t;
+ init_tree_desc(&t, tree, size);
+ retval = find_tree_entry(&t, name, sha1, mode);
+ }
free(tree);
return retval;
}
@@ -522,7 +553,7 @@ static int match_entry(const struct name_entry *entry, int pathlen,
return 0;
}
-static int match_dir_prefix(const char *base, int baselen,
+static int match_dir_prefix(const char *base,
const char *match, int matchlen)
{
if (strncmp(base, match, matchlen))
@@ -546,30 +577,26 @@ static int match_dir_prefix(const char *base, int baselen,
*
* Pre-condition: either baselen == base_offset (i.e. empty path)
* or base[baselen-1] == '/' (i.e. with trailing slash).
- *
- * Return:
- * - 2 for "yes, and all subsequent entries will be"
- * - 1 for yes
- * - zero for no
- * - negative for "no, and no subsequent entries will be either"
*/
-int tree_entry_interesting(const struct name_entry *entry,
- struct strbuf *base, int base_offset,
- const struct pathspec *ps)
+enum interesting tree_entry_interesting(const struct name_entry *entry,
+ struct strbuf *base, int base_offset,
+ const struct pathspec *ps)
{
int i;
int pathlen, baselen = base->len - base_offset;
- int never_interesting = ps->has_wildcard ? 0 : -1;
+ int never_interesting = ps->has_wildcard ?
+ entry_not_interesting : all_entries_not_interesting;
if (!ps->nr) {
if (!ps->recursive || ps->max_depth == -1)
- return 2;
- return !!within_depth(base->buf + base_offset, baselen,
- !!S_ISDIR(entry->mode),
- ps->max_depth);
+ return all_entries_interesting;
+ return within_depth(base->buf + base_offset, baselen,
+ !!S_ISDIR(entry->mode),
+ ps->max_depth) ?
+ entry_interesting : entry_not_interesting;
}
- pathlen = tree_entry_len(entry->path, entry->sha1);
+ pathlen = tree_entry_len(entry);
for (i = ps->nr - 1; i >= 0; i--) {
const struct pathspec_item *item = ps->items+i;
@@ -579,42 +606,43 @@ int tree_entry_interesting(const struct name_entry *entry,
if (baselen >= matchlen) {
/* If it doesn't match, move along... */
- if (!match_dir_prefix(base_str, baselen, match, matchlen))
+ if (!match_dir_prefix(base_str, match, matchlen))
goto match_wildcards;
if (!ps->recursive || ps->max_depth == -1)
- return 2;
+ return all_entries_interesting;
- return !!within_depth(base_str + matchlen + 1,
- baselen - matchlen - 1,
- !!S_ISDIR(entry->mode),
- ps->max_depth);
+ return within_depth(base_str + matchlen + 1,
+ baselen - matchlen - 1,
+ !!S_ISDIR(entry->mode),
+ ps->max_depth) ?
+ entry_interesting : entry_not_interesting;
}
- /* Does the base match? */
- if (!strncmp(base_str, match, baselen)) {
+ /* Either there must be no base, or the base must match. */
+ if (baselen == 0 || !strncmp(base_str, match, baselen)) {
if (match_entry(entry, pathlen,
match + baselen, matchlen - baselen,
&never_interesting))
- return 1;
+ return entry_interesting;
- if (ps->items[i].use_wildcard) {
+ if (item->use_wildcard) {
if (!fnmatch(match + baselen, entry->path, 0))
- return 1;
+ return entry_interesting;
/*
* Match all directories. We'll try to
* match files later on.
*/
if (ps->recursive && S_ISDIR(entry->mode))
- return 1;
+ return entry_interesting;
}
continue;
}
match_wildcards:
- if (!ps->items[i].use_wildcard)
+ if (!item->use_wildcard)
continue;
/*
@@ -626,16 +654,19 @@ match_wildcards:
if (!fnmatch(match, base->buf + base_offset, 0)) {
strbuf_setlen(base, base_offset + baselen);
- return 1;
+ return entry_interesting;
}
strbuf_setlen(base, base_offset + baselen);
/*
* Match all directories. We'll try to match files
* later on.
+ * max_depth is ignored but we may consider support it
+ * in future, see
+ * http://thread.gmane.org/gmane.comp.version-control.git/163757/focus=163840
*/
if (ps->recursive && S_ISDIR(entry->mode))
- return 1;
+ return entry_interesting;
}
return never_interesting; /* No matches */
}
diff --git a/tree-walk.h b/tree-walk.h
index 39524b7..2bf0db9 100644
--- a/tree-walk.h
+++ b/tree-walk.h
@@ -20,9 +20,9 @@ static inline const unsigned char *tree_entry_extract(struct tree_desc *desc, co
return desc->entry.sha1;
}
-static inline int tree_entry_len(const char *name, const unsigned char *sha1)
+static inline int tree_entry_len(const struct name_entry *ne)
{
- return (const char *)sha1 - name - 1;
+ return (const char *)ne->sha1 - ne->path - 1;
}
void update_tree_entry(struct tree_desc *);
@@ -44,6 +44,7 @@ struct traverse_info {
struct traverse_info *prev;
struct name_entry name;
int pathlen;
+ struct pathspec *pathspec;
unsigned long conflicts;
traverse_callback_t fn;
@@ -57,9 +58,19 @@ extern void setup_traverse_info(struct traverse_info *info, const char *base);
static inline int traverse_path_len(const struct traverse_info *info, const struct name_entry *n)
{
- return info->pathlen + tree_entry_len(n->path, n->sha1);
+ return info->pathlen + tree_entry_len(n);
}
-extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps);
+/* in general, positive means "kind of interesting" */
+enum interesting {
+ all_entries_not_interesting = -1, /* no, and no subsequent entries will be either */
+ entry_not_interesting = 0,
+ entry_interesting = 1,
+ all_entries_interesting = 2 /* yes, and all subsequent entries will be */
+};
+
+extern enum interesting tree_entry_interesting(const struct name_entry *,
+ struct strbuf *, int,
+ const struct pathspec *ps);
#endif
diff --git a/tree.c b/tree.c
index 698ecf7..676e9f7 100644
--- a/tree.c
+++ b/tree.c
@@ -52,7 +52,8 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
struct tree_desc desc;
struct name_entry entry;
unsigned char sha1[20];
- int len, retval = 0, oldlen = base->len;
+ int len, oldlen = base->len;
+ enum interesting retval = entry_not_interesting;
if (parse_tree(tree))
return -1;
@@ -60,11 +61,11 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
init_tree_desc(&desc, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
- if (retval != 2) {
+ if (retval != all_entries_interesting) {
retval = tree_entry_interesting(&entry, base, 0, pathspec);
- if (retval < 0)
+ if (retval == all_entries_not_interesting)
break;
- if (retval == 0)
+ if (retval == entry_not_interesting)
continue;
}
@@ -99,7 +100,7 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
else
continue;
- len = tree_entry_len(entry.path, entry.sha1);
+ len = tree_entry_len(&entry);
strbuf_add(base, entry.path, len);
strbuf_addch(base, '/');
retval = read_tree_1(lookup_tree(sha1),
diff --git a/unix-socket.c b/unix-socket.c
new file mode 100644
index 0000000..01f119f
--- /dev/null
+++ b/unix-socket.c
@@ -0,0 +1,122 @@
+#include "cache.h"
+#include "unix-socket.h"
+
+static int unix_stream_socket(void)
+{
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ die_errno("unable to create socket");
+ return fd;
+}
+
+static int chdir_len(const char *orig, int len)
+{
+ char *path = xmemdupz(orig, len);
+ int r = chdir(path);
+ free(path);
+ return r;
+}
+
+struct unix_sockaddr_context {
+ char orig_dir[PATH_MAX];
+};
+
+static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
+{
+ if (!ctx->orig_dir[0])
+ return;
+ /*
+ * If we fail, we can't just return an error, since we have
+ * moved the cwd of the whole process, which could confuse calling
+ * code. We are better off to just die.
+ */
+ if (chdir(ctx->orig_dir) < 0)
+ die("unable to restore original working directory");
+}
+
+static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
+ struct unix_sockaddr_context *ctx)
+{
+ int size = strlen(path) + 1;
+
+ ctx->orig_dir[0] = '\0';
+ if (size > sizeof(sa->sun_path)) {
+ const char *slash = find_last_dir_sep(path);
+ const char *dir;
+
+ if (!slash) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ dir = path;
+ path = slash + 1;
+ size = strlen(path) + 1;
+ if (size > sizeof(sa->sun_path)) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ if (!getcwd(ctx->orig_dir, sizeof(ctx->orig_dir))) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if (chdir_len(dir, slash - dir) < 0)
+ return -1;
+ }
+
+ memset(sa, 0, sizeof(*sa));
+ sa->sun_family = AF_UNIX;
+ memcpy(sa->sun_path, path, size);
+ return 0;
+}
+
+int unix_stream_connect(const char *path)
+{
+ int fd, saved_errno;
+ struct sockaddr_un sa;
+ struct unix_sockaddr_context ctx;
+
+ if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+ return -1;
+ fd = unix_stream_socket();
+ if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
+ goto fail;
+ unix_sockaddr_cleanup(&ctx);
+ return fd;
+
+fail:
+ saved_errno = errno;
+ unix_sockaddr_cleanup(&ctx);
+ close(fd);
+ errno = saved_errno;
+ return -1;
+}
+
+int unix_stream_listen(const char *path)
+{
+ int fd, saved_errno;
+ struct sockaddr_un sa;
+ struct unix_sockaddr_context ctx;
+
+ if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+ return -1;
+ fd = unix_stream_socket();
+
+ unlink(path);
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
+ goto fail;
+
+ if (listen(fd, 5) < 0)
+ goto fail;
+
+ unix_sockaddr_cleanup(&ctx);
+ return fd;
+
+fail:
+ saved_errno = errno;
+ unix_sockaddr_cleanup(&ctx);
+ close(fd);
+ errno = saved_errno;
+ return -1;
+}
diff --git a/unix-socket.h b/unix-socket.h
new file mode 100644
index 0000000..e271aee
--- /dev/null
+++ b/unix-socket.h
@@ -0,0 +1,7 @@
+#ifndef UNIX_SOCKET_H
+#define UNIX_SOCKET_H
+
+int unix_stream_connect(const char *path);
+int unix_stream_listen(const char *path);
+
+#endif /* UNIX_SOCKET_H */
diff --git a/unpack-trees.c b/unpack-trees.c
index c3d3436..33a5819 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -102,21 +102,28 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
opts->unpack_rejects[i].strdup_strings = 1;
}
-static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
- unsigned int set, unsigned int clear)
+static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
+ unsigned int set, unsigned int clear)
{
- unsigned int size = ce_size(ce);
- struct cache_entry *new = xmalloc(size);
-
clear |= CE_HASHED | CE_UNHASHED;
if (set & CE_REMOVE)
set |= CE_WT_REMOVE;
+ ce->next = NULL;
+ ce->ce_flags = (ce->ce_flags & ~clear) | set;
+ add_index_entry(&o->result, ce,
+ ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+}
+
+static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
+ unsigned int set, unsigned int clear)
+{
+ unsigned int size = ce_size(ce);
+ struct cache_entry *new = xmalloc(size);
+
memcpy(new, ce, size);
- new->next = NULL;
- new->ce_flags = (new->ce_flags & ~clear) | set;
- add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
+ do_add_entry(o, new, set, clear);
}
/*
@@ -444,8 +451,9 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
newinfo = *info;
newinfo.prev = info;
+ newinfo.pathspec = info->pathspec;
newinfo.name = *p;
- newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1;
+ newinfo.pathlen += tree_entry_len(p) + 1;
newinfo.conflicts |= df_conflicts;
for (i = 0; i < n; i++, dirmask >>= 1) {
@@ -494,7 +502,7 @@ static int do_compare_entry(const struct cache_entry *ce, const struct traverse_
ce_len -= pathlen;
ce_name = ce->name + pathlen;
- len = tree_entry_len(n->path, n->sha1);
+ len = tree_entry_len(n);
return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode);
}
@@ -586,7 +594,7 @@ static int unpack_nondirectories(int n, unsigned long mask,
for (i = 0; i < n; i++)
if (src[i] && src[i] != o->df_conflict_entry)
- add_entry(o, src[i], 0, 0);
+ do_add_entry(o, src[i], 0, 0);
return 0;
}
@@ -625,7 +633,7 @@ static int find_cache_pos(struct traverse_info *info,
struct unpack_trees_options *o = info->data;
struct index_state *index = o->src_index;
int pfxlen = info->pathlen;
- int p_len = tree_entry_len(p->path, p->sha1);
+ int p_len = tree_entry_len(p);
for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
struct cache_entry *ce = index->cache[pos];
@@ -771,7 +779,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
return -1;
- if (src[0]) {
+ if (o->merge && src[0]) {
if (ce_stage(src[0]))
mark_ce_used_same_name(src[0], o);
else
@@ -1015,10 +1023,15 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->el = &el;
}
+ if (o->dir) {
+ o->path_exclude_check = xmalloc(sizeof(struct path_exclude_check));
+ path_exclude_check_init(o->path_exclude_check, o->dir);
+ }
memset(&o->result, 0, sizeof(o->result));
o->result.initialized = 1;
o->result.timestamp.sec = o->src_index->timestamp.sec;
o->result.timestamp.nsec = o->src_index->timestamp.nsec;
+ o->result.version = o->src_index->version;
o->merge_size = len;
mark_all_ce_unused(o->src_index);
@@ -1040,6 +1053,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
info.fn = unpack_callback;
info.data = o;
info.show_all_errors = o->show_all_errors;
+ info.pathspec = o->pathspec;
if (o->prefix) {
/*
@@ -1089,6 +1103,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
*/
mark_new_skip_worktree(o->el, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
+ ret = 0;
for (i = 0; i < o->result.cache_nr; i++) {
struct cache_entry *ce = o->result.cache[i];
@@ -1101,19 +1116,30 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
* correct CE_NEW_SKIP_WORKTREE
*/
if (ce->ce_flags & CE_ADDED &&
- verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
- return -1;
+ verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+ if (!o->show_all_errors)
+ goto return_failed;
+ ret = -1;
+ }
if (apply_sparse_checkout(ce, o)) {
+ if (!o->show_all_errors)
+ goto return_failed;
ret = -1;
- goto done;
}
if (!ce_skip_worktree(ce))
empty_worktree = 0;
}
+ if (ret < 0)
+ goto return_failed;
+ /*
+ * Sparse checkout is meant to narrow down checkout area
+ * but it does not make sense to narrow down to empty working
+ * tree. This is usually a mistake in sparse checkout rules.
+ * Do not allow users to do that.
+ */
if (o->result.cache_nr && empty_worktree) {
- /* dubious---why should this fail??? */
ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
goto done;
}
@@ -1126,6 +1152,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
done:
free_excludes(&el);
+ if (o->path_exclude_check) {
+ path_exclude_check_clear(o->path_exclude_check);
+ free(o->path_exclude_check);
+ }
return ret;
return_failed:
@@ -1188,7 +1218,7 @@ static int verify_uptodate_1(struct cache_entry *ce,
return 0;
/*
* NEEDSWORK: the current default policy is to allow
- * submodule to be out of sync wrt the supermodule
+ * submodule to be out of sync wrt the superproject
* index. This needs to be tightened later for
* submodules that are marked to be automatically
* checked out.
@@ -1341,7 +1371,8 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
if (ignore_case && icase_exists(o, name, len, st))
return 0;
- if (o->dir && excluded(o->dir, name, &dtype))
+ if (o->dir &&
+ path_excluded(o->path_exclude_check, name, -1, &dtype))
/*
* ce->name is explicitly excluded, so it is Ok to
* overwrite it.
@@ -1771,7 +1802,7 @@ int bind_merge(struct cache_entry **src,
struct cache_entry *a = src[1];
if (o->merge_size != 1)
- return error("Cannot do a bind merge of %d trees\n",
+ return error("Cannot do a bind merge of %d trees",
o->merge_size);
if (a && old)
return o->gently ? -1 :
diff --git a/unpack-trees.h b/unpack-trees.h
index 7998948..ec74a9f 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -52,6 +52,8 @@ struct unpack_trees_options {
const char *prefix;
int cache_bottom;
struct dir_struct *dir;
+ struct path_exclude_check *path_exclude_check;
+ struct pathspec *pathspec;
merge_fn_t fn;
const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
/*
diff --git a/upload-pack.c b/upload-pack.c
index 03adf28..bb08e2e 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -84,22 +84,11 @@ static void show_commit(struct commit *commit, void *data)
commit->buffer = NULL;
}
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
+static void show_object(struct object *obj,
+ const struct name_path *path, const char *component,
+ void *cb_data)
{
- /* An object with name "foo\n0000000..." can be used to
- * confuse downstream git-pack-objects very badly.
- */
- const char *name = path_name(path, component);
- const char *ep = strchr(name, '\n');
- if (ep) {
- fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(obj->sha1),
- (int) (ep - name),
- name);
- }
- else
- fprintf(pack_pipe, "%s %s\n",
- sha1_to_hex(obj->sha1), name);
- free((char *)name);
+ show_object_with_name(pack_pipe, obj, path, component);
}
static void show_edge(struct commit *commit)
@@ -596,6 +585,7 @@ static void receive_needs(void)
write_str_in_full(debug_fd, "#S\n");
for (;;) {
struct object *o;
+ const char *features;
unsigned char sha1_buf[20];
len = packet_read_line(0, line, sizeof(line));
reset_timeout();
@@ -627,23 +617,26 @@ static void receive_needs(void)
get_sha1_hex(line+5, sha1_buf))
die("git upload-pack: protocol error, "
"expected to get sha, not '%s'", line);
- if (strstr(line+45, "multi_ack_detailed"))
+
+ features = line + 45;
+
+ if (parse_feature_request(features, "multi_ack_detailed"))
multi_ack = 2;
- else if (strstr(line+45, "multi_ack"))
+ else if (parse_feature_request(features, "multi_ack"))
multi_ack = 1;
- if (strstr(line+45, "no-done"))
+ if (parse_feature_request(features, "no-done"))
no_done = 1;
- if (strstr(line+45, "thin-pack"))
+ if (parse_feature_request(features, "thin-pack"))
use_thin_pack = 1;
- if (strstr(line+45, "ofs-delta"))
+ if (parse_feature_request(features, "ofs-delta"))
use_ofs_delta = 1;
- if (strstr(line+45, "side-band-64k"))
+ if (parse_feature_request(features, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
- else if (strstr(line+45, "side-band"))
+ else if (parse_feature_request(features, "side-band"))
use_sideband = DEFAULT_PACKET_MAX;
- if (strstr(line+45, "no-progress"))
+ if (parse_feature_request(features, "no-progress"))
no_progress = 1;
- if (strstr(line+45, "include-tag"))
+ if (parse_feature_request(features, "include-tag"))
use_include_tag = 1;
o = lookup_object(sha1_buf);
@@ -731,26 +724,30 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
static const char *capabilities = "multi_ack thin-pack side-band"
" side-band-64k ofs-delta shallow no-progress"
" include-tag multi_ack_detailed";
- struct object *o = parse_object(sha1);
+ struct object *o = lookup_unknown_object(sha1);
+ const char *refname_nons = strip_namespace(refname);
- if (!o)
- die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+ if (o->type == OBJ_NONE) {
+ o->type = sha1_object_info(sha1, NULL);
+ if (o->type < 0)
+ die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+ }
if (capabilities)
- packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname,
+ packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname_nons,
0, capabilities,
stateless_rpc ? " no-done" : "");
else
- packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+ packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
capabilities = NULL;
if (!(o->flags & OUR_REF)) {
o->flags |= OUR_REF;
nr_our_refs++;
}
if (o->type == OBJ_TAG) {
- o = deref_tag(o, refname, 0);
+ o = deref_tag_noverify(o);
if (o)
- packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+ packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname_nons);
}
return 0;
}
@@ -771,12 +768,12 @@ static void upload_pack(void)
{
if (advertise_refs || !stateless_rpc) {
reset_timeout();
- head_ref(send_ref, NULL);
- for_each_ref(send_ref, NULL);
+ head_ref_namespaced(send_ref, NULL);
+ for_each_namespaced_ref(send_ref, NULL);
packet_flush(1);
} else {
- head_ref(mark_our_ref, NULL);
- for_each_ref(mark_our_ref, NULL);
+ head_ref_namespaced(mark_our_ref, NULL);
+ for_each_namespaced_ref(mark_our_ref, NULL);
}
if (advertise_refs)
return;
@@ -794,6 +791,8 @@ int main(int argc, char **argv)
int i;
int strict = 0;
+ git_setup_gettext();
+
packet_trace_identity("upload-pack");
git_extract_argv0_path(argv[0]);
read_replace_refs = 0;
diff --git a/url.c b/url.c
index 3e06fd3..335d97d 100644
--- a/url.c
+++ b/url.c
@@ -18,35 +18,15 @@ int is_urlschemechar(int first_flag, int ch)
int is_url(const char *url)
{
- const char *url2, *first_slash;
-
- if (!url)
- return 0;
- url2 = url;
- first_slash = strchr(url, '/');
-
- /* Input with no slash at all or slash first can't be URL. */
- if (!first_slash || first_slash == url)
+ /* Is "scheme" part reasonable? */
+ if (!url || !is_urlschemechar(1, *url++))
return 0;
- /* Character before must be : and next must be /. */
- if (first_slash[-1] != ':' || first_slash[1] != '/')
- return 0;
- /* There must be something before the :// */
- if (first_slash == url + 1)
- return 0;
- /*
- * Check all characters up to first slash - 1. Only alphanum
- * is allowed.
- */
- url2 = url;
- while (url2 < first_slash - 1) {
- if (!is_urlschemechar(url2 == url, (unsigned char)*url2))
+ while (*url && *url != ':') {
+ if (!is_urlschemechar(0, *url++))
return 0;
- url2++;
}
-
- /* Valid enough. */
- return 1;
+ /* We've seen "scheme"; we want colon-slash-slash */
+ return (url[0] == ':' && url[1] == '/' && url[2] == '/');
}
static int url_decode_char(const char *q)
@@ -68,18 +48,20 @@ static int url_decode_char(const char *q)
return val;
}
-static char *url_decode_internal(const char **query, const char *stop_at,
- struct strbuf *out, int decode_plus)
+static char *url_decode_internal(const char **query, int len,
+ const char *stop_at, struct strbuf *out,
+ int decode_plus)
{
const char *q = *query;
- do {
+ while (len) {
unsigned char c = *q;
if (!c)
break;
if (stop_at && strchr(stop_at, c)) {
q++;
+ len--;
break;
}
@@ -88,6 +70,7 @@ static char *url_decode_internal(const char **query, const char *stop_at,
if (0 <= val) {
strbuf_addch(out, val);
q += 3;
+ len -= 3;
continue;
}
}
@@ -97,34 +80,41 @@ static char *url_decode_internal(const char **query, const char *stop_at,
else
strbuf_addch(out, c);
q++;
- } while (1);
+ len--;
+ }
*query = q;
return strbuf_detach(out, NULL);
}
char *url_decode(const char *url)
{
+ return url_decode_mem(url, strlen(url));
+}
+
+char *url_decode_mem(const char *url, int len)
+{
struct strbuf out = STRBUF_INIT;
- const char *colon = strchr(url, ':');
+ const char *colon = memchr(url, ':', len);
/* Skip protocol part if present */
if (colon && url < colon) {
strbuf_add(&out, url, colon - url);
+ len -= colon - url;
url = colon;
}
- return url_decode_internal(&url, NULL, &out, 0);
+ return url_decode_internal(&url, len, NULL, &out, 0);
}
char *url_decode_parameter_name(const char **query)
{
struct strbuf out = STRBUF_INIT;
- return url_decode_internal(query, "&=", &out, 1);
+ return url_decode_internal(query, -1, "&=", &out, 1);
}
char *url_decode_parameter_value(const char **query)
{
struct strbuf out = STRBUF_INIT;
- return url_decode_internal(query, "&", &out, 1);
+ return url_decode_internal(query, -1, "&", &out, 1);
}
void end_url_with_slash(struct strbuf *buf, const char *url)
diff --git a/url.h b/url.h
index 7100e32..abdaf6f 100644
--- a/url.h
+++ b/url.h
@@ -4,6 +4,7 @@
extern int is_url(const char *url);
extern int is_urlschemechar(int first_flag, int ch);
extern char *url_decode(const char *url);
+extern char *url_decode_mem(const char *url, int len);
extern char *url_decode_parameter_name(const char **query);
extern char *url_decode_parameter_value(const char **query);
diff --git a/userdiff.c b/userdiff.c
index 01d3a8b..1e7184f 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -37,6 +37,9 @@ PATTERNS("java",
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
"|[-+*/<>%&^|=!]="
"|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"),
+PATTERNS("matlab",
+ "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$",
+ "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"),
PATTERNS("objc",
/* Negate C statements that can look like functions */
"!^[ \t]*(do|for|if|else|return|switch|while)\n"
@@ -115,7 +118,7 @@ PATTERNS("cpp",
/* Jump targets or access declarations */
"!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
/* C/++ functions/methods at top level */
- "^([A-Za-z_][A-Za-z_0-9]*([ \t]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
+ "^([A-Za-z_][A-Za-z_0-9]*([ \t*]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
/* compound type at top level */
"^((struct|class|enum)[^;]*)$",
/* -- */
@@ -207,14 +210,7 @@ static int parse_funcname(struct userdiff_funcname *f, const char *k,
if (git_config_string(&f->pattern, k, v) < 0)
return -1;
f->cflags = cflags;
- return 1;
-}
-
-static int parse_string(const char **d, const char *k, const char *v)
-{
- if (git_config_string(d, k, v) < 0)
- return -1;
- return 1;
+ return 0;
}
static int parse_tristate(int *b, const char *k, const char *v)
@@ -223,13 +219,13 @@ static int parse_tristate(int *b, const char *k, const char *v)
*b = -1;
else
*b = git_config_bool(k, v);
- return 1;
+ return 0;
}
static int parse_bool(int *b, const char *k, const char *v)
{
*b = git_config_bool(k, v);
- return 1;
+ return 0;
}
int userdiff_config(const char *k, const char *v)
@@ -243,13 +239,13 @@ int userdiff_config(const char *k, const char *v)
if ((drv = parse_driver(k, v, "binary")))
return parse_tristate(&drv->binary, k, v);
if ((drv = parse_driver(k, v, "command")))
- return parse_string(&drv->external, k, v);
+ return git_config_string(&drv->external, k, v);
if ((drv = parse_driver(k, v, "textconv")))
- return parse_string(&drv->textconv, k, v);
+ return git_config_string(&drv->textconv, k, v);
if ((drv = parse_driver(k, v, "cachetextconv")))
return parse_bool(&drv->textconv_want_cache, k, v);
if ((drv = parse_driver(k, v, "wordregex")))
- return parse_string(&drv->word_regex, k, v);
+ return git_config_string(&drv->word_regex, k, v);
return 0;
}
@@ -270,7 +266,7 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)
if (!path)
return NULL;
- if (git_checkattr(path, 1, &check))
+ if (git_check_attr(path, 1, &check))
return NULL;
if (ATTR_TRUE(check.value))
diff --git a/varint.c b/varint.c
new file mode 100644
index 0000000..4ed7729
--- /dev/null
+++ b/varint.c
@@ -0,0 +1,29 @@
+#include "varint.h"
+
+uintmax_t decode_varint(const unsigned char **bufp)
+{
+ const unsigned char *buf = *bufp;
+ unsigned char c = *buf++;
+ uintmax_t val = c & 127;
+ while (c & 128) {
+ val += 1;
+ if (!val || MSB(val, 7))
+ return 0; /* overflow */
+ c = *buf++;
+ val = (val << 7) + (c & 127);
+ }
+ *bufp = buf;
+ return val;
+}
+
+int encode_varint(uintmax_t value, unsigned char *buf)
+{
+ unsigned char varint[16];
+ unsigned pos = sizeof(varint) - 1;
+ varint[pos] = value & 127;
+ while (value >>= 7)
+ varint[--pos] = 128 | (--value & 127);
+ if (buf)
+ memcpy(buf, varint + pos, sizeof(varint) - pos);
+ return sizeof(varint) - pos;
+}
diff --git a/varint.h b/varint.h
new file mode 100644
index 0000000..0321195
--- /dev/null
+++ b/varint.h
@@ -0,0 +1,9 @@
+#ifndef VARINT_H
+#define VARINT_H
+
+#include "git-compat-util.h"
+
+extern int encode_varint(uintmax_t, unsigned char *);
+extern uintmax_t decode_varint(const unsigned char **);
+
+#endif /* VARINT_H */
diff --git a/vcs-svn/LICENSE b/vcs-svn/LICENSE
index 0a5e3c4..eb91858 100644
--- a/vcs-svn/LICENSE
+++ b/vcs-svn/LICENSE
@@ -1,8 +1,7 @@
Copyright (C) 2010 David Barr <david.barr@cordelta.com>.
All rights reserved.
-Copyright (C) 2008 Jason Evans <jasone@canonware.com>.
-All rights reserved.
+Copyright (C) 2010 Jonathan Nieder <jrnieder@gmail.com>.
Copyright (C) 2005 Stefan Hegny, hydrografix Consulting GmbH,
Frankfurt/Main, Germany
diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c
index 99ed70b..b823b85 100644
--- a/vcs-svn/fast_export.c
+++ b/vcs-svn/fast_export.c
@@ -4,34 +4,77 @@
*/
#include "git-compat-util.h"
+#include "strbuf.h"
+#include "quote.h"
#include "fast_export.h"
-#include "line_buffer.h"
#include "repo_tree.h"
-#include "string_pool.h"
+#include "strbuf.h"
+#include "svndiff.h"
+#include "sliding_window.h"
+#include "line_buffer.h"
#define MAX_GITSVN_LINE_LEN 4096
static uint32_t first_commit_done;
+static struct line_buffer postimage = LINE_BUFFER_INIT;
+static struct line_buffer report_buffer = LINE_BUFFER_INIT;
+
+/* NEEDSWORK: move to fast_export_init() */
+static int init_postimage(void)
+{
+ static int postimage_initialized;
+ if (postimage_initialized)
+ return 0;
+ postimage_initialized = 1;
+ return buffer_tmpfile_init(&postimage);
+}
+
+void fast_export_init(int fd)
+{
+ first_commit_done = 0;
+ if (buffer_fdinit(&report_buffer, fd))
+ die_errno("cannot read from file descriptor %d", fd);
+}
+
+void fast_export_deinit(void)
+{
+ if (buffer_deinit(&report_buffer))
+ die_errno("error closing fast-import feedback stream");
+}
+
+void fast_export_reset(void)
+{
+ buffer_reset(&report_buffer);
+}
-void fast_export_delete(uint32_t depth, uint32_t *path)
+void fast_export_delete(const char *path)
{
putchar('D');
putchar(' ');
- pool_print_seq(depth, path, '/', stdout);
+ quote_c_style(path, NULL, stdout, 0);
putchar('\n');
}
-void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
- uint32_t mark)
+static void fast_export_truncate(const char *path, uint32_t mode)
+{
+ fast_export_modify(path, mode, "inline");
+ printf("data 0\n\n");
+}
+
+void fast_export_modify(const char *path, uint32_t mode, const char *dataref)
{
/* Mode must be 100644, 100755, 120000, or 160000. */
- printf("M %06"PRIo32" :%"PRIu32" ", mode, mark);
- pool_print_seq(depth, path, '/', stdout);
+ if (!dataref) {
+ fast_export_truncate(path, mode);
+ return;
+ }
+ printf("M %06"PRIo32" %s ", mode, dataref);
+ quote_c_style(path, NULL, stdout, 0);
putchar('\n');
}
static char gitsvnline[MAX_GITSVN_LINE_LEN];
-void fast_export_commit(uint32_t revision, const char *author,
+void fast_export_begin_commit(uint32_t revision, const char *author,
const struct strbuf *log,
const char *uuid, const char *url,
unsigned long timestamp)
@@ -47,6 +90,7 @@ void fast_export_commit(uint32_t revision, const char *author,
*gitsvnline = '\0';
}
printf("commit refs/heads/master\n");
+ printf("mark :%"PRIu32"\n", revision);
printf("committer %s <%s@%s> %ld +0000\n",
*author ? author : "nobody",
*author ? author : "nobody",
@@ -57,15 +101,44 @@ void fast_export_commit(uint32_t revision, const char *author,
printf("%s\n", gitsvnline);
if (!first_commit_done) {
if (revision > 1)
- printf("from refs/heads/master^0\n");
+ printf("from :%"PRIu32"\n", revision - 1);
first_commit_done = 1;
}
- repo_diff(revision - 1, revision);
- fputc('\n', stdout);
+}
+void fast_export_end_commit(uint32_t revision)
+{
printf("progress Imported commit %"PRIu32".\n\n", revision);
}
+static void ls_from_rev(uint32_t rev, const char *path)
+{
+ /* ls :5 path/to/old/file */
+ printf("ls :%"PRIu32" ", rev);
+ quote_c_style(path, NULL, stdout, 0);
+ putchar('\n');
+ fflush(stdout);
+}
+
+static void ls_from_active_commit(const char *path)
+{
+ /* ls "path/to/file" */
+ printf("ls \"");
+ quote_c_style(path, NULL, stdout, 1);
+ printf("\"\n");
+ fflush(stdout);
+}
+
+static const char *get_response_line(void)
+{
+ const char *line = buffer_read_line(&report_buffer);
+ if (line)
+ return line;
+ if (buffer_ferror(&report_buffer))
+ die_errno("error reading from fast-import");
+ die("unexpected end of fast-import feedback");
+}
+
static void die_short_read(struct line_buffer *input)
{
if (buffer_ferror(input))
@@ -73,16 +146,171 @@ static void die_short_read(struct line_buffer *input)
die("invalid dump: unexpected end of file");
}
-void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, struct line_buffer *input)
+static int ends_with(const char *s, size_t len, const char *suffix)
{
+ const size_t suffixlen = strlen(suffix);
+ if (len < suffixlen)
+ return 0;
+ return !memcmp(s + len - suffixlen, suffix, suffixlen);
+}
+
+static int parse_cat_response_line(const char *header, off_t *len)
+{
+ size_t headerlen = strlen(header);
+ uintmax_t n;
+ const char *type;
+ const char *end;
+
+ if (ends_with(header, headerlen, " missing"))
+ return error("cat-blob reports missing blob: %s", header);
+ type = memmem(header, headerlen, " blob ", strlen(" blob "));
+ if (!type)
+ return error("cat-blob header has wrong object type: %s", header);
+ n = strtoumax(type + strlen(" blob "), (char **) &end, 10);
+ if (end == type + strlen(" blob "))
+ return error("cat-blob header does not contain length: %s", header);
+ if (memchr(type + strlen(" blob "), '-', end - type - strlen(" blob ")))
+ return error("cat-blob header contains negative length: %s", header);
+ if (n == UINTMAX_MAX || n > maximum_signed_value_of_type(off_t))
+ return error("blob too large for current definition of off_t");
+ *len = n;
+ if (*end)
+ return error("cat-blob header contains garbage after length: %s", header);
+ return 0;
+}
+
+static void check_preimage_overflow(off_t a, off_t b)
+{
+ if (signed_add_overflows(a, b))
+ die("blob too large for current definition of off_t");
+}
+
+static long apply_delta(off_t len, struct line_buffer *input,
+ const char *old_data, uint32_t old_mode)
+{
+ long ret;
+ struct sliding_view preimage = SLIDING_VIEW_INIT(&report_buffer, 0);
+ FILE *out;
+
+ if (init_postimage() || !(out = buffer_tmpfile_rewind(&postimage)))
+ die("cannot open temporary file for blob retrieval");
+ if (old_data) {
+ const char *response;
+ printf("cat-blob %s\n", old_data);
+ fflush(stdout);
+ response = get_response_line();
+ if (parse_cat_response_line(response, &preimage.max_off))
+ die("invalid cat-blob response: %s", response);
+ check_preimage_overflow(preimage.max_off, 1);
+ }
+ if (old_mode == REPO_MODE_LNK) {
+ strbuf_addstr(&preimage.buf, "link ");
+ check_preimage_overflow(preimage.max_off, strlen("link "));
+ preimage.max_off += strlen("link ");
+ check_preimage_overflow(preimage.max_off, 1);
+ }
+ if (svndiff0_apply(input, len, &preimage, out))
+ die("cannot apply delta");
+ if (old_data) {
+ /* Read the remainder of preimage and trailing newline. */
+ assert(!signed_add_overflows(preimage.max_off, 1));
+ preimage.max_off++; /* room for newline */
+ if (move_window(&preimage, preimage.max_off - 1, 1))
+ die("cannot seek to end of input");
+ if (preimage.buf.buf[0] != '\n')
+ die("missing newline after cat-blob response");
+ }
+ ret = buffer_tmpfile_prepare_to_read(&postimage);
+ if (ret < 0)
+ die("cannot read temporary file for blob retrieval");
+ strbuf_release(&preimage.buf);
+ return ret;
+}
+
+void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input)
+{
+ assert(len >= 0);
if (mode == REPO_MODE_LNK) {
/* svn symlink blobs start with "link " */
+ if (len < 5)
+ die("invalid dump: symlink too short for \"link\" prefix");
len -= 5;
if (buffer_skip_bytes(input, 5) != 5)
die_short_read(input);
}
- printf("blob\nmark :%"PRIu32"\ndata %"PRIu32"\n", mark, len);
+ printf("data %"PRIuMAX"\n", (uintmax_t) len);
if (buffer_copy_bytes(input, len) != len)
die_short_read(input);
fputc('\n', stdout);
}
+
+static int parse_ls_response(const char *response, uint32_t *mode,
+ struct strbuf *dataref)
+{
+ const char *tab;
+ const char *response_end;
+
+ assert(response);
+ response_end = response + strlen(response);
+
+ if (*response == 'm') { /* Missing. */
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* Mode. */
+ if (response_end - response < strlen("100644") ||
+ response[strlen("100644")] != ' ')
+ die("invalid ls response: missing mode: %s", response);
+ *mode = 0;
+ for (; *response != ' '; response++) {
+ char ch = *response;
+ if (ch < '0' || ch > '7')
+ die("invalid ls response: mode is not octal: %s", response);
+ *mode *= 8;
+ *mode += ch - '0';
+ }
+
+ /* ' blob ' or ' tree ' */
+ if (response_end - response < strlen(" blob ") ||
+ (response[1] != 'b' && response[1] != 't'))
+ die("unexpected ls response: not a tree or blob: %s", response);
+ response += strlen(" blob ");
+
+ /* Dataref. */
+ tab = memchr(response, '\t', response_end - response);
+ if (!tab)
+ die("invalid ls response: missing tab: %s", response);
+ strbuf_add(dataref, response, tab - response);
+ return 0;
+}
+
+int fast_export_ls_rev(uint32_t rev, const char *path,
+ uint32_t *mode, struct strbuf *dataref)
+{
+ ls_from_rev(rev, path);
+ return parse_ls_response(get_response_line(), mode, dataref);
+}
+
+int fast_export_ls(const char *path, uint32_t *mode, struct strbuf *dataref)
+{
+ ls_from_active_commit(path);
+ return parse_ls_response(get_response_line(), mode, dataref);
+}
+
+void fast_export_blob_delta(uint32_t mode,
+ uint32_t old_mode, const char *old_data,
+ off_t len, struct line_buffer *input)
+{
+ long postimage_len;
+
+ assert(len >= 0);
+ postimage_len = apply_delta(len, input, old_data, old_mode);
+ if (mode == REPO_MODE_LNK) {
+ buffer_skip_bytes(&postimage, strlen("link "));
+ postimage_len -= strlen("link ");
+ }
+ printf("data %ld\n", postimage_len);
+ buffer_copy_bytes(&postimage, postimage_len);
+ fputc('\n', stdout);
+}
diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h
index 33a8fe9..aa629f5 100644
--- a/vcs-svn/fast_export.h
+++ b/vcs-svn/fast_export.h
@@ -1,16 +1,28 @@
#ifndef FAST_EXPORT_H_
#define FAST_EXPORT_H_
-#include "line_buffer.h"
struct strbuf;
+struct line_buffer;
-void fast_export_delete(uint32_t depth, uint32_t *path);
-void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
- uint32_t mark);
-void fast_export_commit(uint32_t revision, const char *author,
+void fast_export_init(int fd);
+void fast_export_deinit(void);
+void fast_export_reset(void);
+
+void fast_export_delete(const char *path);
+void fast_export_modify(const char *path, uint32_t mode, const char *dataref);
+void fast_export_begin_commit(uint32_t revision, const char *author,
const struct strbuf *log, const char *uuid,
const char *url, unsigned long timestamp);
-void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len,
- struct line_buffer *input);
+void fast_export_end_commit(uint32_t revision);
+void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input);
+void fast_export_blob_delta(uint32_t mode,
+ uint32_t old_mode, const char *old_data,
+ off_t len, struct line_buffer *input);
+
+/* If there is no such file at that rev, returns -1, errno == ENOENT. */
+int fast_export_ls_rev(uint32_t rev, const char *path,
+ uint32_t *mode_out, struct strbuf *dataref_out);
+int fast_export_ls(const char *path,
+ uint32_t *mode_out, struct strbuf *dataref_out);
#endif
diff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c
index c390387..01fcb84 100644
--- a/vcs-svn/line_buffer.c
+++ b/vcs-svn/line_buffer.c
@@ -91,10 +91,10 @@ char *buffer_read_line(struct line_buffer *buf)
return buf->line_buffer;
}
-void buffer_read_binary(struct line_buffer *buf,
- struct strbuf *sb, uint32_t size)
+size_t buffer_read_binary(struct line_buffer *buf,
+ struct strbuf *sb, size_t size)
{
- strbuf_fread(sb, size, buf->infile);
+ return strbuf_fread(sb, size, buf->infile);
}
off_t buffer_copy_bytes(struct line_buffer *buf, off_t nbytes)
diff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h
index d0b22dd..8901f21 100644
--- a/vcs-svn/line_buffer.h
+++ b/vcs-svn/line_buffer.h
@@ -23,7 +23,7 @@ long buffer_tmpfile_prepare_to_read(struct line_buffer *buf);
int buffer_ferror(struct line_buffer *buf);
char *buffer_read_line(struct line_buffer *buf);
int buffer_read_char(struct line_buffer *buf);
-void buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, uint32_t len);
+size_t buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, size_t len);
/* Returns number of bytes read (not necessarily written). */
off_t buffer_copy_bytes(struct line_buffer *buf, off_t len);
off_t buffer_skip_bytes(struct line_buffer *buf, off_t len);
diff --git a/vcs-svn/obj_pool.h b/vcs-svn/obj_pool.h
deleted file mode 100644
index deb6eb8..0000000
--- a/vcs-svn/obj_pool.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed under a two-clause BSD-style license.
- * See LICENSE for details.
- */
-
-#ifndef OBJ_POOL_H_
-#define OBJ_POOL_H_
-
-#include "git-compat-util.h"
-
-#define MAYBE_UNUSED __attribute__((__unused__))
-
-#define obj_pool_gen(pre, obj_t, initial_capacity) \
-static struct { \
- uint32_t committed; \
- uint32_t size; \
- uint32_t capacity; \
- obj_t *base; \
-} pre##_pool = {0, 0, 0, NULL}; \
-static MAYBE_UNUSED uint32_t pre##_alloc(uint32_t count) \
-{ \
- uint32_t offset; \
- if (pre##_pool.size + count > pre##_pool.capacity) { \
- while (pre##_pool.size + count > pre##_pool.capacity) \
- if (pre##_pool.capacity) \
- pre##_pool.capacity *= 2; \
- else \
- pre##_pool.capacity = initial_capacity; \
- pre##_pool.base = realloc(pre##_pool.base, \
- pre##_pool.capacity * sizeof(obj_t)); \
- } \
- offset = pre##_pool.size; \
- pre##_pool.size += count; \
- return offset; \
-} \
-static MAYBE_UNUSED void pre##_free(uint32_t count) \
-{ \
- pre##_pool.size -= count; \
-} \
-static MAYBE_UNUSED uint32_t pre##_offset(obj_t *obj) \
-{ \
- return obj == NULL ? ~0 : obj - pre##_pool.base; \
-} \
-static MAYBE_UNUSED obj_t *pre##_pointer(uint32_t offset) \
-{ \
- return offset >= pre##_pool.size ? NULL : &pre##_pool.base[offset]; \
-} \
-static MAYBE_UNUSED void pre##_commit(void) \
-{ \
- pre##_pool.committed = pre##_pool.size; \
-} \
-static MAYBE_UNUSED void pre##_reset(void) \
-{ \
- free(pre##_pool.base); \
- pre##_pool.base = NULL; \
- pre##_pool.size = 0; \
- pre##_pool.capacity = 0; \
- pre##_pool.committed = 0; \
-}
-
-#endif
diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c
index a21d89d..67d27f0 100644
--- a/vcs-svn/repo_tree.c
+++ b/vcs-svn/repo_tree.c
@@ -4,323 +4,45 @@
*/
#include "git-compat-util.h"
-
-#include "string_pool.h"
+#include "strbuf.h"
#include "repo_tree.h"
-#include "obj_pool.h"
#include "fast_export.h"
-#include "trp.h"
-
-struct repo_dirent {
- uint32_t name_offset;
- struct trp_node children;
- uint32_t mode;
- uint32_t content_offset;
-};
-
-struct repo_dir {
- struct trp_root entries;
-};
-
-struct repo_commit {
- uint32_t root_dir_offset;
-};
-
-/* Memory pools for commit, dir and dirent */
-obj_pool_gen(commit, struct repo_commit, 4096)
-obj_pool_gen(dir, struct repo_dir, 4096)
-obj_pool_gen(dent, struct repo_dirent, 4096)
-
-static uint32_t active_commit;
-static uint32_t mark;
-
-static int repo_dirent_name_cmp(const void *a, const void *b);
-
-/* Treap for directory entries */
-trp_gen(static, dent_, struct repo_dirent, children, dent, repo_dirent_name_cmp)
-
-uint32_t next_blob_mark(void)
+const char *repo_read_path(const char *path, uint32_t *mode_out)
{
- return mark++;
-}
+ int err;
+ static struct strbuf buf = STRBUF_INIT;
-static struct repo_dir *repo_commit_root_dir(struct repo_commit *commit)
-{
- return dir_pointer(commit->root_dir_offset);
-}
-
-static struct repo_dirent *repo_first_dirent(struct repo_dir *dir)
-{
- return dent_first(&dir->entries);
-}
-
-static int repo_dirent_name_cmp(const void *a, const void *b)
-{
- const struct repo_dirent *dent1 = a, *dent2 = b;
- uint32_t a_offset = dent1->name_offset;
- uint32_t b_offset = dent2->name_offset;
- return (a_offset > b_offset) - (a_offset < b_offset);
-}
-
-static int repo_dirent_is_dir(struct repo_dirent *dent)
-{
- return dent != NULL && dent->mode == REPO_MODE_DIR;
-}
-
-static struct repo_dir *repo_dir_from_dirent(struct repo_dirent *dent)
-{
- if (!repo_dirent_is_dir(dent))
+ strbuf_reset(&buf);
+ err = fast_export_ls(path, mode_out, &buf);
+ if (err) {
+ if (errno != ENOENT)
+ die_errno("BUG: unexpected fast_export_ls error");
+ /* Treat missing paths as directories. */
+ *mode_out = REPO_MODE_DIR;
return NULL;
- return dir_pointer(dent->content_offset);
-}
-
-static struct repo_dir *repo_clone_dir(struct repo_dir *orig_dir)
-{
- uint32_t orig_o, new_o;
- orig_o = dir_offset(orig_dir);
- if (orig_o >= dir_pool.committed)
- return orig_dir;
- new_o = dir_alloc(1);
- orig_dir = dir_pointer(orig_o);
- *dir_pointer(new_o) = *orig_dir;
- return dir_pointer(new_o);
-}
-
-static struct repo_dirent *repo_read_dirent(uint32_t revision,
- const uint32_t *path)
-{
- uint32_t name = 0;
- struct repo_dirent *key = dent_pointer(dent_alloc(1));
- struct repo_dir *dir = NULL;
- struct repo_dirent *dent = NULL;
- dir = repo_commit_root_dir(commit_pointer(revision));
- while (~(name = *path++)) {
- key->name_offset = name;
- dent = dent_search(&dir->entries, key);
- if (dent == NULL || !repo_dirent_is_dir(dent))
- break;
- dir = repo_dir_from_dirent(dent);
}
- dent_free(1);
- return dent;
+ return buf.buf;
}
-static void repo_write_dirent(const uint32_t *path, uint32_t mode,
- uint32_t content_offset, uint32_t del)
+void repo_copy(uint32_t revision, const char *src, const char *dst)
{
- uint32_t name, revision, dir_o = ~0, parent_dir_o = ~0;
- struct repo_dir *dir;
- struct repo_dirent *key;
- struct repo_dirent *dent = NULL;
- revision = active_commit;
- dir = repo_commit_root_dir(commit_pointer(revision));
- dir = repo_clone_dir(dir);
- commit_pointer(revision)->root_dir_offset = dir_offset(dir);
- while (~(name = *path++)) {
- parent_dir_o = dir_offset(dir);
-
- key = dent_pointer(dent_alloc(1));
- key->name_offset = name;
-
- dent = dent_search(&dir->entries, key);
- if (dent == NULL)
- dent = key;
- else
- dent_free(1);
-
- if (dent == key) {
- dent->mode = REPO_MODE_DIR;
- dent->content_offset = 0;
- dent = dent_insert(&dir->entries, dent);
- }
-
- if (dent_offset(dent) < dent_pool.committed) {
- dir_o = repo_dirent_is_dir(dent) ?
- dent->content_offset : ~0;
- dent_remove(&dir->entries, dent);
- dent = dent_pointer(dent_alloc(1));
- dent->name_offset = name;
- dent->mode = REPO_MODE_DIR;
- dent->content_offset = dir_o;
- dent = dent_insert(&dir->entries, dent);
- }
-
- dir = repo_dir_from_dirent(dent);
- dir = repo_clone_dir(dir);
- dent->content_offset = dir_offset(dir);
- }
- if (dent == NULL)
+ int err;
+ uint32_t mode;
+ static struct strbuf data = STRBUF_INIT;
+
+ strbuf_reset(&data);
+ err = fast_export_ls_rev(revision, src, &mode, &data);
+ if (err) {
+ if (errno != ENOENT)
+ die_errno("BUG: unexpected fast_export_ls_rev error");
+ fast_export_delete(dst);
return;
- dent->mode = mode;
- dent->content_offset = content_offset;
- if (del && ~parent_dir_o)
- dent_remove(&dir_pointer(parent_dir_o)->entries, dent);
-}
-
-uint32_t repo_read_path(const uint32_t *path)
-{
- uint32_t content_offset = 0;
- struct repo_dirent *dent = repo_read_dirent(active_commit, path);
- if (dent != NULL)
- content_offset = dent->content_offset;
- return content_offset;
-}
-
-uint32_t repo_read_mode(const uint32_t *path)
-{
- struct repo_dirent *dent = repo_read_dirent(active_commit, path);
- if (dent == NULL)
- die("invalid dump: path to be modified is missing");
- return dent->mode;
-}
-
-void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst)
-{
- uint32_t mode = 0, content_offset = 0;
- struct repo_dirent *src_dent;
- src_dent = repo_read_dirent(revision, src);
- if (src_dent != NULL) {
- mode = src_dent->mode;
- content_offset = src_dent->content_offset;
- repo_write_dirent(dst, mode, content_offset, 0);
- }
-}
-
-void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark)
-{
- repo_write_dirent(path, mode, blob_mark, 0);
-}
-
-void repo_delete(uint32_t *path)
-{
- repo_write_dirent(path, 0, 0, 1);
-}
-
-static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir);
-
-static void repo_git_add(uint32_t depth, uint32_t *path, struct repo_dirent *dent)
-{
- if (repo_dirent_is_dir(dent))
- repo_git_add_r(depth, path, repo_dir_from_dirent(dent));
- else
- fast_export_modify(depth, path,
- dent->mode, dent->content_offset);
-}
-
-static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir)
-{
- struct repo_dirent *de = repo_first_dirent(dir);
- while (de) {
- path[depth] = de->name_offset;
- repo_git_add(depth + 1, path, de);
- de = dent_next(&dir->entries, de);
- }
-}
-
-static void repo_diff_r(uint32_t depth, uint32_t *path, struct repo_dir *dir1,
- struct repo_dir *dir2)
-{
- struct repo_dirent *de1, *de2;
- de1 = repo_first_dirent(dir1);
- de2 = repo_first_dirent(dir2);
-
- while (de1 && de2) {
- if (de1->name_offset < de2->name_offset) {
- path[depth] = de1->name_offset;
- fast_export_delete(depth + 1, path);
- de1 = dent_next(&dir1->entries, de1);
- continue;
- }
- if (de1->name_offset > de2->name_offset) {
- path[depth] = de2->name_offset;
- repo_git_add(depth + 1, path, de2);
- de2 = dent_next(&dir2->entries, de2);
- continue;
- }
- path[depth] = de1->name_offset;
-
- if (de1->mode == de2->mode &&
- de1->content_offset == de2->content_offset) {
- ; /* No change. */
- } else if (repo_dirent_is_dir(de1) && repo_dirent_is_dir(de2)) {
- repo_diff_r(depth + 1, path,
- repo_dir_from_dirent(de1),
- repo_dir_from_dirent(de2));
- } else if (!repo_dirent_is_dir(de1) && !repo_dirent_is_dir(de2)) {
- repo_git_add(depth + 1, path, de2);
- } else {
- fast_export_delete(depth + 1, path);
- repo_git_add(depth + 1, path, de2);
- }
- de1 = dent_next(&dir1->entries, de1);
- de2 = dent_next(&dir2->entries, de2);
- }
- while (de1) {
- path[depth] = de1->name_offset;
- fast_export_delete(depth + 1, path);
- de1 = dent_next(&dir1->entries, de1);
- }
- while (de2) {
- path[depth] = de2->name_offset;
- repo_git_add(depth + 1, path, de2);
- de2 = dent_next(&dir2->entries, de2);
- }
-}
-
-static uint32_t path_stack[REPO_MAX_PATH_DEPTH];
-
-void repo_diff(uint32_t r1, uint32_t r2)
-{
- repo_diff_r(0,
- path_stack,
- repo_commit_root_dir(commit_pointer(r1)),
- repo_commit_root_dir(commit_pointer(r2)));
-}
-
-void repo_commit(uint32_t revision, const char *author,
- const struct strbuf *log, const char *uuid, const char *url,
- unsigned long timestamp)
-{
- fast_export_commit(revision, author, log, uuid, url, timestamp);
- dent_commit();
- dir_commit();
- active_commit = commit_alloc(1);
- commit_pointer(active_commit)->root_dir_offset =
- commit_pointer(active_commit - 1)->root_dir_offset;
-}
-
-static void mark_init(void)
-{
- uint32_t i;
- mark = 0;
- for (i = 0; i < dent_pool.size; i++)
- if (!repo_dirent_is_dir(dent_pointer(i)) &&
- dent_pointer(i)->content_offset > mark)
- mark = dent_pointer(i)->content_offset;
- mark++;
-}
-
-void repo_init(void)
-{
- mark_init();
- if (commit_pool.size == 0) {
- /* Create empty tree for commit 0. */
- commit_alloc(1);
- commit_pointer(0)->root_dir_offset = dir_alloc(1);
- dir_pointer(0)->entries.trp_root = ~0;
- dir_commit();
}
- /* Preallocate next commit, ready for changes. */
- active_commit = commit_alloc(1);
- commit_pointer(active_commit)->root_dir_offset =
- commit_pointer(active_commit - 1)->root_dir_offset;
+ fast_export_modify(dst, mode, data.buf);
}
-void repo_reset(void)
+void repo_delete(const char *path)
{
- pool_reset();
- commit_reset();
- dir_reset();
- dent_reset();
+ fast_export_delete(path);
}
diff --git a/vcs-svn/repo_tree.h b/vcs-svn/repo_tree.h
index 37bde2e..889c6a3 100644
--- a/vcs-svn/repo_tree.h
+++ b/vcs-svn/repo_tree.h
@@ -8,15 +8,11 @@ struct strbuf;
#define REPO_MODE_EXE 0100755
#define REPO_MODE_LNK 0120000
-#define REPO_MAX_PATH_LEN 4096
-#define REPO_MAX_PATH_DEPTH 1000
-
uint32_t next_blob_mark(void);
-void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst);
-void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark);
-uint32_t repo_read_path(const uint32_t *path);
-uint32_t repo_read_mode(const uint32_t *path);
-void repo_delete(uint32_t *path);
+void repo_copy(uint32_t revision, const char *src, const char *dst);
+void repo_add(const char *path, uint32_t mode, uint32_t blob_mark);
+const char *repo_read_path(const char *path, uint32_t *mode_out);
+void repo_delete(const char *path);
void repo_commit(uint32_t revision, const char *author,
const struct strbuf *log, const char *uuid, const char *url,
long unsigned timestamp);
diff --git a/vcs-svn/sliding_window.c b/vcs-svn/sliding_window.c
new file mode 100644
index 0000000..ec2707c
--- /dev/null
+++ b/vcs-svn/sliding_window.c
@@ -0,0 +1,79 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "sliding_window.h"
+#include "line_buffer.h"
+#include "strbuf.h"
+
+static int input_error(struct line_buffer *file)
+{
+ if (!buffer_ferror(file))
+ return error("delta preimage ends early");
+ return error("cannot read delta preimage: %s", strerror(errno));
+}
+
+static int skip_or_whine(struct line_buffer *file, off_t gap)
+{
+ if (buffer_skip_bytes(file, gap) != gap)
+ return input_error(file);
+ return 0;
+}
+
+static int read_to_fill_or_whine(struct line_buffer *file,
+ struct strbuf *buf, size_t width)
+{
+ buffer_read_binary(file, buf, width - buf->len);
+ if (buf->len != width)
+ return input_error(file);
+ return 0;
+}
+
+static int check_offset_overflow(off_t offset, uintmax_t len)
+{
+ if (len > maximum_signed_value_of_type(off_t))
+ return error("unrepresentable length in delta: "
+ "%"PRIuMAX" > OFF_MAX", len);
+ if (signed_add_overflows(offset, (off_t) len))
+ return error("unrepresentable offset in delta: "
+ "%"PRIuMAX" + %"PRIuMAX" > OFF_MAX",
+ (uintmax_t) offset, len);
+ return 0;
+}
+
+int move_window(struct sliding_view *view, off_t off, size_t width)
+{
+ off_t file_offset;
+ assert(view);
+ assert(view->width <= view->buf.len);
+ assert(!check_offset_overflow(view->off, view->buf.len));
+
+ if (check_offset_overflow(off, width))
+ return -1;
+ if (off < view->off || off + width < view->off + view->width)
+ return error("invalid delta: window slides left");
+ if (view->max_off >= 0 && view->max_off < off + width)
+ return error("delta preimage ends early");
+
+ file_offset = view->off + view->buf.len;
+ if (off < file_offset) {
+ /* Move the overlapping region into place. */
+ strbuf_remove(&view->buf, 0, off - view->off);
+ } else {
+ /* Seek ahead to skip the gap. */
+ if (skip_or_whine(view->file, off - file_offset))
+ return -1;
+ strbuf_setlen(&view->buf, 0);
+ }
+
+ if (view->buf.len > width)
+ ; /* Already read. */
+ else if (read_to_fill_or_whine(view->file, &view->buf, width))
+ return -1;
+
+ view->off = off;
+ view->width = width;
+ return 0;
+}
diff --git a/vcs-svn/sliding_window.h b/vcs-svn/sliding_window.h
new file mode 100644
index 0000000..b43a825
--- /dev/null
+++ b/vcs-svn/sliding_window.h
@@ -0,0 +1,18 @@
+#ifndef SLIDING_WINDOW_H_
+#define SLIDING_WINDOW_H_
+
+#include "strbuf.h"
+
+struct sliding_view {
+ struct line_buffer *file;
+ off_t off;
+ size_t width;
+ off_t max_off; /* -1 means unlimited */
+ struct strbuf buf;
+};
+
+#define SLIDING_VIEW_INIT(input, len) { (input), 0, 0, (len), STRBUF_INIT }
+
+extern int move_window(struct sliding_view *view, off_t off, size_t width);
+
+#endif
diff --git a/vcs-svn/string_pool.c b/vcs-svn/string_pool.c
deleted file mode 100644
index 8af8d54..0000000
--- a/vcs-svn/string_pool.c
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed under a two-clause BSD-style license.
- * See LICENSE for details.
- */
-
-#include "git-compat-util.h"
-#include "trp.h"
-#include "obj_pool.h"
-#include "string_pool.h"
-
-static struct trp_root tree = { ~0 };
-
-struct node {
- uint32_t offset;
- struct trp_node children;
-};
-
-/* Two memory pools: one for struct node, and another for strings */
-obj_pool_gen(node, struct node, 4096)
-obj_pool_gen(string, char, 4096)
-
-static char *node_value(struct node *node)
-{
- return node ? string_pointer(node->offset) : NULL;
-}
-
-static int node_cmp(struct node *a, struct node *b)
-{
- return strcmp(node_value(a), node_value(b));
-}
-
-/* Build a Treap from the node structure (a trp_node w/ offset) */
-trp_gen(static, tree_, struct node, children, node, node_cmp)
-
-const char *pool_fetch(uint32_t entry)
-{
- return node_value(node_pointer(entry));
-}
-
-uint32_t pool_intern(const char *key)
-{
- /* Canonicalize key */
- struct node *match = NULL, *node;
- uint32_t key_len;
- if (key == NULL)
- return ~0;
- key_len = strlen(key) + 1;
- node = node_pointer(node_alloc(1));
- node->offset = string_alloc(key_len);
- strcpy(node_value(node), key);
- match = tree_search(&tree, node);
- if (!match) {
- tree_insert(&tree, node);
- } else {
- node_free(1);
- string_free(key_len);
- node = match;
- }
- return node_offset(node);
-}
-
-uint32_t pool_tok_r(char *str, const char *delim, char **saveptr)
-{
- char *token = strtok_r(str, delim, saveptr);
- return token ? pool_intern(token) : ~0;
-}
-
-void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream)
-{
- uint32_t i;
- for (i = 0; i < len && ~seq[i]; i++) {
- fputs(pool_fetch(seq[i]), stream);
- if (i < len - 1 && ~seq[i + 1])
- fputc(delim, stream);
- }
-}
-
-uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str)
-{
- char *context = NULL;
- uint32_t token = ~0;
- uint32_t length;
-
- if (sz == 0)
- return ~0;
- if (str)
- token = pool_tok_r(str, delim, &context);
- for (length = 0; length < sz; length++) {
- seq[length] = token;
- if (token == ~0)
- return length;
- token = pool_tok_r(NULL, delim, &context);
- }
- seq[sz - 1] = ~0;
- return sz;
-}
-
-void pool_reset(void)
-{
- node_reset();
- string_reset();
-}
diff --git a/vcs-svn/string_pool.h b/vcs-svn/string_pool.h
deleted file mode 100644
index 222fb66..0000000
--- a/vcs-svn/string_pool.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef STRING_POOL_H_
-#define STRING_POOL_H_
-
-uint32_t pool_intern(const char *key);
-const char *pool_fetch(uint32_t entry);
-uint32_t pool_tok_r(char *str, const char *delim, char **saveptr);
-void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream);
-uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str);
-void pool_reset(void);
-
-#endif
diff --git a/vcs-svn/string_pool.txt b/vcs-svn/string_pool.txt
deleted file mode 100644
index 1b41f15..0000000
--- a/vcs-svn/string_pool.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-string_pool API
-===============
-
-The string_pool API provides facilities for replacing strings
-with integer keys that can be more easily compared and stored.
-The facilities are designed so that one could teach Git without
-too much trouble to store the information needed for these keys to
-remain valid over multiple executions.
-
-Functions
----------
-
-pool_intern::
- Include a string in the string pool and get its key.
- If that string is already in the pool, retrieves its
- existing key.
-
-pool_fetch::
- Retrieve the string associated to a given key.
-
-pool_tok_r::
- Extract the key of the next token from a string.
- Interface mimics strtok_r.
-
-pool_print_seq::
- Print a sequence of strings named by key to a file, using the
- specified delimiter to separate them.
-
- If NULL (key ~0) appears in the sequence, the sequence ends
- early.
-
-pool_tok_seq::
- Split a string into tokens, storing the keys of segments
- into a caller-provided array.
-
- Unless sz is 0, the array will always be ~0-terminated.
- If there is not enough room for all the tokens, the
- array holds as many tokens as fit in the entries before
- the terminating ~0. Return value is the index after the
- last token, or sz if the tokens did not fit.
-
-pool_reset::
- Deallocate storage for the string pool.
diff --git a/vcs-svn/svndiff.c b/vcs-svn/svndiff.c
new file mode 100644
index 0000000..1647c1a
--- /dev/null
+++ b/vcs-svn/svndiff.c
@@ -0,0 +1,308 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "sliding_window.h"
+#include "line_buffer.h"
+#include "svndiff.h"
+
+/*
+ * svndiff0 applier
+ *
+ * See http://svn.apache.org/repos/asf/subversion/trunk/notes/svndiff.
+ *
+ * svndiff0 ::= 'SVN\0' window*
+ * window ::= int int int int int instructions inline_data;
+ * instructions ::= instruction*;
+ * instruction ::= view_selector int int
+ * | copyfrom_data int
+ * | packed_view_selector int
+ * | packed_copyfrom_data
+ * ;
+ * view_selector ::= copyfrom_source
+ * | copyfrom_target
+ * ;
+ * copyfrom_source ::= # binary 00 000000;
+ * copyfrom_target ::= # binary 01 000000;
+ * copyfrom_data ::= # binary 10 000000;
+ * packed_view_selector ::= # view_selector OR-ed with 6 bit value;
+ * packed_copyfrom_data ::= # copyfrom_data OR-ed with 6 bit value;
+ * int ::= highdigit* lowdigit;
+ * highdigit ::= # binary 1000 0000 OR-ed with 7 bit value;
+ * lowdigit ::= # 7 bit value;
+ */
+
+#define INSN_MASK 0xc0
+#define INSN_COPYFROM_SOURCE 0x00
+#define INSN_COPYFROM_TARGET 0x40
+#define INSN_COPYFROM_DATA 0x80
+#define OPERAND_MASK 0x3f
+
+#define VLI_CONTINUE 0x80
+#define VLI_DIGIT_MASK 0x7f
+#define VLI_BITS_PER_DIGIT 7
+
+struct window {
+ struct sliding_view *in;
+ struct strbuf out;
+ struct strbuf instructions;
+ struct strbuf data;
+};
+
+#define WINDOW_INIT(w) { (w), STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }
+
+static void window_release(struct window *ctx)
+{
+ strbuf_release(&ctx->out);
+ strbuf_release(&ctx->instructions);
+ strbuf_release(&ctx->data);
+}
+
+static int write_strbuf(struct strbuf *sb, FILE *out)
+{
+ if (fwrite(sb->buf, 1, sb->len, out) == sb->len) /* Success. */
+ return 0;
+ return error("cannot write delta postimage: %s", strerror(errno));
+}
+
+static int error_short_read(struct line_buffer *input)
+{
+ if (buffer_ferror(input))
+ return error("error reading delta: %s", strerror(errno));
+ return error("invalid delta: unexpected end of file");
+}
+
+static int read_chunk(struct line_buffer *delta, off_t *delta_len,
+ struct strbuf *buf, size_t len)
+{
+ strbuf_reset(buf);
+ if (len > *delta_len ||
+ buffer_read_binary(delta, buf, len) != len)
+ return error_short_read(delta);
+ *delta_len -= buf->len;
+ return 0;
+}
+
+static int read_magic(struct line_buffer *in, off_t *len)
+{
+ static const char magic[] = {'S', 'V', 'N', '\0'};
+ struct strbuf sb = STRBUF_INIT;
+
+ if (read_chunk(in, len, &sb, sizeof(magic))) {
+ strbuf_release(&sb);
+ return -1;
+ }
+ if (memcmp(sb.buf, magic, sizeof(magic))) {
+ strbuf_release(&sb);
+ return error("invalid delta: unrecognized file type");
+ }
+ strbuf_release(&sb);
+ return 0;
+}
+
+static int read_int(struct line_buffer *in, uintmax_t *result, off_t *len)
+{
+ uintmax_t rv = 0;
+ off_t sz;
+ for (sz = *len; sz; sz--) {
+ const int ch = buffer_read_char(in);
+ if (ch == EOF)
+ break;
+
+ rv <<= VLI_BITS_PER_DIGIT;
+ rv += (ch & VLI_DIGIT_MASK);
+ if (ch & VLI_CONTINUE)
+ continue;
+
+ *result = rv;
+ *len = sz - 1;
+ return 0;
+ }
+ return error_short_read(in);
+}
+
+static int parse_int(const char **buf, size_t *result, const char *end)
+{
+ size_t rv = 0;
+ const char *pos;
+ for (pos = *buf; pos != end; pos++) {
+ unsigned char ch = *pos;
+
+ rv <<= VLI_BITS_PER_DIGIT;
+ rv += (ch & VLI_DIGIT_MASK);
+ if (ch & VLI_CONTINUE)
+ continue;
+
+ *result = rv;
+ *buf = pos + 1;
+ return 0;
+ }
+ return error("invalid delta: unexpected end of instructions section");
+}
+
+static int read_offset(struct line_buffer *in, off_t *result, off_t *len)
+{
+ uintmax_t val;
+ if (read_int(in, &val, len))
+ return -1;
+ if (val > maximum_signed_value_of_type(off_t))
+ return error("unrepresentable offset in delta: %"PRIuMAX"", val);
+ *result = val;
+ return 0;
+}
+
+static int read_length(struct line_buffer *in, size_t *result, off_t *len)
+{
+ uintmax_t val;
+ if (read_int(in, &val, len))
+ return -1;
+ if (val > SIZE_MAX)
+ return error("unrepresentable length in delta: %"PRIuMAX"", val);
+ *result = val;
+ return 0;
+}
+
+static int copyfrom_source(struct window *ctx, const char **instructions,
+ size_t nbytes, const char *insns_end)
+{
+ size_t offset;
+ if (parse_int(instructions, &offset, insns_end))
+ return -1;
+ if (unsigned_add_overflows(offset, nbytes) ||
+ offset + nbytes > ctx->in->width)
+ return error("invalid delta: copies source data outside view");
+ strbuf_add(&ctx->out, ctx->in->buf.buf + offset, nbytes);
+ return 0;
+}
+
+static int copyfrom_target(struct window *ctx, const char **instructions,
+ size_t nbytes, const char *instructions_end)
+{
+ size_t offset;
+ if (parse_int(instructions, &offset, instructions_end))
+ return -1;
+ if (offset >= ctx->out.len)
+ return error("invalid delta: copies from the future");
+ for (; nbytes > 0; nbytes--)
+ strbuf_addch(&ctx->out, ctx->out.buf[offset++]);
+ return 0;
+}
+
+static int copyfrom_data(struct window *ctx, size_t *data_pos, size_t nbytes)
+{
+ const size_t pos = *data_pos;
+ if (unsigned_add_overflows(pos, nbytes) ||
+ pos + nbytes > ctx->data.len)
+ return error("invalid delta: copies unavailable inline data");
+ strbuf_add(&ctx->out, ctx->data.buf + pos, nbytes);
+ *data_pos += nbytes;
+ return 0;
+}
+
+static int parse_first_operand(const char **buf, size_t *out, const char *end)
+{
+ size_t result = (unsigned char) *(*buf)++ & OPERAND_MASK;
+ if (result) { /* immediate operand */
+ *out = result;
+ return 0;
+ }
+ return parse_int(buf, out, end);
+}
+
+static int execute_one_instruction(struct window *ctx,
+ const char **instructions, size_t *data_pos)
+{
+ unsigned int instruction;
+ const char *insns_end = ctx->instructions.buf + ctx->instructions.len;
+ size_t nbytes;
+ assert(ctx);
+ assert(instructions && *instructions);
+ assert(data_pos);
+
+ instruction = (unsigned char) **instructions;
+ if (parse_first_operand(instructions, &nbytes, insns_end))
+ return -1;
+ switch (instruction & INSN_MASK) {
+ case INSN_COPYFROM_SOURCE:
+ return copyfrom_source(ctx, instructions, nbytes, insns_end);
+ case INSN_COPYFROM_TARGET:
+ return copyfrom_target(ctx, instructions, nbytes, insns_end);
+ case INSN_COPYFROM_DATA:
+ return copyfrom_data(ctx, data_pos, nbytes);
+ default:
+ return error("invalid delta: unrecognized instruction");
+ }
+}
+
+static int apply_window_in_core(struct window *ctx)
+{
+ const char *instructions;
+ size_t data_pos = 0;
+
+ /*
+ * Fill ctx->out.buf using data from the source, target,
+ * and inline data views.
+ */
+ for (instructions = ctx->instructions.buf;
+ instructions != ctx->instructions.buf + ctx->instructions.len;
+ )
+ if (execute_one_instruction(ctx, &instructions, &data_pos))
+ return -1;
+ if (data_pos != ctx->data.len)
+ return error("invalid delta: does not copy all inline data");
+ return 0;
+}
+
+static int apply_one_window(struct line_buffer *delta, off_t *delta_len,
+ struct sliding_view *preimage, FILE *out)
+{
+ struct window ctx = WINDOW_INIT(preimage);
+ size_t out_len;
+ size_t instructions_len;
+ size_t data_len;
+ assert(delta_len);
+
+ /* "source view" offset and length already handled; */
+ if (read_length(delta, &out_len, delta_len) ||
+ read_length(delta, &instructions_len, delta_len) ||
+ read_length(delta, &data_len, delta_len) ||
+ read_chunk(delta, delta_len, &ctx.instructions, instructions_len) ||
+ read_chunk(delta, delta_len, &ctx.data, data_len))
+ goto error_out;
+ strbuf_grow(&ctx.out, out_len);
+ if (apply_window_in_core(&ctx))
+ goto error_out;
+ if (ctx.out.len != out_len) {
+ error("invalid delta: incorrect postimage length");
+ goto error_out;
+ }
+ if (write_strbuf(&ctx.out, out))
+ goto error_out;
+ window_release(&ctx);
+ return 0;
+error_out:
+ window_release(&ctx);
+ return -1;
+}
+
+int svndiff0_apply(struct line_buffer *delta, off_t delta_len,
+ struct sliding_view *preimage, FILE *postimage)
+{
+ assert(delta && preimage && postimage);
+
+ if (read_magic(delta, &delta_len))
+ return -1;
+ while (delta_len) { /* For each window: */
+ off_t pre_off = pre_off; /* stupid GCC... */
+ size_t pre_len;
+
+ if (read_offset(delta, &pre_off, &delta_len) ||
+ read_length(delta, &pre_len, &delta_len) ||
+ move_window(preimage, pre_off, pre_len) ||
+ apply_one_window(delta, &delta_len, preimage, postimage))
+ return -1;
+ }
+ return 0;
+}
diff --git a/vcs-svn/svndiff.h b/vcs-svn/svndiff.h
new file mode 100644
index 0000000..74eb464
--- /dev/null
+++ b/vcs-svn/svndiff.h
@@ -0,0 +1,10 @@
+#ifndef SVNDIFF_H_
+#define SVNDIFF_H_
+
+struct line_buffer;
+struct sliding_view;
+
+extern int svndiff0_apply(struct line_buffer *delta, off_t delta_len,
+ struct sliding_view *preimage, FILE *postimage);
+
+#endif
diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c
index bc79222..0899790 100644
--- a/vcs-svn/svndump.c
+++ b/vcs-svn/svndump.c
@@ -11,7 +11,6 @@
#include "repo_tree.h"
#include "fast_export.h"
#include "line_buffer.h"
-#include "string_pool.h"
#include "strbuf.h"
#include "svndump.h"
@@ -21,15 +20,19 @@
*/
#define constcmp(s, ref) memcmp(s, ref, sizeof(ref) - 1)
+#define REPORT_FILENO 3
+
#define NODEACT_REPLACE 4
#define NODEACT_DELETE 3
#define NODEACT_ADD 2
#define NODEACT_CHANGE 1
#define NODEACT_UNKNOWN 0
-#define DUMP_CTX 0
-#define REV_CTX 1
-#define NODE_CTX 2
+/* States: */
+#define DUMP_CTX 0 /* dump metadata */
+#define REV_CTX 1 /* revision metadata */
+#define NODE_CTX 2 /* node metadata */
+#define INTERNODE_CTX 3 /* between nodes */
#define LENGTH_UNKNOWN (~0)
#define DATE_RFC2822_LEN 31
@@ -37,8 +40,9 @@
static struct line_buffer input = LINE_BUFFER_INIT;
static struct {
- uint32_t action, propLength, textLength, srcRev, type;
- uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH];
+ uint32_t action, propLength, srcRev, type;
+ off_t text_length;
+ struct strbuf src, dst;
uint32_t text_delta, prop_delta;
} node_ctx;
@@ -58,10 +62,12 @@ static void reset_node_ctx(char *fname)
node_ctx.type = 0;
node_ctx.action = NODEACT_UNKNOWN;
node_ctx.propLength = LENGTH_UNKNOWN;
- node_ctx.textLength = LENGTH_UNKNOWN;
- node_ctx.src[0] = ~0;
+ node_ctx.text_length = -1;
+ strbuf_reset(&node_ctx.src);
node_ctx.srcRev = 0;
- pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname);
+ strbuf_reset(&node_ctx.dst);
+ if (fname)
+ strbuf_addstr(&node_ctx.dst, fname);
node_ctx.text_delta = 0;
node_ctx.prop_delta = 0;
}
@@ -169,7 +175,7 @@ static void read_props(void)
int ch;
if (!type || t[1] != ' ')
- die("invalid property line: %s\n", t);
+ die("invalid property line: %s", t);
len = atoi(&t[2]);
strbuf_reset(&val);
buffer_read_binary(&input, &val, len);
@@ -195,35 +201,39 @@ static void read_props(void)
strbuf_reset(&key);
continue;
default:
- die("invalid property line: %s\n", t);
+ die("invalid property line: %s", t);
}
}
}
static void handle_node(void)
{
- uint32_t mark = 0;
const uint32_t type = node_ctx.type;
const int have_props = node_ctx.propLength != LENGTH_UNKNOWN;
- const int have_text = node_ctx.textLength != LENGTH_UNKNOWN;
+ const int have_text = node_ctx.text_length != -1;
+ /*
+ * Old text for this node:
+ * NULL - directory or bug
+ * empty_blob - empty
+ * "<dataref>" - data retrievable from fast-import
+ */
+ static const char *const empty_blob = "::empty::";
+ const char *old_data = NULL;
+ uint32_t old_mode = REPO_MODE_BLB;
- if (node_ctx.text_delta)
- die("text deltas not supported");
- if (have_text)
- mark = next_blob_mark();
if (node_ctx.action == NODEACT_DELETE) {
if (have_text || have_props || node_ctx.srcRev)
die("invalid dump: deletion node has "
"copyfrom info, text, or properties");
- repo_delete(node_ctx.dst);
+ repo_delete(node_ctx.dst.buf);
return;
}
if (node_ctx.action == NODEACT_REPLACE) {
- repo_delete(node_ctx.dst);
+ repo_delete(node_ctx.dst.buf);
node_ctx.action = NODEACT_ADD;
}
if (node_ctx.srcRev) {
- repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst);
+ repo_copy(node_ctx.srcRev, node_ctx.src.buf, node_ctx.dst.buf);
if (node_ctx.action == NODEACT_ADD)
node_ctx.action = NODEACT_CHANGE;
}
@@ -231,23 +241,27 @@ static void handle_node(void)
die("invalid dump: directories cannot have text attached");
/*
- * Decide on the new content (mark) and mode (node_ctx.type).
+ * Find old content (old_data) and decide on the new mode.
*/
- if (node_ctx.action == NODEACT_CHANGE && !~*node_ctx.dst) {
+ if (node_ctx.action == NODEACT_CHANGE && !*node_ctx.dst.buf) {
if (type != REPO_MODE_DIR)
die("invalid dump: root of tree is not a regular file");
+ old_data = NULL;
} else if (node_ctx.action == NODEACT_CHANGE) {
uint32_t mode;
- if (!have_text)
- mark = repo_read_path(node_ctx.dst);
- mode = repo_read_mode(node_ctx.dst);
+ old_data = repo_read_path(node_ctx.dst.buf, &mode);
if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR)
die("invalid dump: cannot modify a directory into a file");
if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR)
die("invalid dump: cannot modify a file into a directory");
node_ctx.type = mode;
+ old_mode = mode;
} else if (node_ctx.action == NODEACT_ADD) {
- if (!have_text && type != REPO_MODE_DIR)
+ if (type == REPO_MODE_DIR)
+ old_data = NULL;
+ else if (have_text)
+ old_data = empty_blob;
+ else
die("invalid dump: adds node without text");
} else {
die("invalid dump: Node-path block lacks Node-action");
@@ -266,18 +280,39 @@ static void handle_node(void)
/*
* Save the result.
*/
- repo_add(node_ctx.dst, node_ctx.type, mark);
- if (have_text)
- fast_export_blob(node_ctx.type, mark,
- node_ctx.textLength, &input);
+ if (type == REPO_MODE_DIR) /* directories are not tracked. */
+ return;
+ assert(old_data);
+ if (old_data == empty_blob)
+ /* For the fast_export_* functions, NULL means empty. */
+ old_data = NULL;
+ if (!have_text) {
+ fast_export_modify(node_ctx.dst.buf, node_ctx.type, old_data);
+ return;
+ }
+ if (!node_ctx.text_delta) {
+ fast_export_modify(node_ctx.dst.buf, node_ctx.type, "inline");
+ fast_export_data(node_ctx.type, node_ctx.text_length, &input);
+ return;
+ }
+ fast_export_modify(node_ctx.dst.buf, node_ctx.type, "inline");
+ fast_export_blob_delta(node_ctx.type, old_mode, old_data,
+ node_ctx.text_length, &input);
}
-static void handle_revision(void)
+static void begin_revision(void)
+{
+ if (!rev_ctx.revision) /* revision 0 gets no git commit. */
+ return;
+ fast_export_begin_commit(rev_ctx.revision, rev_ctx.author.buf,
+ &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf,
+ rev_ctx.timestamp);
+}
+
+static void end_revision(void)
{
if (rev_ctx.revision)
- repo_commit(rev_ctx.revision, rev_ctx.author.buf,
- &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf,
- rev_ctx.timestamp);
+ fast_export_end_commit(rev_ctx.revision);
}
void svndump_read(const char *url)
@@ -318,8 +353,10 @@ void svndump_read(const char *url)
continue;
if (active_ctx == NODE_CTX)
handle_node();
+ if (active_ctx == REV_CTX)
+ begin_revision();
if (active_ctx != DUMP_CTX)
- handle_revision();
+ end_revision();
active_ctx = REV_CTX;
reset_rev_ctx(atoi(val));
break;
@@ -329,6 +366,8 @@ void svndump_read(const char *url)
if (!constcmp(t + strlen("Node-"), "path")) {
if (active_ctx == NODE_CTX)
handle_node();
+ if (active_ctx == REV_CTX)
+ begin_revision();
active_ctx = NODE_CTX;
reset_node_ctx(val);
break;
@@ -361,7 +400,8 @@ void svndump_read(const char *url)
case sizeof("Node-copyfrom-path"):
if (constcmp(t, "Node-copyfrom-path"))
continue;
- pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val);
+ strbuf_reset(&node_ctx.src);
+ strbuf_addstr(&node_ctx.src, val);
break;
case sizeof("Node-copyfrom-rev"):
if (constcmp(t, "Node-copyfrom-rev"))
@@ -370,7 +410,15 @@ void svndump_read(const char *url)
break;
case sizeof("Text-content-length"):
if (!constcmp(t, "Text-content-length")) {
- node_ctx.textLength = atoi(val);
+ char *end;
+ uintmax_t textlen;
+
+ textlen = strtoumax(val, &end, 10);
+ if (!isdigit(*val) || *end)
+ die("invalid dump: non-numeric length %s", val);
+ if (textlen > maximum_signed_value_of_type(off_t))
+ die("unrepresentable length in dump: %s", val);
+ node_ctx.text_length = (off_t) textlen;
break;
}
if (constcmp(t, "Prop-content-length"))
@@ -399,7 +447,7 @@ void svndump_read(const char *url)
read_props();
} else if (active_ctx == NODE_CTX) {
handle_node();
- active_ctx = REV_CTX;
+ active_ctx = INTERNODE_CTX;
} else {
fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len);
if (buffer_skip_bytes(&input, len) != len)
@@ -411,19 +459,23 @@ void svndump_read(const char *url)
die_short_read();
if (active_ctx == NODE_CTX)
handle_node();
+ if (active_ctx == REV_CTX)
+ begin_revision();
if (active_ctx != DUMP_CTX)
- handle_revision();
+ end_revision();
}
int svndump_init(const char *filename)
{
if (buffer_init(&input, filename))
return error("cannot open %s: %s", filename, strerror(errno));
- repo_init();
+ fast_export_init(REPORT_FILENO);
strbuf_init(&dump_ctx.uuid, 4096);
strbuf_init(&dump_ctx.url, 4096);
strbuf_init(&rev_ctx.log, 4096);
strbuf_init(&rev_ctx.author, 4096);
+ strbuf_init(&node_ctx.src, 4096);
+ strbuf_init(&node_ctx.dst, 4096);
reset_dump_ctx(NULL);
reset_rev_ctx(0);
reset_node_ctx(NULL);
@@ -432,11 +484,13 @@ int svndump_init(const char *filename)
void svndump_deinit(void)
{
- repo_reset();
+ fast_export_deinit();
reset_dump_ctx(NULL);
reset_rev_ctx(0);
reset_node_ctx(NULL);
strbuf_release(&rev_ctx.log);
+ strbuf_release(&node_ctx.src);
+ strbuf_release(&node_ctx.dst);
if (buffer_deinit(&input))
fprintf(stderr, "Input error\n");
if (ferror(stdout))
@@ -445,8 +499,8 @@ void svndump_deinit(void)
void svndump_reset(void)
{
+ fast_export_reset();
buffer_reset(&input);
- repo_reset();
strbuf_release(&dump_ctx.uuid);
strbuf_release(&dump_ctx.url);
strbuf_release(&rev_ctx.log);
diff --git a/vcs-svn/trp.h b/vcs-svn/trp.h
deleted file mode 100644
index c32b918..0000000
--- a/vcs-svn/trp.h
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * C macro implementation of treaps.
- *
- * Usage:
- * #include <stdint.h>
- * #include "trp.h"
- * trp_gen(...)
- *
- * Licensed under a two-clause BSD-style license.
- * See LICENSE for details.
- */
-
-#ifndef TRP_H_
-#define TRP_H_
-
-#define MAYBE_UNUSED __attribute__((__unused__))
-
-/* Node structure. */
-struct trp_node {
- uint32_t trpn_left;
- uint32_t trpn_right;
-};
-
-/* Root structure. */
-struct trp_root {
- uint32_t trp_root;
-};
-
-/* Pointer/Offset conversion. */
-#define trpn_pointer(a_base, a_offset) (a_base##_pointer(a_offset))
-#define trpn_offset(a_base, a_pointer) (a_base##_offset(a_pointer))
-#define trpn_modify(a_base, a_offset) \
- do { \
- if ((a_offset) < a_base##_pool.committed) { \
- uint32_t old_offset = (a_offset);\
- (a_offset) = a_base##_alloc(1); \
- *trpn_pointer(a_base, a_offset) = \
- *trpn_pointer(a_base, old_offset); \
- } \
- } while (0)
-
-/* Left accessors. */
-#define trp_left_get(a_base, a_field, a_node) \
- (trpn_pointer(a_base, a_node)->a_field.trpn_left)
-#define trp_left_set(a_base, a_field, a_node, a_left) \
- do { \
- trpn_modify(a_base, a_node); \
- trp_left_get(a_base, a_field, a_node) = (a_left); \
- } while (0)
-
-/* Right accessors. */
-#define trp_right_get(a_base, a_field, a_node) \
- (trpn_pointer(a_base, a_node)->a_field.trpn_right)
-#define trp_right_set(a_base, a_field, a_node, a_right) \
- do { \
- trpn_modify(a_base, a_node); \
- trp_right_get(a_base, a_field, a_node) = (a_right); \
- } while (0)
-
-/*
- * Fibonacci hash function.
- * The multiplier is the nearest prime to (2^32 times (√5 - 1)/2).
- * See Knuth §6.4: volume 3, 3rd ed, p518.
- */
-#define trpn_hash(a_node) (uint32_t) (2654435761u * (a_node))
-
-/* Priority accessors. */
-#define trp_prio_get(a_node) trpn_hash(a_node)
-
-/* Node initializer. */
-#define trp_node_new(a_base, a_field, a_node) \
- do { \
- trp_left_set(a_base, a_field, (a_node), ~0); \
- trp_right_set(a_base, a_field, (a_node), ~0); \
- } while (0)
-
-/* Internal utility macros. */
-#define trpn_first(a_base, a_field, a_root, r_node) \
- do { \
- (r_node) = (a_root); \
- if ((r_node) == ~0) \
- return NULL; \
- while (~trp_left_get(a_base, a_field, (r_node))) \
- (r_node) = trp_left_get(a_base, a_field, (r_node)); \
- } while (0)
-
-#define trpn_rotate_left(a_base, a_field, a_node, r_node) \
- do { \
- (r_node) = trp_right_get(a_base, a_field, (a_node)); \
- trp_right_set(a_base, a_field, (a_node), \
- trp_left_get(a_base, a_field, (r_node))); \
- trp_left_set(a_base, a_field, (r_node), (a_node)); \
- } while (0)
-
-#define trpn_rotate_right(a_base, a_field, a_node, r_node) \
- do { \
- (r_node) = trp_left_get(a_base, a_field, (a_node)); \
- trp_left_set(a_base, a_field, (a_node), \
- trp_right_get(a_base, a_field, (r_node))); \
- trp_right_set(a_base, a_field, (r_node), (a_node)); \
- } while (0)
-
-#define trp_gen(a_attr, a_pre, a_type, a_field, a_base, a_cmp) \
-a_attr a_type MAYBE_UNUSED *a_pre##first(struct trp_root *treap) \
-{ \
- uint32_t ret; \
- trpn_first(a_base, a_field, treap->trp_root, ret); \
- return trpn_pointer(a_base, ret); \
-} \
-a_attr a_type MAYBE_UNUSED *a_pre##next(struct trp_root *treap, a_type *node) \
-{ \
- uint32_t ret; \
- uint32_t offset = trpn_offset(a_base, node); \
- if (~trp_right_get(a_base, a_field, offset)) { \
- trpn_first(a_base, a_field, \
- trp_right_get(a_base, a_field, offset), ret); \
- } else { \
- uint32_t tnode = treap->trp_root; \
- ret = ~0; \
- while (1) { \
- int cmp = (a_cmp)(trpn_pointer(a_base, offset), \
- trpn_pointer(a_base, tnode)); \
- if (cmp < 0) { \
- ret = tnode; \
- tnode = trp_left_get(a_base, a_field, tnode); \
- } else if (cmp > 0) { \
- tnode = trp_right_get(a_base, a_field, tnode); \
- } else { \
- break; \
- } \
- } \
- } \
- return trpn_pointer(a_base, ret); \
-} \
-a_attr a_type MAYBE_UNUSED *a_pre##search(struct trp_root *treap, a_type *key) \
-{ \
- int cmp; \
- uint32_t ret = treap->trp_root; \
- while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \
- if (cmp < 0) { \
- ret = trp_left_get(a_base, a_field, ret); \
- } else { \
- ret = trp_right_get(a_base, a_field, ret); \
- } \
- } \
- return trpn_pointer(a_base, ret); \
-} \
-a_attr a_type MAYBE_UNUSED *a_pre##nsearch(struct trp_root *treap, a_type *key) \
-{ \
- int cmp; \
- uint32_t ret = treap->trp_root; \
- while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \
- if (cmp < 0) { \
- if (!~trp_left_get(a_base, a_field, ret)) \
- break; \
- ret = trp_left_get(a_base, a_field, ret); \
- } else { \
- ret = trp_right_get(a_base, a_field, ret); \
- } \
- } \
- return trpn_pointer(a_base, ret); \
-} \
-a_attr uint32_t MAYBE_UNUSED a_pre##insert_recurse(uint32_t cur_node, uint32_t ins_node) \
-{ \
- if (cur_node == ~0) { \
- return ins_node; \
- } else { \
- uint32_t ret; \
- int cmp = (a_cmp)(trpn_pointer(a_base, ins_node), \
- trpn_pointer(a_base, cur_node)); \
- if (cmp < 0) { \
- uint32_t left = a_pre##insert_recurse( \
- trp_left_get(a_base, a_field, cur_node), ins_node); \
- trp_left_set(a_base, a_field, cur_node, left); \
- if (trp_prio_get(left) < trp_prio_get(cur_node)) \
- trpn_rotate_right(a_base, a_field, cur_node, ret); \
- else \
- ret = cur_node; \
- } else { \
- uint32_t right = a_pre##insert_recurse( \
- trp_right_get(a_base, a_field, cur_node), ins_node); \
- trp_right_set(a_base, a_field, cur_node, right); \
- if (trp_prio_get(right) < trp_prio_get(cur_node)) \
- trpn_rotate_left(a_base, a_field, cur_node, ret); \
- else \
- ret = cur_node; \
- } \
- return ret; \
- } \
-} \
-a_attr a_type *MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \
-{ \
- uint32_t offset = trpn_offset(a_base, node); \
- trp_node_new(a_base, a_field, offset); \
- treap->trp_root = a_pre##insert_recurse(treap->trp_root, offset); \
- return trpn_pointer(a_base, offset); \
-} \
-a_attr uint32_t MAYBE_UNUSED a_pre##remove_recurse(uint32_t cur_node, uint32_t rem_node) \
-{ \
- int cmp = a_cmp(trpn_pointer(a_base, rem_node), \
- trpn_pointer(a_base, cur_node)); \
- if (cmp == 0) { \
- uint32_t ret; \
- uint32_t left = trp_left_get(a_base, a_field, cur_node); \
- uint32_t right = trp_right_get(a_base, a_field, cur_node); \
- if (left == ~0) { \
- if (right == ~0) \
- return ~0; \
- } else if (right == ~0 || trp_prio_get(left) < trp_prio_get(right)) { \
- trpn_rotate_right(a_base, a_field, cur_node, ret); \
- right = a_pre##remove_recurse(cur_node, rem_node); \
- trp_right_set(a_base, a_field, ret, right); \
- return ret; \
- } \
- trpn_rotate_left(a_base, a_field, cur_node, ret); \
- left = a_pre##remove_recurse(cur_node, rem_node); \
- trp_left_set(a_base, a_field, ret, left); \
- return ret; \
- } else if (cmp < 0) { \
- uint32_t left = a_pre##remove_recurse( \
- trp_left_get(a_base, a_field, cur_node), rem_node); \
- trp_left_set(a_base, a_field, cur_node, left); \
- return cur_node; \
- } else { \
- uint32_t right = a_pre##remove_recurse( \
- trp_right_get(a_base, a_field, cur_node), rem_node); \
- trp_right_set(a_base, a_field, cur_node, right); \
- return cur_node; \
- } \
-} \
-a_attr void MAYBE_UNUSED a_pre##remove(struct trp_root *treap, a_type *node) \
-{ \
- treap->trp_root = a_pre##remove_recurse(treap->trp_root, \
- trpn_offset(a_base, node)); \
-} \
-
-#endif
diff --git a/vcs-svn/trp.txt b/vcs-svn/trp.txt
deleted file mode 100644
index 177ebca..0000000
--- a/vcs-svn/trp.txt
+++ /dev/null
@@ -1,109 +0,0 @@
-Motivation
-==========
-
-Treaps provide a memory-efficient binary search tree structure.
-Insertion/deletion/search are about as about as fast in the average
-case as red-black trees and the chances of worst-case behavior are
-vanishingly small, thanks to (pseudo-)randomness. The bad worst-case
-behavior is a small price to pay, given that treaps are much simpler
-to implement.
-
-API
-===
-
-The trp API generates a data structure and functions to handle a
-large growing set of objects stored in a pool.
-
-The caller:
-
-. Specifies parameters for the generated functions with the
- trp_gen(static, foo_, ...) macro.
-
-. Allocates a `struct trp_root` variable and sets it to {~0}.
-
-. Adds new nodes to the set using `foo_insert`. Any pointers
- to existing nodes cannot be relied upon any more, so the caller
- might retrieve them anew with `foo_pointer`.
-
-. Can find a specific item in the set using `foo_search`.
-
-. Can iterate over items in the set using `foo_first` and `foo_next`.
-
-. Can remove an item from the set using `foo_remove`.
-
-Example:
-
-----
-struct ex_node {
- const char *s;
- struct trp_node ex_link;
-};
-static struct trp_root ex_base = {~0};
-obj_pool_gen(ex, struct ex_node, 4096);
-trp_gen(static, ex_, struct ex_node, ex_link, ex, strcmp)
-struct ex_node *item;
-
-item = ex_pointer(ex_alloc(1));
-item->s = "hello";
-ex_insert(&ex_base, item);
-item = ex_pointer(ex_alloc(1));
-item->s = "goodbye";
-ex_insert(&ex_base, item);
-for (item = ex_first(&ex_base); item; item = ex_next(&ex_base, item))
- printf("%s\n", item->s);
-----
-
-Functions
----------
-
-trp_gen(attr, foo_, node_type, link_field, pool, cmp)::
-
- Generate a type-specific treap implementation.
-+
-. The storage class for generated functions will be 'attr' (e.g., `static`).
-. Generated function names are prefixed with 'foo_' (e.g., `treap_`).
-. Treap nodes will be of type 'node_type' (e.g., `struct treap_node`).
- This type must be a struct with at least one `struct trp_node` field
- to point to its children.
-. The field used to access child nodes will be 'link_field'.
-. All treap nodes must lie in the 'pool' object pool.
-. Treap nodes must be totally ordered by the 'cmp' relation, with the
- following prototype:
-+
-int (*cmp)(node_type \*a, node_type \*b)
-+
-and returning a value less than, equal to, or greater than zero
-according to the result of comparison.
-
-node_type {asterisk}foo_insert(struct trp_root *treap, node_type \*node)::
-
- Insert node into treap. If inserted multiple times,
- a node will appear in the treap multiple times.
-+
-The return value is the address of the node within the treap,
-which might differ from `node` if `pool_alloc` had to call
-`realloc` to expand the pool.
-
-void foo_remove(struct trp_root *treap, node_type \*node)::
-
- Remove node from treap. Caller must ensure node is
- present in treap before using this function.
-
-node_type *foo_search(struct trp_root \*treap, node_type \*key)::
-
- Search for a node that matches key. If no match is found,
- result is NULL.
-
-node_type *foo_nsearch(struct trp_root \*treap, node_type \*key)::
-
- Like `foo_search`, but if the key is missing return what
- would be key's successor, were key in treap (NULL if no
- successor).
-
-node_type *foo_first(struct trp_root \*treap)::
-
- Find the first item from the treap, in sorted order.
-
-node_type *foo_next(struct trp_root \*treap, node_type \*node)::
-
- Find the next item.
diff --git a/version.c b/version.c
new file mode 100644
index 0000000..f98d5a6
--- /dev/null
+++ b/version.c
@@ -0,0 +1,17 @@
+#include "git-compat-util.h"
+#include "version.h"
+
+const char git_version_string[] = GIT_VERSION;
+
+const char *git_user_agent(void)
+{
+ static const char *agent = NULL;
+
+ if (!agent) {
+ agent = getenv("GIT_USER_AGENT");
+ if (!agent)
+ agent = GIT_USER_AGENT;
+ }
+
+ return agent;
+}
diff --git a/version.h b/version.h
new file mode 100644
index 0000000..fd9cdd6
--- /dev/null
+++ b/version.h
@@ -0,0 +1,8 @@
+#ifndef VERSION_H
+#define VERSION_H
+
+extern const char git_version_string[];
+
+const char *git_user_agent(void);
+
+#endif /* VERSION_H */
diff --git a/walker.c b/walker.c
index dce7128..be389dc 100644
--- a/walker.c
+++ b/walker.c
@@ -190,7 +190,7 @@ static int interpret_target(struct walker *walker, char *target, unsigned char *
{
if (!get_sha1_hex(target, sha1))
return 0;
- if (!check_ref_format(target)) {
+ if (!check_refname_format(target, 0)) {
struct ref *ref = alloc_ref(target);
if (!walker->fetch_ref(walker, ref)) {
hashcpy(sha1, ref->old_sha1);
diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh
index 09feb1f..53a8dd0 100644
--- a/wrap-for-bin.sh
+++ b/wrap-for-bin.sh
@@ -15,7 +15,8 @@ else
export GIT_TEMPLATE_DIR
fi
GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
+GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale'
PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
-export GIT_EXEC_PATH GITPERLLIB PATH
+export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR
exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
diff --git a/wrapper.c b/wrapper.c
index 85f09df..b5e33e4 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -9,6 +9,18 @@ static void do_nothing(size_t size)
static void (*try_to_free_routine)(size_t size) = do_nothing;
+static void memory_limit_check(size_t size)
+{
+ static int limit = -1;
+ if (limit == -1) {
+ const char *env = getenv("GIT_ALLOC_LIMIT");
+ limit = env ? atoi(env) * 1024 : 0;
+ }
+ if (limit && size > limit)
+ die("attempting to allocate %"PRIuMAX" over limit %d",
+ (intmax_t)size, limit);
+}
+
try_to_free_t set_try_to_free_routine(try_to_free_t routine)
{
try_to_free_t old = try_to_free_routine;
@@ -32,7 +44,10 @@ char *xstrdup(const char *str)
void *xmalloc(size_t size)
{
- void *ret = malloc(size);
+ void *ret;
+
+ memory_limit_check(size);
+ ret = malloc(size);
if (!ret && !size)
ret = malloc(1);
if (!ret) {
@@ -79,7 +94,10 @@ char *xstrndup(const char *str, size_t len)
void *xrealloc(void *ptr, size_t size)
{
- void *ret = realloc(ptr, size);
+ void *ret;
+
+ memory_limit_check(size);
+ ret = realloc(ptr, size);
if (!ret && !size)
ret = realloc(ptr, 1);
if (!ret) {
@@ -95,7 +113,10 @@ void *xrealloc(void *ptr, size_t size)
void *xcalloc(size_t nmemb, size_t size)
{
- void *ret = calloc(nmemb, size);
+ void *ret;
+
+ memory_limit_check(size * nmemb);
+ ret = calloc(nmemb, size);
if (!ret && (!nmemb || !size))
ret = calloc(1, 1);
if (!ret) {
@@ -381,3 +402,15 @@ int remove_or_warn(unsigned int mode, const char *file)
{
return S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file);
}
+
+struct passwd *xgetpwuid_self(void)
+{
+ struct passwd *pw;
+
+ errno = 0;
+ pw = getpwuid(getuid());
+ if (!pw)
+ die(_("unable to look up current user in the passwd file: %s"),
+ errno ? strerror(errno) : _("no such user"));
+ return pw;
+}
diff --git a/ws.c b/ws.c
index 9fb9b14..b498d75 100644
--- a/ws.c
+++ b/ws.c
@@ -88,7 +88,7 @@ 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)) {
+ if (!git_check_attr(pathname, 1, &attr_whitespace_rule)) {
const char *value;
value = attr_whitespace_rule.value;
diff --git a/wt-status.c b/wt-status.c
index c01838c..c749267 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -11,6 +11,8 @@
#include "remote.h"
#include "refs.h"
#include "submodule.h"
+#include "column.h"
+#include "strbuf.h"
static char default_wt_status_colors[][COLOR_MAXLEN] = {
GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -22,11 +24,14 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = {
GIT_COLOR_GREEN, /* WT_STATUS_LOCAL_BRANCH */
GIT_COLOR_RED, /* WT_STATUS_REMOTE_BRANCH */
GIT_COLOR_NIL, /* WT_STATUS_ONBRANCH */
+ GIT_COLOR_NORMAL, /* WT_STATUS_IN_PROGRESS */
};
static const char *color(int slot, struct wt_status *s)
{
- const char *c = s->use_color > 0 ? s->color_palette[slot] : "";
+ const char *c = "";
+ if (want_color(s->use_color))
+ c = s->color_palette[slot];
if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
c = s->color_palette[WT_STATUS_HEADER];
return c;
@@ -109,7 +114,6 @@ void status_printf_more(struct wt_status *s, const char *color,
void wt_status_prepare(struct wt_status *s)
{
unsigned char sha1[20];
- const char *head;
memset(s, 0, sizeof(*s));
memcpy(s->color_palette, default_wt_status_colors,
@@ -117,8 +121,7 @@ void wt_status_prepare(struct wt_status *s)
s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
s->use_color = -1;
s->relative_paths = 1;
- head = resolve_ref("HEAD", sha1, 0, NULL);
- s->branch = head ? xstrdup(head) : NULL;
+ s->branch = resolve_refdup("HEAD", sha1, 0, NULL);
s->reference = "HEAD";
s->fp = stdout;
s->index_file = get_index_file();
@@ -129,9 +132,34 @@ void wt_status_prepare(struct wt_status *s)
static void wt_status_print_unmerged_header(struct wt_status *s)
{
+ int i;
+ int del_mod_conflict = 0;
+ int both_deleted = 0;
+ int not_deleted = 0;
const char *c = color(WT_STATUS_HEADER, s);
status_printf_ln(s, c, _("Unmerged paths:"));
+
+ for (i = 0; i < s->change.nr; i++) {
+ struct string_list_item *it = &(s->change.items[i]);
+ struct wt_status_change_data *d = it->util;
+
+ switch (d->stagemask) {
+ case 0:
+ break;
+ case 1:
+ both_deleted = 1;
+ break;
+ case 3:
+ case 5:
+ del_mod_conflict = 1;
+ break;
+ default:
+ not_deleted = 1;
+ break;
+ }
+ }
+
if (!advice_status_hints)
return;
if (s->whence != FROM_COMMIT)
@@ -140,7 +168,17 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
status_printf_ln(s, c, _(" (use \"git reset %s <file>...\" to unstage)"), s->reference);
else
status_printf_ln(s, c, _(" (use \"git rm --cached <file>...\" to unstage)"));
- status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+
+ if (!both_deleted) {
+ if (!del_mod_conflict)
+ status_printf_ln(s, c, _(" (use \"git add <file>...\" to mark resolution)"));
+ else
+ status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+ } else if (!del_mod_conflict && !not_deleted) {
+ status_printf_ln(s, c, _(" (use \"git rm <file>...\" to mark resolution)"));
+ } else {
+ status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+ }
status_printf_ln(s, c, "");
}
@@ -641,6 +679,8 @@ static void wt_status_print_other(struct wt_status *s,
{
int i;
struct strbuf buf = STRBUF_INIT;
+ static struct string_list output = STRING_LIST_INIT_DUP;
+ struct column_options copts;
if (!l->nr)
return;
@@ -649,12 +689,33 @@ static void wt_status_print_other(struct wt_status *s,
for (i = 0; i < l->nr; i++) {
struct string_list_item *it;
+ const char *path;
it = &(l->items[i]);
+ path = quote_path(it->string, strlen(it->string),
+ &buf, s->prefix);
+ if (column_active(s->colopts)) {
+ string_list_append(&output, path);
+ continue;
+ }
status_printf(s, color(WT_STATUS_HEADER, s), "\t");
status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
- "%s\n", quote_path(it->string, strlen(it->string),
- &buf, s->prefix));
+ "%s\n", path);
}
+
+ strbuf_release(&buf);
+ if (!column_active(s->colopts))
+ return;
+
+ strbuf_addf(&buf, "%s#\t%s",
+ color(WT_STATUS_HEADER, s),
+ color(WT_STATUS_UNTRACKED, s));
+ memset(&copts, 0, sizeof(copts));
+ copts.padding = 1;
+ copts.indent = buf.buf;
+ if (want_color(s->use_color))
+ copts.nl = GIT_COLOR_RESET "\n";
+ print_columns(&output, s->colopts, &copts);
+ string_list_clear(&output, 0);
strbuf_release(&buf);
}
@@ -681,7 +742,7 @@ static void wt_status_print_verbose(struct wt_status *s)
* will have checked isatty on stdout).
*/
if (s->fp != stdout)
- DIFF_OPT_CLR(&rev.diffopt, COLOR_DIFF);
+ rev.diffopt.use_color = 0;
run_diff_index(&rev, 1);
}
@@ -704,6 +765,211 @@ static void wt_status_print_tracking(struct wt_status *s)
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
}
+static int has_unmerged(struct wt_status *s)
+{
+ int i;
+
+ for (i = 0; i < s->change.nr; i++) {
+ struct wt_status_change_data *d;
+ d = s->change.items[i].util;
+ if (d->stagemask)
+ return 1;
+ }
+ return 0;
+}
+
+static void show_merge_in_progress(struct wt_status *s,
+ struct wt_status_state *state,
+ const char *color)
+{
+ if (has_unmerged(s)) {
+ status_printf_ln(s, color, _("You have unmerged paths."));
+ if (advice_status_hints)
+ status_printf_ln(s, color,
+ _(" (fix conflicts and run \"git commit\")"));
+ } else {
+ status_printf_ln(s, color,
+ _("All conflicts fixed but you are still merging."));
+ if (advice_status_hints)
+ status_printf_ln(s, color,
+ _(" (use \"git commit\" to conclude merge)"));
+ }
+ wt_status_print_trailer(s);
+}
+
+static void show_am_in_progress(struct wt_status *s,
+ struct wt_status_state *state,
+ const char *color)
+{
+ status_printf_ln(s, color,
+ _("You are in the middle of an am session."));
+ if (state->am_empty_patch)
+ status_printf_ln(s, color,
+ _("The current patch is empty."));
+ if (advice_status_hints) {
+ if (!state->am_empty_patch)
+ status_printf_ln(s, color,
+ _(" (fix conflicts and then run \"git am --resolved\")"));
+ status_printf_ln(s, color,
+ _(" (use \"git am --skip\" to skip this patch)"));
+ status_printf_ln(s, color,
+ _(" (use \"git am --abort\" to restore the original branch)"));
+ }
+ wt_status_print_trailer(s);
+}
+
+static char *read_line_from_git_path(const char *filename)
+{
+ struct strbuf buf = STRBUF_INIT;
+ FILE *fp = fopen(git_path("%s", filename), "r");
+ if (!fp) {
+ strbuf_release(&buf);
+ return NULL;
+ }
+ strbuf_getline(&buf, fp, '\n');
+ if (!fclose(fp)) {
+ return strbuf_detach(&buf, NULL);
+ } else {
+ strbuf_release(&buf);
+ return NULL;
+ }
+}
+
+static int split_commit_in_progress(struct wt_status *s)
+{
+ int split_in_progress = 0;
+ char *head = read_line_from_git_path("HEAD");
+ char *orig_head = read_line_from_git_path("ORIG_HEAD");
+ char *rebase_amend = read_line_from_git_path("rebase-merge/amend");
+ char *rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head");
+
+ if (!head || !orig_head || !rebase_amend || !rebase_orig_head ||
+ !s->branch || strcmp(s->branch, "HEAD"))
+ return split_in_progress;
+
+ if (!strcmp(rebase_amend, rebase_orig_head)) {
+ if (strcmp(head, rebase_amend))
+ split_in_progress = 1;
+ } else if (strcmp(orig_head, rebase_orig_head)) {
+ split_in_progress = 1;
+ }
+
+ if (!s->amend && !s->nowarn && !s->workdir_dirty)
+ split_in_progress = 0;
+
+ free(head);
+ free(orig_head);
+ free(rebase_amend);
+ free(rebase_orig_head);
+ return split_in_progress;
+}
+
+static void show_rebase_in_progress(struct wt_status *s,
+ struct wt_status_state *state,
+ const char *color)
+{
+ struct stat st;
+
+ if (has_unmerged(s)) {
+ status_printf_ln(s, color, _("You are currently rebasing."));
+ if (advice_status_hints) {
+ status_printf_ln(s, color,
+ _(" (fix conflicts and then run \"git rebase --continue\")"));
+ status_printf_ln(s, color,
+ _(" (use \"git rebase --skip\" to skip this patch)"));
+ status_printf_ln(s, color,
+ _(" (use \"git rebase --abort\" to check out the original branch)"));
+ }
+ } else if (state->rebase_in_progress || !stat(git_path("MERGE_MSG"), &st)) {
+ status_printf_ln(s, color, _("You are currently rebasing."));
+ if (advice_status_hints)
+ status_printf_ln(s, color,
+ _(" (all conflicts fixed: run \"git rebase --continue\")"));
+ } else if (split_commit_in_progress(s)) {
+ status_printf_ln(s, color, _("You are currently splitting a commit during a rebase."));
+ if (advice_status_hints)
+ status_printf_ln(s, color,
+ _(" (Once your working directory is clean, run \"git rebase --continue\")"));
+ } else {
+ status_printf_ln(s, color, _("You are currently editing a commit during a rebase."));
+ if (advice_status_hints && !s->amend) {
+ status_printf_ln(s, color,
+ _(" (use \"git commit --amend\" to amend the current commit)"));
+ status_printf_ln(s, color,
+ _(" (use \"git rebase --continue\" once you are satisfied with your changes)"));
+ }
+ }
+ wt_status_print_trailer(s);
+}
+
+static void show_cherry_pick_in_progress(struct wt_status *s,
+ struct wt_status_state *state,
+ const char *color)
+{
+ status_printf_ln(s, color, _("You are currently cherry-picking."));
+ if (advice_status_hints) {
+ if (has_unmerged(s))
+ status_printf_ln(s, color,
+ _(" (fix conflicts and run \"git commit\")"));
+ else
+ status_printf_ln(s, color,
+ _(" (all conflicts fixed: run \"git commit\")"));
+ }
+ wt_status_print_trailer(s);
+}
+
+static void show_bisect_in_progress(struct wt_status *s,
+ struct wt_status_state *state,
+ const char *color)
+{
+ status_printf_ln(s, color, _("You are currently bisecting."));
+ if (advice_status_hints)
+ status_printf_ln(s, color,
+ _(" (use \"git bisect reset\" to get back to the original branch)"));
+ wt_status_print_trailer(s);
+}
+
+static void wt_status_print_state(struct wt_status *s)
+{
+ const char *state_color = color(WT_STATUS_IN_PROGRESS, s);
+ struct wt_status_state state;
+ struct stat st;
+
+ memset(&state, 0, sizeof(state));
+
+ if (!stat(git_path("MERGE_HEAD"), &st)) {
+ state.merge_in_progress = 1;
+ } else if (!stat(git_path("rebase-apply"), &st)) {
+ if (!stat(git_path("rebase-apply/applying"), &st)) {
+ state.am_in_progress = 1;
+ if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
+ state.am_empty_patch = 1;
+ } else {
+ state.rebase_in_progress = 1;
+ }
+ } else if (!stat(git_path("rebase-merge"), &st)) {
+ if (!stat(git_path("rebase-merge/interactive"), &st))
+ state.rebase_interactive_in_progress = 1;
+ else
+ state.rebase_in_progress = 1;
+ } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) {
+ state.cherry_pick_in_progress = 1;
+ }
+ if (!stat(git_path("BISECT_LOG"), &st))
+ state.bisect_in_progress = 1;
+
+ if (state.merge_in_progress)
+ show_merge_in_progress(s, &state, state_color);
+ else if (state.am_in_progress)
+ show_am_in_progress(s, &state, state_color);
+ else if (state.rebase_in_progress || state.rebase_interactive_in_progress)
+ show_rebase_in_progress(s, &state, state_color);
+ else if (state.cherry_pick_in_progress)
+ show_cherry_pick_in_progress(s, &state, state_color);
+ if (state.bisect_in_progress)
+ show_bisect_in_progress(s, &state, state_color);
+}
+
void wt_status_print(struct wt_status *s)
{
const char *branch_color = color(WT_STATUS_ONBRANCH, s);
@@ -726,6 +992,7 @@ void wt_status_print(struct wt_status *s)
wt_status_print_tracking(s);
}
+ wt_status_print_state(s);
if (s->is_initial) {
status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
@@ -777,7 +1044,7 @@ void wt_status_print(struct wt_status *s)
}
}
-static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it,
+static void wt_shortstatus_unmerged(struct string_list_item *it,
struct wt_status *s)
{
struct wt_status_change_data *d = it->util;
@@ -793,7 +1060,7 @@ static void wt_shortstatus_unmerged(int null_termination, struct string_list_ite
case 7: how = "UU"; break; /* both modified */
}
color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
- if (null_termination) {
+ if (s->null_termination) {
fprintf(stdout, " %s%c", it->string, 0);
} else {
struct strbuf onebuf = STRBUF_INIT;
@@ -804,7 +1071,7 @@ static void wt_shortstatus_unmerged(int null_termination, struct string_list_ite
}
}
-static void wt_shortstatus_status(int null_termination, struct string_list_item *it,
+static void wt_shortstatus_status(struct string_list_item *it,
struct wt_status *s)
{
struct wt_status_change_data *d = it->util;
@@ -818,7 +1085,7 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item
else
putchar(' ');
putchar(' ');
- if (null_termination) {
+ if (s->null_termination) {
fprintf(stdout, "%s%c", it->string, 0);
if (d->head_path)
fprintf(stdout, "%s%c", d->head_path, 0);
@@ -846,10 +1113,10 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item
}
}
-static void wt_shortstatus_other(int null_termination, struct string_list_item *it,
+static void wt_shortstatus_other(struct string_list_item *it,
struct wt_status *s, const char *sign)
{
- if (null_termination) {
+ if (s->null_termination) {
fprintf(stdout, "%s %s%c", sign, it->string, 0);
} else {
struct strbuf onebuf = STRBUF_INIT;
@@ -889,8 +1156,8 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
if (s->is_initial)
color_fprintf(s->fp, header_color, _("Initial commit on "));
if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
- color_fprintf_ln(s->fp, branch_color_local,
- "%s", branch_name);
+ color_fprintf(s->fp, branch_color_local, "%s", branch_name);
+ fputc(s->null_termination ? '\0' : '\n', s->fp);
return;
}
@@ -914,14 +1181,15 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
}
- color_fprintf_ln(s->fp, header_color, "]");
+ color_fprintf(s->fp, header_color, "]");
+ fputc(s->null_termination ? '\0' : '\n', s->fp);
}
-void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch)
+void wt_shortstatus_print(struct wt_status *s)
{
int i;
- if (show_branch)
+ if (s->show_branch)
wt_shortstatus_print_tracking(s);
for (i = 0; i < s->change.nr; i++) {
@@ -931,28 +1199,28 @@ void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_br
it = &(s->change.items[i]);
d = it->util;
if (d->stagemask)
- wt_shortstatus_unmerged(null_termination, it, s);
+ wt_shortstatus_unmerged(it, s);
else
- wt_shortstatus_status(null_termination, it, s);
+ wt_shortstatus_status(it, s);
}
for (i = 0; i < s->untracked.nr; i++) {
struct string_list_item *it;
it = &(s->untracked.items[i]);
- wt_shortstatus_other(null_termination, it, s, "??");
+ wt_shortstatus_other(it, s, "??");
}
for (i = 0; i < s->ignored.nr; i++) {
struct string_list_item *it;
it = &(s->ignored.items[i]);
- wt_shortstatus_other(null_termination, it, s, "!!");
+ wt_shortstatus_other(it, s, "!!");
}
}
-void wt_porcelain_print(struct wt_status *s, int null_termination)
+void wt_porcelain_print(struct wt_status *s)
{
s->use_color = 0;
s->relative_paths = 0;
s->prefix = NULL;
- wt_shortstatus_print(s, null_termination, 0);
+ wt_shortstatus_print(s);
}
diff --git a/wt-status.h b/wt-status.h
index 682b4c8..c1066a0 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -15,6 +15,7 @@ enum color_wt_status {
WT_STATUS_LOCAL_BRANCH,
WT_STATUS_REMOTE_BRANCH,
WT_STATUS_ONBRANCH,
+ WT_STATUS_IN_PROGRESS,
WT_STATUS_MAXSLOT
};
@@ -56,6 +57,9 @@ struct wt_status {
enum untracked_status_type show_untracked_files;
const char *ignore_submodule_arg;
char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
+ unsigned colopts;
+ int null_termination;
+ int show_branch;
/* These are computed during processing of the individual sections */
int commitable;
@@ -68,12 +72,22 @@ struct wt_status {
struct string_list ignored;
};
+struct wt_status_state {
+ int merge_in_progress;
+ int am_in_progress;
+ int am_empty_patch;
+ int rebase_in_progress;
+ int rebase_interactive_in_progress;
+ int cherry_pick_in_progress;
+ int bisect_in_progress;
+};
+
void wt_status_prepare(struct wt_status *s);
void wt_status_print(struct wt_status *s);
void wt_status_collect(struct wt_status *s);
-void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch);
-void wt_porcelain_print(struct wt_status *s, int null_termination);
+void wt_shortstatus_print(struct wt_status *s);
+void wt_porcelain_print(struct wt_status *s);
void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...)
;
diff --git a/xdiff-interface.c b/xdiff-interface.c
index 0e2c169..ecfa05f 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -156,50 +156,6 @@ int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
return ret;
}
-struct xdiff_emit_hunk_state {
- xdiff_emit_hunk_consume_fn consume;
- void *consume_callback_data;
-};
-
-static int process_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
- xdemitconf_t const *xecfg)
-{
- long s1, s2, same, p_next, t_next;
- xdchange_t *xch, *xche;
- struct xdiff_emit_hunk_state *state = ecb->priv;
- xdiff_emit_hunk_consume_fn fn = state->consume;
- void *consume_callback_data = state->consume_callback_data;
-
- for (xch = xscr; xch; xch = xche->next) {
- xche = xdl_get_hunk(xch, xecfg);
-
- s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
- s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
- same = s2 + XDL_MAX(xch->i1 - s1, 0);
- p_next = xche->i1 + xche->chg1;
- t_next = xche->i2 + xche->chg2;
-
- fn(consume_callback_data, same, p_next, t_next);
- }
- return 0;
-}
-
-int xdi_diff_hunks(mmfile_t *mf1, mmfile_t *mf2,
- xdiff_emit_hunk_consume_fn fn, void *consume_callback_data,
- xpparam_t const *xpp, xdemitconf_t *xecfg)
-{
- struct xdiff_emit_hunk_state state;
- xdemitcb_t ecb;
-
- memset(&state, 0, sizeof(state));
- memset(&ecb, 0, sizeof(ecb));
- state.consume = fn;
- state.consume_callback_data = consume_callback_data;
- xecfg->emit_func = (void (*)())process_diff;
- ecb.priv = &state;
- return xdi_diff(mf1, mf2, xpp, xecfg, &ecb);
-}
-
int read_mmfile(mmfile_t *ptr, const char *filename)
{
struct stat st;
diff --git a/xdiff-interface.h b/xdiff-interface.h
index 49d1116..eff7762 100644
--- a/xdiff-interface.h
+++ b/xdiff-interface.h
@@ -4,15 +4,11 @@
#include "xdiff/xdiff.h"
typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long);
-typedef void (*xdiff_emit_hunk_consume_fn)(void *, long, long, long);
int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb);
int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
xdiff_emit_consume_fn fn, void *consume_callback_data,
xpparam_t const *xpp, xdemitconf_t const *xecfg);
-int xdi_diff_hunks(mmfile_t *mf1, mmfile_t *mf2,
- xdiff_emit_hunk_consume_fn fn, void *consume_callback_data,
- xpparam_t const *xpp, xdemitconf_t *xecfg);
int parse_hunk_header(char *line, int len,
int *ob, int *on,
int *nb, int *nn);
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index 711048e..219a3bb 100644
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
@@ -32,16 +32,16 @@ extern "C" {
#define XDF_IGNORE_WHITESPACE (1 << 2)
#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
-#define XDF_PATIENCE_DIFF (1 << 5)
#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
-#define XDL_PATCH_NORMAL '-'
-#define XDL_PATCH_REVERSE '+'
-#define XDL_PATCH_MODEMASK ((1 << 8) - 1)
-#define XDL_PATCH_IGNOREBSPACE (1 << 8)
+#define XDF_PATIENCE_DIFF (1 << 5)
+#define XDF_HISTOGRAM_DIFF (1 << 6)
+#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
+#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
#define XDL_EMIT_FUNCNAMES (1 << 0)
#define XDL_EMIT_COMMON (1 << 1)
+#define XDL_EMIT_FUNCCONTEXT (1 << 2)
#define XDL_MMB_READONLY (1 << 0)
@@ -86,13 +86,17 @@ typedef struct s_xdemitcb {
typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
+typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a,
+ long start_b, long count_b,
+ void *cb_data);
+
typedef struct s_xdemitconf {
long ctxlen;
long interhunkctxlen;
unsigned long flags;
find_func_t find_func;
void *find_func_priv;
- void (*emit_func)();
+ xdl_emit_hunk_consume_func_t hunk_func;
} xdemitconf_t;
typedef struct s_bdiffparam {
@@ -105,7 +109,6 @@ typedef struct s_bdiffparam {
#define xdl_realloc(ptr,x) realloc(ptr,x)
void *xdl_mmfile_first(mmfile_t *mmf, long *size);
-void *xdl_mmfile_next(mmfile_t *mmf, long *size);
long xdl_mmfile_size(mmfile_t *mmf);
int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index da67c04..1b7012a 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -328,9 +328,12 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdalgoenv_t xenv;
diffdata_t dd1, dd2;
- if (xpp->flags & XDF_PATIENCE_DIFF)
+ if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF)
return xdl_do_patience_diff(mf1, mf2, xpp, xe);
+ if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
+ return xdl_do_histogram_diff(mf1, mf2, xpp, xe);
+
if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
return -1;
@@ -535,13 +538,26 @@ void xdl_free_script(xdchange_t *xscr) {
}
}
+static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg)
+{
+ xdchange_t *xch, *xche;
+
+ for (xch = xscr; xch; xch = xche->next) {
+ xche = xdl_get_hunk(xch, xecfg);
+ if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1,
+ xch->i2, xche->i2 + xche->chg2 - xch->i2,
+ ecb->priv) < 0)
+ return -1;
+ }
+ return 0;
+}
int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
xdchange_t *xscr;
xdfenv_t xe;
- emit_func_t ef = xecfg->emit_func ?
- (emit_func_t)xecfg->emit_func : xdl_emit_diff;
+ emit_func_t ef = xecfg->hunk_func ? xdl_call_hunk_func : xdl_emit_diff;
if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) {
diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h
index ad033a8..7a92ea9 100644
--- a/xdiff/xdiffi.h
+++ b/xdiff/xdiffi.h
@@ -57,5 +57,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg);
int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdfenv_t *env);
+int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *env);
#endif /* #if !defined(XDIFFI_H) */
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index 277e2ee..d11dbf9 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -87,7 +87,7 @@ static long def_ff(const char *rec, long len, char *buf, long sz, void *priv)
static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg) {
- xdfile_t *xdf = &xe->xdf1;
+ xdfile_t *xdf = &xe->xdf2;
const char *rchg = xdf->rchg;
long ix;
@@ -100,14 +100,40 @@ static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
return 0;
}
+struct func_line {
+ long len;
+ char buf[80];
+};
+
+static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
+ struct func_line *func_line, long start, long limit)
+{
+ find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
+ long l, size, step = (start > limit) ? -1 : 1;
+ char *buf, dummy[1];
+
+ buf = func_line ? func_line->buf : dummy;
+ size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
+
+ for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+ const char *rec;
+ long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
+ long len = ff(rec, reclen, buf, size, xecfg->find_func_priv);
+ if (len >= 0) {
+ if (func_line)
+ func_line->len = len;
+ return l;
+ }
+ }
+ return -1;
+}
+
int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg) {
long s1, s2, e1, e2, lctx;
xdchange_t *xch, *xche;
- char funcbuf[80];
- long funclen = 0;
long funclineprev = -1;
- find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
+ struct func_line func_line = { 0 };
if (xecfg->flags & XDL_EMIT_COMMON)
return xdl_emit_common(xe, xscr, ecb, xecfg);
@@ -118,6 +144,17 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
+ if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+ long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1);
+ if (fs1 < 0)
+ fs1 = 0;
+ if (fs1 < s1) {
+ s2 -= s1 - fs1;
+ s1 = fs1;
+ }
+ }
+
+ again:
lctx = xecfg->ctxlen;
lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
@@ -125,34 +162,50 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
e1 = xche->i1 + xche->chg1 + lctx;
e2 = xche->i2 + xche->chg2 + lctx;
+ if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+ long fe1 = get_func_line(xe, xecfg, NULL,
+ xche->i1 + xche->chg1,
+ xe->xdf1.nrec);
+ if (fe1 < 0)
+ fe1 = xe->xdf1.nrec;
+ if (fe1 > e1) {
+ e2 += fe1 - e1;
+ e1 = fe1;
+ }
+
+ /*
+ * Overlap with next change? Then include it
+ * in the current hunk and start over to find
+ * its new end.
+ */
+ if (xche->next) {
+ long l = xche->next->i1;
+ if (l <= e1 ||
+ get_func_line(xe, xecfg, NULL, l, e1) < 0) {
+ xche = xche->next;
+ goto again;
+ }
+ }
+ }
+
/*
* Emit current hunk header.
*/
if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
- long l;
- for (l = s1 - 1; l >= 0 && l > funclineprev; l--) {
- const char *rec;
- long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
- long newfunclen = ff(rec, reclen, funcbuf,
- sizeof(funcbuf),
- xecfg->find_func_priv);
- if (newfunclen >= 0) {
- funclen = newfunclen;
- break;
- }
- }
+ get_func_line(xe, xecfg, &func_line,
+ s1 - 1, funclineprev);
funclineprev = s1 - 1;
}
if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
- funcbuf, funclen, ecb) < 0)
+ func_line.buf, func_line.len, ecb) < 0)
return -1;
/*
* Emit pre-context.
*/
- for (; s1 < xch->i1; s1++)
- if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+ for (; s2 < xch->i2; s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
return -1;
for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) {
@@ -160,7 +213,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
* Merge previous with current change atom.
*/
for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++)
- if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
return -1;
/*
@@ -186,8 +239,8 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
/*
* Emit post-context.
*/
- for (s1 = xche->i1 + xche->chg1; s1 < e1; s1++)
- if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+ for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
return -1;
}
diff --git a/xdiff/xhistogram.c b/xdiff/xhistogram.c
new file mode 100644
index 0000000..bf99787
--- /dev/null
+++ b/xdiff/xhistogram.c
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in JGit's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+#define MAX_PTR UINT_MAX
+#define MAX_CNT UINT_MAX
+
+#define LINE_END(n) (line##n + count##n - 1)
+#define LINE_END_PTR(n) (*line##n + *count##n - 1)
+
+struct histindex {
+ struct record {
+ unsigned int ptr, cnt;
+ struct record *next;
+ } **records, /* an ocurrence */
+ **line_map; /* map of line to record chain */
+ chastore_t rcha;
+ unsigned int *next_ptrs;
+ unsigned int table_bits,
+ records_size,
+ line_map_size;
+
+ unsigned int max_chain_length,
+ key_shift,
+ ptr_shift;
+
+ unsigned int cnt,
+ has_common;
+
+ xdfenv_t *env;
+ xpparam_t const *xpp;
+};
+
+struct region {
+ unsigned int begin1, end1;
+ unsigned int begin2, end2;
+};
+
+#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift])
+
+#define NEXT_PTR(index, ptr) \
+ (index->next_ptrs[(ptr) - index->ptr_shift])
+
+#define CNT(index, ptr) \
+ ((LINE_MAP(index, ptr))->cnt)
+
+#define REC(env, s, l) \
+ (env->xdf##s.recs[l - 1])
+
+static int cmp_recs(xpparam_t const *xpp,
+ xrecord_t *r1, xrecord_t *r2)
+{
+ return r1->ha == r2->ha &&
+ xdl_recmatch(r1->ptr, r1->size, r2->ptr, r2->size,
+ xpp->flags);
+}
+
+#define CMP_ENV(xpp, env, s1, l1, s2, l2) \
+ (cmp_recs(xpp, REC(env, s1, l1), REC(env, s2, l2)))
+
+#define CMP(i, s1, l1, s2, l2) \
+ (cmp_recs(i->xpp, REC(i->env, s1, l1), REC(i->env, s2, l2)))
+
+#define TABLE_HASH(index, side, line) \
+ XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits)
+
+static int scanA(struct histindex *index, int line1, int count1)
+{
+ unsigned int ptr, tbl_idx;
+ unsigned int chain_len;
+ struct record **rec_chain, *rec;
+
+ for (ptr = LINE_END(1); line1 <= ptr; ptr--) {
+ tbl_idx = TABLE_HASH(index, 1, ptr);
+ rec_chain = index->records + tbl_idx;
+ rec = *rec_chain;
+
+ chain_len = 0;
+ while (rec) {
+ if (CMP(index, 1, rec->ptr, 1, ptr)) {
+ /*
+ * ptr is identical to another element. Insert
+ * it onto the front of the existing element
+ * chain.
+ */
+ NEXT_PTR(index, ptr) = rec->ptr;
+ rec->ptr = ptr;
+ /* cap rec->cnt at MAX_CNT */
+ rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1);
+ LINE_MAP(index, ptr) = rec;
+ goto continue_scan;
+ }
+
+ rec = rec->next;
+ chain_len++;
+ }
+
+ if (chain_len == index->max_chain_length)
+ return -1;
+
+ /*
+ * This is the first time we have ever seen this particular
+ * element in the sequence. Construct a new chain for it.
+ */
+ if (!(rec = xdl_cha_alloc(&index->rcha)))
+ return -1;
+ rec->ptr = ptr;
+ rec->cnt = 1;
+ rec->next = *rec_chain;
+ *rec_chain = rec;
+ LINE_MAP(index, ptr) = rec;
+
+continue_scan:
+ ; /* no op */
+ }
+
+ return 0;
+}
+
+static int try_lcs(struct histindex *index, struct region *lcs, int b_ptr,
+ int line1, int count1, int line2, int count2)
+{
+ unsigned int b_next = b_ptr + 1;
+ struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)];
+ unsigned int as, ae, bs, be, np, rc;
+ int should_break;
+
+ for (; rec; rec = rec->next) {
+ if (rec->cnt > index->cnt) {
+ if (!index->has_common)
+ index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr);
+ continue;
+ }
+
+ as = rec->ptr;
+ if (!CMP(index, 1, as, 2, b_ptr))
+ continue;
+
+ index->has_common = 1;
+ for (;;) {
+ should_break = 0;
+ np = NEXT_PTR(index, as);
+ bs = b_ptr;
+ ae = as;
+ be = bs;
+ rc = rec->cnt;
+
+ while (line1 < as && line2 < bs
+ && CMP(index, 1, as - 1, 2, bs - 1)) {
+ as--;
+ bs--;
+ if (1 < rc)
+ rc = XDL_MIN(rc, CNT(index, as));
+ }
+ while (ae < LINE_END(1) && be < LINE_END(2)
+ && CMP(index, 1, ae + 1, 2, be + 1)) {
+ ae++;
+ be++;
+ if (1 < rc)
+ rc = XDL_MIN(rc, CNT(index, ae));
+ }
+
+ if (b_next <= be)
+ b_next = be + 1;
+ if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) {
+ lcs->begin1 = as;
+ lcs->begin2 = bs;
+ lcs->end1 = ae;
+ lcs->end2 = be;
+ index->cnt = rc;
+ }
+
+ if (np == 0)
+ break;
+
+ while (np <= ae) {
+ np = NEXT_PTR(index, np);
+ if (np == 0) {
+ should_break = 1;
+ break;
+ }
+ }
+
+ if (should_break)
+ break;
+
+ as = np;
+ }
+ }
+ return b_next;
+}
+
+static int find_lcs(struct histindex *index, struct region *lcs,
+ int line1, int count1, int line2, int count2) {
+ int b_ptr;
+
+ if (scanA(index, line1, count1))
+ return -1;
+
+ index->cnt = index->max_chain_length + 1;
+
+ for (b_ptr = line2; b_ptr <= LINE_END(2); )
+ b_ptr = try_lcs(index, lcs, b_ptr, line1, count1, line2, count2);
+
+ return index->has_common && index->max_chain_length < index->cnt;
+}
+
+static int fall_back_to_classic_diff(struct histindex *index,
+ int line1, int count1, int line2, int count2)
+{
+ xpparam_t xpp;
+ xpp.flags = index->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK;
+
+ return xdl_fall_back_diff(index->env, &xpp,
+ line1, count1, line2, count2);
+}
+
+static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env,
+ int line1, int count1, int line2, int count2)
+{
+ struct histindex index;
+ struct region lcs;
+ int sz;
+ int result = -1;
+
+ if (count1 <= 0 && count2 <= 0)
+ return 0;
+
+ if (LINE_END(1) >= MAX_PTR)
+ return -1;
+
+ if (!count1) {
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ return 0;
+ } else if (!count2) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ return 0;
+ }
+
+ memset(&index, 0, sizeof(index));
+
+ index.env = env;
+ index.xpp = xpp;
+
+ index.records = NULL;
+ index.line_map = NULL;
+ /* in case of early xdl_cha_free() */
+ index.rcha.head = NULL;
+
+ index.table_bits = xdl_hashbits(count1);
+ sz = index.records_size = 1 << index.table_bits;
+ sz *= sizeof(struct record *);
+ if (!(index.records = (struct record **) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.records, 0, sz);
+
+ sz = index.line_map_size = count1;
+ sz *= sizeof(struct record *);
+ if (!(index.line_map = (struct record **) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.line_map, 0, sz);
+
+ sz = index.line_map_size;
+ sz *= sizeof(unsigned int);
+ if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.next_ptrs, 0, sz);
+
+ /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */
+ if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0)
+ goto cleanup;
+
+ index.ptr_shift = line1;
+ index.max_chain_length = 64;
+
+ memset(&lcs, 0, sizeof(lcs));
+ if (find_lcs(&index, &lcs, line1, count1, line2, count2))
+ result = fall_back_to_classic_diff(&index, line1, count1, line2, count2);
+ else {
+ if (lcs.begin1 == 0 && lcs.begin2 == 0) {
+ while (count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ while (count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ result = 0;
+ } else {
+ result = histogram_diff(xpp, env,
+ line1, lcs.begin1 - line1,
+ line2, lcs.begin2 - line2);
+ if (result)
+ goto cleanup;
+ result = histogram_diff(xpp, env,
+ lcs.end1 + 1, LINE_END(1) - lcs.end1,
+ lcs.end2 + 1, LINE_END(2) - lcs.end2);
+ if (result)
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ xdl_free(index.records);
+ xdl_free(index.line_map);
+ xdl_free(index.next_ptrs);
+ xdl_cha_free(&index.rcha);
+
+ return result;
+}
+
+int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env)
+{
+ if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+ return -1;
+
+ return histogram_diff(xpp, env,
+ env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1,
+ env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1);
+}
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
index e42c16a..04e1a1a 100644
--- a/xdiff/xpatience.c
+++ b/xdiff/xpatience.c
@@ -287,34 +287,11 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first,
static int fall_back_to_classic_diff(struct hashmap *map,
int line1, int count1, int line2, int count2)
{
- /*
- * This probably does not work outside Git, since
- * we have a very simple mmfile structure.
- *
- * Note: ideally, we would reuse the prepared environment, but
- * the libxdiff interface does not (yet) allow for diffing only
- * ranges of lines instead of the whole files.
- */
- mmfile_t subfile1, subfile2;
xpparam_t xpp;
- xdfenv_t env;
-
- subfile1.ptr = (char *)map->env->xdf1.recs[line1 - 1]->ptr;
- subfile1.size = map->env->xdf1.recs[line1 + count1 - 2]->ptr +
- map->env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
- subfile2.ptr = (char *)map->env->xdf2.recs[line2 - 1]->ptr;
- subfile2.size = map->env->xdf2.recs[line2 + count2 - 2]->ptr +
- map->env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
- xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF;
- if (xdl_do_diff(&subfile1, &subfile2, &xpp, &env) < 0)
- return -1;
+ xpp.flags = map->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK;
- memcpy(map->env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
- memcpy(map->env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
-
- xdl_free_env(&env);
-
- return 0;
+ return xdl_fall_back_diff(map->env, &xpp,
+ line1, count1, line2, count2);
}
/*
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index 1689085..63a22c6 100644
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
@@ -26,6 +26,8 @@
#define XDL_KPDIS_RUN 4
#define XDL_MAX_EQLIMIT 1024
#define XDL_SIMSCAN_WINDOW 100
+#define XDL_GUESS_NLINES1 256
+#define XDL_GUESS_NLINES2 20
typedef struct s_xdlclass {
@@ -34,6 +36,7 @@ typedef struct s_xdlclass {
char const *line;
long size;
long idx;
+ long len1, len2;
} xdlclass_t;
typedef struct s_xdlclassifier {
@@ -41,6 +44,8 @@ typedef struct s_xdlclassifier {
long hsize;
xdlclass_t **rchash;
chastore_t ncha;
+ xdlclass_t **rcrecs;
+ long alloc;
long count;
long flags;
} xdlclassifier_t;
@@ -50,22 +55,20 @@ typedef struct s_xdlclassifier {
static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
static void xdl_free_classifier(xdlclassifier_t *cf);
-static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
- xrecord_t *rec);
-static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+ unsigned int hbits, xrecord_t *rec);
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
xdlclassifier_t *cf, xdfile_t *xdf);
static void xdl_free_ctx(xdfile_t *xdf);
static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
-static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
- long i;
-
cf->flags = flags;
cf->hbits = xdl_hashbits((unsigned int) size);
@@ -80,8 +83,15 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
xdl_cha_free(&cf->ncha);
return -1;
}
- for (i = 0; i < cf->hsize; i++)
- cf->rchash[i] = NULL;
+ memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *));
+
+ cf->alloc = size;
+ if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) {
+
+ xdl_free(cf->rchash);
+ xdl_cha_free(&cf->ncha);
+ return -1;
+ }
cf->count = 0;
@@ -91,16 +101,18 @@ static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
static void xdl_free_classifier(xdlclassifier_t *cf) {
+ xdl_free(cf->rcrecs);
xdl_free(cf->rchash);
xdl_cha_free(&cf->ncha);
}
-static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
- xrecord_t *rec) {
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+ unsigned int hbits, xrecord_t *rec) {
long hi;
char const *line;
xdlclass_t *rcrec;
+ xdlclass_t **rcrecs;
line = rec->ptr;
hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
@@ -116,13 +128,25 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned
return -1;
}
rcrec->idx = cf->count++;
+ if (cf->count > cf->alloc) {
+ cf->alloc *= 2;
+ if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) {
+
+ return -1;
+ }
+ cf->rcrecs = rcrecs;
+ }
+ cf->rcrecs[rcrec->idx] = rcrec;
rcrec->line = line;
rcrec->size = rec->size;
rcrec->ha = rec->ha;
+ rcrec->len1 = rcrec->len2 = 0;
rcrec->next = cf->rchash[hi];
cf->rchash[hi] = rcrec;
}
+ (pass == 1) ? rcrec->len1++ : rcrec->len2++;
+
rec->ha = (unsigned long) rcrec->idx;
hi = (long) XDL_HASHLONG(rec->ha, hbits);
@@ -133,10 +157,10 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned
}
-static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
xdlclassifier_t *cf, xdfile_t *xdf) {
unsigned int hbits;
- long i, nrec, hsize, bsize;
+ long nrec, hsize, bsize;
unsigned long hav;
char const *blk, *cur, *top, *prev;
xrecord_t *crec;
@@ -146,96 +170,59 @@ static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
char *rchg;
long *rindex;
- if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) {
-
- return -1;
- }
- if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) {
-
- xdl_cha_free(&xdf->rcha);
- return -1;
- }
-
- hbits = xdl_hashbits((unsigned int) narec);
- hsize = 1 << hbits;
- if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) {
-
- xdl_free(recs);
- xdl_cha_free(&xdf->rcha);
- return -1;
+ ha = NULL;
+ rindex = NULL;
+ rchg = NULL;
+ rhash = NULL;
+ recs = NULL;
+
+ if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
+ goto abort;
+ if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *))))
+ goto abort;
+
+ if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
+ hbits = hsize = 0;
+ else {
+ hbits = xdl_hashbits((unsigned int) narec);
+ hsize = 1 << hbits;
+ if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *))))
+ goto abort;
+ memset(rhash, 0, hsize * sizeof(xrecord_t *));
}
- for (i = 0; i < hsize; i++)
- rhash[i] = NULL;
nrec = 0;
if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) {
- for (top = blk + bsize;;) {
- if (cur >= top) {
- if (!(cur = blk = xdl_mmfile_next(mf, &bsize)))
- break;
- top = blk + bsize;
- }
+ for (top = blk + bsize; cur < top; ) {
prev = cur;
hav = xdl_hash_record(&cur, top, xpp->flags);
if (nrec >= narec) {
narec *= 2;
- if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) {
-
- xdl_free(rhash);
- xdl_free(recs);
- xdl_cha_free(&xdf->rcha);
- return -1;
- }
+ if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *))))
+ goto abort;
recs = rrecs;
}
- if (!(crec = xdl_cha_alloc(&xdf->rcha))) {
-
- xdl_free(rhash);
- xdl_free(recs);
- xdl_cha_free(&xdf->rcha);
- return -1;
- }
+ if (!(crec = xdl_cha_alloc(&xdf->rcha)))
+ goto abort;
crec->ptr = prev;
crec->size = (long) (cur - prev);
crec->ha = hav;
recs[nrec++] = crec;
- if (xdl_classify_record(cf, rhash, hbits, crec) < 0) {
-
- xdl_free(rhash);
- xdl_free(recs);
- xdl_cha_free(&xdf->rcha);
- return -1;
- }
+ if ((XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+ xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+ goto abort;
}
}
- if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) {
-
- xdl_free(rhash);
- xdl_free(recs);
- xdl_cha_free(&xdf->rcha);
- return -1;
- }
+ if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char))))
+ goto abort;
memset(rchg, 0, (nrec + 2) * sizeof(char));
- if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long)))) {
-
- xdl_free(rchg);
- xdl_free(rhash);
- xdl_free(recs);
- xdl_cha_free(&xdf->rcha);
- return -1;
- }
- if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long)))) {
-
- xdl_free(rindex);
- xdl_free(rchg);
- xdl_free(rhash);
- xdl_free(recs);
- xdl_cha_free(&xdf->rcha);
- return -1;
- }
+ if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long))))
+ goto abort;
+ if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long))))
+ goto abort;
xdf->nrec = nrec;
xdf->recs = recs;
@@ -249,6 +236,15 @@ static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
xdf->dend = nrec - 1;
return 0;
+
+abort:
+ xdl_free(ha);
+ xdl_free(rindex);
+ xdl_free(rchg);
+ xdl_free(rhash);
+ xdl_free(recs);
+ xdl_cha_free(&xdf->rcha);
+ return -1;
}
@@ -265,39 +261,52 @@ static void xdl_free_ctx(xdfile_t *xdf) {
int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdfenv_t *xe) {
- long enl1, enl2;
+ long enl1, enl2, sample;
xdlclassifier_t cf;
- enl1 = xdl_guess_lines(mf1) + 1;
- enl2 = xdl_guess_lines(mf2) + 1;
+ memset(&cf, 0, sizeof(cf));
+
+ /*
+ * For histogram diff, we can afford a smaller sample size and
+ * thus a poorer estimate of the number of lines, as the hash
+ * table (rhash) won't be filled up/grown. The number of lines
+ * (nrecs) will be updated correctly anyway by
+ * xdl_prepare_ctx().
+ */
+ sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
+ ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
- if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) {
+ enl1 = xdl_guess_lines(mf1, sample) + 1;
+ enl2 = xdl_guess_lines(mf2, sample) + 1;
+ if (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF &&
+ xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
return -1;
- }
- if (xdl_prepare_ctx(mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+ if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
xdl_free_classifier(&cf);
return -1;
}
- if (xdl_prepare_ctx(mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+ if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
xdl_free_ctx(&xe->xdf1);
xdl_free_classifier(&cf);
return -1;
}
- xdl_free_classifier(&cf);
-
- if (!(xpp->flags & XDF_PATIENCE_DIFF) &&
- xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) {
+ if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
+ (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+ xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
xdl_free_ctx(&xe->xdf2);
xdl_free_ctx(&xe->xdf1);
return -1;
}
+ if (!(xpp->flags & XDF_HISTOGRAM_DIFF))
+ xdl_free_classifier(&cf);
+
return 0;
}
@@ -372,11 +381,10 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
* matches on the other file. Also, lines that have multiple matches
* might be potentially discarded if they happear in a run of discardable.
*/
-static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) {
- long i, nm, rhi, nreff, mlim;
- unsigned long hav;
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+ long i, nm, nreff, mlim;
xrecord_t **recs;
- xrecord_t *rec;
+ xdlclass_t *rcrec;
char *dis, *dis1, *dis2;
if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) {
@@ -390,22 +398,16 @@ static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) {
if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
mlim = XDL_MAX_EQLIMIT;
for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
- hav = (*recs)->ha;
- rhi = (long) XDL_HASHLONG(hav, xdf2->hbits);
- for (nm = 0, rec = xdf2->rhash[rhi]; rec; rec = rec->next)
- if (rec->ha == hav && ++nm == mlim)
- break;
+ rcrec = cf->rcrecs[(*recs)->ha];
+ nm = rcrec ? rcrec->len2 : 0;
dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
}
if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
mlim = XDL_MAX_EQLIMIT;
for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
- hav = (*recs)->ha;
- rhi = (long) XDL_HASHLONG(hav, xdf1->hbits);
- for (nm = 0, rec = xdf1->rhash[rhi]; rec; rec = rec->next)
- if (rec->ha == hav && ++nm == mlim)
- break;
+ rcrec = cf->rcrecs[(*recs)->ha];
+ nm = rcrec ? rcrec->len1 : 0;
dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
}
@@ -468,10 +470,10 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
}
-static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2) {
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
if (xdl_trim_ends(xdf1, xdf2) < 0 ||
- xdl_cleanup_records(xdf1, xdf2) < 0) {
+ xdl_cleanup_records(cf, xdf1, xdf2) < 0) {
return -1;
}
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index ab65034..9504eae 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -20,14 +20,12 @@
*
*/
+#include <limits.h>
+#include <assert.h>
#include "xinclude.h"
-#define XDL_GUESS_NLINES 256
-
-
-
long xdl_bogosqrt(long n) {
long i;
@@ -71,12 +69,6 @@ void *xdl_mmfile_first(mmfile_t *mmf, long *size)
}
-void *xdl_mmfile_next(mmfile_t *mmf, long *size)
-{
- return NULL;
-}
-
-
long xdl_mmfile_size(mmfile_t *mmf)
{
return mmf->size;
@@ -130,47 +122,12 @@ void *xdl_cha_alloc(chastore_t *cha) {
return data;
}
-
-void *xdl_cha_first(chastore_t *cha) {
- chanode_t *sncur;
-
- if (!(cha->sncur = sncur = cha->head))
- return NULL;
-
- cha->scurr = 0;
-
- return (char *) sncur + sizeof(chanode_t) + cha->scurr;
-}
-
-
-void *xdl_cha_next(chastore_t *cha) {
- chanode_t *sncur;
-
- if (!(sncur = cha->sncur))
- return NULL;
- cha->scurr += cha->isize;
- if (cha->scurr == sncur->icurr) {
- if (!(sncur = cha->sncur = sncur->next))
- return NULL;
- cha->scurr = 0;
- }
-
- return (char *) sncur + sizeof(chanode_t) + cha->scurr;
-}
-
-
-long xdl_guess_lines(mmfile_t *mf) {
+long xdl_guess_lines(mmfile_t *mf, long sample) {
long nl = 0, size, tsize = 0;
char const *data, *cur, *top;
if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) {
- for (top = data + size; nl < XDL_GUESS_NLINES;) {
- if (cur >= top) {
- tsize += (long) (cur - data);
- if (!(cur = data = xdl_mmfile_next(mf, &size)))
- break;
- top = data + size;
- }
+ for (top = data + size; nl < sample && cur < top; ) {
nl++;
if (!(cur = memchr(cur, '\n', top - cur)))
cur = top;
@@ -292,6 +249,109 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
return ha;
}
+#ifdef XDL_FAST_HASH
+
+#define REPEAT_BYTE(x) ((~0ul / 0xff) * (x))
+
+#define ONEBYTES REPEAT_BYTE(0x01)
+#define NEWLINEBYTES REPEAT_BYTE(0x0a)
+#define HIGHBITS REPEAT_BYTE(0x80)
+
+/* Return the high bit set in the first byte that is a zero */
+static inline unsigned long has_zero(unsigned long a)
+{
+ return ((a - ONEBYTES) & ~a) & HIGHBITS;
+}
+
+static inline long count_masked_bytes(unsigned long mask)
+{
+ if (sizeof(long) == 8) {
+ /*
+ * Jan Achrenius on G+: microoptimized version of
+ * the simpler "(mask & ONEBYTES) * ONEBYTES >> 56"
+ * that works for the bytemasks without having to
+ * mask them first.
+ */
+ /*
+ * return mask * 0x0001020304050608 >> 56;
+ *
+ * Doing it like this avoids warnings on 32-bit machines.
+ */
+ long a = (REPEAT_BYTE(0x01) / 0xff + 1);
+ return mask * a >> (sizeof(long) * 7);
+ } else {
+ /* Carl Chatfield / Jan Achrenius G+ version for 32-bit */
+ /* (000000 0000ff 00ffff ffffff) -> ( 1 1 2 3 ) */
+ long a = (0x0ff0001 + mask) >> 23;
+ /* Fix the 1 for 00 case */
+ return a & mask;
+ }
+}
+
+unsigned long xdl_hash_record(char const **data, char const *top, long flags)
+{
+ unsigned long hash = 5381;
+ unsigned long a = 0, mask = 0;
+ char const *ptr = *data;
+ char const *end = top - sizeof(unsigned long) + 1;
+
+ if (flags & XDF_WHITESPACE_FLAGS)
+ return xdl_hash_record_with_whitespace(data, top, flags);
+
+ ptr -= sizeof(unsigned long);
+ do {
+ hash += hash << 5;
+ hash ^= a;
+ ptr += sizeof(unsigned long);
+ if (ptr >= end)
+ break;
+ a = *(unsigned long *)ptr;
+ /* Do we have any '\n' bytes in this word? */
+ mask = has_zero(a ^ NEWLINEBYTES);
+ } while (!mask);
+
+ if (ptr >= end) {
+ /*
+ * There is only a partial word left at the end of the
+ * buffer. Because we may work with a memory mapping,
+ * we have to grab the rest byte by byte instead of
+ * blindly reading it.
+ *
+ * To avoid problems with masking in a signed value,
+ * we use an unsigned char here.
+ */
+ const char *p;
+ for (p = top - 1; p >= ptr; p--)
+ a = (a << 8) + *((const unsigned char *)p);
+ mask = has_zero(a ^ NEWLINEBYTES);
+ if (!mask)
+ /*
+ * No '\n' found in the partial word. Make a
+ * mask that matches what we read.
+ */
+ mask = 1UL << (8 * (top - ptr) + 7);
+ }
+
+ /* The mask *below* the first high bit set */
+ mask = (mask - 1) & ~mask;
+ mask >>= 7;
+ hash += hash << 5;
+ hash ^= a & mask;
+
+ /* Advance past the last (possibly partial) word */
+ ptr += count_masked_bytes(mask);
+
+ if (ptr < top) {
+ assert(*ptr == '\n');
+ ptr++;
+ }
+
+ *data = ptr;
+
+ return hash;
+}
+
+#else /* XDL_FAST_HASH */
unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
unsigned long ha = 5381;
@@ -309,6 +369,7 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
return ha;
}
+#endif /* XDL_FAST_HASH */
unsigned int xdl_hashbits(unsigned int size) {
unsigned int val = 1, bits = 0;
@@ -340,20 +401,6 @@ int xdl_num_out(char *out, long val) {
return str - out;
}
-
-long xdl_atol(char const *str, char const **next) {
- long val, base;
- char const *top;
-
- for (top = str; XDL_ISDIGIT(*top); top++);
- if (next)
- *next = top;
- for (val = 0, base = 1, top--; top >= str; top--, base *= 10)
- val += base * (long)(*top - '0');
- return val;
-}
-
-
int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
const char *func, long funclen, xdemitcb_t *ecb) {
int nb = 0;
@@ -402,3 +449,34 @@ int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
return 0;
}
+
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+ int line1, int count1, int line2, int count2)
+{
+ /*
+ * This probably does not work outside Git, since
+ * we have a very simple mmfile structure.
+ *
+ * Note: ideally, we would reuse the prepared environment, but
+ * the libxdiff interface does not (yet) allow for diffing only
+ * ranges of lines instead of the whole files.
+ */
+ mmfile_t subfile1, subfile2;
+ xdfenv_t env;
+
+ subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
+ subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
+ diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+ subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
+ subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
+ diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+ if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
+ return -1;
+
+ memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
+ memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
+
+ xdl_free_env(&env);
+
+ return 0;
+}
diff --git a/xdiff/xutils.h b/xdiff/xutils.h
index d5de829..ad1428e 100644
--- a/xdiff/xutils.h
+++ b/xdiff/xutils.h
@@ -31,16 +31,15 @@ int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
int xdl_cha_init(chastore_t *cha, long isize, long icount);
void xdl_cha_free(chastore_t *cha);
void *xdl_cha_alloc(chastore_t *cha);
-void *xdl_cha_first(chastore_t *cha);
-void *xdl_cha_next(chastore_t *cha);
-long xdl_guess_lines(mmfile_t *mf);
+long xdl_guess_lines(mmfile_t *mf, long sample);
int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
unsigned long xdl_hash_record(char const **data, char const *top, long flags);
unsigned int xdl_hashbits(unsigned int size);
int xdl_num_out(char *out, long val);
-long xdl_atol(char const *str, char const **next);
int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
const char *func, long funclen, xdemitcb_t *ecb);
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+ int line1, int count1, int line2, int count2);
diff --git a/zlib.c b/zlib.c
index 3c63d48..2b2c0c7 100644
--- a/zlib.c
+++ b/zlib.c
@@ -188,13 +188,20 @@ void git_deflate_init_gzip(git_zstream *strm, int level)
strm->z.msg ? strm->z.msg : "no message");
}
-void git_deflate_end(git_zstream *strm)
+int git_deflate_abort(git_zstream *strm)
{
int status;
zlib_pre_call(strm);
status = deflateEnd(&strm->z);
zlib_post_call(strm);
+ return status;
+}
+
+void git_deflate_end(git_zstream *strm)
+{
+ int status = git_deflate_abort(strm);
+
if (status == Z_OK)
return;
error("deflateEnd: %s (%s)", zerr_to_string(status),