summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--.mailmap188
-rw-r--r--Documentation/Makefile44
-rw-r--r--Documentation/RelNotes/1.8.3.1.txt14
-rw-r--r--Documentation/RelNotes/1.8.3.2.txt59
-rw-r--r--Documentation/RelNotes/1.8.3.3.txt47
-rw-r--r--Documentation/RelNotes/1.8.3.4.txt20
-rw-r--r--Documentation/RelNotes/1.8.4.txt430
-rw-r--r--Documentation/blame-options.txt21
-rw-r--r--Documentation/config.txt130
-rw-r--r--Documentation/diff-options.txt45
-rw-r--r--Documentation/fetch-options.txt2
-rw-r--r--Documentation/git-am.txt4
-rw-r--r--Documentation/git-blame.txt6
-rw-r--r--Documentation/git-cat-file.txt79
-rw-r--r--Documentation/git-check-attr.txt5
-rw-r--r--Documentation/git-check-ignore.txt22
-rw-r--r--Documentation/git-check-ref-format.txt2
-rw-r--r--Documentation/git-checkout.txt6
-rw-r--r--Documentation/git-clone.txt19
-rw-r--r--Documentation/git-config.txt31
-rw-r--r--Documentation/git-describe.txt9
-rw-r--r--Documentation/git-diff-index.txt12
-rw-r--r--Documentation/git-diff.txt14
-rw-r--r--Documentation/git-fast-export.txt2
-rw-r--r--Documentation/git-format-patch.txt15
-rw-r--r--Documentation/git-gc.txt5
-rw-r--r--Documentation/git-index-pack.txt3
-rw-r--r--Documentation/git-log.txt22
-rw-r--r--Documentation/git-ls-remote.txt11
-rw-r--r--Documentation/git-merge.txt4
-rw-r--r--Documentation/git-name-rev.txt9
-rw-r--r--Documentation/git-p4.txt17
-rw-r--r--Documentation/git-pull.txt2
-rw-r--r--Documentation/git-push.txt16
-rw-r--r--Documentation/git-rebase.txt14
-rw-r--r--Documentation/git-reflog.txt9
-rw-r--r--Documentation/git-remote.txt25
-rw-r--r--Documentation/git-reset.txt2
-rw-r--r--Documentation/git-rev-parse.txt16
-rw-r--r--Documentation/git-stash.txt25
-rw-r--r--Documentation/git-submodule.txt16
-rw-r--r--Documentation/git-web--browse.txt1
-rw-r--r--Documentation/git.txt36
-rw-r--r--Documentation/gitdiffcore.txt45
-rw-r--r--Documentation/gitremote-helpers.txt12
-rw-r--r--Documentation/gitrepository-layout.txt3
-rw-r--r--Documentation/gitweb.conf.txt22
-rw-r--r--Documentation/glossary-content.txt17
-rw-r--r--Documentation/line-range-format.txt25
-rw-r--r--Documentation/merge-options.txt3
-rw-r--r--Documentation/pull-fetch-param.txt11
-rw-r--r--Documentation/rev-list-options.txt46
-rw-r--r--Documentation/revisions.txt3
-rw-r--r--Documentation/technical/api-builtin.txt2
-rw-r--r--Documentation/technical/api-parse-options.txt18
-rw-r--r--Documentation/technical/pack-protocol.txt3
-rw-r--r--Documentation/technical/racy-git.txt6
-rw-r--r--Documentation/urls.txt11
-rw-r--r--Documentation/user-manual.txt55
-rwxr-xr-xGIT-VERSION-GEN4
-rw-r--r--Makefile135
l---------RelNotes2
-rw-r--r--abspath.c2
-rw-r--r--advice.c4
-rw-r--r--advice.h2
-rw-r--r--archive-zip.c1
-rw-r--r--bisect.c5
-rw-r--r--branch.c17
-rw-r--r--builtin.h19
-rw-r--r--builtin/add.c1
-rw-r--r--builtin/apply.c6
-rw-r--r--builtin/blame.c99
-rw-r--r--builtin/cat-file.c210
-rw-r--r--builtin/check-ignore.c92
-rw-r--r--builtin/checkout.c56
-rw-r--r--builtin/clone.c30
-rw-r--r--builtin/commit.c96
-rw-r--r--builtin/config.c9
-rw-r--r--builtin/describe.c11
-rw-r--r--builtin/diff.c69
-rw-r--r--builtin/fast-export.c22
-rw-r--r--builtin/fetch.c102
-rw-r--r--builtin/fsck.c2
-rw-r--r--builtin/help.c2
-rw-r--r--builtin/index-pack.c38
-rw-r--r--builtin/log.c57
-rw-r--r--builtin/ls-files.c20
-rw-r--r--builtin/merge-base.c2
-rw-r--r--builtin/merge.c62
-rw-r--r--builtin/name-rev.c36
-rw-r--r--builtin/notes.c138
-rw-r--r--builtin/pack-refs.c2
-rw-r--r--builtin/prune-packed.c15
-rw-r--r--builtin/prune.c6
-rw-r--r--builtin/push.c66
-rw-r--r--builtin/read-tree.c5
-rw-r--r--builtin/reflog.c14
-rw-r--r--builtin/replace.c2
-rw-r--r--builtin/reset.c5
-rw-r--r--builtin/rev-list.c1
-rw-r--r--builtin/rev-parse.c24
-rw-r--r--builtin/rm.c101
-rw-r--r--builtin/shortlog.c1
-rw-r--r--builtin/show-branch.c14
-rw-r--r--builtin/show-ref.c2
-rw-r--r--bundle.c2
-rw-r--r--cache.h89
-rw-r--r--color.c2
-rw-r--r--combine-diff.c10
-rw-r--r--commit-slab.h98
-rw-r--r--commit.c218
-rw-r--r--commit.h42
-rw-r--r--compat/clipped-write.c13
-rw-r--r--compat/cygwin.c5
-rw-r--r--compat/fnmatch/fnmatch.c3
-rw-r--r--compat/mingw.c10
-rw-r--r--compat/mingw.h11
-rw-r--r--compat/nedmalloc/malloc.c.h6
-rw-r--r--compat/nedmalloc/nedmalloc.c4
-rw-r--r--compat/poll/poll.c2
-rw-r--r--compat/regex/regexec.c6
-rw-r--r--compat/terminal.c4
-rw-r--r--compat/unsetenv.c1
-rw-r--r--compat/win32.h2
-rw-r--r--compat/win32/pthread.c2
-rw-r--r--compat/win32mmap.c4
-rw-r--r--config.c28
-rw-r--r--config.mak.uname8
-rw-r--r--configure.ac2
-rw-r--r--connect.c7
-rw-r--r--connected.c34
-rw-r--r--connected.h5
-rw-r--r--contrib/blameview/README9
-rwxr-xr-xcontrib/blameview/blameview.perl155
-rw-r--r--contrib/completion/git-completion.bash189
-rw-r--r--contrib/completion/git-completion.zsh27
-rw-r--r--contrib/completion/git-prompt.sh345
-rw-r--r--contrib/continuous/cidaemon503
-rw-r--r--contrib/continuous/post-receive-cinotify104
-rw-r--r--contrib/credential/osxkeychain/git-credential-osxkeychain.c12
-rw-r--r--contrib/mw-to-git/.perlcriticrc28
-rw-r--r--contrib/mw-to-git/Git/Mediawiki.pm100
-rw-r--r--contrib/mw-to-git/Makefile35
-rwxr-xr-xcontrib/mw-to-git/bin-wrapper/git14
-rwxr-xr-xcontrib/mw-to-git/git-mw.perl368
-rwxr-xr-xcontrib/mw-to-git/git-remote-mediawiki.perl667
-rwxr-xr-xcontrib/mw-to-git/t/test-gitmw-lib.sh27
-rw-r--r--contrib/mw-to-git/t/test.config4
-rw-r--r--contrib/patches/docbook-xsl-manpages-charmap.patch21
-rwxr-xr-xcontrib/remote-helpers/git-remote-bzr117
-rwxr-xr-xcontrib/remote-helpers/git-remote-hg510
-rwxr-xr-xcontrib/remote-helpers/test-bzr.sh492
-rwxr-xr-xcontrib/remote-helpers/test-hg-bidi.sh35
-rwxr-xr-xcontrib/remote-helpers/test-hg-hg-git.sh55
-rwxr-xr-xcontrib/remote-helpers/test-hg.sh725
-rwxr-xr-xcontrib/subtree/git-subtree.sh5
-rw-r--r--credential-store.c4
-rw-r--r--date.c22
-rw-r--r--decorate.c2
-rw-r--r--diff-lib.c26
-rw-r--r--diff-no-index.c2
-rw-r--r--diff.c12
-rw-r--r--diffcore-pickaxe.c15
-rw-r--r--dir.c11
-rw-r--r--environment.c1
-rw-r--r--fast-import.c6
-rw-r--r--fetch-pack.c100
-rw-r--r--fetch-pack.h4
-rwxr-xr-xfixup-builtins16
-rwxr-xr-xgit-add--interactive.perl5
-rwxr-xr-xgit-am.sh38
-rwxr-xr-xgit-bisect.sh4
-rw-r--r--git-compat-util.h17
-rwxr-xr-xgit-difftool.perl9
-rwxr-xr-xgit-gui/GIT-VERSION-GEN2
-rw-r--r--git-gui/Makefile6
-rwxr-xr-xgit-gui/git-gui.sh27
-rw-r--r--git-gui/lib/choose_repository.tcl4
-rw-r--r--git-gui/lib/diff.tcl11
-rw-r--r--git-gui/lib/mergetool.tcl4
-rw-r--r--git-gui/lib/remote.tcl6
-rw-r--r--git-gui/po/fr.po2
-rw-r--r--git-mergetool--lib.sh82
-rwxr-xr-xgit-p4.py2
-rwxr-xr-xgit-pull.sh13
-rw-r--r--git-rebase--am.sh8
-rw-r--r--git-rebase--interactive.sh63
-rw-r--r--git-rebase--merge.sh5
-rwxr-xr-xgit-rebase.sh61
-rwxr-xr-xgit-remote-testgit.sh (renamed from git-remote-testgit)50
-rwxr-xr-xgit-request-pull.sh14
-rwxr-xr-xgit-send-email.perl51
-rw-r--r--git-sh-setup.sh12
-rwxr-xr-xgit-stash.sh64
-rwxr-xr-xgit-submodule.sh172
-rwxr-xr-xgit-svn.perl6
-rwxr-xr-xgit-web--browse.sh11
-rw-r--r--git.c10
-rw-r--r--git.rc22
-rwxr-xr-xgitweb/gitweb.perl9
-rw-r--r--help.c52
-rw-r--r--help.h5
-rw-r--r--http-backend.c6
-rw-r--r--http-push.c2
-rw-r--r--http.c12
-rw-r--r--imap-send.c10
-rw-r--r--line-log.c1274
-rw-r--r--line-log.h53
-rw-r--r--line-range.c243
-rw-r--r--line-range.h36
-rw-r--r--lockfile.c10
-rw-r--r--log-tree.c7
-rw-r--r--mailmap.c67
-rw-r--r--match-trees.c44
-rw-r--r--notes-merge.c27
-rw-r--r--notes-merge.h14
-rw-r--r--notes-utils.c157
-rw-r--r--notes-utils.h37
-rw-r--r--notes.c10
-rw-r--r--object.c84
-rw-r--r--object.h25
-rw-r--r--pack-refs.c148
-rw-r--r--pack-refs.h18
-rw-r--r--pack-revindex.c108
-rw-r--r--parse-options-cb.c6
-rw-r--r--parse-options.h4
-rw-r--r--pretty.c48
-rw-r--r--prio-queue.c84
-rw-r--r--prio-queue.h48
-rw-r--r--read-cache.c210
-rw-r--r--refs.c1073
-rw-r--r--refs.h83
-rw-r--r--remote-curl.c32
-rw-r--r--remote-testsvn.c2
-rw-r--r--remote.c53
-rw-r--r--revision.c653
-rw-r--r--revision.h60
-rw-r--r--run-command.c15
-rw-r--r--sequencer.c29
-rw-r--r--sha1_file.c51
-rw-r--r--sha1_name.c164
-rw-r--r--shallow.c42
-rw-r--r--streaming.c2
-rw-r--r--submodule.c12
-rw-r--r--t/Makefile13
-rw-r--r--t/README27
-rwxr-xr-xt/check-non-portable-shell.pl1
-rw-r--r--t/lib-httpd/apache.conf20
-rw-r--r--t/lib-rebase.sh123
-rw-r--r--t/lib-t6000.sh104
-rw-r--r--t/perf/README2
-rwxr-xr-xt/perf/p0002-read-cache.sh14
-rwxr-xr-xt/perf/p4211-line-log.sh34
-rw-r--r--t/perf/perf-lib.sh3
-rwxr-xr-xt/t0000-basic.sh101
-rwxr-xr-xt/t0005-signals.sh7
-rwxr-xr-xt/t0008-ignores.sh162
-rwxr-xr-xt/t0009-prio-queue.sh50
-rwxr-xr-xt/t0020-crlf.sh8
-rwxr-xr-xt/t0040-parse-options.sh46
-rwxr-xr-xt/t0070-fundamental.sh2
-rwxr-xr-xt/t0100-previous.sh15
-rwxr-xr-xt/t1004-read-tree-m-u-wf.sh7
-rwxr-xr-xt/t1006-cat-file.sh74
-rwxr-xr-xt/t1507-rev-parse-upstream.sh15
-rwxr-xr-xt/t1508-at-combinations.sh68
-rwxr-xr-xt/t1512-rev-parse-disambiguation.sh50
-rwxr-xr-xt/t1513-rev-parse-prefix.sh96
-rwxr-xr-xt/t2001-checkout-cache-clash.sh7
-rwxr-xr-xt/t2004-checkout-cache-temp.sh5
-rwxr-xr-xt/t2007-checkout-symlink.sh12
-rwxr-xr-xt/t2012-checkout-last.sh34
-rwxr-xr-xt/t2021-checkout-overwrite.sh12
-rwxr-xr-xt/t2024-checkout-dwim.sh167
-rwxr-xr-xt/t2200-add-update.sh5
-rwxr-xr-xt/t2202-add-addremove.sh10
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh18
-rwxr-xr-xt/t3010-ls-files-killed-modified.sh141
-rwxr-xr-xt/t3030-merge-recursive.sh62
-rwxr-xr-xt/t3070-wildmatch.sh55
-rwxr-xr-xt/t3100-ls-tree-restrict.sh42
-rwxr-xr-xt/t3200-branch.sh8
-rwxr-xr-xt/t3210-pack-refs.sh33
-rwxr-xr-xt/t3211-peel-ref.sh9
-rwxr-xr-xt/t3400-rebase.sh62
-rwxr-xr-xt/t3401-rebase-partial.sh69
-rwxr-xr-xt/t3403-rebase-skip.sh7
-rwxr-xr-xt/t3404-rebase-interactive.sh49
-rwxr-xr-xt/t3406-rebase-message.sh50
-rwxr-xr-xt/t3409-rebase-preserve-merges.sh53
-rwxr-xr-xt/t3415-rebase-autosquash.sh57
-rwxr-xr-xt/t3420-rebase-autostash.sh170
-rwxr-xr-xt/t3421-rebase-topology-linear.sh350
-rwxr-xr-xt/t3425-rebase-topology-merges.sh258
-rwxr-xr-xt/t3505-cherry-pick-empty.sh18
-rwxr-xr-xt/t3509-cherry-pick-merge-df.sh12
-rwxr-xr-xt/t3600-rm.sh96
-rwxr-xr-xt/t3700-add.sh15
-rwxr-xr-xt/t3900-i18n-commit.sh36
-rwxr-xr-xt/t3903-stash.sh86
-rwxr-xr-xt/t4008-diff-break-rewrite.sh12
-rwxr-xr-xt/t4011-diff-symlink.sh35
-rwxr-xr-xt/t4014-format-patch.sh43
-rwxr-xr-xt/t4015-diff-whitespace.sh345
-rwxr-xr-xt/t4023-diff-rename-typechange.sh28
-rwxr-xr-xt/t4030-diff-textconv.sh8
-rwxr-xr-xt/t4038-diff-combined.sh48
-rwxr-xr-xt/t4041-diff-submodule-option.sh25
-rwxr-xr-xt/t4111-apply-subdir.sh14
-rwxr-xr-xt/t4114-apply-typechange.sh29
-rwxr-xr-xt/t4115-apply-symlink.sh10
-rwxr-xr-xt/t4122-apply-symlink-inside.sh8
-rwxr-xr-xt/t4150-am.sh40
-rwxr-xr-xt/t4202-log.sh14
-rwxr-xr-xt/t4203-mailmap.sh18
-rwxr-xr-xt/t4205-log-pretty-formats.sh120
-rwxr-xr-xt/t4211-line-log.sh67
-rw-r--r--t/t4211/expect.beginning-of-file43
-rw-r--r--t/t4211/expect.end-of-file62
-rw-r--r--t/t4211/expect.move-support-f80
-rw-r--r--t/t4211/expect.multiple104
-rw-r--r--t/t4211/expect.multiple-overlapping187
-rw-r--r--t/t4211/expect.multiple-superset187
-rw-r--r--t/t4211/expect.parallel-change-f-to-main160
-rw-r--r--t/t4211/expect.simple-f59
-rw-r--r--t/t4211/expect.simple-f-to-main100
-rw-r--r--t/t4211/expect.simple-main68
-rw-r--r--t/t4211/expect.simple-main-to-end70
-rw-r--r--t/t4211/expect.two-ranges102
-rw-r--r--t/t4211/expect.vanishes-early39
-rw-r--r--t/t4211/history.export406
-rwxr-xr-xt/t5000-tar-tree.sh168
-rw-r--r--t/t5000/pax.tarbin0 -> 10240 bytes
-rwxr-xr-xt/t5003-archive-zip.sh2
-rwxr-xr-xt/t5004-archive-corner-cases.sh15
-rw-r--r--t/t5004/empty-with-pax-header.tarbin0 -> 10240 bytes
-rwxr-xr-xt/t5150-request-pull.sh2
-rwxr-xr-xt/t5303-pack-corruption-resilience.sh29
-rwxr-xr-xt/t5407-post-rewrite-hook.sh4
-rwxr-xr-xt/t5500-fetch-pack.sh21
-rwxr-xr-xt/t5505-remote.sh850
-rwxr-xr-xt/t5510-fetch.sh34
-rwxr-xr-xt/t5520-pull.sh29
-rwxr-xr-xt/t5521-pull-options.sh26
-rwxr-xr-xt/t5528-push-default.sh65
-rwxr-xr-xt/t5551-http-fetch.sh8
-rwxr-xr-xt/t5601-clone.sh5
-rwxr-xr-xt/t5702-clone-options.sh2
-rwxr-xr-xt/t5710-info-alternate.sh8
-rwxr-xr-xt/t5801-remote-helpers.sh78
-rwxr-xr-xt/t6002-rev-list-bisect.sh84
-rwxr-xr-xt/t6003-rev-list-topo-order.sh101
-rwxr-xr-xt/t6006-rev-list-format.sh208
-rwxr-xr-xt/t6012-rev-list-simplify.sh31
-rwxr-xr-xt/t6019-rev-list-ancestry-path.sh48
-rwxr-xr-xt/t6035-merge-dir-to-symlink.sh73
-rwxr-xr-xt/t6111-rev-list-treesame.sh196
-rwxr-xr-xt/t6120-describe.sh3
-rwxr-xr-xt/t7001-mv.sh18
-rwxr-xr-xt/t7011-skip-worktree-reading.sh4
-rwxr-xr-xt/t7102-reset.sh41
-rwxr-xr-xt/t7201-co.sh1
-rwxr-xr-xt/t7400-submodule-basic.sh117
-rwxr-xr-xt/t7401-submodule-summary.sh116
-rwxr-xr-xt/t7403-submodule-sync.sh388
-rwxr-xr-xt/t7406-submodule-update.sh68
-rwxr-xr-xt/t7407-submodule-foreach.sh16
-rwxr-xr-xt/t7500-commit.sh6
-rwxr-xr-xt/t7501-commit.sh13
-rwxr-xr-xt/t7502-commit.sh17
-rwxr-xr-xt/t7508-status.sh62
-rwxr-xr-xt/t7512-status-help.sh57
-rwxr-xr-xt/t7600-merge.sh14
-rwxr-xr-xt/t7607-merge-overwrite.sh5
-rwxr-xr-xt/t7800-difftool.sh19
-rwxr-xr-xt/t8003-blame-corner-cases.sh6
-rwxr-xr-xt/t8006-blame-textconv.sh14
-rwxr-xr-xt/t8007-cat-file-textconv.sh10
-rwxr-xr-xt/t9001-send-email.sh138
-rwxr-xr-xt/t9114-git-svn-dcommit-merge.sh2
-rwxr-xr-xt/t9350-fast-export.sh5
-rwxr-xr-xt/t9402-git-cvsserver-refs.sh12
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh15
-rwxr-xr-xt/t9801-git-p4-branch.sh23
-rwxr-xr-xt/t9802-git-p4-filetype.sh4
-rwxr-xr-xt/t9902-completion.sh214
-rwxr-xr-xt/t9903-bash-prompt.sh464
-rw-r--r--t/test-lib-functions.sh35
-rw-r--r--t/test-lib.sh260
-rwxr-xr-xt/valgrind/analyze.sh8
-rwxr-xr-xt/valgrind/valgrind.sh3
-rwxr-xr-x[-rw-r--r--]templates/hooks--pre-push.sample0
-rw-r--r--test-chmtime.c12
-rw-r--r--test-index-version.c2
-rw-r--r--test-mergesort.c2
-rw-r--r--test-parse-options.c4
-rw-r--r--test-prio-queue.c39
-rw-r--r--test-read-cache.c13
-rw-r--r--test-subprocess.c4
-rw-r--r--thread-utils.c2
-rw-r--r--transport-helper.c55
-rw-r--r--transport.c6
-rw-r--r--transport.h4
-rw-r--r--tree-walk.h2
-rw-r--r--unpack-trees.c166
-rw-r--r--unpack-trees.h14
-rw-r--r--upload-pack.c2
-rw-r--r--wildmatch.c7
-rw-r--r--wrap-for-bin.sh2
-rw-r--r--wrapper.c16
-rw-r--r--wt-status.c16
-rw-r--r--xdiff/xdiff.h2
-rw-r--r--xdiff/xdiffi.c29
-rw-r--r--xdiff/xdiffi.h1
-rw-r--r--xdiff/xemit.c49
-rw-r--r--xdiff/xemit.h2
-rw-r--r--xdiff/xutils.c13
-rw-r--r--xdiff/xutils.h1
419 files changed, 18419 insertions, 6542 deletions
diff --git a/.gitignore b/.gitignore
index 6669bf0..efa8db0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,6 +125,7 @@
/git-remote-ftps
/git-remote-fd
/git-remote-ext
+/git-remote-testgit
/git-remote-testpy
/git-remote-testsvn
/git-repack
@@ -190,6 +191,8 @@
/test-mktemp
/test-parse-options
/test-path-utils
+/test-prio-queue
+/test-read-cache
/test-regex
/test-revision-walking
/test-run-command
@@ -228,6 +231,7 @@
/cscope*
*.obj
*.lib
+*.res
*.sln
*.suo
*.ncb
diff --git a/.mailmap b/.mailmap
index 48d7acf..57070b5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -5,98 +5,216 @@
# same person appearing not to be so.
#
+<nico@fluxnic.net> <nico@cam.org>
+Alejandro R. Sedeño <asedeno@MIT.EDU> <asedeno@mit.edu>
Alex Bennée <kernel-hacker@bennee.com>
+Alex Riesen <raa.lkml@gmail.com> <fork0@t-online.de>
+Alex Riesen <raa.lkml@gmail.com> <raa@limbo.localdomain>
+Alex Riesen <raa.lkml@gmail.com> <raa@steel.home>
+Alex Vandiver <alex@chmrr.net> <alexmv@MIT.EDU>
Alexander Gavrilov <angavrilov@gmail.com>
+Alexey Shumkin <alex.crezoff@gmail.com> <zapped@mail.ru>
+Anders Kaseorg <andersk@MIT.EDU> <andersk@ksplice.com>
+Anders Kaseorg <andersk@MIT.EDU> <andersk@mit.edu>
Aneesh Kumar K.V <aneesh.kumar@gmail.com>
+Ben Walton <bdwalton@gmail.com> <bwalton@artsci.utoronto.ca>
+Bernt Hansen <bernt@norang.ca> <bernt@alumni.uwaterloo.ca>
+Brandon Casey <drafnel@gmail.com> <casey@nrlssc.navy.mil>
Brian M. Carlson <sandals@crustytoothpaste.ath.cx>
+Bryan Larsen <bryan@larsen.st> <bryan.larsen@gmail.com>
+Bryan Larsen <bryan@larsen.st> <bryanlarsen@yahoo.com>
Cheng Renquan <crquan@gmail.com>
Chris Shoemaker <c.shoemaker@cox.net>
+Chris Wright <chrisw@sous-sol.org> <chrisw@osdl.org>
+Csaba Henk <csaba@gluster.com> <csaba@lowlife.hu>
Dan Johnson <computerdruid@gmail.com>
-Dana L. How <danahow@gmail.com>
-Dana L. How <how@deathvalley.cswitch.com>
+Dana L. How <danahow@gmail.com> <how@deathvalley.cswitch.com>
Daniel Barkalow <barkalow@iabervon.org>
+David Brown <git@davidb.org> <davidb@quicinc.com>
David D. Kilzer <ddkilzer@kilzer.net>
David KÃ¥gedal <davidk@lysator.liu.se>
+David Reiss <dreiss@facebook.com> <dreiss@dreiss-vmware.(none)>
David S. Miller <davem@davemloft.net>
Deskin Miller <deskinm@umich.edu>
Dirk Süsserott <newsletter@dirk.my1.cc>
+Eric Blake <eblake@redhat.com> <ebb9@byu.net>
+Eric Hanchrow <eric.hanchrow@gmail.com> <offby1@blarg.net>
Eric S. Raymond <esr@thyrsus.com>
Erik Faye-Lund <kusmabite@gmail.com> <kusmabite@googlemail.com>
-Fredrik Kuivinen <freku045@student.liu.se>
+Eyvind Bernhardsen <eyvind.bernhardsen@gmail.com> <eyvind-git@orakel.ntnu.no>
+Florian Achleitner <florian.achleitner.2.6.31@gmail.com> <florian.achleitner2.6.31@gmail.com>
+Franck Bui-Huu <vagabon.xyz@gmail.com> <fbuihuu@gmail.com>
+Frank Lichtenheld <frank@lichtenheld.de> <djpig@debian.org>
+Frank Lichtenheld <frank@lichtenheld.de> <flichtenheld@astaro.com>
+Fredrik Kuivinen <frekui@gmail.com> <freku045@student.liu.se>
Frédéric Heitzmann <frederic.heitzmann@gmail.com>
-H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
-H. Peter Anvin <hpa@tazenda.sc.orionmulti.com>
-H. Peter Anvin <hpa@trantor.hos.anvin.org>
+Garry Dolley <gdolley@ucla.edu> <gdolley@arpnetworks.com>
+Greg Price <price@mit.edu> <price@MIT.EDU>
+Greg Price <price@mit.edu> <price@ksplice.com>
+Heiko Voigt <hvoigt@hvoigt.net> <git-list@hvoigt.net>
+H. Merijn Brand <h.m.brand@xs4all.nl> H.Merijn Brand <h.m.brand@xs4all.nl>
+H. Peter Anvin <hpa@zytor.com> <hpa@bonde.sc.orionmulti.com>
+H. Peter Anvin <hpa@zytor.com> <hpa@smyrno.hos.anvin.org>
+H. Peter Anvin <hpa@zytor.com> <hpa@tazenda.sc.orionmulti.com>
+H. Peter Anvin <hpa@zytor.com> <hpa@trantor.hos.anvin.org>
+Han-Wen Nienhuys <hanwen@google.com> Han-Wen Nienhuys <hanwen@xs4all.nl>
Horst H. von Brand <vonbrand@inf.utfsm.cl>
-İsmail Dönmez <ismail@pardus.org.tr>
+J. Bruce Fields <bfields@citi.umich.edu> <bfields@fieldses.org>
+J. Bruce Fields <bfields@citi.umich.edu> <bfields@pig.linuxdev.us.dell.com>
+J. Bruce Fields <bfields@citi.umich.edu> <bfields@puzzle.fieldses.org>
Jakub Narębski <jnareb@gmail.com>
-Jay Soffian <jaysoffian+git@gmail.com>
+Jason Riedy <ejr@eecs.berkeley.edu> <ejr@EECS.Berkeley.EDU>
+Jason Riedy <ejr@eecs.berkeley.edu> <ejr@cs.berkeley.edu>
+Jay Soffian <jaysoffian@gmail.com> <jaysoffian+git@gmail.com>
Jeff King <peff@peff.net> <peff@github.com>
+Jeff Muizelaar <jmuizelaar@mozilla.com> <jeff@infidigm.net>
+Jim Meyering <jim@meyering.net> <meyering@redhat.com>
Joachim Berdal Haga <cjhaga@fys.uio.no>
-Johannes Sixt <j6t@kdbg.org> <johannes.sixt@telecom.at>
-Johannes Sixt <j6t@kdbg.org> <j.sixt@viscovery.net>
+Johannes Schindelin <Johannes.Schindelin@gmx.de> <johannes.schindelin@gmx.de>
Johannes Sixt <j6t@kdbg.org> <J.Sixt@eudaptics.com>
-Jon Loeliger <jdl@freescale.com>
-Jon Seymour <jon@blackcubes.dyndns.org>
-Jonathan Nieder <jrnieder@uchicago.edu>
+Johannes Sixt <j6t@kdbg.org> <j.sixt@viscovery.net>
+Johannes Sixt <j6t@kdbg.org> <johannes.sixt@telecom.at>
+Jon Loeliger <jdl@jdl.com> <jdl@freescale.com>
+Jon Loeliger <jdl@jdl.com> <jdl@freescale.org>
+Jon Seymour <jon.seymour@gmail.com> <jon@blackcubes.dyndns.org>
+Jonathan Nieder <jrnieder@gmail.com> <jrnieder@uchicago.edu>
+Jonathan del Strother <jon.delStrother@bestbefore.tv> <maillist@steelskies.com>
+Josh Triplett <josh@joshtriplett.org> <josh@freedesktop.org>
+Josh Triplett <josh@joshtriplett.org> <josht@us.ibm.com>
+Julian Phillips <julian@quantumfyre.co.uk> <jp3@quantumfyre.co.uk>
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> <junio@pobox.com>
+Junio C Hamano <gitster@pobox.com> <junio@twinsun.com>
Junio C Hamano <gitster@pobox.com> <junkio@cox.net>
-Karl Hasselström <kha@treskal.com>
-Kevin Leung <kevinlsk@gmail.com>
+Junio C Hamano <gitster@pobox.com> <junkio@twinsun.com>
+Karl Wiberg <kha@treskal.com> Karl Hasselström <kha@treskal.com>
+Karl Wiberg <kha@treskal.com> Karl Hasselström <kha@yoghurt.hemma.treskal.com>
+Karsten Blees <blees@dcon.de> <karsten.blees@dcon.de>
+Karsten Blees <blees@dcon.de> <karsten.blees@gmail.com>
+Kay Sievers <kay.sievers@vrfy.org> <kay.sievers@suse.de>
+Kay Sievers <kay.sievers@vrfy.org> <kay@mam.(none)>
+Keith Cascio <keith@CS.UCLA.EDU> <keith@cs.ucla.edu>
Kent Engstrom <kent@lysator.liu.se>
+Kevin Leung <kevinlsk@gmail.com>
+Kirill Smelkov <kirr@navytux.spb.ru> <kirr@landau.phys.spbu.ru>
+Kirill Smelkov <kirr@navytux.spb.ru> <kirr@mns.spb.ru>
+Knut Franke <Knut.Franke@gmx.de> <k.franke@science-computing.de>
Lars Doelle <lars.doelle@on-line ! de>
Lars Doelle <lars.doelle@on-line.de>
+Lars Noschinski <lars@public.noschinski.de> <lars.noschinski@rwth-aachen.de>
Li Hong <leehong@pku.edu.cn>
-Linus Torvalds <torvalds@linux-foundation.org> <torvalds@woody.linux-foundation.org>
-Linus Torvalds <torvalds@linux-foundation.org> <torvalds@osdl.org>
-Linus Torvalds <torvalds@linux-foundation.org> <torvalds@g5.osdl.org>
Linus Torvalds <torvalds@linux-foundation.org> <torvalds@evo.osdl.org>
-Linus Torvalds <torvalds@linux-foundation.org> <torvalds@ppc970.osdl.org>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@g5.osdl.org>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@osdl.org>
Linus Torvalds <torvalds@linux-foundation.org> <torvalds@ppc970.osdl.org.(none)>
-Lukas Sandström <lukass@etek.chalmers.se>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@ppc970.osdl.org>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@woody.linux-foundation.org>
+Lukas Sandström <luksan@gmail.com> <lukass@etek.chalmers.se>
+Marc Khouzam <marc.khouzam@ericsson.com> <marc.khouzam@gmail.com>
Marc-André Lureau <marcandre.lureau@gmail.com>
+Marco Costalba <mcostalba@gmail.com> <mcostalba@yahoo.it>
+Mark Levedahl <mdl123@verizon.net> <mlevedahl@gmail.com>
Mark Rada <marada@uwaterloo.ca>
Martin Langhoff <martin@laptop.org> <martin@catalyst.net.nz>
Martin von Zweigbergk <martinvonz@gmail.com> <martin.von.zweigbergk@gmail.com>
+Matt Draisey <matt@draisey.ca> <mattdraisey@sympatico.ca>
+Matt Kraai <kraai@ftbfs.org> <matt.kraai@amo.abbott.com>
+Matt McCutchen <matt@mattmccutchen.net> <hashproduct@gmail.com>
+Matthias Kestenholz <matthias@spinlock.ch> <mk@spinlock.ch>
+Matthias Urlichs <matthias@urlichs.de> <smurf@kiste.(none)>
+Matthias Urlichs <matthias@urlichs.de> <smurf@smurf.noris.de>
Michael Coleman <tutufan@gmail.com>
Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm>
Michael W. Olson <mwolson@gnu.org>
+Michael Witten <mfwitten@gmail.com> <mfwitten@MIT.EDU>
+Michael Witten <mfwitten@gmail.com> <mfwitten@mit.edu>
+Michal Rokos <michal.rokos@nextsoft.cz> <rokos@nextsoft.cz>
Michele Ballabio <barra_cuda@katamail.com>
+Miklos Vajna <vmiklos@frugalware.org> <vmiklos@suse.cz>
+Namhyung Kim <namhyung@gmail.com> <namhyung.kim@lge.com>
+Namhyung Kim <namhyung@gmail.com> <namhyung@kernel.org>
Nanako Shiraishi <nanako3@bluebottle.com>
Nanako Shiraishi <nanako3@lavabit.com>
+Nelson Elhage <nelhage@mit.edu> <nelhage@MIT.EDU>
+Nelson Elhage <nelhage@mit.edu> <nelhage@ksplice.com>
Nguyá»…n Thái Ngá»c Duy <pclouds@gmail.com>
-<nico@fluxnic.net> <nico@cam.org>
-Peter Krefting <peter@softwolves.pp.se> <peter@svarten.intern.softwolves.pp.se>
+Nick Stokoe <nick@noodlefactory.co.uk> Nick Woolley <nick@noodlefactory.co.uk>
+Nick Stokoe <nick@noodlefactory.co.uk> Nick Woolley <nickwoolley@yahoo.co.uk>
+Nicolas Morey-Chaisemartin <devel-git@morey-chaisemartin.com> <nicolas.morey@free.fr>
+Nicolas Morey-Chaisemartin <devel-git@morey-chaisemartin.com> <nmorey@kalray.eu>
+Nicolas Sebrecht <nicolas.s.dev@gmx.fr> <ni.s@laposte.net>
+Paolo Bonzini <bonzini@gnu.org> <paolo.bonzini@lu.unisi.ch>
+Pascal Obry <pascal@obry.net> <pascal.obry@gmail.com>
+Pascal Obry <pascal@obry.net> <pascal.obry@wanadoo.fr>
+Pat Notz <patnotz@gmail.com> <pknotz@sandia.gov>
+Paul Mackerras <paulus@samba.org> <paulus@dorrigo.(none)>
+Paul Mackerras <paulus@samba.org> <paulus@pogo.(none)>
+Peter Baumann <waste.manager@gmx.de> <Peter.B.Baumann@stud.informatik.uni-erlangen.de>
+Peter Baumann <waste.manager@gmx.de> <siprbaum@stud.informatik.uni-erlangen.de>
Peter Krefting <peter@softwolves.pp.se> <peter@softwolves.pp.se>
+Peter Krefting <peter@softwolves.pp.se> <peter@svarten.intern.softwolves.pp.se>
Petr Baudis <pasky@ucw.cz> <pasky@suse.cz>
+Petr Baudis <pasky@ucw.cz> <xpasky@machine>
+Phil Hord <hordp@cisco.com> <phil.hord@gmail.com>
+Philip Jägenstedt <philip@foolip.org> <philip.jagenstedt@gmail.com>
+Philipp A. Hartmann <pah@qo.cx> <ph@sorgh.de>
Philippe Bruhat <book@cpan.org>
Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com>
Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk>
-René Scharfe <rene.scharfe@lsrfire.ath.cx>
+René Scharfe <l.s.r@web.de> <rene.scharfe@lsrfire.ath.cx>
Robert Fitzsimons <robfitz@273k.net>
+Robert Shearman <robertshearman@gmail.com> <rob@codeweavers.com>
Robert Zeh <robert.a.zeh@gmail.com>
-Sam Vilain <sam@vilain.net>
-Santi Béjar <sbejar@gmail.com>
+Robin Rosenberg <robin.rosenberg@dewire.com> <robin.rosenberg.lists@dewire.com>
+Ryan Anderson <ryan@michonline.com> <rda@google.com>
+Salikh Zakirov <salikh.zakirov@gmail.com> <Salikh.Zakirov@Intel.com>
+Sam Vilain <sam@vilain.net> <sam.vilain@catalyst.net.nz>
+Santi Béjar <santi@agolina.net> <sbejar@gmail.com>
Sean Estabrooks <seanlkml@sympatico.ca>
+Sebastian Schuberth <sschuberth@gmail.com> <sschuberth@visageimaging.com>
+Seth Falcon <seth@userprimary.net> <sfalcon@fhcrc.org>
Shawn O. Pearce <spearce@spearce.org>
-Steven Grimm <koreth@midwinter.com>
+Simon Hausmann <hausmann@kde.org> <simon@lst.de>
+Simon Hausmann <hausmann@kde.org> <shausman@trolltech.com>
+Stefan Naewe <stefan.naewe@gmail.com> <stefan.naewe@atlas-elektronik.com>
+Stefan Naewe <stefan.naewe@gmail.com> <stefan.naewe@googlemail.com>
+Stefan Sperling <stsp@elego.de> <stsp@stsp.name>
+Stephen Boyd <bebarino@gmail.com> <sboyd@codeaurora.org>
+Steven Drake <sdrake@xnet.co.nz> <sdrake@ihug.co.nz>
+Steven Grimm <koreth@midwinter.com> <sgrimm@sgrimm-mbp.local>
+Steven Walter <stevenrwalter@gmail.com> <swalter@lexmark.com>
+Steven Walter <stevenrwalter@gmail.com> <swalter@lpdev.prtdev.lexmark.com>
+Sven Verdoolaege <skimo@kotnet.org> <Sven.Verdoolaege@cs.kuleuven.ac.be>
+Sven Verdoolaege <skimo@kotnet.org> <skimo@liacs.nl>
Tay Ray Chuan <rctay89@gmail.com>
+Ted Percival <ted@midg3t.net> <ted.percival@quest.com>
Theodore Ts'o <tytso@mit.edu>
+Thomas Ackermann <th.acker@arcor.de> <th.acker66@arcor.de>
Thomas Rast <trast@inf.ethz.ch> <trast@student.ethz.ch>
+Timo Hirvonen <tihirvon@gmail.com> <tihirvon@ee.oulu.fi>
+Toby Allsopp <Toby.Allsopp@navman.co.nz> <toby.allsopp@navman.co.nz>
+Tom Grennan <tmgrennan@gmail.com> <tgrennan@redback.com>
+Tommi Virtanen <tv@debian.org> <tv@eagain.net>
+Tommi Virtanen <tv@debian.org> <tv@inoi.fi>
+Tommy Thorn <tommy-git@thorn.ws> <tt1729@yahoo.com>
Tony Luck <tony.luck@intel.com>
-Uwe Kleine-König <Uwe_Zeisberger@digi.com>
-Uwe Kleine-König <Uwe.Kleine-Koenig@digi.com>
-Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de>
-Uwe Kleine-König <uzeisberger@io.fsforth.de>
-Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de>
-Ville Skyttä <scop@xemacs.org>
+Tor Arne Vestbø <torarnv@gmail.com> <tavestbo@trolltech.com>
+Trent Piepho <tpiepho@gmail.com> <tpiepho@freescale.com>
+Trent Piepho <tpiepho@gmail.com> <xyzzy@speakeasy.org>
+Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <Uwe.Kleine-Koenig@digi.com>
+Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <ukleinek@informatik.uni-freiburg.de>
+Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <uzeisberger@io.fsforth.de>
+Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <zeisberg@informatik.uni-freiburg.de>
+Ville Skyttä <ville.skytta@iki.fi> <scop@xemacs.org>
Vitaly "_Vi" Shukela <public_vi@tut.by>
+W. Trevor King <wking@tremily.us> <wking@drexel.edu>
William Pursell <bill.pursell@gmail.com>
+YONETANI Tomokazu <y0n3t4n1@gmail.com> <qhwt+git@les.ath.cx>
+YONETANI Tomokazu <y0n3t4n1@gmail.com> <y0netan1@dragonflybsd.org>
YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+# the two anonymous contributors are different persons:
anonymous <linux@horizon.com>
anonymous <linux@horizon.net>
+İsmail Dönmez <ismail@pardus.org.tr>
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 62dbd9a..0cfdc36 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -31,11 +31,11 @@ MAN7_TXT += gittutorial.txt
MAN7_TXT += gitworkflows.txt
MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
-MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
-MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT))
+MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT))
+MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
OBSOLETE_HTML = git-remote-helpers.html
-DOC_HTML=$(MAN_HTML) $(OBSOLETE_HTML)
+DOC_HTML = $(MAN_HTML) $(OBSOLETE_HTML)
ARTICLES = howto-index
ARTICLES += everyday
@@ -74,35 +74,35 @@ SP_ARTICLES += technical/api-index
DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
-DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
-DOC_MAN5=$(patsubst %.txt,%.5,$(MAN5_TXT))
-DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
-
-prefix?=$(HOME)
-bindir?=$(prefix)/bin
-htmldir?=$(prefix)/share/doc/git-doc
-pdfdir?=$(prefix)/share/doc/git-doc
-mandir?=$(prefix)/share/man
-man1dir=$(mandir)/man1
-man5dir=$(mandir)/man5
-man7dir=$(mandir)/man7
-# DESTDIR=
+DOC_MAN1 = $(patsubst %.txt,%.1,$(MAN1_TXT))
+DOC_MAN5 = $(patsubst %.txt,%.5,$(MAN5_TXT))
+DOC_MAN7 = $(patsubst %.txt,%.7,$(MAN7_TXT))
+
+prefix ?= $(HOME)
+bindir ?= $(prefix)/bin
+htmldir ?= $(prefix)/share/doc/git-doc
+infodir ?= $(prefix)/share/info
+pdfdir ?= $(prefix)/share/doc/git-doc
+mandir ?= $(prefix)/share/man
+man1dir = $(mandir)/man1
+man5dir = $(mandir)/man5
+man7dir = $(mandir)/man7
+# DESTDIR =
ASCIIDOC = asciidoc
ASCIIDOC_EXTRA =
MANPAGE_XSL = manpage-normal.xsl
XMLTO = xmlto
XMLTO_EXTRA =
-INSTALL?=install
+INSTALL ?= install
RM ?= rm -f
MAN_REPO = ../../git-manpages
HTML_REPO = ../../git-htmldocs
-infodir?=$(prefix)/share/info
-MAKEINFO=makeinfo
-INSTALL_INFO=install-info
-DOCBOOK2X_TEXI=docbook2x-texi
-DBLATEX=dblatex
+MAKEINFO = makeinfo
+INSTALL_INFO = install-info
+DOCBOOK2X_TEXI = docbook2x-texi
+DBLATEX = dblatex
ifndef PERL_PATH
PERL_PATH = /usr/bin/perl
endif
diff --git a/Documentation/RelNotes/1.8.3.1.txt b/Documentation/RelNotes/1.8.3.1.txt
new file mode 100644
index 0000000..fc3ea18
--- /dev/null
+++ b/Documentation/RelNotes/1.8.3.1.txt
@@ -0,0 +1,14 @@
+Git v1.8.3.1 Release Notes
+========================
+
+Fixes since v1.8.3
+------------------
+
+ * When $HOME is misconfigured to point at an unreadable directory, we
+ used to complain and die. The check has been loosened.
+
+ * Handling of negative exclude pattern for directories "!dir" was
+ broken in the update to v1.8.3.
+
+Also contains a handful of trivial code clean-ups, documentation
+updates, updates to the test suite, etc.
diff --git a/Documentation/RelNotes/1.8.3.2.txt b/Documentation/RelNotes/1.8.3.2.txt
new file mode 100644
index 0000000..26ae142
--- /dev/null
+++ b/Documentation/RelNotes/1.8.3.2.txt
@@ -0,0 +1,59 @@
+Git v1.8.3.2 Release Notes
+==========================
+
+Fixes since v1.8.3.1
+--------------------
+
+ * Cloning with "git clone --depth N" while fetch.fsckobjects (or
+ transfer.fsckobjects) is set to true did not tell the cut-off
+ points of the shallow history to the process that validates the
+ objects and the history received, causing the validation to fail.
+
+ * "git checkout foo" DWIMs the intended "upstream" and turns it into
+ "git checkout -t -b foo remotes/origin/foo". This codepath has been
+ updated to correctly take existing remote definitions into account.
+
+ * "git fetch" into a shallow repository from a repository that does
+ not know about the shallow boundary commits (e.g. a different fork
+ from the repository the current shallow repository was cloned from)
+ did not work correctly.
+
+ * "git subtree" (in contrib/) had one codepath with loose error
+ checks to lose data at the remote side.
+
+ * "git log --ancestry-path A...B" did not work as expected, as it did
+ not pay attention to the fact that the merge base between A and B
+ was the bottom of the range being specified.
+
+ * "git diff -c -p" was not showing a deleted line from a hunk when
+ another hunk immediately begins where the earlier one ends.
+
+ * "git merge @{-1}~22" was rewritten to "git merge frotz@{1}~22"
+ incorrectly when your previous branch was "frotz" (it should be
+ rewritten to "git merge frotz~22" instead).
+
+ * "git commit --allow-empty-message -m ''" should not start an
+ editor.
+
+ * "git push --[no-]verify" was not documented.
+
+ * An entry for "file://" scheme in the enumeration of URL types Git
+ can take in the HTML documentation was made into a clickable link
+ by mistake.
+
+ * zsh prompt script that borrowed from bash prompt script did not
+ work due to slight differences in array variable notation between
+ these two shells.
+
+ * The bash prompt code (in contrib/) displayed the name of the branch
+ being rebased when "rebase -i/-m/-p" modes are in use, but not the
+ plain vanilla "rebase".
+
+ * "git push $there HEAD:branch" did not resolve HEAD early enough, so
+ it was easy to flip it around while push is still going on and push
+ out a branch that the user did not originally intended when the
+ command was started.
+
+ * "difftool --dir-diff" did not copy back changes made by the
+ end-user in the diff tool backend to the working tree in some
+ cases.
diff --git a/Documentation/RelNotes/1.8.3.3.txt b/Documentation/RelNotes/1.8.3.3.txt
new file mode 100644
index 0000000..9ba4f4d
--- /dev/null
+++ b/Documentation/RelNotes/1.8.3.3.txt
@@ -0,0 +1,47 @@
+Git v1.8.3.3 Release Notes
+==========================
+
+Fixes since v1.8.3.2
+--------------------
+
+ * "git apply" parsed patches that add new files, generated by programs
+ other than Git, incorrectly. This is an old breakage in v1.7.11.
+
+ * Older cURL wanted piece of memory we call it with to be stable, but
+ we updated the auth material after handing it to a call.
+
+ * "git pull" into nothing trashed "local changes" that were in the
+ index.
+
+ * Many "git submodule" operations did not work on a submodule at a
+ path whose name is not in ASCII.
+
+ * "cherry-pick" had a small leak in its error codepath.
+
+ * Logic used by git-send-email to suppress cc mishandled names like
+ "A U. Thor" <author@example.xz>, where the human readable part
+ needs to be quoted (the user input may not have the double quotes
+ around the name, and comparison was done between quoted and
+ unquoted strings). It also mishandled names that need RFC2047
+ quoting.
+
+ * "gitweb" forgot to clear a global variable $search_regexp upon each
+ request, mistakenly carrying over the previous search to a new one
+ when used as a persistent CGI.
+
+ * The wildmatch engine did not honor WM_CASEFOLD option correctly.
+
+ * "git log -c --follow $path" segfaulted upon hitting the commit that
+ renamed the $path being followed.
+
+ * When a reflog notation is used for implicit "current branch",
+ e.g. "git log @{u}", we did not say which branch and worse said
+ "branch ''" in the error messages.
+
+ * Mac OS X does not like to write(2) more than INT_MAX number of
+ bytes; work it around by chopping write(2) into smaller pieces.
+
+ * Newer MacOS X encourages the programs to compile and link with
+ their CommonCrypto, not with OpenSSL.
+
+Also contains various minor documentation updates.
diff --git a/Documentation/RelNotes/1.8.3.4.txt b/Documentation/RelNotes/1.8.3.4.txt
new file mode 100644
index 0000000..56f106e
--- /dev/null
+++ b/Documentation/RelNotes/1.8.3.4.txt
@@ -0,0 +1,20 @@
+Git v1.8.3.4 Release Notes
+==========================
+
+This update is mostly to propagate documentation fixes and test
+updates from the master front back to the maintenance track.
+
+Fixes since v1.8.3.3
+--------------------
+
+ * The bisect log listed incorrect commits when bisection ends with
+ only skipped ones.
+
+ * The test coverage framework was left broken for some time.
+
+ * The test suite for HTTP transport did not run with Apache 2.4.
+
+ * "git diff" used to fail when core.safecrlf is set and the working
+ tree contents had mixed CRLF/LF line endings. Committing such a
+ content must be prohibited, but "git diff" should help the user to
+ locate and fix such problems without failing.
diff --git a/Documentation/RelNotes/1.8.4.txt b/Documentation/RelNotes/1.8.4.txt
new file mode 100644
index 0000000..2226abe
--- /dev/null
+++ b/Documentation/RelNotes/1.8.4.txt
@@ -0,0 +1,430 @@
+Git v1.8.4 Release Notes
+========================
+
+Backward compatibility notes (for Git 2.0)
+------------------------------------------
+
+When "git push [$there]" does not say what to push, we have used the
+traditional "matching" semantics so far (all your branches were sent
+to the remote as long as there already are branches of the same name
+over there). In Git 2.0, the default will change to the "simple"
+semantics that pushes:
+
+ - only the current branch to the branch with the same name, and only
+ when the current branch is set to integrate with that remote
+ branch, if you are pushing to the same remote as you fetch from; or
+
+ - only the current branch to the branch with the same name, if you
+ are pushing to a remote that is not where you usually fetch from.
+
+Use the user preference configuration variable "push.default" to
+change this. If you are an old-timer who is used to the "matching"
+semantics, you can set the variable to "matching" to keep the
+traditional behaviour. If you want to live in the future early, you
+can set it to "simple" today without waiting for Git 2.0.
+
+When "git add -u" (and "git add -A") is run inside a subdirectory and
+does not specify which paths to add on the command line, it
+will operate on the entire tree in Git 2.0 for consistency
+with "git commit -a" and other commands. There will be no
+mechanism to make plain "git add -u" behave like "git add -u .".
+Current users of "git add -u" (without a pathspec) should start
+training their fingers to explicitly say "git add -u ."
+before Git 2.0 comes. A warning is issued when these commands are
+run without a pathspec and when you have local changes outside the
+current directory, because the behaviour in Git 2.0 will be different
+from today's version in such a situation.
+
+In Git 2.0, "git add <path>" will behave as "git add -A <path>", so
+that "git add dir/" will notice paths you removed from the directory
+and record the removal. Versions before Git 2.0, including this
+release, will keep ignoring removals, but the users who rely on this
+behaviour are encouraged to start using "git add --ignore-removal <path>"
+now before 2.0 is released.
+
+
+Updates since v1.8.3
+--------------------
+
+Foreign interfaces, subsystems and ports.
+
+ * "git rebase -i" now honors --strategy and -X options.
+
+ * Git-gui has been updated to its 0.18.0 version.
+
+ * MediaWiki remote helper (in contrib/) has been updated to use the
+ credential helper interface from Git.pm.
+
+ * Update build for Cygwin 1.[57]. Torsten Bögershausen reports that
+ this is fine with Cygwin 1.7 ($gmane/225824) so let's try moving it
+ ahead.
+
+ * The credential helper to talk to keychain on OS X (in contrib/) has
+ been updated to kick in not just when talking http/https but also
+ imap(s) and smtp.
+
+ * Remote transport helper has been updated to report errors and
+ maintain ref hierarchy used to keep track of its own state better.
+
+ * With "export" remote-helper protocol, (1) a push that tries to
+ update a remote ref whose name is different from the pushing side
+ does not work yet, and (2) the helper may not know how to do
+ --dry-run; these problematic cases are disabled for now.
+
+ * git-remote-hg/bzr (in contrib/) updates.
+
+ * git-remote-mw (in contrib/) hints users to check the certificate,
+ when https:// connection failed.
+
+ * git-remote-mw (in contrib/) adds a command to allow previewing the
+ contents locally before pushing it out, when working with a
+ MediaWiki remote.
+
+
+UI, Workflows & Features
+
+ * "git cat-file --batch-check=<format>" is added, primarily to allow
+ on-disk footprint of objects in packfiles (often they are a lot
+ smaller than their true size, when expressed as deltas) to be
+ reported.
+
+ * "git rebase [-i]" used to leave just "rebase" as its reflog messages
+ for some operations. They have been reworded to be more informative.
+
+ * In addition to the choice from "rebase, merge, or checkout-detach",
+ "submodule update" can allow a custom command to be used in to
+ update the working tree of submodules via the "submodule.*.update"
+ configuration variable.
+
+ * "git submodule update" can optionally clone the submodule
+ repositories shallowly.
+
+ * "git format-patch" learned "--from[=whom]" option, which sets the
+ "From: " header to the specified person (or the person who runs the
+ command, if "=whom" part is missing) and move the original author
+ information to an in-body From: header as necessary.
+
+ * The configuration variable "merge.ff" was cleary a tri-state to
+ choose one from "favor fast-forward when possible", "always create
+ a merge even when the history could fast-forward" and "do not
+ create any merge, only update when the history fast-forwards", but
+ the command line parser did not implement the usual convention of
+ "last one wins, and command line overrides the configuration"
+ correctly.
+
+ * "gitweb" learned to optionally place extra links that point at the
+ levels higher than the Gitweb pages themselves in the breadcrumbs,
+ so that it can be used as part of a larger installation.
+
+ * "git log --format=" now honors i18n.logoutputencoding configuration
+ variable.
+
+ * The "push.default=simple" mode of "git push" has been updated to
+ behave like "current" without requiring a remote tracking
+ information, when you push to a remote that is different from where
+ you fetch from (i.e. a triangular workflow).
+
+ * Having multiple "fixup!" on a line in the rebase instruction sheet
+ did not work very well with "git rebase -i --autosquash".
+
+ * "git log" learned the "--author-date-order" option, with which the
+ output is topologically sorted and commits in parallel histories
+ are shown intermixed together based on the author timestamp.
+
+ * Various subcommands of "git submodule" refused to run from anywhere
+ other than the top of the working tree of the superproject, but
+ they have been taught to let you run from a subdirectory.
+
+ * "git diff" learned a mode that ignores hunks whose change consists
+ only of additions and removals of blank lines, which is the same as
+ "diff -B" (ignore blank lines) of GNU diff.
+
+ * "git rm" gives a single message followed by list of paths to report
+ multiple paths that cannot be removed.
+
+ * "git rebase" can be told with ":/look for this string" syntax commits
+ to replay the changes onto and where the work to be replayed begins.
+
+ * Many tutorials teach users to set "color.ui" to "auto" as the first
+ thing after you set "user.name/email" to introduce yourselves to
+ Git. Now the variable defaults to "auto".
+
+ * On Cygwin, "cygstart" is now recognised as a possible way to start
+ a web browser (used in "help -w" and "instaweb" among others).
+
+ * "git status" learned status.branch and status.short configuration
+ variables to use --branch and --short options by default (override
+ with --no-branch and --no-short options from the command line).
+
+ * "git cmd <name>", when <name> happens to be a 40-hex string,
+ directly uses the 40-hex string as an object name, even if a ref
+ "refs/<some hierarchy>/<name>" exists. This disambiguation order
+ is unlikely to change, but we should warn about the ambiguity just
+ like we warn when more than one refs/ hierachies share the same
+ name.
+
+ * "git rebase" learned "--[no-]autostash" option to save local
+ changes instead of refusing to run (to which people's normal
+ response was to stash them and re-run). This introduced a corner
+ case breakage to "git am --abort" but it has been fixed.
+
+ * Instead of typing four capital letters "HEAD", you can say "@" now,
+ e.g. "git log @".
+
+ * "check-ignore" (new feature since 1.8.2) has been updated to work
+ more like "check-attr" over bidi-pipes.
+
+ * "git describe" learned "--first-parent" option to limit its closest
+ tagged commit search to the first-parent chain.
+
+ * "git merge foo" that might have meant "git merge origin/foo" is
+ diagnosed with a more informative error message.
+
+ * "git log -L<line>,<range>:<filename>" has been added. This may
+ still have leaks and rough edges, though.
+
+ * We used the approxidate() parser for "--expire=<timestamp>" options
+ of various commands, but it is better to treat --expire=all and
+ --expire=now a bit more specially than using the current timestamp.
+ "git gc" and "git reflog" have been updated with a new parsing
+ function for expiry dates.
+
+ * Updates to completion (both bash and zsh) helpers.
+
+ * The behaviour of the "--chain-reply-to" option of "git send-email"
+ have changed at 1.7.0, and we added a warning/advice message to
+ help users adjust to the new behaviour back then, but we kept it
+ around for too long. The message has finally been removed.
+
+ * "git fetch origin master" unlike "git fetch origin" or "git fetch"
+ did not update "refs/remotes/origin/master"; this was an early
+ design decision to keep the update of remote tracking branches
+ predictable, but in practice it turns out that people find it more
+ convenient to opportunistically update them whenever we have a
+ chance, and we have been updating them when we run "git push" which
+ already breaks the original "predictability" anyway.
+
+ * The configuration variable core.checkstat was advertised in the
+ documentation but the code expected core.statinfo instead.
+ For now, we accept both core.checkstat and core.statinfo, but the
+ latter will be removed in the longer term.
+
+
+Performance, Internal Implementation, etc.
+
+ * Fetching between repositories with many refs employed O(n^2)
+ algorithm to match up the common objects, which has been corrected.
+
+ * The original way to specify remote repository using .git/branches/
+ used to have a nifty feature. The code to support the feature was
+ still in a function but the caller was changed not to call it 5
+ years ago, breaking that feature and leaving the supporting code
+ unreachable. The dead code has been removed.
+
+ * "git pack-refs" that races with new ref creation or deletion have
+ been susceptible to lossage of refs under right conditions, which
+ has been tightened up.
+
+ * We read loose and packed rerferences in two steps, but after
+ deciding to read a loose ref but before actually opening it to read
+ it, another process racing with us can unlink it, which would cause
+ us to barf. The codepath has been updated to retry when such a
+ race is detected, instead of outright failing.
+
+ * Uses of the platform fnmatch(3) function (many places in the code,
+ matching pathspec, .gitignore and .gitattributes to name a few)
+ have been replaced with wildmatch, allowing "foo/**/bar" that would
+ match foo/bar, foo/a/bar, foo/a/b/bar, etc.
+
+ * Memory ownership and lifetime rules for what for-each-ref feeds to
+ its callbacks have been clarified (in short, "you do not own it, so
+ make a copy if you want to keep it").
+
+ * The revision traversal logic to improve culling of irrelevant
+ parents while traversing a mergy history has been updated.
+
+ * Some leaks in unpack-trees (used in merge, cherry-pick and other
+ codepaths) have been plugged.
+
+ * The codepath to read from marks files in fast-import/export did not
+ have to accept anything but 40-hex representation of the object
+ name. Further, fast-export did not need full in-core object
+ representation to have parsed wen reading from them. These
+ codepaths have been optimized by taking advantage of these access
+ patterns.
+
+ * Object lookup logic, when the object hashtable starts to become
+ crowded, has been optimized.
+
+ * When TEST_OUTPUT_DIRECTORY setting is used, it was handled somewhat
+ inconsistently between the test framework and t/Makefile, and logic
+ to summarize the results looked at a wrong place.
+
+ * "git clone" uses a lighter-weight implementation when making sure
+ that the history behind refs are complete.
+
+ * Many warnings from sparse source checker in compat/ area has been
+ squelched.
+
+ * The code to reading and updating packed-refs file has been updated,
+ correcting corner case bugs.
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v1.8.3
+------------------
+
+Unless otherwise noted, all the fixes since v1.8.3 in the maintenance
+track are contained in this release (see release notes to them for
+details).
+
+ * Logic to auto-detect character encodings in the commit log message
+ did not reject overlong and invalid UTF-8 characters.
+ (merge 81050ac bc/commit-invalid-utf8 later to maint).
+
+ * Pass port number as a separate argument when "send-email" initializes
+ Net::SMTP, instead of as a part of the hostname, i.e. host:port.
+ This allows GSSAPI codepath to match with the hostname given.
+ (merge 1a741bf bc/send-email-use-port-as-separate-param later to maint).
+
+ * "git diff" refused to even show difference when core.safecrlf is
+ set to true (i.e. error out) and there are offending lines in the
+ working tree files.
+ (merge 5430bb2 jc/maint-diff-core-safecrlf later to maint).
+
+ * A test that should have failed but didn't revealed a bug that needs
+ to be corrected.
+ (merge 94d75d1 jc/t1512-fix later to maint).
+
+ * An overlong path to a .git directory may have overflown the
+ temporary path buffer used to create a name for lockfiles.
+ (merge 2fbd4f9 mh/maint-lockfile-overflow later to maint).
+
+ * Invocations of "git checkout" used internally by "git rebase" were
+ counted as "checkout", and affected later "git checkout -" to the
+ the user to an unexpected place.
+ (merge 3bed291 rr/rebase-checkout-reflog later to maint).
+
+ * "git stash save", when your local change turns a tracked file into
+ a directory, has to remove files in that directory in order to
+ revert your working tree to a pristine state. This will lose
+ untracked files in such a directory, and the command now requires
+ you to "--force" it.
+
+ * The configuration variable column.ui was poorly documented.
+ (merge 5e62cc1 rr/column-doc later to maint).
+
+ * "git name-rev --refs=tags/v*" were forbidden, which was a bit
+ inconvenient (you had to give a pattern to match refs fully, like
+ --refs=refs/tags/v*).
+ (merge 98c5c4a nk/name-rev-abbreviated-refs later to maint).
+
+ * "git apply" parsed patches that add new files, generated by
+ programs other than Git, incorrectly. This is an old breakage in
+ v1.7.11 and will need to be merged down to the maintanance tracks.
+
+ * Older cURL wanted piece of memory we call it with to be stable, but
+ we updated the auth material after handing it to a call.
+
+ * "git pull" into nothing trashed "local changes" that were in the
+ index, and this avoids it.
+
+ * Many "git submodule" operations do not work on a submodule at a
+ path whose name is not in ASCII.
+
+ * "cherry-pick" had a small leak in an error codepath.
+
+ * Logic used by git-send-email to suppress cc mishandled names like
+ "A U. Thor" <author@example.xz>, where the human readable part
+ needs to be quoted (the user input may not have the double quotes
+ around the name, and comparison was done between quoted and
+ unquoted strings). It also mishandled names that need RFC2047
+ quoting.
+
+ * Call to discard_cache/discard_index (used when we use different
+ contents of the index in-core, in many operations like commit,
+ apply, and merge) used to leak memory that held the array of index
+ entries, which has been plugged.
+ (merge a0fc4db rs/discard-index-discard-array later to maint).
+
+ * "gitweb" forgot to clear a global variable $search_regexp upon each
+ request, mistakenly carrying over the previous search to a new one
+ when used as a persistent CGI.
+
+ * The wildmatch engine did not honor WM_CASEFOLD option correctly.
+
+ * "git log -c --follow $path" segfaulted upon hitting the commit that
+ renamed the $path being followed.
+
+ * When a reflog notation is used for implicit "current branch", we
+ did not say which branch and worse said "branch ''".
+
+ * "difftool --dir-diff" did not copy back changes made by the
+ end-user in the diff tool backend to the working tree in some
+ cases.
+
+ * "git push $there HEAD:branch" did not resolve HEAD early enough, so
+ it was easy to flip it around while push is still going on and push
+ out a branch that the user did not originally intended when the
+ command was started.
+
+ * The bash prompt code (in contrib/) displayed the name of the branch
+ being rebased when "rebase -i/-m/-p" modes are in use, but not the
+ plain vanilla "rebase".
+
+ * Handling of negative exclude pattern for directories "!dir" was
+ broken in the update to v1.8.3.
+
+ * zsh prompt script that borrowed from bash prompt script did not
+ work due to slight differences in array variable notation between
+ these two shells.
+
+ * An entry for "file://" scheme in the enumeration of URL types Git
+ can take in the HTML documentation was made into a clickable link
+ by mistake.
+
+ * "git push --[no-]verify" was not documented.
+
+ * Stop installing the git-remote-testpy script that is only used for
+ testing.
+
+ * "git commit --allow-empty-message -m ''" should not start an
+ editor.
+
+ * "git merge @{-1}~22" was rewritten to "git merge frotz@{1}~22"
+ incorrectly when your previous branch was "frotz" (it should be
+ rewritten to "git merge frotz~22" instead).
+
+ * "git diff -c -p" was not showing a deleted line from a hunk when
+ another hunk immediately begins where the earlier one ends.
+
+ * "git log --ancestry-path A...B" did not work as expected, as it did
+ not pay attention to the fact that the merge base between A and B
+ was the bottom of the range being specified.
+
+ * Mac OS X does not like to write(2) more than INT_MAX number of
+ bytes; work it around by chopping write(2) into smaller pieces.
+
+ * Newer MacOS X encourages the programs to compile and link with
+ their CommonCrypto, not with OpenSSL.
+
+ * "git clone foo/bar:baz" cannot be a request to clone from a remote
+ over git-over-ssh specified in the scp style. This case is now
+ detected and clones from a local repository at "foo/bar:baz".
+
+ * When $HOME is misconfigured to point at an unreadable directory, we
+ used to complain and die. Loosen the check.
+
+ * "git subtree" (in contrib/) had one codepath with loose error
+ checks to lose data at the remote side.
+
+ * "git fetch" into a shallow repository from a repository that does
+ not know about the shallow boundary commits (e.g. a different fork
+ from the repository the current shallow repository was cloned from)
+ did not work correctly.
+
+ * "git checkout foo" DWIMs the intended "upstream" and turns it into
+ "git checkout -t -b foo remotes/origin/foo". This codepath has been
+ updated to correctly take existing remote definitions into account.
diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index b0d31df..e9f984b 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -9,28 +9,11 @@
--show-stats::
Include additional statistics at the end of blame output.
--L <start>,<end>::
+-L <start>,<end>, -L :<regex>::
Annotate only the given line range. <start> and <end> can take
one of these forms:
- - number
-+
-If <start> or <end> is a number, it specifies an
-absolute line number (lines count from 1).
-+
-
-- /regex/
-+
-This form will use the first line matching the given
-POSIX regex. If <end> is a regex, it will search
-starting at the line given by <start>.
-+
-
-- +offset or -offset
-+
-This is only valid for <end> and will specify a number
-of lines before or after the line given by <start>.
-+
+include::line-range-format.txt[]
-l::
Show long rev (Default: off).
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 6e53fc5..81856dd 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -199,6 +199,9 @@ advice.*::
amWorkDir::
Advice that shows the location of the patch file when
linkgit:git-am[1] fails to apply it.
+ rmHints::
+ In case of failure in the output of linkgit:git-rm[1],
+ show directions on how to proceed from the current state.
--
core.fileMode::
@@ -919,17 +922,21 @@ color.ui::
as `color.diff` and `color.grep` that control the use of color
per command family. Its scope will expand as more commands learn
configuration to set a default for the `--color` option. Set it
- to `always` if you want all output not intended for machine
- consumption to use color, to `true` or `auto` if you want such
- output to use color when written to the terminal, or to `false` or
- `never` if you prefer Git commands not to use color unless enabled
- explicitly with some other configuration or the `--color` option.
+ to `false` or `never` if you prefer Git commands not to use
+ color unless enabled explicitly with some other configuration
+ or the `--color` option. Set it to `always` if you want all
+ output not intended for machine consumption to use color, to
+ `true` or `auto` (this is the default since Git 1.8.4) if you
+ want such output to use color when written to the terminal.
column.ui::
Specify whether supported commands should output in columns.
This variable consists of a list of tokens separated by spaces
or commas:
+
+These options control when the feature should be enabled
+(defaults to 'never'):
++
--
`always`;;
always show in columns
@@ -937,19 +944,30 @@ column.ui::
never show in columns
`auto`;;
show in columns if the output is to the terminal
+--
++
+These options control layout (defaults to 'column'). Setting any
+of these implies 'always' if none of 'always', 'never', or 'auto' are
+specified.
++
+--
`column`;;
- fill columns before rows (default)
+ fill columns before rows
`row`;;
fill rows before columns
`plain`;;
show in one column
+--
++
+Finally, these options can be combined with a layout option (defaults
+to 'nodense'):
++
+--
`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.
@@ -1826,39 +1844,59 @@ pull.twohead::
The default merge strategy to use when pulling a single branch.
push.default::
- Defines the action `git push` should take if no refspec is given
- on the command line, no refspec is configured in the remote, and
- no refspec is implied by any of the options given on the command
- line. Possible values are:
+ Defines the action `git push` should take if no refspec is
+ explicitly given. Different values are well-suited for
+ specific workflows; for instance, in a purely central workflow
+ (i.e. the fetch source is equal to the push destination),
+ `upstream` is probably what you want. Possible values are:
+
--
-* `nothing` - do not push anything.
-* `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` is a deprecated synonym for this).
- 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.
---
+
+* `nothing` - do not push anything (error out) unless a refspec is
+ explicitly given. This is primarily meant for people who want to
+ avoid mistakes by always being explicit.
+
+* `current` - push the current branch to update a branch with the same
+ name on the receiving end. Works in both central and non-central
+ workflows.
+
+* `upstream` - push the current branch back to the branch whose
+ changes are usually integrated into the current branch (which is
+ called `@{upstream}`). This mode only makes sense if you are
+ pushing to the same repository you would normally pull from
+ (i.e. central workflow).
+
+* `simple` - in centralized workflow, work like `upstream` with an
+ added safety to refuse to push if the upstream branch's name is
+ different from the local one.
++
+When pushing to a remote that is different from the remote you normally
+pull from, work as `current`. This is the safest option and is suited
+for beginners.
++
+This mode will become the default in Git 2.0.
+
+* `matching` - push all branches having the same name on both ends.
+ This makes the repository you are pushing to remember the set of
+ branches that will be pushed out (e.g. if you always push 'maint'
+ and 'master' there and no other branches, the repository you push
+ to will have these two branches, and your local 'maint' and
+ 'master' will be pushed there).
++
+To use this mode effectively, you have to make sure _all_ the
+branches you would push out are ready to be pushed out before
+running 'git push', as the whole point of this mode is to allow you
+to push all of the branches in one go. If you usually finish work
+on only one branch and push out the result, while other branches are
+unfinished, this mode is not for you. Also this mode is not
+suitable for pushing into a shared central repository, as other
+people may add new branches there, or update the tip of existing
+branches outside your control.
+
-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.
+This is currently the default, but Git 2.0 will change the default
+to `simple`.
+
+--
rebase.stat::
Whether to show a diffstat of what changed upstream since the last
@@ -1867,6 +1905,14 @@ rebase.stat::
rebase.autosquash::
If set to true enable '--autosquash' option by default.
+rebase.autostash::
+ When set to true, automatically create a temporary stash
+ before the operation begins, and apply it after the operation
+ ends. This means that you can run rebase on a dirty worktree.
+ However, use with care: the final stash application after a
+ successful rebase might result in non-trivial conflicts.
+ Defaults to false.
+
receive.autogc::
By default, git-receive-pack will run "git-gc --auto" after
receiving data from git-push and updating refs. You can stop
@@ -2066,6 +2112,14 @@ status.relativePaths::
relative to the repository root (this was the default for Git
prior to v1.5.4).
+status.short::
+ Set to true to enable --short by default in linkgit:git-status[1].
+ The option --no-short takes precedence over this variable.
+
+status.branch::
+ Set to true to enable --branch by default in linkgit:git-status[1].
+ The option --no-branch takes precedence over this variable.
+
status.showUntrackedFiles::
By default, linkgit:git-status[1] and linkgit:git-commit[1] show
files which are not currently tracked by Git. Directories which
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index fe6a1cb..bbed2cd 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -338,7 +338,7 @@ endif::git-log[]
a fraction, with a decimal point before it. I.e., `-M5` becomes
0.5, and is thus the same as `-M50%`. Similarly, `-M05` is
the same as `-M5%`. To limit detection to exact renames, use
- `-M100%`.
+ `-M100%`. The default similarity index is 50%.
-C[<n>]::
--find-copies[=<n>]::
@@ -388,14 +388,36 @@ ifndef::git-format-patch[]
that matches other criteria, nothing is selected.
-S<string>::
- Look for differences that introduce or remove an instance of
- <string>. Note that this is different than the string simply
- appearing in diff output; see the 'pickaxe' entry in
- linkgit:gitdiffcore[7] for more details.
+ Look for differences that change the number of occurrences of
+ the specified string (i.e. addition/deletion) in a file.
+ Intended for the scripter's use.
++
+It is useful when you're looking for an exact block of code (like a
+struct), and want to know the history of that block since it first
+came into being: use the feature iteratively to feed the interesting
+block in the preimage back into `-S`, and keep going until you get the
+very first version of the block.
-G<regex>::
- Look for differences whose added or removed line matches
- the given <regex>.
+ Look for differences whose patch text contains added/removed
+ lines that match <regex>.
++
+To illustrate the difference between `-S<regex> --pickaxe-regex` and
+`-G<regex>`, consider a commit with the following diff in the same
+file:
++
+----
++ return !regexec(regexp, two->ptr, 1, &regmatch, 0);
+...
+- hit = !regexec(regexp, mf2.ptr, 1, &regmatch, 0);
+----
++
+While `git log -G"regexec\(regexp"` will show this commit, `git log
+-S"regexec\(regexp" --pickaxe-regex` will not (because the number of
+occurrences of that string did not change).
++
+See the 'pickaxe' entry in linkgit:gitdiffcore[7] for more
+information.
--pickaxe-all::
When `-S` or `-G` finds a change, show all the changes in that
@@ -403,8 +425,8 @@ ifndef::git-format-patch[]
in <string>.
--pickaxe-regex::
- Make the <string> not a plain string but an extended POSIX
- regex to match.
+ Treat the <string> given to `-S` as an extended POSIX regular
+ expression to match.
endif::git-format-patch[]
-O<orderfile>::
@@ -444,6 +466,9 @@ endif::git-format-patch[]
differences even if one line has whitespace where the other
line has none.
+--ignore-blank-lines::
+ Ignore changes whose lines are all blank.
+
--inter-hunk-context=<lines>::
Show the context between diff hunks, up to the specified number
of lines, thereby fusing hunks that are close to each other.
@@ -485,7 +510,7 @@ endif::git-format-patch[]
--ignore-submodules[=<when>]::
Ignore changes to submodules in the diff generation. <when> can be
- either "none", "untracked", "dirty" or "all", which is the default
+ either "none", "untracked", "dirty" or "all", which is the default.
Using "none" will consider the submodule modified when it either contains
untracked or modified files or its HEAD differs from the commit recorded
in the superproject and can be used to override any settings of the
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index 9cb6496..ba1fe49 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -61,7 +61,7 @@ endif::git-pull[]
ifndef::git-pull[]
-t::
--tags::
- This is a short-hand for giving "refs/tags/*:refs/tags/*"
+ This is a short-hand for giving `refs/tags/*:refs/tags/*`
refspec from the command line, to ask all tags to be fetched
and stored locally. Because this acts as an explicit
refspec, the default refspecs (configured with the
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 5bbe7b6..54d8461 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -132,7 +132,7 @@ default. You can use `--no-utf8` to override this.
--resolvemsg=<msg>::
When a patch failure occurs, <msg> will be printed
to the screen before exiting. This overrides the
- standard message informing you to use `--resolved`
+ standard message informing you to use `--continue`
or `--skip` to handle the failure. This is solely
for internal use between 'git rebase' and 'git am'.
@@ -176,7 +176,7 @@ aborts in the middle. You can recover from this in one of two ways:
. hand resolve the conflict in the working directory, and update
the index file to bring it into a state that the patch should
- have produced. Then run the command with the '--resolved' option.
+ have produced. Then run the command with the '--continue' option.
The command refuses to process new mailboxes until the current
operation is finished, so if you decide to start over from scratch,
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index 9a05c2b..6cea7f1 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -8,9 +8,9 @@ git-blame - Show what revision and author last modified each line of a file
SYNOPSIS
--------
[verse]
-'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
- [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>]
- [<rev> | --contents <file> | --reverse <rev>] [--] <file>
+'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
+ [-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
+ [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
DESCRIPTION
-----------
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt
index 30d585a..3ddec0b 100644
--- a/Documentation/git-cat-file.txt
+++ b/Documentation/git-cat-file.txt
@@ -58,12 +58,16 @@ OPTIONS
to apply the filter to the content recorded in the index at <path>.
--batch::
- Print the SHA-1, type, size, and contents of each object provided on
- stdin. May not be combined with any other options or arguments.
+--batch=<format>::
+ Print object information and contents for each object provided
+ on stdin. May not be combined with any other options or arguments.
+ See the section `BATCH OUTPUT` below for details.
--batch-check::
- Print the SHA-1, type, and size of each object provided on stdin. May not
- be combined with any other options or arguments.
+--batch-check=<format>::
+ Print object information for each object provided on stdin. May
+ not be combined with any other options or arguments. See the
+ section `BATCH OUTPUT` below for details.
OUTPUT
------
@@ -78,28 +82,81 @@ If '-p' is specified, the contents of <object> are pretty-printed.
If <type> is specified, the raw (though uncompressed) contents of the <object>
will be returned.
-If '--batch' is specified, output of the following form is printed for each
-object specified on stdin:
+BATCH OUTPUT
+------------
+
+If `--batch` or `--batch-check` is given, `cat-file` will read objects
+from stdin, one per line, and print information about them.
+
+Each line is split at the first whitespace boundary. All characters
+before that whitespace are considered as a whole object name, and are
+parsed as if given to linkgit:git-rev-parse[1]. Characters after that
+whitespace can be accessed using the `%(rest)` atom (see below).
+
+You can specify the information shown for each object by using a custom
+`<format>`. The `<format>` is copied literally to stdout for each
+object, with placeholders of the form `%(atom)` expanded, followed by a
+newline. The available atoms are:
+
+`objectname`::
+ The 40-hex object name of the object.
+
+`objecttype`::
+ The type of of the object (the same as `cat-file -t` reports).
+
+`objectsize`::
+ The size, in bytes, of the object (the same as `cat-file -s`
+ reports).
+
+`objectsize:disk`::
+ The size, in bytes, that the object takes up on disk. See the
+ note about on-disk sizes in the `CAVEATS` section below.
+
+`rest`::
+ The text (if any) found after the first run of whitespace on the
+ input line (i.e., the "rest" of the line).
+
+If no format is specified, the default format is `%(objectname)
+%(objecttype) %(objectsize)`.
+
+If `--batch` is specified, the object information is followed by the
+object contents (consisting of `%(objectsize)` bytes), followed by a
+newline.
+
+For example, `--batch` without a custom format would produce:
------------
<sha1> SP <type> SP <size> LF
<contents> LF
------------
-If '--batch-check' is specified, output of the following form is printed for
-each object specified on stdin:
+Whereas `--batch-check='%(objectname) %(objecttype)'` would produce:
------------
-<sha1> SP <type> SP <size> LF
+<sha1> SP <type> LF
------------
-For both '--batch' and '--batch-check', output of the following form is printed
-for each object specified on stdin that does not exist in the repository:
+If a name is specified on stdin that cannot be resolved to an object in
+the repository, then `cat-file` will ignore any custom format and print:
------------
<object> SP missing LF
------------
+
+CAVEATS
+-------
+
+Note that the sizes of objects on disk are reported accurately, but care
+should be taken in drawing conclusions about which refs or objects are
+responsible for disk usage. The size of a packed non-delta object may be
+much larger than the size of objects which delta against it, but the
+choice of which object is the base and which is the delta is arbitrary
+and is subject to change during a repack. Note also that multiple copies
+of an object may be present in the object database; in this case, it is
+undefined which copy's size will be reported.
+
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt
index 5abdbaa..a7be80d 100644
--- a/Documentation/git-check-attr.txt
+++ b/Documentation/git-check-attr.txt
@@ -56,6 +56,11 @@ being queried and <info> can be either:
'set';; when the attribute is defined as true.
<value>;; when a value has been assigned to the attribute.
+Buffering happens as documented under the `GIT_FLUSH` option in
+linkgit:git[1]. The caller is responsible for avoiding deadlocks
+caused by overfilling an input buffer or reading from an empty output
+buffer.
+
EXAMPLES
--------
diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt
index 854e4d0..d2df487 100644
--- a/Documentation/git-check-ignore.txt
+++ b/Documentation/git-check-ignore.txt
@@ -39,6 +39,12 @@ OPTIONS
below). If `--stdin` is also given, input paths are separated
with a NUL character instead of a linefeed character.
+-n, --non-matching::
+ Show given paths which don't match any pattern. This only
+ makes sense when `--verbose` is enabled, otherwise it would
+ not be possible to distinguish between paths which match a
+ pattern and those which don't.
+
OUTPUT
------
@@ -65,6 +71,20 @@ are also used instead of colons and hard tabs:
<source> <NULL> <linenum> <NULL> <pattern> <NULL> <pathname> <NULL>
+If `-n` or `--non-matching` are specified, non-matching pathnames will
+also be output, in which case all fields in each output record except
+for <pathname> will be empty. This can be useful when running
+non-interactively, so that files can be incrementally streamed to
+STDIN of a long-running check-ignore process, and for each of these
+files, STDOUT will indicate whether that file matched a pattern or
+not. (Without this option, it would be impossible to tell whether the
+absence of output for a given file meant that it didn't match any
+pattern, or that the output hadn't been generated yet.)
+
+Buffering happens as documented under the `GIT_FLUSH` option in
+linkgit:git[1]. The caller is responsible for avoiding deadlocks
+caused by overfilling an input buffer or reading from an empty output
+buffer.
EXIT STATUS
-----------
@@ -82,7 +102,7 @@ SEE ALSO
--------
linkgit:gitignore[5]
linkgit:gitconfig[5]
-linkgit:git-ls-files[5]
+linkgit:git-ls-files[1]
GIT
---
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index a49be1b..fc02959 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -54,6 +54,8 @@ Git imposes the following rules on how references are named:
. They cannot contain a sequence `@{`.
+. They cannot be the single character `@`.
+
. They cannot contain a `\`.
These rules make it easy for shell script based tools to parse
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 23a9413..ca118ac 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -131,9 +131,9 @@ entries; instead, unmerged entries are ignored.
"--track" in linkgit:git-branch[1] for details.
+
If no '-b' option is given, the name of the new branch will be
-derived from the remote-tracking branch. If "remotes/" or "refs/remotes/"
-is prefixed it is stripped away, and then the part up to the
-next slash (which would be the nickname of the remote) is removed.
+derived from the remote-tracking branch, by looking at the local part of
+the refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".
This would tell us to use "hack" as the local branch when branching
off of "origin/hack" (or "remotes/origin/hack", or even
"refs/remotes/origin/hack"). If the given name has no slash, or the above
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index a0727d7..450f158 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -239,8 +239,8 @@ Examples
* Clone from upstream:
+
------------
-$ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
-$ cd my2.6
+$ git clone git://git.kernel.org/pub/scm/.../linux.git my-linux
+$ cd my-linux
$ make
------------
@@ -257,10 +257,10 @@ $ git show-branch
* Clone from upstream while borrowing from an existing local directory:
+
------------
-$ git clone --reference my2.6 \
- git://git.kernel.org/pub/scm/.../linux-2.7 \
- my2.7
-$ cd my2.7
+$ git clone --reference /git/linux.git \
+ git://git.kernel.org/pub/scm/.../linux.git \
+ my-linux
+$ cd my-linux
------------
@@ -271,13 +271,6 @@ $ git clone --bare -l /home/proj/.git /pub/scm/proj.git
------------
-* Create a repository on the kernel.org machine that borrows from Linus:
-+
-------------
-$ git clone --bare -l -s /pub/scm/.../torvalds/linux-2.6.git \
- /pub/scm/.../me/subsys-2.6.git
-------------
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index d88a6fc..99dc497 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -82,7 +82,7 @@ OPTIONS
--get::
Get the value for a given key (optionally filtered by a regex
matching the value). Returns error code 1 if the key was not
- found and error code 2 if multiple key values were found.
+ found and the last value if multiple key values were found.
--get-all::
Like get, but does not fail if the number of values for the key
@@ -114,6 +114,15 @@ rather than from all available files.
+
See also <<FILES>>.
+--local::
+ For writing options: write to the repository .git/config file.
+ This is the default behavior.
++
+For reading options: read only from the repository .git/config rather than
+from all available files.
++
+See also <<FILES>>.
+
-f config-file::
--file config-file::
Use the given config file instead of the one specified by GIT_CONFIG.
@@ -197,12 +206,8 @@ FILES
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.
-
-~/.gitconfig::
- User-specific configuration file. Also called "global"
- configuration file.
+$(prefix)/etc/gitconfig::
+ System-wide configuration file.
$XDG_CONFIG_HOME/git/config::
Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
@@ -212,8 +217,12 @@ $XDG_CONFIG_HOME/git/config::
you sometimes use older versions of Git, as support for this
file was added fairly recently.
-$(prefix)/etc/gitconfig::
- System-wide configuration file.
+~/.gitconfig::
+ User-specific configuration file. Also called "global"
+ configuration file.
+
+$GIT_DIR/config::
+ Repository specific configuration file.
If no further options are given, all reading options will read all of these
files that are available. If the global or the system-wide configuration
@@ -221,6 +230,10 @@ file are not available they will be ignored. If the repository configuration
file is not available or readable, 'git config' will exit with a non-zero
error code. However, in neither case will an error message be issued.
+The files are read in the order given above, with last value found taking
+precedence over values read earlier. When multiple values are taken then all
+values of a key from all files will be used.
+
All writing options will per default write to the repository specific
configuration file. Note that this also affects options like '--replace-all'
and '--unset'. *'git config' will only ever change one file at a time*.
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index 28e5ec0..9439cd6 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -88,6 +88,11 @@ OPTIONS
--always::
Show uniquely abbreviated commit object as fallback.
+--first-parent::
+ Follow only the first parent commit upon seeing a merge commit.
+ This is useful when you wish to not match tags on branches merged
+ in the history of the target commit.
+
EXAMPLES
--------
@@ -149,7 +154,9 @@ is found, its name will be output and searching will stop.
If an exact match was not found, 'git describe' will walk back
through the commit history to locate an ancestor commit which
has been tagged. The ancestor's tag will be output along with an
-abbreviation of the input committish's SHA-1.
+abbreviation of the input committish's SHA-1. If '--first-parent' was
+specified then the walk will only consider the first parent of each
+commit.
If multiple tags were found during the walk then the tag which
has the fewest commits different from the input committish will be
diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt
index c0b7c58..a86cf62 100644
--- a/Documentation/git-diff-index.txt
+++ b/Documentation/git-diff-index.txt
@@ -3,7 +3,7 @@ git-diff-index(1)
NAME
----
-git-diff-index - Compares content and mode of blobs between the index and repository
+git-diff-index - Compare a tree to the working tree or index
SYNOPSIS
@@ -13,11 +13,11 @@ SYNOPSIS
DESCRIPTION
-----------
-Compares the content and mode of the blobs found via a tree
-object with the content of the current index and, optionally
-ignoring the stat state of the file on disk. When paths are
-specified, compares only those named paths. Otherwise all
-entries in the index are compared.
+Compares the content and mode of the blobs found in a tree object
+with the corresponding tracked files in the working tree, or with the
+corresponding paths in the index. When <path> arguments are present,
+compares only paths matching those patterns. Otherwise all tracked
+files are compared.
OPTIONS
-------
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index a7b4620..78d6d50 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -18,8 +18,8 @@ SYNOPSIS
DESCRIPTION
-----------
Show changes between the working tree and the index or a tree, changes
-between the index and a tree, changes between two trees, or changes
-between two files on disk.
+between the index and a tree, changes between two trees, changes between
+two blob objects, or changes between two files on disk.
'git diff' [--options] [--] [<path>...]::
@@ -56,11 +56,6 @@ directories. This behavior can be forced by --no-index.
This is to view the changes between two arbitrary
<commit>.
-'git diff' [options] <blob> <blob>::
-
- This form is to view the differences between the raw
- contents of two blob objects.
-
'git diff' [--options] <commit>..<commit> [--] [<path>...]::
This is synonymous to the previous form. If <commit> on
@@ -87,6 +82,11 @@ and the range notations ("<commit>..<commit>" and
"<commit>\...<commit>") do not mean a range as defined in the
"SPECIFYING RANGES" section in linkgit:gitrevisions[7].
+'git diff' [options] <blob> <blob>::
+
+ This form is to view the differences between the raw
+ contents of two blob objects.
+
OPTIONS
-------
:git-diff: 1
diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt
index efb0380..85f1f30 100644
--- a/Documentation/git-fast-export.txt
+++ b/Documentation/git-fast-export.txt
@@ -141,7 +141,7 @@ Limitations
-----------
Since 'git fast-import' cannot tag trees, you will not be
-able to export the linux-2.6.git repository completely, as it contains
+able to export the linux.git repository completely, as it contains
a tag referencing a tree instead of a commit.
GIT
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 3911877..e394276 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -187,6 +187,21 @@ will want to ensure that threading is disabled for `git send-email`.
The negated form `--no-cc` discards all `Cc:` headers added so
far (from config or command line).
+--from::
+--from=<ident>::
+ Use `ident` in the `From:` header of each commit email. If the
+ author ident of the commit is not textually identical to the
+ provided `ident`, place a `From:` header in the body of the
+ message with the original author. If no `ident` is given, use
+ the committer ident.
++
+Note that this option is only useful if you are actually sending the
+emails and want to identify yourself as the sender, but retain the
+original author (and `git am` will correctly pick up the in-body
+header). Note also that `git send-email` already handles this
+transformation for you, and this option should not be used if you are
+feeding the result to `git send-email`.
+
--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.
diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt
index b370b02..2402ed6 100644
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
@@ -62,8 +62,9 @@ automatic consolidation of packs.
--prune=<date>::
Prune loose objects older than date (default is 2 weeks ago,
- overridable by the config variable `gc.pruneExpire`). This
- option is on by default.
+ overridable by the config variable `gc.pruneExpire`).
+ --prune=all prunes loose objects regardless of their age.
+ --prune is on by default.
--no-prune::
Do not prune any loose objects.
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
index bde8eec..7a4e055 100644
--- a/Documentation/git-index-pack.txt
+++ b/Documentation/git-index-pack.txt
@@ -74,6 +74,9 @@ OPTIONS
--strict::
Die, if the pack contains broken objects or links.
+--check-self-contained-and-connected::
+ Die if the pack contains broken links. For internal use only.
+
--threads=<n>::
Specifies the number of threads to spawn when resolving
deltas. This requires that index-pack be compiled with
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 88f90ed..ac2694d 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -62,6 +62,19 @@ produced by --stat etc.
Note that only message is considered, if also a diff is shown
its size is not included.
+-L <start>,<end>:<file>, -L :<regex>:<file>::
+
+ Trace the evolution of the line range given by "<start>,<end>"
+ (or the funcname regex <regex>) within the <file>. You may
+ not give any pathspec limiters. This is currently limited to
+ a walk starting from a single revision, i.e., you may only
+ give zero or one positive revision arguments.
+ You can specify this option more than once.
++
+<start> and <end> can take one of these forms:
+
+include::line-range-format.txt[]
+
<revision range>::
Show only commits in the specified revision range. When no
<revision range> is specified, it defaults to `HEAD` (i.e. the
@@ -115,9 +128,9 @@ EXAMPLES
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
+ Shows the commits that changed builtin/rev-list.c, including
those commits that occurred before the file was given its
present name.
@@ -140,6 +153,11 @@ EXAMPLES
This makes sense only when following a strict policy of merging all
topic branches when staying on a single integration branch.
+`git log -L '/int main/',/^}/:main.c`::
+
+ Shows how the function `main()` in the file 'main.c' evolved
+ over time.
+
`git log -3`::
Limits the number of commits to show to 3.
diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt
index 774de5e..2e22915 100644
--- a/Documentation/git-ls-remote.txt
+++ b/Documentation/git-ls-remote.txt
@@ -48,9 +48,9 @@ OPTIONS
exit without talking to the remote.
<repository>::
- Location of the repository. The shorthand defined in
- $GIT_DIR/branches/ can be used. Use "." (dot) to list references in
- the local repository.
+ The "remote" repository to query. This parameter can be
+ either a URL or the name of a remote (see the GIT URLS and
+ REMOTES sections of linkgit:git-fetch[1]).
<refs>...::
When unspecified, all references, after filtering done
@@ -70,9 +70,8 @@ EXAMPLES
$ git ls-remote http://www.kernel.org/pub/scm/git/git.git master pu rc
5fe978a5381f1fbad26a80e682ddd2a401966740 refs/heads/master
c781a84b5204fb294c9ccc79f8b3baceeb32c061 refs/heads/pu
- b1d096f2926c4e37c9c0b6a7bf2119bedaa277cb refs/heads/rc
- $ echo http://www.kernel.org/pub/scm/git/git.git >.git/branches/public
- $ git ls-remote --tags public v\*
+ $ git remote add korg http://www.kernel.org/pub/scm/git/git.git
+ $ git ls-remote --tags korg v\*
d6602ec5194c87b0fc87103ca4d67251c76f233a refs/tags/v0.99
f25a265a342aed6041ab0cc484224d9ca54b6f41 refs/tags/v0.99.1
c5db5456ae3b0873fc659c19fafdde22313cc441 refs/tags/v0.99.2
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 67ca99c..8c7f2f6 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -56,8 +56,8 @@ especially if those changes were further modified after the merge
was started), 'git merge --abort' will in some cases be unable to
reconstruct the original (pre-merge) changes. Therefore:
-*Warning*: Running 'git merge' with uncommitted changes is
-discouraged: while possible, it leaves you in a state that is hard to
+*Warning*: Running 'git merge' with non-trivial uncommitted changes is
+discouraged: while possible, it may leave you in a state that is hard to
back out of in the case of a conflict.
diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt
index ad1d146..15b00e0 100644
--- a/Documentation/git-name-rev.txt
+++ b/Documentation/git-name-rev.txt
@@ -25,14 +25,17 @@ OPTIONS
Do not use branch names, but only tags to name the commits
--refs=<pattern>::
- Only use refs whose names match a given shell pattern.
+ Only use refs whose names match a given shell pattern. The pattern
+ can be one of branch name, tag name or fully qualified ref name.
--all::
List all commits reachable from all refs
--stdin::
- Read from stdin, append "(<rev_name>)" to all sha1's of nameable
- commits, and pass to stdout
+ Transform stdin by substituting all the 40-character SHA-1
+ hexes (say $hex) with "$hex ($rev_name)". When used with
+ --name-only, substitute with "$rev_name", omitting $hex
+ altogether. Intended for the scripter's use.
--name-only::
Instead of printing both the SHA-1 and the name, print only
diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
index c579fbc..8cba16d 100644
--- a/Documentation/git-p4.txt
+++ b/Documentation/git-p4.txt
@@ -176,13 +176,16 @@ 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 if it does not start
- with 'p4/', that prefix is added. The branch is assumed to
- name a remote tracking, but this can be modified using
- '--import-local', or by giving a full ref name. The default
- branch is 'master'.
+--branch <ref>::
+ Import changes into <ref> instead of refs/remotes/p4/master.
+ If <ref> starts with refs/, it is used as is. Otherwise, if
+ it does not start with p4/, that prefix is added.
++
+By default a <ref> not starting with refs/ is treated as the
+name of a remote-tracking branch (under refs/remotes/). This
+behavior can be modified using the --import-local option.
++
+The default <ref> is "master".
+
This example imports a new remote "p4/proj2" into an existing
Git repository:
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 24ab07a..6ef8d59 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -3,7 +3,7 @@ git-pull(1)
NAME
----
-git-pull - Fetch from and merge with another repository or a local branch
+git-pull - Fetch from and integrate with another repository or a local branch
SYNOPSIS
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index d514813..f7dfe48 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
- [<repository> [<refspec>...]]
+ [--no-verify] [<repository> [<refspec>...]]
DESCRIPTION
-----------
@@ -136,6 +136,15 @@ already exists on the remote side.
not an ancestor of the local ref used to overwrite it.
This flag disables the check. This can cause the
remote repository to lose commits; use it with care.
+ Note that `--force` applies to all the refs that are pushed,
+ hence using it with `push.default` set to `matching` or with
+ multiple push destinations configured with `remote.*.push`
+ may overwrite refs other than the current branch (including
+ local refs that are strictly behind their remote counterpart).
+ To force a push to only one branch, use a `+` in front of the
+ refspec to push (e.g `git push origin +master` to force a push
+ to the `master` branch). See the `<refspec>...` section above
+ for details.
--repo=<repository>::
This option is only relevant if no <repository> argument is
@@ -195,6 +204,11 @@ useful if you write an alias or script around 'git push'.
be pushed. If on-demand was not able to push all necessary
revisions it will also be aborted and exit with non-zero status.
+--[no-]verify::
+ Toggle the pre-push hook (see linkgit:githooks[5]). The
+ default is \--verify, giving the hook a chance to prevent the
+ push. With \--no-verify, the hook is bypassed completely.
+
include::urls-remotes.txt[]
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index aca8405..6b2e1c8 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -208,6 +208,9 @@ rebase.stat::
rebase.autosquash::
If set to true enable '--autosquash' option by default.
+rebase.autostash::
+ If set to true enable '--autostash' option by default.
+
OPTIONS
-------
--onto <newbase>::
@@ -386,7 +389,9 @@ squash/fixup series.
the same ..., automatically modify the todo list of rebase -i
so that the commit marked for squashing comes right after the
commit to be modified, and change the action of the moved
- commit from `pick` to `squash` (or `fixup`).
+ commit from `pick` to `squash` (or `fixup`). Ignores subsequent
+ "fixup! " or "squash! " after the first, in case you referred to an
+ earlier fixup/squash with `git commit --fixup/--squash`.
+
This option is only valid when the '--interactive' option is used.
+
@@ -394,6 +399,13 @@ If the '--autosquash' option is enabled by default using the
configuration variable `rebase.autosquash`, this option can be
used to override and disable this setting.
+--[no-]autostash::
+ Automatically create a temporary stash before the operation
+ begins, and apply it after the operation ends. This means
+ that you can run rebase on a dirty worktree. However, use
+ with care: the final stash application after a successful
+ rebase might result in non-trivial conflicts.
+
--no-ff::
With --interactive, cherry-pick all rebased commits instead of
fast-forwarding over the unchanged ones. This ensures that the
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index fb8697e..70791b9 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -67,14 +67,19 @@ them.
--expire=<time>::
Entries older than this time are pruned. Without the
option it is taken from configuration `gc.reflogExpire`,
- which in turn defaults to 90 days.
+ which in turn defaults to 90 days. --expire=all prunes
+ entries regardless of their age; --expire=never turns off
+ pruning of reachable entries (but see --expire-unreachable).
--expire-unreachable=<time>::
Entries older than this time and not reachable from
the current tip of the branch are pruned. Without the
option it is taken from configuration
`gc.reflogExpireUnreachable`, which in turn defaults to
- 30 days.
+ 30 days. --expire-unreachable=all prunes unreachable
+ entries regardless of their age; --expire-unreachable=never
+ turns off early pruning of unreachable entries (but see
+ --expire).
--all::
Instead of listing <refs> explicitly, prune all refs.
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 581bb4c..9c3e3bf 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -187,18 +187,25 @@ Examples
$ git remote
origin
$ git branch -r
-origin/master
-$ git remote add linux-nfs git://linux-nfs.org/pub/linux/nfs-2.6.git
+ origin/HEAD -> origin/master
+ origin/master
+$ git remote add staging git://git.kernel.org/.../gregkh/staging.git
$ git remote
-linux-nfs
origin
-$ git fetch
-* refs/remotes/linux-nfs/master: storing branch 'master' ...
- commit: bf81b46
+staging
+$ git fetch staging
+...
+From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
+ * [new branch] master -> staging/master
+ * [new branch] staging-linus -> staging/staging-linus
+ * [new branch] staging-next -> staging/staging-next
$ git branch -r
-origin/master
-linux-nfs/master
-$ git checkout -b nfs linux-nfs/master
+ origin/HEAD -> origin/master
+ origin/master
+ staging/master
+ staging/staging-linus
+ staging/staging-next
+$ git checkout -b staging staging/master
...
------------
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index a404b47..f445cb3 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git reset' [-q] [<tree-ish>] [--] <paths>...
-'git reset' (--patch | -p) [<tree-sh>] [--] [<paths>...]
+'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]
'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
DESCRIPTION
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 947d62f..993903c 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -59,6 +59,22 @@ OPTIONS
If there is no parameter given by the user, use `<arg>`
instead.
+--prefix <arg>::
+ Behave as if 'git rev-parse' was invoked from the `<arg>`
+ subdirectory of the working tree. Any relative filenames are
+ resolved as if they are prefixed by `<arg>` and will be printed
+ in that form.
++
+This can be used to convert arguments to a command run in a subdirectory
+so that they can still be used after moving to the top-level of the
+repository. For example:
++
+----
+prefix=$(git rev-parse --show-prefix)
+cd "$(git rev-parse --show-toplevel)"
+eval "set -- $(git rev-parse --sq --prefix "$prefix" "$@")"
+----
+
--verify::
Verify that exactly one parameter is provided, and that it
can be turned into a raw 20-byte SHA-1 that can be used to
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 711ffe1..7c8b648 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -13,10 +13,12 @@ SYNOPSIS
'git stash' drop [-q|--quiet] [<stash>]
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
'git stash' branch <branchname> [<stash>]
-'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
- [-u|--include-untracked] [-a|--all] [<message>]]
+'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
+ [-u|--include-untracked] [-a|--all] [-f|--force]
+ [<message>]]
'git stash' clear
-'git stash' create
+'git stash' create [<message>]
+'git stash' store [-m|--message <message>] [-q|--quiet] <commit>
DESCRIPTION
-----------
@@ -43,7 +45,7 @@ is also possible).
OPTIONS
-------
-save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
+save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-f|--force] [<message>]::
Save your local modifications to a new 'stash', and run `git reset
--hard` to revert them. The <message> part is optional and gives
@@ -70,6 +72,13 @@ 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.
++
+In some cases, saving a stash could mean irretrievably removing some
+data - if a directory with untracked files replaces a tracked file of
+the same name, the new untracked files are not saved (except in case
+of `--include-untracked`) but the original tracked file shall be restored.
+By default, `stash save` will abort in such a case; `--force` will allow
+it to remove the untracked files.
list [<options>]::
@@ -151,7 +160,15 @@ create::
Create a stash (which is a regular commit object) and return its
object name, without storing it anywhere in the ref namespace.
+ This is intended to be useful for scripts. It is probably not
+ the command you want to use; see "save" above.
+
+store::
+ Store a given stash created via 'git stash create' (which is a
+ dangling merge commit) in the stash ref, updating the stash
+ reflog. This is intended to be useful for scripts. It is
+ probably not the command you want to use; see "save" above.
DISCUSSION
----------
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index e576713..bfef8a0 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -10,12 +10,12 @@ SYNOPSIS
--------
[verse]
'git submodule' [--quiet] add [-b <branch>] [-f|--force] [--name <name>]
- [--reference <repository>] [--] <repository> [<path>]
+ [--reference <repository>] [--depth <depth>] [--] <repository> [<path>]
'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
'git submodule' [--quiet] deinit [-f|--force] [--] <path>...
'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]
- [-f|--force] [--rebase] [--reference <repository>]
+ [-f|--force] [--rebase] [--reference <repository>] [--depth <depth>]
[--merge] [--recursive] [--] [<path>...]
'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
[commit] [--] [<path>...]
@@ -159,7 +159,9 @@ update::
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`.
+ `--checkout`. Setting the key `submodule.$name.update` to `!command`
+ will cause `command` to be run. `command` can be any arbitrary shell
+ command that takes a single argument, namely the sha1 to update to.
+
If the submodule is not yet initialized, and you just want to use the
setting as stored in .gitmodules, you can automatically initialize the
@@ -262,7 +264,7 @@ OPTIONS
--remote::
This option is only valid for the update command. Instead of using
the superproject's recorded SHA-1 to update the submodule, use the
- status of the submodule's remote tracking branch. The remote used
+ status of the submodule's remote-tracking branch. The remote used
is branch's remote (`branch.<name>.remote`), defaulting to `origin`.
The remote branch used defaults to `master`, but the branch name may
be overridden by setting the `submodule.<name>.branch` option in
@@ -328,6 +330,12 @@ for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully.
only in the submodules of the current repo, but also
in any nested submodules inside those submodules (and so on).
+--depth::
+ This option is valid for add and update commands. Create a 'shallow'
+ clone with a history truncated to the specified number of revisions.
+ See linkgit:git-clone[1]
+
+
<path>...::
Paths to submodule(s). When specified this will restrict the command
to only operate on the submodules found at the specified paths.
diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt
index ba79cb4..5aec4ec 100644
--- a/Documentation/git-web--browse.txt
+++ b/Documentation/git-web--browse.txt
@@ -34,6 +34,7 @@ The following browsers (or commands) are currently supported:
* dillo
* open (this is the default under Mac OS X GUI)
* start (this is the default under MinGW)
+* cygstart (this is the default under Cygwin)
Custom commands may also be specified.
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 9e302b0..e9b4922 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -43,12 +43,20 @@ unreleased) version of Git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
+* link:v1.8.3.3/git.html[documentation for release 1.8.3.3]
+
+* release notes for
+ link:RelNotes/1.8.3.3.txt[1.8.3.3],
+ link:RelNotes/1.8.3.2.txt[1.8.3.2],
+ link:RelNotes/1.8.3.1.txt[1.8.3.1],
+ link:RelNotes/1.8.3.txt[1.8.3].
+
* link:v1.8.2.3/git.html[documentation for release 1.8.2.3]
* release notes for
- link:RelNotes/1.8.2.3.txt[1.8.2.3].
- link:RelNotes/1.8.2.2.txt[1.8.2.2].
- link:RelNotes/1.8.2.1.txt[1.8.2.1].
+ link:RelNotes/1.8.2.3.txt[1.8.2.3],
+ link:RelNotes/1.8.2.2.txt[1.8.2.2],
+ link:RelNotes/1.8.2.1.txt[1.8.2.1],
link:RelNotes/1.8.2.txt[1.8.2].
* link:v1.8.1.6/git.html[documentation for release 1.8.1.6]
@@ -677,9 +685,7 @@ Git so take care if using Cogito etc.
The '--git-dir' command-line option also sets this value.
'GIT_WORK_TREE'::
- Set the path to the working tree. The value will not be
- used in combination with repositories found automatically in
- a .git directory (i.e. $GIT_DIR is not set).
+ Set the path to the root of the working tree.
This can also be controlled by the '--work-tree' command line
option and the core.worktree configuration variable.
@@ -811,8 +817,9 @@ for further details.
'GIT_FLUSH'::
If this environment variable is set to "1", then commands such
as 'git blame' (in incremental mode), 'git rev-list', 'git log',
- and 'git whatchanged' will force a flush of the output stream
- after each commit-oriented record have been flushed. If this
+ 'git check-attr', 'git check-ignore', and 'git whatchanged' will
+ force a flush of the output stream after each record have been
+ flushed. If this
variable is set to "0", the output of these commands will be done
using completely buffered I/O. If this environment variable is
not set, Git will choose buffered or record-oriented flushing
@@ -832,6 +839,19 @@ for further details.
as a file path and will try to write the trace messages
into it.
+'GIT_TRACE_PACK_ACCESS'::
+ If this variable is set to a path, a file will be created at
+ the given path logging all accesses to any packs. For each
+ access, the pack file name and an offset in the pack is
+ recorded. This may be helpful for troubleshooting some
+ pack-related performance problems.
+
+'GIT_TRACE_PACKET'::
+ If this variable is set, it shows a trace of all packets
+ coming in or out of a given program. This can help with
+ debugging object negotiation or other protocol issues. Tracing
+ is turned off at a packet starting with "PACK".
+
GIT_LITERAL_PATHSPECS::
Setting this variable to `1` will cause Git to treat all
pathspecs literally, rather than as glob patterns. For example,
diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt
index 568d757..c8b3e51 100644
--- a/Documentation/gitdiffcore.txt
+++ b/Documentation/gitdiffcore.txt
@@ -222,26 +222,35 @@ version prefixed with '+'.
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-*'
-commands.
-
-When diffcore-pickaxe is in use, it checks if there are
-filepairs whose "result" side and whose "origin" side have
-different number of specified string. Such a filepair represents
-"the string appeared in this changeset". It also checks for the
-opposite case that loses the specified string.
-
-When `--pickaxe-all` is not in effect, diffcore-pickaxe leaves
-only such filepairs that touch the specified string in its
-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
+This transformation limits the set of filepairs to those that change
+specified strings between the preimage and the postimage in a certain
+way. -S<block of text> and -G<regular expression> options are used to
+specify different ways these strings are sought.
+
+"-S<block of text>" detects filepairs whose preimage and postimage
+have different number of occurrences of the specified block of text.
+By definition, it will not detect in-file moves. Also, when a
+changeset moves a file wholesale without affecting the interesting
+string, diffcore-rename kicks in as usual, and `-S` omits the filepair
+(since the number of occurrences of that string didn't change in that
+rename-detected filepair). When used with `--pickaxe-regex`, treat
+the <block of text> as an extended POSIX regular expression to match,
+instead of a literal string.
+
+"-G<regular expression>" (mnemonic: grep) detects filepairs whose
+textual diff has an added or a deleted line that matches the given
+regular expression. This means that it will detect in-file (or what
+rename-detection considers the same file) moves, which is noise. The
+implementation runs diff twice and greps, and this can be quite
+expensive.
+
+When `-S` or `-G` are used without `--pickaxe-all`, only filepairs
+that match their respective criterion are kept in the output. When
+`--pickaxe-all` is used, if even one filepair matches their respective
+criterion in a changeset, the entire changeset is kept. This behavior
+is designed to make reviewing changes in the context of the whole
changeset easier.
-
diffcore-order: For Sorting the Output Based on Filenames
---------------------------------------------------------
diff --git a/Documentation/gitremote-helpers.txt b/Documentation/gitremote-helpers.txt
index da74641..0827f69 100644
--- a/Documentation/gitremote-helpers.txt
+++ b/Documentation/gitremote-helpers.txt
@@ -159,11 +159,11 @@ Miscellaneous capabilities
carried out.
'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.
+ For remote helpers that implement 'import' or 'export', this capability
+ allows the refs to be constrained to 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.
+ capability use this. It's mandatory for 'export'.
+
A helper advertising the capability
`refspec refs/heads/*:refs/svn/origin/branches/*`
@@ -174,8 +174,8 @@ 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 a helper does not need a specific 'refspec'
-capability then it should advertise `refspec *:*`.
+the list command. If no 'refspec' capability is advertised,
+there is an implied `refspec *:*`.
'bidi-import'::
This modifies the 'import' capability.
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index d6f3393..aa03882 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -211,6 +211,9 @@ shallow::
and maintained by shallow clone mechanism. See `--depth`
option to linkgit:git-clone[1] and linkgit:git-fetch[1].
+modules::
+ Contains the git-repositories of the submodules.
+
SEE ALSO
--------
linkgit:git-init[1],
diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt
index ea0526e..305db63 100644
--- a/Documentation/gitweb.conf.txt
+++ b/Documentation/gitweb.conf.txt
@@ -336,8 +336,26 @@ $home_link_str::
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.
+ as this link leads to the list of projects. Another popular choice is to
+ set it to the name of site. Note that it is treated as raw HTML so it
+ should not be set from untrusted sources.
+
+@extra_breadcrumbs::
+ Additional links to be added to the start of the breadcrumb trail before
+ the home link, to pages that are logically "above" the gitweb projects
+ list, such as the organization and department which host the gitweb
+ server. Each element of the list is a reference to an array, in which
+ element 0 is the link text (equivalent to `$home_link_str`) and element
+ 1 is the target URL (equivalent to `$home_link`).
++
+For example, the following setting produces a breadcrumb trail like
+"home / dev / projects / ..." where "projects" is the home link.
+----------------------------------------------------------------------------
+ our @extra_breadcrumbs = (
+ [ 'home' => 'https://www.example.org/' ],
+ [ 'dev' => 'https://dev.example.org/' ],
+ );
+----------------------------------------------------------------------------
$logo_url::
$logo_label::
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 68a18e1..dba5062 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -113,7 +113,7 @@ Note that commands that operate on the history of the current branch
while the HEAD is detached. They update the HEAD to point at the tip
of the updated history without affecting any branch. Commands that
update or inquire information _about_ the current branch (e.g. `git
-branch --set-upstream-to` that sets what remote tracking branch the
+branch --set-upstream-to` that sets what remote-tracking branch the
current branch integrates with) obviously do not work, as there is no
(real) current branch to ask about in this state.
@@ -267,7 +267,7 @@ This commit is referred to as a "merge commit", or sometimes just a
The default upstream <<def_repository,repository>>. Most projects have
at least one upstream project which they track. By default
'origin' is used for that purpose. New upstream updates
- will be fetched into remote <<def_remote_tracking_branch,remote-tracking branches>> named
+ will be fetched into <<def_remote_tracking_branch,remote-tracking branches>> named
origin/name-of-upstream-branch, which you can see using
`git branch -r`.
@@ -400,12 +400,13 @@ should not be combined with other pathspec.
<<def_ref,ref>> and local ref.
[[def_remote_tracking_branch]]remote-tracking branch::
- A regular Git <<def_branch,branch>> that is used to follow changes from
- another <<def_repository,repository>>. A remote-tracking
- branch should not contain direct modifications or have local commits
- made to it. A remote-tracking branch can usually be
- identified as the right-hand-side <<def_ref,ref>> in a Pull:
- <<def_refspec,refspec>>.
+ A <<def_ref,ref>> that is used to follow changes from another
+ <<def_repository,repository>>. It typically looks like
+ 'refs/remotes/foo/bar' (indicating that it tracks a branch named
+ 'bar' in a remote named 'foo'), and matches the right-hand-side of
+ a configured fetch <<def_refspec,refspec>>. A remote-tracking
+ branch should not contain direct modifications or have local
+ commits made to it.
[[def_repository]]repository::
A collection of <<def_ref,refs>> together with an
diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
new file mode 100644
index 0000000..3e7ce72
--- /dev/null
+++ b/Documentation/line-range-format.txt
@@ -0,0 +1,25 @@
+- number
++
+If <start> or <end> is a number, it specifies an
+absolute line number (lines count from 1).
++
+
+- /regex/
++
+This form will use the first line matching the given
+POSIX regex. If <end> is a regex, it will search
+starting at the line given by <start>.
++
+
+- +offset or -offset
++
+This is only valid for <end> and will specify a number
+of lines before or after the line given by <start>.
++
+
+- :regex
++
+If the option's argument is of the form :regex, it denotes the range
+from the first funcname line that matches <regex>, up to the next
+funcname line.
++
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 2adccf8..afba8d4 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -8,12 +8,13 @@ failed and do not autocommit, to give the user a chance to
inspect and further tweak the merge result before committing.
--edit::
+-e::
--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
+ discouraged). The `--edit` (or `-e`) 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.
+
diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt
index 94a9d32..18cffc2 100644
--- a/Documentation/pull-fetch-param.txt
+++ b/Documentation/pull-fetch-param.txt
@@ -68,6 +68,11 @@ Some short-cut notations are also supported.
+
* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`;
it requests fetching everything up to the given tag.
-* A parameter <ref> without a colon is equivalent to
- <ref>: when pulling/fetching, so it merges <ref> into the current
- branch without storing the remote branch anywhere locally
+ifndef::git-pull[]
+* A parameter <ref> without a colon fetches that ref into FETCH_HEAD,
+endif::git-pull[]
+ifdef::git-pull[]
+* A parameter <ref> without a colon merges <ref> into the current
+ branch,
+endif::git-pull[]
+ and updates the remote-tracking branches (if any).
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index c23688a..e632e85 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -271,8 +271,8 @@ See also linkgit:git-reflog[1].
--boundary::
- Output uninteresting commits at the boundary, which are usually
- not shown.
+ Output excluded boundary commits. Boundary commits are
+ prefixed with `-`.
--
@@ -342,13 +342,13 @@ In the following, we will always refer to the same example history to
illustrate the differences between simplification settings. We assume
that you are filtering for a file `foo` in this commit graph:
-----------------------------------------------------------------------
- .-A---M---N---O---P
- / / / / /
- I B C D E
- \ / / / /
- `-------------'
+ .-A---M---N---O---P---Q
+ / / / / / /
+ I B C D E Y
+ \ / / / / /
+ `-------------' X
-----------------------------------------------------------------------
-The horizontal line of history A---P is taken to be the first parent of
+The horizontal line of history A---Q is taken to be the first parent of
each merge. The commits are:
* `I` is the initial commit, in which `foo` exists with contents
@@ -367,8 +367,11 @@ each merge. The commits are:
`N` and `D` to "foobarbaz"; i.e., it is not TREESAME to any parent.
* `E` changes `quux` to "xyzzy", and its merge `P` combines the
- strings to "quux xyzzy". Despite appearing interesting, `P` is
- TREESAME to all parents.
+ strings to "quux xyzzy". `P` is TREESAME to `O`, but not to `E`.
+
+* `X` is an indpendent root commit that added a new file `side`, and `Y`
+ modified it. `Y` is TREESAME to `X`. Its merge `Q` added `side` to `P`, and
+ `Q` is TREESAME to `P`, but not to `Y`.
'rev-list' walks backwards through history, including or excluding
commits based on whether '\--full-history' and/or parent rewriting
@@ -410,10 +413,10 @@ parent lines.
the example, we get
+
-----------------------------------------------------------------------
- I A B N D O
+ I A B N D O P Q
-----------------------------------------------------------------------
+
-`P` and `M` were excluded because they are TREESAME to a parent. `E`,
+`M` was excluded because it is TREESAME to both parents. `E`,
`C` and `B` were all walked, but only `B` was !TREESAME, so the others
do not appear.
+
@@ -431,7 +434,7 @@ Along each parent, prune away commits that are not included
themselves. This results in
+
-----------------------------------------------------------------------
- .-A---M---N---O---P
+ .-A---M---N---O---P---Q
/ / / / /
I B / D /
\ / / / /
@@ -441,7 +444,7 @@ themselves. This results in
Compare to '\--full-history' without rewriting above. Note that `E`
was pruned away because it is TREESAME, but the parent list of P was
rewritten to contain `E`'s parent `I`. The same happened for `C` and
-`N`. Note also that `P` was included despite being TREESAME.
+`N`, and `X`, `Y` and `Q`.
In addition to the above settings, you can change whether TREESAME
affects inclusion:
@@ -471,8 +474,9 @@ history according to the following rules:
* Set `C'` to `C`.
+
* Replace each parent `P` of `C'` with its simplification `P'`. In
- the process, drop parents that are ancestors of other parents, and
- remove duplicates.
+ the process, drop parents that are ancestors of other parents or that are
+ root commits TREESAME to an empty tree, and remove duplicates, but take care
+ to never drop all parents that we are TREESAME to.
+
* If after this parent rewriting, `C'` is a root or merge commit (has
zero or >1 parents), a boundary commit, or !TREESAME, it remains.
@@ -490,7 +494,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`, `P` and `Q` over '--full-history':
+
--
* `N`'s parent list had `I` removed, because it is an ancestor of the
@@ -498,6 +502,10 @@ Note the major differences in `N` and `P` over '--full-history':
+
* `P`'s parent list similarly had `I` removed. `P` was then
removed completely, because it had one parent and is TREESAME.
++
+* `Q`'s parent list had `Y` simplified to `X`. `X` was then removed, because it
+ was a TREESAME root. `Q` was then removed completely, because it had one
+ parent and is TREESAME.
--
Finally, there is a fifth simplification mode available:
@@ -617,6 +625,10 @@ By default, the commits are shown in reverse chronological order.
Show no parents before all of its children are shown, but
otherwise show commits in the commit timestamp order.
+--author-date-order::
+ Show no parents before all of its children are shown, but
+ otherwise show commits in the author timestamp order.
+
--topo-order::
Show no parents before all of its children are shown, and
avoid showing commits on multiple lines of history
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index d477b3f..09896a3 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -58,6 +58,9 @@ the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file.
While the ref name encoding is unspecified, UTF-8 is preferred as
some output processing may assume ref names in UTF-8.
+'@'::
+ '@' alone is a shortcut for 'HEAD'.
+
'<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}'::
A ref followed by the suffix '@' with a date specification
enclosed in a brace
diff --git a/Documentation/technical/api-builtin.txt b/Documentation/technical/api-builtin.txt
index 4a4228b..f3c1357 100644
--- a/Documentation/technical/api-builtin.txt
+++ b/Documentation/technical/api-builtin.txt
@@ -39,7 +39,7 @@ where options is the bitwise-or of:
on bare repositories.
This only makes sense when `RUN_SETUP` is also set.
-. Add `builtin-foo.o` to `BUILTIN_OBJS` in `Makefile`.
+. Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`.
Additionally, if `foo` is a new command, there are 3 more things to do:
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
index 32ddc1c..0be2b51 100644
--- a/Documentation/technical/api-parse-options.txt
+++ b/Documentation/technical/api-parse-options.txt
@@ -41,6 +41,8 @@ The parse-options API allows:
* Boolean long options can be 'negated' (or 'unset') by prepending
`no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
options that begin with `no-` can be 'negated' by removing it.
+ Other long options can be unset (e.g., set string to NULL, set
+ integer to 0) by prepending `no-`.
* Options and non-option arguments can clearly be separated using the `--`
option, e.g. `-a -b --option -- --this-is-a-file` indicates that
@@ -174,6 +176,10 @@ There are some macros to easily define options:
Introduce an option with date argument, see `approxidate()`.
The timestamp is put into `int_var`.
+`OPT_EXPIRY_DATE(short, long, &int_var, description)`::
+ Introduce an option with expiry date argument, see `parse_expiry_date()`.
+ The timestamp is put into `int_var`.
+
`OPT_CALLBACK(short, long, &var, arg_str, description, func_ptr)`::
Introduce an option with argument.
The argument will be fed into the function given by `func_ptr`
@@ -269,10 +275,10 @@ Examples
--------
See `test-parse-options.c` and
-`builtin-add.c`,
-`builtin-clone.c`,
-`builtin-commit.c`,
-`builtin-fetch.c`,
-`builtin-fsck.c`,
-`builtin-rm.c`
+`builtin/add.c`,
+`builtin/clone.c`,
+`builtin/commit.c`,
+`builtin/fetch.c`,
+`builtin/fsck.c`,
+`builtin/rm.c`
for real-world examples.
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index f1a51ed..b898e97 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -228,8 +228,7 @@ obtained through ref discovery.
The client MUST write all obj-ids which it only has shallow copies
of (meaning that it does not have the parents of a commit) as
'shallow' lines so that the server is aware of the limitations of
-the client's history. Clients MUST NOT mention an obj-id which
-it does not know exists on the server.
+the client's history.
The client now sends the maximum commit history depth it wants for
this transaction, which is the number of commits it wants from the
diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt
index 6dc82ca..f716d6d 100644
--- a/Documentation/technical/racy-git.txt
+++ b/Documentation/technical/racy-git.txt
@@ -135,9 +135,9 @@ them, and give the same timestamp to the index file:
$ git ls-files | git update-index --stdin
$ touch -r .datestamp .git/index
-This will make all index entries racily clean. The linux-2.6
-project, for example, there are over 20,000 files in the working
-tree. On my Athlon 64 X2 3800+, after the above:
+This will make all index entries racily clean. The linux project, for
+example, there are over 20,000 files in the working tree. On my
+Athlon 64 X2 3800+, after the above:
$ /usr/bin/time git diff-files
1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
diff --git a/Documentation/urls.txt b/Documentation/urls.txt
index 3ca122f..9ccb246 100644
--- a/Documentation/urls.txt
+++ b/Documentation/urls.txt
@@ -11,6 +11,9 @@ and ftps can be used for fetching and rsync can be used for fetching
and pushing, but these are inefficient and deprecated; do not use
them).
+The native transport (i.e. git:// URL) does no authentication and
+should be used with caution on unsecured networks.
+
The following syntaxes may be used with them:
- ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/path/to/repo.git/
@@ -23,6 +26,12 @@ An alternative scp-like syntax may also be used with the ssh protocol:
- {startsb}user@{endsb}host.xz:path/to/repo.git/
+This syntax is only recognized if there are no slashes before the
+first colon. This helps differentiate a local path that contains a
+colon. For example the local path `foo:bar` could be specified as an
+absolute path or `./foo:bar` to avoid being misinterpreted as an ssh
+url.
+
The ssh and git protocols additionally support ~username expansion:
- ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/
@@ -33,7 +42,7 @@ For local repositories, also supported by Git natively, the following
syntaxes may be used:
- /path/to/repo.git/
-- file:///path/to/repo.git/
+- \file:///path/to/repo.git/
ifndef::git-clone[]
These two syntaxes are mostly equivalent, except when cloning, when
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index e831cc2..e364007 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -57,17 +57,17 @@ download a copy of an existing repository. If you don't already have a
project in mind, here are some interesting examples:
------------------------------------------------
- # Git itself (approx. 10MB download):
+ # Git itself (approx. 40MB download):
$ git clone git://git.kernel.org/pub/scm/git/git.git
- # the Linux kernel (approx. 150MB download):
-$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+ # the Linux kernel (approx. 640MB download):
+$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
------------------------------------------------
The initial clone may be time-consuming for a large project, but you
will only need to clone once.
-The clone command creates a new directory named after the project (`git`
-or `linux-2.6` in the examples above). After you cd into this
+The clone command creates a new directory named after the project
+(`git` or `linux` in the examples above). After you cd into this
directory, you will see that it contains a copy of the project files,
called the <<def_working_tree,working tree>>, together with a special
top-level directory named `.git`, which contains all the information
@@ -431,19 +431,25 @@ You can also track branches from repositories other than the one you
cloned from, using linkgit:git-remote[1]:
-------------------------------------------------
-$ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
-$ git fetch linux-nfs
-* refs/remotes/linux-nfs/master: storing branch 'master' ...
- commit: bf81b46
+$ git remote add staging git://git.kernel.org/.../gregkh/staging.git
+$ git fetch staging
+...
+From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
+ * [new branch] master -> staging/master
+ * [new branch] staging-linus -> staging/staging-linus
+ * [new branch] staging-next -> staging/staging-next
-------------------------------------------------
New remote-tracking branches will be stored under the shorthand name
-that you gave `git remote add`, in this case `linux-nfs`:
+that you gave `git remote add`, in this case `staging`:
-------------------------------------------------
$ git branch -r
-linux-nfs/master
-origin/master
+ origin/HEAD -> origin/master
+ origin/master
+ staging/master
+ staging/staging-linus
+ staging/staging-next
-------------------------------------------------
If you run `git fetch <remote>` later, the remote-tracking branches
@@ -455,9 +461,9 @@ a new stanza:
-------------------------------------------------
$ cat .git/config
...
-[remote "linux-nfs"]
- url = git://linux-nfs.org/pub/nfs-2.6.git
- fetch = +refs/heads/*:refs/remotes/linux-nfs/*
+[remote "staging"]
+ url = git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
+ fetch = +refs/heads/*:refs/remotes/staging/*
...
-------------------------------------------------
@@ -1835,7 +1841,7 @@ Once the index is updated with the results of the conflict
resolution, instead of creating a new commit, just run
-------------------------------------------------
-$ git am --resolved
+$ git am --continue
-------------------------------------------------
and Git will create the commit for you and continue applying the
@@ -2156,7 +2162,7 @@ To set this up, first create your work tree by cloning Linus's public
tree:
-------------------------------------------------
-$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git work
+$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git work
$ cd work
-------------------------------------------------
@@ -2198,7 +2204,7 @@ make it easy to push both branches to your public tree. (See
-------------------------------------------------
$ cat >> .git/config <<EOF
[remote "mytree"]
- url = master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git
+ url = master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux.git
push = release
push = test
EOF
@@ -4256,15 +4262,16 @@ no longer need to call `setup_pager()` directly).
Nowadays, `git log` is a builtin, which means that it is _contained_ in the
command `git`. The source side of a builtin is
-- a function called `cmd_<bla>`, typically defined in `builtin-<bla>.c`,
- and declared in `builtin.h`,
+- a function called `cmd_<bla>`, typically defined in `builtin/<bla.c>`
+ (note that older versions of Git used to have it in `builtin-<bla>.c`
+ instead), and declared in `builtin.h`.
- an entry in the `commands[]` array in `git.c`, and
- an entry in `BUILTIN_OBJECTS` in the `Makefile`.
Sometimes, more than one builtin is contained in one source file. For
-example, `cmd_whatchanged()` and `cmd_log()` both reside in `builtin-log.c`,
+example, `cmd_whatchanged()` and `cmd_log()` both reside in `builtin/log.c`,
since they share quite a bit of code. In that case, the commands which are
_not_ named like the `.c` file in which they live have to be listed in
`BUILT_INS` in the `Makefile`.
@@ -4287,10 +4294,10 @@ For the sake of clarity, let's stay with `git cat-file`, because it
- is plumbing, and
- was around even in the initial commit (it literally went only through
- some 20 revisions as `cat-file.c`, was renamed to `builtin-cat-file.c`
+ some 20 revisions as `cat-file.c`, was renamed to `builtin/cat-file.c`
when made a builtin, and then saw less than 10 versions).
-So, look into `builtin-cat-file.c`, search for `cmd_cat_file()` and look what
+So, look into `builtin/cat-file.c`, search for `cmd_cat_file()` and look what
it does.
------------------------------------------------------------------
@@ -4366,7 +4373,7 @@ Another example: Find out what to do in order to make some script a
builtin:
-------------------------------------------------
-$ git log --no-merges --diff-filter=A builtin-*.c
+$ git log --no-merges --diff-filter=A builtin/*.c
-------------------------------------------------
You see, Git is actually the best tool to find out about the source of Git
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index aa87dbf..b4d4e50 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.8.3-rc3
+DEF_VER=v1.8.3.GIT
LF='
'
@@ -11,7 +11,7 @@ LF='
if test -f version
then
VN=$(cat version) || VN="$DEF_VER"
-elif test -d .git -o -f .git &&
+elif test -d ${GIT_DIR:-.git} -o -f .git &&
VN=$(git describe --match "v[0-9]*" --abbrev=7 HEAD 2>/dev/null) &&
case "$VN" in
*$LF*) (exit 1) ;;
diff --git a/Makefile b/Makefile
index 0f931a2..0600eb4 100644
--- a/Makefile
+++ b/Makefile
@@ -69,6 +69,9 @@ all::
# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
# doesn't support GNU extensions like --check and --statistics
#
+# Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than
+# INT_MAX bytes at once (e.g. MacOS X).
+#
# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
# it specifies.
#
@@ -106,7 +109,7 @@ all::
# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
# FNM_CASEFOLD GNU extension.
#
-# Define USE_WILDMATCH if you want to use Git's wildmatch
+# Define NO_WILDMATCH if you do not want to use Git's wildmatch
# implementation as fnmatch
#
# Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd
@@ -137,6 +140,10 @@ all::
# specify your own (or DarwinPort's) include directories and
# library directories by defining CFLAGS and LDFLAGS appropriately.
#
+# Define NO_APPLE_COMMON_CRYPTO if you are building on Darwin/Mac OS X
+# and do not want to use Apple's CommonCrypto library. This allows you
+# to provide your own OpenSSL library, for example from MacPorts.
+#
# Define BLK_SHA1 environment variable to make use of the bundled
# optimized C SHA1 routine.
#
@@ -290,10 +297,6 @@ all::
#
# Define NO_REGEX if you have no or inferior regex support in your C library.
#
-# Define CYGWIN_V15_WIN32API if you are using Cygwin v1.7.x but are not
-# using the current w32api packages. The recommended approach, however,
-# is to update your installation if compilation errors occur.
-#
# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
# user.
#
@@ -460,6 +463,7 @@ SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-rebase.sh
+SCRIPT_SH += git-remote-testgit.sh
SCRIPT_SH += git-repack.sh
SCRIPT_SH += git-request-pull.sh
SCRIPT_SH += git-stash.sh
@@ -487,11 +491,18 @@ SCRIPT_PERL += git-svn.perl
SCRIPT_PYTHON += git-remote-testpy.py
SCRIPT_PYTHON += git-p4.py
+NO_INSTALL += git-remote-testgit
+NO_INSTALL += git-remote-testpy
+
# Generated files for scripts
SCRIPT_SH_GEN = $(patsubst %.sh,%,$(SCRIPT_SH))
SCRIPT_PERL_GEN = $(patsubst %.perl,%,$(SCRIPT_PERL))
SCRIPT_PYTHON_GEN = $(patsubst %.py,%,$(SCRIPT_PYTHON))
+SCRIPT_SH_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_SH_GEN))
+SCRIPT_PERL_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_PERL_GEN))
+SCRIPT_PYTHON_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_PYTHON_GEN))
+
# Individual rules to allow e.g.
# "make -C ../.. SCRIPT_PERL=contrib/foo/bar.perl build-perl-script"
# from subdirectories like contrib/*/
@@ -501,12 +512,12 @@ build-sh-script: $(SCRIPT_SH_GEN)
build-python-script: $(SCRIPT_PYTHON_GEN)
.PHONY: install-perl-script install-sh-script install-python-script
-install-sh-script: $(SCRIPT_SH_GEN)
- $(INSTALL) $(SCRIPT_SH_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
-install-perl-script: $(SCRIPT_PERL_GEN)
- $(INSTALL) $(SCRIPT_PERL_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
-install-python-script: $(SCRIPT_PYTHON_GEN)
- $(INSTALL) $(SCRIPT_PYTHON_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+install-sh-script: $(SCRIPT_SH_INS)
+ $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+install-perl-script: $(SCRIPT_PERL_INS)
+ $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+install-python-script: $(SCRIPT_PYTHON_INS)
+ $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
.PHONY: clean-perl-script clean-sh-script clean-python-script
clean-sh-script:
@@ -516,9 +527,9 @@ clean-perl-script:
clean-python-script:
$(RM) $(SCRIPT_PYTHON_GEN)
-SCRIPTS = $(SCRIPT_SH_GEN) \
- $(SCRIPT_PERL_GEN) \
- $(SCRIPT_PYTHON_GEN) \
+SCRIPTS = $(SCRIPT_SH_INS) \
+ $(SCRIPT_PERL_INS) \
+ $(SCRIPT_PYTHON_INS) \
git-instaweb
ETAGS_TARGET = TAGS
@@ -558,6 +569,8 @@ TEST_PROGRAMS_NEED_X += test-mergesort
TEST_PROGRAMS_NEED_X += test-mktemp
TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-prio-queue
+TEST_PROGRAMS_NEED_X += test-read-cache
TEST_PROGRAMS_NEED_X += test-regex
TEST_PROGRAMS_NEED_X += test-revision-walking
TEST_PROGRAMS_NEED_X += test-run-command
@@ -673,6 +686,8 @@ LIB_H += help.h
LIB_H += http.h
LIB_H += kwset.h
LIB_H += levenshtein.h
+LIB_H += line-log.h
+LIB_H += line-range.h
LIB_H += list-objects.h
LIB_H += ll-merge.h
LIB_H += log-tree.h
@@ -682,15 +697,16 @@ LIB_H += merge-recursive.h
LIB_H += mergesort.h
LIB_H += notes-cache.h
LIB_H += notes-merge.h
+LIB_H += notes-utils.h
LIB_H += notes.h
LIB_H += object.h
-LIB_H += pack-refs.h
LIB_H += pack-revindex.h
LIB_H += pack.h
LIB_H += parse-options.h
LIB_H += patch-ids.h
LIB_H += pathspec.h
LIB_H += pkt-line.h
+LIB_H += prio-queue.h
LIB_H += progress.h
LIB_H += prompt.h
LIB_H += quote.h
@@ -801,6 +817,8 @@ LIB_OBJS += hex.o
LIB_OBJS += ident.o
LIB_OBJS += kwset.o
LIB_OBJS += levenshtein.o
+LIB_OBJS += line-log.o
+LIB_OBJS += line-range.o
LIB_OBJS += list-objects.o
LIB_OBJS += ll-merge.o
LIB_OBJS += lockfile.o
@@ -815,9 +833,9 @@ LIB_OBJS += name-hash.o
LIB_OBJS += notes.o
LIB_OBJS += notes-cache.o
LIB_OBJS += notes-merge.o
+LIB_OBJS += notes-utils.o
LIB_OBJS += object.o
LIB_OBJS += pack-check.o
-LIB_OBJS += pack-refs.o
LIB_OBJS += pack-revindex.o
LIB_OBJS += pack-write.o
LIB_OBJS += pager.o
@@ -830,6 +848,7 @@ LIB_OBJS += pathspec.o
LIB_OBJS += pkt-line.o
LIB_OBJS += preload-index.o
LIB_OBJS += pretty.o
+LIB_OBJS += prio-queue.o
LIB_OBJS += progress.o
LIB_OBJS += prompt.o
LIB_OBJS += quote.o
@@ -1054,6 +1073,11 @@ ifeq ($(uname_S),Darwin)
BASIC_LDFLAGS += -L/opt/local/lib
endif
endif
+ ifndef NO_APPLE_COMMON_CRYPTO
+ APPLE_COMMON_CRYPTO = YesPlease
+ COMPAT_CFLAGS += -DAPPLE_COMMON_CRYPTO
+ endif
+ NO_REGEX = YesPlease
PTHREAD_LIBS =
endif
@@ -1261,7 +1285,7 @@ ifdef NO_FNMATCH_CASEFOLD
COMPAT_OBJS += compat/fnmatch/fnmatch.o
endif
endif
-ifdef USE_WILDMATCH
+ifndef NO_WILDMATCH
COMPAT_CFLAGS += -DUSE_WILDMATCH
endif
ifdef NO_SETENV
@@ -1388,10 +1412,16 @@ ifdef PPC_SHA1
LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
LIB_H += ppc/sha1.h
else
+ifdef APPLE_COMMON_CRYPTO
+ COMPAT_CFLAGS += -DCOMMON_DIGEST_FOR_OPENSSL
+ SHA1_HEADER = <CommonCrypto/CommonDigest.h>
+else
SHA1_HEADER = <openssl/sha.h>
EXTLIBS += $(LIB_4_CRYPTO)
endif
endif
+endif
+
ifdef NO_PERL_MAKEMAKER
export NO_PERL_MAKEMAKER
endif
@@ -1449,9 +1479,6 @@ ifdef NO_REGEX
COMPAT_CFLAGS += -Icompat/regex
COMPAT_OBJS += compat/regex/regex.o
endif
-ifdef CYGWIN_V15_WIN32API
- COMPAT_CFLAGS += -DCYGWIN_V15_WIN32API
-endif
ifdef USE_NED_ALLOCATOR
COMPAT_CFLAGS += -Icompat/nedmalloc
@@ -1466,6 +1493,11 @@ ifndef NO_MSGFMT_EXTENDED_OPTIONS
MSGFMT += --check --statistics
endif
+ifdef NEEDS_CLIPPED_WRITE
+ BASIC_CFLAGS += -DNEEDS_CLIPPED_WRITE
+ COMPAT_OBJS += compat/clipped-write.o
+endif
+
ifneq (,$(XDL_FAST_HASH))
BASIC_CFLAGS += -DXDL_FAST_HASH
endif
@@ -1503,6 +1535,7 @@ ifndef V
QUIET_MSGFMT = @echo ' ' MSGFMT $@;
QUIET_GCOV = @echo ' ' GCOV $@;
QUIET_SP = @echo ' ' SP $<;
+ QUIET_RC = @echo ' ' RC $@;
QUIET_SUBDIR0 = +@subdir=
QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
$(MAKE) $(PRINT_DIR) -C $$subdir
@@ -1645,7 +1678,7 @@ please_set_SHELL_PATH_to_a_more_modern_shell:
shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
strip: $(PROGRAMS) git$X
- $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
+ $(STRIP) $(STRIP_OPTS) $^
### Target-specific flags and dependencies
@@ -1705,9 +1738,9 @@ version.sp version.s version.o: EXTRA_CPPFLAGS = \
$(BUILT_INS): git$X
$(QUIET_BUILT_IN)$(RM) $@ && \
- ln git$X $@ 2>/dev/null || \
- ln -s git$X $@ 2>/dev/null || \
- cp git$X $@
+ ln $< $@ 2>/dev/null || \
+ ln -s $< $@ 2>/dev/null || \
+ cp $< $@
common-cmds.h: ./generate-cmdlist.sh command-list.txt
@@ -1748,6 +1781,11 @@ $(SCRIPT_LIB) : % : %.sh GIT-SCRIPT-DEFINES
$(QUIET_GEN)$(cmd_munge_script) && \
mv $@+ $@
+git.res: git.rc GIT-VERSION-FILE
+ $(QUIET_RC)$(RC) \
+ $(join -DMAJOR= -DMINOR= -DPATCH=, $(wordlist 1,3,$(subst -, ,$(subst ., ,$(GIT_VERSION))))) \
+ -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" $< -o $@
+
ifndef NO_PERL
$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
@@ -1772,7 +1810,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl GIT-VERSION-FILE
-e ' x' \
-e '}' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
- $@.perl >$@+ && \
+ $< >$@+ && \
chmod +x $@+ && \
mv $@+ $@
@@ -1796,8 +1834,8 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
endif # NO_PERL
ifndef NO_PYTHON
-$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
-$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
+$(SCRIPT_PYTHON_GEN): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
+$(SCRIPT_PYTHON_GEN): % : %.py
$(QUIET_GEN)$(RM) $@ $@+ && \
INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
--no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
@@ -1805,11 +1843,11 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
-e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \
-e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
- $@.py >$@+ && \
+ $< >$@+ && \
chmod +x $@+ && \
mv $@+ $@
else # NO_PYTHON
-$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
+$(SCRIPT_PYTHON_GEN): % : unimplemented.sh
$(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
@@ -2004,6 +2042,7 @@ endif
ifdef USE_NED_ALLOCATOR
compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
-DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR
+compat/nedmalloc/nedmalloc.sp: SPARSE_FLAGS += -Wno-non-pointer-null
endif
git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
@@ -2035,13 +2074,13 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
$(LIB_FILE): $(LIB_OBJS)
- $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
$(XDIFF_LIB): $(XDIFF_OBJS)
- $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
$(VCSSVN_LIB): $(VCSSVN_OBJS)
- $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
export DEFAULT_EDITOR DEFAULT_PAGER
@@ -2159,6 +2198,9 @@ GIT-BUILD-OPTIONS: FORCE
@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
@echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ifdef TEST_OUTPUT_DIRECTORY
+ @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@
+endif
ifdef GIT_TEST_OPTS
@echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
endif
@@ -2198,6 +2240,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
all:: $(TEST_PROGRAMS) $(test_bindir_programs)
+all:: $(NO_INSTALL)
bin-wrappers/%: wrap-for-bin.sh
@mkdir -p bin-wrappers
@@ -2262,9 +2305,6 @@ check: common-cmds.h
exit 1; \
fi
-remove-dashes:
- ./fixup-builtins $(BUILT_INS) $(PROGRAMS) $(SCRIPTS)
-
### Installation rules
ifneq ($(filter /%,$(firstword $(template_dir))),)
@@ -2443,11 +2483,11 @@ 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 \
+clean: profile-clean coverage-clean
+ $(RM) *.o *.res 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) $(TEST_PROGRAMS) $(NO_INSTALL)
$(RM) -r bin-wrappers $(dep_dirs)
$(RM) -r po/build/
$(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
@@ -2524,29 +2564,34 @@ check-builtins::
### Test suite coverage testing
#
-.PHONY: coverage coverage-clean coverage-build coverage-report
+.PHONY: coverage coverage-clean coverage-compile coverage-test coverage-report
+.PHONY: coverage-clean-results
coverage:
- $(MAKE) coverage-build
- $(MAKE) coverage-report
+ $(MAKE) coverage-test
+ $(MAKE) coverage-untested-functions
object_dirs := $(sort $(dir $(OBJECTS)))
-coverage-clean:
+coverage-clean-results:
$(RM) $(addsuffix *.gcov,$(object_dirs))
$(RM) $(addsuffix *.gcda,$(object_dirs))
- $(RM) $(addsuffix *.gcno,$(object_dirs))
$(RM) coverage-untested-functions
$(RM) -r cover_db/
$(RM) -r cover_db_html/
+coverage-clean: coverage-clean-results
+ $(RM) $(addsuffix *.gcno,$(object_dirs))
+
COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs
COVERAGE_LDFLAGS = $(CFLAGS) -O0 -lgcov
GCOVFLAGS = --preserve-paths --branch-probabilities --all-blocks
-coverage-build: coverage-clean
+coverage-compile:
$(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all
+
+coverage-test: coverage-clean-results coverage-compile
$(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \
- -j1 test
+ DEFAULT_TEST_TARGET=test -j1 test
coverage-report:
$(QUIET_GCOV)for dir in $(object_dirs); do \
diff --git a/RelNotes b/RelNotes
index 80b7e38..fce99fb 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.8.3.txt \ No newline at end of file
+Documentation/RelNotes/1.8.4.txt \ No newline at end of file
diff --git a/abspath.c b/abspath.c
index 40cdc46..64adbe2 100644
--- a/abspath.c
+++ b/abspath.c
@@ -216,7 +216,7 @@ const char *absolute_path(const char *path)
const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
{
static char path[PATH_MAX];
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
if (!pfx_len || is_absolute_path(arg))
return arg;
memcpy(path, pfx, pfx_len);
diff --git a/advice.c b/advice.c
index a8deee6..2a52098 100644
--- a/advice.c
+++ b/advice.c
@@ -14,6 +14,8 @@ int advice_resolve_conflict = 1;
int advice_implicit_identity = 1;
int advice_detached_head = 1;
int advice_set_upstream_failure = 1;
+int advice_object_name_warning = 1;
+int advice_rm_hints = 1;
static struct {
const char *name;
@@ -33,6 +35,8 @@ static struct {
{ "implicitidentity", &advice_implicit_identity },
{ "detachedhead", &advice_detached_head },
{ "setupstreamfailure", &advice_set_upstream_failure },
+ { "object_name_warning", &advice_object_name_warning },
+ { "rmhints", &advice_rm_hints },
/* make this an alias for backward compatibility */
{ "pushnonfastforward", &advice_push_update_rejected }
diff --git a/advice.h b/advice.h
index 94caa32..93a7d11 100644
--- a/advice.h
+++ b/advice.h
@@ -17,6 +17,8 @@ extern int advice_resolve_conflict;
extern int advice_implicit_identity;
extern int advice_detached_head;
extern int advice_set_upstream_failure;
+extern int advice_object_name_warning;
+extern int advice_rm_hints;
int git_default_advice_config(const char *var, const char *value);
void advise(const char *advice, ...);
diff --git a/archive-zip.c b/archive-zip.c
index b2c4fe0..4bde019 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -232,7 +232,6 @@ static int write_zip_entry(struct archiver_args *args,
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);
diff --git a/bisect.c b/bisect.c
index 374d9e2..71c1958 100644
--- a/bisect.c
+++ b/bisect.c
@@ -15,7 +15,7 @@
static struct sha1_array good_revs;
static struct sha1_array skipped_revs;
-static const unsigned char *current_bad_sha1;
+static unsigned char *current_bad_sha1;
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
@@ -404,7 +404,8 @@ static int register_ref(const char *refname, const unsigned char *sha1,
int flags, void *cb_data)
{
if (!strcmp(refname, "bad")) {
- current_bad_sha1 = sha1;
+ current_bad_sha1 = xmalloc(20);
+ hashcpy(current_bad_sha1, sha1);
} else if (!prefixcmp(refname, "good-")) {
sha1_array_append(&good_revs, sha1);
} else if (!prefixcmp(refname, "skip-")) {
diff --git a/branch.c b/branch.c
index 97c72bf..c5c6984 100644
--- a/branch.c
+++ b/branch.c
@@ -197,6 +197,21 @@ int validate_new_branchname(const char *name, struct strbuf *ref,
return 1;
}
+static int check_tracking_branch(struct remote *remote, void *cb_data)
+{
+ char *tracking_branch = cb_data;
+ struct refspec query;
+ memset(&query, 0, sizeof(struct refspec));
+ query.dst = tracking_branch;
+ return !(remote_find_tracking(remote, &query) ||
+ prefixcmp(query.src, "refs/heads/"));
+}
+
+static int validate_remote_tracking_branch(char *ref)
+{
+ return !for_each_remote(check_tracking_branch, ref);
+}
+
static const char upstream_not_branch[] =
N_("Cannot setup tracking information; starting point '%s' is not a branch.");
static const char upstream_missing[] =
@@ -259,7 +274,7 @@ void create_branch(const char *head,
case 1:
/* Unique completion -- good, only if it is a real branch */
if (prefixcmp(real_ref, "refs/heads/") &&
- prefixcmp(real_ref, "refs/remotes/")) {
+ validate_remote_tracking_branch(real_ref)) {
if (explicit_tracking)
die(_(upstream_not_branch), start_name);
else
diff --git a/builtin.h b/builtin.h
index faef559..1ed8edb 100644
--- a/builtin.h
+++ b/builtin.h
@@ -5,13 +5,15 @@
#include "strbuf.h"
#include "cache.h"
#include "commit.h"
-#include "notes.h"
#define DEFAULT_MERGE_LOG_LEN 20
extern const char git_usage_string[];
extern const char git_more_info_string[];
+#define PRUNE_PACKED_DRY_RUN 01
+#define PRUNE_PACKED_VERBOSE 02
+
extern void prune_packed_objects(int);
struct fmt_merge_msg_opts {
@@ -23,21 +25,6 @@ struct fmt_merge_msg_opts {
extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
struct fmt_merge_msg_opts *);
-struct notes_rewrite_cfg {
- struct notes_tree **trees;
- const char *cmd;
- int enabled;
- combine_notes_fn combine;
- struct string_list *refs;
- int refs_from_env;
- int mode_from_env;
-};
-
-struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
-int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
- const unsigned char *from_obj, const unsigned char *to_obj);
-void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
-
extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size);
extern int cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/builtin/add.c b/builtin/add.c
index f45d9d4..8266a9c 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -343,6 +343,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;
+ rev.diffopt.use_color = 0;
DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
out = open(file, O_CREAT | O_WRONLY, 0666);
if (out < 0)
diff --git a/builtin/apply.c b/builtin/apply.c
index 30eefc3..ff01ebf 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -722,7 +722,7 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
static char *find_name_traditional(const char *line, char *def, int p_value)
{
- size_t len = strlen(line);
+ size_t len;
size_t date_len;
if (*line == '"') {
@@ -906,7 +906,7 @@ static void parse_traditional_patch(const char *first, const char *second, struc
patch->old_name = name;
} else {
patch->old_name = name;
- patch->new_name = xstrdup(name);
+ patch->new_name = null_strdup(name);
}
}
if (!name)
@@ -3525,7 +3525,7 @@ static int check_patch(struct patch *patch)
ok_if_exists = 0;
if (new_name &&
- ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
+ ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) {
int err = check_to_create(new_name, ok_if_exists);
if (err && threeway) {
diff --git a/builtin/blame.c b/builtin/blame.c
index 57a487e..079dcd3 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -21,6 +21,7 @@
#include "parse-options.h"
#include "utf8.h"
#include "userdiff.h"
+#include "line-range.h"
static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
@@ -566,11 +567,16 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
dst->score = 0;
}
-static const char *nth_line(struct scoreboard *sb, int lno)
+static const char *nth_line(struct scoreboard *sb, long lno)
{
return sb->final_buf + sb->lineno[lno];
}
+static const char *nth_line_cb(void *data, long lno)
+{
+ return nth_line((struct scoreboard *)data, lno);
+}
+
/*
* It is known that lines between tlno to same came from parent, and e
* has an overlap with that range. it also is known that parent's
@@ -1932,83 +1938,6 @@ static const char *add_prefix(const char *prefix, const char *path)
}
/*
- * Parsing of (comma separated) one item in the -L option
- */
-static const char *parse_loc(const char *spec,
- struct scoreboard *sb, long lno,
- long begin, long *ret)
-{
- char *term;
- const char *line;
- long num;
- int reg_error;
- regex_t regexp;
- regmatch_t match[1];
-
- /* Allow "-L <something>,+20" to mean starting at <something>
- * for 20 lines, or "-L <something>,-5" for 5 lines ending at
- * <something>.
- */
- if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
- num = strtol(spec + 1, &term, 10);
- if (term != spec + 1) {
- if (spec[0] == '-')
- num = 0 - num;
- if (0 < num)
- *ret = begin + num - 2;
- else if (!num)
- *ret = begin;
- else
- *ret = begin + num;
- return term;
- }
- return spec;
- }
- num = strtol(spec, &term, 10);
- if (term != spec) {
- *ret = num;
- return term;
- }
- if (spec[0] != '/')
- return spec;
-
- /* it could be a regexp of form /.../ */
- for (term = (char *) spec + 1; *term && *term != '/'; term++) {
- if (*term == '\\')
- term++;
- }
- if (*term != '/')
- return spec;
-
- /* try [spec+1 .. term-1] as regexp */
- *term = 0;
- begin--; /* input is in human terms */
- line = nth_line(sb, begin);
-
- if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
- !(reg_error = regexec(&regexp, line, 1, match, 0))) {
- const char *cp = line + match[0].rm_so;
- const char *nline;
-
- while (begin++ < lno) {
- nline = nth_line(sb, begin);
- if (line <= cp && cp < nline)
- break;
- line = nline;
- }
- *ret = begin;
- regfree(&regexp);
- *term++ = '/';
- return term;
- }
- else {
- char errbuf[1024];
- regerror(reg_error, &regexp, errbuf, 1024);
- die("-L parameter '%s': %s", spec + 1, errbuf);
- }
-}
-
-/*
* Parsing of -L option
*/
static void prepare_blame_range(struct scoreboard *sb,
@@ -2016,15 +1945,7 @@ static void prepare_blame_range(struct scoreboard *sb,
long lno,
long *bottom, long *top)
{
- const char *term;
-
- term = parse_loc(bottomtop, sb, lno, 1, bottom);
- if (*term == ',') {
- term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
- if (*term)
- usage(blame_usage);
- }
- if (*term)
+ if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
usage(blame_usage);
}
@@ -2574,10 +2495,6 @@ parse_done:
bottom = top = 0;
if (bottomtop)
prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
- if (bottom && top && top < bottom) {
- long tmp;
- tmp = top; top = bottom; bottom = tmp;
- }
if (bottom < 1)
bottom = 1;
if (top < 1)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 045cee7..0e64b41 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -13,9 +13,6 @@
#include "userdiff.h"
#include "streaming.h"
-#define BATCH 1
-#define BATCH_CHECK 2
-
static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
{
unsigned char sha1[20];
@@ -117,54 +114,174 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
return 0;
}
-static int batch_one_object(const char *obj_name, int print_contents)
-{
+struct expand_data {
unsigned char sha1[20];
- enum object_type type = 0;
+ enum object_type type;
unsigned long size;
- void *contents = NULL;
+ unsigned long disk_size;
+ const char *rest;
+
+ /*
+ * If mark_query is true, we do not expand anything, but rather
+ * just mark the object_info with items we wish to query.
+ */
+ int mark_query;
+
+ /*
+ * After a mark_query run, this object_info is set up to be
+ * passed to sha1_object_info_extended. It will point to the data
+ * elements above, so you can retrieve the response from there.
+ */
+ struct object_info info;
+};
+
+static int is_atom(const char *atom, const char *s, int slen)
+{
+ int alen = strlen(atom);
+ return alen == slen && !memcmp(atom, s, alen);
+}
+
+static void expand_atom(struct strbuf *sb, const char *atom, int len,
+ void *vdata)
+{
+ struct expand_data *data = vdata;
+
+ if (is_atom("objectname", atom, len)) {
+ if (!data->mark_query)
+ strbuf_addstr(sb, sha1_to_hex(data->sha1));
+ } else if (is_atom("objecttype", atom, len)) {
+ if (!data->mark_query)
+ strbuf_addstr(sb, typename(data->type));
+ } else if (is_atom("objectsize", atom, len)) {
+ if (data->mark_query)
+ data->info.sizep = &data->size;
+ else
+ strbuf_addf(sb, "%lu", data->size);
+ } else if (is_atom("objectsize:disk", atom, len)) {
+ if (data->mark_query)
+ data->info.disk_sizep = &data->disk_size;
+ else
+ strbuf_addf(sb, "%lu", data->disk_size);
+ } else if (is_atom("rest", atom, len)) {
+ if (!data->mark_query && data->rest)
+ strbuf_addstr(sb, data->rest);
+ } else
+ die("unknown format element: %.*s", len, atom);
+}
+
+static size_t expand_format(struct strbuf *sb, const char *start, void *data)
+{
+ const char *end;
+
+ if (*start != '(')
+ return 0;
+ end = strchr(start + 1, ')');
+ if (!end)
+ die("format element '%s' does not end in ')'", start);
+
+ expand_atom(sb, start + 1, end - start - 1, data);
+
+ return end - start + 1;
+}
+
+static void print_object_or_die(int fd, const unsigned char *sha1,
+ enum object_type type, unsigned long size)
+{
+ if (type == OBJ_BLOB) {
+ if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0)
+ die("unable to stream %s to stdout", sha1_to_hex(sha1));
+ }
+ else {
+ enum object_type rtype;
+ unsigned long rsize;
+ void *contents;
+
+ contents = read_sha1_file(sha1, &rtype, &rsize);
+ if (!contents)
+ die("object %s disappeared", sha1_to_hex(sha1));
+ if (rtype != type)
+ die("object %s changed type!?", sha1_to_hex(sha1));
+ if (rsize != size)
+ die("object %s change size!?", sha1_to_hex(sha1));
+
+ write_or_die(fd, contents, size);
+ free(contents);
+ }
+}
+
+struct batch_options {
+ int enabled;
+ int print_contents;
+ const char *format;
+};
+
+static int batch_one_object(const char *obj_name, struct batch_options *opt,
+ struct expand_data *data)
+{
+ struct strbuf buf = STRBUF_INIT;
if (!obj_name)
return 1;
- if (get_sha1(obj_name, sha1)) {
+ if (get_sha1(obj_name, data->sha1)) {
printf("%s missing\n", obj_name);
fflush(stdout);
return 0;
}
- if (print_contents == BATCH)
- contents = read_sha1_file(sha1, &type, &size);
- else
- type = sha1_object_info(sha1, &size);
-
- if (type <= 0) {
+ data->type = sha1_object_info_extended(data->sha1, &data->info);
+ if (data->type <= 0) {
printf("%s missing\n", obj_name);
fflush(stdout);
- if (print_contents == BATCH)
- free(contents);
return 0;
}
- printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
- fflush(stdout);
+ strbuf_expand(&buf, opt->format, expand_format, data);
+ strbuf_addch(&buf, '\n');
+ write_or_die(1, buf.buf, buf.len);
+ strbuf_release(&buf);
- if (print_contents == BATCH) {
- write_or_die(1, contents, size);
- printf("\n");
- fflush(stdout);
- free(contents);
+ if (opt->print_contents) {
+ print_object_or_die(1, data->sha1, data->type, data->size);
+ write_or_die(1, "\n", 1);
}
-
return 0;
}
-static int batch_objects(int print_contents)
+static int batch_objects(struct batch_options *opt)
{
struct strbuf buf = STRBUF_INIT;
+ struct expand_data data;
+
+ if (!opt->format)
+ opt->format = "%(objectname) %(objecttype) %(objectsize)";
+
+ /*
+ * Expand once with our special mark_query flag, which will prime the
+ * object_info to be handed to sha1_object_info_extended for each
+ * object.
+ */
+ memset(&data, 0, sizeof(data));
+ data.mark_query = 1;
+ strbuf_expand(&buf, opt->format, expand_format, &data);
+ data.mark_query = 0;
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
- int error = batch_one_object(buf.buf, print_contents);
+ char *p;
+ int error;
+
+ /*
+ * Split at first whitespace, tying off the beginning of the
+ * string and saving the remainder (or NULL) in data.rest.
+ */
+ p = strpbrk(buf.buf, " \t");
+ if (p) {
+ while (*p && strchr(" \t", *p))
+ *p++ = '\0';
+ }
+ data.rest = p;
+
+ error = batch_one_object(buf.buf, opt, &data);
if (error)
return error;
}
@@ -186,10 +303,29 @@ static int git_cat_file_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
+static int batch_option_callback(const struct option *opt,
+ const char *arg,
+ int unset)
+{
+ struct batch_options *bo = opt->value;
+
+ if (unset) {
+ memset(bo, 0, sizeof(*bo));
+ return 0;
+ }
+
+ bo->enabled = 1;
+ bo->print_contents = !strcmp(opt->long_name, "batch");
+ bo->format = arg;
+
+ return 0;
+}
+
int cmd_cat_file(int argc, const char **argv, const char *prefix)
{
- int opt = 0, batch = 0;
+ int opt = 0;
const char *exp_type = NULL, *obj_name = NULL;
+ struct batch_options batch = {0};
const struct option options[] = {
OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
@@ -200,12 +336,12 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
OPT_SET_INT(0, "textconv", &opt,
N_("for blob objects, run textconv on object's content"), 'c'),
- OPT_SET_INT(0, "batch", &batch,
- N_("show info and content of objects fed from the standard input"),
- BATCH),
- OPT_SET_INT(0, "batch-check", &batch,
- N_("show info about objects fed from the standard input"),
- BATCH_CHECK),
+ { OPTION_CALLBACK, 0, "batch", &batch, "format",
+ N_("show info and content of objects fed from the standard input"),
+ PARSE_OPT_OPTARG, batch_option_callback },
+ { OPTION_CALLBACK, 0, "batch-check", &batch, "format",
+ N_("show info about objects fed from the standard input"),
+ PARSE_OPT_OPTARG, batch_option_callback },
OPT_END()
};
@@ -222,19 +358,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
else
usage_with_options(cat_file_usage, options);
}
- if (!opt && !batch) {
+ if (!opt && !batch.enabled) {
if (argc == 2) {
exp_type = argv[0];
obj_name = argv[1];
} else
usage_with_options(cat_file_usage, options);
}
- if (batch && (opt || argc)) {
+ if (batch.enabled && (opt || argc)) {
usage_with_options(cat_file_usage, options);
}
- if (batch)
- return batch_objects(batch);
+ if (batch.enabled)
+ return batch_objects(&batch);
return cat_one_file(opt, exp_type, obj_name);
}
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 854a88a..4a8fc70 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -5,7 +5,7 @@
#include "pathspec.h"
#include "parse-options.h"
-static int quiet, verbose, stdin_paths;
+static int quiet, verbose, stdin_paths, show_non_matching;
static const char * const check_ignore_usage[] = {
"git check-ignore [options] pathname...",
"git check-ignore [options] --stdin < <list-of-paths>",
@@ -22,21 +22,28 @@ static const struct option check_ignore_options[] = {
N_("read file names from stdin")),
OPT_BOOLEAN('z', NULL, &null_term_line,
N_("input paths are terminated by a null character")),
+ OPT_BOOLEAN('n', "non-matching", &show_non_matching,
+ N_("show non-matching input paths")),
OPT_END()
};
static void output_exclude(const char *path, struct exclude *exclude)
{
- char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : "";
- char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : "";
+ char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : "";
+ char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
if (!null_term_line) {
if (!verbose) {
write_name_quoted(path, stdout, '\n');
} else {
- quote_c_style(exclude->el->src, NULL, stdout, 0);
- printf(":%d:%s%s%s\t",
- exclude->srcpos,
- bang, exclude->pattern, slash);
+ if (exclude) {
+ quote_c_style(exclude->el->src, NULL, stdout, 0);
+ printf(":%d:%s%s%s\t",
+ exclude->srcpos,
+ bang, exclude->pattern, slash);
+ }
+ else {
+ printf("::\t");
+ }
quote_c_style(path, NULL, stdout, 0);
fputc('\n', stdout);
}
@@ -44,30 +51,26 @@ static void output_exclude(const char *path, struct exclude *exclude)
if (!verbose) {
printf("%s%c", path, '\0');
} else {
- printf("%s%c%d%c%s%s%s%c%s%c",
- exclude->el->src, '\0',
- exclude->srcpos, '\0',
- bang, exclude->pattern, slash, '\0',
- path, '\0');
+ if (exclude)
+ printf("%s%c%d%c%s%s%s%c%s%c",
+ exclude->el->src, '\0',
+ exclude->srcpos, '\0',
+ bang, exclude->pattern, slash, '\0',
+ path, '\0');
+ else
+ printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0');
}
}
}
-static int check_ignore(const char *prefix, const char **pathspec)
+static int check_ignore(struct dir_struct *dir,
+ const char *prefix, const char **pathspec)
{
- struct dir_struct dir;
const char *path, *full_path;
char *seen;
int num_ignored = 0, dtype = DT_UNKNOWN, i;
struct exclude *exclude;
- /* read_cache() is only necessary so we can watch out for submodules. */
- if (read_cache() < 0)
- die(_("index file corrupt"));
-
- memset(&dir, 0, sizeof(dir));
- setup_standard_excludes(&dir);
-
if (!pathspec || !*pathspec) {
if (!quiet)
fprintf(stderr, "no pathspec given.\n");
@@ -86,28 +89,26 @@ static int check_ignore(const char *prefix, const char **pathspec)
? strlen(prefix) : 0, path);
full_path = check_path_for_gitlink(full_path);
die_if_path_beyond_symlink(full_path, prefix);
+ exclude = NULL;
if (!seen[i]) {
- exclude = last_exclude_matching(&dir, full_path, &dtype);
- if (exclude) {
- if (!quiet)
- output_exclude(path, exclude);
- num_ignored++;
- }
+ exclude = last_exclude_matching(dir, full_path, &dtype);
}
+ if (!quiet && (exclude || show_non_matching))
+ output_exclude(path, exclude);
+ if (exclude)
+ num_ignored++;
}
free(seen);
- clear_directory(&dir);
return num_ignored;
}
-static int check_ignore_stdin_paths(const char *prefix)
+static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
{
struct strbuf buf, nbuf;
- char **pathspec = NULL;
- size_t nr = 0, alloc = 0;
+ char *pathspec[2] = { NULL, NULL };
int line_termination = null_term_line ? 0 : '\n';
- int num_ignored;
+ int num_ignored = 0;
strbuf_init(&buf, 0);
strbuf_init(&nbuf, 0);
@@ -118,23 +119,19 @@ static int check_ignore_stdin_paths(const char *prefix)
die("line is badly quoted");
strbuf_swap(&buf, &nbuf);
}
- ALLOC_GROW(pathspec, nr + 1, alloc);
- pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf));
- strcpy(pathspec[nr++], buf.buf);
+ pathspec[0] = buf.buf;
+ num_ignored += check_ignore(dir, prefix, (const char **)pathspec);
+ maybe_flush_or_die(stdout, "check-ignore to stdout");
}
- ALLOC_GROW(pathspec, nr + 1, alloc);
- pathspec[nr] = NULL;
- num_ignored = check_ignore(prefix, (const char **)pathspec);
- maybe_flush_or_die(stdout, "attribute to stdout");
strbuf_release(&buf);
strbuf_release(&nbuf);
- free(pathspec);
return num_ignored;
}
int cmd_check_ignore(int argc, const char **argv, const char *prefix)
{
int num_ignored;
+ struct dir_struct dir;
git_config(git_default_config, NULL);
@@ -156,13 +153,24 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
if (verbose)
die(_("cannot have both --quiet and --verbose"));
}
+ if (show_non_matching && !verbose)
+ die(_("--non-matching is only valid with --verbose"));
+
+ /* read_cache() is only necessary so we can watch out for submodules. */
+ if (read_cache() < 0)
+ die(_("index file corrupt"));
+
+ memset(&dir, 0, sizeof(dir));
+ setup_standard_excludes(&dir);
if (stdin_paths) {
- num_ignored = check_ignore_stdin_paths(prefix);
+ num_ignored = check_ignore_stdin_paths(&dir, prefix);
} else {
- num_ignored = check_ignore(prefix, argv);
+ num_ignored = check_ignore(&dir, prefix, argv);
maybe_flush_or_die(stdout, "ignore to stdout");
}
+ clear_directory(&dir);
+
return !num_ignored;
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 81b4419..7fe0bff 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -587,7 +587,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
struct branch_info *new)
{
struct strbuf msg = STRBUF_INIT;
- const char *old_desc;
+ const char *old_desc, *reflog_msg;
if (opts->new_branch) {
if (opts->new_orphan_branch) {
if (opts->new_branch_log && !log_all_ref_updates) {
@@ -620,8 +620,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
old_desc = old->name;
if (!old_desc && old->commit)
old_desc = sha1_to_hex(old->commit->object.sha1);
- strbuf_addf(&msg, "checkout: moving from %s to %s",
- old_desc ? old_desc : "(invalid)", new->name);
+
+ reflog_msg = getenv("GIT_REFLOG_ACTION");
+ if (!reflog_msg)
+ strbuf_addf(&msg, "checkout: moving from %s to %s",
+ old_desc ? old_desc : "(invalid)", new->name);
+ else
+ strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg));
if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
/* Nothing to do. */
@@ -825,38 +830,43 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
}
struct tracking_name_data {
- const char *name;
- char *remote;
+ /* const */ char *src_ref;
+ char *dst_ref;
+ unsigned char *dst_sha1;
int unique;
};
-static int check_tracking_name(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
+static int check_tracking_name(struct remote *remote, void *cb_data)
{
struct tracking_name_data *cb = cb_data;
- const char *slash;
-
- if (prefixcmp(refname, "refs/remotes/"))
+ struct refspec query;
+ memset(&query, 0, sizeof(struct refspec));
+ query.src = cb->src_ref;
+ if (remote_find_tracking(remote, &query) ||
+ get_sha1(query.dst, cb->dst_sha1)) {
+ free(query.dst);
return 0;
- slash = strchr(refname + 13, '/');
- if (!slash || strcmp(slash + 1, cb->name))
- return 0;
- if (cb->remote) {
+ }
+ if (cb->dst_ref) {
+ free(query.dst);
cb->unique = 0;
return 0;
}
- cb->remote = xstrdup(refname);
+ cb->dst_ref = query.dst;
return 0;
}
-static const char *unique_tracking_name(const char *name)
+static const char *unique_tracking_name(const char *name, unsigned char *sha1)
{
- struct tracking_name_data cb_data = { NULL, NULL, 1 };
- cb_data.name = name;
- for_each_ref(check_tracking_name, &cb_data);
+ struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
+ char src_ref[PATH_MAX];
+ snprintf(src_ref, PATH_MAX, "refs/heads/%s", name);
+ cb_data.src_ref = src_ref;
+ cb_data.dst_sha1 = sha1;
+ for_each_remote(check_tracking_name, &cb_data);
if (cb_data.unique)
- return cb_data.remote;
- free(cb_data.remote);
+ return cb_data.dst_ref;
+ free(cb_data.dst_ref);
return NULL;
}
@@ -919,8 +929,8 @@ static int parse_branchname_arg(int argc, const char **argv,
if (dwim_new_local_branch_ok &&
!check_filename(NULL, arg) &&
argc == 1) {
- const char *remote = unique_tracking_name(arg);
- if (!remote || get_sha1(remote, rev))
+ const char *remote = unique_tracking_name(arg, rev);
+ if (!remote)
return argcount;
*new_branch = arg;
arg = remote;
diff --git a/builtin/clone.c b/builtin/clone.c
index 035ab64..430307b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -18,7 +18,6 @@
#include "transport.h"
#include "strbuf.h"
#include "dir.h"
-#include "pack-refs.h"
#include "sigchain.h"
#include "branch.h"
#include "remote.h"
@@ -494,13 +493,16 @@ static void write_remote_refs(const struct ref *local_refs)
{
const struct ref *r;
+ lock_packed_refs(LOCK_DIE_ON_ERROR);
+
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);
+ if (commit_packed_refs())
+ die_errno("unable to overwrite old ref-pack file");
}
static void write_followtags(const struct ref *refs, const char *msg)
@@ -542,12 +544,21 @@ 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)
+ const char *msg,
+ struct transport *transport,
+ int check_connectivity)
{
const struct ref *rm = mapped_refs;
- if (check_everything_connected(iterate_ref_map, 0, &rm))
- die(_("remote did not send all necessary objects"));
+ if (check_connectivity) {
+ if (0 <= option_verbosity)
+ printf(_("Checking connectivity... "));
+ if (check_everything_connected_with_transport(iterate_ref_map,
+ 0, &rm, transport))
+ die(_("remote did not send all necessary objects"));
+ if (0 <= option_verbosity)
+ printf(_("done\n"));
+ }
if (refs) {
write_remote_refs(mapped_refs);
@@ -693,7 +704,7 @@ static void write_refspec_config(const char* src_ref_prefix,
/*
* otherwise, the next "git fetch" will
* simply fetch from HEAD without updating
- * any remote tracking branch, which is what
+ * any remote-tracking branch, which is what
* we want.
*/
} else {
@@ -783,6 +794,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
is_local = option_local != 0 && path && !is_bundle;
if (is_local && option_depth)
warning(_("--depth is ignored in local clones; use file:// instead."));
+ if (option_local > 0 && !is_local)
+ warning(_("--local is ignored"));
if (argc == 2)
dir = xstrdup(argv[1]);
@@ -889,6 +902,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_upload_pack)
transport_set_option(transport, TRANS_OPT_UPLOADPACK,
option_upload_pack);
+
+ if (transport->smart_options && !option_depth)
+ transport->smart_options->check_self_contained_and_connected = 1;
}
refs = transport_get_remote_refs(transport);
@@ -950,7 +966,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport_fetch_refs(transport, mapped_refs);
update_remote_refs(refs, mapped_refs, remote_head_points_at,
- branch_top.buf, reflog_msg.buf);
+ branch_top.buf, reflog_msg.buf, transport, !is_local);
update_head(our_head_points_at, remote_head, reflog_msg.buf);
diff --git a/builtin/commit.c b/builtin/commit.c
index d2f30d9..790e5ab 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -29,6 +29,7 @@
#include "gpg-interface.h"
#include "column.h"
#include "sequencer.h"
+#include "notes-utils.h"
static const char * const builtin_commit_usage[] = {
N_("git commit [options] [--] <pathspec>..."),
@@ -107,23 +108,27 @@ static const char *cleanup_arg;
static enum commit_whence whence;
static int use_editor = 1, include_status = 1;
-static int show_ignored_in_status;
+static int show_ignored_in_status, have_option_m;
static const char *only_include_assumed;
static struct strbuf message = STRBUF_INIT;
-static enum {
+static enum status_format {
STATUS_FORMAT_NONE = 0,
STATUS_FORMAT_LONG,
STATUS_FORMAT_SHORT,
- STATUS_FORMAT_PORCELAIN
-} status_format;
+ STATUS_FORMAT_PORCELAIN,
+
+ STATUS_FORMAT_UNSPECIFIED
+} status_format = STATUS_FORMAT_UNSPECIFIED;
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
{
struct strbuf *buf = opt->value;
- if (unset)
+ if (unset) {
+ have_option_m = 0;
strbuf_setlen(buf, 0);
- else {
+ } else {
+ have_option_m = 1;
if (buf->len)
strbuf_addch(buf, '\n');
strbuf_addstr(buf, arg);
@@ -457,6 +462,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
case STATUS_FORMAT_PORCELAIN:
wt_porcelain_print(s);
break;
+ case STATUS_FORMAT_UNSPECIFIED:
+ die("BUG: finalize_deferred_config() should have been called");
+ break;
case STATUS_FORMAT_NONE:
case STATUS_FORMAT_LONG:
wt_status_print(s);
@@ -958,6 +966,42 @@ static const char *read_commit_message(const char *name)
return logmsg_reencode(commit, NULL, out_enc);
}
+/*
+ * Enumerate what needs to be propagated when --porcelain
+ * is not in effect here.
+ */
+static struct status_deferred_config {
+ enum status_format status_format;
+ int show_branch;
+} status_deferred_config = {
+ STATUS_FORMAT_UNSPECIFIED,
+ -1 /* unspecified */
+};
+
+static void finalize_deferred_config(struct wt_status *s)
+{
+ int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN &&
+ !s->null_termination);
+
+ if (s->null_termination) {
+ if (status_format == STATUS_FORMAT_NONE ||
+ status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = STATUS_FORMAT_PORCELAIN;
+ else if (status_format == STATUS_FORMAT_LONG)
+ die(_("--long and -z are incompatible"));
+ }
+
+ if (use_deferred_config && status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = status_deferred_config.status_format;
+ if (status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = STATUS_FORMAT_NONE;
+
+ if (use_deferred_config && s->show_branch < 0)
+ s->show_branch = status_deferred_config.show_branch;
+ if (s->show_branch < 0)
+ s->show_branch = 0;
+}
+
static int parse_and_validate_options(int argc, const char *argv[],
const struct option *options,
const char * const usage[],
@@ -968,6 +1012,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
int f = 0;
argc = parse_options(argc, argv, prefix, options, usage, 0);
+ finalize_deferred_config(s);
if (force_author && !strchr(force_author, '>'))
force_author = find_author_by_nickname(force_author);
@@ -975,7 +1020,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (force_author && renew_authorship)
die(_("Using both --reset-author and --author does not make sense"));
- if (logfile || message.len || use_message || fixup_message)
+ if (logfile || have_option_m || use_message || fixup_message)
use_editor = 0;
if (0 <= edit_flag)
use_editor = edit_flag;
@@ -1052,12 +1097,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (all && argc > 0)
die(_("Paths with -a does not make sense."));
- if (s->null_termination) {
- if (status_format == STATUS_FORMAT_NONE)
- status_format = STATUS_FORMAT_PORCELAIN;
- else if (status_format == STATUS_FORMAT_LONG)
- die(_("--long and -z are incompatible"));
- }
if (status_format != STATUS_FORMAT_NONE)
dry_run = 1;
@@ -1110,6 +1149,17 @@ static int git_status_config(const char *k, const char *v, void *cb)
s->submodule_summary = -1;
return 0;
}
+ if (!strcmp(k, "status.short")) {
+ if (git_config_bool(k, v))
+ status_deferred_config.status_format = STATUS_FORMAT_SHORT;
+ else
+ status_deferred_config.status_format = STATUS_FORMAT_NONE;
+ return 0;
+ }
+ if (!strcmp(k, "status.branch")) {
+ status_deferred_config.show_branch = git_config_bool(k, v);
+ return 0;
+ }
if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
s->use_color = git_config_colorbool(k, v);
return 0;
@@ -1152,8 +1202,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_SET_INT('s', "short", &status_format,
N_("show status concisely"), STATUS_FORMAT_SHORT),
- OPT_BOOLEAN('b', "branch", &s.show_branch,
- N_("show branch information")),
+ OPT_BOOL('b', "branch", &s.show_branch,
+ N_("show branch information")),
OPT_SET_INT(0, "porcelain", &status_format,
N_("machine-readable output"),
STATUS_FORMAT_PORCELAIN),
@@ -1186,13 +1236,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
builtin_status_options,
builtin_status_usage, 0);
finalize_colopts(&s.colopts, -1);
-
- if (s.null_termination) {
- if (status_format == STATUS_FORMAT_NONE)
- status_format = STATUS_FORMAT_PORCELAIN;
- else if (status_format == STATUS_FORMAT_LONG)
- die(_("--long and -z are incompatible"));
- }
+ finalize_deferred_config(&s);
handle_untracked_files_arg(&s);
if (show_ignored_in_status)
@@ -1221,6 +1265,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
case STATUS_FORMAT_PORCELAIN:
wt_porcelain_print(&s);
break;
+ case STATUS_FORMAT_UNSPECIFIED:
+ die("BUG: finalize_deferred_config() should have been called");
+ break;
case STATUS_FORMAT_NONE:
case STATUS_FORMAT_LONG:
s.verbose = verbose;
@@ -1389,7 +1436,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")),
OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
STATUS_FORMAT_SHORT),
- OPT_BOOLEAN(0, "branch", &s.show_branch, N_("show branch information")),
+ OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
OPT_SET_INT(0, "porcelain", &status_format,
N_("machine-readable output"), STATUS_FORMAT_PORCELAIN),
OPT_SET_INT(0, "long", &status_format,
@@ -1430,6 +1477,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
wt_status_prepare(&s);
gitmodules_config();
git_config(git_commit_config, &s);
+ status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
determine_whence(&s);
s.colopts = 0;
@@ -1591,7 +1639,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (cfg) {
/* 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);
+ finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
}
run_rewrite_hook(current_head->object.sha1, sha1);
}
diff --git a/builtin/config.c b/builtin/config.c
index 33c9bf9..7759671 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -329,6 +329,7 @@ static int get_colorbool(int print)
{
get_colorbool_found = -1;
get_diff_color_found = -1;
+ get_color_ui_found = -1;
git_config_with_options(git_get_colorbool_config, NULL,
given_config_file, respect_includes);
@@ -339,6 +340,10 @@ static int get_colorbool(int print)
get_colorbool_found = get_color_ui_found;
}
+ if (get_colorbool_found < 0)
+ /* default value if none found in config */
+ get_colorbool_found = GIT_COLOR_AUTO;
+
get_colorbool_found = want_color(get_colorbool_found);
if (print) {
@@ -379,8 +384,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
*/
die("$HOME not set");
- if (access_or_warn(user_config, R_OK) &&
- xdg_config && !access_or_warn(xdg_config, R_OK))
+ if (access_or_warn(user_config, R_OK, 0) &&
+ xdg_config && !access_or_warn(xdg_config, R_OK, 0))
given_config_file = xdg_config;
else
given_config_file = user_config;
diff --git a/builtin/describe.c b/builtin/describe.c
index 6636a68..4e675c3 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -21,6 +21,7 @@ static int debug; /* Display lots of verbose info */
static int all; /* Any valid ref can be used */
static int tags; /* Allow lightweight tags */
static int longformat;
+static int first_parent;
static int abbrev = -1; /* unspecified */
static int max_candidates = 10;
static struct hash_table names;
@@ -42,7 +43,7 @@ struct commit_name {
unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
unsigned name_checked:1;
unsigned char sha1[20];
- const char *path;
+ char *path;
};
static const char *prio_names[] = {
"head", "lightweight", "annotated",
@@ -126,12 +127,14 @@ static void add_to_known_names(const char *path,
} else {
e->next = NULL;
}
+ e->path = NULL;
}
e->tag = tag;
e->prio = prio;
e->name_checked = 0;
hashcpy(e->sha1, sha1);
- e->path = path;
+ free(e->path);
+ e->path = xstrdup(path);
}
}
@@ -336,6 +339,9 @@ static void describe(const char *arg, int last_one)
commit_list_insert_by_date(p, &list);
p->object.flags |= c->object.flags;
parents = parents->next;
+
+ if (first_parent)
+ break;
}
}
@@ -404,6 +410,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN(0, "all", &all, N_("use any ref")),
OPT_BOOLEAN(0, "tags", &tags, N_("use any tag, even unannotated")),
OPT_BOOLEAN(0, "long", &longformat, N_("always use long format")),
+ OPT_BOOLEAN(0, "first-parent", &first_parent, N_("only follow first parent")),
OPT__ABBREV(&abbrev),
OPT_SET_INT(0, "exact-match", &max_candidates,
N_("only output exact matches"), 0),
diff --git a/builtin/diff.c b/builtin/diff.c
index 8c2af6c..9fc273d 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -153,7 +153,8 @@ static int builtin_diff_index(struct rev_info *revs,
static int builtin_diff_tree(struct rev_info *revs,
int argc, const char **argv,
- struct object_array_entry *ent)
+ struct object_array_entry *ent0,
+ struct object_array_entry *ent1)
{
const unsigned char *(sha1[2]);
int swap = 0;
@@ -161,13 +162,14 @@ static int builtin_diff_tree(struct rev_info *revs,
if (argc > 1)
usage(builtin_diff_usage);
- /* We saw two trees, ent[0] and ent[1].
- * if ent[1] is uninteresting, they are swapped
+ /*
+ * We saw two trees, ent0 and ent1. If ent1 is uninteresting,
+ * swap them.
*/
- if (ent[1].item->flags & UNINTERESTING)
+ if (ent1->item->flags & UNINTERESTING)
swap = 1;
- sha1[swap] = ent[0].item->sha1;
- sha1[1-swap] = ent[1].item->sha1;
+ sha1[swap] = ent0->item->sha1;
+ sha1[1-swap] = ent1->item->sha1;
diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
log_tree_diff_flush(revs);
return 0;
@@ -251,8 +253,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
{
int i;
struct rev_info rev;
- struct object_array_entry ent[100];
- int ents = 0, blobs = 0, paths = 0;
+ struct object_array ent = OBJECT_ARRAY_INIT;
+ int blobs = 0, paths = 0;
const char *path = NULL;
struct blobinfo blob[2];
int nongit;
@@ -337,9 +339,9 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
for (i = 0; i < rev.pending.nr; i++) {
- struct object_array_entry *list = rev.pending.objects+i;
- struct object *obj = list->item;
- const char *name = list->name;
+ struct object_array_entry *entry = &rev.pending.objects[i];
+ struct object *obj = entry->item;
+ const char *name = entry->name;
int flags = (obj->flags & UNINTERESTING);
if (!obj->parsed)
obj = parse_object(obj->sha1);
@@ -348,27 +350,21 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
die(_("invalid object '%s' given."), name);
if (obj->type == OBJ_COMMIT)
obj = &((struct commit *)obj)->tree->object;
+
if (obj->type == OBJ_TREE) {
- if (ARRAY_SIZE(ent) <= ents)
- die(_("more than %d trees given: '%s'"),
- (int) ARRAY_SIZE(ent), name);
obj->flags |= flags;
- ent[ents].item = obj;
- ent[ents].name = name;
- ents++;
- continue;
- }
- if (obj->type == OBJ_BLOB) {
+ add_object_array(obj, name, &ent);
+ } else if (obj->type == OBJ_BLOB) {
if (2 <= blobs)
die(_("more than two blobs given: '%s'"), name);
hashcpy(blob[blobs].sha1, obj->sha1);
blob[blobs].name = name;
- blob[blobs].mode = list->mode;
+ blob[blobs].mode = entry->mode;
blobs++;
- continue;
+ } else {
+ die(_("unhandled object '%s' given."), name);
}
- die(_("unhandled object '%s' given."), name);
}
if (rev.prune_data.nr) {
if (!path)
@@ -379,7 +375,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
/*
* Now, do the arguments look reasonable?
*/
- if (!ents) {
+ if (!ent.nr) {
switch (blobs) {
case 0:
result = builtin_diff_files(&rev, argc, argv);
@@ -400,23 +396,26 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
else if (blobs)
usage(builtin_diff_usage);
- else if (ents == 1)
+ else if (ent.nr == 1)
result = builtin_diff_index(&rev, argc, argv);
- else if (ents == 2)
- result = builtin_diff_tree(&rev, argc, argv, ent);
- else if (ent[0].item->flags & UNINTERESTING) {
+ else if (ent.nr == 2)
+ result = builtin_diff_tree(&rev, argc, argv,
+ &ent.objects[0], &ent.objects[1]);
+ else if (ent.objects[0].item->flags & UNINTERESTING) {
/*
* diff A...B where there is at least one merge base
- * between A and B. We have ent[0] == merge-base,
- * ent[ents-2] == A, and ent[ents-1] == B. Show diff
- * between the base and B. Note that we pick one
- * merge base at random if there are more than one.
+ * between A and B. We have ent.objects[0] ==
+ * merge-base, ent.objects[ents-2] == A, and
+ * ent.objects[ents-1] == B. Show diff between the
+ * base and B. Note that we pick one merge base at
+ * random if there are more than one.
*/
- ent[1] = ent[ents-1];
- result = builtin_diff_tree(&rev, argc, argv, ent);
+ result = builtin_diff_tree(&rev, argc, argv,
+ &ent.objects[0],
+ &ent.objects[ent.nr-1]);
} else
result = builtin_diff_combined(&rev, argc, argv,
- ent, ents);
+ ent.objects, ent.nr);
result = diff_result_code(&rev.diffopt, result);
if (1 < rev.diffopt.skip_stat_unmatch)
refresh_index_quietly();
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index d60d675..d1d68e9 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -613,6 +613,8 @@ static void import_marks(char *input_file)
char *line_end, *mark_end;
unsigned char sha1[20];
struct object *object;
+ struct commit *commit;
+ enum object_type type;
line_end = strchr(line, '\n');
if (line[0] != ':' || !line_end)
@@ -621,23 +623,29 @@ static void import_marks(char *input_file)
mark = strtoumax(line + 1, &mark_end, 10);
if (!mark || mark_end == line + 1
- || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
+ || *mark_end != ' ' || get_sha1_hex(mark_end + 1, sha1))
die("corrupt mark line: %s", line);
if (last_idnum < mark)
last_idnum = mark;
- object = parse_object(sha1);
- if (!object)
+ type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("object not found: %s", sha1_to_hex(sha1));
+
+ if (type != OBJ_COMMIT)
+ /* only commits */
continue;
+ commit = lookup_commit(sha1);
+ if (!commit)
+ die("not a commit? can't happen: %s", sha1_to_hex(sha1));
+
+ object = &commit->object;
+
if (object->flags & SHOWN)
error("Object %s already has a mark", sha1_to_hex(sha1));
- if (object->type != OBJ_COMMIT)
- /* only commits */
- continue;
-
mark_object(object, mark);
object->flags |= SHOWN;
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 4b6b1df..d784b2e 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -119,7 +119,7 @@ static void add_merge_config(struct ref **head,
for (rm = *head; rm; rm = rm->next) {
if (branch_merge_matches(branch, i, rm->name)) {
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
break;
}
}
@@ -140,7 +140,7 @@ static void add_merge_config(struct ref **head,
refspec.src = branch->merge[i]->src;
get_fetch_map(remote_refs, &refspec, tail, 1);
for (rm = *old_tail; rm; rm = rm->next)
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
}
}
@@ -160,6 +160,8 @@ static struct ref *get_ref_map(struct transport *transport,
const struct ref *remote_refs = transport_get_remote_refs(transport);
if (ref_count || tags == TAGS_SET) {
+ struct ref **old_tail;
+
for (i = 0; i < ref_count; i++) {
get_fetch_map(remote_refs, &refs[i], &tail, 0);
if (refs[i].dst && refs[i].dst[0])
@@ -167,9 +169,23 @@ static struct ref *get_ref_map(struct transport *transport,
}
/* Merge everything on the command line, but not --tags */
for (rm = ref_map; rm; rm = rm->next)
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
if (tags == TAGS_SET)
get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+
+ /*
+ * For any refs that we happen to be fetching via command-line
+ * arguments, take the opportunity to update their configured
+ * counterparts. However, we do not want to mention these
+ * entries in FETCH_HEAD at all, as they would simply be
+ * duplicates of existing entries.
+ */
+ old_tail = tail;
+ for (i = 0; i < transport->remote->fetch_refspec_nr; i++)
+ get_fetch_map(ref_map, &transport->remote->fetch[i],
+ &tail, 1);
+ for (rm = *old_tail; rm; rm = rm->next)
+ rm->fetch_head_status = FETCH_HEAD_IGNORE;
} else {
/* Use the defaults */
struct remote *remote = transport->remote;
@@ -186,7 +202,7 @@ static struct ref *get_ref_map(struct transport *transport,
*autotags = 1;
if (!i && !has_merge && ref_map &&
!remote->fetch[0].pattern)
- ref_map->merge = 1;
+ ref_map->fetch_head_status = FETCH_HEAD_MERGE;
}
/*
* if the remote we're fetching from is the same
@@ -202,7 +218,7 @@ static struct ref *get_ref_map(struct transport *transport,
ref_map = get_remote_ref(remote_refs, "HEAD");
if (!ref_map)
die(_("Couldn't find remote ref HEAD"));
- ref_map->merge = 1;
+ ref_map->fetch_head_status = FETCH_HEAD_MERGE;
tail = &ref_map->next;
}
}
@@ -389,7 +405,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;
+ int want_status;
fp = fopen(filename, "a");
if (!fp)
@@ -407,19 +423,22 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
}
/*
- * 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.
+ * We do a pass for each fetch_head_status type in their enum order, so
+ * merged entries are written before not-for-merge. That lets readers
+ * use FETCH_HEAD as a refname to refer to the ref to be merged.
*/
- for (want_merge = 1; 0 <= want_merge; want_merge--) {
+ for (want_status = FETCH_HEAD_MERGE;
+ want_status <= FETCH_HEAD_IGNORE;
+ want_status++) {
for (rm = ref_map; rm; rm = rm->next) {
struct ref *ref = NULL;
+ const char *merge_status_marker = "";
commit = lookup_commit_reference_gently(rm->old_sha1, 1);
if (!commit)
- rm->merge = 0;
+ rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
- if (rm->merge != want_merge)
+ if (rm->fetch_head_status != want_status)
continue;
if (rm->peer_ref) {
@@ -465,16 +484,26 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
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);
+ switch (rm->fetch_head_status) {
+ case FETCH_HEAD_NOT_FOR_MERGE:
+ merge_status_marker = "not-for-merge";
+ /* fall-through */
+ case FETCH_HEAD_MERGE:
+ fprintf(fp, "%s\t%s\t%s",
+ sha1_to_hex(rm->old_sha1),
+ merge_status_marker,
+ note.buf);
+ for (i = 0; i < url_len; ++i)
+ if ('\n' == url[i])
+ fputs("\\n", fp);
+ else
+ fputc(url[i], fp);
+ fputc('\n', fp);
+ break;
+ default:
+ /* do not write anything to FETCH_HEAD */
+ break;
+ }
strbuf_reset(&note);
if (ref) {
@@ -571,7 +600,8 @@ static int add_existing(const char *refname, const unsigned char *sha1,
{
struct string_list *list = (struct string_list *)cbdata;
struct string_list_item *item = string_list_insert(list, refname);
- item->util = (void *)sha1;
+ item->util = xmalloc(20);
+ hashcpy(item->util, sha1);
return 0;
}
@@ -590,7 +620,7 @@ static void find_non_local_tags(struct transport *transport,
struct ref **head,
struct ref ***tail)
{
- struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+ struct string_list existing_refs = STRING_LIST_INIT_DUP;
struct string_list remote_refs = STRING_LIST_INIT_NODUP;
const struct ref *ref;
struct string_list_item *item = NULL;
@@ -636,7 +666,7 @@ static void find_non_local_tags(struct transport *transport,
item = string_list_insert(&remote_refs, ref->name);
item->util = (void *)ref->old_sha1;
}
- string_list_clear(&existing_refs, 0);
+ string_list_clear(&existing_refs, 1);
/*
* We may have a final lightweight tag that needs to be
@@ -693,11 +723,11 @@ static int truncate_fetch_head(void)
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
- struct string_list existing_refs = STRING_LIST_INIT_NODUP;
- struct string_list_item *peer_item = NULL;
+ struct string_list existing_refs = STRING_LIST_INIT_DUP;
struct ref *ref_map;
struct ref *rm;
int autotags = (transport->remote->fetch_tags == 1);
+ int retcode = 0;
for_each_ref(add_existing, &existing_refs);
@@ -713,9 +743,9 @@ static int do_fetch(struct transport *transport,
/* if not appending, truncate FETCH_HEAD */
if (!append && !dry_run) {
- int errcode = truncate_fetch_head();
- if (errcode)
- return errcode;
+ retcode = truncate_fetch_head();
+ if (retcode)
+ goto cleanup;
}
ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
@@ -724,8 +754,9 @@ static int do_fetch(struct transport *transport,
for (rm = ref_map; rm; rm = rm->next) {
if (rm->peer_ref) {
- peer_item = string_list_lookup(&existing_refs,
- rm->peer_ref->name);
+ struct string_list_item *peer_item =
+ string_list_lookup(&existing_refs,
+ rm->peer_ref->name);
if (peer_item)
hashcpy(rm->peer_ref->old_sha1,
peer_item->util);
@@ -736,7 +767,8 @@ static int do_fetch(struct transport *transport,
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
if (fetch_refs(transport, ref_map)) {
free_refs(ref_map);
- return 1;
+ retcode = 1;
+ goto cleanup;
}
if (prune) {
/* If --tags was specified, pretend the user gave us the canonical tags refspec */
@@ -779,7 +811,9 @@ static int do_fetch(struct transport *transport,
free_refs(ref_map);
}
- return 0;
+ cleanup:
+ string_list_clear(&existing_refs, 1);
+ return retcode;
}
static void set_option(const char *name, const char *value)
diff --git a/builtin/fsck.c b/builtin/fsck.c
index bb9a2cd..9909b6d 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -112,7 +112,7 @@ static int mark_object(struct object *obj, int type, void *data)
return 1;
}
- add_object_array(obj, (void *) parent, &pending);
+ add_object_array(obj, NULL, &pending);
return 0;
}
diff --git a/builtin/help.c b/builtin/help.c
index 062957f..f1e236b 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -1,6 +1,4 @@
/*
- * builtin-help.c
- *
* Builtin help command
*/
#include "cache.h"
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 79dfe47..9c1cfac 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -77,8 +77,10 @@ static int nr_threads;
static int from_stdin;
static int strict;
+static int do_fsck_object;
static int verbose;
static int show_stat;
+static int check_self_contained_and_connected;
static struct progress *progress;
@@ -187,13 +189,13 @@ static int mark_link(struct object *obj, int type, void *data)
/* The content of each linked object must have been checked
or it must be already present in the object database */
-static void check_object(struct object *obj)
+static unsigned check_object(struct object *obj)
{
if (!obj)
- return;
+ return 0;
if (!(obj->flags & FLAG_LINK))
- return;
+ return 0;
if (!(obj->flags & FLAG_CHECKED)) {
unsigned long size;
@@ -201,17 +203,20 @@ static void check_object(struct object *obj)
if (type != obj->type || type <= 0)
die(_("object of unexpected type"));
obj->flags |= FLAG_CHECKED;
- return;
+ return 1;
}
+
+ return 0;
}
-static void check_objects(void)
+static unsigned check_objects(void)
{
- unsigned i, max;
+ unsigned i, max, foreign_nr = 0;
max = get_max_object_index();
for (i = 0; i < max; i++)
- check_object(get_indexed_object(i));
+ foreign_nr += check_object(get_indexed_object(i));
+ return foreign_nr;
}
@@ -747,8 +752,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
int eaten;
void *buf = (void *) data;
- if (!buf)
- buf = new_data = get_data_from_pack(obj_entry);
+ assert(data && "data can only be NULL for large _blobs_");
/*
* we do not need to free the memory here, as the
@@ -757,7 +761,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
obj = parse_object_buffer(sha1, type, size, buf, &eaten);
if (!obj)
die(_("invalid %s"), typename(type));
- if (fsck_object(obj, 1, fsck_error_function))
+ if (do_fsck_object &&
+ fsck_object(obj, 1, fsck_error_function))
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));
@@ -1491,6 +1496,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
struct pack_idx_entry **idx_objects;
struct pack_idx_option opts;
unsigned char pack_sha1[20];
+ unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(index_pack_usage);
@@ -1512,6 +1518,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
fix_thin_pack = 1;
} else if (!strcmp(arg, "--strict")) {
strict = 1;
+ do_fsck_object = 1;
+ } else if (!strcmp(arg, "--check-self-contained-and-connected")) {
+ strict = 1;
+ check_self_contained_and_connected = 1;
} else if (!strcmp(arg, "--verify")) {
verify = 1;
} else if (!strcmp(arg, "--verify-stat")) {
@@ -1625,7 +1635,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
free(deltas);
if (strict)
- check_objects();
+ foreign_nr = check_objects();
if (show_stat)
show_pack_info(stat_only);
@@ -1651,5 +1661,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (index_name == NULL)
free((void *) curr_index);
+ /*
+ * Let the caller know this pack is not self contained
+ */
+ if (check_self_contained_and_connected && foreign_nr)
+ return 1;
+
return 0;
}
diff --git a/builtin/log.c b/builtin/log.c
index 6e56a50..2625f98 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -19,6 +19,7 @@
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
+#include "line-log.h"
#include "branch.h"
#include "streaming.h"
#include "version.h"
@@ -42,6 +43,12 @@ static const char * const builtin_log_usage[] = {
NULL
};
+struct line_opt_callback_data {
+ struct rev_info *rev;
+ const char *prefix;
+ struct string_list args;
+};
+
static int parse_decoration_style(const char *var, const char *value)
{
switch (git_config_maybe_bool(var, value)) {
@@ -76,6 +83,19 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
return 0;
}
+static int log_line_range_callback(const struct option *option, const char *arg, int unset)
+{
+ struct line_opt_callback_data *data = option->value;
+
+ if (!arg)
+ return -1;
+
+ data->rev->line_level_traverse = 1;
+ string_list_append(&data->args, arg);
+
+ return 0;
+}
+
static void cmd_log_init_defaults(struct rev_info *rev)
{
if (fmt_pretty)
@@ -98,6 +118,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
{
struct userformat_want w;
int quiet = 0, source = 0, mailmap = 0;
+ static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
const struct option builtin_log_options[] = {
OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")),
@@ -105,9 +126,15 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
{ OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback},
+ OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
+ "Process line range n,m in file, counting from 1",
+ log_line_range_callback),
OPT_END()
};
+ line_cb.rev = rev;
+ line_cb.prefix = prefix;
+
mailmap = use_mailmap_config;
argc = parse_options(argc, argv, prefix,
builtin_log_options, builtin_log_usage,
@@ -161,6 +188,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
rev->show_decorations = 1;
load_ref_decorations(decoration_style);
}
+
+ if (rev->line_level_traverse)
+ line_log_init(rev, line_cb.prefix, &line_cb.args);
+
setup_pager();
}
@@ -206,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
int i = revs->early_output;
int show_header = 1;
- sort_in_topological_order(&list, revs->lifo);
+ sort_in_topological_order(&list, revs->sort_order);
while (list && i) {
struct commit *commit = list->item;
switch (simplify_commit(revs, commit)) {
@@ -1081,6 +1112,21 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int from_callback(const struct option *opt, const char *arg, int unset)
+{
+ char **from = opt->value;
+
+ free(*from);
+
+ if (unset)
+ *from = NULL;
+ else if (arg)
+ *from = xstrdup(arg);
+ else
+ *from = xstrdup(git_committer_info(IDENT_NO_DATE));
+ return 0;
+}
+
int cmd_format_patch(int argc, const char **argv, const char *prefix)
{
struct commit *commit;
@@ -1103,6 +1149,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int quiet = 0;
int reroll_count = -1;
char *branch_name = NULL;
+ char *from = NULL;
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
N_("use [PATCH n/m] even with a single patch"),
@@ -1146,6 +1193,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
0, to_callback },
{ OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
0, cc_callback },
+ { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
+ N_("set From address to <ident> (or committer ident if absent)"),
+ PARSE_OPT_OPTARG, from_callback },
OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
N_("make first mail a reply to <message-id>")),
{ OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
@@ -1233,6 +1283,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.extra_headers = strbuf_detach(&buf, NULL);
+ if (from) {
+ if (split_ident_line(&rev.from_ident, from, strlen(from)))
+ die(_("invalid ident line: %s"), from);
+ }
+
if (start_number < 0)
start_number = 1;
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 2202072..3a410c3 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -165,11 +165,13 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
}
write_name(ce->name, ce_namelen(ce));
if (debug_mode) {
- printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
- printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
- printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
- printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
- printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
+ struct stat_data *sd = &ce->ce_stat_data;
+
+ printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
+ printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
+ printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
+ printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
+ printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
}
@@ -219,7 +221,7 @@ static void show_files(struct dir_struct *dir)
if (show_killed)
show_killed_files(dir);
}
- if (show_cached | show_stage) {
+ if (show_cached || show_stage) {
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if ((dir->flags & DIR_SHOW_IGNORED) &&
@@ -233,7 +235,7 @@ static void show_files(struct dir_struct *dir)
(ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
}
}
- if (show_deleted | show_modified) {
+ if (show_deleted || show_modified) {
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
struct stat st;
@@ -571,8 +573,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
die("ls-files --ignored needs some exclude pattern");
/* With no flags, we default to showing the cached files */
- if (!(show_stage | show_deleted | show_others | show_unmerged |
- show_killed | show_modified | show_resolve_undo))
+ if (!(show_stage || show_deleted || show_others || show_unmerged ||
+ show_killed || show_modified || show_resolve_undo))
show_cached = 1;
if (max_prefix)
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 1bc7991..0c4cd2f 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -107,7 +107,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
if (!octopus && !reduce && argc < 2)
usage_with_options(merge_base_usage, options);
- if (is_ancestor && (show_all | octopus | reduce))
+ if (is_ancestor && (show_all || octopus || reduce))
die("--is-ancestor cannot be used with other options");
if (is_ancestor)
return handle_is_ancestor(argc, argv);
diff --git a/builtin/merge.c b/builtin/merge.c
index 3e2daa3..9505e7e 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -47,8 +47,8 @@ static const char * const builtin_merge_usage[] = {
};
static int show_diffstat = 1, shortlog_len = -1, squash;
-static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only, option_edit = -1;
+static int option_commit = 1;
+static int option_edit = -1;
static int allow_trivial = 1, have_message, verify_signatures;
static int overwrite_ignore = 1;
static struct strbuf merge_msg = STRBUF_INIT;
@@ -76,6 +76,14 @@ static struct strategy all_strategy[] = {
static const char *pull_twohead, *pull_octopus;
+enum ff_type {
+ FF_NO,
+ FF_ALLOW,
+ FF_ONLY
+};
+
+static enum ff_type fast_forward = FF_ALLOW;
+
static int option_parse_message(const struct option *opt,
const char *arg, int unset)
{
@@ -178,6 +186,13 @@ static int option_parse_n(const struct option *opt,
return 0;
}
+static int option_parse_ff_only(const struct option *opt,
+ const char *arg, int unset)
+{
+ fast_forward = FF_ONLY;
+ return 0;
+}
+
static struct option builtin_merge_options[] = {
{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
N_("do not show a diffstat at the end of the merge"),
@@ -194,10 +209,10 @@ static struct option builtin_merge_options[] = {
N_("perform a commit if the merge succeeds (default)")),
OPT_BOOL('e', "edit", &option_edit,
N_("edit message before committing")),
- OPT_BOOLEAN(0, "ff", &allow_fast_forward,
- N_("allow fast-forward (default)")),
- OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
- N_("abort if fast-forward is not possible")),
+ OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW),
+ { OPTION_CALLBACK, 0, "ff-only", NULL, NULL,
+ N_("abort if fast-forward is not possible"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, option_parse_ff_only },
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
OPT_BOOL(0, "verify-signatures", &verify_signatures,
N_("Verify that the named commit has a valid GPG signature")),
@@ -581,10 +596,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
else if (!strcmp(k, "merge.ff")) {
int boolval = git_config_maybe_bool(k, v);
if (0 <= boolval) {
- allow_fast_forward = boolval;
+ fast_forward = boolval ? FF_ALLOW : FF_NO;
} else if (v && !strcmp(v, "only")) {
- allow_fast_forward = 1;
- fast_forward_only = 1;
+ fast_forward = FF_ONLY;
} /* do not barf on values from future versions of git */
return 0;
} else if (!strcmp(k, "merge.defaulttoupstream")) {
@@ -863,7 +877,7 @@ static int finish_automerge(struct commit *head,
free_commit_list(common);
parents = remoteheads;
- if (!head_subsumed || !allow_fast_forward)
+ if (!head_subsumed || fast_forward == FF_NO)
commit_list_insert(head, &parents);
strbuf_addch(&merge_msg, '\n');
prepare_to_commit(remoteheads);
@@ -948,7 +962,7 @@ static int evaluate_result(void)
}
/*
- * Pretend as if the user told us to merge with the tracking
+ * Pretend as if the user told us to merge with the remote-tracking
* branch we have for the upstream of the current branch
*/
static int setup_with_upstream(const char ***argv)
@@ -967,7 +981,7 @@ static int setup_with_upstream(const char ***argv)
args = xcalloc(branch->merge_nr + 1, sizeof(char *));
for (i = 0; i < branch->merge_nr; i++) {
if (!branch->merge[i]->dst)
- die(_("No remote tracking branch for %s from %s"),
+ die(_("No remote-tracking branch for %s from %s"),
branch->merge[i]->src, branch->remote_name);
args[i] = branch->merge[i]->dst;
}
@@ -1008,7 +1022,7 @@ static void write_merge_state(struct commit_list *remoteheads)
if (fd < 0)
die_errno(_("Could not open '%s' for writing"), filename);
strbuf_reset(&buf);
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
strbuf_addf(&buf, "no-ff");
if (write_in_full(fd, buf.buf, buf.len) != buf.len)
die_errno(_("Could not write to '%s'"), filename);
@@ -1054,7 +1068,8 @@ static struct commit_list *collect_parents(struct commit *head_commit,
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]);
+ help_unknown_ref(argv[i], "merge",
+ "not something we can merge");
remotes = &commit_list_insert(commit, remotes)->next;
}
*remotes = NULL;
@@ -1156,14 +1171,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
show_diffstat = 0;
if (squash) {
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
die(_("You cannot combine --squash with --no-ff."));
option_commit = 0;
}
- if (!allow_fast_forward && fast_forward_only)
- die(_("You cannot combine --no-ff with --ff-only."));
-
if (!abort_current_merge) {
if (!argc) {
if (default_to_upstream)
@@ -1205,7 +1217,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
"empty head"));
if (squash)
die(_("Squash commit into empty head not supported yet"));
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
die(_("Non-fast-forward commit does not make sense into "
"an empty head"));
remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
@@ -1293,11 +1305,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
sha1_to_hex(commit->object.sha1));
setenv(buf.buf, merge_remote_util(commit)->name, 1);
strbuf_reset(&buf);
- if (!fast_forward_only &&
+ if (fast_forward != FF_ONLY &&
merge_remote_util(commit) &&
merge_remote_util(commit)->obj &&
merge_remote_util(commit)->obj->type == OBJ_TAG)
- allow_fast_forward = 0;
+ fast_forward = FF_NO;
}
if (option_edit < 0)
@@ -1314,7 +1326,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
for (i = 0; i < use_strategies_nr; i++) {
if (use_strategies[i]->attr & NO_FAST_FORWARD)
- allow_fast_forward = 0;
+ fast_forward = FF_NO;
if (use_strategies[i]->attr & NO_TRIVIAL)
allow_trivial = 0;
}
@@ -1344,7 +1356,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
*/
finish_up_to_date("Already up-to-date.");
goto done;
- } else if (allow_fast_forward && !remoteheads->next &&
+ } else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
!hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
/* Again the most common case of merging one remote. */
@@ -1391,7 +1403,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* only one common.
*/
refresh_cache(REFRESH_QUIET);
- if (allow_trivial && !fast_forward_only) {
+ if (allow_trivial && fast_forward != FF_ONLY) {
/* See if it is really trivial. */
git_committer_info(IDENT_STRICT);
printf(_("Trying really trivial in-index merge...\n"));
@@ -1432,7 +1444,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
}
- if (fast_forward_only)
+ if (fast_forward == FF_ONLY)
die(_("Not possible to fast-forward, aborting."));
/* We are going to make a new commit. */
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 6238247..87d4854 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -82,6 +82,20 @@ copy_data:
}
}
+static int subpath_matches(const char *path, const char *filter)
+{
+ const char *subpath = path;
+
+ while (subpath) {
+ if (!fnmatch(filter, subpath, 0))
+ return subpath - path;
+ subpath = strchr(subpath, '/');
+ if (subpath)
+ subpath++;
+ }
+ return -1;
+}
+
struct name_ref_data {
int tags_only;
int name_only;
@@ -92,13 +106,23 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
{
struct object *o = parse_object(sha1);
struct name_ref_data *data = cb_data;
+ int can_abbreviate_output = data->tags_only && data->name_only;
int deref = 0;
if (data->tags_only && prefixcmp(path, "refs/tags/"))
return 0;
- if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
- return 0;
+ if (data->ref_filter) {
+ switch (subpath_matches(path, data->ref_filter)) {
+ case -1: /* did not match */
+ return 0;
+ case 0: /* matched fully */
+ break;
+ default: /* matched subpath */
+ can_abbreviate_output = 1;
+ break;
+ }
+ }
while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
@@ -110,12 +134,10 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
if (o && o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
- if (!prefixcmp(path, "refs/heads/"))
+ if (can_abbreviate_output)
+ path = shorten_unambiguous_ref(path, 0);
+ else if (!prefixcmp(path, "refs/heads/"))
path = path + 11;
- else if (data->tags_only
- && data->name_only
- && !prefixcmp(path, "refs/tags/"))
- path = path + 10;
else if (!prefixcmp(path, "refs/"))
path = path + 5;
diff --git a/builtin/notes.c b/builtin/notes.c
index 57748a6..e4100c4 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -4,7 +4,7 @@
* Copyright (c) 2010 Johan Herland <johan@herland.net>
*
* Based on git-notes.sh by Johannes Schindelin,
- * and builtin-tag.c by Kristian Høgsberg and Carlos Rica.
+ * and builtin/tag.c by Kristian Høgsberg and Carlos Rica.
*/
#include "cache.h"
@@ -18,9 +18,7 @@
#include "parse-options.h"
#include "string-list.h"
#include "notes-merge.h"
-
-static void commit_notes(struct notes_tree *t, const char *msg);
-static combine_notes_fn parse_combine_notes_fn(const char *v);
+#include "notes-utils.h"
static const char * const git_notes_usage[] = {
N_("git notes [--ref <notes_ref>] [list [<object>]]"),
@@ -287,139 +285,13 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
return parse_reuse_arg(opt, arg, unset);
}
-static void commit_notes(struct notes_tree *t, const char *msg)
-{
- struct strbuf buf = STRBUF_INIT;
- unsigned char commit_sha1[20];
-
- if (!t)
- t = &default_notes_tree;
- if (!t->initialized || !t->ref || !*t->ref)
- die(_("Cannot commit uninitialized/unreferenced notes tree"));
- if (!t->dirty)
- return; /* don't have to commit an unchanged tree */
-
- /* Prepare commit message and reflog message */
- 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, 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);
-}
-
-static combine_notes_fn parse_combine_notes_fn(const char *v)
-{
- if (!strcasecmp(v, "overwrite"))
- return combine_notes_overwrite;
- else if (!strcasecmp(v, "ignore"))
- return combine_notes_ignore;
- else if (!strcasecmp(v, "concatenate"))
- return combine_notes_concatenate;
- else if (!strcasecmp(v, "cat_sort_uniq"))
- return combine_notes_cat_sort_uniq;
- else
- return NULL;
-}
-
-static int notes_rewrite_config(const char *k, const char *v, void *cb)
-{
- struct notes_rewrite_cfg *c = cb;
- if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
- c->enabled = git_config_bool(k, v);
- return 0;
- } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
- if (!v)
- config_error_nonbool(k);
- c->combine = parse_combine_notes_fn(v);
- if (!c->combine) {
- error(_("Bad notes.rewriteMode value: '%s'"), v);
- return 1;
- }
- return 0;
- } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
- /* note that a refs/ prefix is implied in the
- * underlying for_each_glob_ref */
- if (!prefixcmp(v, "refs/notes/"))
- string_list_add_refs_by_glob(c->refs, v);
- else
- warning(_("Refusing to rewrite notes in %s"
- " (outside of refs/notes/)"), v);
- return 0;
- }
-
- return 0;
-}
-
-
-struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
-{
- struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
- const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
- const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
- c->cmd = cmd;
- c->enabled = 1;
- c->combine = combine_notes_concatenate;
- c->refs = xcalloc(1, sizeof(struct string_list));
- c->refs->strdup_strings = 1;
- c->refs_from_env = 0;
- c->mode_from_env = 0;
- if (rewrite_mode_env) {
- c->mode_from_env = 1;
- c->combine = parse_combine_notes_fn(rewrite_mode_env);
- if (!c->combine)
- /* TRANSLATORS: The first %s is the name of the
- environment variable, the second %s is its value */
- error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT,
- rewrite_mode_env);
- }
- if (rewrite_refs_env) {
- c->refs_from_env = 1;
- string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
- }
- git_config(notes_rewrite_config, c);
- if (!c->enabled || !c->refs->nr) {
- string_list_clear(c->refs, 0);
- free(c->refs);
- free(c);
- return NULL;
- }
- c->trees = load_notes_trees(c->refs);
- string_list_clear(c->refs, 0);
- free(c->refs);
- return c;
-}
-
-int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
- const unsigned char *from_obj, const unsigned char *to_obj)
-{
- int ret = 0;
- int i;
- for (i = 0; c->trees[i]; i++)
- ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
- return ret;
-}
-
-void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
-{
- int i;
- for (i = 0; c->trees[i]; i++) {
- commit_notes(c->trees[i], "Notes added by 'git notes copy'");
- free_notes(c->trees[i]);
- }
- free(c->trees);
- free(c);
-}
-
static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
{
struct strbuf buf = STRBUF_INIT;
struct notes_rewrite_cfg *c = NULL;
struct notes_tree *t = NULL;
int ret = 0;
+ const char *msg = "Notes added by 'git notes copy'";
if (rewrite_cmd) {
c = init_copy_notes_for_rewrite(rewrite_cmd);
@@ -461,10 +333,10 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
}
if (!rewrite_cmd) {
- commit_notes(t, "Notes added by 'git notes copy'");
+ commit_notes(t, msg);
free_notes(t);
} else {
- finish_copy_notes_for_rewrite(c);
+ finish_copy_notes_for_rewrite(c, msg);
}
return ret;
}
diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c
index b5a0f88..b20b1ec 100644
--- a/builtin/pack-refs.c
+++ b/builtin/pack-refs.c
@@ -1,6 +1,6 @@
#include "builtin.h"
#include "parse-options.h"
-#include "pack-refs.h"
+#include "refs.h"
static char const * const pack_refs_usage[] = {
N_("git pack-refs [options]"),
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
index 83382c1..fa6ce42 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -8,9 +8,6 @@ static const char * const prune_packed_usage[] = {
NULL
};
-#define DRY_RUN 01
-#define VERBOSE 02
-
static struct progress *progress;
static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
@@ -29,7 +26,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
if (!has_sha1_pack(sha1))
continue;
memcpy(pathname + len, de->d_name, 38);
- if (opts & DRY_RUN)
+ if (opts & PRUNE_PACKED_DRY_RUN)
printf("rm -f %s\n", pathname);
else
unlink_or_warn(pathname);
@@ -44,7 +41,7 @@ void prune_packed_objects(int opts)
const char *dir = get_object_directory();
int len = strlen(dir);
- if (opts == VERBOSE)
+ if (opts & PRUNE_PACKED_VERBOSE)
progress = start_progress_delay("Removing duplicate objects",
256, 95, 2);
@@ -71,10 +68,12 @@ void prune_packed_objects(int opts)
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
- int opts = isatty(2) ? VERBOSE : 0;
+ int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0;
const struct option prune_packed_options[] = {
- OPT_BIT('n', "dry-run", &opts, N_("dry run"), DRY_RUN),
- OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"), VERBOSE),
+ OPT_BIT('n', "dry-run", &opts, N_("dry run"),
+ PRUNE_PACKED_DRY_RUN),
+ OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"),
+ PRUNE_PACKED_VERBOSE),
OPT_END()
};
diff --git a/builtin/prune.c b/builtin/prune.c
index 85843d4..6366917 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -132,8 +132,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned objects")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
- OPT_DATE(0, "expire", &expire,
- N_("expire objects older than <time>")),
+ OPT_EXPIRY_DATE(0, "expire", &expire,
+ N_("expire objects older than <time>")),
OPT_END()
};
char *s;
@@ -165,7 +165,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
stop_progress(&progress);
prune_object_dir(get_object_directory());
- prune_packed_objects(show_only);
+ prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
remove_temporary_files(get_object_directory());
s = mkpathdup("%s/pack", get_object_directory());
remove_temporary_files(s);
diff --git a/builtin/push.c b/builtin/push.c
index 909c34d..6d36c24 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -113,17 +113,20 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote
remote->name, branch->name, advice_maybe);
}
-static void setup_push_upstream(struct remote *remote, int simple)
+static const char message_detached_head_die[] =
+ N_("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");
+
+static void setup_push_upstream(struct remote *remote, struct branch *branch,
+ int triangular)
{
struct strbuf refspec = STRBUF_INIT;
- struct branch *branch = branch_get(NULL);
+
if (!branch)
- die(_("You are not currently on a branch.\n"
- "To push the history leading to the current (detached HEAD)\n"
- "state now, use\n"
- "\n"
- " git push %s HEAD:<name-of-remote-branch>\n"),
- remote->name);
+ die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n"
@@ -135,18 +138,29 @@ static void setup_push_upstream(struct remote *remote, int simple)
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))
+ if (triangular)
die(_("You are pushing to remote '%s', which is not the upstream of\n"
"your current branch '%s', without telling me what to push\n"
"to update which remote branch."),
remote->name, branch->name);
- if (simple && strcmp(branch->refname, branch->merge[0]->src))
- die_push_simple(branch, remote);
+
+ if (push_default == PUSH_DEFAULT_SIMPLE) {
+ /* Additional safety */
+ if (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);
}
+static void setup_push_current(struct remote *remote, struct branch *branch)
+{
+ if (!branch)
+ die(_(message_detached_head_die), remote->name);
+ add_refspec(branch->name);
+}
+
static char warn_unspecified_push_default_msg[] =
N_("push.default is unset; its implicit value is changing in\n"
"Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
@@ -171,8 +185,17 @@ static void warn_unspecified_push_default_configuration(void)
warning("%s\n", _(warn_unspecified_push_default_msg));
}
+static int is_workflow_triangular(struct remote *remote)
+{
+ struct remote *fetch_remote = remote_get(NULL);
+ return (fetch_remote && fetch_remote != remote);
+}
+
static void setup_default_push_refspecs(struct remote *remote)
{
+ struct branch *branch = branch_get(NULL);
+ int triangular = is_workflow_triangular(remote);
+
switch (push_default) {
default:
case PUSH_DEFAULT_UNSPECIFIED:
@@ -184,15 +207,18 @@ static void setup_default_push_refspecs(struct remote *remote)
break;
case PUSH_DEFAULT_SIMPLE:
- setup_push_upstream(remote, 1);
+ if (triangular)
+ setup_push_current(remote, branch);
+ else
+ setup_push_upstream(remote, branch, triangular);
break;
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote, 0);
+ setup_push_upstream(remote, branch, triangular);
break;
case PUSH_DEFAULT_CURRENT:
- add_refspec("HEAD");
+ setup_push_current(remote, branch);
break;
case PUSH_DEFAULT_NOTHING:
@@ -204,8 +230,8 @@ 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"
+ "its remote counterpart. Integrate the remote changes (e.g.\n"
+ "'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_use_upstream[] =
@@ -216,15 +242,15 @@ static const char message_advice_use_upstream[] =
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"
+ "counterpart. Check out this branch and integrate 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 const char message_advice_ref_fetch_first[] =
N_("Updates were rejected because the remote contains work that you do\n"
"not have locally. This is usually caused by another repository pushing\n"
- "to the same ref. You may want to first merge the remote changes (e.g.,\n"
- "'git pull') before pushing again.\n"
+ "to the same ref. You may want to first integrate 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 const char message_advice_ref_already_exists[] =
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 042ac1b..0f5d7fe 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -66,7 +66,7 @@ static int exclude_per_directory_cb(const struct option *opt, const char *arg,
return 0;
}
-static void debug_stage(const char *label, struct cache_entry *ce,
+static void debug_stage(const char *label, const struct cache_entry *ce,
struct unpack_trees_options *o)
{
printf("%s ", label);
@@ -80,7 +80,8 @@ static void debug_stage(const char *label, struct cache_entry *ce,
sha1_to_hex(ce->sha1));
}
-static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+static int debug_merge(const struct cache_entry * const *stages,
+ struct unpack_trees_options *o)
{
int i;
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 72a0af7..54184b3 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -496,11 +496,9 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l
{
if (!value)
return config_error_nonbool(var);
- if (!strcmp(value, "never") || !strcmp(value, "false")) {
- *expire = 0;
- return 0;
- }
- *expire = approxidate(value);
+ if (parse_expiry_date(value, expire))
+ return error(_("%s' for '%s' is not a valid timestamp"),
+ value, var);
return 0;
}
@@ -614,11 +612,13 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
cb.dry_run = 1;
else if (!prefixcmp(arg, "--expire=")) {
- cb.expire_total = approxidate(arg + 9);
+ if (parse_expiry_date(arg + 9, &cb.expire_total))
+ die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_TOTAL;
}
else if (!prefixcmp(arg, "--expire-unreachable=")) {
- cb.expire_unreachable = approxidate(arg + 21);
+ if (parse_expiry_date(arg + 21, &cb.expire_unreachable))
+ die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_UNREACH;
}
else if (!strcmp(arg, "--stale-fix"))
diff --git a/builtin/replace.c b/builtin/replace.c
index 398ccd5..59d3115 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
*
- * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com>
+ * Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com>
* and Carlos Rica <jasampler@gmail.com> that was itself based on
* git-tag.sh and mktag.c by Linus Torvalds.
*/
diff --git a/builtin/reset.c b/builtin/reset.c
index 6032131..afa6e02 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -93,10 +93,12 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
static void print_new_head_line(struct commit *commit)
{
const char *hex, *body;
+ char *msg;
hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
printf(_("HEAD is now at %s"), hex);
- body = strstr(commit->buffer, "\n\n");
+ msg = logmsg_reencode(commit, NULL, get_log_output_encoding());
+ body = strstr(msg, "\n\n");
if (body) {
const char *eol;
size_t len;
@@ -107,6 +109,7 @@ static void print_new_head_line(struct commit *commit)
}
else
printf("\n");
+ logmsg_free(msg, commit);
}
static void update_index_from_diff(struct diff_queue_struct *q,
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 67701be..a5ec30d 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -111,6 +111,7 @@ static void show_commit(struct commit *commit, void *data)
ctx.date_mode = revs->date_mode;
ctx.date_mode_explicit = revs->date_mode_explicit;
ctx.fmt = revs->commit_format;
+ ctx.output_encoding = get_log_output_encoding();
pretty_print_commit(&ctx, commit, &buf);
if (revs->graph) {
if (buf.len) {
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index f267a1d..de894c7 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -212,11 +212,17 @@ static void show_datestring(const char *flag, const char *datestr)
show(buffer);
}
-static int show_file(const char *arg)
+static int show_file(const char *arg, int output_prefix)
{
show_default();
if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
- show(arg);
+ if (output_prefix) {
+ const char *prefix = startup_info->prefix;
+ show(prefix_filename(prefix,
+ prefix ? strlen(prefix) : 0,
+ arg));
+ } else
+ show(arg);
return 1;
}
return 0;
@@ -470,6 +476,7 @@ N_("git rev-parse --parseopt [options] -- [<args>...]\n"
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
{
int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
+ int output_prefix = 0;
unsigned char sha1[20];
const char *name = NULL;
@@ -503,7 +510,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
const char *arg = argv[i];
if (as_is) {
- if (show_file(arg) && as_is < 2)
+ if (show_file(arg, output_prefix) && as_is < 2)
verify_filename(prefix, arg, 0);
continue;
}
@@ -527,7 +534,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
as_is = 2;
/* Pass on the "--" if we show anything but files.. */
if (filter & (DO_FLAGS | DO_REVS))
- show_file(arg);
+ show_file(arg, 0);
continue;
}
if (!strcmp(arg, "--default")) {
@@ -535,6 +542,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
i++;
continue;
}
+ if (!strcmp(arg, "--prefix")) {
+ prefix = argv[i+1];
+ startup_info->prefix = prefix;
+ output_prefix = 1;
+ i++;
+ continue;
+ }
if (!strcmp(arg, "--revs-only")) {
filter &= ~DO_NOREV;
continue;
@@ -754,7 +768,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (verify)
die_no_single_rev(quiet);
as_is = 1;
- if (!show_file(arg))
+ if (!show_file(arg, output_prefix))
continue;
verify_filename(prefix, arg, 1);
}
diff --git a/builtin/rm.c b/builtin/rm.c
index 7b91d52..06025a2 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -9,6 +9,7 @@
#include "cache-tree.h"
#include "tree-walk.h"
#include "parse-options.h"
+#include "string-list.h"
#include "submodule.h"
static const char * const builtin_rm_usage[] = {
@@ -36,10 +37,32 @@ static int get_ours_cache_pos(const char *path, int pos)
return -1;
}
+static void print_error_files(struct string_list *files_list,
+ const char *main_msg,
+ const char *hints_msg,
+ int *errs)
+{
+ if (files_list->nr) {
+ int i;
+ struct strbuf err_msg = STRBUF_INIT;
+
+ strbuf_addstr(&err_msg, main_msg);
+ for (i = 0; i < files_list->nr; i++)
+ strbuf_addf(&err_msg,
+ "\n %s",
+ files_list->items[i].string);
+ if (advice_rm_hints)
+ strbuf_addstr(&err_msg, hints_msg);
+ *errs = error("%s", err_msg.buf);
+ strbuf_release(&err_msg);
+ }
+}
+
static int check_submodules_use_gitfiles(void)
{
int i;
int errs = 0;
+ struct string_list files = STRING_LIST_INIT_NODUP;
for (i = 0; i < list.nr; i++) {
const char *name = list.entry[i].name;
@@ -61,11 +84,18 @@ static int check_submodules_use_gitfiles(void)
continue;
if (!submodule_uses_gitfile(name))
- errs = error(_("submodule '%s' (or one of its nested "
- "submodules) uses a .git directory\n"
- "(use 'rm -rf' if you really want to remove "
- "it including all of its history)"), name);
+ string_list_append(&files, name);
}
+ print_error_files(&files,
+ Q_("the following submodule (or one of its nested "
+ "submodules)\n uses a .git directory:",
+ "the following submodules (or one of its nested "
+ "submodules)\n use a .git directory:",
+ files.nr),
+ _("\n(use 'rm -rf' if you really want to remove "
+ "it including all of its history)"),
+ &errs);
+ string_list_clear(&files, 0);
return errs;
}
@@ -81,6 +111,10 @@ static int check_local_mod(unsigned char *head, int index_only)
*/
int i, no_head;
int errs = 0;
+ struct string_list files_staged = STRING_LIST_INIT_NODUP;
+ struct string_list files_cached = STRING_LIST_INIT_NODUP;
+ struct string_list files_submodule = STRING_LIST_INIT_NODUP;
+ struct string_list files_local = STRING_LIST_INIT_NODUP;
no_head = is_null_sha1(head);
for (i = 0; i < list.nr; i++) {
@@ -171,29 +205,58 @@ static int check_local_mod(unsigned char *head, int index_only)
*/
if (local_changes && staged_changes) {
if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
- errs = error(_("'%s' has staged content different "
- "from both the file and the HEAD\n"
- "(use -f to force removal)"), name);
+ string_list_append(&files_staged, name);
}
else if (!index_only) {
if (staged_changes)
- errs = error(_("'%s' has changes staged in the index\n"
- "(use --cached to keep the file, "
- "or -f to force removal)"), name);
+ string_list_append(&files_cached, name);
if (local_changes) {
if (S_ISGITLINK(ce->ce_mode) &&
- !submodule_uses_gitfile(name)) {
- errs = error(_("submodule '%s' (or one of its nested "
- "submodules) uses a .git directory\n"
- "(use 'rm -rf' if you really want to remove "
- "it including all of its history)"), name);
- } else
- errs = error(_("'%s' has local modifications\n"
- "(use --cached to keep the file, "
- "or -f to force removal)"), name);
+ !submodule_uses_gitfile(name))
+ string_list_append(&files_submodule, name);
+ else
+ string_list_append(&files_local, name);
}
}
}
+ print_error_files(&files_staged,
+ Q_("the following file has staged content different "
+ "from both the\nfile and the HEAD:",
+ "the following files have staged content different"
+ " from both the\nfile and the HEAD:",
+ files_staged.nr),
+ _("\n(use -f to force removal)"),
+ &errs);
+ string_list_clear(&files_staged, 0);
+ print_error_files(&files_cached,
+ Q_("the following file has changes "
+ "staged in the index:",
+ "the following files have changes "
+ "staged in the index:", files_cached.nr),
+ _("\n(use --cached to keep the file,"
+ " or -f to force removal)"),
+ &errs);
+ string_list_clear(&files_cached, 0);
+ print_error_files(&files_submodule,
+ Q_("the following submodule (or one of its nested "
+ "submodule)\nuses a .git directory:",
+ "the following submodules (or one of its nested "
+ "submodule)\nuse a .git directory:",
+ files_submodule.nr),
+ _("\n(use 'rm -rf' if you really "
+ "want to remove it including all "
+ "of its history)"),
+ &errs);
+ string_list_clear(&files_submodule, 0);
+ print_error_files(&files_local,
+ Q_("the following file has local modifications:",
+ "the following files have local modifications:",
+ files_local.nr),
+ _("\n(use --cached to keep the file,"
+ " or -f to force removal)"),
+ &errs);
+ string_list_clear(&files_local, 0);
+
return errs;
}
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 1fd6f8a..1434f8f 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -137,6 +137,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
ctx.subject = "";
ctx.after_subject = "";
ctx.date_mode = DATE_NORMAL;
+ ctx.output_encoding = get_log_output_encoding();
pretty_print_commit(&ctx, commit, &ufbuf);
buffer = ufbuf.buf;
} else if (*buffer) {
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 90fc6b1..99ec4af 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int num_rev, i, extra = 0;
int all_heads = 0, all_remotes = 0;
int all_mask, all_revs;
- int lifo = 1;
+ enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
char head[128];
const char *head_p;
int head_len;
@@ -665,15 +665,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
N_("show possible merge bases")),
OPT_BOOLEAN(0, "independent", &independent,
N_("show refs unreachable from any other ref")),
- OPT_BOOLEAN(0, "topo-order", &lifo,
- N_("show commits in topological order")),
+ OPT_SET_INT(0, "topo-order", &sort_order,
+ N_("show commits in topological order"),
+ REV_SORT_IN_GRAPH_ORDER),
OPT_BOOLEAN(0, "topics", &topics,
N_("show only commits not on the first branch")),
OPT_SET_INT(0, "sparse", &dense,
N_("show merges reachable from only one tip"), 0),
- OPT_SET_INT(0, "date-order", &lifo,
+ OPT_SET_INT(0, "date-order", &sort_order,
N_("show commits where no parent comes before its "
- "children"), 0),
+ "children"),
+ REV_SORT_BY_COMMIT_DATE),
{ OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
N_("show <n> most recent ref-log entries starting at "
"base"),
@@ -900,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
exit(0);
/* Sort topologically */
- sort_in_topological_order(&seen, lifo);
+ sort_in_topological_order(&seen, sort_order);
/* Give names to commits */
if (!sha1_name && !no_name)
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 8d9b76a..4a0310d 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -103,7 +103,7 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag
*/
static int exclude_existing(const char *match)
{
- static struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+ static struct string_list existing_refs = STRING_LIST_INIT_DUP;
char buf[1024];
int matchlen = match ? strlen(match) : 0;
diff --git a/bundle.c b/bundle.c
index 4b0e5cd..3d64311 100644
--- a/bundle.c
+++ b/bundle.c
@@ -281,7 +281,7 @@ int create_bundle(struct bundle_header *header, const char *path,
if (!get_sha1_hex(buf.buf + 1, sha1)) {
struct object *object = parse_object_or_die(sha1, buf.buf);
object->flags |= UNINTERESTING;
- add_pending_object(&revs, object, xstrdup(buf.buf));
+ add_pending_object(&revs, object, buf.buf);
}
} else if (!get_sha1_hex(buf.buf, sha1)) {
struct object *object = parse_object_or_die(sha1, buf.buf);
diff --git a/cache.h b/cache.h
index 94ca1ac..2d06169 100644
--- a/cache.h
+++ b/cache.h
@@ -119,15 +119,19 @@ struct cache_time {
unsigned int nsec;
};
+struct stat_data {
+ struct cache_time sd_ctime;
+ struct cache_time sd_mtime;
+ unsigned int sd_dev;
+ unsigned int sd_ino;
+ unsigned int sd_uid;
+ unsigned int sd_gid;
+ unsigned int sd_size;
+};
+
struct cache_entry {
- struct cache_time ce_ctime;
- struct cache_time ce_mtime;
- unsigned int ce_dev;
- unsigned int ce_ino;
+ struct stat_data ce_stat_data;
unsigned int ce_mode;
- unsigned int ce_uid;
- unsigned int ce_gid;
- unsigned int ce_size;
unsigned int ce_flags;
unsigned int ce_namelen;
unsigned char sha1[20];
@@ -190,7 +194,8 @@ struct cache_entry {
* another. But we never change the name, or the hash state!
*/
#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
-static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
+static inline void copy_cache_entry(struct cache_entry *dst,
+ const struct cache_entry *src)
{
unsigned int state = dst->ce_flags & CE_STATE_MASK;
@@ -222,7 +227,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)
return S_IFGITLINK;
return S_IFREG | ce_permissions(mode);
}
-static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
+static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
+ unsigned int mode)
{
extern int trust_executable_bit, has_symlinks;
if (!has_symlinks && S_ISREG(mode) &&
@@ -480,8 +486,8 @@ extern void *read_blob_data_from_index(struct index_state *, const char *, unsig
#define CE_MATCH_RACY_IS_DIRTY 02
/* do stat comparison even if CE_SKIP_WORKTREE is true */
#define CE_MATCH_IGNORE_SKIP_WORKTREE 04
-extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
-extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
+extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
+extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */
@@ -509,6 +515,21 @@ extern int limit_pathspec_to_literal(void);
#define HASH_FORMAT_CHECK 2
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
+
+/*
+ * Record to sd the data from st that we use to check whether a file
+ * might have changed.
+ */
+extern void fill_stat_data(struct stat_data *sd, struct stat *st);
+
+/*
+ * Return 0 if st is consistent with a file not having been changed
+ * since sd was filled. If there are differences, return a
+ * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
+ * INODE_CHANGED, and DATA_CHANGED.
+ */
+extern int match_stat_data(const struct stat_data *sd, struct stat *st);
+
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
#define REFRESH_REALLY 0x0001 /* ignore_valid */
@@ -772,9 +793,6 @@ 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);
@@ -910,6 +928,7 @@ 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);
+int parse_expiry_date(const char *date, unsigned long *timestamp);
void datestamp(char *buf, int bufsize);
#define approxidate(s) approxidate_careful((s), NULL)
unsigned long approxidate_careful(const char *, int *);
@@ -1024,9 +1043,21 @@ struct ref {
unsigned int
force:1,
forced_update:1,
- merge:1,
deletion:1,
matched:1;
+
+ /*
+ * Order is important here, as we write to FETCH_HEAD
+ * in numeric order. And the default NOT_FOR_MERGE
+ * should be 0, so that xcalloc'd structures get it
+ * by default.
+ */
+ enum {
+ FETCH_HEAD_MERGE = -1,
+ FETCH_HEAD_NOT_FOR_MERGE = 0,
+ FETCH_HEAD_IGNORE = 1
+ } fetch_head_status;
+
enum {
REF_STATUS_NONE = 0,
REF_STATUS_OK,
@@ -1099,6 +1130,7 @@ extern int unpack_object_header(struct packed_git *, struct pack_window **, off_
struct object_info {
/* Request */
unsigned long *sizep;
+ unsigned long *disk_sizep;
/* Response */
enum {
@@ -1326,4 +1358,31 @@ int checkout_fast_forward(const unsigned char *from,
int sane_execvp(const char *file, char *const argv[]);
+/*
+ * A struct to encapsulate the concept of whether a file has changed
+ * since we last checked it. This uses criteria similar to those used
+ * for the index.
+ */
+struct stat_validity {
+ struct stat_data *sd;
+};
+
+void stat_validity_clear(struct stat_validity *sv);
+
+/*
+ * Returns 1 if the path is a regular file (or a symlink to a regular
+ * file) and matches the saved stat_validity, 0 otherwise. A missing
+ * or inaccessible file is considered a match if the struct was just
+ * initialized, or if the previous update found an inaccessible file.
+ */
+int stat_validity_check(struct stat_validity *sv, const char *path);
+
+/*
+ * Update the stat_validity from a file opened at descriptor fd. If
+ * the file is missing, inaccessible, or not a regular file, then
+ * future calls to stat_validity_check will match iff one of those
+ * conditions continues to be true.
+ */
+void stat_validity_update(struct stat_validity *sv, int fd);
+
#endif /* CACHE_H */
diff --git a/color.c b/color.c
index e8e2681..f672885 100644
--- a/color.c
+++ b/color.c
@@ -1,7 +1,7 @@
#include "cache.h"
#include "color.h"
-static int git_use_color_default = 0;
+static int git_use_color_default = GIT_COLOR_AUTO;
int color_stdout_is_tty = -1;
/*
diff --git a/combine-diff.c b/combine-diff.c
index 77d7872..6dc0609 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -518,8 +518,11 @@ static int give_context(struct sline *sline, unsigned long cnt, int num_parent)
unsigned long k;
/* Paint a few lines before the first interesting line. */
- while (j < i)
- sline[j++].flag |= mark | no_pre_delete;
+ while (j < i) {
+ if (!(sline[j].flag & mark))
+ sline[j].flag |= no_pre_delete;
+ sline[j++].flag |= mark;
+ }
again:
/* we know up to i is to be included. where does the
@@ -1302,6 +1305,7 @@ void diff_tree_combined(const unsigned char *sha1,
int i, num_paths, needsep, show_log_first, num_parent = parents->nr;
diffopts = *opt;
+ diff_tree_setup_paths(diffopts.pathspec.raw, &diffopts);
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
DIFF_OPT_SET(&diffopts, RECURSIVE);
DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL);
@@ -1372,6 +1376,8 @@ void diff_tree_combined(const unsigned char *sha1,
paths = paths->next;
free(tmp);
}
+
+ diff_tree_release_paths(&diffopts);
}
void diff_tree_combined_merge(const struct commit *commit, int dense,
diff --git a/commit-slab.h b/commit-slab.h
new file mode 100644
index 0000000..7d48163
--- /dev/null
+++ b/commit-slab.h
@@ -0,0 +1,98 @@
+#ifndef COMMIT_SLAB_H
+#define COMMIT_SLAB_H
+
+/*
+ * define_commit_slab(slabname, elemtype) creates boilerplate code to define
+ * a new struct (struct slabname) that is used to associate a piece of data
+ * of elemtype to commits, and a few functions to use that struct.
+ *
+ * After including this header file, using:
+ *
+ * define_commit_slab(indegee, int);
+ *
+ * will let you call the following functions:
+ *
+ * - int *indegree_at(struct indegree *, struct commit *);
+ *
+ * This function locates the data associated with the given commit in
+ * the indegree slab, and returns the pointer to it.
+ *
+ * - void init_indegree(struct indegree *);
+ * void init_indegree_with_stride(struct indegree *, int);
+ *
+ * Initializes the indegree slab that associates an array of integers
+ * to each commit. 'stride' specifies how big each array is. The slab
+ * that id initialied by the variant without "_with_stride" associates
+ * each commit with an array of one integer.
+ */
+
+/* allocate ~512kB at once, allowing for malloc overhead */
+#ifndef COMMIT_SLAB_SIZE
+#define COMMIT_SLAB_SIZE (512*1024-32)
+#endif
+
+#define define_commit_slab(slabname, elemtype) \
+ \
+struct slabname { \
+ unsigned slab_size; \
+ unsigned stride; \
+ unsigned slab_count; \
+ elemtype **slab; \
+}; \
+static int stat_ ##slabname## realloc; \
+ \
+static void init_ ##slabname## _with_stride(struct slabname *s, \
+ unsigned stride) \
+{ \
+ unsigned int elem_size; \
+ if (!stride) \
+ stride = 1; \
+ s->stride = stride; \
+ elem_size = sizeof(struct slabname) * stride; \
+ s->slab_size = COMMIT_SLAB_SIZE / elem_size; \
+ s->slab_count = 0; \
+ s->slab = NULL; \
+} \
+ \
+static void init_ ##slabname(struct slabname *s) \
+{ \
+ init_ ##slabname## _with_stride(s, 1); \
+} \
+ \
+static void clear_ ##slabname(struct slabname *s) \
+{ \
+ int i; \
+ for (i = 0; i < s->slab_count; i++) \
+ free(s->slab[i]); \
+ s->slab_count = 0; \
+ free(s->slab); \
+ s->slab = NULL; \
+} \
+ \
+static elemtype *slabname## _at(struct slabname *s, \
+ const struct commit *c) \
+{ \
+ int nth_slab, nth_slot, ix; \
+ \
+ ix = c->index * s->stride; \
+ nth_slab = ix / s->slab_size; \
+ nth_slot = ix % s->slab_size; \
+ \
+ if (s->slab_count <= nth_slab) { \
+ int i; \
+ s->slab = xrealloc(s->slab, \
+ (nth_slab + 1) * sizeof(s->slab)); \
+ stat_ ##slabname## realloc++; \
+ for (i = s->slab_count; i <= nth_slab; i++) \
+ s->slab[i] = NULL; \
+ s->slab_count = nth_slab + 1; \
+ } \
+ if (!s->slab[nth_slab]) \
+ s->slab[nth_slab] = xcalloc(s->slab_size, \
+ sizeof(**s->slab)); \
+ return &s->slab[nth_slab][nth_slot]; \
+} \
+ \
+static int stat_ ##slabname## realloc
+
+#endif /* COMMIT_SLAB_H */
diff --git a/commit.c b/commit.c
index 888e02a..e5862f6 100644
--- a/commit.c
+++ b/commit.c
@@ -8,12 +8,15 @@
#include "notes.h"
#include "gpg-interface.h"
#include "mergesort.h"
+#include "commit-slab.h"
+#include "prio-queue.h"
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
int save_commit_buffer = 1;
const char *commit_type = "commit";
+static int commit_count;
static struct commit *check_commit(struct object *obj,
const unsigned char *sha1,
@@ -58,8 +61,11 @@ struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_n
struct commit *lookup_commit(const unsigned char *sha1)
{
struct object *obj = lookup_object(sha1);
- if (!obj)
- return create_object(sha1, OBJ_COMMIT, alloc_commit_node());
+ if (!obj) {
+ struct commit *c = alloc_commit_node();
+ c->index = commit_count++;
+ return create_object(sha1, OBJ_COMMIT, c);
+ }
if (!obj->type)
obj->type = OBJ_COMMIT;
return check_commit(obj, sha1, 0);
@@ -507,32 +513,136 @@ struct commit *pop_commit(struct commit_list **stack)
}
/*
+ * Topological sort support
+ */
+
+/* count number of children that have not been emitted */
+define_commit_slab(indegree_slab, int);
+
+/* record author-date for each commit object */
+define_commit_slab(author_date_slab, unsigned long);
+
+static void record_author_date(struct author_date_slab *author_date,
+ struct commit *commit)
+{
+ const char *buf, *line_end;
+ char *buffer = NULL;
+ struct ident_split ident;
+ char *date_end;
+ unsigned long date;
+
+ if (!commit->buffer) {
+ unsigned long size;
+ enum object_type type;
+ buffer = read_sha1_file(commit->object.sha1, &type, &size);
+ if (!buffer)
+ return;
+ }
+
+ for (buf = commit->buffer ? commit->buffer : buffer;
+ buf;
+ buf = line_end + 1) {
+ line_end = strchrnul(buf, '\n');
+ if (prefixcmp(buf, "author ")) {
+ if (!line_end[0] || line_end[1] == '\n')
+ return; /* end of header */
+ continue;
+ }
+ if (split_ident_line(&ident,
+ buf + strlen("author "),
+ line_end - (buf + strlen("author "))) ||
+ !ident.date_begin || !ident.date_end)
+ goto fail_exit; /* malformed "author" line */
+ break;
+ }
+
+ date = strtoul(ident.date_begin, &date_end, 10);
+ if (date_end != ident.date_end)
+ goto fail_exit; /* malformed date */
+ *(author_date_slab_at(author_date, commit)) = date;
+
+fail_exit:
+ free(buffer);
+}
+
+static int compare_commits_by_author_date(const void *a_, const void *b_,
+ void *cb_data)
+{
+ const struct commit *a = a_, *b = b_;
+ struct author_date_slab *author_date = cb_data;
+ unsigned long a_date = *(author_date_slab_at(author_date, a));
+ unsigned long b_date = *(author_date_slab_at(author_date, b));
+
+ /* newer commits with larger date first */
+ if (a_date < b_date)
+ return 1;
+ else if (a_date > b_date)
+ return -1;
+ return 0;
+}
+
+int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)
+{
+ const struct commit *a = a_, *b = b_;
+ /* newer commits with larger date first */
+ if (a->date < b->date)
+ return 1;
+ else if (a->date > b->date)
+ return -1;
+ return 0;
+}
+
+/*
* Performs an in-place topological sort on the list supplied.
*/
-void sort_in_topological_order(struct commit_list ** list, int lifo)
+void sort_in_topological_order(struct commit_list **list, enum rev_sort_order sort_order)
{
struct commit_list *next, *orig = *list;
- struct commit_list *work, **insert;
struct commit_list **pptr;
+ struct indegree_slab indegree;
+ struct prio_queue queue;
+ struct commit *commit;
+ struct author_date_slab author_date;
if (!orig)
return;
*list = NULL;
+ init_indegree_slab(&indegree);
+ memset(&queue, '\0', sizeof(queue));
+
+ switch (sort_order) {
+ default: /* REV_SORT_IN_GRAPH_ORDER */
+ queue.compare = NULL;
+ break;
+ case REV_SORT_BY_COMMIT_DATE:
+ queue.compare = compare_commits_by_commit_date;
+ break;
+ case REV_SORT_BY_AUTHOR_DATE:
+ init_author_date_slab(&author_date);
+ queue.compare = compare_commits_by_author_date;
+ queue.cb_data = &author_date;
+ break;
+ }
+
/* Mark them and clear the indegree */
for (next = orig; next; next = next->next) {
struct commit *commit = next->item;
- commit->indegree = 1;
+ *(indegree_slab_at(&indegree, commit)) = 1;
+ /* also record the author dates, if needed */
+ if (sort_order == REV_SORT_BY_AUTHOR_DATE)
+ record_author_date(&author_date, commit);
}
/* update the indegree */
for (next = orig; next; next = next->next) {
- struct commit_list * parents = next->item->parents;
+ struct commit_list *parents = next->item->parents;
while (parents) {
struct commit *parent = parents->item;
+ int *pi = indegree_slab_at(&indegree, parent);
- if (parent->indegree)
- parent->indegree++;
+ if (*pi)
+ (*pi)++;
parents = parents->next;
}
}
@@ -544,34 +654,33 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
*
* the tips serve as a starting set for the work queue.
*/
- work = NULL;
- insert = &work;
for (next = orig; next; next = next->next) {
struct commit *commit = next->item;
- if (commit->indegree == 1)
- insert = &commit_list_insert(commit, insert)->next;
+ if (*(indegree_slab_at(&indegree, commit)) == 1)
+ prio_queue_put(&queue, commit);
}
- /* process the list in topological order */
- if (!lifo)
- commit_list_sort_by_date(&work);
+ /*
+ * This is unfortunate; the initial tips need to be shown
+ * in the order given from the revision traversal machinery.
+ */
+ if (sort_order == REV_SORT_IN_GRAPH_ORDER)
+ prio_queue_reverse(&queue);
+
+ /* We no longer need the commit list */
+ free_commit_list(orig);
pptr = list;
*list = NULL;
- while (work) {
- struct commit *commit;
- struct commit_list *parents, *work_item;
-
- work_item = work;
- work = work_item->next;
- work_item->next = NULL;
+ while ((commit = prio_queue_get(&queue)) != NULL) {
+ struct commit_list *parents;
- commit = work_item->item;
for (parents = commit->parents; parents ; parents = parents->next) {
struct commit *parent = parents->item;
+ int *pi = indegree_slab_at(&indegree, parent);
- if (!parent->indegree)
+ if (!*pi)
continue;
/*
@@ -579,21 +688,22 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
* when all their children have been emitted thereby
* guaranteeing topological order.
*/
- if (--parent->indegree == 1) {
- if (!lifo)
- commit_list_insert_by_date(parent, &work);
- else
- commit_list_insert(parent, &work);
- }
+ if (--(*pi) == 1)
+ prio_queue_put(&queue, parent);
}
/*
- * work_item is a commit all of whose children
- * have already been emitted. we can emit it now.
+ * all children of commit have already been
+ * emitted. we can emit it now.
*/
- commit->indegree = 0;
- *pptr = work_item;
- pptr = &work_item->next;
+ *(indegree_slab_at(&indegree, commit)) = 0;
+
+ pptr = &commit_list_insert(commit, pptr)->next;
}
+
+ clear_indegree_slab(&indegree);
+ clear_prio_queue(&queue);
+ if (sort_order == REV_SORT_BY_AUTHOR_DATE)
+ clear_author_date_slab(&author_date);
}
/* merge-base stuff */
@@ -1240,10 +1350,15 @@ int commit_tree(const struct strbuf *msg, unsigned char *tree,
static int find_invalid_utf8(const char *buf, int len)
{
int offset = 0;
+ static const unsigned int max_codepoint[] = {
+ 0x7f, 0x7ff, 0xffff, 0x10ffff
+ };
while (len) {
unsigned char c = *buf++;
int bytes, bad_offset;
+ unsigned int codepoint;
+ unsigned int min_val, max_val;
len--;
offset++;
@@ -1264,24 +1379,48 @@ static int find_invalid_utf8(const char *buf, int len)
bytes++;
}
- /* Must be between 1 and 5 more bytes */
- if (bytes < 1 || bytes > 5)
+ /*
+ * Must be between 1 and 3 more bytes. Longer sequences result in
+ * codepoints beyond U+10FFFF, which are guaranteed never to exist.
+ */
+ if (bytes < 1 || 3 < bytes)
return bad_offset;
/* Do we *have* that many bytes? */
if (len < bytes)
return bad_offset;
+ /*
+ * Place the encoded bits at the bottom of the value and compute the
+ * valid range.
+ */
+ codepoint = (c & 0x7f) >> bytes;
+ min_val = max_codepoint[bytes-1] + 1;
+ max_val = max_codepoint[bytes];
+
offset += bytes;
len -= bytes;
/* And verify that they are good continuation bytes */
do {
+ codepoint <<= 6;
+ codepoint |= *buf & 0x3f;
if ((*buf++ & 0xc0) != 0x80)
return bad_offset;
} while (--bytes);
- /* We could/should check the value and length here too */
+ /* Reject codepoints that are out of range for the sequence length. */
+ if (codepoint < min_val || codepoint > max_val)
+ return bad_offset;
+ /* Surrogates are only for UTF-16 and cannot be encoded in UTF-8. */
+ if ((codepoint & 0x1ff800) == 0xd800)
+ return bad_offset;
+ /* U+xxFFFE and U+xxFFFF are guaranteed non-characters. */
+ if ((codepoint & 0xffffe) == 0xfffe)
+ return bad_offset;
+ /* So are anything in the range U+FDD0..U+FDEF. */
+ if (codepoint >= 0xfdd0 && codepoint <= 0xfdef)
+ return bad_offset;
}
return -1;
}
@@ -1291,9 +1430,6 @@ static int find_invalid_utf8(const char *buf, int len)
*
* If it isn't, it assumes any non-utf8 characters are Latin1,
* and does the conversion.
- *
- * Fixme: we should probably also disallow overlong forms and
- * invalid characters. But we don't do that currently.
*/
static int verify_utf8(struct strbuf *buf)
{
diff --git a/commit.h b/commit.h
index 67bd509..35cc4e2 100644
--- a/commit.h
+++ b/commit.h
@@ -6,6 +6,7 @@
#include "strbuf.h"
#include "decorate.h"
#include "gpg-interface.h"
+#include "string-list.h"
struct commit_list {
struct commit *item;
@@ -15,7 +16,7 @@ struct commit_list {
struct commit {
struct object object;
void *util;
- unsigned int indegree;
+ unsigned int index;
unsigned long date;
struct commit_list *parents;
struct tree *tree;
@@ -79,6 +80,9 @@ enum cmit_fmt {
};
struct pretty_print_context {
+ /*
+ * Callers should tweak these to change the behavior of pp_* functions.
+ */
enum cmit_fmt fmt;
int abbrev;
const char *subject;
@@ -92,6 +96,15 @@ struct pretty_print_context {
const char *output_encoding;
struct string_list *mailmap;
int color;
+ struct ident_split *from_ident;
+
+ /*
+ * Fields below here are manipulated internally by pp_* functions and
+ * should not be counted on by callers.
+ */
+
+ /* Manipulated by the pp_* functions internally. */
+ struct string_list in_body_headers;
};
struct userformat_want {
@@ -111,20 +124,20 @@ extern void userformat_find_requirements(const char *fmt, struct userformat_want
extern void format_commit_message(const struct commit *commit,
const char *format, struct strbuf *sb,
const struct pretty_print_context *context);
-extern void pretty_print_commit(const struct pretty_print_context *pp,
+extern void pretty_print_commit(struct pretty_print_context *pp,
const struct commit *commit,
struct strbuf *sb);
extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
struct strbuf *sb);
-void pp_user_info(const struct pretty_print_context *pp,
+void pp_user_info(struct pretty_print_context *pp,
const char *what, struct strbuf *sb,
const char *line, const char *encoding);
-void pp_title_line(const struct pretty_print_context *pp,
+void pp_title_line(struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
const char *encoding,
int need_8bit_cte);
-void pp_remainder(const struct pretty_print_context *pp,
+void pp_remainder(struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
int indent);
@@ -142,15 +155,24 @@ void clear_commit_marks(struct commit *commit, unsigned int mark);
void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
+
+enum rev_sort_order {
+ REV_SORT_IN_GRAPH_ORDER = 0,
+ REV_SORT_BY_COMMIT_DATE,
+ REV_SORT_BY_AUTHOR_DATE
+};
+
/*
* Performs an in-place topological sort of list supplied.
*
* invariant of resulting list is:
* a reachable from b => ord(b) < ord(a)
- * in addition, when lifo == 0, commits on parallel tracks are
- * sorted in the dates order.
+ * sort_order further specifies:
+ * REV_SORT_IN_GRAPH_ORDER: try to show a commit on a single-parent
+ * chain together.
+ * REV_SORT_BY_COMMIT_DATE: show eligible commits in committer-date order.
*/
-void sort_in_topological_order(struct commit_list ** list, int lifo);
+void sort_in_topological_order(struct commit_list **, enum rev_sort_order);
struct commit_graft {
unsigned char sha1[20];
@@ -176,6 +198,8 @@ 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);
+extern void check_shallow_file_for_update(void);
+extern void set_alternate_shallow_file(const char *path);
int is_descendant_of(struct commit *, struct commit_list *);
int in_merge_bases(struct commit *, struct commit *);
@@ -243,4 +267,6 @@ extern void print_commit_list(struct commit_list *list,
*/
extern void check_commit_signature(const struct commit* commit, struct signature_check *sigc);
+int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused);
+
#endif /* COMMIT_H */
diff --git a/compat/clipped-write.c b/compat/clipped-write.c
new file mode 100644
index 0000000..b8f98ff
--- /dev/null
+++ b/compat/clipped-write.c
@@ -0,0 +1,13 @@
+#include "../git-compat-util.h"
+#undef write
+
+/*
+ * Version of write that will write at most INT_MAX bytes.
+ * Workaround a xnu bug on Mac OS X
+ */
+ssize_t clipped_write(int fildes, const void *buf, size_t nbyte)
+{
+ if (nbyte > INT_MAX)
+ nbyte = INT_MAX;
+ return write(fildes, buf, nbyte);
+}
diff --git a/compat/cygwin.c b/compat/cygwin.c
index 871b41d..91ce5d4 100644
--- a/compat/cygwin.c
+++ b/compat/cygwin.c
@@ -1,14 +1,9 @@
#define CYGWIN_C
#define WIN32_LEAN_AND_MEAN
-#ifdef CYGWIN_V15_WIN32API
-#include "../git-compat-util.h"
-#include "win32.h"
-#else
#include <sys/stat.h>
#include <sys/errno.h>
#include "win32.h"
#include "../git-compat-util.h"
-#endif
#include "../cache.h" /* to read configuration */
/*
diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c
index 5ef0685..378c467 100644
--- a/compat/fnmatch/fnmatch.c
+++ b/compat/fnmatch/fnmatch.c
@@ -25,6 +25,7 @@
# define _GNU_SOURCE 1
#endif
+#include <stddef.h>
#include <errno.h>
#include <fnmatch.h>
#include <ctype.h>
@@ -121,7 +122,7 @@
whose names are inconsistent. */
# if !defined _LIBC && !defined getenv
-extern char *getenv ();
+extern char *getenv (const char *name);
# endif
# ifndef errno
diff --git a/compat/mingw.c b/compat/mingw.c
index b673625..bb92c43 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -841,8 +841,8 @@ struct pinfo_t {
struct pinfo_t *next;
pid_t pid;
HANDLE proc;
-} pinfo_t;
-struct pinfo_t *pinfo = NULL;
+};
+static struct pinfo_t *pinfo = NULL;
CRITICAL_SECTION pinfo_cs;
static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
@@ -1253,7 +1253,7 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
else
sin->sin_addr.s_addr = INADDR_LOOPBACK;
ai->ai_addr = (struct sockaddr *)sin;
- ai->ai_next = 0;
+ ai->ai_next = NULL;
return 0;
}
@@ -1677,14 +1677,16 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out)
#undef signal
sig_handler_t mingw_signal(int sig, sig_handler_t handler)
{
- sig_handler_t old = timer_fn;
+ sig_handler_t old;
switch (sig) {
case SIGALRM:
+ old = timer_fn;
timer_fn = handler;
break;
case SIGINT:
+ old = sigint_fn;
sigint_fn = handler;
break;
diff --git a/compat/mingw.h b/compat/mingw.h
index 685cd2c..bd0a88b 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -334,13 +334,20 @@ char **make_augmented_environ(const char *const *vars);
void free_environ(char **env);
/*
+ * A critical section used in the implementation of the spawn
+ * functions (mingw_spawnv[p]e()) and waitpid(). Intialised in
+ * the replacement main() macro below.
+ */
+extern CRITICAL_SECTION pinfo_cs;
+
+/*
* A replacement of main() that ensures that argv[0] has a path
* and that default fmode and std(in|out|err) are in binary mode
*/
#define main(c,v) dummy_decl_mingw_main(); \
-static int mingw_main(); \
-int main(int argc, const char **argv) \
+static int mingw_main(c,v); \
+int main(int argc, char **argv) \
{ \
extern CRITICAL_SECTION pinfo_cs; \
_fmode = _O_BINARY; \
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h
index 1401a67..5a44dea 100644
--- a/compat/nedmalloc/malloc.c.h
+++ b/compat/nedmalloc/malloc.c.h
@@ -484,6 +484,10 @@ MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP
#define DLMALLOC_VERSION 20804
#endif /* DLMALLOC_VERSION */
+#if defined(linux)
+#define _GNU_SOURCE 1
+#endif
+
#ifndef WIN32
#ifdef _WIN32
#define WIN32 1
@@ -1802,7 +1806,7 @@ struct win32_mlock_t
static MLOCK_T malloc_global_mutex = { 0, 0, 0};
-static FORCEINLINE long win32_getcurrentthreadid() {
+static FORCEINLINE long win32_getcurrentthreadid(void) {
#ifdef _MSC_VER
#if defined(_M_IX86)
long *threadstruct=(long *)__readfsdword(0x18);
diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c
index 91c4e7f..609ebba 100644
--- a/compat/nedmalloc/nedmalloc.c
+++ b/compat/nedmalloc/nedmalloc.c
@@ -159,8 +159,8 @@ struct mallinfo nedmallinfo(void) THROWSPEC { return nedpmallinfo(0); }
#endif
int nedmallopt(int parno, int value) THROWSPEC { return nedpmallopt(0, parno, value); }
int nedmalloc_trim(size_t pad) THROWSPEC { return nedpmalloc_trim(0, pad); }
-void nedmalloc_stats() THROWSPEC { nedpmalloc_stats(0); }
-size_t nedmalloc_footprint() THROWSPEC { return nedpmalloc_footprint(0); }
+void nedmalloc_stats(void) THROWSPEC { nedpmalloc_stats(0); }
+size_t nedmalloc_footprint(void) THROWSPEC { return nedpmalloc_footprint(0); }
void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); }
void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC { return nedpindependent_comalloc(0, elems, sizes, chunks); }
diff --git a/compat/poll/poll.c b/compat/poll/poll.c
index 7d226ec..4410310 100644
--- a/compat/poll/poll.c
+++ b/compat/poll/poll.c
@@ -576,7 +576,7 @@ restart:
{
/* It's a socket. */
WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
- WSAEventSelect ((SOCKET) h, 0, 0);
+ WSAEventSelect ((SOCKET) h, NULL, 0);
/* If we're lucky, WSAEnumNetworkEvents already provided a way
to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */
diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c
index 0194965..0cd6e0e 100644
--- a/compat/regex/regexec.c
+++ b/compat/regex/regexec.c
@@ -2313,7 +2313,7 @@ transit_state (reg_errcode_t *err, re_match_context_t *mctx,
}
/* Update the state_log if we need */
-re_dfastate_t *
+static re_dfastate_t *
internal_function
merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
re_dfastate_t *next_state)
@@ -2326,7 +2326,7 @@ merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
mctx->state_log[cur_idx] = next_state;
mctx->state_log_top = cur_idx;
}
- else if (mctx->state_log[cur_idx] == 0)
+ else if (mctx->state_log[cur_idx] == NULL)
{
mctx->state_log[cur_idx] = next_state;
}
@@ -2392,7 +2392,7 @@ merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
/* Skip bytes in the input that correspond to part of a
multi-byte match, then look in the log for a state
from which to restart matching. */
-re_dfastate_t *
+static re_dfastate_t *
internal_function
find_recover_state (reg_errcode_t *err, re_match_context_t *mctx)
{
diff --git a/compat/terminal.c b/compat/terminal.c
index 9b5e3d1..313897d 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -3,7 +3,7 @@
#include "sigchain.h"
#include "strbuf.h"
-#if defined(HAVE_DEV_TTY) || defined(WIN32)
+#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
static void restore_term(void);
@@ -53,7 +53,7 @@ error:
return -1;
}
-#elif defined(WIN32)
+#elif defined(GIT_WINDOWS_NATIVE)
#define INPUT_PATH "CONIN$"
#define OUTPUT_PATH "CONOUT$"
diff --git a/compat/unsetenv.c b/compat/unsetenv.c
index eb29f5e..4ea1856 100644
--- a/compat/unsetenv.c
+++ b/compat/unsetenv.c
@@ -2,7 +2,6 @@
void gitunsetenv (const char *name)
{
- extern char **environ;
int src, dst;
size_t nmln;
diff --git a/compat/win32.h b/compat/win32.h
index 8ce9104..a97e880 100644
--- a/compat/win32.h
+++ b/compat/win32.h
@@ -2,7 +2,7 @@
#define WIN32_H
/* common Win32 functions for MinGW and Cygwin */
-#ifndef WIN32 /* Not defined by Cygwin */
+#ifndef GIT_WINDOWS_NATIVE /* Not defined for Cygwin */
#include <windows.h>
#endif
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c
index 010e875..e18f5c6 100644
--- a/compat/win32/pthread.c
+++ b/compat/win32/pthread.c
@@ -52,7 +52,7 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr)
pthread_t pthread_self(void)
{
- pthread_t t = { 0 };
+ pthread_t t = { NULL };
t.tid = GetCurrentThreadId();
return t;
}
diff --git a/compat/win32mmap.c b/compat/win32mmap.c
index 61d2ef8..80a8c9a 100644
--- a/compat/win32mmap.c
+++ b/compat/win32mmap.c
@@ -21,8 +21,8 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
if (!(flags & MAP_PRIVATE))
die("Invalid usage of mmap when built with USE_WIN32_MMAP");
- hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), 0, PAGE_WRITECOPY,
- 0, 0, 0);
+ hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), NULL,
+ PAGE_WRITECOPY, 0, 0, NULL);
if (!hmap)
return MAP_FAILED;
diff --git a/config.c b/config.c
index aefd80b..d04e815 100644
--- a/config.c
+++ b/config.c
@@ -58,7 +58,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc
path = buf.buf;
}
- if (!access_or_die(path, R_OK)) {
+ if (!access_or_die(path, R_OK, 0)) {
if (++inc->depth > MAX_INCLUDE_DEPTH)
die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
cf && cf->name ? cf->name : "the command line");
@@ -566,7 +566,20 @@ static int git_default_core_config(const char *var, const char *value)
trust_ctime = git_config_bool(var, value);
return 0;
}
- if (!strcmp(var, "core.statinfo")) {
+ if (!strcmp(var, "core.statinfo") ||
+ !strcmp(var, "core.checkstat")) {
+ /*
+ * NEEDSWORK: statinfo was a typo in v1.8.2 that has
+ * never been advertised. we will remove it at Git
+ * 2.0 boundary.
+ */
+ if (!strcmp(var, "core.statinfo")) {
+ static int warned;
+ if (!warned++) {
+ warning("'core.statinfo' will be removed in Git 2.0; "
+ "use 'core.checkstat' instead.");
+ }
+ }
if (!strcasecmp(value, "default"))
check_stat = 1;
else if (!strcasecmp(value, "minimal"))
@@ -675,9 +688,6 @@ static int git_default_core_config(const char *var, const char *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)
@@ -954,23 +964,23 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
home_config_paths(&user_config, &xdg_config, "config");
- if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK)) {
+ if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
data);
found += 1;
}
- if (xdg_config && !access_or_die(xdg_config, R_OK)) {
+ if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {
ret += git_config_from_file(fn, xdg_config, data);
found += 1;
}
- if (user_config && !access_or_die(user_config, R_OK)) {
+ if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) {
ret += git_config_from_file(fn, user_config, data);
found += 1;
}
- if (repo_config && !access_or_die(repo_config, R_OK)) {
+ if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
ret += git_config_from_file(fn, repo_config, data);
found += 1;
}
diff --git a/config.mak.uname b/config.mak.uname
index d78fd3d..7ac541e 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -95,6 +95,7 @@ ifeq ($(uname_S),Darwin)
NO_MEMMEM = YesPlease
USE_ST_TIMESPEC = YesPlease
HAVE_DEV_TTY = YesPlease
+ NEEDS_CLIPPED_WRITE = YesPlease
COMPAT_OBJS += compat/precompose_utf8.o
BASIC_CFLAGS += -DPRECOMPOSE_UNICODE
endif
@@ -158,7 +159,6 @@ ifeq ($(uname_O),Cygwin)
NO_SYMLINK_HEAD = YesPlease
NO_IPV6 = YesPlease
OLD_ICONV = UnfortunatelyYes
- CYGWIN_V15_WIN32API = YesPlease
endif
NO_THREAD_SAFE_PREAD = YesPlease
NEEDS_LIBICONV = YesPlease
@@ -480,7 +480,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_FNMATCH = YesPlease
NO_MEMMEM = YesPlease
NEEDS_LIBICONV = YesPlease
- OLD_ICONV = YesPlease
NO_STRTOUMAX = YesPlease
NO_MKDTEMP = YesPlease
NO_MKSTEMPS = YesPlease
@@ -500,13 +499,17 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_INET_PTON = YesPlease
NO_INET_NTOP = YesPlease
NO_POSIX_GOODIES = UnfortunatelyYes
+ DEFAULT_HELP_FORMAT = html
COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o
+ BASIC_LDFLAGS += -Wl,--large-address-aware
EXTLIBS += -lws2_32
+ GITLIBS += git.res
PTHREAD_LIBS =
+ RC = windres -O coff
NATIVE_CRLF = YesPlease
X = .exe
SPARSE_FLAGS = -Wno-one-bit-signed-bitfield
@@ -518,6 +521,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
NO_R_TO_GCC_LINKER = YesPlease
INTERNAL_QSORT = YesPlease
HAVE_LIBCHARSET_H = YesPlease
+ NO_GETTEXT = YesPlease
else
NO_CURL = YesPlease
endif
diff --git a/configure.ac b/configure.ac
index f3462d9..2f43393 100644
--- a/configure.ac
+++ b/configure.ac
@@ -193,7 +193,7 @@ AC_ARG_ENABLE([pthreads],
[FLAGS is the value to pass to the compiler to enable POSIX Threads.]
[The default if FLAGS is not specified is to try first -pthread]
[and then -lpthread.]
- [--without-pthreads will disable threading.])],
+ [--disable-pthreads will disable threading.])],
[
if test "x$enableval" = "xyes"; then
AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads])
diff --git a/connect.c b/connect.c
index f57efd0..a0783d4 100644
--- a/connect.c
+++ b/connect.c
@@ -551,8 +551,11 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
path = strchr(end, c);
if (path && !has_dos_drive_prefix(end)) {
if (c == ':') {
- protocol = PROTO_SSH;
- *path++ = '\0';
+ if (path < strchrnul(host, '/')) {
+ protocol = PROTO_SSH;
+ *path++ = '\0';
+ } else /* '/' in the host part, assume local path */
+ path = end;
}
} else
path = end;
diff --git a/connected.c b/connected.c
index 1e89c1c..fae8d64 100644
--- a/connected.c
+++ b/connected.c
@@ -2,7 +2,12 @@
#include "run-command.h"
#include "sigchain.h"
#include "connected.h"
+#include "transport.h"
+int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
+{
+ return check_everything_connected_with_transport(fn, quiet, cb_data, NULL);
+}
/*
* If we feed all the commits we want to verify to this command
*
@@ -14,7 +19,10 @@
*
* Returns 0 if everything is connected, non-zero otherwise.
*/
-int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
+int check_everything_connected_with_transport(sha1_iterate_fn fn,
+ int quiet,
+ void *cb_data,
+ struct transport *transport)
{
struct child_process rev_list;
const char *argv[] = {"rev-list", "--objects",
@@ -22,10 +30,23 @@ int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
char commit[41];
unsigned char sha1[20];
int err = 0;
+ struct packed_git *new_pack = NULL;
if (fn(cb_data, sha1))
return err;
+ if (transport && transport->smart_options &&
+ transport->smart_options->self_contained_and_connected &&
+ transport->pack_lockfile &&
+ !suffixcmp(transport->pack_lockfile, ".keep")) {
+ struct strbuf idx_file = STRBUF_INIT;
+ strbuf_addstr(&idx_file, transport->pack_lockfile);
+ strbuf_setlen(&idx_file, idx_file.len - 5); /* ".keep" */
+ strbuf_addstr(&idx_file, ".idx");
+ new_pack = add_packed_git(idx_file.buf, idx_file.len, 1);
+ strbuf_release(&idx_file);
+ }
+
if (quiet)
argv[5] = "--quiet";
@@ -42,6 +63,17 @@ int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
commit[40] = '\n';
do {
+ /*
+ * If index-pack already checked that:
+ * - there are no dangling pointers in the new pack
+ * - the pack is self contained
+ * Then if the updated ref is in the new pack, then we
+ * are sure the ref is good and not sending it to
+ * rev-list for verification.
+ */
+ if (new_pack && find_pack_entry_one(sha1, new_pack))
+ continue;
+
memcpy(commit, sha1_to_hex(sha1), 40);
if (write_in_full(rev_list.in, commit, 41) < 0) {
if (errno != EPIPE && errno != EINVAL)
diff --git a/connected.h b/connected.h
index 7e4585a..0b060b7 100644
--- a/connected.h
+++ b/connected.h
@@ -1,6 +1,8 @@
#ifndef CONNECTED_H
#define CONNECTED_H
+struct transport;
+
/*
* Take callback data, and return next object name in the buffer.
* When called after returning the name for the last object, return -1
@@ -16,5 +18,8 @@ typedef int (*sha1_iterate_fn)(void *, unsigned char [20]);
* 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);
+extern int check_everything_connected_with_transport(sha1_iterate_fn, int quiet,
+ void *cb_data,
+ struct transport *transport);
#endif /* CONNECTED_H */
diff --git a/contrib/blameview/README b/contrib/blameview/README
deleted file mode 100644
index fada5ce..0000000
--- a/contrib/blameview/README
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a sample program to use 'git-blame --incremental', based
-on this message.
-
-From: Jeff King <peff@peff.net>
-Subject: Re: More precise tag following
-To: Linus Torvalds <torvalds@linux-foundation.org>
-Cc: git@vger.kernel.org
-Date: Sat, 27 Jan 2007 18:52:38 -0500
-Message-ID: <20070127235238.GA28706@coredump.intra.peff.net>
diff --git a/contrib/blameview/blameview.perl b/contrib/blameview/blameview.perl
deleted file mode 100755
index 1dec001..0000000
--- a/contrib/blameview/blameview.perl
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/perl
-
-use Gtk2 -init;
-use Gtk2::SimpleList;
-
-my $hash;
-my $fn;
-if ( @ARGV == 1 ) {
- $hash = "HEAD";
- $fn = shift;
-} elsif ( @ARGV == 2 ) {
- $hash = shift;
- $fn = shift;
-} else {
- die "Usage blameview [<rev>] <filename>";
-}
-
-Gtk2::Rc->parse_string(<<'EOS');
-style "treeview_style"
-{
- GtkTreeView::vertical-separator = 0
-}
-class "GtkTreeView" style "treeview_style"
-EOS
-
-my $window = Gtk2::Window->new('toplevel');
-$window->signal_connect(destroy => sub { Gtk2->main_quit });
-my $vpan = Gtk2::VPaned->new();
-$window->add($vpan);
-my $scrolled_window = Gtk2::ScrolledWindow->new;
-$vpan->pack1($scrolled_window, 1, 1);
-my $fileview = Gtk2::SimpleList->new(
- 'Commit' => 'text',
- 'FileLine' => 'text',
- 'Data' => 'text'
-);
-$scrolled_window->add($fileview);
-$fileview->get_column(0)->set_spacing(0);
-$fileview->set_size_request(1024, 768);
-$fileview->set_rules_hint(1);
-$fileview->signal_connect (row_activated => sub {
- my ($sl, $path, $column) = @_;
- my $row_ref = $sl->get_row_data_from_path ($path);
- system("blameview @$row_ref[0]~1 $fn &");
- });
-
-my $commitwindow = Gtk2::ScrolledWindow->new();
-$commitwindow->set_policy ('GTK_POLICY_AUTOMATIC','GTK_POLICY_AUTOMATIC');
-$vpan->pack2($commitwindow, 1, 1);
-my $commit_text = Gtk2::TextView->new();
-my $commit_buffer = Gtk2::TextBuffer->new();
-$commit_text->set_buffer($commit_buffer);
-$commitwindow->add($commit_text);
-
-$fileview->signal_connect (cursor_changed => sub {
- my ($sl) = @_;
- my ($path, $focus_column) = $sl->get_cursor();
- my $row_ref = $sl->get_row_data_from_path ($path);
- my $c_fh;
- open($c_fh, '-|', "git cat-file commit @$row_ref[0]")
- or die "unable to find commit @$row_ref[0]";
- my @buffer = <$c_fh>;
- $commit_buffer->set_text("@buffer");
- close($c_fh);
- });
-
-my $fh;
-open($fh, '-|', "git cat-file blob $hash:$fn")
- or die "unable to open $fn: $!";
-
-while(<$fh>) {
- chomp;
- $fileview->{data}->[$.] = ['HEAD', "$fn:$.", $_];
-}
-
-my $blame;
-open($blame, '-|', qw(git blame --incremental --), $fn, $hash)
- or die "cannot start git-blame $fn";
-
-Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line);
-
-$window->show_all;
-Gtk2->main;
-exit 0;
-
-my %commitinfo = ();
-
-sub flush_blame_line {
- my ($attr) = @_;
-
- return unless defined $attr;
-
- my ($commit, $s_lno, $lno, $cnt) =
- @{$attr}{qw(COMMIT S_LNO LNO CNT)};
-
- my ($filename, $author, $author_time, $author_tz) =
- @{$commitinfo{$commit}}{qw(FILENAME AUTHOR AUTHOR-TIME AUTHOR-TZ)};
- my $info = $author . ' ' . format_time($author_time, $author_tz);
-
- for(my $i = 0; $i < $cnt; $i++) {
- @{$fileview->{data}->[$lno+$i-1]}[0,1,2] =
- (substr($commit, 0, 8), $filename . ':' . ($s_lno+$i));
- }
-}
-
-my $buf;
-my $current;
-sub read_blame_line {
-
- my $r = sysread($blame, $buf, 1024, length($buf));
- die "I/O error" unless defined $r;
-
- if ($r == 0) {
- flush_blame_line($current);
- $current = undef;
- return 0;
- }
-
- while ($buf =~ s/([^\n]*)\n//) {
- my $line = $1;
-
- if (($commit, $s_lno, $lno, $cnt) =
- ($line =~ /^([0-9a-f]{40}) (\d+) (\d+) (\d+)$/)) {
- flush_blame_line($current);
- $current = +{
- COMMIT => $1,
- S_LNO => $2,
- LNO => $3,
- CNT => $4,
- };
- next;
- }
-
- # extended attribute values
- if ($line =~ /^(author|author-mail|author-time|author-tz|committer|committer-mail|committer-time|committer-tz|summary|filename) (.*)$/) {
- my $commit = $current->{COMMIT};
- $commitinfo{$commit}{uc($1)} = $2;
- next;
- }
- }
- return 1;
-}
-
-sub format_time {
- my $time = shift;
- my $tz = shift;
-
- my $minutes = $tz < 0 ? 0-$tz : $tz;
- $minutes = ($minutes / 100)*60 + ($minutes % 100);
- $minutes = $tz < 0 ? 0-$minutes : $minutes;
- $time += $minutes * 60;
- my @t = gmtime($time);
- return sprintf('%04d-%02d-%02d %02d:%02d:%02d %s',
- $t[5] + 1900, @t[4,3,2,1,0], $tz);
-}
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 1c35eef..fa3ae1e 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -33,8 +33,6 @@ 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"
@@ -252,106 +250,50 @@ __gitcomp_file ()
# since tilde expansion is not applied.
# This means that COMPREPLY will be empty and Bash default
# completion will be used.
- COMPREPLY=($(compgen -P "${2-}" -W "$1" -- "${3-$cur}"))
+ __gitcompadd "$1" "${2-}" "${3-$cur}" ""
- # Tell Bash that compspec generates filenames.
- compopt -o filenames 2>/dev/null
+ # use a hack to enable file mode in bash < 4
+ compopt -o filenames +o nospace 2>/dev/null ||
+ compgen -f /non-existing-dir/ > /dev/null
}
-__git_index_file_list_filter_compat ()
-{
- local path
-
- while read -r path; do
- case "$path" in
- ?*/*) echo "${path%%/*}/" ;;
- *) echo "$path" ;;
- esac
- done
-}
-
-__git_index_file_list_filter_bash ()
-{
- local path
-
- while read -r path; do
- case "$path" in
- ?*/*)
- # XXX if we append a slash to directory names when using
- # `compopt -o filenames`, Bash will append another slash.
- # This is pretty stupid, and this the reason why we have to
- # define a compatible version for this function.
- echo "${path%%/*}" ;;
- *)
- echo "$path" ;;
- esac
- done
-}
-
-# Process path list returned by "ls-files" and "diff-index --name-only"
-# commands, in order to list only file names relative to a specified
-# directory, and append a slash to directory names.
-__git_index_file_list_filter ()
-{
- # Default to Bash >= 4.x
- __git_index_file_list_filter_bash
-}
-
-# Execute git ls-files, returning paths relative to the directory
-# specified in the first argument, and using the options specified in
-# the second argument.
+# Execute 'git ls-files', unless the --committable option is specified, in
+# which case it runs 'git diff-index' to find out the files that can be
+# committed. It return paths relative to the directory specified in the first
+# argument, and using the options specified in the second argument.
__git_ls_files_helper ()
{
(
test -n "${CDPATH+set}" && unset CDPATH
- # NOTE: $2 is not quoted in order to support multiple options
- cd "$1" && git ls-files --exclude-standard $2
+ cd "$1"
+ if [ "$2" == "--committable" ]; then
+ git diff-index --name-only --relative HEAD
+ else
+ # NOTE: $2 is not quoted in order to support multiple options
+ git ls-files --exclude-standard $2
+ fi
) 2>/dev/null
}
-# Execute git diff-index, returning paths relative to the directory
-# specified in the first argument, and using the tree object id
-# specified in the second argument.
-__git_diff_index_helper ()
-{
- (
- test -n "${CDPATH+set}" && unset CDPATH
- cd "$1" && git diff-index --name-only --relative "$2"
- ) 2>/dev/null
-}
-
# __git_index_files accepts 1 or 2 arguments:
# 1: Options to pass to ls-files (required).
-# Supported options are --cached, --modified, --deleted, --others,
-# and --directory.
# 2: A directory path (optional).
# If provided, only files within the specified directory are listed.
# Sub directories are never recursed. Path must have a trailing
# slash.
__git_index_files ()
{
- local dir="$(__gitdir)" root="${2-.}"
+ local dir="$(__gitdir)" root="${2-.}" file
if [ -d "$dir" ]; then
- __git_ls_files_helper "$root" "$1" | __git_index_file_list_filter |
- sort | uniq
- fi
-}
-
-# __git_diff_index_files accepts 1 or 2 arguments:
-# 1) The id of a tree object.
-# 2) A directory path (optional).
-# If provided, only files within the specified directory are listed.
-# Sub directories are never recursed. Path must have a trailing
-# slash.
-__git_diff_index_files ()
-{
- local dir="$(__gitdir)" root="${2-.}"
-
- if [ -d "$dir" ]; then
- __git_diff_index_helper "$root" "$1" | __git_index_file_list_filter |
- sort | uniq
+ __git_ls_files_helper "$root" "$1" |
+ while read -r file; do
+ case "$file" in
+ ?*/*) echo "${file%%/*}" ;;
+ *) echo "$file" ;;
+ esac
+ done | sort | uniq
fi
}
@@ -427,14 +369,8 @@ __git_refs ()
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
+ echo "HEAD"
+ git for-each-ref --format="%(refname:short)" -- "refs/remotes/$dir/" | sed -e "s#^$dir/##"
;;
esac
}
@@ -552,44 +488,23 @@ __git_complete_revlist_file ()
}
-# __git_complete_index_file requires 1 argument: the options to pass to
-# ls-file
+# __git_complete_index_file requires 1 argument:
+# 1: the options to pass to ls-file
+#
+# The exception is --committable, which finds the files appropriate commit.
__git_complete_index_file ()
{
- local pfx cur_="$cur"
+ local pfx="" cur_="$cur"
case "$cur_" in
?*/*)
pfx="${cur_%/*}"
cur_="${cur_##*/}"
pfx="${pfx}/"
-
- __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_"
- ;;
- *)
- __gitcomp_file "$(__git_index_files "$1")" "" "$cur_"
;;
esac
-}
-
-# __git_complete_diff_index_file requires 1 argument: the id of a tree
-# object
-__git_complete_diff_index_file ()
-{
- local pfx cur_="$cur"
-
- case "$cur_" in
- ?*/*)
- pfx="${cur_%/*}"
- cur_="${cur_##*/}"
- pfx="${pfx}/"
- __gitcomp_file "$(__git_diff_index_files "$1" "$pfx")" "$pfx" "$cur_"
- ;;
- *)
- __gitcomp_file "$(__git_diff_index_files "$1")" "" "$cur_"
- ;;
- esac
+ __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_"
}
__git_complete_file ()
@@ -1213,7 +1128,7 @@ _git_commit ()
esac
if git rev-parse --verify --quiet HEAD >/dev/null; then
- __git_complete_diff_index_file "HEAD"
+ __git_complete_index_file "--committable"
else
# This is the first commit
__git_complete_index_file "--cached"
@@ -1246,7 +1161,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
--no-prefix --src-prefix= --dst-prefix=
--inter-hunk-context=
--patience --histogram --minimal
- --raw
+ --raw --word-diff
--dirstat --dirstat= --dirstat-by-file
--dirstat-by-file= --cumulative
--diff-algorithm=
@@ -1294,7 +1209,7 @@ _git_difftool ()
return
;;
esac
- __git_complete_file
+ __git_complete_revlist_file
}
__git_fetch_options="
@@ -2360,7 +2275,7 @@ _git_show ()
return
;;
esac
- __git_complete_file
+ __git_complete_revlist_file
}
_git_show_branch ()
@@ -2575,9 +2490,10 @@ __git_main ()
i="${words[c]}"
case "$i" in
--git-dir=*) __git_dir="${i#--git-dir=}" ;;
+ --git-dir) ((c++)) ; __git_dir="${words[c]}" ;;
--bare) __git_dir="." ;;
--help) command="help"; break ;;
- -c) c=$((++c)) ;;
+ -c|--work-tree|--namespace) ((c++)) ;;
-*) ;;
*) command="$i"; break ;;
esac
@@ -2595,6 +2511,7 @@ __git_main ()
--exec-path
--exec-path=
--html-path
+ --man-path
--info-path
--work-tree=
--namespace=
@@ -2663,7 +2580,7 @@ if [[ -n ${ZSH_VERSION-} ]]; then
--*=*|*.) ;;
*) c="$c " ;;
esac
- array[$#array+1]="$c"
+ array+=("$c")
done
compset -P '*[=:]'
compadd -Q -S '' -p "${2-}" -a -- array && _ret=0
@@ -2689,35 +2606,19 @@ if [[ -n ${ZSH_VERSION-} ]]; then
compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
}
- __git_zsh_helper ()
- {
- emulate -L ksh
- local cur cword prev
- cur=${words[CURRENT-1]}
- prev=${words[CURRENT-2]}
- let cword=CURRENT-1
- __${service}_main
- }
-
_git ()
{
- emulate -L zsh
- local _ret=1
- __git_zsh_helper
- let _ret && _default -S '' && _ret=0
+ local _ret=1 cur cword prev
+ cur=${words[CURRENT]}
+ prev=${words[CURRENT-1]}
+ let cword=CURRENT-1
+ emulate ksh -c __${service}_main
+ let _ret && _default && _ret=0
return _ret
}
compdef _git git gitk
return
-elif [[ -n ${BASH_VERSION-} ]]; then
- if ((${BASH_VERSINFO[0]} < 4)); then
- # compopt is not supported
- __git_index_file_list_filter ()
- {
- __git_index_file_list_filter_compat
- }
- fi
fi
__git_func_wrap ()
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index 2565d2e..fac5e71 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -4,18 +4,17 @@
#
# Copyright (c) 2012-2013 Felipe Contreras <felipe.contreras@gmail.com>
#
-# You need git's bash completion script installed somewhere, by default on the
-# same directory as this script.
+# You need git's bash completion script installed somewhere, by default it
+# would be the location bash-completion uses.
#
-# If your script is on ~/.git-completion.sh instead, you can configure it on
-# your ~/.zshrc:
+# If your script is somewhere else, you can configure it on your ~/.zshrc:
#
# zstyle ':completion:*:*:git:*' script ~/.git-completion.sh
#
-# The recommended way to install this script is to copy to
-# '~/.zsh/completion/_git', and then add the following to your ~/.zshrc file:
+# The recommended way to install this script is to copy to '~/.zsh/_git', and
+# then add the following to your ~/.zshrc file:
#
-# fpath=(~/.zsh/completion $fpath)
+# fpath=(~/.zsh $fpath)
complete ()
{
@@ -27,7 +26,19 @@ zstyle -T ':completion:*:*:git:*' tag-order && \
zstyle ':completion:*:*:git:*' tag-order 'common-commands'
zstyle -s ":completion:*:*:git:*" script script
-test -z "$script" && script="$(dirname ${funcsourcetrace[1]%:*})"/git-completion.bash
+if [ -z "$script" ]; then
+ local -a locations
+ local e
+ locations=(
+ '/etc/bash_completion.d/git' # fedora, old debian
+ '/usr/share/bash-completion/completions/git' # arch, ubuntu, new debian
+ '/usr/share/bash-completion/git' # gentoo
+ $(dirname ${funcsourcetrace[1]%:*})/git-completion.bash
+ )
+ for e in $locations; do
+ test -f $e && script="$e" && break
+ done
+fi
ZSH_VERSION='' . "$script"
__gitcomp ()
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index eaf5c36..a81ef5a 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -3,7 +3,7 @@
# 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.
+# This script allows you to see repository status in your prompt.
#
# To enable:
#
@@ -13,23 +13,27 @@
# 3a) Change your PS1 to call __git_ps1 as
# command-substitution:
# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
-# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
+# ZSH: setopt PROMPT_SUBST ; PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
# the optional argument will be used as format string.
-# 3b) Alternatively, if you are using bash, __git_ps1 can be
-# used for PROMPT_COMMAND with two parameters, <pre> and
-# <post>, which are strings you would put in $PS1 before
-# and after the status string generated by the git-prompt
-# machinery. e.g.
-# PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
-# will show username, at-sign, host, colon, cwd, then
-# various status string, followed by dollar and SP, as
-# your prompt.
+# 3b) Alternatively, for a slightly faster prompt, __git_ps1 can
+# be used for PROMPT_COMMAND in Bash or for precmd() in Zsh
+# with two parameters, <pre> and <post>, which are strings
+# you would put in $PS1 before and after the status string
+# generated by the git-prompt machinery. e.g.
+# Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
+# will show username, at-sign, host, colon, cwd, then
+# various status string, followed by dollar and SP, as
+# your prompt.
+# ZSH: precmd () { __git_ps1 "%n" ":%~$ " "|%s" }
+# will show username, pipe, then various status string,
+# followed by colon, cwd, dollar and SP, as your prompt.
# Optionally, you can supply a third argument with a printf
# format string to finetune the output of the branch status
#
-# 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.
+# The repository status will be displayed only if you are currently in a
+# git repository. The %s token is the placeholder for the shown status.
+#
+# The prompt status always includes the current branch name.
#
# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
# unstaged (*) and staged (+) changes will be shown next to the branch
@@ -77,31 +81,8 @@
#
# If you would like a colored hint about the current dirty state, set
# GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on
-# the colored output of "git status -sb".
-
-# __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
-}
+# the colored output of "git status -sb" and are available only when
+# using __git_ps1 for PROMPT_COMMAND or precmd.
# stores the divergence from upstream in $p
# used by GIT_PS1_SHOWUPSTREAM
@@ -124,7 +105,7 @@ __git_ps1_show_upstream ()
fi
;;
svn-remote.*.url)
- svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+ svn_remote[$((${#svn_remote[@]} + 1))]="$value"
svn_url_pattern+="\\|$value"
upstream=svn+git # default upstream is SVN if available, else git
;;
@@ -146,10 +127,11 @@ __git_ps1_show_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 \
+ local -a svn_upstream
+ 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[${#svn_upstream[@]} - 2]}
svn_upstream=${svn_upstream%@*}
local n_stop="${#svn_remote[@]}"
for ((n=1; n <= n_stop; n++)); do
@@ -222,6 +204,51 @@ __git_ps1_show_upstream ()
}
+# Helper function that is meant to be called from __git_ps1. It
+# injects color codes into the appropriate gitstring variables used
+# to build a gitstring.
+__git_ps1_colorize_gitstring ()
+{
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ local c_red='%F{red}'
+ local c_green='%F{green}'
+ local c_lblue='%F{blue}'
+ local c_clear='%f'
+ else
+ # Using \[ and \] around colors is necessary to prevent
+ # issues with command line editing/browsing/completion!
+ local c_red='\[\e[31m\]'
+ local c_green='\[\e[32m\]'
+ local c_lblue='\[\e[1;34m\]'
+ local c_clear='\[\e[0m\]'
+ fi
+ local bad_color=$c_red
+ local ok_color=$c_green
+ local flags_color="$c_lblue"
+
+ local branch_color=""
+ if [ $detached = no ]; then
+ branch_color="$ok_color"
+ else
+ branch_color="$bad_color"
+ fi
+ c="$branch_color$c"
+
+ z="$c_clear$z"
+ if [ "$w" = "*" ]; then
+ w="$bad_color$w"
+ fi
+ if [ -n "$i" ]; then
+ i="$ok_color$i"
+ fi
+ if [ -n "$s" ]; then
+ s="$flags_color$s"
+ fi
+ if [ -n "$u" ]; then
+ u="$bad_color$u"
+ fi
+ r="$c_clear$r"
+}
# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
# when called from PS1 using command substitution
@@ -254,48 +281,83 @@ __git_ps1 ()
;;
esac
- local g="$(__gitdir)"
- if [ -z "$g" ]; then
+ local repo_info rev_parse_exit_code
+ repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
+ --is-bare-repository --is-inside-work-tree \
+ --short HEAD 2>/dev/null)"
+ rev_parse_exit_code="$?"
+
+ if [ -z "$repo_info" ]; then
if [ $pcmode = yes ]; then
#In PC mode PS1 always needs to be set
PS1="$ps1pc_start$ps1pc_end"
fi
+ return
+ fi
+
+ local short_sha
+ if [ "$rev_parse_exit_code" = "0" ]; then
+ short_sha="${repo_info##*$'\n'}"
+ repo_info="${repo_info%$'\n'*}"
+ fi
+ local inside_worktree="${repo_info##*$'\n'}"
+ repo_info="${repo_info%$'\n'*}"
+ local bare_repo="${repo_info##*$'\n'}"
+ repo_info="${repo_info%$'\n'*}"
+ local inside_gitdir="${repo_info##*$'\n'}"
+ local g="${repo_info%$'\n'*}"
+
+ local r=""
+ local b=""
+ local step=""
+ local total=""
+ if [ -d "$g/rebase-merge" ]; then
+ read b 2>/dev/null <"$g/rebase-merge/head-name"
+ read step 2>/dev/null <"$g/rebase-merge/msgnum"
+ read total 2>/dev/null <"$g/rebase-merge/end"
+ if [ -f "$g/rebase-merge/interactive" ]; then
+ r="|REBASE-i"
+ else
+ r="|REBASE-m"
+ fi
else
- local r=""
- local b=""
- local step=""
- local total=""
- if [ -d "$g/rebase-merge" ]; then
- b="$(cat "$g/rebase-merge/head-name")"
- step=$(cat "$g/rebase-merge/msgnum")
- total=$(cat "$g/rebase-merge/end")
- if [ -f "$g/rebase-merge/interactive" ]; then
- r="|REBASE-i"
+ if [ -d "$g/rebase-apply" ]; then
+ read step 2>/dev/null <"$g/rebase-apply/next"
+ read total 2>/dev/null <"$g/rebase-apply/last"
+ if [ -f "$g/rebase-apply/rebasing" ]; then
+ read b 2>/dev/null <"$g/rebase-apply/head-name"
+ r="|REBASE"
+ elif [ -f "$g/rebase-apply/applying" ]; then
+ r="|AM"
else
- r="|REBASE-m"
+ 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/REVERT_HEAD" ]; then
+ r="|REVERTING"
+ elif [ -f "$g/BISECT_LOG" ]; then
+ r="|BISECTING"
+ fi
+
+ if [ -n "$b" ]; then
+ :
+ elif [ -h "$g/HEAD" ]; then
+ # symlink symbolic ref
+ b="$(git symbolic-ref HEAD 2>/dev/null)"
else
- if [ -d "$g/rebase-apply" ]; then
- step=$(cat "$g/rebase-apply/next")
- total=$(cat "$g/rebase-apply/last")
- if [ -f "$g/rebase-apply/rebasing" ]; then
- r="|REBASE"
- elif [ -f "$g/rebase-apply/applying" ]; then
- r="|AM"
- else
- r="|AM/REBASE"
+ local head=""
+ if ! read head 2>/dev/null <"$g/HEAD"; then
+ if [ $pcmode = yes ]; then
+ PS1="$ps1pc_start$ps1pc_end"
fi
- elif [ -f "$g/MERGE_HEAD" ]; then
- r="|MERGING"
- elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
- r="|CHERRY-PICKING"
- elif [ -f "$g/REVERT_HEAD" ]; then
- r="|REVERTING"
- elif [ -f "$g/BISECT_LOG" ]; then
- r="|BISECTING"
+ return
fi
-
- b="$(git symbolic-ref HEAD 2>/dev/null)" || {
+ # is it a symbolic ref?
+ b="${head#ref: }"
+ if [ "$head" = "$b" ]; then
detached=yes
b="$(
case "${GIT_PS1_DESCRIBE_STYLE-}" in
@@ -309,104 +371,75 @@ __git_ps1 ()
git describe --tags --exact-match HEAD ;;
esac 2>/dev/null)" ||
- b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
- b="unknown"
+ b="$short_sha..."
b="($b)"
- }
+ fi
fi
+ fi
- if [ -n "$step" ] && [ -n "$total" ]; then
- r="$r $step/$total"
- fi
+ if [ -n "$step" ] && [ -n "$total" ]; then
+ r="$r $step/$total"
+ fi
- local w=""
- local i=""
- local s=""
- local u=""
- local c=""
- local p=""
+ 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:"
+ if [ "true" = "$inside_gitdir" ]; then
+ if [ "true" = "$bare_repo" ]; then
+ c="BARE:"
+ else
+ b="GIT_DIR!"
+ fi
+ elif [ "true" = "$inside_worktree" ]; then
+ if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
+ [ "$(git config --bool bash.showDirtyState)" != "false" ]
+ then
+ git diff --no-ext-diff --quiet --exit-code || w="*"
+ if [ -n "$short_sha" ]; then
+ git diff-index --cached --quiet HEAD -- || i="+"
else
- b="GIT_DIR!"
- fi
- elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
- if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
- [ "$(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
- if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
- git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+ i="#"
fi
+ fi
+ if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] &&
+ [ -r "$g/refs/stash" ]; then
+ s="$"
+ fi
- if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
- [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
- [ -n "$(git ls-files --others --exclude-standard)" ]
- then
- u="%${ZSH_VERSION+%}"
- fi
+ if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
+ [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
+ git ls-files --others --exclude-standard --error-unmatch -- '*' >/dev/null 2>/dev/null
+ then
+ u="%${ZSH_VERSION+%}"
+ fi
- if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
- __git_ps1_show_upstream
- fi
+ if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+ __git_ps1_show_upstream
fi
+ fi
- local f="$w$i$s$u"
- if [ $pcmode = yes ]; then
- local gitstring=
- if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
- local c_red='\e[31m'
- local c_green='\e[32m'
- local c_lblue='\e[1;34m'
- local c_clear='\e[0m'
- local bad_color=$c_red
- local ok_color=$c_green
- local branch_color="$c_clear"
- local flags_color="$c_lblue"
- local branchstring="$c${b##refs/heads/}"
+ local z="${GIT_PS1_STATESEPARATOR-" "}"
- if [ $detached = no ]; then
- branch_color="$ok_color"
- else
- branch_color="$bad_color"
- fi
+ # NO color option unless in PROMPT_COMMAND mode
+ if [ $pcmode = yes ] && [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
+ __git_ps1_colorize_gitstring
+ fi
- # Setting gitstring directly with \[ and \] around colors
- # is necessary to prevent wrapping issues!
- gitstring="\[$branch_color\]$branchstring\[$c_clear\]"
+ local f="$w$i$s$u"
+ local gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p"
- if [ -n "$w$i$s$u$r$p" ]; then
- gitstring="$gitstring "
- fi
- if [ "$w" = "*" ]; then
- gitstring="$gitstring\[$bad_color\]$w"
- fi
- if [ -n "$i" ]; then
- gitstring="$gitstring\[$ok_color\]$i"
- fi
- if [ -n "$s" ]; then
- gitstring="$gitstring\[$flags_color\]$s"
- fi
- if [ -n "$u" ]; then
- gitstring="$gitstring\[$bad_color\]$u"
- fi
- gitstring="$gitstring\[$c_clear\]$r$p"
- else
- gitstring="$c${b##refs/heads/}${f:+ $f}$r$p"
- fi
+ if [ $pcmode = yes ]; then
+ if [[ -n ${ZSH_VERSION-} ]]; then
gitstring=$(printf -- "$printf_format" "$gitstring")
- PS1="$ps1pc_start$gitstring$ps1pc_end"
else
- # NO color option unless in PROMPT_COMMAND mode
- printf -- "$printf_format" "$c${b##refs/heads/}${f:+ $f}$r$p"
+ printf -v gitstring -- "$printf_format" "$gitstring"
fi
+ PS1="$ps1pc_start$gitstring$ps1pc_end"
+ else
+ printf -- "$printf_format" "$gitstring"
fi
}
diff --git a/contrib/continuous/cidaemon b/contrib/continuous/cidaemon
deleted file mode 100644
index 4009a15..0000000
--- a/contrib/continuous/cidaemon
+++ /dev/null
@@ -1,503 +0,0 @@
-#!/usr/bin/perl
-#
-# A daemon that waits for update events sent by its companion
-# post-receive-cinotify hook, checks out a new copy of source,
-# compiles it, and emails the guilty parties if the compile
-# (and optionally test suite) fails.
-#
-# To use this daemon, configure it and run it. It will disconnect
-# from your terminal and fork into the background. The daemon must
-# have local filesystem access to the source repositories, as it
-# uses objects/info/alternates to avoid copying objects.
-#
-# Add its companion post-receive-cinotify hook as the post-receive
-# hook to each repository that the daemon should monitor. Yes, a
-# single daemon can monitor more than one repository.
-#
-# To use multiple daemons on the same system, give them each a
-# unique queue file and tmpdir.
-#
-# Global Config
-# -------------
-# Reads from a Git style configuration file. This will be
-# ~/.gitconfig by default but can be overridden by setting
-# the GIT_CONFIG_FILE environment variable before starting.
-#
-# cidaemon.smtpHost
-# Hostname of the SMTP server the daemon will send email
-# through. Defaults to 'localhost'.
-#
-# cidaemon.smtpUser
-# Username to authenticate to the SMTP server as. This
-# variable is optional; if it is not supplied then no
-# authentication will be performed.
-#
-# cidaemon.smtpPassword
-# Password to authenticate to the SMTP server as. This
-# variable is optional. If not supplied but smtpUser was,
-# the daemon prompts for the password before forking into
-# the background.
-#
-# cidaemon.smtpAuth
-# Type of authentication to perform with the SMTP server.
-# If set to 'login' and smtpUser was defined, this will
-# use the AUTH LOGIN command, which is suitable for use
-# with at least one version of Microsoft Exchange Server.
-# If not set the daemon will use whatever auth methods
-# are supported by your version of Net::SMTP.
-#
-# cidaemon.email
-# Email address that daemon generated emails will be sent
-# from. This should be a useful email address within your
-# organization. Required.
-#
-# cidaemon.name
-# Human friendly name that the daemon will send emails as.
-# Defaults to 'cidaemon'.
-#
-# cidaemon.scanDelay
-# Number of seconds to sleep between polls of the queue file.
-# Defaults to 60.
-#
-# cidaemon.recentCache
-# Number of recent commit SHA-1s per repository to cache and
-# skip building if they appear again. This is useful to avoid
-# rebuilding the same commit multiple times just because it was
-# pushed into more than one branch. Defaults to 100.
-#
-# cidaemon.tmpdir
-# Scratch directory to create the builds within. The daemon
-# makes a new subdirectory for each build, then deletes it when
-# the build has finished. The pid file is also placed here.
-# Defaults to '/tmp'.
-#
-# cidaemon.queue
-# Path to the queue file that the post-receive-cinotify hook
-# appends events to. This file is polled by the daemon. It
-# must not be on an NFS mount (uses flock). Required.
-#
-# cidaemon.nocc
-# Perl regex patterns to match against author and committer
-# lines. If a pattern matches, that author or committer will
-# not be notified of a build failure.
-#
-# Per Repository Config
-# ----------------------
-# Read from the source repository's config file.
-#
-# builder.command
-# Shell command to execute the build. This command must
-# return 0 on "success" and non-zero on failure. If you
-# also want to run a test suite, make sure your command
-# does that too. Required.
-#
-# builder.queue
-# Queue file to notify the cidaemon through. Should match
-# cidaemon.queue. If not set the hook will not notify the
-# cidaemon.
-#
-# builder.skip
-# Perl regex patterns of refs that should not be sent to
-# cidaemon. Updates of these refs will be ignored.
-#
-# builder.newBranchBase
-# Glob patterns of refs that should be used to form the
-# 'old' revions of a newly created ref. This should set
-# to be globs that match your 'mainline' branches. This
-# way a build failure of a brand new topic branch does not
-# attempt to email everyone since the beginning of time;
-# instead it only emails those authors of commits not in
-# these 'mainline' branches.
-
-local $ENV{PATH} = join ':', qw(
- /opt/git/bin
- /usr/bin
- /bin
- );
-
-use strict;
-use warnings;
-use FindBin qw($RealBin);
-use File::Spec;
-use lib File::Spec->catfile($RealBin, '..', 'perl5');
-use Storable qw(retrieve nstore);
-use Fcntl ':flock';
-use POSIX qw(strftime);
-use Getopt::Long qw(:config no_auto_abbrev auto_help);
-
-sub git_config ($;$)
-{
- my $var = shift;
- my $required = shift || 0;
- local *GIT;
- open GIT, '-|','git','config','--get',$var;
- my $r = <GIT>;
- chop $r if $r;
- close GIT;
- die "error: $var not set.\n" if ($required && !$r);
- return $r;
-}
-
-package EXCHANGE_NET_SMTP;
-
-# Microsoft Exchange Server requires an 'AUTH LOGIN'
-# style of authentication. This is different from
-# the default supported by Net::SMTP so we subclass
-# and override the auth method to support that.
-
-use Net::SMTP;
-use Net::Cmd;
-use MIME::Base64 qw(encode_base64);
-our @ISA = qw(Net::SMTP);
-our $auth_type = ::git_config 'cidaemon.smtpAuth';
-
-sub new
-{
- my $self = shift;
- my $type = ref($self) || $self;
- $type->SUPER::new(@_);
-}
-
-sub auth
-{
- my $self = shift;
- return $self->SUPER::auth(@_) unless $auth_type eq 'login';
-
- my $user = encode_base64 shift, '';
- my $pass = encode_base64 shift, '';
- return 0 unless CMD_MORE == $self->command("AUTH LOGIN")->response;
- return 0 unless CMD_MORE == $self->command($user)->response;
- CMD_OK == $self->command($pass)->response;
-}
-
-package main;
-
-my ($debug_flag, %recent);
-
-my $ex_host = git_config('cidaemon.smtpHost') || 'localhost';
-my $ex_user = git_config('cidaemon.smtpUser');
-my $ex_pass = git_config('cidaemon.smtpPassword');
-
-my $ex_from_addr = git_config('cidaemon.email', 1);
-my $ex_from_name = git_config('cidaemon.name') || 'cidaemon';
-
-my $scan_delay = git_config('cidaemon.scanDelay') || 60;
-my $recent_size = git_config('cidaemon.recentCache') || 100;
-my $tmpdir = git_config('cidaemon.tmpdir') || '/tmp';
-my $queue_name = git_config('cidaemon.queue', 1);
-my $queue_lock = "$queue_name.lock";
-
-my @nocc_list;
-open GIT,'git config --get-all cidaemon.nocc|';
-while (<GIT>) {
- chop;
- push @nocc_list, $_;
-}
-close GIT;
-
-sub nocc_author ($)
-{
- local $_ = shift;
- foreach my $pat (@nocc_list) {
- return 1 if /$pat/;
- }
- 0;
-}
-
-sub input_echo ($)
-{
- my $prompt = shift;
-
- local $| = 1;
- print $prompt;
- my $input = <STDIN>;
- chop $input;
- return $input;
-}
-
-sub input_noecho ($)
-{
- my $prompt = shift;
-
- my $end = sub {system('stty','echo');print "\n";exit};
- local $SIG{TERM} = $end;
- local $SIG{INT} = $end;
- system('stty','-echo');
-
- local $| = 1;
- print $prompt;
- my $input = <STDIN>;
- system('stty','echo');
- print "\n";
- chop $input;
- return $input;
-}
-
-sub rfc2822_date ()
-{
- strftime("%a, %d %b %Y %H:%M:%S %Z", localtime);
-}
-
-sub send_email ($$$)
-{
- my ($subj, $body, $to) = @_;
- my $now = rfc2822_date;
- my $to_str = '';
- my @rcpt_to;
- foreach (@$to) {
- my $s = $_;
- $s =~ s/^/"/;
- $s =~ s/(\s+<)/"$1/;
- $to_str .= ', ' if $to_str;
- $to_str .= $s;
- push @rcpt_to, $1 if $s =~ /<(.*)>/;
- }
- die "Nobody to send to.\n" unless @rcpt_to;
- my $msg = <<EOF;
-From: "$ex_from_name" <$ex_from_addr>
-To: $to_str
-Date: $now
-Subject: $subj
-
-$body
-EOF
-
- my $smtp = EXCHANGE_NET_SMTP->new(Host => $ex_host)
- or die "Cannot connect to $ex_host: $!\n";
- if ($ex_user && $ex_pass) {
- $smtp->auth($ex_user,$ex_pass)
- or die "$ex_host rejected $ex_user\n";
- }
- $smtp->mail($ex_from_addr)
- or die "$ex_host rejected $ex_from_addr\n";
- scalar($smtp->recipient(@rcpt_to, { SkipBad => 1 }))
- or die "$ex_host did not accept any addresses.\n";
- $smtp->data($msg)
- or die "$ex_host rejected message data\n";
- $smtp->quit;
-}
-
-sub pop_queue ()
-{
- open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
- flock LOCK, LOCK_EX;
-
- my $queue = -f $queue_name ? retrieve $queue_name : [];
- my $ent = shift @$queue;
- nstore $queue, $queue_name;
-
- flock LOCK, LOCK_UN;
- close LOCK;
- $ent;
-}
-
-sub git_exec (@)
-{
- system('git',@_) == 0 or die "Cannot git " . join(' ', @_) . "\n";
-}
-
-sub git_val (@)
-{
- open(C, '-|','git',@_);
- my $r = <C>;
- chop $r if $r;
- close C;
- $r;
-}
-
-sub do_build ($$)
-{
- my ($git_dir, $new) = @_;
-
- my $tmp = File::Spec->catfile($tmpdir, "builder$$");
- system('rm','-rf',$tmp) == 0 or die "Cannot clear $tmp\n";
- die "Cannot clear $tmp.\n" if -e $tmp;
-
- my $result = 1;
- eval {
- my $command;
- {
- local $ENV{GIT_DIR} = $git_dir;
- $command = git_val 'config','builder.command';
- }
- die "No builder.command for $git_dir.\n" unless $command;
-
- git_exec 'clone','-n','-l','-s',$git_dir,$tmp;
- chmod 0700, $tmp or die "Cannot lock $tmp\n";
- chdir $tmp or die "Cannot enter $tmp\n";
-
- git_exec 'update-ref','HEAD',$new;
- git_exec 'read-tree','-m','-u','HEAD','HEAD';
- system $command;
- if ($? == -1) {
- print STDERR "failed to execute '$command': $!\n";
- $result = 1;
- } elsif ($? & 127) {
- my $sig = $? & 127;
- print STDERR "'$command' died from signal $sig\n";
- $result = 1;
- } else {
- my $r = $? >> 8;
- print STDERR "'$command' exited with $r\n" if $r;
- $result = $r;
- }
- };
- if ($@) {
- $result = 2;
- print STDERR "$@\n";
- }
-
- chdir '/';
- system('rm','-rf',$tmp);
- rmdir $tmp;
- $result;
-}
-
-sub build_failed ($$$$$)
-{
- my ($git_dir, $ref, $old, $new, $msg) = @_;
-
- $git_dir =~ m,/([^/]+)$,;
- my $repo_name = $1;
- $ref =~ s,^refs/(heads|tags)/,,;
-
- my %authors;
- my $shortlog;
- my $revstr;
- {
- local $ENV{GIT_DIR} = $git_dir;
- my @revs = ($new);
- push @revs, '--not', @$old if @$old;
- open LOG,'-|','git','rev-list','--pretty=raw',@revs;
- while (<LOG>) {
- if (s/^(author|committer) //) {
- chomp;
- s/>.*$/>/;
- $authors{$_} = 1 unless nocc_author $_;
- }
- }
- close LOG;
- open LOG,'-|','git','shortlog',@revs;
- $shortlog .= $_ while <LOG>;
- close LOG;
- $revstr = join(' ', @revs);
- }
-
- my @to = sort keys %authors;
- unless (@to) {
- print STDERR "error: No authors in $revstr\n";
- return;
- }
-
- my $subject = "[$repo_name] $ref : Build Failed";
- my $body = <<EOF;
-Project: $git_dir
-Branch: $ref
-Commits: $revstr
-
-$shortlog
-Build Output:
---------------------------------------------------------------
-$msg
-EOF
- send_email($subject, $body, \@to);
-}
-
-sub run_build ($$$$)
-{
- my ($git_dir, $ref, $old, $new) = @_;
-
- if ($debug_flag) {
- my @revs = ($new);
- push @revs, '--not', @$old if @$old;
- print "BUILDING $git_dir\n";
- print " BRANCH: $ref\n";
- print " COMMITS: ", join(' ', @revs), "\n";
- }
-
- local(*R, *W);
- pipe R, W or die "cannot pipe builder: $!";
-
- my $builder = fork();
- if (!defined $builder) {
- die "cannot fork builder: $!";
- } elsif (0 == $builder) {
- close R;
- close STDIN;open(STDIN, '/dev/null');
- open(STDOUT, '>&W');
- open(STDERR, '>&W');
- exit do_build $git_dir, $new;
- } else {
- close W;
- my $out = '';
- $out .= $_ while <R>;
- close R;
- waitpid $builder, 0;
- build_failed $git_dir, $ref, $old, $new, $out if $?;
- }
-
- print "DONE\n\n" if $debug_flag;
-}
-
-sub daemon_loop ()
-{
- my $run = 1;
- my $stop_sub = sub {$run = 0};
- $SIG{HUP} = $stop_sub;
- $SIG{INT} = $stop_sub;
- $SIG{TERM} = $stop_sub;
-
- mkdir $tmpdir, 0755;
- my $pidfile = File::Spec->catfile($tmpdir, "cidaemon.pid");
- open(O, ">$pidfile"); print O "$$\n"; close O;
-
- while ($run) {
- my $ent = pop_queue;
- if ($ent) {
- my ($git_dir, $ref, $old, $new) = @$ent;
-
- $ent = $recent{$git_dir};
- $recent{$git_dir} = $ent = [[], {}] unless $ent;
- my ($rec_arr, $rec_hash) = @$ent;
- next if $rec_hash->{$new}++;
- while (@$rec_arr >= $recent_size) {
- my $to_kill = shift @$rec_arr;
- delete $rec_hash->{$to_kill};
- }
- push @$rec_arr, $new;
-
- run_build $git_dir, $ref, $old, $new;
- } else {
- sleep $scan_delay;
- }
- }
-
- unlink $pidfile;
-}
-
-$debug_flag = 0;
-GetOptions(
- 'debug|d' => \$debug_flag,
- 'smtp-user=s' => \$ex_user,
-) or die "usage: $0 [--debug] [--smtp-user=user]\n";
-
-$ex_pass = input_noecho("$ex_user SMTP password: ")
- if ($ex_user && !$ex_pass);
-
-if ($debug_flag) {
- daemon_loop;
- exit 0;
-}
-
-my $daemon = fork();
-if (!defined $daemon) {
- die "cannot fork daemon: $!";
-} elsif (0 == $daemon) {
- close STDIN;open(STDIN, '/dev/null');
- close STDOUT;open(STDOUT, '>/dev/null');
- close STDERR;open(STDERR, '>/dev/null');
- daemon_loop;
- exit 0;
-} else {
- print "Daemon $daemon running in the background.\n";
-}
diff --git a/contrib/continuous/post-receive-cinotify b/contrib/continuous/post-receive-cinotify
deleted file mode 100644
index b8f5a60..0000000
--- a/contrib/continuous/post-receive-cinotify
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/perl
-#
-# A hook that notifies its companion cidaemon through a simple
-# queue file that a ref has been updated via a push (actually
-# by a receive-pack running on the server).
-#
-# See cidaemon for per-repository configuration details.
-#
-# To use this hook, add it as the post-receive hook, make it
-# executable, and set its configuration options.
-#
-
-local $ENV{PATH} = '/opt/git/bin';
-
-use strict;
-use warnings;
-use File::Spec;
-use Storable qw(retrieve nstore);
-use Fcntl ':flock';
-
-my $git_dir = File::Spec->rel2abs($ENV{GIT_DIR});
-my $queue_name = `git config --get builder.queue`;chop $queue_name;
-$queue_name =~ m,^([^\s]+)$,; $queue_name = $1; # untaint
-unless ($queue_name) {
- 1 while <STDIN>;
- print STDERR "\nerror: builder.queue not set. Not enqueing.\n\n";
- exit;
-}
-my $queue_lock = "$queue_name.lock";
-
-my @skip;
-open S, "git config --get-all builder.skip|";
-while (<S>) {
- chop;
- push @skip, $_;
-}
-close S;
-
-my @new_branch_base;
-open S, "git config --get-all builder.newBranchBase|";
-while (<S>) {
- chop;
- push @new_branch_base, $_;
-}
-close S;
-
-sub skip ($)
-{
- local $_ = shift;
- foreach my $p (@skip) {
- return 1 if /^$p/;
- }
- 0;
-}
-
-open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
-flock LOCK, LOCK_EX;
-
-my $queue = -f $queue_name ? retrieve $queue_name : [];
-my %existing;
-foreach my $r (@$queue) {
- my ($gd, $ref) = @$r;
- $existing{$gd}{$ref} = $r;
-}
-
-my @new_branch_commits;
-my $loaded_new_branch_commits = 0;
-
-while (<STDIN>) {
- chop;
- my ($old, $new, $ref) = split / /, $_, 3;
-
- next if $old eq $new;
- next if $new =~ /^0{40}$/;
- next if skip $ref;
-
- my $r = $existing{$git_dir}{$ref};
- if ($r) {
- $r->[3] = $new;
- } else {
- if ($old =~ /^0{40}$/) {
- if (!$loaded_new_branch_commits && @new_branch_base) {
- open M,'-|','git','show-ref',@new_branch_base;
- while (<M>) {
- ($_) = split / /, $_;
- push @new_branch_commits, $_;
- }
- close M;
- $loaded_new_branch_commits = 1;
- }
- $old = [@new_branch_commits];
- } else {
- $old = [$old];
- }
-
- $r = [$git_dir, $ref, $old, $new];
- $existing{$git_dir}{$ref} = $r;
- push @$queue, $r;
- }
-}
-nstore $queue, $queue_name;
-
-flock LOCK, LOCK_UN;
-close LOCK;
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
index 3940202..bcd3f57 100644
--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -127,10 +127,20 @@ static void read_credential(void)
*v++ = '\0';
if (!strcmp(buf, "protocol")) {
- if (!strcmp(v, "https"))
+ if (!strcmp(v, "imap"))
+ protocol = kSecProtocolTypeIMAP;
+ else if (!strcmp(v, "imaps"))
+ protocol = kSecProtocolTypeIMAPS;
+ else if (!strcmp(v, "ftp"))
+ protocol = kSecProtocolTypeFTP;
+ else if (!strcmp(v, "ftps"))
+ protocol = kSecProtocolTypeFTPS;
+ else if (!strcmp(v, "https"))
protocol = kSecProtocolTypeHTTPS;
else if (!strcmp(v, "http"))
protocol = kSecProtocolTypeHTTP;
+ else if (!strcmp(v, "smtp"))
+ protocol = kSecProtocolTypeSMTP;
else /* we don't yet handle other protocols */
exit(0);
}
diff --git a/contrib/mw-to-git/.perlcriticrc b/contrib/mw-to-git/.perlcriticrc
new file mode 100644
index 0000000..5a9955d
--- /dev/null
+++ b/contrib/mw-to-git/.perlcriticrc
@@ -0,0 +1,28 @@
+# These 3 rules demand to add the s, m and x flag to *every* regexp. This is
+# overkill and would be harmful for readability.
+[-RegularExpressions::RequireExtendedFormatting]
+[-RegularExpressions::RequireDotMatchAnything]
+[-RegularExpressions::RequireLineBoundaryMatching]
+
+# This rule says that builtin functions should not be called with parentheses
+# e.g.: (taken from CPAN's documentation)
+# open($handle, '>', $filename); #not ok
+# open $handle, '>', $filename; #ok
+# Applying such a rule would mean modifying a huge number of lines for a
+# question of style.
+[-CodeLayout::ProhibitParensWithBuiltins]
+
+# This rule states that each system call should have its return value checked
+# The problem is that it includes the print call. Checking every print call's
+# return value would be harmful to the code readabilty.
+# This configuration keeps all default function but print.
+[InputOutput::RequireCheckedSyscalls]
+functions = open say close
+
+# This rules demands to add a dependancy for the Readonly module. This is not
+# wished.
+[-ValuesAndExpressions::ProhibitConstantPragma]
+
+# This rule is not really useful (rather a question of style) and produces many
+# warnings among the code.
+[-ValuesAndExpressions::ProhibitNoisyQuotes]
diff --git a/contrib/mw-to-git/Git/Mediawiki.pm b/contrib/mw-to-git/Git/Mediawiki.pm
new file mode 100644
index 0000000..d13c4df
--- /dev/null
+++ b/contrib/mw-to-git/Git/Mediawiki.pm
@@ -0,0 +1,100 @@
+package Git::Mediawiki;
+
+use 5.008;
+use strict;
+use Git;
+
+BEGIN {
+
+our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);
+
+# Totally unstable API.
+$VERSION = '0.01';
+
+require Exporter;
+
+@ISA = qw(Exporter);
+
+@EXPORT = ();
+
+# Methods which can be called as standalone functions as well:
+@EXPORT_OK = qw(clean_filename smudge_filename connect_maybe
+ EMPTY HTTP_CODE_OK HTTP_CODE_PAGE_NOT_FOUND);
+}
+
+# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
+use constant SLASH_REPLACEMENT => '%2F';
+
+# Used to test for empty strings
+use constant EMPTY => q{};
+
+# HTTP codes
+use constant HTTP_CODE_OK => 200;
+use constant HTTP_CODE_PAGE_NOT_FOUND => 404;
+
+sub 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 smudge_filename {
+ my $filename = shift;
+ $filename =~ s{/}{@{[SLASH_REPLACEMENT]}}g;
+ $filename =~ s/ /_/g;
+ # Decode forbidden characters encoded in clean_filename
+ $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf('%c', hex($1))/ge;
+ return $filename;
+}
+
+sub connect_maybe {
+ my $wiki = shift;
+ if ($wiki) {
+ return $wiki;
+ }
+
+ my $remote_name = shift;
+ my $remote_url = shift;
+ my ($wiki_login, $wiki_password, $wiki_domain);
+
+ $wiki_login = Git::config("remote.${remote_name}.mwLogin");
+ $wiki_password = Git::config("remote.${remote_name}.mwPassword");
+ $wiki_domain = Git::config("remote.${remote_name}.mwDomain");
+
+ $wiki = MediaWiki::API->new;
+ $wiki->{config}->{api_url} = "${remote_url}/api.php";
+ if ($wiki_login) {
+ my %credential = (
+ 'url' => $remote_url,
+ 'username' => $wiki_login,
+ 'password' => $wiki_password
+ );
+ Git::credential(\%credential);
+ my $request = {lgname => $credential{username},
+ lgpassword => $credential{password},
+ lgdomain => $wiki_domain};
+ if ($wiki->login($request)) {
+ Git::credential(\%credential, 'approve');
+ print {*STDERR} qq(Logged in mediawiki user "$credential{username}".\n);
+ } else {
+ print {*STDERR} qq(Failed to log in mediawiki user "$credential{username}" on ${remote_url}\n);
+ print {*STDERR} ' (error ' .
+ $wiki->{error}->{code} . ': ' .
+ $wiki->{error}->{details} . ")\n";
+ Git::credential(\%credential, 'reject');
+ exit 1;
+ }
+ }
+
+ return $wiki;
+}
+
+1; # Famous last words
diff --git a/contrib/mw-to-git/Makefile b/contrib/mw-to-git/Makefile
index f149719..76fcd4d 100644
--- a/contrib/mw-to-git/Makefile
+++ b/contrib/mw-to-git/Makefile
@@ -2,16 +2,43 @@
# Copyright (C) 2013
# Matthieu Moy <Matthieu.Moy@imag.fr>
#
-## Build git-remote-mediawiki
+# To build and test:
+#
+# make
+# bin-wrapper/git mw preview Some_page.mw
+# bin-wrapper/git clone mediawiki::http://example.com/wiki/
+#
+# To install, run Git's toplevel 'make install' then run:
+#
+# make install
+GIT_MEDIAWIKI_PM=Git/Mediawiki.pm
SCRIPT_PERL=git-remote-mediawiki.perl
+SCRIPT_PERL+=git-mw.perl
GIT_ROOT_DIR=../..
HERE=contrib/mw-to-git/
SCRIPT_PERL_FULL=$(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
+INSTLIBDIR=$(shell $(MAKE) -C $(GIT_ROOT_DIR)/perl \
+ -s --no-print-directory instlibdir)
all: build
-build install clean:
- $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL=$(SCRIPT_PERL_FULL) \
- $@-perl-script
+install_pm:
+ install $(GIT_MEDIAWIKI_PM) $(INSTLIBDIR)/$(GIT_MEDIAWIKI_PM)
+
+build:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ build-perl-script
+
+install: install_pm
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ install-perl-script
+
+clean:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ clean-perl-script
+ rm $(INSTLIBDIR)/$(GIT_MEDIAWIKI_PM)
+
+perlcritic:
+ perlcritic -2 *.perl
diff --git a/contrib/mw-to-git/bin-wrapper/git b/contrib/mw-to-git/bin-wrapper/git
new file mode 100755
index 0000000..6663ae5
--- /dev/null
+++ b/contrib/mw-to-git/bin-wrapper/git
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# git executable wrapper script for Git-Mediawiki to run tests without
+# installing all the scripts and perl packages.
+
+GIT_ROOT_DIR=../../..
+GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd ${GIT_ROOT_DIR} && pwd)
+
+GITPERLLIB="$GIT_EXEC_PATH"'/contrib/mw-to-git'"${GITPERLLIB:+:$GITPERLLIB}"
+PATH="$GIT_EXEC_PATH"'/contrib/mw-to-git:'"$PATH"
+
+export GITPERLLIB PATH
+
+exec "${GIT_EXEC_PATH}/bin-wrappers/git" "$@"
diff --git a/contrib/mw-to-git/git-mw.perl b/contrib/mw-to-git/git-mw.perl
new file mode 100755
index 0000000..28df3ee
--- /dev/null
+++ b/contrib/mw-to-git/git-mw.perl
@@ -0,0 +1,368 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2013
+# Benoit Person <benoit.person@ensimag.imag.fr>
+# Celestin Matte <celestin.matte@ensimag.imag.fr>
+# License: GPL v2 or later
+
+# Set of tools for git repo with a mediawiki remote.
+# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use URI::URL qw(url);
+use LWP::UserAgent;
+use HTML::TreeBuilder;
+
+use Git;
+use MediaWiki::API;
+use Git::Mediawiki qw(clean_filename connect_maybe
+ EMPTY HTTP_CODE_PAGE_NOT_FOUND);
+
+# By default, use UTF-8 to communicate with Git and the user
+binmode STDERR, ':encoding(UTF-8)';
+binmode STDOUT, ':encoding(UTF-8)';
+
+# Global parameters
+my $verbose = 0;
+sub v_print {
+ if ($verbose) {
+ return print {*STDERR} @_;
+ }
+ return;
+}
+
+# Preview parameters
+my $file_name = EMPTY;
+my $remote_name = EMPTY;
+my $preview_file_name = EMPTY;
+my $autoload = 0;
+sub file {
+ $file_name = shift;
+ return $file_name;
+}
+
+my %commands = (
+ 'help' =>
+ [\&help, {}, \&help],
+ 'preview' =>
+ [\&preview, {
+ '<>' => \&file,
+ 'output|o=s' => \$preview_file_name,
+ 'remote|r=s' => \$remote_name,
+ 'autoload|a' => \$autoload
+ }, \&preview_help]
+);
+
+# Search for sub-command
+my $cmd = $commands{'help'};
+for (0..@ARGV-1) {
+ if (defined $commands{$ARGV[$_]}) {
+ $cmd = $commands{$ARGV[$_]};
+ splice @ARGV, $_, 1;
+ last;
+ }
+};
+GetOptions( %{$cmd->[1]},
+ 'help|h' => \&{$cmd->[2]},
+ 'verbose|v' => \$verbose);
+
+# Launch command
+&{$cmd->[0]};
+
+############################# Preview Functions ################################
+
+sub preview_help {
+ print {*STDOUT} <<'END';
+USAGE: git mw preview [--remote|-r <remote name>] [--autoload|-a]
+ [--output|-o <output filename>] [--verbose|-v]
+ <blob> | <filename>
+
+DESCRIPTION:
+Preview is an utiliy to preview local content of a mediawiki repo as if it was
+pushed on the remote.
+
+For that, preview searches for the remote name of the current branch's
+upstream if --remote is not set. If that remote is not found or if it
+is not a mediawiki, it lists all mediawiki remotes configured and asks
+you to replay your command with the --remote option set properly.
+
+Then, it searches for a file named 'filename'. If it's not found in
+the current dir, it will assume it's a blob.
+
+The content retrieved in the file (or in the blob) will then be parsed
+by the remote mediawiki and combined with a template retrieved from
+the mediawiki.
+
+Finally, preview will save the HTML result in a file. and autoload it
+in your default web browser if the option --autoload is present.
+
+OPTIONS:
+ -r <remote name>, --remote <remote name>
+ If the remote is a mediawiki, the template and the parse engine
+ used for the preview will be those of that remote.
+ If not, a list of valid remotes will be shown.
+
+ -a, --autoload
+ Try to load the HTML output in a new tab (or new window) of your
+ default web browser.
+
+ -o <output filename>, --output <output filename>
+ Change the HTML output filename. Default filename is based on the
+ input filename with its extension replaced by '.html'.
+
+ -v, --verbose
+ Show more information on what's going on under the hood.
+END
+ exit;
+}
+
+sub preview {
+ my $wiki;
+ my ($remote_url, $wiki_page_name);
+ my ($new_content, $template);
+ my $file_content;
+
+ if ($file_name eq EMPTY) {
+ die "Missing file argument, see `git mw help`\n";
+ }
+
+ v_print("### Selecting remote\n");
+ if ($remote_name eq EMPTY) {
+ $remote_name = find_upstream_remote_name();
+ if ($remote_name) {
+ $remote_url = mediawiki_remote_url_maybe($remote_name);
+ }
+
+ if (! $remote_url) {
+ my @valid_remotes = find_mediawiki_remotes();
+
+ if ($#valid_remotes == 0) {
+ print {*STDERR} "No mediawiki remote in this repo. \n";
+ exit 1;
+ } else {
+ my $remotes_list = join("\n\t", @valid_remotes);
+ print {*STDERR} <<"MESSAGE";
+There are multiple mediawiki remotes, which of:
+ ${remotes_list}
+do you want ? Use the -r option to specify the remote.
+MESSAGE
+ }
+
+ exit 1;
+ }
+ } else {
+ if (!is_valid_remote($remote_name)) {
+ die "${remote_name} is not a remote\n";
+ }
+
+ $remote_url = mediawiki_remote_url_maybe($remote_name);
+ if (! $remote_url) {
+ die "${remote_name} is not a mediawiki remote\n";
+ }
+ }
+ v_print("selected remote:\n\tname: ${remote_name}\n\turl: ${remote_url}\n");
+
+ $wiki = connect_maybe($wiki, $remote_name, $remote_url);
+
+ # Read file content
+ if (! -e $file_name) {
+ $file_content = git_cmd_try {
+ Git::command('cat-file', 'blob', $file_name); }
+ "%s failed w/ code %d";
+
+ if ($file_name =~ /(.+):(.+)/) {
+ $file_name = $2;
+ }
+ } else {
+ open my $read_fh, "<", $file_name
+ or die "could not open ${file_name}: $!\n";
+ $file_content = do { local $/ = undef; <$read_fh> };
+ close $read_fh
+ or die "unable to close: $!\n";
+ }
+
+ v_print("### Retrieving template\n");
+ ($wiki_page_name = clean_filename($file_name)) =~ s/\.[^.]+$//;
+ $template = get_template($remote_url, $wiki_page_name);
+
+ v_print("### Parsing local content\n");
+ $new_content = $wiki->api({
+ action => 'parse',
+ text => $file_content,
+ title => $wiki_page_name
+ }, {
+ skip_encoding => 1
+ }) or die "No response from remote mediawiki\n";
+ $new_content = $new_content->{'parse'}->{'text'}->{'*'};
+
+ v_print("### Merging contents\n");
+ if ($preview_file_name eq EMPTY) {
+ ($preview_file_name = $file_name) =~ s/\.[^.]+$/.html/;
+ }
+ open(my $save_fh, '>:encoding(UTF-8)', $preview_file_name)
+ or die "Could not open: $!\n";
+ print {$save_fh} merge_contents($template, $new_content, $remote_url);
+ close($save_fh)
+ or die "Could not close: $!\n";
+
+ v_print("### Results\n");
+ if ($autoload) {
+ v_print("Launching browser w/ file: ${preview_file_name}");
+ system('git', 'web--browse', $preview_file_name);
+ } else {
+ print {*STDERR} "Preview file saved as: ${preview_file_name}\n";
+ }
+
+ exit;
+}
+
+# uses global scope variable: $remote_name
+sub merge_contents {
+ my $template = shift;
+ my $content = shift;
+ my $remote_url = shift;
+ my ($content_tree, $html_tree, $mw_content_text);
+ my $template_content_id = 'bodyContent';
+
+ $html_tree = HTML::TreeBuilder->new;
+ $html_tree->parse($template);
+
+ $content_tree = HTML::TreeBuilder->new;
+ $content_tree->parse($content);
+
+ $template_content_id = Git::config("remote.${remote_name}.mwIDcontent")
+ || $template_content_id;
+ v_print("Using '${template_content_id}' as the content ID\n");
+
+ $mw_content_text = $html_tree->look_down('id', $template_content_id);
+ if (!defined $mw_content_text) {
+ print {*STDERR} <<"CONFIG";
+Could not combine the new content with the template. You might want to
+configure `mediawiki.IDContent` in your config:
+ git config --add remote.${remote_name}.mwIDcontent <id>
+and re-run the command afterward.
+CONFIG
+ exit 1;
+ }
+ $mw_content_text->delete_content();
+ $mw_content_text->push_content($content_tree);
+
+ make_links_absolute($html_tree, $remote_url);
+
+ return $html_tree->as_HTML;
+}
+
+sub make_links_absolute {
+ my $html_tree = shift;
+ my $remote_url = shift;
+ for (@{ $html_tree->extract_links() }) {
+ my ($link, $element, $attr) = @{ $_ };
+ my $url = url($link)->canonical;
+ if ($url !~ /#/) {
+ $element->attr($attr, URI->new_abs($url, $remote_url));
+ }
+ }
+ return $html_tree;
+}
+
+sub is_valid_remote {
+ my $remote = shift;
+ my @remotes = git_cmd_try {
+ Git::command('remote') }
+ "%s failed w/ code %d";
+ my $found_remote = 0;
+ foreach my $remote (@remotes) {
+ if ($remote eq $remote) {
+ $found_remote = 1;
+ last;
+ }
+ }
+ return $found_remote;
+}
+
+sub find_mediawiki_remotes {
+ my @remotes = git_cmd_try {
+ Git::command('remote'); }
+ "%s failed w/ code %d";
+ my $remote_url;
+ my @valid_remotes = ();
+ foreach my $remote (@remotes) {
+ $remote_url = mediawiki_remote_url_maybe($remote);
+ if ($remote_url) {
+ push(@valid_remotes, $remote);
+ }
+ }
+ return @valid_remotes;
+}
+
+sub find_upstream_remote_name {
+ my $current_branch = git_cmd_try {
+ Git::command_oneline('symbolic-ref', '--short', 'HEAD') }
+ "%s failed w/ code %d";
+ return Git::config("branch.${current_branch}.remote");
+}
+
+sub mediawiki_remote_url_maybe {
+ my $remote = shift;
+
+ # Find remote url
+ my $remote_url = Git::config("remote.${remote}.url");
+ if ($remote_url =~ s/mediawiki::(.*)/$1/) {
+ return url($remote_url)->canonical;
+ }
+
+ return;
+}
+
+sub get_template {
+ my $url = shift;
+ my $page_name = shift;
+ my ($req, $res, $code, $url_after);
+
+ $req = LWP::UserAgent->new;
+ if ($verbose) {
+ $req->show_progress(1);
+ }
+
+ $res = $req->get("${url}/index.php?title=${page_name}");
+ if (!$res->is_success) {
+ $code = $res->code;
+ $url_after = $res->request()->uri(); # resolve all redirections
+ if ($code == HTTP_CODE_PAGE_NOT_FOUND) {
+ if ($verbose) {
+ print {*STDERR} <<"WARNING";
+Warning: Failed to retrieve '$page_name'. Create it on the mediawiki if you want
+all the links to work properly.
+Trying to use the mediawiki homepage as a fallback template ...
+WARNING
+ }
+
+ # LWP automatically redirects GET request
+ $res = $req->get("${url}/index.php");
+ if (!$res->is_success) {
+ $url_after = $res->request()->uri(); # resolve all redirections
+ die "Failed to get homepage @ ${url_after} w/ code ${code}\n";
+ }
+ } else {
+ die "Failed to get '${page_name}' @ ${url_after} w/ code ${code}\n";
+ }
+ }
+
+ return $res->decoded_content;
+}
+
+############################## Help Functions ##################################
+
+sub help {
+ print {*STDOUT} <<'END';
+usage: git mw <command> <args>
+
+git mw commands are:
+ help Display help information about git mw
+ preview Parse and render local file into HTML
+END
+ exit;
+}
diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl
index 9c14c1f..f8d7d2c 100755
--- a/contrib/mw-to-git/git-remote-mediawiki.perl
+++ b/contrib/mw-to-git/git-remote-mediawiki.perl
@@ -13,19 +13,17 @@
use strict;
use MediaWiki::API;
+use Git;
+use Git::Mediawiki qw(clean_filename smudge_filename connect_maybe
+ EMPTY HTTP_CODE_OK);
use DateTime::Format::ISO8601;
+use warnings;
# By default, use UTF-8 to communicate with Git and the user
-binmode STDERR, ":utf8";
-binmode STDOUT, ":utf8";
+binmode STDERR, ':encoding(UTF-8)';
+binmode STDOUT, ':encoding(UTF-8)';
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
# privileges). Deleted pages are replaced with this content.
@@ -36,45 +34,57 @@ use constant DELETED_CONTENT => "[[Category:Deleted]]\n";
use constant EMPTY_CONTENT => "<!-- empty page -->\n";
# used to reflect file creation or deletion in diff.
-use constant NULL_SHA1 => "0000000000000000000000000000000000000000";
+use constant NULL_SHA1 => '0000000000000000000000000000000000000000';
# Used on Git's side to reflect empty edit messages on the wiki
use constant EMPTY_MESSAGE => '*Empty MediaWiki Message*';
+# Number of pages taken into account at once in submodule get_mw_page_list
+use constant SLICE_SIZE => 50;
+
+# Number of linked mediafile to get at once in get_linked_mediafiles
+# The query is split in small batches because of the MW API limit of
+# the number of links to be returned (500 links max).
+use constant BATCH_SIZE => 10;
+
+if (@ARGV != 2) {
+ exit_error_usage();
+}
+
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"));
+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"));
+my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.${remotename}.categories"));
chomp(@tracked_categories);
# Import media files on pull
-my $import_media = run_git("config --get --bool remote.". $remotename .".mediaimport");
+my $import_media = run_git("config --get --bool remote.${remotename}.mediaimport");
chomp($import_media);
-$import_media = ($import_media eq "true");
+$import_media = ($import_media eq 'true');
# Export media files on push
-my $export_media = run_git("config --get --bool remote.". $remotename .".mediaexport");
+my $export_media = run_git("config --get --bool remote.${remotename}.mediaexport");
chomp($export_media);
-$export_media = !($export_media eq "false");
+$export_media = !($export_media eq 'false');
-my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin");
+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");
+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");
+my $shallow_import = run_git("config --get --bool remote.${remotename}.shallow");
chomp($shallow_import);
-$shallow_import = ($shallow_import eq "true");
+$shallow_import = ($shallow_import eq 'true');
# Fetch (clone and pull) by revisions instead of by pages. This behavior
# is more efficient when we have a wiki with lots of pages and we fetch
@@ -82,15 +92,18 @@ $shallow_import = ($shallow_import eq "true");
# Possible values:
# - by_rev: perform one query per new revision on the remote wiki
# - by_page: query each tracked page for new revision
-my $fetch_strategy = run_git("config --get remote.$remotename.fetchStrategy");
-unless ($fetch_strategy) {
- $fetch_strategy = run_git("config --get mediawiki.fetchStrategy");
+my $fetch_strategy = run_git("config --get remote.${remotename}.fetchStrategy");
+if (!$fetch_strategy) {
+ $fetch_strategy = run_git('config --get mediawiki.fetchStrategy');
}
chomp($fetch_strategy);
-unless ($fetch_strategy) {
- $fetch_strategy = "by_page";
+if (!$fetch_strategy) {
+ $fetch_strategy = 'by_page';
}
+# Remember the timestamp corresponding to a revision id.
+my %basetimestamps;
+
# Dumb push: don't update notes and mediawiki ref to reflect the last push.
#
# Configurable with mediawiki.dumbPush, or per-remote with
@@ -105,48 +118,25 @@ unless ($fetch_strategy) {
# 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");
+my $dumb_push = run_git("config --get --bool remote.${remotename}.dumbPush");
+if (!$dumb_push) {
+ $dumb_push = run_git('config --get --bool mediawiki.dumbPush');
}
chomp($dumb_push);
-$dumb_push = ($dumb_push eq "true");
+$dumb_push = ($dumb_push eq 'true');
my $wiki_name = $url;
-$wiki_name =~ s/[^\/]*:\/\///;
+$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
+
+ if (!parse_command($_)) {
last;
}
@@ -156,107 +146,91 @@ while (<STDIN>) {
########################## Functions ##############################
-## credential API management (generic functions)
-
-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;
+## error handling
+sub exit_error_usage {
+ die "ERROR: git-remote-mediawiki module was not called with a correct number of\n" .
+ "parameters\n" .
+ "You may obtain this error because you attempted to run the git-remote-mediawiki\n" .
+ "module directly.\n" .
+ "This module can be used the following way:\n" .
+ "\tgit clone mediawiki://<address of a mediawiki>\n" .
+ "Then, use git commit, push and pull as with every normal git repository.\n";
}
-sub credential_write {
- my $credential = shift;
- my $writer = shift;
- # url overwrites other fields, so it must come first
- print $writer "url=$credential->{url}\n" if exists $credential->{url};
- while (my ($key, $value) = each(%$credential) ) {
- if (length $value && $key ne 'url') {
- print $writer "$key=$value\n";
- }
+sub parse_command {
+ my ($line) = @_;
+ my @cmd = split(/ /, $line);
+ if (!defined $cmd[0]) {
+ return 0;
}
-}
-
-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);
+ if ($cmd[0] eq 'capabilities') {
+ die("Too many arguments for capabilities\n")
+ if (defined($cmd[1]));
+ mw_capabilities();
+ } elsif ($cmd[0] eq 'list') {
+ die("Too many arguments for list\n") if (defined($cmd[2]));
+ mw_list($cmd[1]);
+ } elsif ($cmd[0] eq 'import') {
+ die("Invalid argument for import\n")
+ if ($cmd[1] eq EMPTY);
+ die("Too many arguments for import\n")
+ if (defined($cmd[2]));
+ mw_import($cmd[1]);
+ } elsif ($cmd[0] eq 'option') {
+ die("Invalid arguments for option\n")
+ if ($cmd[1] eq EMPTY || $cmd[2] eq EMPTY);
+ die("Too many arguments for option\n")
+ if (defined($cmd[3]));
+ mw_option($cmd[1],$cmd[2]);
+ } elsif ($cmd[0] eq 'push') {
+ mw_push($cmd[1]);
} 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.";
+ print {*STDERR} "Unknown command. Aborting...\n";
+ return 0;
}
+ return 1;
}
# 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 = (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 fatal_mw_error {
+ my $action = shift;
+ print STDERR "fatal: could not $action.\n";
+ print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+ if ($url =~ /^https/) {
+ print STDERR "fatal: make sure '$url/api.php' is a valid page\n";
+ print STDERR "fatal: and the SSL certificate is correct.\n";
+ } else {
+ print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
}
+ print STDERR "fatal: (error " .
+ $mediawiki->{error}->{code} . ': ' .
+ $mediawiki->{error}->{details} . ")\n";
+ exit 1;
}
## Functions for listing pages on the remote wiki
sub get_mw_tracked_pages {
my $pages = shift;
get_mw_page_list(\@tracked_pages, $pages);
+ return;
}
sub get_mw_page_list {
my $page_list = shift;
my $pages = shift;
- my @some_pages = @$page_list;
+ my @some_pages = @{$page_list};
while (@some_pages) {
- my $last = 50;
- if ($#some_pages < $last) {
- $last = $#some_pages;
+ my $last_page = SLICE_SIZE;
+ if ($#some_pages < $last_page) {
+ $last_page = $#some_pages;
}
- my @slice = @some_pages[0..$last];
+ my @slice = @some_pages[0..$last_page];
get_mw_first_pages(\@slice, $pages);
- @some_pages = @some_pages[51..$#some_pages];
+ @some_pages = @some_pages[(SLICE_SIZE + 1)..$#some_pages];
}
+ return;
}
sub get_mw_tracked_categories {
@@ -266,7 +240,7 @@ sub get_mw_tracked_categories {
# Mediawiki requires the Category
# prefix, but let's not force the user
# to specify it.
- $category = "Category:" . $category;
+ $category = "Category:${category}";
}
my $mw_pages = $mediawiki->list( {
action => 'query',
@@ -274,11 +248,12 @@ sub get_mw_tracked_categories {
cmtitle => $category,
cmlimit => 'max' } )
|| die $mediawiki->{error}->{code} . ': '
- . $mediawiki->{error}->{details};
+ . $mediawiki->{error}->{details} . "\n";
foreach my $page (@{$mw_pages}) {
$pages->{$page->{title}} = $page;
}
}
+ return;
}
sub get_mw_all_pages {
@@ -290,14 +265,12 @@ sub get_mw_all_pages {
aplimit => 'max'
});
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;
+ fatal_mw_error("get the list of wiki pages");
}
foreach my $page (@{$mw_pages}) {
$pages->{$page->{title}} = $page;
}
+ return;
}
# queries the wiki for a set of pages. Meant to be used within a loop
@@ -316,25 +289,23 @@ sub get_mw_first_pages {
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;
+ fatal_mw_error("query the list of wiki pages");
}
while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) {
if ($id < 0) {
- print STDERR "Warning: page $page->{title} not found on wiki\n";
+ print {*STDERR} "Warning: page $page->{title} not found on wiki\n";
} else {
$pages->{$page->{title}} = $page;
}
}
+ return;
}
# Get the list of pages to be fetched according to configuration.
sub get_mw_pages {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
- print STDERR "Listing pages on remote wiki...\n";
+ print {*STDERR} "Listing pages on remote wiki...\n";
my %pages; # hash on page titles to avoid duplicates
my $user_defined;
@@ -352,14 +323,14 @@ sub get_mw_pages {
get_mw_all_pages(\%pages);
}
if ($import_media) {
- print STDERR "Getting media files for selected pages...\n";
+ print {*STDERR} "Getting media files for selected pages...\n";
if ($user_defined) {
get_linked_mediafiles(\%pages);
} else {
get_all_mediafiles(\%pages);
}
}
- print STDERR (scalar keys %pages) . " pages found.\n";
+ print {*STDERR} (scalar keys %pages) . " pages found.\n";
return %pages;
}
@@ -367,9 +338,13 @@ sub get_mw_pages {
# $out = run_git("command args", "raw"); # don't interpret output as UTF-8.
sub run_git {
my $args = shift;
- my $encoding = (shift || "encoding(UTF-8)");
- open(my $git, "-|:$encoding", "git " . $args);
- my $res = do { local $/; <$git> };
+ my $encoding = (shift || 'encoding(UTF-8)');
+ open(my $git, "-|:${encoding}", "git ${args}")
+ or die "Unable to fork: $!\n";
+ my $res = do {
+ local $/ = undef;
+ <$git>
+ };
close($git);
return $res;
@@ -384,27 +359,26 @@ sub get_all_mediafiles {
my $mw_pages = $mediawiki->list({
action => 'query',
list => 'allpages',
- apnamespace => get_mw_namespace_id("File"),
+ apnamespace => get_mw_namespace_id('File'),
aplimit => 'max'
});
if (!defined($mw_pages)) {
- print STDERR "fatal: could not get the list of pages for media files.\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";
+ print {*STDERR} "fatal: could not get the list of pages for media files.\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;
}
sub get_linked_mediafiles {
my $pages = shift;
- my @titles = map $_->{title}, values(%{$pages});
+ my @titles = map { $_->{title} } values(%{$pages});
- # The query is split in small batches because of the MW API limit of
- # the number of links to be returned (500 links max).
- my $batch = 10;
+ my $batch = BATCH_SIZE;
while (@titles) {
if ($#titles < $batch) {
$batch = $#titles;
@@ -420,7 +394,7 @@ sub get_linked_mediafiles {
action => 'query',
prop => 'links|images',
titles => $mw_titles,
- plnamespace => get_mw_namespace_id("File"),
+ plnamespace => get_mw_namespace_id('File'),
pllimit => 'max'
};
my $result = $mediawiki->api($query);
@@ -428,11 +402,13 @@ sub get_linked_mediafiles {
while (my ($id, $page) = each(%{$result->{query}->{pages}})) {
my @media_titles;
if (defined($page->{links})) {
- my @link_titles = map $_->{title}, @{$page->{links}};
+ my @link_titles
+ = map { $_->{title} } @{$page->{links}};
push(@media_titles, @link_titles);
}
if (defined($page->{images})) {
- my @image_titles = map $_->{title}, @{$page->{images}};
+ my @image_titles
+ = map { $_->{title} } @{$page->{images}};
push(@media_titles, @image_titles);
}
if (@media_titles) {
@@ -442,6 +418,7 @@ sub get_linked_mediafiles {
@titles = @titles[($batch+1)..$#titles];
}
+ return;
}
sub get_mw_mediafile_for_page_revision {
@@ -455,7 +432,7 @@ sub get_mw_mediafile_for_page_revision {
my $query = {
action => 'query',
prop => 'imageinfo',
- titles => "File:" . $filename,
+ titles => "File:${filename}",
iistart => $timestamp,
iiend => $timestamp,
iiprop => 'timestamp|archivename|url',
@@ -473,53 +450,50 @@ sub get_mw_mediafile_for_page_revision {
$mediafile{timestamp} = $fileinfo->{timestamp};
# Mediawiki::API's download function doesn't support https URLs
# and can't download old versions of files.
- print STDERR "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n";
+ print {*STDERR} "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n";
$mediafile{content} = download_mw_mediafile($fileinfo->{url});
}
return %mediafile;
}
sub download_mw_mediafile {
- my $url = shift;
+ my $download_url = shift;
- my $response = $mediawiki->{ua}->get($url);
- if ($response->code == 200) {
+ my $response = $mediawiki->{ua}->get($download_url);
+ if ($response->code == HTTP_CODE_OK) {
return $response->decoded_content;
} else {
- print STDERR "Error downloading mediafile from :\n";
- print STDERR "URL: $url\n";
- print STDERR "Server response: " . $response->code . " " . $response->message . "\n";
+ print {*STDERR} "Error downloading mediafile from :\n";
+ print {*STDERR} "URL: ${download_url}\n";
+ print {*STDERR} 'Server response: ' . $response->code . q{ } . $response->message . "\n";
exit 1;
}
}
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 = 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";
+ 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";
+ print {*STDERR} "Last local mediawiki revision found is ${lastrevision_number}";
}
return $lastrevision_number;
}
-# Remember the timestamp corresponding to a revision id.
-my %basetimestamps;
-
# Get the last remote revision without taking in account which pages are
# tracked or not. This function makes a single request to the wiki thus
# avoid a loop onto all tracked pages. This is useful for the fetch-by-rev
# option.
sub get_last_global_remote_rev {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $query = {
action => 'query',
@@ -535,14 +509,14 @@ sub get_last_global_remote_rev {
# Get the last remote revision concerning the tracked pages and the tracked
# categories.
sub get_last_remote_revision {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my %pages_hash = get_mw_pages();
my @pages = values(%pages_hash);
my $max_rev_num = 0;
- print STDERR "Getting last revision id on tracked pages...\n";
+ print {*STDERR} "Getting last revision id on tracked pages...\n";
foreach my $page (@pages) {
my $id = $page->{pageid};
@@ -563,7 +537,7 @@ sub get_last_remote_revision {
$max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num);
}
- print STDERR "Last remote revision found is $max_rev_num.\n";
+ print {*STDERR} "Last remote revision found is $max_rev_num.\n";
return $max_rev_num;
}
@@ -574,7 +548,7 @@ sub mediawiki_clean {
# 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) {
+ if ($string eq EMPTY && $page_created) {
# Creating empty pages is forbidden.
$string = EMPTY_CONTENT;
}
@@ -585,38 +559,16 @@ sub mediawiki_clean {
sub mediawiki_smudge {
my $string = shift;
if ($string eq EMPTY_CONTENT) {
- $string = "";
+ $string = EMPTY;
}
# 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;
+ return "${string}\n";
}
sub literal_data {
my ($content) = @_;
- print STDOUT "data ", bytes::length($content), "\n", $content;
+ print {*STDOUT} 'data ', bytes::length($content), "\n", $content;
+ return;
}
sub literal_data_raw {
@@ -624,33 +576,37 @@ sub literal_data_raw {
my ($content) = @_;
# Avoid confusion between size in bytes and in characters
utf8::downgrade($content);
- binmode STDOUT, ":raw";
- print STDOUT "data ", bytes::length($content), "\n", $content;
- binmode STDOUT, ":utf8";
+ binmode STDOUT, ':raw';
+ print {*STDOUT} 'data ', bytes::length($content), "\n", $content;
+ binmode STDOUT, ':encoding(UTF-8)';
+ return;
}
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";
+ print {*STDOUT} "refspec refs/heads/*:refs/mediawiki/${remotename}/*\n";
+ print {*STDOUT} "import\n";
+ print {*STDOUT} "list\n";
+ print {*STDOUT} "push\n";
+ print {*STDOUT} "\n";
+ return;
}
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";
+ print {*STDOUT} "? refs/heads/master\n";
+ print {*STDOUT} "\@refs/heads/master HEAD\n";
+ print {*STDOUT} "\n";
+ return;
}
sub mw_option {
- print STDERR "remote-helper command 'option $_[0]' not yet implemented\n";
- print STDOUT "unsupported\n";
+ print {*STDERR} "remote-helper command 'option $_[0]' not yet implemented\n";
+ print {*STDOUT} "unsupported\n";
+ return;
}
sub fetch_mw_revisions_for_page {
@@ -681,15 +637,15 @@ sub fetch_mw_revisions_for_page {
push(@page_revs, $page_rev_ids);
$revnum++;
}
- last unless $result->{'query-continue'};
+ last if (!$result->{'query-continue'});
$query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid};
}
if ($shallow_import && @page_revs) {
- print STDERR " Found 1 revision (shallow import).\n";
+ 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";
+ print {*STDERR} " Found ${revnum} revision(s).\n";
return @page_revs;
}
@@ -701,8 +657,7 @@ sub fetch_mw_revisions {
my $n = 1;
foreach my $page (@pages) {
my $id = $page->{pageid};
-
- print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n";
+ 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);
@@ -716,7 +671,7 @@ sub fe_escape_path {
$path =~ s/\\/\\\\/g;
$path =~ s/"/\\"/g;
$path =~ s/\n/\\n/g;
- return '"' . $path . '"';
+ return qq("${path}");
}
sub import_file_revision {
@@ -736,42 +691,43 @@ sub import_file_revision {
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";
+ 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";
+ print {*STDOUT} "from refs/mediawiki/${remotename}/master^0\n";
}
if ($content ne DELETED_CONTENT) {
- print STDOUT "M 644 inline " .
- fe_escape_path($title . ".mw") . "\n";
+ print {*STDOUT} 'M 644 inline ' .
+ fe_escape_path("${title}.mw") . "\n";
literal_data($content);
if (%mediafile) {
- print STDOUT "M 644 inline "
+ print {*STDOUT} 'M 644 inline '
. fe_escape_path($mediafile{title}) . "\n";
literal_data_raw($mediafile{content});
}
- print STDOUT "\n\n";
+ print {*STDOUT} "\n\n";
} else {
- print STDOUT "D " . fe_escape_path($title . ".mw") . "\n";
+ print {*STDOUT} 'D ' . fe_escape_path("${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} "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");
+ 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} "from refs/notes/${remotename}/mediawiki^0\n";
}
- print STDOUT "N inline :$n\n";
- literal_data("mediawiki_revision: " . $commit{mw_revision});
- print STDOUT "\n\n";
+ print {*STDOUT} "N inline :${n}\n";
+ literal_data("mediawiki_revision: $commit{mw_revision}");
+ print {*STDOUT} "\n\n";
+ return;
}
# parse a sequence of
@@ -784,23 +740,25 @@ sub get_more_refs {
my @refs;
while (1) {
my $line = <STDIN>;
- if ($line =~ m/^$cmd (.*)$/) {
+ if ($line =~ /^$cmd (.*)$/) {
push(@refs, $1);
} elsif ($line eq "\n") {
return @refs;
} else {
- die("Invalid command in a '$cmd' batch: ". $_);
+ die("Invalid command in a '$cmd' batch: $_\n");
}
}
+ return;
}
sub mw_import {
# multiple import commands can follow each other.
- my @refs = (shift, get_more_refs("import"));
+ my @refs = (shift, get_more_refs('import'));
foreach my $ref (@refs) {
mw_import_ref($ref);
}
- print STDOUT "done\n";
+ print {*STDOUT} "done\n";
+ return;
}
sub mw_import_ref {
@@ -810,40 +768,41 @@ sub mw_import_ref {
# 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") {
+ if ($ref eq 'HEAD') {
return;
}
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
- print STDERR "Searching revisions...\n";
+ 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";
+ print {*STDERR} ", fetching from beginning.\n";
} else {
- print STDERR ", fetching from here.\n";
+ print {*STDERR} ", fetching from here.\n";
}
my $n = 0;
- if ($fetch_strategy eq "by_rev") {
- print STDERR "Fetching & writing export data by revs...\n";
+ if ($fetch_strategy eq 'by_rev') {
+ print {*STDERR} "Fetching & writing export data by revs...\n";
$n = mw_import_ref_by_revs($fetch_from);
- } elsif ($fetch_strategy eq "by_page") {
- print STDERR "Fetching & writing export data by pages...\n";
+ } elsif ($fetch_strategy eq 'by_page') {
+ print {*STDERR} "Fetching & writing export data by pages...\n";
$n = mw_import_ref_by_pages($fetch_from);
} else {
- print STDERR "fatal: invalid fetch strategy \"$fetch_strategy\".\n";
- print STDERR "Check your configuration variables remote.$remotename.fetchStrategy and mediawiki.fetchStrategy\n";
+ print {*STDERR} qq(fatal: invalid fetch strategy "${fetch_strategy}".\n);
+ print {*STDERR} "Check your configuration variables remote.${remotename}.fetchStrategy and mediawiki.fetchStrategy\n";
exit 1;
}
if ($fetch_from == 1 && $n == 0) {
- print STDERR "You appear to have cloned an empty MediaWiki.\n";
+ 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 referring to unknown object 0000000000000000000
# and the clone fails.
}
+ return;
}
sub mw_import_ref_by_pages {
@@ -855,7 +814,7 @@ sub mw_import_ref_by_pages {
my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from);
@revisions = sort {$a->{revid} <=> $b->{revid}} @revisions;
- my @revision_ids = map $_->{revid}, @revisions;
+ my @revision_ids = map { $_->{revid} } @revisions;
return mw_import_revids($fetch_from, \@revision_ids, \%pages_hash);
}
@@ -882,7 +841,7 @@ sub mw_import_revids {
my $n_actual = 0;
my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
- foreach my $pagerevid (@$revision_ids) {
+ foreach my $pagerevid (@{$revision_ids}) {
# Count page even if we skip it, since we display
# $n/$total and $total includes skipped pages.
$n++;
@@ -898,7 +857,7 @@ sub mw_import_revids {
my $result = $mediawiki->api($query);
if (!$result) {
- die "Failed to retrieve modified page for revision $pagerevid";
+ die "Failed to retrieve modified page for revision $pagerevid\n";
}
if (defined($result->{query}->{badrevids}->{$pagerevid})) {
@@ -907,7 +866,7 @@ sub mw_import_revids {
}
if (!defined($result->{query}->{pages})) {
- die "Invalid revision $pagerevid.";
+ die "Invalid revision ${pagerevid}.\n";
}
my @result_pages = values(%{$result->{query}->{pages}});
@@ -917,8 +876,8 @@ sub mw_import_revids {
my $page_title = $result_page->{title};
if (!exists($pages->{$page_title})) {
- print STDERR "$n/", scalar(@$revision_ids),
- ": Skipping revision #$rev->{revid} of $page_title\n";
+ print {*STDERR} "${n}/", scalar(@{$revision_ids}),
+ ": Skipping revision #$rev->{revid} of ${page_title}\n";
next;
}
@@ -927,7 +886,7 @@ sub mw_import_revids {
my %commit;
$commit{author} = $rev->{user} || 'Anonymous';
$commit{comment} = $rev->{comment} || EMPTY_MESSAGE;
- $commit{title} = mediawiki_smudge_filename($page_title);
+ $commit{title} = smudge_filename($page_title);
$commit{mw_revision} = $rev->{revid};
$commit{content} = mediawiki_smudge($rev->{'*'});
@@ -943,14 +902,14 @@ sub mw_import_revids {
my %mediafile;
if ($namespace) {
my $id = get_mw_namespace_id($namespace);
- if ($id && $id == get_mw_namespace_id("File")) {
+ if ($id && $id == get_mw_namespace_id('File')) {
%mediafile = get_mw_mediafile_for_page_revision($filename, $rev->{timestamp});
}
}
# If this is a revision of the media page for new version
# of a file do one common commit for both file and media page.
# Else do commit only for that page.
- print STDERR "$n/", scalar(@$revision_ids), ": Revision #$rev->{revid} of $commit{title}\n";
+ print {*STDERR} "${n}/", scalar(@{$revision_ids}), ": Revision #$rev->{revid} of $commit{title}\n";
import_file_revision(\%commit, ($fetch_from == 1), $n_actual, \%mediafile);
}
@@ -958,17 +917,17 @@ sub mw_import_revids {
}
sub error_non_fast_forward {
- my $advice = run_git("config --bool advice.pushNonFastForward");
+ my $advice = run_git('config --bool advice.pushNonFastForward');
chomp($advice);
- if ($advice ne "false") {
+ 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 {*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";
+ print {*STDOUT} qq(error $_[0] "non-fast-forward"\n);
return 0;
}
@@ -979,34 +938,34 @@ sub mw_upload_file {
my $file_deleted = shift;
my $summary = shift;
my $newrevid;
- my $path = "File:" . $complete_file_name;
+ my $path = "File:${complete_file_name}";
my %hashFiles = get_allowed_file_extensions();
if (!exists($hashFiles{$extension})) {
- print STDERR "$complete_file_name is not a permitted file on this wiki.\n";
- print STDERR "Check the configuration of file uploads in your mediawiki.\n";
+ print {*STDERR} "${complete_file_name} is not a permitted file on this wiki.\n";
+ print {*STDERR} "Check the configuration of file uploads in your mediawiki.\n";
return $newrevid;
}
# Deleting and uploading a file requires a priviledged user
if ($file_deleted) {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $query = {
action => 'delete',
title => $path,
reason => $summary
};
if (!$mediawiki->edit($query)) {
- print STDERR "Failed to delete file on remote wiki\n";
- print STDERR "Check your permissions on the remote site. Error code:\n";
- print STDERR $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details};
+ print {*STDERR} "Failed to delete file on remote wiki\n";
+ print {*STDERR} "Check your permissions on the remote site. Error code:\n";
+ print {*STDERR} $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details};
exit 1;
}
} else {
# Don't let perl try to interpret file content as UTF-8 => use "raw"
- my $content = run_git("cat-file blob $new_sha1", "raw");
- if ($content ne "") {
- mw_connect_maybe();
+ my $content = run_git("cat-file blob ${new_sha1}", 'raw');
+ if ($content ne EMPTY) {
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
$mediawiki->{config}->{upload_url} =
- "$url/index.php/Special:Upload";
+ "${url}/index.php/Special:Upload";
$mediawiki->edit({
action => 'upload',
filename => $complete_file_name,
@@ -1018,12 +977,12 @@ sub mw_upload_file {
}, {
skip_encoding => 1
} ) || die $mediawiki->{error}->{code} . ':'
- . $mediawiki->{error}->{details};
+ . $mediawiki->{error}->{details} . "\n";
my $last_file_page = $mediawiki->get_page({title => $path});
$newrevid = $last_file_page->{revid};
- print STDERR "Pushed file: $new_sha1 - $complete_file_name.\n";
+ print {*STDERR} "Pushed file: ${new_sha1} - ${complete_file_name}.\n";
} else {
- print STDERR "Empty file $complete_file_name not pushed.\n";
+ print {*STDERR} "Empty file ${complete_file_name} not pushed.\n";
}
}
return $newrevid;
@@ -1045,24 +1004,24 @@ sub mw_push_file {
my $newrevid;
if ($summary eq EMPTY_MESSAGE) {
- $summary = '';
+ $summary = EMPTY;
}
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);
+ $complete_file_name = clean_filename($complete_file_name);
my ($title, $extension) = $complete_file_name =~ /^(.*)\.([^\.]*)$/;
if (!defined($extension)) {
- $extension = "";
+ $extension = EMPTY;
}
- if ($extension eq "mw") {
+ if ($extension eq 'mw') {
my $ns = get_mw_namespace_id_for_page($complete_file_name);
- if ($ns && $ns == get_mw_namespace_id("File") && (!$export_media)) {
- print STDERR "Ignoring media file related page: $complete_file_name\n";
- return ($oldrevid, "ok");
+ if ($ns && $ns == get_mw_namespace_id('File') && (!$export_media)) {
+ print {*STDERR} "Ignoring media file related page: ${complete_file_name}\n";
+ return ($oldrevid, 'ok');
}
my $file_content;
if ($page_deleted) {
@@ -1072,10 +1031,10 @@ sub mw_push_file {
# with this content instead:
$file_content = DELETED_CONTENT;
} else {
- $file_content = run_git("cat-file blob $new_sha1");
+ $file_content = run_git("cat-file blob ${new_sha1}");
}
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $result = $mediawiki->edit( {
action => 'edit',
@@ -1089,49 +1048,49 @@ sub mw_push_file {
if (!$result) {
if ($mediawiki->{error}->{code} == 3) {
# edit conflicts, considered as non-fast-forward
- print STDERR 'Warning: Error ' .
+ print {*STDERR} 'Warning: Error ' .
$mediawiki->{error}->{code} .
- ' from mediwiki: ' . $mediawiki->{error}->{details} .
+ ' from mediawiki: ' . $mediawiki->{error}->{details} .
".\n";
- return ($oldrevid, "non-fast-forward");
+ return ($oldrevid, 'non-fast-forward');
} else {
# Other errors. Shouldn't happen => just die()
die 'Fatal: Error ' .
$mediawiki->{error}->{code} .
- ' from mediwiki: ' . $mediawiki->{error}->{details};
+ ' from mediawiki: ' . $mediawiki->{error}->{details} . "\n";
}
}
$newrevid = $result->{edit}->{newrevid};
- print STDERR "Pushed file: $new_sha1 - $title\n";
+ print {*STDERR} "Pushed file: ${new_sha1} - ${title}\n";
} elsif ($export_media) {
$newrevid = mw_upload_file($complete_file_name, $new_sha1,
$extension, $page_deleted,
$summary);
} else {
- print STDERR "Ignoring media file $title\n";
+ print {*STDERR} "Ignoring media file ${title}\n";
}
$newrevid = ($newrevid or $oldrevid);
- return ($newrevid, "ok");
+ return ($newrevid, 'ok');
}
sub mw_push {
# multiple push statements can follow each other
- my @refsspecs = (shift, get_more_refs("push"));
+ 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>");
+ or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>\n");
if ($force) {
- print STDERR "Warning: forced push not allowed on a MediaWiki.\n";
+ 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";
+ if ($local eq EMPTY) {
+ 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";
+ 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)) {
@@ -1140,30 +1099,32 @@ sub mw_push {
}
# Notify Git that the push is done
- print STDOUT "\n";
+ 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";
+ 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";
}
+ return;
}
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()
+ 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);
+ 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");
+ my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/${remotename}/master 2>/dev/null");
chomp($remoteorigin_sha1);
if ($last_local_revid > 0 &&
@@ -1182,22 +1143,22 @@ sub mw_push_revision {
if ($last_local_revid > 0) {
my $parsed_sha1 = $remoteorigin_sha1;
# Find a path from last MediaWiki commit to pushed commit
- print STDERR "Computing path from local to remote ...\n";
- my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents $local ^$parsed_sha1"));
+ print {*STDERR} "Computing path from local to remote ...\n";
+ my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents ${local} ^${parsed_sha1}"));
my %local_ancestry;
foreach my $line (@local_ancestry) {
- if (my ($child, $parents) = $line =~ m/^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
- foreach my $parent (split(' ', $parents)) {
+ if (my ($child, $parents) = $line =~ /^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
+ foreach my $parent (split(/ /, $parents)) {
$local_ancestry{$parent} = $child;
}
- } elsif (!$line =~ m/^([a-f0-9]+)/) {
- die "Unexpected output from git rev-list: $line";
+ } elsif (!$line =~ /^([a-f0-9]+)/) {
+ die "Unexpected output from git rev-list: ${line}\n";
}
}
while ($parsed_sha1 ne $HEAD_sha1) {
my $child = $local_ancestry{$parsed_sha1};
if (!$child) {
- printf STDERR "Cannot find a path in history from remote commit to last commit\n";
+ print {*STDERR} "Cannot find a path in history from remote commit to last commit\n";
return error_non_fast_forward($remote);
}
push(@commit_pairs, [$parsed_sha1, $child]);
@@ -1206,12 +1167,12 @@ sub mw_push_revision {
} 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);
+ 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);
+ my @commit_info_split = split(/[ \n]/, $line);
push(@commit_pairs, \@commit_info_split);
}
}
@@ -1219,12 +1180,12 @@ sub mw_push_revision {
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");
+ 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 subject line of the commit message as mediawiki comment for the revision
- my $commit_msg = run_git("log --no-walk --format=\"%s\" $sha1_commit");
+ my $commit_msg = run_git(qq(log --no-walk --format="%s" ${sha1_commit}));
chomp($commit_msg);
# Push every blob
while (@diff_info_list) {
@@ -1236,7 +1197,7 @@ sub mw_push_revision {
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") {
+ 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
@@ -1244,22 +1205,22 @@ sub mw_push_revision {
# accurate error message.
return error_non_fast_forward($remote);
}
- if ($status ne "ok") {
- die("Unknown error from mw_push_file()");
+ if ($status ne 'ok') {
+ die("Unknown error from mw_push_file()\n");
}
}
- unless ($dumb_push) {
- run_git("notes --ref=$remotename/mediawiki add -f -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
- run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
+ if (!$dumb_push) {
+ run_git(qq(notes --ref=${remotename}/mediawiki add -f -m "mediawiki_revision: ${mw_revision}" ${sha1_commit}));
+ run_git(qq(update-ref -m "Git-MediaWiki push" refs/mediawiki/${remotename}/master ${sha1_commit} ${sha1_child}));
}
}
- print STDOUT "ok $remote\n";
+ print {*STDOUT} "ok ${remote}\n";
return 1;
}
sub get_allowed_file_extensions {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $query = {
action => 'query',
@@ -1267,8 +1228,8 @@ sub get_allowed_file_extensions {
siprop => 'fileextensions'
};
my $result = $mediawiki->api($query);
- my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}};
- my %hashFile = map {$_ => 1}@file_extensions;
+ my @file_extensions = map { $_->{ext}} @{$result->{query}->{fileextensions}};
+ my %hashFile = map { $_ => 1 } @file_extensions;
return %hashFile;
}
@@ -1283,15 +1244,15 @@ my %cached_mw_namespace_id;
# Return MediaWiki id for a canonical namespace name.
# Ex.: "File", "Project".
sub get_mw_namespace_id {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $name = shift;
if (!exists $namespace_id{$name}) {
# Look at configuration file, if the record for that namespace is
# already cached. Namespaces are stored in form:
# "Name_of_namespace:Id_namespace", ex.: "File:6".
- my @temp = split(/[\n]/, run_git("config --get-all remote."
- . $remotename .".namespaceCache"));
+ my @temp = split(/\n/,
+ run_git("config --get-all remote.${remotename}.namespaceCache"));
chomp(@temp);
foreach my $ns (@temp) {
my ($n, $id) = split(/:/, $ns);
@@ -1305,7 +1266,7 @@ sub get_mw_namespace_id {
}
if (!exists $namespace_id{$name}) {
- print STDERR "Namespace $name not found in cache, querying the wiki ...\n";
+ print {*STDERR} "Namespace ${name} not found in cache, querying the wiki ...\n";
# NS not found => get namespace id from MW and store it in
# configuration file.
my $query = {
@@ -1329,8 +1290,8 @@ sub get_mw_namespace_id {
my $ns = $namespace_id{$name};
my $id;
- unless (defined $ns) {
- print STDERR "No such namespace $name on MediaWiki.\n";
+ if (!defined $ns) {
+ print {*STDERR} "No such namespace ${name} on MediaWiki.\n";
$ns = {is_namespace => 0};
$namespace_id{$name} = $ns;
}
@@ -1344,15 +1305,15 @@ sub get_mw_namespace_id {
# Store explicitely requested namespaces on disk
if (!exists $cached_mw_namespace_id{$name}) {
- run_git("config --add remote.". $remotename
- .".namespaceCache \"". $name .":". $store_id ."\"");
+ run_git(qq(config --add remote.${remotename}.namespaceCache "${name}:${store_id}"));
$cached_mw_namespace_id{$name} = 1;
}
return $id;
}
sub get_mw_namespace_id_for_page {
- if (my ($namespace) = $_[0] =~ /^([^:]*):/) {
+ my $namespace = shift;
+ if ($namespace =~ /^([^:]*):/) {
return get_mw_namespace_id($namespace);
} else {
return;
diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh
index 3b2cfac..ca6860f 100755
--- a/contrib/mw-to-git/t/test-gitmw-lib.sh
+++ b/contrib/mw-to-git/t/test-gitmw-lib.sh
@@ -62,12 +62,8 @@ test_check_precond () {
test_done
fi
- if [ ! -f "$GIT_BUILD_DIR"/git-remote-mediawiki ];
- then
- echo "No remote mediawiki for git found. Copying it in git"
- echo "cp $GIT_BUILD_DIR/contrib/mw-to-git/git-remote-mediawiki $GIT_BUILD_DIR/"
- ln -s "$GIT_BUILD_DIR"/contrib/mw-to-git/git-remote-mediawiki "$GIT_BUILD_DIR"
- fi
+ GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd "../.." && pwd)
+ PATH="$GIT_EXEC_PATH"'/bin-wrapper:'"$PATH"
if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ];
then
@@ -336,20 +332,21 @@ wiki_install () {
fi
# Fetch MediaWiki's archive if not already present in the TMP directory
+ MW_FILENAME="mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
cd "$TMP"
- if [ ! -f "$MW_VERSION.tar.gz" ] ; then
- echo "Downloading $MW_VERSION sources ..."
- wget "http://download.wikimedia.org/mediawiki/1.19/mediawiki-1.19.0.tar.gz" ||
+ if [ ! -f $MW_FILENAME ] ; then
+ echo "Downloading $MW_VERSION_MAJOR.$MW_VERSION_MINOR sources ..."
+ wget "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/$MW_FILENAME" ||
error "Unable to download "\
- "http://download.wikimedia.org/mediawiki/1.19/"\
- "mediawiki-1.19.0.tar.gz. "\
+ "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/"\
+ "$MW_FILENAME. "\
"Please fix your connection and launch the script again."
- echo "$MW_VERSION.tar.gz downloaded in `pwd`. "\
+ echo "$MW_FILENAME downloaded in `pwd`. "\
"You can delete it later if you want."
else
- echo "Reusing existing $MW_VERSION.tar.gz downloaded in `pwd`."
+ echo "Reusing existing $MW_FILENAME downloaded in `pwd`."
fi
- archive_abs_path=$(pwd)/"$MW_VERSION.tar.gz"
+ archive_abs_path=$(pwd)/$MW_FILENAME
cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" ||
error "can't cd to $WIKI_DIR_INST/$WIKI_DIR_NAME/"
tar xzf "$archive_abs_path" --strip-components=1 ||
@@ -431,5 +428,5 @@ wiki_delete () {
# Delete the wiki's SQLite database
rm -f "$TMP/$DB_FILE" || error "Database $TMP/$DB_FILE could not be deleted."
rm -f "$FILES_FOLDER/$DB_FILE"
- rm -rf "$TMP/$MW_VERSION"
+ rm -rf "$TMP/mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
}
diff --git a/contrib/mw-to-git/t/test.config b/contrib/mw-to-git/t/test.config
index 958b37b..4cfebe9 100644
--- a/contrib/mw-to-git/t/test.config
+++ b/contrib/mw-to-git/t/test.config
@@ -30,6 +30,8 @@ WEB_WWW=$WEB/www
# The variables below are used by the script to install a wiki.
# You should not modify these unless you are modifying the script itself.
-MW_VERSION=mediawiki-1.19.0
+# tested versions: 1.19.X -> 1.21.1
+MW_VERSION_MAJOR=1.21
+MW_VERSION_MINOR=1
FILES_FOLDER=install-wiki
DB_INSTALL_SCRIPT=db_install.php
diff --git a/contrib/patches/docbook-xsl-manpages-charmap.patch b/contrib/patches/docbook-xsl-manpages-charmap.patch
deleted file mode 100644
index f2b08b4..0000000
--- a/contrib/patches/docbook-xsl-manpages-charmap.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-From: Ismail Dönmez <ismail@pardus.org.tr>
-
-Trying to build the documentation with docbook-xsl 1.73 may result in
-the following error. This patch fixes it.
-
-$ xmlto -m callouts.xsl man git-add.xml
-runtime error: file
-file:///usr/share/sgml/docbook/xsl-stylesheets-1.73.0/manpages/other.xsl line
-129 element call-template
-The called template 'read-character-map' was not found.
-
---- docbook-xsl-1.73.0/manpages/docbook.xsl.manpages-charmap 2007-07-23 16:24:23.000000000 +0100
-+++ docbook-xsl-1.73.0/manpages/docbook.xsl 2007-07-23 16:25:16.000000000 +0100
-@@ -37,6 +37,7 @@
- <xsl:include href="lists.xsl"/>
- <xsl:include href="endnotes.xsl"/>
- <xsl:include href="table.xsl"/>
-+ <xsl:include href="../common/charmap.xsl"/>
-
- <!-- * we rename the following just to avoid using params with "man" -->
- <!-- * prefixes in the table.xsl stylesheet (because that stylesheet -->
diff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr
index 10300c6..c3a3cac 100755
--- a/contrib/remote-helpers/git-remote-bzr
+++ b/contrib/remote-helpers/git-remote-bzr
@@ -116,7 +116,10 @@ class Marks:
self.last_mark = mark
def get_tip(self, branch):
- return self.tips.get(branch, None)
+ try:
+ return str(self.tips[branch])
+ except KeyError:
+ return None
def set_tip(self, branch, tip):
self.tips[branch] = tip
@@ -278,7 +281,7 @@ def export_branch(repo, name):
ref = '%s/heads/%s' % (prefix, name)
tip = marks.get_tip(name)
- branch = bzrlib.branch.Branch.open(branches[name])
+ branch = get_remote_branch(name)
repo = branch.repository
branch.lock_read()
@@ -590,7 +593,7 @@ def parse_commit(parser):
if ref.startswith('refs/heads/'):
name = ref[len('refs/heads/'):]
- branch = bzrlib.branch.Branch.open(branches[name])
+ branch = get_remote_branch(name)
else:
die('unknown ref')
@@ -621,7 +624,7 @@ def parse_commit(parser):
mark = int(mark_ref[1:])
f = { 'mode' : m, 'mark' : mark }
elif parser.check('D'):
- t, path = line.split(' ')
+ t, path = line.split(' ', 1)
f = { 'deleted' : True }
else:
die('Unknown file command: %s' % line)
@@ -692,7 +695,7 @@ def do_export(parser):
for ref, revid in parsed_refs.iteritems():
if ref.startswith('refs/heads/'):
name = ref[len('refs/heads/'):]
- branch = bzrlib.branch.Branch.open(branches[name])
+ branch = get_remote_branch(name)
branch.generate_revision_history(revid, marks.get_tip(name))
if name in peers:
@@ -749,7 +752,7 @@ def do_list(parser):
master_branch = name
print "? refs/heads/%s" % name
- branch = bzrlib.branch.Branch.open(branches[master_branch])
+ branch = get_remote_branch(master_branch)
branch.lock_read()
for tag, revid in branch.tags.get_tag_dict().items():
try:
@@ -765,30 +768,40 @@ def do_list(parser):
print "@refs/heads/%s HEAD" % master_branch
print
-def get_remote_branch(origin, remote_branch, name):
- global dirname, peers
+def clone(path, remote_branch):
+ try:
+ bdir = bzrlib.bzrdir.BzrDir.create(path)
+ except bzrlib.errors.AlreadyControlDirError:
+ bdir = bzrlib.bzrdir.BzrDir.open(path)
+ repo = bdir.find_repository()
+ repo.fetch(remote_branch.repository)
+ return remote_branch.sprout(bdir, repository=repo)
+
+def get_remote_branch(name):
+ global dirname, branches
+
+ remote_branch = bzrlib.branch.Branch.open(branches[name])
+ if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport):
+ return remote_branch
branch_path = os.path.join(dirname, 'clone', name)
- if os.path.exists(branch_path):
+
+ try:
+ branch = bzrlib.branch.Branch.open(branch_path)
+ except bzrlib.errors.NotBranchError:
+ # clone
+ branch = clone(branch_path, remote_branch)
+ else:
# pull
- d = bzrlib.bzrdir.BzrDir.open(branch_path)
- branch = d.open_branch()
try:
- branch.pull(remote_branch, [], None, False)
+ branch.pull(remote_branch, overwrite=True)
except bzrlib.errors.DivergedBranches:
# use remote branch for now
return remote_branch
- else:
- # clone
- d = origin.sprout(branch_path, None,
- hardlink=True, create_tree_if_local=False,
- force_new_repo=False,
- source_branch=remote_branch)
- branch = d.open_branch()
return branch
-def find_branches(repo, wanted):
+def find_branches(repo):
transport = repo.bzrdir.root_transport
for fn in transport.iter_files_recursive():
@@ -799,16 +812,13 @@ def find_branches(repo, wanted):
name = name if name != '' else 'master'
name = name.replace('/', '+')
- if wanted and not name in wanted:
- continue
-
try:
cur = transport.clone(subdir)
branch = bzrlib.branch.Branch.open_from_transport(cur)
except bzrlib.errors.NotBranchError:
continue
else:
- yield name, branch
+ yield name, branch.base
def get_repo(url, alias):
global dirname, peer, branches
@@ -841,44 +851,35 @@ def get_repo(url, alias):
except bzrlib.errors.NoRepositoryPresent:
pass
- try:
- repo = origin.open_repository()
- if not repo.user_transport.listable():
- # this repository is not usable for us
- raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
- except bzrlib.errors.NoRepositoryPresent:
- # branch
-
- name = 'master'
- remote_branch = origin.open_branch()
-
- if not is_local:
- peers[name] = remote_branch.base
- branch = get_remote_branch(origin, remote_branch, name)
- else:
- branch = remote_branch
-
- branches[name] = branch.base
+ wanted = get_config('remote-bzr.branches').rstrip().split(', ')
+ # stupid python
+ wanted = [e for e in wanted if e]
- return branch.repository
+ if not wanted:
+ try:
+ repo = origin.open_repository()
+ if not repo.user_transport.listable():
+ # this repository is not usable for us
+ raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
+ except bzrlib.errors.NoRepositoryPresent:
+ wanted = ['master']
+
+ if wanted:
+ def list_wanted(url, wanted):
+ for name in wanted:
+ subdir = name if name != 'master' else ''
+ yield name, bzrlib.urlutils.join(url, subdir)
+
+ branch_list = list_wanted(url, wanted)
else:
- # repository
-
- wanted = get_config('remote-bzr.branches').rstrip().split(', ')
- # stupid python
- wanted = [e for e in wanted if e]
-
- for name, remote_branch in find_branches(repo, wanted):
+ branch_list = find_branches(repo)
- if not is_local:
- peers[name] = remote_branch.base
- branch = get_remote_branch(origin, remote_branch, name)
- else:
- branch = remote_branch
-
- branches[name] = branch.base
+ for name, url in branch_list:
+ if not is_local:
+ peers[name] = url
+ branches[name] = url
- return repo
+ return origin
def fix_path(alias, orig_url):
url = urlparse.urlparse(orig_url, 'file')
diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg
index 1dd3d70..0194c67 100755
--- a/contrib/remote-helpers/git-remote-hg
+++ b/contrib/remote-helpers/git-remote-hg
@@ -12,7 +12,7 @@
# For remote repositories a local clone is stored in
# "$GIT_DIR/hg/origin/clone/.hg/".
-from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions
+from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions, discovery, util
import re
import sys
@@ -29,9 +29,6 @@ import urlparse, hashlib
# named branches:
# git config --global remote-hg.track-branches false
#
-# If you don't want to force pushes (and thus risk creating new remote heads):
-# git config --global remote-hg.force-push false
-#
# If you want the equivalent of hg's clone/pull--insecure option:
# git config --global remote-hg.insecure true
#
@@ -55,6 +52,8 @@ EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\
AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
+VERSION = 2
+
def die(msg, *args):
sys.stderr.write('ERROR: %s\n' % (msg % args))
sys.exit(1)
@@ -72,8 +71,11 @@ def hgmode(mode):
m = { '100755': 'x', '120000': 'l' }
return m.get(mode, '')
-def hghex(node):
- return hg.node.hex(node)
+def hghex(n):
+ return node.hex(n)
+
+def hgbin(n):
+ return node.bin(n)
def hgref(ref):
return ref.replace('___', ' ')
@@ -81,6 +83,11 @@ def hgref(ref):
def gitref(ref):
return ref.replace(' ', '___')
+def check_version(*check):
+ if not hg_version:
+ return True
+ return hg_version >= check
+
def get_config(config):
cmd = ['git', 'config', '--get', config]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
@@ -98,14 +105,27 @@ def get_config_bool(config, default=False):
class Marks:
- def __init__(self, path):
+ def __init__(self, path, repo):
self.path = path
+ self.repo = repo
+ self.clear()
+ self.load()
+
+ if self.version < VERSION:
+ if self.version == 1:
+ self.upgrade_one()
+
+ # upgraded?
+ if self.version < VERSION:
+ self.clear()
+ self.version = VERSION
+
+ def clear(self):
self.tips = {}
self.marks = {}
self.rev_marks = {}
self.last_mark = 0
-
- self.load()
+ self.version = 0
def load(self):
if not os.path.exists(self.path):
@@ -116,12 +136,21 @@ class Marks:
self.tips = tmp['tips']
self.marks = tmp['marks']
self.last_mark = tmp['last-mark']
+ self.version = tmp.get('version', 1)
for rev, mark in self.marks.iteritems():
- self.rev_marks[mark] = int(rev)
+ self.rev_marks[mark] = rev
+
+ def upgrade_one(self):
+ def get_id(rev):
+ return hghex(self.repo.changelog.node(int(rev)))
+ self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
+ self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
+ self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
+ self.version = 2
def dict(self):
- return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
+ return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version }
def store(self):
json.dump(self.dict(), open(self.path, 'w'))
@@ -130,10 +159,10 @@ class Marks:
return str(self.dict())
def from_rev(self, rev):
- return self.marks[str(rev)]
+ return self.marks[rev]
def to_rev(self, mark):
- return self.rev_marks[mark]
+ return str(self.rev_marks[mark])
def next_mark(self):
self.last_mark += 1
@@ -141,19 +170,19 @@ class Marks:
def get_mark(self, rev):
self.last_mark += 1
- self.marks[str(rev)] = self.last_mark
+ self.marks[rev] = self.last_mark
return self.last_mark
def new_mark(self, rev, mark):
- self.marks[str(rev)] = mark
+ self.marks[rev] = mark
self.rev_marks[mark] = rev
self.last_mark = mark
def is_marked(self, rev):
- return str(rev) in self.marks
+ return rev in self.marks
def get_tip(self, branch):
- return self.tips.get(branch, 0)
+ return str(self.tips[branch])
def set_tip(self, branch, tip):
self.tips[branch] = tip
@@ -261,7 +290,7 @@ def get_filechanges(repo, ctx, parent):
removed = set()
# load earliest manifest first for caching reasons
- prev = repo[parent].manifest().copy()
+ prev = parent.manifest().copy()
cur = ctx.manifest()
for fn in cur:
@@ -329,6 +358,21 @@ def fixup_user(user):
return '%s <%s>' % (name, mail)
+def updatebookmarks(repo, peer):
+ remotemarks = peer.listkeys('bookmarks')
+ localmarks = repo._bookmarks
+
+ if not remotemarks:
+ return
+
+ for k, v in remotemarks.iteritems():
+ localmarks[k] = hgbin(v)
+
+ if hasattr(localmarks, 'write'):
+ localmarks.write()
+ else:
+ bookmarks.write(repo)
+
def get_repo(url, alias):
global dirname, peer
@@ -339,35 +383,41 @@ def get_repo(url, alias):
if get_config_bool('remote-hg.insecure'):
myui.setconfig('web', 'cacerts', '')
- try:
- mod = extensions.load(myui, 'hgext.schemes', None)
- mod.extsetup(myui)
- except ImportError:
- pass
+ extensions.loadall(myui)
- if hg.islocal(url):
+ if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
repo = hg.repository(myui, url)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
else:
- local_path = os.path.join(dirname, 'clone')
- if not os.path.exists(local_path):
- try:
- peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
- except:
- die('Repository error')
- repo = dstpeer.local()
- else:
- repo = hg.repository(myui, local_path)
+ shared_path = os.path.join(gitdir, 'hg')
+ if not os.path.exists(shared_path):
try:
- peer = hg.peer(myui, {}, url)
+ hg.clone(myui, {}, url, shared_path, update=False, pull=True)
except:
die('Repository error')
- repo.pull(peer, heads=None, force=True)
+
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ local_path = os.path.join(dirname, 'clone')
+ if not os.path.exists(local_path):
+ hg.share(myui, shared_path, local_path, update=False)
+
+ repo = hg.repository(myui, local_path)
+ try:
+ peer = hg.peer(myui, {}, url)
+ except:
+ die('Repository error')
+ repo.pull(peer, heads=None, force=True)
+
+ updatebookmarks(repo, peer)
return repo
def rev_to_mark(rev):
global marks
- return marks.from_rev(rev)
+ return marks.from_rev(rev.hex())
def mark_to_rev(mark):
global marks
@@ -377,17 +427,24 @@ def export_ref(repo, name, kind, head):
global prefix, marks, mode
ename = '%s/%s' % (kind, name)
- tip = marks.get_tip(ename)
+ try:
+ tip = marks.get_tip(ename)
+ tip = repo[tip].rev()
+ except:
+ tip = 0
revs = xrange(tip, head.rev() + 1)
- count = 0
-
- revs = [rev for rev in revs if not marks.is_marked(rev)]
+ total = len(revs)
for rev in revs:
c = repo[rev]
- (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
+ node = c.node()
+
+ if marks.is_marked(c.hex()):
+ continue
+
+ (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
rev_branch = extra['branch']
author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
@@ -397,7 +454,7 @@ def export_ref(repo, name, kind, head):
else:
committer = author
- parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
+ parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
if len(parents) == 0:
modified = c.manifest().keys()
@@ -439,7 +496,7 @@ def export_ref(repo, name, kind, head):
modified_final = export_files(c.filectx(f) for f in modified)
print "commit %s/%s" % (prefix, ename)
- print "mark :%d" % (marks.get_mark(rev))
+ print "mark :%d" % (marks.get_mark(c.hex()))
print "author %s" % (author)
print "committer %s" % (committer)
print "data %d" % (len(desc))
@@ -450,22 +507,22 @@ def export_ref(repo, name, kind, head):
if len(parents) > 1:
print "merge :%s" % (rev_to_mark(parents[1]))
- for f in modified_final:
- print "M %s :%u %s" % f
for f in removed:
print "D %s" % (fix_file_path(f))
+ for f in modified_final:
+ print "M %s :%u %s" % f
print
- count += 1
- if (count % 100 == 0):
- print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
+ progress = (rev - tip)
+ if (progress % 100 == 0):
+ print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
# make sure the ref is updated
print "reset %s/%s" % (prefix, ename)
- print "from :%u" % rev_to_mark(rev)
+ print "from :%u" % rev_to_mark(head)
print
- marks.set_tip(ename, rev)
+ marks.set_tip(ename, head.hex())
def export_tag(repo, tag):
export_ref(repo, tag, 'tags', repo[hgref(tag)])
@@ -497,15 +554,12 @@ def do_capabilities(parser):
if os.path.exists(path):
print "*import-marks %s" % path
print "*export-marks %s" % path
+ print "option"
print
-def branch_tip(repo, branch):
- # older versions of mercurial don't have this
- if hasattr(repo, 'branchtip'):
- return repo.branchtip(branch)
- else:
- return repo.branchtags()[branch]
+def branch_tip(branch):
+ return branches[branch][-1]
def get_branch_tip(repo, branch):
global branches
@@ -517,27 +571,21 @@ def get_branch_tip(repo, branch):
# verify there's only one head
if (len(heads) > 1):
warn("Branch '%s' has more than one head, consider merging" % branch)
- return branch_tip(repo, hgref(branch))
+ return branch_tip(hgref(branch))
return heads[0]
def list_head(repo, cur):
- global g_head, bmarks
+ global g_head, bmarks, fake_bmark
- head = bookmarks.readcurrent(repo)
- if head:
- node = repo[head]
- else:
- # fake bookmark from current branch
- head = cur
- node = repo['.']
- if not node:
- node = repo['tip']
- if not node:
- return
- if head == 'default':
- head = 'master'
- bmarks[head] = node
+ if 'default' not in branches:
+ # empty repo
+ return
+
+ node = repo[branch_tip('default')]
+ head = 'master' if not 'master' in bmarks else 'default'
+ fake_bmark = head
+ bmarks[head] = node
head = gitref(head)
print "@refs/heads/%s HEAD" % head
@@ -551,15 +599,17 @@ def do_list(parser):
bmarks[bmark] = repo[node]
cur = repo.dirstate.branch()
+ orig = peer if peer else repo
+
+ for branch, heads in orig.branchmap().iteritems():
+ # only open heads
+ heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
+ if heads:
+ branches[branch] = heads
list_head(repo, cur)
if track_branches:
- for branch in repo.branchmap():
- heads = repo.branchheads(branch)
- if len(heads):
- branches[branch] = heads
-
for branch in branches:
print "? refs/heads/branches/%s" % gitref(branch)
@@ -582,6 +632,7 @@ def do_import(parser):
if os.path.exists(path):
print "feature import-marks=%s" % path
print "feature export-marks=%s" % path
+ print "feature force"
sys.stdout.flush()
tmp = encoding.encoding
@@ -671,6 +722,11 @@ def parse_commit(parser):
die('Unknown file command: %s' % line)
files[path] = f
+ # only export the commits if we are on an internal proxy repo
+ if dry_run and not peer:
+ parsed_refs[ref] = None
+ return
+
def getfilectx(repo, memctx, f):
of = files[f]
if 'deleted' in of:
@@ -692,14 +748,14 @@ def parse_commit(parser):
extra['committer'] = "%s %u %u" % committer
if from_mark:
- p1 = repo.changelog.node(mark_to_rev(from_mark))
+ p1 = mark_to_rev(from_mark)
else:
- p1 = '\0' * 20
+ p1 = '0' * 40
if merge_mark:
- p2 = repo.changelog.node(mark_to_rev(merge_mark))
+ p2 = mark_to_rev(merge_mark)
else:
- p2 = '\0' * 20
+ p2 = '0' * 40
#
# If files changed from any of the parents, hg wants to know, but in git if
@@ -735,14 +791,12 @@ def parse_commit(parser):
tmp = encoding.encoding
encoding.encoding = 'utf-8'
- node = repo.commitctx(ctx)
+ node = hghex(repo.commitctx(ctx))
encoding.encoding = tmp
- rev = repo[node].rev()
-
parsed_refs[ref] = node
- marks.new_mark(rev, commit_mark)
+ marks.new_mark(node, commit_mark)
def parse_reset(parser):
global parsed_refs
@@ -758,8 +812,11 @@ def parse_reset(parser):
from_mark = parser.get_mark()
parser.next()
- node = parser.repo.changelog.node(mark_to_rev(from_mark))
- parsed_refs[ref] = node
+ try:
+ rev = mark_to_rev(from_mark)
+ except KeyError:
+ rev = None
+ parsed_refs[ref] = rev
def parse_tag(parser):
name = parser[1]
@@ -775,7 +832,7 @@ def parse_tag(parser):
def write_tag(repo, tag, node, msg, author):
branch = repo[node].branch()
- tip = branch_tip(repo, branch)
+ tip = branch_tip(branch)
tip = repo[tip]
def getfilectx(repo, memctx, f):
@@ -784,18 +841,28 @@ def write_tag(repo, tag, node, msg, author):
data = fctx.data()
except error.ManifestLookupError:
data = ""
- content = data + "%s %s\n" % (hghex(node), tag)
+ content = data + "%s %s\n" % (node, tag)
return context.memfilectx(f, content, False, False, None)
p1 = tip.hex()
- p2 = '\0' * 20
- if not author:
- author = (None, 0, 0)
- user, date, tz = author
+ p2 = '0' * 40
+ if author:
+ user, date, tz = author
+ date_tz = (date, tz)
+ else:
+ cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ output, _ = process.communicate()
+ m = re.match('^.* <.*>', output)
+ if m:
+ user = m.group(0)
+ else:
+ user = repo.ui.username()
+ date_tz = None
ctx = context.memctx(repo, (p1, p2), msg,
['.hgtags'], getfilectx,
- user, (date, tz), {'branch' : branch})
+ user, date_tz, {'branch' : branch})
tmp = encoding.encoding
encoding.encoding = 'utf-8'
@@ -804,12 +871,132 @@ def write_tag(repo, tag, node, msg, author):
encoding.encoding = tmp
- return tagnode
+ return (tagnode, branch)
+
+def checkheads_bmark(repo, ref, ctx):
+ bmark = ref[len('refs/heads/'):]
+ if not bmark in bmarks:
+ # new bmark
+ return True
+
+ ctx_old = bmarks[bmark]
+ ctx_new = ctx
+ if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
+ if force_push:
+ print "ok %s forced update" % ref
+ else:
+ print "error %s non-fast forward" % ref
+ return False
+
+ return True
+
+def checkheads(repo, remote, p_revs):
+
+ remotemap = remote.branchmap()
+ if not remotemap:
+ # empty repo
+ return True
+
+ new = {}
+ ret = True
+
+ for node, ref in p_revs.iteritems():
+ ctx = repo[node]
+ branch = ctx.branch()
+ if not branch in remotemap:
+ # new branch
+ continue
+ if not ref.startswith('refs/heads/branches'):
+ if ref.startswith('refs/heads/'):
+ if not checkheads_bmark(repo, ref, ctx):
+ ret = False
+
+ # only check branches
+ continue
+ new.setdefault(branch, []).append(ctx.rev())
+
+ for branch, heads in new.iteritems():
+ old = [repo.changelog.rev(x) for x in remotemap[branch]]
+ for rev in heads:
+ if check_version(2, 3):
+ ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
+ else:
+ ancestors = repo.changelog.ancestors(rev)
+ found = False
+
+ for x in old:
+ if x in ancestors:
+ found = True
+ break
+
+ if found:
+ continue
+
+ node = repo.changelog.node(rev)
+ ref = p_revs[node]
+ if force_push:
+ print "ok %s forced update" % ref
+ else:
+ print "error %s non-fast forward" % ref
+ ret = False
+
+ return ret
+
+def push_unsafe(repo, remote, parsed_refs, p_revs):
+
+ force = force_push
+
+ fci = discovery.findcommonincoming
+ commoninc = fci(repo, remote, force=force)
+ common, _, remoteheads = commoninc
+
+ if not checkheads(repo, remote, p_revs):
+ return None
+
+ cg = repo.getbundle('push', heads=list(p_revs), common=common)
+
+ unbundle = remote.capable('unbundle')
+ if unbundle:
+ if force:
+ remoteheads = ['force']
+ return remote.unbundle(cg, remoteheads, 'push')
+ else:
+ return remote.addchangegroup(cg, 'push', repo.url())
+
+def push(repo, remote, parsed_refs, p_revs):
+ if hasattr(remote, 'canpush') and not remote.canpush():
+ print "error cannot push"
+
+ if not p_revs:
+ # nothing to push
+ return
+
+ lock = None
+ unbundle = remote.capable('unbundle')
+ if not unbundle:
+ lock = remote.lock()
+ try:
+ ret = push_unsafe(repo, remote, parsed_refs, p_revs)
+ finally:
+ if lock is not None:
+ lock.release()
+
+ return ret
+
+def check_tip(ref, kind, name, heads):
+ try:
+ ename = '%s/%s' % (kind, name)
+ tip = marks.get_tip(ename)
+ except KeyError:
+ return True
+ else:
+ return tip in heads
def do_export(parser):
global parsed_refs, bmarks, peer
p_bmarks = []
+ p_revs = {}
parser.next()
@@ -827,72 +1014,114 @@ def do_export(parser):
else:
die('unhandled export command: %s' % line)
+ need_fetch = False
+
for ref, node in parsed_refs.iteritems():
+ bnode = hgbin(node) if node else None
if ref.startswith('refs/heads/branches'):
branch = ref[len('refs/heads/branches/'):]
- if branch in branches and node in branches[branch]:
+ if branch in branches and bnode in branches[branch]:
# up to date
continue
+
+ if peer:
+ remotemap = peer.branchmap()
+ if remotemap and branch in remotemap:
+ heads = [hghex(e) for e in remotemap[branch]]
+ if not check_tip(ref, 'branches', branch, heads):
+ print "error %s fetch first" % ref
+ need_fetch = True
+ continue
+
+ p_revs[bnode] = ref
print "ok %s" % ref
elif ref.startswith('refs/heads/'):
bmark = ref[len('refs/heads/'):]
- p_bmarks.append((bmark, node))
- continue
+ new = node
+ old = bmarks[bmark].hex() if bmark in bmarks else ''
+
+ if old == new:
+ continue
+
+ print "ok %s" % ref
+ if bmark != fake_bmark and \
+ not (bmark == 'master' and bmark not in parser.repo._bookmarks):
+ p_bmarks.append((ref, bmark, old, new))
+
+ if peer:
+ remote_old = peer.listkeys('bookmarks').get(bmark)
+ if remote_old:
+ if not check_tip(ref, 'bookmarks', bmark, remote_old):
+ print "error %s fetch first" % ref
+ need_fetch = True
+ continue
+
+ p_revs[bnode] = ref
elif ref.startswith('refs/tags/'):
+ if dry_run:
+ print "ok %s" % ref
+ continue
tag = ref[len('refs/tags/'):]
tag = hgref(tag)
author, msg = parsed_tags.get(tag, (None, None))
if mode == 'git':
if not msg:
- msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
- write_tag(parser.repo, tag, node, msg, author)
+ msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
+ tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
+ p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
else:
fp = parser.repo.opener('localtags', 'a')
- fp.write('%s %s\n' % (hghex(node), tag))
+ fp.write('%s %s\n' % (node, tag))
fp.close()
+ p_revs[bnode] = ref
print "ok %s" % ref
else:
# transport-helper/fast-export bugs
continue
- if peer:
- parser.repo.push(peer, force=force_push, newbranch=True)
-
- # handle bookmarks
- for bmark, node in p_bmarks:
- ref = 'refs/heads/' + bmark
- new = hghex(node)
-
- if bmark in bmarks:
- old = bmarks[bmark].hex()
- else:
- old = ''
+ if need_fetch:
+ print
+ return
- if old == new:
- continue
+ if dry_run:
+ if peer and not force_push:
+ checkheads(parser.repo, peer, p_revs)
+ print
+ return
- if bmark == 'master' and 'master' not in parser.repo._bookmarks:
- # fake bookmark
- print "ok %s" % ref
- continue
- elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
- # updated locally
- pass
- else:
- print "error %s" % ref
- continue
+ if peer:
+ if not push(parser.repo, peer, parsed_refs, p_revs):
+ # do not update bookmarks
+ print
+ return
- if peer:
- rb = peer.listkeys('bookmarks')
- old = rb.get(bmark, '')
+ # update remote bookmarks
+ remote_bmarks = peer.listkeys('bookmarks')
+ for ref, bmark, old, new in p_bmarks:
+ if force_push:
+ old = remote_bmarks.get(bmark, '')
if not peer.pushkey('bookmarks', bmark, old, new):
print "error %s" % ref
- continue
-
- print "ok %s" % ref
+ else:
+ # update local bookmarks
+ for ref, bmark, old, new in p_bmarks:
+ if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
+ print "error %s" % ref
print
+def do_option(parser):
+ global dry_run, force_push
+ _, key, value = parser.line.split(' ')
+ if key == 'dry-run':
+ dry_run = (value == 'true')
+ print 'ok'
+ elif key == 'force':
+ force_push = (value == 'true')
+ print 'ok'
+ else:
+ print 'unsupported'
+
def fix_path(alias, repo, orig_url):
url = urlparse.urlparse(orig_url, 'file')
if url.scheme != 'file' or os.path.isabs(url.path):
@@ -902,12 +1131,14 @@ def fix_path(alias, repo, orig_url):
subprocess.call(cmd)
def main(args):
- global prefix, dirname, branches, bmarks
+ global prefix, gitdir, dirname, branches, bmarks
global marks, blob_marks, parsed_refs
global peer, mode, bad_mail, bad_name
global track_branches, force_push, is_tmp
global parsed_tags
global filenodes
+ global fake_bmark, hg_version
+ global dry_run
alias = args[1]
url = args[2]
@@ -915,7 +1146,7 @@ def main(args):
hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
track_branches = get_config_bool('remote-hg.track-branches', True)
- force_push = get_config_bool('remote-hg.force-push')
+ force_push = False
if hg_git_compat:
mode = 'hg'
@@ -941,6 +1172,12 @@ def main(args):
marks = None
parsed_tags = {}
filenodes = {}
+ fake_bmark = None
+ try:
+ hg_version = tuple(int(e) for e in util.version().split('.'))
+ except:
+ hg_version = None
+ dry_run = False
repo = get_repo(url, alias)
prefix = 'refs/hg/%s' % alias
@@ -948,11 +1185,8 @@ def main(args):
if not is_tmp:
fix_path(alias, peer or repo, url)
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
marks_path = os.path.join(dirname, 'marks-hg')
- marks = Marks(marks_path)
+ marks = Marks(marks_path, repo)
if sys.platform == 'win32':
import msvcrt
@@ -968,6 +1202,8 @@ def main(args):
do_import(parser)
elif parser.check('export'):
do_export(parser)
+ elif parser.check('option'):
+ do_option(parser)
else:
die('unhandled command: %s' % line)
sys.stdout.flush()
diff --git a/contrib/remote-helpers/test-bzr.sh b/contrib/remote-helpers/test-bzr.sh
index 5dfa070..dce281f 100755
--- a/contrib/remote-helpers/test-bzr.sh
+++ b/contrib/remote-helpers/test-bzr.sh
@@ -12,86 +12,90 @@ if ! test_have_prereq PYTHON; then
test_done
fi
-if ! "$PYTHON_PATH" -c 'import bzrlib'; then
+if ! python -c 'import bzrlib'; then
skip_all='skipping remote-bzr tests; bzr not available'
test_done
fi
check () {
- (cd $1 &&
- git log --format='%s' -1 &&
- git symbolic-ref HEAD) > actual &&
- (echo $2 &&
- echo "refs/heads/$3") > expected &&
+ echo $3 > expected &&
+ git --git-dir=$1/.git log --format='%s' -1 $2 > actual
test_cmp expected actual
}
bzr whoami "A U Thor <author@example.com>"
test_expect_success 'cloning' '
- (bzr init bzrrepo &&
- cd bzrrepo &&
- echo one > content &&
- bzr add content &&
- bzr commit -m one
- ) &&
-
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
- check gitrepo one master
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
+ echo one > content &&
+ bzr add content &&
+ bzr commit -m one
+ ) &&
+
+ git clone "bzr::bzrrepo" gitrepo &&
+ check gitrepo HEAD one
'
test_expect_success 'pulling' '
- (cd bzrrepo &&
- echo two > content &&
- bzr commit -m two
- ) &&
+ (
+ cd bzrrepo &&
+ echo two > content &&
+ bzr commit -m two
+ ) &&
- (cd gitrepo && git pull) &&
+ (cd gitrepo && git pull) &&
- check gitrepo two master
+ check gitrepo HEAD two
'
test_expect_success 'pushing' '
- (cd gitrepo &&
- echo three > content &&
- git commit -a -m three &&
- git push
- ) &&
-
- echo three > expected &&
- cat bzrrepo/content > actual &&
- test_cmp expected actual
+ (
+ cd gitrepo &&
+ echo three > content &&
+ git commit -a -m three &&
+ git push
+ ) &&
+
+ echo three > expected &&
+ cat bzrrepo/content > actual &&
+ test_cmp expected actual
'
test_expect_success 'roundtrip' '
- (cd gitrepo &&
- git pull &&
- git log --format="%s" -1 origin/master > actual) &&
- echo three > expected &&
- test_cmp expected actual &&
+ (
+ cd gitrepo &&
+ git pull &&
+ git log --format="%s" -1 origin/master > actual
+ ) &&
+ echo three > expected &&
+ test_cmp expected actual &&
- (cd gitrepo && git push && git pull) &&
+ (cd gitrepo && git push && git pull) &&
- (cd bzrrepo &&
- echo four > content &&
- bzr commit -m four
- ) &&
+ (
+ cd bzrrepo &&
+ echo four > content &&
+ bzr commit -m four
+ ) &&
- (cd gitrepo && git pull && git push) &&
+ (cd gitrepo && git pull && git push) &&
- check gitrepo four master &&
+ check gitrepo HEAD four &&
- (cd gitrepo &&
- echo five > content &&
- git commit -a -m five &&
- git push && git pull
- ) &&
+ (
+ cd gitrepo &&
+ echo five > content &&
+ git commit -a -m five &&
+ git push && git pull
+ ) &&
- (cd bzrrepo && bzr revert) &&
+ (cd bzrrepo && bzr revert) &&
- echo five > expected &&
- cat bzrrepo/content > actual &&
- test_cmp expected actual
+ echo five > expected &&
+ cat bzrrepo/content > actual &&
+ test_cmp expected actual
'
cat > expected <<EOF
@@ -101,29 +105,35 @@ cat > expected <<EOF
EOF
test_expect_success 'special modes' '
- (cd bzrrepo &&
- echo exec > executable
- chmod +x executable &&
- bzr add executable
- bzr commit -m exec &&
- ln -s content link
- bzr add link
- bzr commit -m link &&
- mkdir dir &&
- bzr add dir &&
- bzr commit -m dir) &&
-
- (cd gitrepo &&
- git pull
- git ls-tree HEAD > ../actual) &&
-
- test_cmp expected actual &&
-
- (cd gitrepo &&
- git cat-file -p HEAD:link > ../actual) &&
-
- printf content > expected &&
- test_cmp expected actual
+ (
+ cd bzrrepo &&
+ echo exec > executable
+ chmod +x executable &&
+ bzr add executable
+ bzr commit -m exec &&
+ ln -s content link
+ bzr add link
+ bzr commit -m link &&
+ mkdir dir &&
+ bzr add dir &&
+ bzr commit -m dir
+ ) &&
+
+ (
+ cd gitrepo &&
+ git pull
+ git ls-tree HEAD > ../actual
+ ) &&
+
+ test_cmp expected actual &&
+
+ (
+ cd gitrepo &&
+ git cat-file -p HEAD:link > ../actual
+ ) &&
+
+ printf content > expected &&
+ test_cmp expected actual
'
cat > expected <<EOF
@@ -134,134 +144,145 @@ cat > expected <<EOF
EOF
test_expect_success 'moving directory' '
- (cd bzrrepo &&
- mkdir movedir &&
- echo one > movedir/one &&
- echo two > movedir/two &&
- bzr add movedir &&
- bzr commit -m movedir &&
- bzr mv movedir movedir-new &&
- bzr commit -m movedir-new) &&
-
- (cd gitrepo &&
- git pull &&
- git ls-tree HEAD > ../actual) &&
-
- test_cmp expected actual
+ (
+ cd bzrrepo &&
+ mkdir movedir &&
+ echo one > movedir/one &&
+ echo two > movedir/two &&
+ bzr add movedir &&
+ bzr commit -m movedir &&
+ bzr mv movedir movedir-new &&
+ bzr commit -m movedir-new
+ ) &&
+
+ (
+ cd gitrepo &&
+ git pull &&
+ git ls-tree HEAD > ../actual
+ ) &&
+
+ test_cmp expected actual
'
test_expect_success 'different authors' '
- (cd bzrrepo &&
- echo john >> content &&
- bzr commit -m john \
- --author "Jane Rey <jrey@example.com>" \
- --author "John Doe <jdoe@example.com>") &&
-
- (cd gitrepo &&
- git pull &&
- git show --format="%an <%ae>, %cn <%ce>" --quiet > ../actual) &&
-
- echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" > expected &&
- test_cmp expected actual
+ (
+ cd bzrrepo &&
+ echo john >> content &&
+ bzr commit -m john \
+ --author "Jane Rey <jrey@example.com>" \
+ --author "John Doe <jdoe@example.com>"
+ ) &&
+
+ (
+ cd gitrepo &&
+ git pull &&
+ git show --format="%an <%ae>, %cn <%ce>" --quiet > ../actual
+ ) &&
+
+ echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" > expected &&
+ test_cmp expected actual
'
+# cleanup previous stuff
+rm -rf bzrrepo gitrepo
+
test_expect_success 'fetch utf-8 filenames' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp && LC_ALL=C" &&
-
- LC_ALL=en_US.UTF-8
- export LC_ALL
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
-
- echo test >> "ærø" &&
- bzr add "ærø" &&
- echo test >> "ø~?" &&
- bzr add "ø~?" &&
- bzr commit -m add-utf-8 &&
- echo test >> "ærø" &&
- bzr commit -m test-utf-8 &&
- bzr rm "ø~?" &&
- bzr mv "ærø" "ø~?" &&
- bzr commit -m bzr-mv-utf-8
- ) &&
-
- (
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
- cd gitrepo &&
- git -c core.quotepath=false ls-files > ../actual
- ) &&
- echo "ø~?" > expected &&
- test_cmp expected actual
+ test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C" &&
+
+ LC_ALL=en_US.UTF-8
+ export LC_ALL
+
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
+
+ echo test >> "ærø" &&
+ bzr add "ærø" &&
+ echo test >> "ø~?" &&
+ bzr add "ø~?" &&
+ bzr commit -m add-utf-8 &&
+ echo test >> "ærø" &&
+ bzr commit -m test-utf-8 &&
+ bzr rm "ø~?" &&
+ bzr mv "ærø" "ø~?" &&
+ bzr commit -m bzr-mv-utf-8
+ ) &&
+
+ (
+ git clone "bzr::bzrrepo" gitrepo &&
+ cd gitrepo &&
+ git -c core.quotepath=false ls-files > ../actual
+ ) &&
+ echo "ø~?" > expected &&
+ test_cmp expected actual
'
test_expect_success 'push utf-8 filenames' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp && LC_ALL=C" &&
+ test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C" &&
+
+ mkdir -p tmp && cd tmp &&
- LC_ALL=en_US.UTF-8
- export LC_ALL
+ LC_ALL=en_US.UTF-8
+ export LC_ALL
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
- echo one >> content &&
- bzr add content &&
- bzr commit -m one
- ) &&
+ echo one >> content &&
+ bzr add content &&
+ bzr commit -m one
+ ) &&
- (
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
- cd gitrepo &&
+ (
+ git clone "bzr::bzrrepo" gitrepo &&
+ cd gitrepo &&
- echo test >> "ærø" &&
- git add "ærø" &&
- git commit -m utf-8 &&
+ echo test >> "ærø" &&
+ git add "ærø" &&
+ git commit -m utf-8 &&
- git push
- ) &&
+ git push
+ ) &&
- (cd bzrrepo && bzr ls > ../actual) &&
- printf "content\nærø\n" > expected &&
- test_cmp expected actual
+ (cd bzrrepo && bzr ls > ../actual) &&
+ printf "content\nærø\n" > expected &&
+ test_cmp expected actual
'
test_expect_success 'pushing a merge' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
- echo one > content &&
- bzr add content &&
- bzr commit -m one
- ) &&
-
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
-
- (
- cd bzrrepo &&
- echo two > content &&
- bzr commit -m two
- ) &&
-
- (
- cd gitrepo &&
- echo three > content &&
- git commit -a -m three &&
- git fetch &&
- git merge origin/master || true &&
- echo three > content &&
- git commit -a --no-edit &&
- git push
- ) &&
-
- echo three > expected &&
- cat bzrrepo/content > actual &&
- test_cmp expected actual
+ test_when_finished "rm -rf bzrrepo gitrepo" &&
+
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
+ echo one > content &&
+ bzr add content &&
+ bzr commit -m one
+ ) &&
+
+ git clone "bzr::bzrrepo" gitrepo &&
+
+ (
+ cd bzrrepo &&
+ echo two > content &&
+ bzr commit -m two
+ ) &&
+
+ (
+ cd gitrepo &&
+ echo three > content &&
+ git commit -a -m three &&
+ git fetch &&
+ git merge origin/master || true &&
+ echo three > content &&
+ git commit -a --no-edit &&
+ git push
+ ) &&
+
+ echo three > expected &&
+ cat bzrrepo/content > actual &&
+ test_cmp expected actual
'
cat > expected <<EOF
@@ -271,71 +292,70 @@ origin/trunk
EOF
test_expect_success 'proper bzr repo' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- bzr init-repo bzrrepo &&
-
- bzr init bzrrepo/trunk &&
- (
- cd bzrrepo/trunk &&
- echo one >> content &&
- bzr add content &&
- bzr commit -m one
- ) &&
-
- bzr branch bzrrepo/trunk bzrrepo/branch &&
- (
- cd bzrrepo/branch &&
- echo two >> content &&
- bzr commit -m one
- ) &&
-
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
- (
- cd gitrepo &&
- git for-each-ref --format "%(refname:short)" refs/remotes/origin > ../actual
- ) &&
-
- test_cmp ../expected actual
+ test_when_finished "rm -rf bzrrepo gitrepo" &&
+
+ bzr init-repo bzrrepo &&
+
+ (
+ bzr init bzrrepo/trunk &&
+ cd bzrrepo/trunk &&
+ echo one >> content &&
+ bzr add content &&
+ bzr commit -m one
+ ) &&
+
+ (
+ bzr branch bzrrepo/trunk bzrrepo/branch &&
+ cd bzrrepo/branch &&
+ echo two >> content &&
+ bzr commit -m one
+ ) &&
+
+ (
+ git clone "bzr::bzrrepo" gitrepo &&
+ cd gitrepo &&
+ git for-each-ref --format "%(refname:short)" refs/remotes/origin > ../actual
+ ) &&
+
+ test_cmp expected actual
'
test_expect_success 'strip' '
- # Do not imitate this style; always chdir inside a subshell instead
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf bzrrepo gitrepo" &&
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
- echo one >> content &&
- bzr add content &&
- bzr commit -m one &&
+ echo one >> content &&
+ bzr add content &&
+ bzr commit -m one &&
- echo two >> content &&
- bzr commit -m two
- ) &&
+ echo two >> content &&
+ bzr commit -m two
+ ) &&
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
+ git clone "bzr::bzrrepo" gitrepo &&
- (
- cd bzrrepo &&
- bzr uncommit --force &&
+ (
+ cd bzrrepo &&
+ bzr uncommit --force &&
- echo three >> content &&
- bzr commit -m three &&
+ echo three >> content &&
+ bzr commit -m three &&
- echo four >> content &&
- bzr commit -m four &&
- bzr log --line | sed -e "s/^[0-9][0-9]*: //" > ../expected
- ) &&
+ echo four >> content &&
+ bzr commit -m four &&
+ bzr log --line | sed -e "s/^[0-9][0-9]*: //" > ../expected
+ ) &&
- (cd gitrepo &&
- git fetch &&
- git log --format="%an %ad %s" --date=short origin/master > ../actual) &&
+ (
+ cd gitrepo &&
+ git fetch &&
+ git log --format="%an %ad %s" --date=short origin/master > ../actual
+ ) &&
- test_cmp expected actual
+ test_cmp expected actual
'
test_done
diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh
index f569697..f83d67d 100755
--- a/contrib/remote-helpers/test-hg-bidi.sh
+++ b/contrib/remote-helpers/test-hg-bidi.sh
@@ -15,23 +15,22 @@ if ! test_have_prereq PYTHON; then
test_done
fi
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
+if ! python -c 'import mercurial'; then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
# clone to a git repo
git_clone () {
- git clone -q "hg::$PWD/$1" $2
+ git clone -q "hg::$1" $2
}
# clone to an hg repo
hg_clone () {
(
hg init $2 &&
- hg -R $2 bookmark -i master &&
cd $1 &&
- git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
+ git push -q "hg::../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
) &&
(cd $2 && hg -q update)
@@ -41,17 +40,15 @@ hg_clone () {
hg_push () {
(
cd $2
- old=$(git symbolic-ref --short HEAD)
git checkout -q -b tmp &&
- git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
- git checkout -q $old &&
+ git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
+ git checkout -q @{-1} &&
git branch -q -D tmp 2> /dev/null || true
)
}
hg_log () {
- hg -R $1 log --graph --debug >log &&
- grep -v 'tag: *default/' log
+ hg -R $1 log --graph --debug
}
setup () {
@@ -67,6 +64,7 @@ setup () {
echo "graphlog ="
) >> "$HOME"/.hgrc &&
git config --global remote-hg.hg-git-compat true
+ git config --global remote-hg.track-branches true
HGEDITOR=/usr/bin/true
GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
@@ -77,8 +75,7 @@ setup () {
setup
test_expect_success 'encoding' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -115,8 +112,7 @@ test_expect_success 'encoding' '
'
test_expect_success 'file removal' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -148,8 +144,7 @@ test_expect_success 'file removal' '
'
test_expect_success 'git tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -177,8 +172,7 @@ test_expect_success 'git tags' '
'
test_expect_success 'hg branch' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -194,7 +188,7 @@ test_expect_success 'hg branch' '
hg_clone gitrepo hgrepo &&
cd hgrepo &&
- hg -q co master &&
+ hg -q co default &&
hg mv alpha beta &&
hg -q commit -m "rename alpha to beta" &&
hg branch gamma | grep -v "permanent and global" &&
@@ -214,8 +208,7 @@ test_expect_success 'hg branch' '
'
test_expect_success 'hg tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -231,7 +224,7 @@ test_expect_success 'hg tags' '
hg_clone gitrepo hgrepo &&
cd hgrepo &&
- hg co master &&
+ hg co default &&
hg tag alpha
) &&
diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh
index 7f579c8..2219284 100755
--- a/contrib/remote-helpers/test-hg-hg-git.sh
+++ b/contrib/remote-helpers/test-hg-hg-git.sh
@@ -15,19 +15,20 @@ if ! test_have_prereq PYTHON; then
test_done
fi
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
+if ! python -c 'import mercurial'; then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
-if ! "$PYTHON_PATH" -c 'import hggit'; then
+if ! python -c 'import hggit'; then
skip_all='skipping remote-hg tests; hg-git not available'
test_done
fi
# clone to a git repo with git
git_clone_git () {
- git clone -q "hg::$PWD/$1" $2
+ git clone -q "hg::$1" $2 &&
+ (cd $2 && git checkout master && git branch -D default)
}
# clone to an hg repo with git
@@ -36,7 +37,7 @@ hg_clone_git () {
hg init $2 &&
hg -R $2 bookmark -i master &&
cd $1 &&
- git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
+ git push -q "hg::../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
) &&
(cd $2 && hg -q update)
@@ -61,10 +62,10 @@ hg_clone_hg () {
hg_push_git () {
(
cd $2
- old=$(git symbolic-ref --short HEAD)
git checkout -q -b tmp &&
- git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
- git checkout -q $old &&
+ git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
+ git branch -D default &&
+ git checkout -q @{-1} &&
git branch -q -D tmp 2> /dev/null || true
)
}
@@ -104,18 +105,18 @@ setup () {
git config --global remote-hg.hg-git-compat true
git config --global remote-hg.track-branches false
- HGEDITOR=/usr/bin/true
+ HGEDITOR=true
+ HGMERGE=true
GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
- export HGEDITOR GIT_AUTHOR_DATE GIT_COMMITTER_DATE
+ export HGEDITOR HGMERGE GIT_AUTHOR_DATE GIT_COMMITTER_DATE
}
setup
test_expect_success 'executable bit' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -150,8 +151,7 @@ test_expect_success 'executable bit' '
'
test_expect_success 'symlink' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -181,8 +181,7 @@ test_expect_success 'symlink' '
'
test_expect_success 'merge conflict 1' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
hg init hgrepo1 &&
@@ -198,7 +197,7 @@ test_expect_success 'merge conflict 1' '
echo C > afile &&
hg ci -m "A->C" &&
- hg merge -r1 || true &&
+ hg merge -r1 &&
echo C > afile &&
hg resolve -m afile &&
hg ci -m "merge to C"
@@ -216,8 +215,7 @@ test_expect_success 'merge conflict 1' '
'
test_expect_success 'merge conflict 2' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
hg init hgrepo1 &&
@@ -251,8 +249,7 @@ test_expect_success 'merge conflict 2' '
'
test_expect_success 'converged merge' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
hg init hgrepo1 &&
@@ -287,8 +284,7 @@ test_expect_success 'converged merge' '
'
test_expect_success 'encoding' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -327,8 +323,7 @@ test_expect_success 'encoding' '
'
test_expect_success 'file removal' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -367,8 +362,7 @@ test_expect_success 'file removal' '
'
test_expect_success 'git tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
@@ -394,8 +388,7 @@ test_expect_success 'git tags' '
'
test_expect_success 'hg author' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
for x in hg git; do
(
@@ -461,8 +454,7 @@ test_expect_success 'hg author' '
'
test_expect_success 'hg branch' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
for x in hg git; do
(
@@ -498,8 +490,7 @@ test_expect_success 'hg branch' '
'
test_expect_success 'hg tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
for x in hg git; do
(
diff --git a/contrib/remote-helpers/test-hg.sh b/contrib/remote-helpers/test-hg.sh
index 8de2aa7..f7ce8aa 100755
--- a/contrib/remote-helpers/test-hg.sh
+++ b/contrib/remote-helpers/test-hg.sh
@@ -15,143 +15,678 @@ if ! test_have_prereq PYTHON; then
test_done
fi
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
+if ! python -c 'import mercurial'; then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
check () {
- (cd $1 &&
- git log --format='%s' -1 &&
- git symbolic-ref HEAD) > actual &&
- (echo $2 &&
- echo "refs/heads/$3") > expected &&
+ echo $3 > expected &&
+ git --git-dir=$1/.git log --format='%s' -1 $2 > actual
test_cmp expected actual
}
+check_branch () {
+ if [ -n "$3" ]; then
+ echo $3 > expected &&
+ hg -R $1 log -r $2 --template '{desc}\n' > actual &&
+ test_cmp expected actual
+ else
+ hg -R $1 branches > out &&
+ ! grep $2 out
+ fi
+}
+
+check_bookmark () {
+ if [ -n "$3" ]; then
+ echo $3 > expected &&
+ hg -R $1 log -r "bookmark('$2')" --template '{desc}\n' > actual &&
+ test_cmp expected actual
+ else
+ hg -R $1 bookmarks > out &&
+ ! grep $2 out
+ fi
+}
+
+check_push () {
+ local expected_ret=$1 ret=0 ref_ret=0 IFS=':'
+
+ shift
+ git push origin "$@" 2> error
+ ret=$?
+ cat error
+
+ while read branch kind
+ do
+ case "$kind" in
+ 'new')
+ grep "^ \* \[new branch\] *${branch} -> ${branch}$" error || ref_ret=1
+ ;;
+ 'non-fast-forward')
+ grep "^ ! \[rejected\] *${branch} -> ${branch} (non-fast-forward)$" error || ref_ret=1
+ ;;
+ 'fetch-first')
+ grep "^ ! \[rejected\] *${branch} -> ${branch} (fetch first)$" error || ref_ret=1
+ ;;
+ 'forced-update')
+ grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *${branch} -> ${branch} (forced update)$" error || ref_ret=1
+ ;;
+ '')
+ grep "^ [a-f0-9]*\.\.[a-f0-9]* *${branch} -> ${branch}$" error || ref_ret=1
+ ;;
+ esac
+ let 'ref_ret' && echo "match for '$branch' failed" && break
+ done
+
+ if let 'expected_ret != ret || ref_ret'
+ then
+ return 1
+ fi
+
+ return 0
+}
+
setup () {
(
echo "[ui]"
echo "username = H G Wells <wells@example.com>"
- ) >> "$HOME"/.hgrc
+ echo "[extensions]"
+ echo "mq ="
+ ) >> "$HOME"/.hgrc &&
+
+ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" &&
+ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" &&
+ export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
}
setup
test_expect_success 'cloning' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero > content &&
- hg add content &&
- hg commit -m zero
- ) &&
+ test_when_finished "rm -rf gitrepo*" &&
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo zero master
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo HEAD zero
'
test_expect_success 'cloning with branches' '
- test_when_finished "rm -rf gitrepo*" &&
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ cd hgrepo &&
+ hg branch next &&
+ echo next > content &&
+ hg commit -m next
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo origin/branches/next next
+'
+
+test_expect_success 'cloning with bookmarks' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ cd hgrepo &&
+ hg checkout default &&
+ hg bookmark feature-a &&
+ echo feature-a > content &&
+ hg commit -m feature-a
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo origin/feature-a feature-a
+'
+
+test_expect_success 'update bookmark' '
+ test_when_finished "rm -rf gitrepo*" &&
- (
- cd hgrepo &&
- hg branch next &&
- echo next > content &&
- hg commit -m next
- ) &&
+ (
+ cd hgrepo &&
+ hg bookmark devel
+ ) &&
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo next next &&
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet devel &&
+ echo devel > content &&
+ git commit -a -m devel &&
+ git push --quiet
+ ) &&
+
+ check_bookmark hgrepo devel devel
+'
- (cd hgrepo && hg checkout default) &&
+test_expect_success 'new bookmark' '
+ test_when_finished "rm -rf gitrepo*" &&
- git clone "hg::$PWD/hgrepo" gitrepo2 &&
- check gitrepo2 zero master
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet -b feature-b &&
+ echo feature-b > content &&
+ git commit -a -m feature-b &&
+ git push --quiet origin feature-b
+ ) &&
+
+ check_bookmark hgrepo feature-b feature-b
'
-test_expect_success 'cloning with bookmarks' '
- test_when_finished "rm -rf gitrepo*" &&
+# cleanup previous stuff
+rm -rf hgrepo
+
+author_test () {
+ echo $1 >> content &&
+ hg commit -u "$2" -m "add $1" &&
+ echo "$3" >> ../expected
+}
- (
- cd hgrepo &&
- hg bookmark feature-a &&
- echo feature-a > content &&
- hg commit -m feature-a
- ) &&
+test_expect_success 'authors' '
+ test_when_finished "rm -rf hgrepo gitrepo" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+
+ touch content &&
+ hg add content &&
+
+ > ../expected &&
+ author_test alpha "" "H G Wells <wells@example.com>" &&
+ author_test beta "test" "test <unknown>" &&
+ author_test beta "test <test@example.com> (comment)" "test <test@example.com>" &&
+ author_test gamma "<test@example.com>" "Unknown <test@example.com>" &&
+ author_test delta "name<test@example.com>" "name <test@example.com>" &&
+ author_test epsilon "name <test@example.com" "name <test@example.com>" &&
+ author_test zeta " test " "test <unknown>" &&
+ author_test eta "test < test@example.com >" "test <test@example.com>" &&
+ author_test theta "test >test@example.com>" "test <test@example.com>" &&
+ author_test iota "test < test <at> example <dot> com>" "test <unknown>" &&
+ author_test kappa "test@example.com" "Unknown <test@example.com>"
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual &&
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo feature-a feature-a
+ test_cmp expected actual
'
-test_expect_success 'cloning with detached head' '
- test_when_finished "rm -rf gitrepo*" &&
+test_expect_success 'strip' '
+ test_when_finished "rm -rf hgrepo gitrepo" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+
+ echo one >> content &&
+ hg add content &&
+ hg commit -m one &&
+
+ echo two >> content &&
+ hg commit -m two
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd hgrepo &&
+ hg strip 1 &&
- (
- cd hgrepo &&
- hg update -r 0
- ) &&
+ echo three >> content &&
+ hg commit -m three &&
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo zero master
+ echo four >> content &&
+ hg commit -m four
+ ) &&
+
+ (
+ cd gitrepo &&
+ git fetch &&
+ git log --format="%s" origin/master > ../actual
+ ) &&
+
+ hg -R hgrepo log --template "{desc}\n" > expected &&
+ test_cmp actual expected
'
-test_expect_success 'update bookmark' '
- test_when_finished "rm -rf gitrepo*" &&
+test_expect_success 'remote push with master bookmark' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
- (
- cd hgrepo &&
- hg bookmark devel
- ) &&
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero &&
+ hg bookmark master &&
+ echo one > content &&
+ hg commit -m one
+ ) &&
- (
- git clone "hg::$PWD/hgrepo" gitrepo &&
- cd gitrepo &&
- git checkout devel &&
- echo devel > content &&
- git commit -a -m devel &&
- git push
- ) &&
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ echo two > content &&
+ git commit -a -m two &&
+ git push
+ ) &&
+
+ check_branch hgrepo default two
+'
- hg -R hgrepo bookmarks | egrep "devel[ ]+3:"
+cat > expected <<EOF
+changeset: 0:6e2126489d3d
+tag: tip
+user: A U Thor <author@example.com>
+date: Mon Jan 01 00:00:00 2007 +0230
+summary: one
+
+EOF
+
+test_expect_success 'remote push from master branch' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ hg init hgrepo &&
+
+ (
+ git init gitrepo &&
+ cd gitrepo &&
+ git remote add origin "hg::../hgrepo" &&
+ echo one > content &&
+ git add content &&
+ git commit -a -m one &&
+ git push origin master
+ ) &&
+
+ hg -R hgrepo log > actual &&
+ cat actual &&
+ test_cmp expected actual &&
+
+ check_branch hgrepo default one
'
-author_test () {
- echo $1 >> content &&
- hg commit -u "$2" -m "add $1" &&
- echo "$3" >> ../expected
+GIT_REMOTE_HG_TEST_REMOTE=1
+export GIT_REMOTE_HG_TEST_REMOTE
+
+test_expect_success 'remote cloning' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo HEAD zero
+'
+
+test_expect_success 'remote update bookmark' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ cd hgrepo &&
+ hg bookmark devel
+ ) &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet devel &&
+ echo devel > content &&
+ git commit -a -m devel &&
+ git push --quiet
+ ) &&
+
+ check_bookmark hgrepo devel devel
+'
+
+test_expect_success 'remote new bookmark' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet -b feature-b &&
+ echo feature-b > content &&
+ git commit -a -m feature-b &&
+ git push --quiet origin feature-b
+ ) &&
+
+ check_bookmark hgrepo feature-b feature-b
+'
+
+test_expect_success 'remote push diverged' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd hgrepo &&
+ hg checkout default &&
+ echo bump > content &&
+ hg commit -m bump
+ ) &&
+
+ (
+ cd gitrepo &&
+ echo diverge > content &&
+ git commit -a -m diverged &&
+ check_push 1 <<-EOF
+ master:non-fast-forward
+ EOF
+ ) &&
+
+ check_branch hgrepo default bump
+'
+
+test_expect_success 'remote update bookmark diverge' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ cd hgrepo &&
+ hg checkout tip^ &&
+ hg bookmark diverge
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd hgrepo &&
+ echo "bump bookmark" > content &&
+ hg commit -m "bump bookmark"
+ ) &&
+
+ (
+ cd gitrepo &&
+ git checkout --quiet diverge &&
+ echo diverge > content &&
+ git commit -a -m diverge &&
+ check_push 1 <<-EOF
+ diverge:fetch-first
+ EOF
+ ) &&
+
+ check_bookmark hgrepo diverge "bump bookmark"
+'
+
+test_expect_success 'remote new bookmark multiple branch head' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet -b feature-c HEAD^ &&
+ echo feature-c > content &&
+ git commit -a -m feature-c &&
+ git push --quiet origin feature-c
+ ) &&
+
+ check_bookmark hgrepo feature-c feature-c
+'
+
+# cleanup previous stuff
+rm -rf hgrepo
+
+setup_big_push () {
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero &&
+ hg bookmark bad_bmark1 &&
+ echo one > content &&
+ hg commit -m one &&
+ hg bookmark bad_bmark2 &&
+ hg bookmark good_bmark &&
+ hg bookmark -i good_bmark &&
+ hg -q branch good_branch &&
+ echo "good branch" > content &&
+ hg commit -m "good branch" &&
+ hg -q branch bad_branch &&
+ echo "bad branch" > content &&
+ hg commit -m "bad branch"
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd gitrepo &&
+ echo two > content &&
+ git commit -q -a -m two &&
+
+ git checkout -q good_bmark &&
+ echo three > content &&
+ git commit -q -a -m three &&
+
+ git checkout -q bad_bmark1 &&
+ git reset --hard HEAD^ &&
+ echo four > content &&
+ git commit -q -a -m four &&
+
+ git checkout -q bad_bmark2 &&
+ git reset --hard HEAD^ &&
+ echo five > content &&
+ git commit -q -a -m five &&
+
+ git checkout -q -b new_bmark master &&
+ echo six > content &&
+ git commit -q -a -m six &&
+
+ git checkout -q branches/good_branch &&
+ echo seven > content &&
+ git commit -q -a -m seven &&
+ echo eight > content &&
+ git commit -q -a -m eight &&
+
+ git checkout -q branches/bad_branch &&
+ git reset --hard HEAD^ &&
+ echo nine > content &&
+ git commit -q -a -m nine &&
+
+ git checkout -q -b branches/new_branch master &&
+ echo ten > content &&
+ git commit -q -a -m ten
+ )
}
-test_expect_success 'authors' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
-
- touch content &&
- hg add content &&
-
- author_test alpha "" "H G Wells <wells@example.com>" &&
- author_test beta "test" "test <unknown>" &&
- author_test beta "test <test@example.com> (comment)" "test <test@example.com>" &&
- author_test gamma "<test@example.com>" "Unknown <test@example.com>" &&
- author_test delta "name<test@example.com>" "name <test@example.com>" &&
- author_test epsilon "name <test@example.com" "name <test@example.com>" &&
- author_test zeta " test " "test <unknown>" &&
- author_test eta "test < test@example.com >" "test <test@example.com>" &&
- author_test theta "test >test@example.com>" "test <test@example.com>" &&
- author_test iota "test < test <at> example <dot> com>" "test <unknown>" &&
- author_test kappa "test@example.com" "Unknown <test@example.com>"
- ) &&
-
- git clone "hg::$PWD/hgrepo" gitrepo &&
- git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual &&
-
- test_cmp expected actual
+test_expect_success 'remote big push' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ setup_big_push
+
+ (
+ cd gitrepo &&
+
+ check_push 1 --all <<-EOF
+ master
+ good_bmark
+ branches/good_branch
+ new_bmark:new
+ branches/new_branch:new
+ bad_bmark1:non-fast-forward
+ bad_bmark2:non-fast-forward
+ branches/bad_branch:non-fast-forward
+ EOF
+ ) &&
+
+ check_branch hgrepo default one &&
+ check_branch hgrepo good_branch "good branch" &&
+ check_branch hgrepo bad_branch "bad branch" &&
+ check_branch hgrepo new_branch '' &&
+ check_bookmark hgrepo good_bmark one &&
+ check_bookmark hgrepo bad_bmark1 one &&
+ check_bookmark hgrepo bad_bmark2 one &&
+ check_bookmark hgrepo new_bmark ''
+'
+
+test_expect_success 'remote big push fetch first' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero &&
+ hg bookmark bad_bmark &&
+ hg bookmark good_bmark &&
+ hg bookmark -i good_bmark &&
+ hg -q branch good_branch &&
+ echo "good branch" > content &&
+ hg commit -m "good branch" &&
+ hg -q branch bad_branch &&
+ echo "bad branch" > content &&
+ hg commit -m "bad branch"
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd hgrepo &&
+ hg bookmark -f bad_bmark &&
+ echo update_bmark > content &&
+ hg commit -m "update bmark"
+ ) &&
+
+ (
+ cd gitrepo &&
+ echo two > content &&
+ git commit -q -a -m two &&
+
+ git checkout -q good_bmark &&
+ echo three > content &&
+ git commit -q -a -m three &&
+
+ git checkout -q bad_bmark &&
+ echo four > content &&
+ git commit -q -a -m four &&
+
+ git checkout -q branches/bad_branch &&
+ echo five > content &&
+ git commit -q -a -m five &&
+
+ check_push 1 --all <<-EOF
+ master
+ good_bmark
+ new_bmark:new
+ new_branch:new
+ bad_bmark:fetch-first
+ branches/bad_branch:festch-first
+ EOF
+
+ git fetch &&
+
+ check_push 1 --all <<-EOF
+ master
+ good_bmark
+ bad_bmark:non-fast-forward
+ branches/bad_branch:non-fast-forward
+ EOF
+ )
+'
+
+test_expect_failure 'remote big push force' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ setup_big_push
+
+ (
+ cd gitrepo &&
+
+ check_push 0 --force --all <<-EOF
+ master
+ good_bmark
+ branches/good_branch
+ new_bmark:new
+ branches/new_branch:new
+ bad_bmark1:forced-update
+ bad_bmark2:forced-update
+ branches/bad_branch:forced-update
+ EOF
+ ) &&
+
+ check_branch hgrepo default six &&
+ check_branch hgrepo good_branch eight &&
+ check_branch hgrepo bad_branch nine &&
+ check_branch hgrepo new_branch ten &&
+ check_bookmark hgrepo good_bmark three &&
+ check_bookmark hgrepo bad_bmark1 four &&
+ check_bookmark hgrepo bad_bmark2 five &&
+ check_bookmark hgrepo new_bmark six
+'
+
+test_expect_failure 'remote big push dry-run' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ setup_big_push
+
+ (
+ cd gitrepo &&
+
+ check_push 0 --dry-run --all <<-EOF
+ master
+ good_bmark
+ branches/good_branch
+ new_bmark:new
+ branches/new_branch:new
+ bad_bmark1:non-fast-forward
+ bad_bmark2:non-fast-forward
+ branches/bad_branch:non-fast-forward
+ EOF
+
+ check_push 0 --dry-run master good_bmark new_bmark branches/good_branch branches/new_branch <<-EOF
+ master
+ good_bmark
+ branches/good_branch
+ new_bmark:new
+ branches/new_branch:new
+ EOF
+ ) &&
+
+ check_branch hgrepo default one &&
+ check_branch hgrepo good_branch "good branch" &&
+ check_branch hgrepo bad_branch "bad branch" &&
+ check_branch hgrepo new_branch '' &&
+ check_bookmark hgrepo good_bmark one &&
+ check_bookmark hgrepo bad_bmark1 one &&
+ check_bookmark hgrepo bad_bmark2 one &&
+ check_bookmark hgrepo new_bmark ''
+'
+
+test_expect_success 'remote double failed push' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero &&
+ echo one > content &&
+ hg commit -m one
+ ) &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git reset --hard HEAD^ &&
+ echo two > content &&
+ git commit -a -m two &&
+ test_expect_code 1 git push &&
+ test_expect_code 1 git push
+ )
'
test_done
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 8a23f58..51ae932 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
#
# git-subtree.sh: split/join git repositories in subdirectories of this one
#
@@ -715,7 +715,8 @@ cmd_push()
repository=$1
refspec=$2
echo "git push using: " $repository $refspec
- git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
+ localrev=$(git subtree split --prefix="$prefix") || die
+ git push $repository $localrev:refs/heads/$refspec
else
die "'$dir' must already exist. Try 'git subtree add'."
fi
diff --git a/credential-store.c b/credential-store.c
index 26f7589..f9146e5 100644
--- a/credential-store.c
+++ b/credential-store.c
@@ -114,7 +114,7 @@ static int lookup_credential(const char *fn, struct credential *c)
return c->username && c->password;
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
const char * const usage[] = {
"git credential-store [options] <action>",
@@ -131,7 +131,7 @@ int main(int argc, const char **argv)
umask(077);
- argc = parse_options(argc, argv, NULL, options, usage, 0);
+ argc = parse_options(argc, (const char **)argv, NULL, options, usage, 0);
if (argc != 1)
usage_with_options(usage, options);
op = argv[0];
diff --git a/date.c b/date.c
index df20d0b..29f1540 100644
--- a/date.c
+++ b/date.c
@@ -711,6 +711,28 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
return 0; /* success */
}
+int parse_expiry_date(const char *date, unsigned long *timestamp)
+{
+ int errors = 0;
+
+ if (!strcmp(date, "never") || !strcmp(date, "false"))
+ *timestamp = 0;
+ else if (!strcmp(date, "all") || !strcmp(date, "now"))
+ /*
+ * We take over "now" here, which usually translates
+ * to the current timestamp. This is because the user
+ * really means to expire everything she has done in
+ * the past, and by definition reflogs are the record
+ * of the past, and there is nothing from the future
+ * to be kept.
+ */
+ *timestamp = ULONG_MAX;
+ else
+ *timestamp = approxidate_careful(date, &errors);
+
+ return errors;
+}
+
int parse_date(const char *date, char *result, int maxlen)
{
unsigned long timestamp;
diff --git a/decorate.c b/decorate.c
index 2f8a63e..7cb5d29 100644
--- a/decorate.c
+++ b/decorate.c
@@ -49,7 +49,7 @@ static void grow_decoration(struct decoration *n)
const struct object *base = old_hash[i].base;
void *decoration = old_hash[i].decoration;
- if (!base)
+ if (!decoration)
continue;
insert_decoration(n, base, decoration);
}
diff --git a/diff-lib.c b/diff-lib.c
index f35de0f..b6f4b21 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -64,8 +64,9 @@ static int check_removed(const struct cache_entry *ce, struct stat *st)
* commits, untracked content and/or modified content).
*/
static int match_stat_with_submodule(struct diff_options *diffopt,
- struct cache_entry *ce, struct stat *st,
- unsigned ce_option, unsigned *dirty_submodule)
+ const struct cache_entry *ce,
+ struct stat *st, unsigned ce_option,
+ unsigned *dirty_submodule)
{
int changed = ce_match_stat(ce, st, ce_option);
if (S_ISGITLINK(ce->ce_mode)) {
@@ -237,7 +238,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
/* A file entry went away or appeared */
static void diff_index_show_file(struct rev_info *revs,
const char *prefix,
- struct cache_entry *ce,
+ const struct cache_entry *ce,
const unsigned char *sha1, int sha1_valid,
unsigned int mode,
unsigned dirty_submodule)
@@ -246,7 +247,7 @@ static void diff_index_show_file(struct rev_info *revs,
sha1, sha1_valid, ce->name, dirty_submodule);
}
-static int get_stat_data(struct cache_entry *ce,
+static int get_stat_data(const struct cache_entry *ce,
const unsigned char **sha1p,
unsigned int *modep,
int cached, int match_missing,
@@ -283,7 +284,7 @@ static int get_stat_data(struct cache_entry *ce,
}
static void show_new_file(struct rev_info *revs,
- struct cache_entry *new,
+ const struct cache_entry *new,
int cached, int match_missing)
{
const unsigned char *sha1;
@@ -302,8 +303,8 @@ static void show_new_file(struct rev_info *revs,
}
static int show_modified(struct rev_info *revs,
- struct cache_entry *old,
- struct cache_entry *new,
+ const struct cache_entry *old,
+ const struct cache_entry *new,
int report_missing,
int cached, int match_missing)
{
@@ -362,8 +363,8 @@ static int show_modified(struct rev_info *revs,
* give you the position and number of entries in the index).
*/
static void do_oneway_diff(struct unpack_trees_options *o,
- struct cache_entry *idx,
- struct cache_entry *tree)
+ const struct cache_entry *idx,
+ const struct cache_entry *tree)
{
struct rev_info *revs = o->unpack_data;
int match_missing, cached;
@@ -423,10 +424,11 @@ static void do_oneway_diff(struct unpack_trees_options *o,
* the fairly complex unpack_trees() semantic requirements, including
* the skipping, the path matching, the type conflict cases etc.
*/
-static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
+static int oneway_diff(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
- struct cache_entry *idx = src[0];
- struct cache_entry *tree = src[1];
+ const struct cache_entry *idx = src[0];
+ const struct cache_entry *tree = src[1];
struct rev_info *revs = o->unpack_data;
/*
diff --git a/diff-no-index.c b/diff-no-index.c
index 74da659..e66fdf3 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -45,7 +45,7 @@ static int get_mode(const char *path, int *mode)
if (!path || !strcmp(path, "/dev/null"))
*mode = 0;
-#ifdef _WIN32
+#ifdef GIT_WINDOWS_NATIVE
else if (!strcasecmp(path, "nul"))
*mode = 0;
#endif
diff --git a/diff.c b/diff.c
index 5290306..0b1b5e7 100644
--- a/diff.c
+++ b/diff.c
@@ -2677,6 +2677,14 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
int diff_populate_filespec(struct diff_filespec *s, int size_only)
{
int err = 0;
+ /*
+ * demote FAIL to WARN to allow inspecting the situation
+ * instead of refusing.
+ */
+ enum safe_crlf crlf_warn = (safe_crlf == SAFE_CRLF_FAIL
+ ? SAFE_CRLF_WARN
+ : safe_crlf);
+
if (!DIFF_FILE_VALID(s))
die("internal error: asking to populate invalid file.");
if (S_ISDIR(s->mode))
@@ -2732,7 +2740,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
/*
* Convert from working tree format to canonical git format
*/
- if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) {
+ if (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) {
size_t size = 0;
munmap(s->data, s->size);
s->should_munmap = 0;
@@ -3599,6 +3607,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
else if (!strcmp(arg, "--ignore-space-at-eol"))
DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
+ else if (!strcmp(arg, "--ignore-blank-lines"))
+ DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
else if (!strcmp(arg, "--patience"))
options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
else if (!strcmp(arg, "--histogram"))
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c
index 63722f8..401eb72 100644
--- a/diffcore-pickaxe.c
+++ b/diffcore-pickaxe.c
@@ -122,7 +122,7 @@ static void diffcore_pickaxe_grep(struct diff_options *o)
char errbuf[1024];
regerror(err, &regex, errbuf, 1024);
regfree(&regex);
- die("invalid log-grep regex: %s", errbuf);
+ die("invalid regex: %s", errbuf);
}
pickaxe(&diff_queued_diff, o, &regex, NULL, diff_grep);
@@ -131,8 +131,7 @@ static void diffcore_pickaxe_grep(struct diff_options *o)
return;
}
-static unsigned int contains(mmfile_t *mf, struct diff_options *o,
- regex_t *regexp, kwset_t kws)
+static unsigned int contains(mmfile_t *mf, regex_t *regexp, kwset_t kws)
{
unsigned int cnt;
unsigned long sz;
@@ -176,11 +175,9 @@ static int has_changes(mmfile_t *one, mmfile_t *two,
struct diff_options *o,
regex_t *regexp, kwset_t kws)
{
- if (!one)
- return contains(two, o, regexp, kws) != 0;
- if (!two)
- return contains(one, o, regexp, kws) != 0;
- return contains(one, o, regexp, kws) != contains(two, o, regexp, kws);
+ unsigned int one_contains = one ? contains(one, regexp, kws) : 0;
+ unsigned int two_contains = two ? contains(two, regexp, kws) : 0;
+ return one_contains != two_contains;
}
static int pickaxe_match(struct diff_filepair *p, struct diff_options *o,
@@ -246,7 +243,7 @@ static void diffcore_pickaxe_count(struct diff_options *o)
char errbuf[1024];
regerror(err, &regex, errbuf, 1024);
regfree(&regex);
- die("invalid pickaxe regex: %s", errbuf);
+ die("invalid regex: %s", errbuf);
}
regexp = &regex;
} else {
diff --git a/dir.c b/dir.c
index a5926fb..0480419 100644
--- a/dir.c
+++ b/dir.c
@@ -821,6 +821,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
dir->basebuf, stk->baselen - 1,
dir->basebuf + current, &dt);
dir->basebuf[stk->baselen - 1] = '/';
+ if (dir->exclude &&
+ dir->exclude->flags & EXC_FLAG_NEGATIVE)
+ dir->exclude = NULL;
if (dir->exclude) {
dir->basebuf[stk->baselen] = 0;
dir->exclude_stack = stk;
@@ -1033,9 +1036,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
return path_recurse;
case index_gitdir:
- if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
- return path_none;
- return path_untracked;
+ return path_none;
case index_nonexistent:
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
@@ -1542,9 +1543,9 @@ void setup_standard_excludes(struct dir_struct *dir)
home_config_paths(NULL, &xdg_path, "ignore");
excludes_file = xdg_path;
}
- if (!access_or_warn(path, R_OK))
+ if (!access_or_warn(path, R_OK, 0))
add_excludes_from_file(dir, path);
- if (excludes_file && !access_or_warn(excludes_file, R_OK))
+ if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
add_excludes_from_file(dir, excludes_file);
}
diff --git a/environment.c b/environment.c
index e2e75c1..0cb67b2 100644
--- a/environment.c
+++ b/environment.c
@@ -37,7 +37,6 @@ 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;
diff --git a/fast-import.c b/fast-import.c
index 5f539d7..23f625f 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -297,7 +297,7 @@ static int failure;
static FILE *pack_edges;
static unsigned int show_stats = 1;
static int global_argc;
-static const char **global_argv;
+static char **global_argv;
/* Memory pools */
static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool);
@@ -1822,7 +1822,7 @@ static void read_marks(void)
*end = 0;
mark = strtoumax(line + 1, &end, 10);
if (!mark || end == line + 1
- || *end != ' ' || get_sha1(end + 1, sha1))
+ || *end != ' ' || get_sha1_hex(end + 1, sha1))
die("corrupt mark line: %s", line);
e = find_object(sha1);
if (!e) {
@@ -3347,7 +3347,7 @@ static void parse_argv(void)
read_marks();
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
unsigned int i;
diff --git a/fetch-pack.c b/fetch-pack.c
index f156dd4..6684348 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -11,6 +11,7 @@
#include "run-command.h"
#include "transport.h"
#include "version.h"
+#include "prio-queue.h"
static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
@@ -20,6 +21,8 @@ static int no_done;
static int fetch_fsck_objects = -1;
static int transfer_fsck_objects = -1;
static int agent_supported;
+static struct lock_file shallow_lock;
+static const char *alternate_shallow_file;
#define COMPLETE (1U << 0)
#define COMMON (1U << 1)
@@ -35,7 +38,7 @@ static int marked;
*/
#define MAX_IN_VAIN 256
-static struct commit_list *rev_list;
+static struct prio_queue rev_list = { compare_commits_by_commit_date };
static int non_common_revs, multi_ack, use_sideband, allow_tip_sha1_in_want;
static void rev_list_push(struct commit *commit, int mark)
@@ -47,7 +50,7 @@ static void rev_list_push(struct commit *commit, int mark)
if (parse_commit(commit))
return;
- commit_list_insert_by_date(commit, &rev_list);
+ prio_queue_put(&rev_list, commit);
if (!(commit->object.flags & COMMON))
non_common_revs++;
@@ -120,10 +123,10 @@ static const unsigned char *get_rev(void)
unsigned int mark;
struct commit_list *parents;
- if (rev_list == NULL || non_common_revs == 0)
+ if (rev_list.nr == 0 || non_common_revs == 0)
return NULL;
- commit = rev_list->item;
+ commit = prio_queue_get(&rev_list);
if (!commit->object.parsed)
parse_commit(commit);
parents = commit->parents;
@@ -150,8 +153,6 @@ static const unsigned char *get_rev(void)
mark_common(parents->item, 1, 0);
parents = parents->next;
}
-
- rev_list = rev_list->next;
}
return commit->object.sha1;
@@ -440,7 +441,7 @@ static int find_common(struct fetch_pack_args *args,
in_vain = 0;
got_continue = 1;
if (ack == ACK_ready) {
- rev_list = NULL;
+ clear_prio_queue(&rev_list);
got_ready = 1;
}
break;
@@ -503,7 +504,7 @@ static int mark_complete(const char *refname, const unsigned char *sha1, int fla
struct commit *commit = (struct commit *)o;
if (!(commit->object.flags & COMPLETE)) {
commit->object.flags |= COMPLETE;
- commit_list_insert_by_date(commit, &complete);
+ commit_list_insert(commit, &complete);
}
}
return 0;
@@ -620,6 +621,7 @@ static int everything_local(struct fetch_pack_args *args,
if (!args->depth) {
for_each_ref(mark_complete, NULL);
for_each_alternate_ref(mark_alternate_complete, NULL);
+ commit_list_sort_by_date(&complete);
if (cutoff)
mark_recent_complete_commits(args, cutoff);
}
@@ -683,12 +685,13 @@ static int get_pack(struct fetch_pack_args *args,
int xd[2], char **pack_lockfile)
{
struct async demux;
- const char *argv[20];
+ const char *argv[22];
char keep_arg[256];
char hdr_arg[256];
const char **av;
int do_keep = args->keep_pack;
struct child_process cmd;
+ int ret;
memset(&demux, 0, sizeof(demux));
if (use_sideband) {
@@ -724,6 +727,11 @@ static int get_pack(struct fetch_pack_args *args,
do_keep = 1;
}
+ if (alternate_shallow_file) {
+ *av++ = "--shallow-file";
+ *av++ = alternate_shallow_file;
+ }
+
if (do_keep) {
if (pack_lockfile)
cmd.out = -1;
@@ -740,11 +748,14 @@ static int get_pack(struct fetch_pack_args *args,
strcpy(keep_arg + s, "localhost");
*av++ = keep_arg;
}
+ if (args->check_self_contained_and_connected)
+ *av++ = "--check-self-contained-and-connected";
}
else {
*av++ = "unpack-objects";
if (args->quiet || args->no_progress)
*av++ = "-q";
+ args->check_self_contained_and_connected = 0;
}
if (*hdr_arg)
*av++ = hdr_arg;
@@ -765,7 +776,12 @@ static int get_pack(struct fetch_pack_args *args,
close(cmd.out);
}
- if (finish_command(&cmd))
+ ret = finish_command(&cmd);
+ if (!ret || (args->check_self_contained_and_connected && ret == 1))
+ args->self_contained_and_connected =
+ args->check_self_contained_and_connected &&
+ ret == 0;
+ else
die("%s failed", argv[0]);
if (use_sideband && finish_async(&demux))
die("error in sideband demultiplexer");
@@ -779,6 +795,27 @@ static int cmp_ref_by_name(const void *a_, const void *b_)
return strcmp(a->name, b->name);
}
+static void setup_alternate_shallow(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int fd;
+
+ check_shallow_file_for_update();
+ fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"),
+ LOCK_DIE_ON_ERROR);
+ if (write_shallow_commits(&sb, 0)) {
+ if (write_in_full(fd, sb.buf, sb.len) != sb.len)
+ die_errno("failed to write to %s", shallow_lock.filename);
+ alternate_shallow_file = shallow_lock.filename;
+ } else
+ /*
+ * is_repository_shallow() sees empty string as "no
+ * shallow file".
+ */
+ alternate_shallow_file = "";
+ strbuf_release(&sb);
+}
+
static struct ref *do_fetch_pack(struct fetch_pack_args *args,
int fd[2],
const struct ref *orig_ref,
@@ -858,6 +895,8 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
if (args->stateless_rpc)
packet_flush(fd[1]);
+ if (args->depth > 0)
+ setup_alternate_shallow();
if (get_pack(args, fd, pack_lockfile))
die("git fetch-pack: fetch failed.");
@@ -936,15 +975,9 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
struct ref **sought, int nr_sought,
char **pack_lockfile)
{
- struct stat st;
struct ref *ref_cpy;
fetch_pack_setup();
- if (args->depth > 0) {
- if (stat(git_path("shallow"), &st))
- st.st_mtime = 0;
- }
-
if (nr_sought)
nr_sought = remove_duplicates_in_refs(sought, nr_sought);
@@ -954,35 +987,12 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
}
ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile);
- if (args->depth > 0) {
- static struct lock_file lock;
- struct cache_time mtime;
- struct strbuf sb = STRBUF_INIT;
- char *shallow = git_path("shallow");
- int fd;
-
- mtime.sec = st.st_mtime;
- mtime.nsec = ST_MTIME_NSEC(st);
- if (stat(shallow, &st)) {
- if (mtime.sec)
- die("shallow file was removed during fetch");
- } else if (st.st_mtime != mtime.sec
-#ifdef USE_NSEC
- || ST_MTIME_NSEC(st) != mtime.nsec
-#endif
- )
- die("shallow file was changed during fetch");
-
- fd = hold_lock_file_for_update(&lock, shallow,
- LOCK_DIE_ON_ERROR);
- if (!write_shallow_commits(&sb, 0)
- || write_in_full(fd, sb.buf, sb.len) != sb.len) {
- unlink_or_warn(shallow);
- rollback_lock_file(&lock);
- } else {
- commit_lock_file(&lock);
- }
- strbuf_release(&sb);
+ if (alternate_shallow_file) {
+ if (*alternate_shallow_file == '\0') { /* --unshallow */
+ unlink_or_warn(git_path("shallow"));
+ rollback_lock_file(&shallow_lock);
+ } else
+ commit_lock_file(&shallow_lock);
}
reprepare_packed_git();
diff --git a/fetch-pack.h b/fetch-pack.h
index dc5266c..40f08ba 100644
--- a/fetch-pack.h
+++ b/fetch-pack.h
@@ -16,7 +16,9 @@ struct fetch_pack_args {
verbose:1,
no_progress:1,
include_tag:1,
- stateless_rpc:1;
+ stateless_rpc:1,
+ check_self_contained_and_connected:1,
+ self_contained_and_connected:1;
};
/*
diff --git a/fixup-builtins b/fixup-builtins
deleted file mode 100755
index 63dfa4c..0000000
--- a/fixup-builtins
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-while [ "$1" ]
-do
- if [ "$1" != "git-sh-setup" -a "$1" != "git-parse-remote" -a "$1" != "git-svn" ]; then
- old="$1"
- new=$(echo "$1" | sed 's/git-/git /')
- echo "Converting '$old' to '$new'"
- sed -i "s/\\<$old\\>/$new/g" $(git ls-files '*.sh')
- fi
- shift
-done
-
-sed -i 's/git merge-one-file/git-merge-one-file/g
-s/git rebase-todo/git-rebase-todo/g' $(git ls-files '*.sh')
-git update-index --refresh >& /dev/null
-exit 0
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index d2c4ce6..75a991f 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -44,6 +44,8 @@ my ($diff_new_color) =
my $normal_color = $repo->get_color("", "reset");
+my $diff_algorithm = $repo->config('diff.algorithm');
+
my $use_readkey = 0;
my $use_termcap = 0;
my %term_escapes;
@@ -731,6 +733,9 @@ sub run_git_apply {
sub parse_diff {
my ($path) = @_;
my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
+ if (defined $diff_algorithm) {
+ splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
+ }
if (defined $patch_mode_revision) {
push @diff_cmd, $patch_mode_revision;
}
diff --git a/git-am.sh b/git-am.sh
index c092855..7ea40fe 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -6,7 +6,7 @@ SUBDIRECTORY_OK=Yes
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
git am [options] [(<mbox>|<Maildir>)...]
-git am [options] (--resolved | --skip | --abort)
+git am [options] (--continue | --skip | --abort)
--
i,interactive run interactively
b,binary* (historical option -- no-op)
@@ -102,7 +102,7 @@ stop_here_user_resolve () {
printf '%s\n' "$resolvemsg"
stop_here $1
fi
- eval_gettextln "When you have resolved this problem, run \"\$cmdline --resolved\".
+ eval_gettextln "When you have resolved this problem, run \"\$cmdline --continue\".
If you prefer to skip this patch, run \"\$cmdline --skip\" instead.
To restore the original branch and stop patching, run \"\$cmdline --abort\"."
@@ -446,6 +446,8 @@ done
# If the dotest directory exists, but we have finished applying all the
# patches in them, clear it out.
if test -d "$dotest" &&
+ test -f "$dotest/last" &&
+ test -f "$dotest/next" &&
last=$(cat "$dotest/last") &&
next=$(cat "$dotest/next") &&
test $# != 0 &&
@@ -454,7 +456,7 @@ then
rm -fr "$dotest"
fi
-if test -d "$dotest"
+if test -d "$dotest" && test -f "$dotest/last" && test -f "$dotest/next"
then
case "$#,$skip$resolved$abort" in
0,*t*)
@@ -504,7 +506,24 @@ then
esac
rm -f "$dotest/dirtyindex"
else
- # Make sure we are not given --skip, --resolved, nor --abort
+ # Possible stray $dotest directory in the independent-run
+ # case; in the --rebasing case, it is upto the caller
+ # (git-rebase--am) to take care of stray directories.
+ if test -d "$dotest" && test -z "$rebasing"
+ then
+ case "$skip,$resolved,$abort" in
+ ,,t)
+ rm -fr "$dotest"
+ exit 0
+ ;;
+ *)
+ die "$(eval_gettext "Stray \$dotest directory found.
+Use \"git am --abort\" to remove it.")"
+ ;;
+ esac
+ fi
+
+ # Make sure we are not given --skip, --continue, nor --abort
test "$skip$resolved$abort" = "" ||
die "$(gettext "Resolve operation not in progress, we are not resuming.")"
@@ -651,7 +670,7 @@ do
# - patch is the patch body.
#
# When we are resuming, these files are either already prepared
- # by the user, or the user can tell us to do so by --resolved flag.
+ # by the user, or the user can tell us to do so by --continue flag.
case "$resume" in
'')
if test -f "$dotest/rebasing"
@@ -904,5 +923,10 @@ if test -s "$dotest"/rewritten; then
fi
fi
-rm -fr "$dotest"
-git gc --auto
+# If am was called with --rebasing (from git-rebase--am), it's up to
+# the caller to take care of housekeeping.
+if ! test -f "$dotest/rebasing"
+then
+ rm -fr "$dotest"
+ git gc --auto
+fi
diff --git a/git-bisect.sh b/git-bisect.sh
index d7518e9..9f064b6 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -320,8 +320,8 @@ bisect_next() {
elif test $res -eq 2
then
echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
- good_revs=$(git for-each-ref --format="--not %(objectname)" "refs/bisect/good-*")
- for skipped in $(git rev-list refs/bisect/bad $good_revs)
+ good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/good-*")
+ for skipped in $(git rev-list refs/bisect/bad --not $good_revs)
do
skipped_commit=$(git show-branch $skipped)
echo "# possible first bad commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
diff --git a/git-compat-util.h b/git-compat-util.h
index e955bb5..ff193f4 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -85,13 +85,14 @@
#define _NETBSD_SOURCE 1
#define _SGI_SOURCE 1
-#ifdef WIN32 /* Both MinGW and MSVC */
+#if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */
# if defined (_MSC_VER)
# define _WIN32_WINNT 0x0502
# endif
#define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */
#include <winsock2.h>
#include <windows.h>
+#define GIT_WINDOWS_NATIVE
#endif
#include <unistd.h>
@@ -127,6 +128,9 @@
#else
#include <poll.h>
#endif
+
+extern int get_st_mode_bits(const char *path, int *mode);
+
#if defined(__MINGW32__)
/* pull in Windows compatibility stuff */
#include "compat/mingw.h"
@@ -163,7 +167,6 @@
typedef long intptr_t;
typedef unsigned long uintptr_t;
#endif
-int get_st_mode_bits(const char *path, int *mode);
#if defined(__CYGWIN__)
#undef _XOPEN_SOURCE
#include <grp.h>
@@ -185,6 +188,11 @@ int get_st_mode_bits(const char *path, int *mode);
#define probe_utf8_pathname_composition(a,b)
#endif
+#ifdef NEEDS_CLIPPED_WRITE
+ssize_t clipped_write(int fildes, const void *buf, size_t nbyte);
+#define write(x,y,z) clipped_write((x),(y),(z))
+#endif
+
#ifdef MKDIR_WO_TRAILING_SLASH
#define mkdir(a,b) compat_mkdir_wo_trailing_slash((a),(b))
extern int compat_mkdir_wo_trailing_slash(const char*, mode_t);
@@ -692,8 +700,9 @@ int remove_or_warn(unsigned int mode, const char *path);
* Call access(2), but warn for any error except "missing file"
* (ENOENT or ENOTDIR).
*/
-int access_or_warn(const char *path, int mode);
-int access_or_die(const char *path, int mode);
+#define ACCESS_EACCES_OK (1U << 0)
+int access_or_warn(const char *path, int mode, unsigned flag);
+int access_or_die(const char *path, int mode, unsigned flag);
/* Warn on an inaccessible file that ought to be accessible */
void warn_on_inaccessible(const char *path);
diff --git a/git-difftool.perl b/git-difftool.perl
index 8a75205..e57d3d12 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -85,13 +85,9 @@ sub exit_cleanup
sub use_wt_file
{
- my ($repo, $workdir, $file, $sha1, $symlinks) = @_;
+ my ($repo, $workdir, $file, $sha1) = @_;
my $null_sha1 = '0' x 40;
- if ($sha1 ne $null_sha1 and not $symlinks) {
- return 0;
- }
-
if (! -e "$workdir/$file") {
# If the file doesn't exist in the working tree, we cannot
# use it.
@@ -213,8 +209,7 @@ EOF
if ($rmode ne $null_mode) {
my ($use, $wt_sha1) = use_wt_file($repo, $workdir,
- $dst_path, $rsha1,
- $symlinks);
+ $dst_path, $rsha1);
if ($use) {
push @working_tree, $dst_path;
$wtindex .= "$rmode $wt_sha1\t$dst_path\0";
diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN
index 49b62ea..0a1f8d4 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.17.GITGUI
+DEF_VER=0.18.GITGUI
LF='
'
diff --git a/git-gui/Makefile b/git-gui/Makefile
index e22ba5c..e9c2bc3 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -254,7 +254,7 @@ lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS
auto_mkindex lib '*.tcl' \
| $(TCL_PATH) $(QUIET_2DEVNULL); then : ok; \
else \
- echo 1>&2 " * $(TCL_PATH) failed; using unoptimized loading"; \
+ echo >&2 " * $(TCL_PATH) failed; using unoptimized loading"; \
rm -f $@ ; \
echo '# Autogenerated by git-gui Makefile' >$@ && \
echo >>$@ && \
@@ -274,8 +274,8 @@ TRACK_VARS = \
GIT-GUI-VARS: FORCE
@VARS='$(TRACK_VARS)'; \
if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
- echo 1>&2 " * new locations or Tcl/Tk interpreter"; \
- echo 1>$@ "$$VARS"; \
+ echo >&2 " * new locations or Tcl/Tk interpreter"; \
+ echo >$@ "$$VARS"; \
fi
ifdef GITGUI_MACOSXAPP
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 89f636f..153f85d 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -137,6 +137,20 @@ unset oguimsg
######################################################################
##
+## On Mac, bring the current Wish process window to front
+
+if {[tk windowingsystem] eq "aqua"} {
+ catch {
+ exec osascript -e [format {
+ tell application "System Events"
+ set frontmost of processes whose unix id is %d to true
+ end tell
+ } [pid]]
+ }
+}
+
+######################################################################
+##
## read only globals
set _appname {Git Gui}
@@ -3003,18 +3017,11 @@ blame {
set jump_spec {}
set is_path 0
foreach a $argv {
- if {[file exists $a]} {
- if {$path ne {}} usage
- set path [normalize_relpath $a]
- break
- } elseif {[file exists $_prefix$a]} {
- if {$path ne {}} usage
- set path [normalize_relpath $_prefix$a]
- break
- }
+ set p [file join $_prefix $a]
- if {$is_path} {
+ if {$is_path || [file exists $p]} {
if {$path ne {}} usage
+ set path [normalize_relpath $p]
break
} elseif {$a eq {--}} {
if {$path ne {}} {
diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl
index 657f7d5..ee58981 100644
--- a/git-gui/lib/choose_repository.tcl
+++ b/git-gui/lib/choose_repository.tcl
@@ -286,7 +286,9 @@ method _next {action} {
destroy $w_body
if {![winfo exists $w_next]} {
${NS}::button $w_next -default active
- pack $w_next -side right -padx 5 -before $w_quit
+ set pos -before
+ if {[tk windowingsystem] eq "win32"} { set pos -after }
+ pack $w_next -side right -padx 5 $pos $w_quit
}
_do_$action $this
}
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
index ec44055..30d9a79 100644
--- a/git-gui/lib/diff.tcl
+++ b/git-gui/lib/diff.tcl
@@ -764,8 +764,15 @@ proc apply_range_or_line {x y} {
# context line
set ln [$ui_diff get $i_l $next_l]
set patch "$patch$pre_context$ln"
- set n [expr $n+1]
- set m [expr $m+1]
+ # Skip the "\ No newline at end of
+ # file". Depending on the locale setting
+ # we don't know what this line looks
+ # like exactly. The only thing we do
+ # know is that it starts with "\ "
+ if {![string match {\\ *} $ln]} {
+ set n [expr $n+1]
+ set m [expr $m+1]
+ }
set pre_context {}
} elseif {$c1 eq $to_context} {
# turn change line into context line
diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl
index 3c8e73b..120bc40 100644
--- a/git-gui/lib/mergetool.tcl
+++ b/git-gui/lib/mergetool.tcl
@@ -189,9 +189,9 @@ proc merge_resolve_tool2 {} {
}
bc3 {
if {$base_stage ne {}} {
- set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" -mergeoutput="$MERGED"]
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" "-mergeoutput=$MERGED"]
} else {
- set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -mergeoutput="$MERGED"]
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "-mergeoutput=$MERGED"]
}
}
ecmerge {
diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl
index 5e4e7f4..4e5c784 100644
--- a/git-gui/lib/remote.tcl
+++ b/git-gui/lib/remote.tcl
@@ -245,7 +245,8 @@ proc update_all_remotes_menu_entry {} {
set prune_m $remote_m.prune
if {$have_remote > 1} {
make_sure_remote_submenues_exist $remote_m
- if {[$fetch_m entrycget end -label] ne "All"} {
+ if {[$fetch_m type end] eq "command" \
+ && [$fetch_m entrycget end -label] ne "All"} {
$fetch_m insert end separator
$fetch_m insert end command \
@@ -259,7 +260,8 @@ proc update_all_remotes_menu_entry {} {
}
} else {
if {[winfo exists $fetch_m]} {
- if {[$fetch_m entrycget end -label] eq "All"} {
+ if {[$fetch_m type end] eq "command" \
+ && [$fetch_m entrycget end -label] eq "All"} {
delete_from_menu $fetch_m end
delete_from_menu $fetch_m end
diff --git a/git-gui/po/fr.po b/git-gui/po/fr.po
index 40441db..0aff186 100644
--- a/git-gui/po/fr.po
+++ b/git-gui/po/fr.po
@@ -1139,7 +1139,7 @@ msgstr "Standard (rapide, semi-redondant, liens durs)"
#: lib/choose_repository.tcl:514
msgid "Full Copy (Slower, Redundant Backup)"
-msgstr "Copy complète (plus lent, sauvegarde redondante)"
+msgstr "Copie complète (plus lent, sauvegarde redondante)"
#: lib/choose_repository.tcl:520
msgid "Shared (Fastest, Not Recommended, No Backup)"
diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh
index e338be5..6a72106 100644
--- a/git-mergetool--lib.sh
+++ b/git-mergetool--lib.sh
@@ -114,6 +114,33 @@ valid_tool () {
test -n "$cmd"
}
+setup_user_tool () {
+ merge_tool_cmd=$(get_merge_tool_cmd "$tool")
+ test -n "$merge_tool_cmd" || return 1
+
+ diff_cmd () {
+ ( eval $merge_tool_cmd )
+ status=$?
+ return $status
+ }
+
+ merge_cmd () {
+ 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
+ }
+}
+
setup_tool () {
tool="$1"
@@ -142,15 +169,15 @@ setup_tool () {
if ! test -f "$MERGE_TOOLS_DIR/$tool"
then
- # Use a special return code for this case since we want to
- # source "defaults" even when an explicit tool path is
- # configured since the user can use that to override the
- # default path in the scriptlet.
- return 2
+ setup_user_tool
+ return $?
fi
# Load the redefined functions
. "$MERGE_TOOLS_DIR/$tool"
+ # Now let the user override the default command for the tool. If
+ # they have not done so then this will return 1 which we ignore.
+ setup_user_tool
if merge_mode && ! can_merge
then
@@ -187,20 +214,7 @@ run_merge_tool () {
status=0
# Bring tool-specific functions into scope
- setup_tool "$1"
- exitcode=$?
- case $exitcode in
- 0)
- :
- ;;
- 2)
- # The configured tool is not a built-in tool.
- test -n "$merge_tool_path" || return 1
- ;;
- *)
- return $exitcode
- ;;
- esac
+ setup_tool "$1" || return 1
if merge_mode
then
@@ -213,38 +227,12 @@ run_merge_tool () {
# Run a either a configured or built-in diff tool
run_diff_cmd () {
- merge_tool_cmd=$(get_merge_tool_cmd "$1")
- if test -n "$merge_tool_cmd"
- then
- ( eval $merge_tool_cmd )
- status=$?
- return $status
- else
- diff_cmd "$1"
- fi
+ diff_cmd "$1"
}
# Run a either a configured or built-in merge tool
run_merge_cmd () {
- merge_tool_cmd=$(get_merge_tool_cmd "$1")
- if test -n "$merge_tool_cmd"
- 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 )
- status=$?
- check_unchanged
- else
- ( eval $merge_tool_cmd )
- status=$?
- fi
- return $status
- else
- merge_cmd "$1"
- fi
+ merge_cmd "$1"
}
list_merge_tool_candidates () {
diff --git a/git-p4.py b/git-p4.py
index 911bbce..88fcf23 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -3168,7 +3168,7 @@ class P4Rebase(Command):
if os.system("git update-index --refresh") != 0:
die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
if len(read_pipe("git diff-index HEAD --")) > 0:
- die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
+ die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
[upstream, settings] = findUpstreamBranchPoint()
if len(upstream) == 0:
diff --git a/git-pull.sh b/git-pull.sh
index 638aabb..f0df41c 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -5,7 +5,7 @@
# Fetch one or more remote refs and merge it/them into the current HEAD.
USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'
-LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.'
+LONG_USAGE='Fetch one or more remote refs and integrate it/them with the current HEAD.'
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
. git-sh-setup
@@ -266,10 +266,17 @@ case "$merge_head" in
;;
esac
+# Pulling into unborn branch: a shorthand for branching off
+# FETCH_HEAD, for lazy typers.
if test -z "$orig_head"
then
- git update-ref -m "initial pull" HEAD $merge_head "$curr_head" &&
- git read-tree -m -u HEAD || exit 1
+ # Two-way merge: we claim the index is based on an empty tree,
+ # and try to fast-forward to HEAD. This ensures we will not
+ # lose index/worktree changes that the user already made on
+ # the unborn branch.
+ empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+ git read-tree -m -u $empty_tree $merge_head &&
+ git update-ref -m "initial pull" HEAD $merge_head "$curr_head"
exit
fi
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index f84854f..34e3102 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -7,12 +7,12 @@ case "$action" in
continue)
git am --resolved --resolvemsg="$resolvemsg" &&
move_to_original_branch
- exit
+ return
;;
skip)
git am --skip --resolvemsg="$resolvemsg" &&
move_to_original_branch
- exit
+ return
;;
esac
@@ -56,7 +56,7 @@ else
As a result, git cannot rebase them.
EOF
- exit $?
+ return $?
fi
git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" <"$GIT_DIR/rebased-patches"
@@ -68,7 +68,7 @@ fi
if test 0 != $ret
then
test -d "$state_dir" && write_basic_state
- exit $ret
+ return $ret
fi
move_to_original_branch
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5822b2c..83d6d46 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -80,6 +80,18 @@ amend="$state_dir"/amend
rewritten_list="$state_dir"/rewritten-list
rewritten_pending="$state_dir"/rewritten-pending
+strategy_args=
+if test -n "$do_merge"
+then
+ strategy_args=${strategy:+--strategy=$strategy}
+ eval '
+ for strategy_opt in '"$strategy_opts"'
+ do
+ strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")"
+ done
+ '
+fi
+
GIT_CHERRY_PICK_HELP="$resolvemsg"
export GIT_CHERRY_PICK_HELP
@@ -239,7 +251,7 @@ pick_one () {
test -d "$rewritten" &&
pick_one_preserving_merges "$@" && return
- output git cherry-pick $empty_args $ff "$@"
+ output eval git cherry-pick "$strategy_args" $empty_args $ff "$@"
}
pick_one_preserving_merges () {
@@ -340,9 +352,8 @@ pick_one_preserving_merges () {
msg_content="$(commit_message $sha1)"
# No point in merging the first parent, that's HEAD
new_parents=${new_parents# $first_parent}
- if ! do_with_author output \
- git merge --no-ff ${strategy:+-s $strategy} -m \
- "$msg_content" $new_parents
+ if ! do_with_author output eval \
+ 'git merge --no-ff $strategy_args -m "$msg_content" $new_parents'
then
printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
die_with_patch $sha1 "Error redoing merge $sha1"
@@ -350,7 +361,7 @@ pick_one_preserving_merges () {
echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
;;
*)
- output git cherry-pick "$@" ||
+ output eval git cherry-pick "$strategy_args" "$@" ||
die_with_patch $sha1 "Could not pick $sha1"
;;
esac
@@ -628,17 +639,16 @@ do_next () {
"$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
true # we don't care if this hook failed
fi &&
- rm -rf "$state_dir" &&
- git gc --auto &&
warn "Successfully rebased and updated $head_name."
- exit
+ return 1 # not failure; just to break the do_rest loop
}
+# can only return 0, when the infinite loop breaks
do_rest () {
while :
do
- do_next
+ do_next || break
done
}
@@ -690,8 +700,22 @@ rearrange_squash () {
case "$message" in
"squash! "*|"fixup! "*)
action="${message%%!*}"
- rest="${message#*! }"
- echo "$sha1 $action $rest"
+ rest=$message
+ prefix=
+ # skip all squash! or fixup! (but save for later)
+ while :
+ do
+ case "$rest" in
+ "squash! "*|"fixup! "*)
+ prefix="$prefix${rest%%!*},"
+ rest="${rest#*! }"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+ echo "$sha1 $action $prefix $rest"
# if it's a single word, try to resolve to a full sha1 and
# emit a second copy. This allows us to match on both message
# and on sha1 prefix
@@ -700,7 +724,7 @@ rearrange_squash () {
if test -n "$fullsha"; then
# prefix the action to uniquely identify this line as
# intended for full sha1 match
- echo "$sha1 +$action $fullsha"
+ echo "$sha1 +$action $prefix $fullsha"
fi
fi
esac
@@ -715,7 +739,7 @@ rearrange_squash () {
esac
printf '%s\n' "$pick $sha1 $message"
used="$used$sha1 "
- while read -r squash action msg_content
+ while read -r squash action msg_prefix msg_content
do
case " $used" in
*" $squash "*) continue ;;
@@ -731,7 +755,8 @@ rearrange_squash () {
case "$message" in "$msg_content"*) emit=1;; esac ;;
esac
if test $emit = 1; then
- printf '%s\n' "$action $squash $action! $msg_content"
+ real_prefix=$(echo "$msg_prefix" | sed "s/,/! /g")
+ printf '%s\n' "$action $squash ${real_prefix}$msg_content"
used="$used$squash "
fi
done <"$1.sq"
@@ -805,11 +830,13 @@ first and then run 'git rebase --continue' again."
require_clean_work_tree "rebase"
do_rest
+ return 0
;;
skip)
git rerere clear
do_rest
+ return 0
;;
edit-todo)
git stripspace --strip-comments <"$todo" >"$todo".new
@@ -837,12 +864,15 @@ comment_for_reflog start
if test ! -z "$switch_to"
then
+ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
output git checkout "$switch_to" -- ||
- die "Could not checkout $switch_to"
+ die "Could not checkout $switch_to"
+
+ comment_for_reflog start
fi
orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?"
-mkdir "$state_dir" || die "Could not create temporary $state_dir"
+mkdir -p "$state_dir" || die "Could not create temporary $state_dir"
: > "$state_dir"/interactive || die "Could not mark as interactive"
write_basic_state
@@ -980,6 +1010,7 @@ has_action "$todo" ||
test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
+GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
output git checkout $onto || die_abort "could not detach HEAD"
git update-ref ORIG_HEAD $orig_head
do_rest
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
index b10f2cf..16d1817 100644
--- a/git-rebase--merge.sh
+++ b/git-rebase--merge.sh
@@ -96,7 +96,6 @@ finish_rb_merge () {
"$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
fi
fi
- rm -r "$state_dir"
say All done.
}
@@ -110,7 +109,7 @@ continue)
continue_merge
done
finish_rb_merge
- exit
+ return
;;
skip)
read_state
@@ -122,7 +121,7 @@ skip)
continue_merge
done
finish_rb_merge
- exit
+ return
;;
esac
diff --git a/git-rebase.sh b/git-rebase.sh
index 2c692c3..0039ecf 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -13,6 +13,7 @@ git-rebase --continue | --abort | --skip | --edit-todo
Available options are
v,verbose! display a diffstat of what changed upstream
q,quiet! be quiet. implies --no-stat
+autostash! automatically stash/stash pop before and after
onto=! rebase onto given branch instead of upstream
p,preserve-merges! try to recreate merges instead of ignoring them
s,strategy=! use the given merge strategy
@@ -64,6 +65,7 @@ apply_dir="$GIT_DIR"/rebase-apply
verbose=
diffstat=
test "$(git config --bool rebase.stat)" = true && diffstat=t
+autostash="$(git config --bool rebase.autostash || echo false)"
git_am_opt=
rebase_root=
force_rebase=
@@ -82,6 +84,8 @@ keep_empty=
test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
read_basic_state () {
+ test -f "$state_dir/head-name" &&
+ test -f "$state_dir/onto" &&
head_name=$(cat "$state_dir"/head-name) &&
onto=$(cat "$state_dir"/onto) &&
# We always write to orig-head, but interactive rebase used to write to
@@ -143,6 +147,26 @@ move_to_original_branch () {
esac
}
+finish_rebase () {
+ if test -f "$state_dir/autostash"
+ then
+ stash_sha1=$(cat "$state_dir/autostash")
+ if git stash apply $stash_sha1 2>&1 >/dev/null
+ then
+ echo "$(gettext 'Applied autostash.')"
+ else
+ git stash store -m "autostash" -q $stash_sha1 ||
+ die "$(eval_gettext "Cannot store \$stash_sha1")"
+ gettext 'Applying autostash resulted in conflicts.
+Your changes are safe in the stash.
+You can run "git stash pop" or "git stash drop" it at any time.
+'
+ fi
+ fi
+ git gc --auto &&
+ rm -rf "$state_dir"
+}
+
run_specific_rebase () {
if [ "$interactive_rebase" = implied ]; then
GIT_EDITOR=:
@@ -150,6 +174,12 @@ run_specific_rebase () {
autosquash=
fi
. git-rebase--$type
+ ret=$?
+ if test $ret -eq 0
+ then
+ finish_rebase
+ fi
+ exit $ret
}
run_pre_rebase_hook () {
@@ -241,6 +271,9 @@ do
--stat)
diffstat=t
;;
+ --autostash)
+ autostash=true
+ ;;
-v)
verbose=t
diffstat=t
@@ -341,7 +374,7 @@ abort)
;;
esac
output git reset --hard $orig_head
- rm -r "$state_dir"
+ finish_rebase
exit
;;
edit-todo)
@@ -400,7 +433,7 @@ then
shift
;;
esac
- upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+ upstream=$(peel_committish "${upstream_name}") ||
die "$(eval_gettext "invalid upstream \$upstream_name")"
upstream_arg="$upstream_name"
else
@@ -436,7 +469,7 @@ case "$onto_name" in
fi
;;
*)
- onto=$(git rev-parse --verify "${onto_name}^0") ||
+ onto=$(peel_committish "$onto_name") ||
die "$(eval_gettext "Does not point to a valid commit: \$onto_name")"
;;
esac
@@ -480,6 +513,18 @@ case "$#" in
;;
esac
+if test "$autostash" = true && ! (require_clean_work_tree) 2>/dev/null
+then
+ stash_sha1=$(git stash create "autostash") ||
+ die "$(gettext 'Cannot autostash')"
+
+ mkdir -p "$state_dir" &&
+ echo $stash_sha1 >"$state_dir/autostash" &&
+ stash_abbrev=$(git rev-parse --short $stash_sha1) &&
+ echo "$(eval_gettext 'Created autostash: $stash_abbrev')" &&
+ git reset --hard
+fi
+
require_clean_work_tree "rebase" "$(gettext "Please commit or stash them.")"
# Now we are rebasing commits $upstream..$orig_head (or with --root,
@@ -497,8 +542,11 @@ then
if test -z "$force_rebase"
then
# Lazily switch to the target branch if needed...
- test -z "$switch_to" || git checkout "$switch_to" --
+ test -z "$switch_to" ||
+ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \
+ git checkout "$switch_to" --
say "$(eval_gettext "Current branch \$branch_name is up to date.")"
+ finish_rebase
exit 0
else
say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")"
@@ -522,7 +570,9 @@ test "$type" = interactive && run_specific_rebase
# Detach HEAD and reset the tree
say "$(gettext "First, rewinding head to replay your work on top of it...")"
-git checkout -q "$onto^0" || die "could not detach HEAD"
+
+GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \
+ git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $orig_head
# If the $onto is a proper descendant of the tip of the branch, then
@@ -531,6 +581,7 @@ if test "$mb" = "$orig_head"
then
say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
move_to_original_branch
+ finish_rebase
exit 0
fi
diff --git a/git-remote-testgit b/git-remote-testgit.sh
index e7ed3a3..2109070 100755
--- a/git-remote-testgit
+++ b/git-remote-testgit.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/bin/sh
# Copyright (c) 2012 Felipe Contreras
alias=$1
@@ -23,7 +23,6 @@ then
testgitmarks="$dir/testgit.marks"
test -e "$gitmarks" || >"$gitmarks"
test -e "$testgitmarks" || >"$testgitmarks"
- testgitmarks_args=( "--"{import,export}"-marks=$testgitmarks" )
fi
while read line
@@ -62,24 +61,55 @@ do
echo "feature import-marks=$gitmarks"
echo "feature export-marks=$gitmarks"
fi
+
+ if test -n "$GIT_REMOTE_TESTGIT_FAILURE"
+ then
+ echo "feature done"
+ exit 1
+ fi
+
echo "feature done"
- git fast-export "${testgitmarks_args[@]}" $refs |
+ git fast-export \
+ ${testgitmarks:+"--import-marks=$testgitmarks"} \
+ ${testgitmarks:+"--export-marks=$testgitmarks"} \
+ $refs |
sed -e "s#refs/heads/#${prefix}/heads/#g"
echo "done"
;;
export)
- before=$(git for-each-ref --format='%(refname) %(objectname)')
+ if test -n "$GIT_REMOTE_TESTGIT_FAILURE"
+ then
+ # consume input so fast-export doesn't get SIGPIPE;
+ # git would also notice that case, but we want
+ # to make sure we are exercising the later
+ # error checks
+ while read line; do
+ test "done" = "$line" && break
+ done
+ exit 1
+ fi
- git fast-import "${testgitmarks_args[@]}" --quiet
+ before=$(git for-each-ref --format=' %(refname) %(objectname) ')
- after=$(git for-each-ref --format='%(refname) %(objectname)')
+ git fast-import \
+ ${testgitmarks:+"--import-marks=$testgitmarks"} \
+ ${testgitmarks:+"--export-marks=$testgitmarks"} \
+ --quiet
# figure out which refs were updated
- join -e 0 -o '0 1.2 2.2' -a 2 <(echo "$before") <(echo "$after") |
- while read ref a b
+ git for-each-ref --format='%(refname) %(objectname)' |
+ while read ref a
do
- test $a == $b && continue
- echo "ok $ref"
+ case "$before" in
+ *" $ref $a "*)
+ continue ;; # unchanged
+ esac
+ if test -z "$GIT_REMOTE_TESTGIT_PUSH_ERROR"
+ then
+ echo "ok $ref"
+ else
+ echo "error $ref $GIT_REMOTE_TESTGIT_PUSH_ERROR"
+ fi
done
echo
diff --git a/git-request-pull.sh b/git-request-pull.sh
index d566015..ebf1269 100755
--- a/git-request-pull.sh
+++ b/git-request-pull.sh
@@ -51,8 +51,18 @@ fi
tag_name=$(git describe --exact "$head^0" 2>/dev/null)
test -n "$base" && test -n "$url" || usage
-baserev=$(git rev-parse --verify "$base"^0) &&
-headrev=$(git rev-parse --verify "$head"^0) || exit
+
+baserev=$(git rev-parse --verify --quiet "$base"^0)
+if test -z "$baserev"
+then
+ die "fatal: Not a valid revision: $base"
+fi
+
+headrev=$(git rev-parse --verify --quiet "$head"^0)
+if test -z "$headrev"
+then
+ die "fatal: Not a valid revision: $head"
+fi
merge_base=$(git merge-base $baserev $headrev) ||
die "fatal: No commits in common between $base and $head"
diff --git a/git-send-email.perl b/git-send-email.perl
index bd13cc8..45c3863 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -203,11 +203,9 @@ my ($compose_encoding);
my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
-my $not_set_by_user = "true but not set by the user";
-
my %config_bool_settings = (
"thread" => [\$thread, 1],
- "chainreplyto" => [\$chain_reply_to, $not_set_by_user],
+ "chainreplyto" => [\$chain_reply_to, 0],
"suppressfrom" => [\$suppress_from, undef],
"signedoffbycc" => [\$signed_off_by_cc, undef],
"signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated
@@ -241,19 +239,6 @@ my %config_path_settings = (
"aliasesfile" => \@alias_files,
);
-# Help users prepare for 1.7.0
-sub chain_reply_to {
- if (defined $chain_reply_to &&
- $chain_reply_to eq $not_set_by_user) {
- print STDERR
- "In git 1.7.0, the default has changed to --no-chain-reply-to\n" .
- "Set sendemail.chainreplyto configuration variable to true if\n" .
- "you want to keep --chain-reply-to as your default.\n";
- $chain_reply_to = 0;
- }
- return $chain_reply_to;
-}
-
# Handle Uncouth Termination
sub signal_handler {
@@ -760,6 +745,11 @@ if (!defined $sender) {
$sender = $repoauthor || $repocommitter || '';
}
+# $sender could be an already sanitized address
+# (e.g. sendemail.from could be manually sanitized by user).
+# But it's a no-op to run sanitize_address on an already sanitized address.
+$sender = sanitize_address($sender);
+
my $prompting = 0;
if (!@initial_to && !defined $to_cmd) {
my $to = ask("Who should the emails be sent to (if any)? ",
@@ -1113,10 +1103,9 @@ sub send_message {
if ($cc ne '') {
$ccline = "\nCc: $cc";
}
- my $sanitized_sender = sanitize_address($sender);
make_message_id() unless defined($message_id);
- my $header = "From: $sanitized_sender
+ my $header = "From: $sender
To: $to${ccline}
Subject: $subject
Date: $date
@@ -1133,7 +1122,7 @@ X-Mailer: git-send-email $gitversion
}
my @sendmail_parameters = ('-i', @recipients);
- my $raw_from = $sanitized_sender;
+ my $raw_from = $sender;
if (defined $envelope_sender && $envelope_sender ne "auto") {
$raw_from = $envelope_sender;
}
@@ -1199,9 +1188,11 @@ X-Mailer: git-send-email $gitversion
else {
require Net::SMTP;
$smtp_domain ||= maildomain();
- $smtp ||= Net::SMTP->new(smtp_host_string(),
+ $smtp_server_port ||= 25;
+ $smtp ||= Net::SMTP->new($smtp_server,
Hello => $smtp_domain,
- Debug => $debug_net_smtp);
+ Debug => $debug_net_smtp,
+ Port => $smtp_server_port);
if ($smtp_encryption eq 'tls' && $smtp) {
require Net::SMTP::SSL;
$smtp->command('STARTTLS');
@@ -1270,6 +1261,7 @@ foreach my $t (@files) {
open my $fh, "<", $t or die "can't open file $t";
my $author = undef;
+ my $sauthor = undef;
my $author_encoding;
my $has_content_type;
my $body_encoding;
@@ -1308,8 +1300,9 @@ foreach my $t (@files) {
}
elsif (/^From:\s+(.*)$/i) {
($author, $author_encoding) = unquote_rfc2047($1);
+ $sauthor = sanitize_address($author);
next if $suppress_cc{'author'};
- next if $suppress_cc{'self'} and $author eq $sender;
+ next if $suppress_cc{'self'} and $sauthor eq $sender;
printf("(mbox) Adding cc: %s from line '%s'\n",
$1, $_) unless $quiet;
push @cc, $1;
@@ -1323,7 +1316,9 @@ foreach my $t (@files) {
}
elsif (/^Cc:\s+(.*)$/i) {
foreach my $addr (parse_address_line($1)) {
- if (unquote_rfc2047($addr) eq $sender) {
+ my $qaddr = unquote_rfc2047($addr);
+ my $saddr = sanitize_address($qaddr);
+ if ($saddr eq $sender) {
next if ($suppress_cc{'self'});
} else {
next if ($suppress_cc{'cc'});
@@ -1370,7 +1365,8 @@ foreach my $t (@files) {
chomp;
my ($what, $c) = ($1, $2);
chomp $c;
- if ($c eq $sender) {
+ my $sc = sanitize_address($c);
+ if ($sc eq $sender) {
next if ($suppress_cc{'self'});
} else {
next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i;
@@ -1400,7 +1396,7 @@ foreach my $t (@files) {
$subject = quote_subject($subject, $auto_8bit_encoding);
}
- if (defined $author and $author ne $sender) {
+ if (defined $sauthor and $sauthor ne $sender) {
$message = "From: $author\n\n$message";
if (defined $author_encoding) {
if ($has_content_type) {
@@ -1437,7 +1433,7 @@ foreach my $t (@files) {
# set up for the next message
if ($thread && $message_was_sent &&
- (chain_reply_to() || !defined $reply_to || length($reply_to) == 0 ||
+ ($chain_reply_to || !defined $reply_to || length($reply_to) == 0 ||
$message_num == 1)) {
$reply_to = $message_id;
if (length $references > 0) {
@@ -1454,7 +1450,6 @@ foreach my $t (@files) {
sub recipients_cmd {
my ($prefix, $what, $cmd, $file) = @_;
- my $sanitized_sender = sanitize_address($sender);
my @addresses = ();
open my $fh, "-|", "$cmd \Q$file\E"
or die "($prefix) Could not execute '$cmd'";
@@ -1462,7 +1457,7 @@ sub recipients_cmd {
$address =~ s/^\s*//g;
$address =~ s/\s*$//g;
$address = sanitize_address($address);
- next if ($address eq $sanitized_sender and $suppress_from);
+ next if ($address eq $sender and $suppress_cc{'self'});
push @addresses, $address;
printf("($prefix) Adding %s: %s from: '%s'\n",
$what, $address, $cmd) unless $quiet;
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 2f78359..7a964ad 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -313,3 +313,15 @@ then
}
: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
fi
+
+peel_committish () {
+ case "$1" in
+ :/*)
+ peeltmp=$(git rev-parse --verify "$1") &&
+ git rev-parse --verify "${peeltmp}^0"
+ ;;
+ *)
+ git rev-parse --verify "${1}^0"
+ ;;
+ esac
+}
diff --git a/git-stash.sh b/git-stash.sh
index bbefdf6..85c9e2c 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -156,10 +156,46 @@ create_stash () {
die "$(gettext "Cannot record working tree state")"
}
+store_stash () {
+ while test $# != 0
+ do
+ case "$1" in
+ -m|--message)
+ shift
+ stash_msg="$1"
+ ;;
+ -q|--quiet)
+ quiet=t
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done
+ test $# = 1 ||
+ die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
+
+ w_commit="$1"
+ if test -z "$stash_msg"
+ then
+ stash_msg="Created via \"git stash store\"."
+ fi
+
+ # Make sure the reflog for stash is kept.
+ : >>"$GIT_DIR/logs/$ref_stash"
+ git update-ref -m "$stash_msg" $ref_stash $w_commit
+ ret=$?
+ test $ret != 0 && test -z $quiet &&
+ die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
+ return $ret
+}
+
save_stash () {
keep_index=
patch_mode=
untracked=
+ force=
while test $# != 0
do
case "$1" in
@@ -180,6 +216,9 @@ save_stash () {
-u|--include-untracked)
untracked=untracked
;;
+ -f|--force)
+ force=t
+ ;;
-a|--all)
untracked=all
;;
@@ -223,16 +262,20 @@ save_stash () {
say "$(gettext "No local changes to save")"
exit 0
fi
+ if test -z "$untracked$force" &&
+ test -n "$(git ls-files --killed | head -n 1)"
+ then
+ say "$(gettext "The following untracked files would NOT be saved but need to be removed by stash save:")"
+ test -n "$GIT_QUIET" || git ls-files --killed | sed 's/^/\t/'
+ say "$(gettext "Aborting. Consider using either the --force or --include-untracked option.")" >&2
+ exit 1
+ fi
test -f "$GIT_DIR/logs/$ref_stash" ||
clear_stash || die "$(gettext "Cannot initialize stash")"
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 "$(gettext "Cannot save the current status")"
+ store_stash -m "$stash_msg" -q $w_commit ||
+ die "$(gettext "Cannot save the current status")"
say Saved working directory and index state "$stash_msg"
if test -z "$patch_mode"
@@ -546,12 +589,13 @@ clear)
clear_stash "$@"
;;
create)
- if test $# -gt 0 && test "$1" = create
- then
- shift
- fi
+ shift
create_stash "$*" && echo "$w_commit"
;;
+store)
+ shift
+ store_stash "$@"
+ ;;
drop)
shift
drop_stash "$@"
diff --git a/git-submodule.sh b/git-submodule.sh
index 79bfaac..2979197 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -14,10 +14,13 @@ USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <re
or: $dashless [--quiet] foreach [--recursive] <command>
or: $dashless [--quiet] sync [--recursive] [--] [<path>...]"
OPTIONS_SPEC=
+SUBDIRECTORY_OK=Yes
. git-sh-setup
. git-sh-i18n
. git-parse-remote
require_work_tree
+wt_prefix=$(git rev-parse --show-prefix)
+cd_to_toplevel
command=
branch=
@@ -32,6 +35,7 @@ nofetch=
update=
prefix=
custom_name=
+depth=
# The function takes at most 2 arguments. The first argument is the
# URL that navigates to the submodule origin repo. When relative, this URL
@@ -106,14 +110,50 @@ resolve_relative_url ()
echo "${is_relative:+${up_path}}${remoteurl#./}"
}
+# Resolve a path to be relative to another path. This is intended for
+# converting submodule paths when git-submodule is run in a subdirectory
+# and only handles paths where the directory separator is '/'.
+#
+# The output is the first argument as a path relative to the second argument,
+# which defaults to $wt_prefix if it is omitted.
+relative_path ()
+{
+ local target curdir result
+ target=$1
+ curdir=${2-$wt_prefix}
+ curdir=${curdir%/}
+ result=
+
+ while test -n "$curdir"
+ do
+ case "$target" in
+ "$curdir/"*)
+ target=${target#"$curdir"/}
+ break
+ ;;
+ esac
+
+ result="${result}../"
+ if test "$curdir" = "${curdir%/*}"
+ then
+ curdir=
+ else
+ curdir="${curdir%/*}"
+ fi
+ done
+
+ echo "$result$target"
+}
+
#
# Get submodule info for registered submodules
# $@ = path to limit submodule list
#
module_list()
{
+ eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
(
- git ls-files --error-unmatch --stage -- "$@" ||
+ git ls-files -z --error-unmatch --stage -- "$@" ||
echo "unmatched pathspec exists"
) |
perl -e '
@@ -121,6 +161,7 @@ module_list()
my ($null_sha1) = ("0" x 40);
my @out = ();
my $unmatched = 0;
+ $/ = "\0";
while (<STDIN>) {
if (/^unmatched pathspec/) {
$unmatched = 1;
@@ -211,6 +252,7 @@ module_clone()
name=$2
url=$3
reference="$4"
+ depth="$5"
quiet=
if test -n "$GIT_QUIET"
then
@@ -233,7 +275,7 @@ module_clone()
mkdir -p "$gitdir_base"
(
clear_local_git_env
- git clone $quiet -n ${reference:+"$reference"} \
+ git clone $quiet ${depth:+"$depth"} -n ${reference:+"$reference"} \
--separate-git-dir "$gitdir" "$url" "$sm_path"
) ||
die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
@@ -282,6 +324,7 @@ isnumber()
cmd_add()
{
# parse $args after "submodule ... add".
+ reference_path=
while test $# -ne 0
do
case "$1" in
@@ -298,17 +341,25 @@ cmd_add()
;;
--reference)
case "$2" in '') usage ;; esac
- reference="--reference=$2"
+ reference_path=$2
shift
;;
--reference=*)
- reference="$1"
+ reference_path="${1#--reference=}"
;;
--name)
case "$2" in '') usage ;; esac
custom_name=$2
shift
;;
+ --depth)
+ case "$2" in '') usage ;; esac
+ depth="--depth=$2"
+ shift
+ ;;
+ --depth=*)
+ depth=$1
+ ;;
--)
shift
break
@@ -323,6 +374,14 @@ cmd_add()
shift
done
+ if test -n "$reference_path"
+ then
+ is_absolute_path "$reference_path" ||
+ reference_path="$wt_prefix$reference_path"
+
+ reference="--reference=$reference_path"
+ fi
+
repo=$1
sm_path=$2
@@ -335,9 +394,14 @@ cmd_add()
usage
fi
+ is_absolute_path "$sm_path" || sm_path="$wt_prefix$sm_path"
+
# assure repo is absolute or relative to parent
case "$repo" in
./*|../*)
+ test -z "$wt_prefix" ||
+ die "$(gettext "Relative path can only be used from the toplevel of the working tree")"
+
# dereference source url relative to parent's url
realrepo=$(resolve_relative_url "$repo") || exit
;;
@@ -405,7 +469,7 @@ Use -f if you really want to add it." >&2
echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
fi
fi
- module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" || exit
+ module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" "$depth" || exit
(
clear_local_git_env
cd "$sm_path" &&
@@ -471,21 +535,23 @@ cmd_foreach()
die_if_unmatched "$mode"
if test -e "$sm_path"/.git
then
- say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
+ displaypath=$(relative_path "$sm_path")
+ say "$(eval_gettext "Entering '\$prefix\$displaypath'")"
name=$(module_name "$sm_path")
(
prefix="$prefix$sm_path/"
clear_local_git_env
- # we make $path available to scripts ...
- path=$sm_path
cd "$sm_path" &&
+ sm_path=$(relative_path "$sm_path") &&
+ # we make $path available to scripts ...
+ path=$sm_path &&
eval "$@" &&
if test -n "$recursive"
then
cmd_foreach "--recursive" "$@"
fi
) <&3 3<&- ||
- die "$(eval_gettext "Stopping at '\$sm_path'; script returned non-zero status.")"
+ die "$(eval_gettext "Stopping at '\$prefix\$displaypath'; script returned non-zero status.")"
fi
done
}
@@ -524,12 +590,14 @@ cmd_init()
die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
+ displaypath=$(relative_path "$sm_path")
+
# 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 "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")"
+ die "$(eval_gettext "No url found for submodule path '\$displaypath' in .gitmodules")"
# Possibly a url relative to parent
case "$url" in
@@ -538,9 +606,9 @@ cmd_init()
;;
esac
git config submodule."$name".url "$url" ||
- die "$(eval_gettext "Failed to register url for submodule path '\$sm_path'")"
+ die "$(eval_gettext "Failed to register url for submodule path '\$displaypath'")"
- say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$sm_path'")"
+ say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$displaypath'")"
fi
# Copy "update" setting when it is not set yet
@@ -548,7 +616,7 @@ cmd_init()
test -z "$upd" ||
test -n "$(git config submodule."$name".update)" ||
git config submodule."$name".update "$upd" ||
- die "$(eval_gettext "Failed to register update mode for submodule path '\$sm_path'")"
+ die "$(eval_gettext "Failed to register update mode for submodule path '\$displaypath'")"
done
}
@@ -594,27 +662,29 @@ cmd_deinit()
die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
+ displaypath=$(relative_path "$sm_path")
+
# Remove the submodule work tree (unless the user already did it)
if test -d "$sm_path"
then
# Protect submodules containing a .git directory
if test -d "$sm_path/.git"
then
- echo >&2 "$(eval_gettext "Submodule work tree '\$sm_path' contains a .git directory")"
+ echo >&2 "$(eval_gettext "Submodule work tree '\$displaypath' contains a .git directory")"
die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
fi
if test -z "$force"
then
git rm -qn "$sm_path" ||
- die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")"
+ die "$(eval_gettext "Submodule work tree '\$displaypath' contains local modifications; use '-f' to discard them")"
fi
rm -rf "$sm_path" &&
- say "$(eval_gettext "Cleared directory '\$sm_path'")" ||
- say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")"
+ say "$(eval_gettext "Cleared directory '\$displaypath'")" ||
+ say "$(eval_gettext "Could not remove submodule work tree '\$displaypath'")"
fi
- mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")"
+ mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$displaypath'")"
# Remove the .git/config entries (unless the user already did it)
if test -n "$(git config --get-regexp submodule."$name\.")"
@@ -623,7 +693,7 @@ cmd_deinit()
# the user later decides to init this submodule again
url=$(git config submodule."$name".url)
git config --remove-section submodule."$name" 2>/dev/null &&
- say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$sm_path'")"
+ say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$displaypath'")"
fi
done
}
@@ -676,6 +746,14 @@ cmd_update()
--checkout)
update="checkout"
;;
+ --depth)
+ case "$2" in '') usage ;; esac
+ depth="--depth=$2"
+ shift
+ ;;
+ --depth=*)
+ depth=$1
+ ;;
--)
shift
break
@@ -717,9 +795,11 @@ cmd_update()
update_module=$(git config submodule."$name".update)
fi
+ displaypath=$(relative_path "$prefix$sm_path")
+
if test "$update_module" = "none"
then
- echo "Skipping submodule '$prefix$sm_path'"
+ echo "Skipping submodule '$displaypath'"
continue
fi
@@ -728,20 +808,20 @@ cmd_update()
# Only mention uninitialized submodules when its
# path have been specified
test "$#" != "0" &&
- say "$(eval_gettext "Submodule path '\$prefix\$sm_path' not initialized
+ say "$(eval_gettext "Submodule path '\$displaypath' not initialized
Maybe you want to use 'update --init'?")"
continue
fi
if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
then
- module_clone "$sm_path" "$name" "$url" "$reference" || exit
+ module_clone "$sm_path" "$name" "$url" "$reference" "$depth" || exit
cloned_modules="$cloned_modules;$name"
subsha1=
else
subsha1=$(clear_local_git_env; cd "$sm_path" &&
git rev-parse --verify HEAD) ||
- die "$(eval_gettext "Unable to find current revision in submodule path '\$prefix\$sm_path'")"
+ die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
fi
if test -n "$remote"
@@ -774,7 +854,7 @@ Maybe you want to use 'update --init'?")"
(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 "$(eval_gettext "Unable to fetch in submodule path '\$prefix\$sm_path'")"
+ die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
fi
# Is this something we just cloned?
@@ -788,20 +868,26 @@ Maybe you want to use 'update --init'?")"
case "$update_module" in
rebase)
command="git rebase"
- die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$prefix\$sm_path'")"
- say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': rebased into '\$sha1'")"
+ die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'")"
+ say_msg="$(eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'")"
must_die_on_failure=yes
;;
merge)
command="git merge"
- die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$prefix\$sm_path'")"
- say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': merged in '\$sha1'")"
+ die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'")"
+ say_msg="$(eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'")"
+ must_die_on_failure=yes
+ ;;
+ !*)
+ command="${update_module#!}"
+ die_msg="$(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$prefix\$sm_path'")"
+ say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': '\$command \$sha1'")"
must_die_on_failure=yes
;;
*)
command="git checkout $subforce -q"
- die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$prefix\$sm_path'")"
- say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': checked out '\$sha1'")"
+ die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")"
+ say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")"
;;
esac
@@ -828,7 +914,7 @@ Maybe you want to use 'update --init'?")"
res=$?
if test $res -gt 0
then
- die_msg="$(eval_gettext "Failed to recurse into submodule path '\$prefix\$sm_path'")"
+ die_msg="$(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
if test $res -eq 1
then
err="${err};$die_msg"
@@ -942,6 +1028,7 @@ cmd_summary() {
fi
cd_to_toplevel
+ eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
# Get modified modules cared by user
modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" |
sane_egrep '^:([0-7]* )?160000' |
@@ -991,16 +1078,18 @@ cmd_summary() {
! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null &&
missing_dst=t
+ display_name=$(relative_path "$name")
+
total_commits=
case "$missing_src,$missing_dst" in
t,)
- errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_src")"
+ errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_src")"
;;
,t)
- errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_dst")"
+ errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_dst")"
;;
t,t)
- errmsg="$(eval_gettext " Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")"
+ errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commits \$sha1_src and \$sha1_dst")"
;;
*)
errmsg=
@@ -1029,12 +1118,12 @@ cmd_summary() {
submodule="$(gettext "submodule")"
if test $mod_dst = 160000
then
- echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
+ echo "* $display_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 "* $display_name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
fi
else
- echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
+ echo "* $display_name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
fi
if test -n "$errmsg"
then
@@ -1118,7 +1207,7 @@ cmd_status()
die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url)
- displaypath="$prefix$sm_path"
+ displaypath=$(relative_path "$prefix$sm_path")
if test "$stage" = U
then
say "U$sha1 $displaypath"
@@ -1129,16 +1218,16 @@ cmd_status()
say "-$sha1 $displaypath"
continue;
fi
- set_name_rev "$sm_path" "$sha1"
if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path"
then
+ set_name_rev "$sm_path" "$sha1"
say " $sha1 $displaypath$revname"
else
if test -z "$cached"
then
sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
- set_name_rev "$sm_path" "$sha1"
fi
+ set_name_rev "$sm_path" "$sha1"
say "+$sha1 $displaypath$revname"
fi
@@ -1213,7 +1302,8 @@ cmd_sync()
if git config "submodule.$name.url" >/dev/null 2>/dev/null
then
- say "$(eval_gettext "Synchronizing submodule url for '\$prefix\$sm_path'")"
+ displaypath=$(relative_path "$prefix$sm_path")
+ say "$(eval_gettext "Synchronizing submodule url for '\$displaypath'")"
git config submodule."$name".url "$super_config_url"
if test -e "$sm_path"/.git
diff --git a/git-svn.perl b/git-svn.perl
index d070de0..ff1ce3d 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -831,7 +831,7 @@ sub dcommit_rebase {
sub cmd_dcommit {
my $head = shift;
command_noisy(qw/update-index --refresh/);
- git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }
+ git_cmd_try { command_oneline(qw/diff-index --quiet HEAD --/) }
'Cannot dcommit with a dirty index. Commit your changes first, '
. "or stash them with `git stash'.\n";
$head ||= 'HEAD';
@@ -1246,7 +1246,7 @@ sub cmd_rebase {
return;
}
if (command(qw/diff-index HEAD --/)) {
- print STDERR "Cannot rebase with uncommited changes:\n";
+ print STDERR "Cannot rebase with uncommitted changes:\n";
command_noisy('status');
exit 1;
}
@@ -1932,7 +1932,7 @@ sub cmt_sha2rev_batch {
sub working_head_info {
my ($head, $refs) = @_;
my @args = qw/rev-list --first-parent --pretty=medium/;
- my ($fh, $ctx) = command_output_pipe(@args, $head);
+ my ($fh, $ctx) = command_output_pipe(@args, $head, "--");
my $hash;
my %max;
while (<$fh>) {
diff --git a/git-web--browse.sh b/git-web--browse.sh
index 9f44679..1d72ec7 100755
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -32,8 +32,9 @@ valid_custom_tool()
valid_tool() {
case "$1" in
firefox | iceweasel | seamonkey | iceape | \
- chrome | google-chrome | chromium | chromium-browser |\
- konqueror | opera | w3m | elinks | links | lynx | dillo | open | start)
+ chrome | google-chrome | chromium | chromium-browser | \
+ konqueror | opera | w3m | elinks | links | lynx | dillo | open | \
+ start | cygstart)
;; # happy
*)
valid_custom_tool "$1" || return 1
@@ -127,6 +128,10 @@ if test -z "$browser" ; then
if test -x /bin/start; then
browser_candidates="start $browser_candidates"
fi
+ # /usr/bin/cygstart indicates Cygwin
+ if test -x /usr/bin/cygstart; then
+ browser_candidates="cygstart $browser_candidates"
+ fi
for i in $browser_candidates; do
init_browser_path $i
@@ -174,7 +179,7 @@ konqueror)
;;
esac
;;
-w3m|elinks|links|lynx|open)
+w3m|elinks|links|lynx|open|cygstart)
"$browser_path" "$@"
;;
start)
diff --git a/git.c b/git.c
index 1ada169..4359086 100644
--- a/git.c
+++ b/git.c
@@ -4,6 +4,7 @@
#include "help.h"
#include "quote.h"
#include "run-command.h"
+#include "commit.h"
const char git_usage_string[] =
"git [--version] [--help] [-c name=value]\n"
@@ -146,6 +147,12 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1);
if (envchanged)
*envchanged = 1;
+ } else if (!strcmp(cmd, "--shallow-file")) {
+ (*argv)++;
+ (*argc)--;
+ set_alternate_shallow_file((*argv)[0]);
+ if (envchanged)
+ *envchanged = 1;
} else {
fprintf(stderr, "Unknown option: %s\n", cmd);
usage(git_usage_string);
@@ -507,8 +514,9 @@ static int run_argv(int *argcp, const char ***argv)
}
-int main(int argc, const char **argv)
+int main(int argc, char **av)
{
+ const char **argv = (const char **) av;
const char *cmd;
startup_info = &git_startup_info;
diff --git a/git.rc b/git.rc
new file mode 100644
index 0000000..bce6db9
--- /dev/null
+++ b/git.rc
@@ -0,0 +1,22 @@
+1 VERSIONINFO
+FILEVERSION MAJOR,MINOR,PATCH,0
+PRODUCTVERSION MAJOR,MINOR,PATCH,0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" /* LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP */
+ BEGIN
+ VALUE "CompanyName", "The Git Development Community\0"
+ VALUE "FileDescription", "Git for Windows\0"
+ VALUE "InternalName", "git\0"
+ VALUE "OriginalFilename", "git.exe\0"
+ VALUE "ProductName", "Git\0"
+ VALUE "ProductVersion", GIT_VERSION "\0"
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 80950c0..f429f75 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -85,6 +85,9 @@ our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
# string of the home link on top of all pages
our $home_link_str = "++GITWEB_HOME_LINK_STR++";
+# extra breadcrumbs preceding the home link
+our @extra_breadcrumbs = ();
+
# name of your site or organization to appear in page titles
# replace this with something more descriptive for clearer bookmarks
our $site_name = "++GITWEB_SITENAME++"
@@ -1086,7 +1089,7 @@ sub evaluate_and_validate_params {
our $search_use_regexp = $input_params{'search_use_regexp'};
our $searchtext = $input_params{'searchtext'};
- our $search_regexp;
+ our $search_regexp = undef;
if (defined $searchtext) {
if (length($searchtext) < 2) {
die_error(403, "At least two characters are required for search parameter");
@@ -3982,7 +3985,9 @@ sub print_nav_breadcrumbs_path {
sub print_nav_breadcrumbs {
my %opts = @_;
- print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
+ for my $crumb (@extra_breadcrumbs, [ $home_link_str => $home_link ]) {
+ print $cgi->a({-href => esc_url($crumb->[1])}, $crumb->[0]) . " / ";
+ }
if (defined $project) {
my @dirname = split '/', $project;
my $projectbasename = pop @dirname;
diff --git a/help.c b/help.c
index 02ba043..08c54ef 100644
--- a/help.c
+++ b/help.c
@@ -7,6 +7,7 @@
#include "string-list.h"
#include "column.h"
#include "version.h"
+#include "refs.h"
void add_cmdname(struct cmdnames *cmds, const char *name, int len)
{
@@ -106,7 +107,7 @@ static int is_executable(const char *name)
!S_ISREG(st.st_mode))
return 0;
-#if defined(WIN32) || defined(__CYGWIN__)
+#if defined(GIT_WINDOWS_NATIVE) || defined(__CYGWIN__)
#if defined(__CYGWIN__)
if ((st.st_mode & S_IXUSR) == 0)
#endif
@@ -404,3 +405,52 @@ int cmd_version(int argc, const char **argv, const char *prefix)
printf("git version %s\n", git_version_string);
return 0;
}
+
+struct similar_ref_cb {
+ const char *base_ref;
+ struct string_list *similar_refs;
+};
+
+static int append_similar_ref(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);
+ char *branch = strrchr(refname, '/') + 1;
+ /* A remote branch of the same name is deemed similar */
+ if (!prefixcmp(refname, "refs/remotes/") &&
+ !strcmp(branch, cb->base_ref))
+ string_list_append(cb->similar_refs,
+ refname + strlen("refs/remotes/"));
+ return 0;
+}
+
+static struct string_list guess_refs(const char *ref)
+{
+ struct similar_ref_cb ref_cb;
+ struct string_list similar_refs = STRING_LIST_INIT_NODUP;
+
+ ref_cb.base_ref = ref;
+ ref_cb.similar_refs = &similar_refs;
+ for_each_ref(append_similar_ref, &ref_cb);
+ return similar_refs;
+}
+
+void help_unknown_ref(const char *ref, const char *cmd, const char *error)
+{
+ int i;
+ struct string_list suggested_refs = guess_refs(ref);
+
+ fprintf_ln(stderr, _("%s: %s - %s"), cmd, ref, error);
+
+ if (suggested_refs.nr > 0) {
+ fprintf_ln(stderr,
+ Q_("\nDid you mean this?",
+ "\nDid you mean one of these?",
+ suggested_refs.nr));
+ for (i = 0; i < suggested_refs.nr; i++)
+ fprintf(stderr, "\t%s\n", suggested_refs.items[i].string);
+ }
+
+ string_list_clear(&suggested_refs, 0);
+ exit(1);
+}
diff --git a/help.h b/help.h
index 0ae5a12..b21d7c9 100644
--- a/help.h
+++ b/help.h
@@ -27,4 +27,9 @@ 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(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
+/*
+ * call this to die(), when it is suspected that the user mistyped a
+ * ref to the command, to give suggested "correct" refs.
+ */
+extern void help_unknown_ref(const char *ref, const char *cmd, const char *error);
#endif /* HELP_H */
diff --git a/http-backend.c b/http-backend.c
index 6b85ffa..0324417 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -410,14 +410,14 @@ static void get_info_refs(char *arg)
strbuf_release(&buf);
}
-static int show_head_ref(const char *name, const unsigned char *sha1,
+static int show_head_ref(const char *refname, const unsigned char *sha1,
int flag, void *cb_data)
{
struct strbuf *buf = cb_data;
if (flag & REF_ISSYMREF) {
- unsigned char sha1[20];
- const char *target = resolve_ref_unsafe(name, sha1, 1, NULL);
+ unsigned char unused[20];
+ const char *target = resolve_ref_unsafe(refname, unused, 1, NULL);
const char *target_nons = strip_namespace(target);
strbuf_addf(buf, "ref: %s\n", target_nons);
diff --git a/http-push.c b/http-push.c
index 395a8cf..6dad188 100644
--- a/http-push.c
+++ b/http-push.c
@@ -663,7 +663,7 @@ static void add_fetch_request(struct object *obj)
static int add_send_request(struct object *obj, struct remote_lock *lock)
{
- struct transfer_request *request = request_queue_head;
+ struct transfer_request *request;
struct packed_git *target;
/* Keep locks active */
diff --git a/http.c b/http.c
index 92aba59..2d086ae 100644
--- a/http.c
+++ b/http.c
@@ -228,9 +228,15 @@ static void init_curl_http_auth(CURL *result)
#else
{
static struct strbuf up = STRBUF_INIT;
- strbuf_reset(&up);
- strbuf_addf(&up, "%s:%s",
- http_auth.username, http_auth.password);
+ /*
+ * Note that we assume we only ever have a single set of
+ * credentials in a given program run, so we do not have
+ * to worry about updating this buffer, only setting its
+ * initial value.
+ */
+ if (!up.len)
+ strbuf_addf(&up, "%s:%s",
+ http_auth.username, http_auth.password);
curl_easy_setopt(result, CURLOPT_USERPWD, up.buf);
}
#endif
diff --git a/imap-send.c b/imap-send.c
index d9bcfb4..d6b65e2 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -29,8 +29,18 @@
#ifdef NO_OPENSSL
typedef void *SSL;
#else
+#ifdef APPLE_COMMON_CRYPTO
+#include <CommonCrypto/CommonHMAC.h>
+#define HMAC_CTX CCHmacContext
+#define HMAC_Init(hmac, key, len, algo) CCHmacInit(hmac, algo, key, len)
+#define HMAC_Update CCHmacUpdate
+#define HMAC_Final(hmac, hash, ptr) CCHmacFinal(hmac, hash)
+#define HMAC_CTX_cleanup(ignore)
+#define EVP_md5() kCCHmacAlgMD5
+#else
#include <openssl/evp.h>
#include <openssl/hmac.h>
+#endif
#include <openssl/x509v3.h>
#endif
diff --git a/line-log.c b/line-log.c
new file mode 100644
index 0000000..8cc29a0
--- /dev/null
+++ b/line-log.c
@@ -0,0 +1,1274 @@
+#include "git-compat-util.h"
+#include "line-range.h"
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "diff.h"
+#include "commit.h"
+#include "decorate.h"
+#include "revision.h"
+#include "xdiff-interface.h"
+#include "strbuf.h"
+#include "log-tree.h"
+#include "graph.h"
+#include "userdiff.h"
+#include "line-log.h"
+
+static void range_set_grow(struct range_set *rs, size_t extra)
+{
+ ALLOC_GROW(rs->ranges, rs->nr + extra, rs->alloc);
+}
+
+/* Either initialization would be fine */
+#define RANGE_SET_INIT {0}
+
+static void range_set_init(struct range_set *rs, size_t prealloc)
+{
+ rs->alloc = rs->nr = 0;
+ rs->ranges = NULL;
+ if (prealloc)
+ range_set_grow(rs, prealloc);
+}
+
+static void range_set_release(struct range_set *rs)
+{
+ free(rs->ranges);
+ rs->alloc = rs->nr = 0;
+ rs->ranges = NULL;
+}
+
+/* dst must be uninitialized! */
+static void range_set_copy(struct range_set *dst, struct range_set *src)
+{
+ range_set_init(dst, src->nr);
+ memcpy(dst->ranges, src->ranges, src->nr*sizeof(struct range_set));
+ dst->nr = src->nr;
+}
+static void range_set_move(struct range_set *dst, struct range_set *src)
+{
+ range_set_release(dst);
+ dst->ranges = src->ranges;
+ dst->nr = src->nr;
+ dst->alloc = src->alloc;
+ src->ranges = NULL;
+ src->alloc = src->nr = 0;
+}
+
+/* tack on a _new_ range _at the end_ */
+static void range_set_append_unsafe(struct range_set *rs, long a, long b)
+{
+ assert(a <= b);
+ range_set_grow(rs, 1);
+ rs->ranges[rs->nr].start = a;
+ rs->ranges[rs->nr].end = b;
+ rs->nr++;
+}
+
+static void range_set_append(struct range_set *rs, long a, long b)
+{
+ assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
+ range_set_append_unsafe(rs, a, b);
+}
+
+static int range_cmp(const void *_r, const void *_s)
+{
+ const struct range *r = _r;
+ const struct range *s = _s;
+
+ /* this could be simply 'return r.start-s.start', but for the types */
+ if (r->start == s->start)
+ return 0;
+ if (r->start < s->start)
+ return -1;
+ return 1;
+}
+
+/*
+ * Check that the ranges are non-empty, sorted and non-overlapping
+ */
+static void range_set_check_invariants(struct range_set *rs)
+{
+ int i;
+
+ if (!rs)
+ return;
+
+ if (rs->nr)
+ assert(rs->ranges[0].start < rs->ranges[0].end);
+
+ for (i = 1; i < rs->nr; i++) {
+ assert(rs->ranges[i-1].end < rs->ranges[i].start);
+ assert(rs->ranges[i].start < rs->ranges[i].end);
+ }
+}
+
+/*
+ * In-place pass of sorting and merging the ranges in the range set,
+ * to establish the invariants when we get the ranges from the user
+ */
+static void sort_and_merge_range_set(struct range_set *rs)
+{
+ int i;
+ int o = 1; /* output cursor */
+
+ qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp);
+
+ for (i = 1; i < rs->nr; i++) {
+ if (rs->ranges[i].start <= rs->ranges[o-1].end) {
+ if (rs->ranges[o-1].end < rs->ranges[i].end)
+ rs->ranges[o-1].end = rs->ranges[i].end;
+ } else {
+ rs->ranges[o].start = rs->ranges[i].start;
+ rs->ranges[o].end = rs->ranges[i].end;
+ o++;
+ }
+ }
+ assert(o <= rs->nr);
+ rs->nr = o;
+
+ range_set_check_invariants(rs);
+}
+
+/*
+ * Union of range sets (i.e., sets of line numbers). Used to merge
+ * them when searches meet at a common ancestor.
+ *
+ * This is also where the ranges are consolidated into canonical form:
+ * overlapping and adjacent ranges are merged, and empty ranges are
+ * removed.
+ */
+static void range_set_union(struct range_set *out,
+ struct range_set *a, struct range_set *b)
+{
+ int i = 0, j = 0, o = 0;
+ struct range *ra = a->ranges;
+ struct range *rb = b->ranges;
+ /* cannot make an alias of out->ranges: it may change during grow */
+
+ assert(out->nr == 0);
+ while (i < a->nr || j < b->nr) {
+ struct range *new;
+ if (i < a->nr && j < b->nr) {
+ if (ra[i].start < rb[j].start)
+ new = &ra[i++];
+ else if (ra[i].start > rb[j].start)
+ new = &rb[j++];
+ else if (ra[i].end < rb[j].end)
+ new = &ra[i++];
+ else
+ new = &rb[j++];
+ } else if (i < a->nr) /* b exhausted */
+ new = &ra[i++];
+ else /* a exhausted */
+ new = &rb[j++];
+ if (new->start == new->end)
+ ; /* empty range */
+ else if (!o || out->ranges[o-1].end < new->start) {
+ range_set_grow(out, 1);
+ out->ranges[o].start = new->start;
+ out->ranges[o].end = new->end;
+ o++;
+ } else if (out->ranges[o-1].end < new->end) {
+ out->ranges[o-1].end = new->end;
+ }
+ }
+ out->nr = o;
+}
+
+/*
+ * Difference of range sets (out = a \ b). Pass the "interesting"
+ * ranges as 'a' and the target side of the diff as 'b': it removes
+ * the ranges for which the commit is responsible.
+ */
+static void range_set_difference(struct range_set *out,
+ struct range_set *a, struct range_set *b)
+{
+ int i, j = 0;
+ for (i = 0; i < a->nr; i++) {
+ long start = a->ranges[i].start;
+ long end = a->ranges[i].end;
+ while (start < end) {
+ while (j < b->nr && start >= b->ranges[j].end)
+ /*
+ * a: |-------
+ * b: ------|
+ */
+ j++;
+ if (j >= b->nr || end < b->ranges[j].start) {
+ /*
+ * b exhausted, or
+ * a: ----|
+ * b: |----
+ */
+ range_set_append(out, start, end);
+ break;
+ }
+ if (start >= b->ranges[j].start) {
+ /*
+ * a: |--????
+ * b: |------|
+ */
+ start = b->ranges[j].end;
+ } else if (end > b->ranges[j].start) {
+ /*
+ * a: |-----|
+ * b: |--?????
+ */
+ if (start < b->ranges[j].start)
+ range_set_append(out, start, b->ranges[j].start);
+ start = b->ranges[j].end;
+ }
+ }
+ }
+}
+
+static void diff_ranges_init(struct diff_ranges *diff)
+{
+ range_set_init(&diff->parent, 0);
+ range_set_init(&diff->target, 0);
+}
+
+static void diff_ranges_release(struct diff_ranges *diff)
+{
+ range_set_release(&diff->parent);
+ range_set_release(&diff->target);
+}
+
+void line_log_data_init(struct line_log_data *r)
+{
+ memset(r, 0, sizeof(struct line_log_data));
+ range_set_init(&r->ranges, 0);
+}
+
+static void line_log_data_clear(struct line_log_data *r)
+{
+ range_set_release(&r->ranges);
+ if (r->pair)
+ diff_free_filepair(r->pair);
+}
+
+static void free_line_log_data(struct line_log_data *r)
+{
+ while (r) {
+ struct line_log_data *next = r->next;
+ line_log_data_clear(r);
+ free(r);
+ r = next;
+ }
+}
+
+static struct line_log_data *
+search_line_log_data(struct line_log_data *list, const char *path,
+ struct line_log_data **insertion_point)
+{
+ struct line_log_data *p = list;
+ if (insertion_point)
+ *insertion_point = NULL;
+ while (p) {
+ int cmp = strcmp(p->path, path);
+ if (!cmp)
+ return p;
+ if (insertion_point && cmp < 0)
+ *insertion_point = p;
+ p = p->next;
+ }
+ return NULL;
+}
+
+/*
+ * Note: takes ownership of 'path', which happens to be what the only
+ * caller needs.
+ */
+static void line_log_data_insert(struct line_log_data **list,
+ char *path,
+ long begin, long end)
+{
+ struct line_log_data *ip;
+ struct line_log_data *p = search_line_log_data(*list, path, &ip);
+
+ if (p) {
+ range_set_append_unsafe(&p->ranges, begin, end);
+ sort_and_merge_range_set(&p->ranges);
+ free(path);
+ return;
+ }
+
+ p = xcalloc(1, sizeof(struct line_log_data));
+ p->path = path;
+ range_set_append(&p->ranges, begin, end);
+ if (ip) {
+ p->next = ip->next;
+ ip->next = p;
+ } else {
+ p->next = *list;
+ *list = p;
+ }
+}
+
+struct collect_diff_cbdata {
+ struct diff_ranges *diff;
+};
+
+static int collect_diff_cb(long start_a, long count_a,
+ long start_b, long count_b,
+ void *data)
+{
+ struct collect_diff_cbdata *d = data;
+
+ if (count_a >= 0)
+ range_set_append(&d->diff->parent, start_a, start_a + count_a);
+ if (count_b >= 0)
+ range_set_append(&d->diff->target, start_b, start_b + count_b);
+
+ return 0;
+}
+
+static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
+{
+ struct collect_diff_cbdata cbdata = {NULL};
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ memset(&xpp, 0, sizeof(xpp));
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = xecfg.interhunkctxlen = 0;
+
+ cbdata.diff = out;
+ xecfg.hunk_func = collect_diff_cb;
+ memset(&ecb, 0, sizeof(ecb));
+ ecb.priv = &cbdata;
+ xdi_diff(parent, target, &xpp, &xecfg, &ecb);
+}
+
+/*
+ * These are handy for debugging. Removing them with #if 0 silences
+ * the "unused function" warning.
+ */
+#if 0
+static void dump_range_set(struct range_set *rs, const char *desc)
+{
+ int i;
+ printf("range set %s (%d items):\n", desc, rs->nr);
+ for (i = 0; i < rs->nr; i++)
+ printf("\t[%ld,%ld]\n", rs->ranges[i].start, rs->ranges[i].end);
+}
+
+static void dump_line_log_data(struct line_log_data *r)
+{
+ char buf[4096];
+ while (r) {
+ snprintf(buf, 4096, "file %s\n", r->path);
+ dump_range_set(&r->ranges, buf);
+ r = r->next;
+ }
+}
+
+static void dump_diff_ranges(struct diff_ranges *diff, const char *desc)
+{
+ int i;
+ assert(diff->parent.nr == diff->target.nr);
+ printf("diff ranges %s (%d items):\n", desc, diff->parent.nr);
+ printf("\tparent\ttarget\n");
+ for (i = 0; i < diff->parent.nr; i++) {
+ printf("\t[%ld,%ld]\t[%ld,%ld]\n",
+ diff->parent.ranges[i].start,
+ diff->parent.ranges[i].end,
+ diff->target.ranges[i].start,
+ diff->target.ranges[i].end);
+ }
+}
+#endif
+
+
+static int ranges_overlap(struct range *a, struct range *b)
+{
+ return !(a->end <= b->start || b->end <= a->start);
+}
+
+/*
+ * Given a diff and the set of interesting ranges, determine all hunks
+ * of the diff which touch (overlap) at least one of the interesting
+ * ranges in the target.
+ */
+static void diff_ranges_filter_touched(struct diff_ranges *out,
+ struct diff_ranges *diff,
+ struct range_set *rs)
+{
+ int i, j = 0;
+
+ assert(out->target.nr == 0);
+
+ for (i = 0; i < diff->target.nr; i++) {
+ while (diff->target.ranges[i].start > rs->ranges[j].end) {
+ j++;
+ if (j == rs->nr)
+ return;
+ }
+ if (ranges_overlap(&diff->target.ranges[i], &rs->ranges[j])) {
+ range_set_append(&out->parent,
+ diff->parent.ranges[i].start,
+ diff->parent.ranges[i].end);
+ range_set_append(&out->target,
+ diff->target.ranges[i].start,
+ diff->target.ranges[i].end);
+ }
+ }
+}
+
+/*
+ * Adjust the line counts in 'rs' to account for the lines
+ * added/removed in the diff.
+ */
+static void range_set_shift_diff(struct range_set *out,
+ struct range_set *rs,
+ struct diff_ranges *diff)
+{
+ int i, j = 0;
+ long offset = 0;
+ struct range *src = rs->ranges;
+ struct range *target = diff->target.ranges;
+ struct range *parent = diff->parent.ranges;
+
+ for (i = 0; i < rs->nr; i++) {
+ while (j < diff->target.nr && src[i].start >= target[j].start) {
+ offset += (parent[j].end-parent[j].start)
+ - (target[j].end-target[j].start);
+ j++;
+ }
+ range_set_append(out, src[i].start+offset, src[i].end+offset);
+ }
+}
+
+/*
+ * Given a diff and the set of interesting ranges, map the ranges
+ * across the diff. That is: observe that the target commit takes
+ * blame for all the + (target-side) ranges. So for every pair of
+ * ranges in the diff that was touched, we remove the latter and add
+ * its parent side.
+ */
+static void range_set_map_across_diff(struct range_set *out,
+ struct range_set *rs,
+ struct diff_ranges *diff,
+ struct diff_ranges **touched_out)
+{
+ struct diff_ranges *touched = xmalloc(sizeof(*touched));
+ struct range_set tmp1 = RANGE_SET_INIT;
+ struct range_set tmp2 = RANGE_SET_INIT;
+
+ diff_ranges_init(touched);
+ diff_ranges_filter_touched(touched, diff, rs);
+ range_set_difference(&tmp1, rs, &touched->target);
+ range_set_shift_diff(&tmp2, &tmp1, diff);
+ range_set_union(out, &tmp2, &touched->parent);
+ range_set_release(&tmp1);
+ range_set_release(&tmp2);
+
+ *touched_out = touched;
+}
+
+static struct commit *check_single_commit(struct rev_info *revs)
+{
+ struct object *commit = NULL;
+ int found = -1;
+ int i;
+
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ while (obj->type == OBJ_TAG)
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (commit)
+ die("More than one commit to dig from: %s and %s?",
+ revs->pending.objects[i].name,
+ revs->pending.objects[found].name);
+ commit = obj;
+ found = i;
+ }
+
+ if (!commit)
+ die("No commit specified?");
+
+ return (struct commit *) commit;
+}
+
+static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec)
+{
+ unsigned mode;
+ unsigned char sha1[20];
+
+ if (get_tree_entry(commit->object.sha1, spec->path,
+ sha1, &mode))
+ die("There is no path %s in the commit", spec->path);
+ fill_filespec(spec, sha1, 1, mode);
+
+ return;
+}
+
+static void fill_line_ends(struct diff_filespec *spec, long *lines,
+ unsigned long **line_ends)
+{
+ int num = 0, size = 50;
+ long cur = 0;
+ unsigned long *ends = NULL;
+ char *data = NULL;
+
+ if (diff_populate_filespec(spec, 0))
+ die("Cannot read blob %s", sha1_to_hex(spec->sha1));
+
+ ends = xmalloc(size * sizeof(*ends));
+ ends[cur++] = 0;
+ data = spec->data;
+ while (num < spec->size) {
+ if (data[num] == '\n' || num == spec->size - 1) {
+ ALLOC_GROW(ends, (cur + 1), size);
+ ends[cur++] = num;
+ }
+ num++;
+ }
+
+ /* shrink the array to fit the elements */
+ ends = xrealloc(ends, cur * sizeof(*ends));
+ *lines = cur-1;
+ *line_ends = ends;
+}
+
+struct nth_line_cb {
+ struct diff_filespec *spec;
+ long lines;
+ unsigned long *line_ends;
+};
+
+static const char *nth_line(void *data, long line)
+{
+ struct nth_line_cb *d = data;
+ assert(d && line <= d->lines);
+ assert(d->spec && d->spec->data);
+
+ if (line == 0)
+ return (char *)d->spec->data;
+ else
+ return (char *)d->spec->data + d->line_ends[line] + 1;
+}
+
+static struct line_log_data *
+parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
+{
+ long lines = 0;
+ unsigned long *ends = NULL;
+ struct nth_line_cb cb_data;
+ struct string_list_item *item;
+ struct line_log_data *ranges = NULL;
+
+ for_each_string_list_item(item, args) {
+ const char *name_part, *range_part;
+ char *full_name;
+ struct diff_filespec *spec;
+ long begin = 0, end = 0;
+
+ name_part = skip_range_arg(item->string);
+ if (!name_part || *name_part != ':' || !name_part[1])
+ die("-L argument '%s' not of the form start,end:file",
+ item->string);
+ range_part = xstrndup(item->string, name_part - item->string);
+ name_part++;
+
+ full_name = prefix_path(prefix, prefix ? strlen(prefix) : 0,
+ name_part);
+
+ spec = alloc_filespec(full_name);
+ fill_blob_sha1(commit, spec);
+ fill_line_ends(spec, &lines, &ends);
+ cb_data.spec = spec;
+ cb_data.lines = lines;
+ cb_data.line_ends = ends;
+
+ if (parse_range_arg(range_part, nth_line, &cb_data,
+ lines, &begin, &end,
+ full_name))
+ die("malformed -L argument '%s'", range_part);
+ if (begin < 1)
+ begin = 1;
+ if (end < 1)
+ end = lines;
+ begin--;
+ if (lines < end || lines < begin)
+ die("file %s has only %ld lines", name_part, lines);
+ line_log_data_insert(&ranges, full_name, begin, end);
+
+ free_filespec(spec);
+ free(ends);
+ ends = NULL;
+ }
+
+ return ranges;
+}
+
+static struct line_log_data *line_log_data_copy_one(struct line_log_data *r)
+{
+ struct line_log_data *ret = xmalloc(sizeof(*ret));
+
+ assert(r);
+ line_log_data_init(ret);
+ range_set_copy(&ret->ranges, &r->ranges);
+
+ ret->path = xstrdup(r->path);
+
+ return ret;
+}
+
+static struct line_log_data *
+line_log_data_copy(struct line_log_data *r)
+{
+ struct line_log_data *ret = NULL;
+ struct line_log_data *tmp = NULL, *prev = NULL;
+
+ assert(r);
+ ret = tmp = prev = line_log_data_copy_one(r);
+ r = r->next;
+ while (r) {
+ tmp = line_log_data_copy_one(r);
+ prev->next = tmp;
+ prev = tmp;
+ r = r->next;
+ }
+
+ return ret;
+}
+
+/* merge two range sets across files */
+static struct line_log_data *line_log_data_merge(struct line_log_data *a,
+ struct line_log_data *b)
+{
+ struct line_log_data *head = NULL, **pp = &head;
+
+ while (a || b) {
+ struct line_log_data *src;
+ struct line_log_data *src2 = NULL;
+ struct line_log_data *d;
+ int cmp;
+ if (!a)
+ cmp = 1;
+ else if (!b)
+ cmp = -1;
+ else
+ cmp = strcmp(a->path, b->path);
+ if (cmp < 0) {
+ src = a;
+ a = a->next;
+ } else if (cmp == 0) {
+ src = a;
+ a = a->next;
+ src2 = b;
+ b = b->next;
+ } else {
+ src = b;
+ b = b->next;
+ }
+ d = xmalloc(sizeof(struct line_log_data));
+ line_log_data_init(d);
+ d->path = xstrdup(src->path);
+ *pp = d;
+ pp = &d->next;
+ if (src2)
+ range_set_union(&d->ranges, &src->ranges, &src2->ranges);
+ else
+ range_set_copy(&d->ranges, &src->ranges);
+ }
+
+ return head;
+}
+
+static void add_line_range(struct rev_info *revs, struct commit *commit,
+ struct line_log_data *range)
+{
+ struct line_log_data *old = NULL;
+ struct line_log_data *new = NULL;
+
+ old = lookup_decoration(&revs->line_log_data, &commit->object);
+ if (old && range) {
+ new = line_log_data_merge(old, range);
+ free_line_log_data(old);
+ } else if (range)
+ new = line_log_data_copy(range);
+
+ if (new)
+ add_decoration(&revs->line_log_data, &commit->object, new);
+}
+
+static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)
+{
+ struct line_log_data *r;
+ r = lookup_decoration(&revs->line_log_data, &commit->object);
+ if (!r)
+ return;
+ free_line_log_data(r);
+ add_decoration(&revs->line_log_data, &commit->object, NULL);
+}
+
+static struct line_log_data *lookup_line_range(struct rev_info *revs,
+ struct commit *commit)
+{
+ struct line_log_data *ret = NULL;
+ struct line_log_data *d;
+
+ ret = lookup_decoration(&revs->line_log_data, &commit->object);
+
+ for (d = ret; d; d = d->next)
+ range_set_check_invariants(&d->ranges);
+
+ return ret;
+}
+
+void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args)
+{
+ struct commit *commit = NULL;
+ struct line_log_data *range;
+
+ commit = check_single_commit(rev);
+ range = parse_lines(commit, prefix, args);
+ add_line_range(rev, commit, range);
+
+ if (!rev->diffopt.detect_rename) {
+ int i, count = 0;
+ struct line_log_data *r = range;
+ const char **paths;
+ while (r) {
+ count++;
+ r = r->next;
+ }
+ paths = xmalloc((count+1)*sizeof(char *));
+ r = range;
+ for (i = 0; i < count; i++) {
+ paths[i] = xstrdup(r->path);
+ r = r->next;
+ }
+ paths[count] = NULL;
+ init_pathspec(&rev->diffopt.pathspec, paths);
+ free(paths);
+ }
+}
+
+static void load_tree_desc(struct tree_desc *desc, void **tree,
+ const unsigned char *sha1)
+{
+ unsigned long size;
+ *tree = read_object_with_reference(sha1, tree_type, &size, NULL);
+ if (!*tree)
+ die("Unable to read tree (%s)", sha1_to_hex(sha1));
+ init_tree_desc(desc, *tree, size);
+}
+
+static int count_parents(struct commit *commit)
+{
+ struct commit_list *parents = commit->parents;
+ int count = 0;
+ while (parents) {
+ count++;
+ parents = parents->next;
+ }
+ return count;
+}
+
+static void move_diff_queue(struct diff_queue_struct *dst,
+ struct diff_queue_struct *src)
+{
+ assert(src != dst);
+ memcpy(dst, src, sizeof(struct diff_queue_struct));
+ DIFF_QUEUE_CLEAR(src);
+}
+
+static void filter_diffs_for_paths(struct line_log_data *range, int keep_deletions)
+{
+ int i;
+ struct diff_queue_struct outq;
+ DIFF_QUEUE_CLEAR(&outq);
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ struct line_log_data *rg = NULL;
+
+ if (!DIFF_FILE_VALID(p->two)) {
+ if (keep_deletions)
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ continue;
+ }
+ for (rg = range; rg; rg = rg->next) {
+ if (!strcmp(rg->path, p->two->path))
+ break;
+ }
+ if (rg)
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+ free(diff_queued_diff.queue);
+ diff_queued_diff = outq;
+}
+
+static inline int diff_might_be_rename(void)
+{
+ int i;
+ for (i = 0; i < diff_queued_diff.nr; i++)
+ if (!DIFF_FILE_VALID(diff_queued_diff.queue[i]->one)) {
+ /* fprintf(stderr, "diff_might_be_rename found creation of: %s\n", */
+ /* diff_queued_diff.queue[i]->two->path); */
+ return 1;
+ }
+ return 0;
+}
+
+static void queue_diffs(struct line_log_data *range,
+ struct diff_options *opt,
+ struct diff_queue_struct *queue,
+ struct commit *commit, struct commit *parent)
+{
+ void *tree1 = NULL, *tree2 = NULL;
+ struct tree_desc desc1, desc2;
+
+ assert(commit);
+ load_tree_desc(&desc2, &tree2, commit->tree->object.sha1);
+ if (parent)
+ load_tree_desc(&desc1, &tree1, parent->tree->object.sha1);
+ else
+ init_tree_desc(&desc1, "", 0);
+
+ DIFF_QUEUE_CLEAR(&diff_queued_diff);
+ diff_tree(&desc1, &desc2, "", opt);
+ if (opt->detect_rename) {
+ filter_diffs_for_paths(range, 1);
+ if (diff_might_be_rename())
+ diffcore_std(opt);
+ filter_diffs_for_paths(range, 0);
+ }
+ move_diff_queue(queue, &diff_queued_diff);
+
+ if (tree1)
+ free(tree1);
+ if (tree2)
+ free(tree2);
+}
+
+static char *get_nth_line(long line, unsigned long *ends, void *data)
+{
+ if (line == 0)
+ return (char *)data;
+ else
+ return (char *)data + ends[line] + 1;
+}
+
+static void print_line(const char *prefix, char first,
+ long line, unsigned long *ends, void *data,
+ const char *color, const char *reset)
+{
+ char *begin = get_nth_line(line, ends, data);
+ char *end = get_nth_line(line+1, ends, data);
+ int had_nl = 0;
+
+ if (end > begin && end[-1] == '\n') {
+ end--;
+ had_nl = 1;
+ }
+
+ fputs(prefix, stdout);
+ fputs(color, stdout);
+ putchar(first);
+ fwrite(begin, 1, end-begin, stdout);
+ fputs(reset, stdout);
+ putchar('\n');
+ if (!had_nl)
+ fputs("\\ No newline at end of file\n", stdout);
+}
+
+static char *output_prefix(struct diff_options *opt)
+{
+ char *prefix = "";
+
+ if (opt->output_prefix) {
+ struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
+ prefix = sb->buf;
+ }
+
+ return prefix;
+}
+
+static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
+{
+ int i, j = 0;
+ long p_lines, t_lines;
+ unsigned long *p_ends = NULL, *t_ends = NULL;
+ struct diff_filepair *pair = range->pair;
+ struct diff_ranges *diff = &range->diff;
+
+ struct diff_options *opt = &rev->diffopt;
+ char *prefix = output_prefix(opt);
+ const char *c_reset = diff_get_color(opt->use_color, DIFF_RESET);
+ const char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO);
+ const char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);
+ const char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);
+ const char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);
+ const char *c_plain = diff_get_color(opt->use_color, DIFF_PLAIN);
+
+ if (!pair || !diff)
+ return;
+
+ if (pair->one->sha1_valid)
+ fill_line_ends(pair->one, &p_lines, &p_ends);
+ fill_line_ends(pair->two, &t_lines, &t_ends);
+
+ printf("%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset);
+ printf("%s%s--- %s%s%s\n", prefix, c_meta,
+ pair->one->sha1_valid ? "a/" : "",
+ pair->one->sha1_valid ? pair->one->path : "/dev/null",
+ c_reset);
+ printf("%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset);
+ for (i = 0; i < range->ranges.nr; i++) {
+ long p_start, p_end;
+ long t_start = range->ranges.ranges[i].start;
+ long t_end = range->ranges.ranges[i].end;
+ long t_cur = t_start;
+ int j_last;
+
+ while (j < diff->target.nr && diff->target.ranges[j].end < t_start)
+ j++;
+ if (j == diff->target.nr || diff->target.ranges[j].start > t_end)
+ continue;
+
+ /* Scan ahead to determine the last diff that falls in this range */
+ j_last = j;
+ while (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end)
+ j_last++;
+ if (j_last > j)
+ j_last--;
+
+ /*
+ * Compute parent hunk headers: we know that the diff
+ * has the correct line numbers (but not all hunks).
+ * So it suffices to shift the start/end according to
+ * the line numbers of the first/last hunk(s) that
+ * fall in this range.
+ */
+ if (t_start < diff->target.ranges[j].start)
+ p_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start);
+ else
+ p_start = diff->parent.ranges[j].start;
+ if (t_end > diff->target.ranges[j_last].end)
+ p_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end);
+ else
+ p_end = diff->parent.ranges[j_last].end;
+
+ if (!p_start && !p_end) {
+ p_start = -1;
+ p_end = -1;
+ }
+
+ /* Now output a diff hunk for this range */
+ printf("%s%s@@ -%ld,%ld +%ld,%ld @@%s\n",
+ prefix, c_frag,
+ p_start+1, p_end-p_start, t_start+1, t_end-t_start,
+ c_reset);
+ while (j < diff->target.nr && diff->target.ranges[j].start < t_end) {
+ int k;
+ for (; t_cur < diff->target.ranges[j].start; t_cur++)
+ print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
+ c_plain, c_reset);
+ for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)
+ print_line(prefix, '-', k, p_ends, pair->one->data,
+ c_old, c_reset);
+ for (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++)
+ print_line(prefix, '+', t_cur, t_ends, pair->two->data,
+ c_new, c_reset);
+ j++;
+ }
+ for (; t_cur < t_end; t_cur++)
+ print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
+ c_plain, c_reset);
+ }
+
+ free(p_ends);
+ free(t_ends);
+}
+
+/*
+ * NEEDSWORK: manually building a diff here is not the Right
+ * Thing(tm). log -L should be built into the diff pipeline.
+ */
+static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
+{
+ puts(output_prefix(&rev->diffopt));
+ while (range) {
+ dump_diff_hacky_one(rev, range);
+ range = range->next;
+ }
+}
+
+/*
+ * Unlike most other functions, this destructively operates on
+ * 'range'.
+ */
+static int process_diff_filepair(struct rev_info *rev,
+ struct diff_filepair *pair,
+ struct line_log_data *range,
+ struct diff_ranges **diff_out)
+{
+ struct line_log_data *rg = range;
+ struct range_set tmp;
+ struct diff_ranges diff;
+ mmfile_t file_parent, file_target;
+
+ assert(pair->two->path);
+ while (rg) {
+ assert(rg->path);
+ if (!strcmp(rg->path, pair->two->path))
+ break;
+ rg = rg->next;
+ }
+
+ if (!rg)
+ return 0;
+ if (rg->ranges.nr == 0)
+ return 0;
+
+ assert(pair->two->sha1_valid);
+ diff_populate_filespec(pair->two, 0);
+ file_target.ptr = pair->two->data;
+ file_target.size = pair->two->size;
+
+ if (pair->one->sha1_valid) {
+ diff_populate_filespec(pair->one, 0);
+ file_parent.ptr = pair->one->data;
+ file_parent.size = pair->one->size;
+ } else {
+ file_parent.ptr = "";
+ file_parent.size = 0;
+ }
+
+ diff_ranges_init(&diff);
+ collect_diff(&file_parent, &file_target, &diff);
+
+ /* NEEDSWORK should apply some heuristics to prevent mismatches */
+ free(rg->path);
+ rg->path = xstrdup(pair->one->path);
+
+ range_set_init(&tmp, 0);
+ range_set_map_across_diff(&tmp, &rg->ranges, &diff, diff_out);
+ range_set_release(&rg->ranges);
+ range_set_move(&rg->ranges, &tmp);
+
+ diff_ranges_release(&diff);
+
+ return ((*diff_out)->parent.nr > 0);
+}
+
+static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)
+{
+ struct diff_filepair *new = xmalloc(sizeof(struct diff_filepair));
+ new->one = pair->one;
+ new->two = pair->two;
+ new->one->count++;
+ new->two->count++;
+ return new;
+}
+
+static void free_diffqueues(int n, struct diff_queue_struct *dq)
+{
+ int i, j;
+ for (i = 0; i < n; i++)
+ for (j = 0; j < dq[i].nr; j++)
+ diff_free_filepair(dq[i].queue[j]);
+ free(dq);
+}
+
+static int process_all_files(struct line_log_data **range_out,
+ struct rev_info *rev,
+ struct diff_queue_struct *queue,
+ struct line_log_data *range)
+{
+ int i, changed = 0;
+
+ *range_out = line_log_data_copy(range);
+
+ for (i = 0; i < queue->nr; i++) {
+ struct diff_ranges *pairdiff = NULL;
+ struct diff_filepair *pair = queue->queue[i];
+ if (process_diff_filepair(rev, pair, *range_out, &pairdiff)) {
+ /*
+ * Store away the diff for later output. We
+ * tuck it in the ranges we got as _input_,
+ * since that's the commit that caused the
+ * diff.
+ *
+ * NEEDSWORK not enough when we get around to
+ * doing something interesting with merges;
+ * currently each invocation on a merge parent
+ * trashes the previous one's diff.
+ *
+ * NEEDSWORK tramples over data structures not owned here
+ */
+ struct line_log_data *rg = range;
+ changed++;
+ while (rg && strcmp(rg->path, pair->two->path))
+ rg = rg->next;
+ assert(rg);
+ rg->pair = diff_filepair_dup(queue->queue[i]);
+ memcpy(&rg->diff, pairdiff, sizeof(struct diff_ranges));
+ }
+ }
+
+ return changed;
+}
+
+int line_log_print(struct rev_info *rev, struct commit *commit)
+{
+ struct line_log_data *range = lookup_line_range(rev, commit);
+
+ show_log(rev);
+ dump_diff_hacky(rev, range);
+ return 1;
+}
+
+static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit,
+ struct line_log_data *range)
+{
+ struct commit *parent = NULL;
+ struct diff_queue_struct queue;
+ struct line_log_data *parent_range;
+ int changed;
+
+ if (commit->parents)
+ parent = commit->parents->item;
+
+ queue_diffs(range, &rev->diffopt, &queue, commit, parent);
+ changed = process_all_files(&parent_range, rev, &queue, range);
+ if (parent)
+ add_line_range(rev, parent, parent_range);
+ return changed;
+}
+
+static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit,
+ struct line_log_data *range)
+{
+ struct diff_queue_struct *diffqueues;
+ struct line_log_data **cand;
+ struct commit **parents;
+ struct commit_list *p;
+ int i;
+ int nparents = count_parents(commit);
+
+ diffqueues = xmalloc(nparents * sizeof(*diffqueues));
+ cand = xmalloc(nparents * sizeof(*cand));
+ parents = xmalloc(nparents * sizeof(*parents));
+
+ p = commit->parents;
+ for (i = 0; i < nparents; i++) {
+ parents[i] = p->item;
+ p = p->next;
+ queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]);
+ }
+
+ for (i = 0; i < nparents; i++) {
+ int changed;
+ cand[i] = NULL;
+ changed = process_all_files(&cand[i], rev, &diffqueues[i], range);
+ if (!changed) {
+ /*
+ * This parent can take all the blame, so we
+ * don't follow any other path in history
+ */
+ add_line_range(rev, parents[i], cand[i]);
+ clear_commit_line_range(rev, commit);
+ commit->parents = xmalloc(sizeof(struct commit_list));
+ commit->parents->item = parents[i];
+ commit->parents->next = NULL;
+ free(parents);
+ free(cand);
+ free_diffqueues(nparents, diffqueues);
+ /* NEEDSWORK leaking like a sieve */
+ return 0;
+ }
+ }
+
+ /*
+ * No single parent took the blame. We add the candidates
+ * from the above loop to the parents.
+ */
+ for (i = 0; i < nparents; i++) {
+ add_line_range(rev, parents[i], cand[i]);
+ }
+
+ clear_commit_line_range(rev, commit);
+ free(parents);
+ free(cand);
+ free_diffqueues(nparents, diffqueues);
+ return 1;
+
+ /* NEEDSWORK evil merge detection stuff */
+ /* NEEDSWORK leaking like a sieve */
+}
+
+static int process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit)
+{
+ struct line_log_data *range = lookup_line_range(rev, commit);
+ int changed = 0;
+
+ if (range) {
+ if (!commit->parents || !commit->parents->next)
+ changed = process_ranges_ordinary_commit(rev, commit, range);
+ else
+ changed = process_ranges_merge_commit(rev, commit, range);
+ }
+
+ if (!changed)
+ commit->object.flags |= TREESAME;
+
+ return changed;
+}
+
+static enum rewrite_result line_log_rewrite_one(struct rev_info *rev, struct commit **pp)
+{
+ for (;;) {
+ struct commit *p = *pp;
+ if (p->parents && p->parents->next)
+ return rewrite_one_ok;
+ if (p->object.flags & UNINTERESTING)
+ return rewrite_one_ok;
+ if (!(p->object.flags & TREESAME))
+ return rewrite_one_ok;
+ if (!p->parents)
+ return rewrite_one_noparents;
+ *pp = p->parents->item;
+ }
+}
+
+int line_log_filter(struct rev_info *rev)
+{
+ struct commit *commit;
+ struct commit_list *list = rev->commits;
+ struct commit_list *out = NULL, **pp = &out;
+
+ while (list) {
+ struct commit_list *to_free = NULL;
+ commit = list->item;
+ if (process_ranges_arbitrary_commit(rev, commit)) {
+ *pp = list;
+ pp = &list->next;
+ } else
+ to_free = list;
+ list = list->next;
+ free(to_free);
+ }
+ *pp = NULL;
+
+ for (list = out; list; list = list->next)
+ rewrite_parents(rev, list->item, line_log_rewrite_one);
+
+ rev->commits = out;
+
+ return 0;
+}
diff --git a/line-log.h b/line-log.h
new file mode 100644
index 0000000..8bea45f
--- /dev/null
+++ b/line-log.h
@@ -0,0 +1,53 @@
+#ifndef LINE_LOG_H
+#define LINE_LOG_H
+
+#include "diffcore.h"
+
+struct rev_info;
+struct commit;
+
+/* A range [start,end]. Lines are numbered starting at 0, and the
+ * ranges include start but exclude end. */
+struct range {
+ long start, end;
+};
+
+/* A set of ranges. The ranges must always be disjoint and sorted. */
+struct range_set {
+ int alloc, nr;
+ struct range *ranges;
+};
+
+/* A diff, encoded as the set of pre- and post-image ranges where the
+ * files differ. A pair of ranges corresponds to a hunk. */
+struct diff_ranges {
+ struct range_set parent;
+ struct range_set target;
+};
+
+/* Linked list of interesting files and their associated ranges. The
+ * list must be kept sorted by path.
+ *
+ * For simplicity, even though this is highly redundant, each
+ * line_log_data owns its 'path'.
+ */
+struct line_log_data {
+ struct line_log_data *next;
+ char *path;
+ char status;
+ struct range_set ranges;
+ int arg_alloc, arg_nr;
+ const char **args;
+ struct diff_filepair *pair;
+ struct diff_ranges diff;
+};
+
+extern void line_log_data_init(struct line_log_data *r);
+
+extern void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args);
+
+extern int line_log_filter(struct rev_info *rev);
+
+extern int line_log_print(struct rev_info *rev, struct commit *commit);
+
+#endif /* LINE_LOG_H */
diff --git a/line-range.c b/line-range.c
new file mode 100644
index 0000000..8faf943
--- /dev/null
+++ b/line-range.c
@@ -0,0 +1,243 @@
+#include "git-compat-util.h"
+#include "line-range.h"
+#include "xdiff-interface.h"
+#include "strbuf.h"
+#include "userdiff.h"
+
+/*
+ * Parse one item in the -L option
+ */
+static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
+ void *data, long lines, long begin, long *ret)
+{
+ char *term;
+ const char *line;
+ long num;
+ int reg_error;
+ regex_t regexp;
+ regmatch_t match[1];
+
+ /* Allow "-L <something>,+20" to mean starting at <something>
+ * for 20 lines, or "-L <something>,-5" for 5 lines ending at
+ * <something>.
+ */
+ if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
+ num = strtol(spec + 1, &term, 10);
+ if (term != spec + 1) {
+ if (!ret)
+ return term;
+ if (spec[0] == '-')
+ num = 0 - num;
+ if (0 < num)
+ *ret = begin + num - 2;
+ else if (!num)
+ *ret = begin;
+ else
+ *ret = begin + num;
+ return term;
+ }
+ return spec;
+ }
+ num = strtol(spec, &term, 10);
+ if (term != spec) {
+ if (ret)
+ *ret = num;
+ return term;
+ }
+ if (spec[0] != '/')
+ return spec;
+
+ /* it could be a regexp of form /.../ */
+ for (term = (char *) spec + 1; *term && *term != '/'; term++) {
+ if (*term == '\\')
+ term++;
+ }
+ if (*term != '/')
+ return spec;
+
+ /* in the scan-only case we are not interested in the regex */
+ if (!ret)
+ return term+1;
+
+ /* try [spec+1 .. term-1] as regexp */
+ *term = 0;
+ begin--; /* input is in human terms */
+ line = nth_line(data, begin);
+
+ if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
+ !(reg_error = regexec(&regexp, line, 1, match, 0))) {
+ const char *cp = line + match[0].rm_so;
+ const char *nline;
+
+ while (begin++ < lines) {
+ nline = nth_line(data, begin);
+ if (line <= cp && cp < nline)
+ break;
+ line = nline;
+ }
+ *ret = begin;
+ regfree(&regexp);
+ *term++ = '/';
+ return term;
+ }
+ else {
+ char errbuf[1024];
+ regerror(reg_error, &regexp, errbuf, 1024);
+ die("-L parameter '%s': %s", spec + 1, errbuf);
+ }
+}
+
+static int match_funcname(xdemitconf_t *xecfg, const char *bol, const char *eol)
+{
+ if (xecfg) {
+ char buf[1];
+ return xecfg->find_func(bol, eol - bol, buf, 1,
+ xecfg->find_func_priv) >= 0;
+ }
+
+ if (bol == eol)
+ return 0;
+ if (isalpha(*bol) || *bol == '_' || *bol == '$')
+ return 1;
+ return 0;
+}
+
+static const char *find_funcname_matching_regexp(xdemitconf_t *xecfg, const char *start,
+ regex_t *regexp)
+{
+ int reg_error;
+ regmatch_t match[1];
+ while (1) {
+ const char *bol, *eol;
+ reg_error = regexec(regexp, start, 1, match, 0);
+ if (reg_error == REG_NOMATCH)
+ return NULL;
+ else if (reg_error) {
+ char errbuf[1024];
+ regerror(reg_error, regexp, errbuf, 1024);
+ die("-L parameter: regexec() failed: %s", errbuf);
+ }
+ /* determine extent of line matched */
+ bol = start+match[0].rm_so;
+ eol = start+match[0].rm_eo;
+ while (bol > start && *bol != '\n')
+ bol--;
+ if (*bol == '\n')
+ bol++;
+ while (*eol && *eol != '\n')
+ eol++;
+ if (*eol == '\n')
+ eol++;
+ /* is it a funcname line? */
+ if (match_funcname(xecfg, (char*) bol, (char*) eol))
+ return bol;
+ start = eol;
+ }
+}
+
+static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb,
+ void *cb_data, long lines, long *begin, long *end,
+ const char *path)
+{
+ char *pattern;
+ const char *term;
+ struct userdiff_driver *drv;
+ xdemitconf_t *xecfg = NULL;
+ const char *start;
+ const char *p;
+ int reg_error;
+ regex_t regexp;
+
+ assert(*arg == ':');
+ term = arg+1;
+ while (*term && *term != ':') {
+ if (*term == '\\' && *(term+1))
+ term++;
+ term++;
+ }
+ if (term == arg+1)
+ return NULL;
+ if (!begin) /* skip_range_arg case */
+ return term;
+
+ pattern = xstrndup(arg+1, term-(arg+1));
+
+ start = nth_line_cb(cb_data, 0);
+
+ drv = userdiff_find_by_path(path);
+ if (drv && drv->funcname.pattern) {
+ const struct userdiff_funcname *pe = &drv->funcname;
+ xecfg = xcalloc(1, sizeof(*xecfg));
+ xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
+ }
+
+ reg_error = regcomp(&regexp, pattern, REG_NEWLINE);
+ if (reg_error) {
+ char errbuf[1024];
+ regerror(reg_error, &regexp, errbuf, 1024);
+ die("-L parameter '%s': %s", pattern, errbuf);
+ }
+
+ p = find_funcname_matching_regexp(xecfg, (char*) start, &regexp);
+ if (!p)
+ die("-L parameter '%s': no match", pattern);
+ *begin = 0;
+ while (p > nth_line_cb(cb_data, *begin))
+ (*begin)++;
+
+ if (*begin >= lines)
+ die("-L parameter '%s' matches at EOF", pattern);
+
+ *end = *begin+1;
+ while (*end < lines) {
+ const char *bol = nth_line_cb(cb_data, *end);
+ const char *eol = nth_line_cb(cb_data, *end+1);
+ if (match_funcname(xecfg, bol, eol))
+ break;
+ (*end)++;
+ }
+
+ regfree(&regexp);
+ free(xecfg);
+ free(pattern);
+
+ /* compensate for 1-based numbering */
+ (*begin)++;
+
+ return term;
+}
+
+int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
+ void *cb_data, long lines, long *begin, long *end,
+ const char *path)
+{
+ if (*arg == ':') {
+ arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);
+ if (!arg || *arg)
+ return -1;
+ return 0;
+ }
+
+ arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
+
+ if (*arg == ',')
+ arg = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end);
+
+ if (*arg)
+ return -1;
+
+ return 0;
+}
+
+const char *skip_range_arg(const char *arg)
+{
+ if (*arg == ':')
+ return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL);
+
+ arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
+
+ if (*arg == ',')
+ arg = parse_loc(arg+1, NULL, NULL, 0, 0, NULL);
+
+ return arg;
+}
diff --git a/line-range.h b/line-range.h
new file mode 100644
index 0000000..ae3d012
--- /dev/null
+++ b/line-range.h
@@ -0,0 +1,36 @@
+#ifndef LINE_RANGE_H
+#define LINE_RANGE_H
+
+/*
+ * Parse one item in an -L begin,end option w.r.t. the notional file
+ * object 'cb_data' consisting of 'lines' lines.
+ *
+ * The 'nth_line_cb' callback is used to determine the start of the
+ * line 'lno' inside the 'cb_data'. The caller is expected to already
+ * have a suitable map at hand to make this a constant-time lookup.
+ *
+ * Returns 0 in case of success and -1 if there was an error. The
+ * actual range is stored in *begin and *end. The counting starts
+ * at 1! In case of error, the caller should show usage message.
+ */
+
+typedef const char *(*nth_line_fn_t)(void *data, long lno);
+
+extern int parse_range_arg(const char *arg,
+ nth_line_fn_t nth_line_cb,
+ void *cb_data, long lines,
+ long *begin, long *end,
+ const char *path);
+
+/*
+ * Scan past a range argument that could be parsed by
+ * 'parse_range_arg', to help the caller determine the start of the
+ * filename in '-L n,m:file' syntax.
+ *
+ * Returns a pointer to the first character after the 'n,m' part, or
+ * NULL in case the argument is obviously malformed.
+ */
+
+extern const char *skip_range_arg(const char *arg);
+
+#endif /* LINE_RANGE_H */
diff --git a/lockfile.c b/lockfile.c
index c6fb77b..8fbcb6a 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -124,15 +124,17 @@ static char *resolve_symlink(char *p, size_t s)
static int lock_file(struct lock_file *lk, const char *path, int flags)
{
- if (strlen(path) >= sizeof(lk->filename))
- return -1;
- strcpy(lk->filename, path);
/*
* subtract 5 from size to make sure there's room for adding
* ".lock" for the lock file name
*/
+ static const size_t max_path_len = sizeof(lk->filename) - 5;
+
+ if (strlen(path) >= max_path_len)
+ return -1;
+ strcpy(lk->filename, path);
if (!(flags & LOCK_NODEREF))
- resolve_symlink(lk->filename, sizeof(lk->filename)-5);
+ resolve_symlink(lk->filename, max_path_len);
strcat(lk->filename, ".lock");
lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
if (0 <= lk->fd) {
diff --git a/log-tree.c b/log-tree.c
index 1946e9c..a49d8e8 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -10,6 +10,7 @@
#include "color.h"
#include "gpg-interface.h"
#include "sequencer.h"
+#include "line-log.h"
struct decoration name_decoration = { "object names" };
@@ -616,6 +617,9 @@ void show_log(struct rev_info *opt)
ctx.fmt = opt->commit_format;
ctx.mailmap = opt->mailmap;
ctx.color = opt->diffopt.use_color;
+ ctx.output_encoding = get_log_output_encoding();
+ if (opt->from_ident.mail_begin && opt->from_ident.name_begin)
+ ctx.from_ident = &opt->from_ident;
pretty_print_commit(&ctx, commit, &msgbuf);
if (opt->add_signoff)
@@ -796,6 +800,9 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
log.parent = NULL;
opt->loginfo = &log;
+ if (opt->line_level_traverse)
+ return line_log_print(opt, commit);
+
shown = log_tree_diff(opt, commit, &log);
if (!shown && opt->loginfo && opt->always_show_header) {
log.parent = NULL;
diff --git a/mailmap.c b/mailmap.c
index 2a7b366..44614fc 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -5,8 +5,10 @@
#define DEBUG_MAILMAP 0
#if DEBUG_MAILMAP
#define debug_mm(...) fprintf(stderr, __VA_ARGS__)
+#define debug_str(X) ((X) ? (X) : "(none)")
#else
static inline void debug_mm(const char *format, ...) {}
+static inline const char *debug_str(const char *s) { return s; }
#endif
const char *git_mailmap_file;
@@ -29,7 +31,8 @@ struct mailmap_entry {
static void free_mailmap_info(void *p, const char *s)
{
struct mailmap_info *mi = (struct mailmap_info *)p;
- debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", s, mi->name, mi->email);
+ debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n",
+ s, debug_str(mi->name), debug_str(mi->email));
free(mi->name);
free(mi->email);
}
@@ -37,8 +40,11 @@ static void free_mailmap_info(void *p, const char *s)
static void free_mailmap_entry(void *p, const char *s)
{
struct mailmap_entry *me = (struct mailmap_entry *)p;
- debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", s, me->namemap.nr);
- debug_mm("mailmap: - simple: '%s' <%s>\n", me->name, me->email);
+ debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n",
+ s, me->namemap.nr);
+ debug_mm("mailmap: - simple: '%s' <%s>\n",
+ debug_str(me->name), debug_str(me->email));
+
free(me->name);
free(me->email);
@@ -47,18 +53,11 @@ static void free_mailmap_entry(void *p, const char *s)
}
static void add_mapping(struct string_list *map,
- char *new_name, char *new_email, char *old_name, char *old_email)
+ char *new_name, char *new_email,
+ char *old_name, char *old_email)
{
struct mailmap_entry *me;
int index;
- char *p;
-
- if (old_email)
- for (p = old_email; *p; p++)
- *p = tolower(*p);
- if (new_email)
- for (p = new_email; *p; p++)
- *p = tolower(*p);
if (old_email == NULL) {
old_email = new_email;
@@ -68,16 +67,21 @@ static void add_mapping(struct string_list *map,
if ((index = string_list_find_insert_index(map, old_email, 1)) < 0) {
/* mailmap entry exists, invert index value */
index = -1 - index;
+ me = (struct mailmap_entry *)map->items[index].util;
} else {
/* create mailmap entry */
- struct string_list_item *item = string_list_insert_at_index(map, index, old_email);
- item->util = xcalloc(1, sizeof(struct mailmap_entry));
- ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1;
+ struct string_list_item *item;
+
+ item = string_list_insert_at_index(map, index, old_email);
+ me = xcalloc(1, sizeof(struct mailmap_entry));
+ me->namemap.strdup_strings = 1;
+ me->namemap.cmp = strcasecmp;
+ item->util = me;
}
- me = (struct mailmap_entry *)map->items[index].util;
if (old_name == NULL) {
- debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index);
+ debug_mm("mailmap: adding (simple) entry for %s at index %d\n",
+ old_email, index);
/* Replace current name and new email for simple entry */
if (new_name) {
free(me->name);
@@ -89,7 +93,8 @@ static void add_mapping(struct string_list *map,
}
} else {
struct mailmap_info *mi = xcalloc(1, sizeof(struct mailmap_info));
- debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index);
+ debug_mm("mailmap: adding (complex) entry for %s at index %d\n",
+ old_email, index);
if (new_name)
mi->name = xstrdup(new_name);
if (new_email)
@@ -98,11 +103,12 @@ static void add_mapping(struct string_list *map,
}
debug_mm("mailmap: '%s' <%s> -> '%s' <%s>\n",
- old_name, old_email, new_name, new_email);
+ debug_str(old_name), old_email,
+ debug_str(new_name), debug_str(new_email));
}
static char *parse_name_and_email(char *buffer, char **name,
- char **email, int allow_empty_email)
+ char **email, int allow_empty_email)
{
char *left, *right, *nstart, *nend;
*name = *email = NULL;
@@ -122,7 +128,7 @@ static char *parse_name_and_email(char *buffer, char **name,
while (nend > nstart && isspace(*nend))
--nend;
- *name = (nstart < nend ? nstart : NULL);
+ *name = (nstart <= nend ? nstart : NULL);
*email = left+1;
*(nend+1) = '\0';
*right++ = '\0';
@@ -306,21 +312,25 @@ static struct string_list_item *lookup_prefix(struct string_list *map,
}
int map_user(struct string_list *map,
- const char **email, size_t *emaillen,
- const char **name, size_t *namelen)
+ const char **email, size_t *emaillen,
+ const char **name, size_t *namelen)
{
struct string_list_item *item;
struct mailmap_entry *me;
debug_mm("map_user: map '%.*s' <%.*s>\n",
- *name, *namelen, *emaillen, *email);
+ (int)*namelen, debug_str(*name),
+ (int)*emaillen, debug_str(*email));
item = lookup_prefix(map, *email, *emaillen);
if (item != NULL) {
me = (struct mailmap_entry *)item->util;
if (me->namemap.nr) {
- /* The item has multiple items, so we'll look up on name too */
- /* If the name is not found, we choose the simple entry */
+ /*
+ * The item has multiple items, so we'll look up on
+ * name too. If the name is not found, we choose the
+ * simple entry.
+ */
struct string_list_item *subitem;
subitem = lookup_prefix(&me->namemap, *name, *namelen);
if (subitem)
@@ -341,8 +351,9 @@ int map_user(struct string_list *map,
*name = mi->name;
*namelen = strlen(*name);
}
- debug_mm("map_user: to '%.*s' <.*%s>\n", *namelen, *name,
- *emaillen, *email);
+ debug_mm("map_user: to '%.*s' <%.*s>\n",
+ (int)*namelen, debug_str(*name),
+ (int)*emaillen, debug_str(*email));
return 1;
}
debug_mm("map_user: --\n");
diff --git a/match-trees.c b/match-trees.c
index 2bb734d..7873cde 100644
--- a/match-trees.c
+++ b/match-trees.c
@@ -47,6 +47,22 @@ static int score_matches(unsigned mode1, unsigned mode2, const char *path)
return score;
}
+static void *fill_tree_desc_strict(struct tree_desc *desc,
+ const unsigned char *hash)
+{
+ void *buffer;
+ enum object_type type;
+ unsigned long size;
+
+ buffer = read_sha1_file(hash, &type, &size);
+ if (!buffer)
+ die("unable to read tree (%s)", sha1_to_hex(hash));
+ if (type != OBJ_TREE)
+ die("%s is not a tree", sha1_to_hex(hash));
+ init_tree_desc(desc, buffer, size);
+ return buffer;
+}
+
static int base_name_entries_compare(const struct name_entry *a,
const struct name_entry *b)
{
@@ -61,23 +77,10 @@ static int score_trees(const unsigned char *hash1, const unsigned char *hash2)
{
struct tree_desc one;
struct tree_desc two;
- void *one_buf, *two_buf;
+ void *one_buf = fill_tree_desc_strict(&one, hash1);
+ void *two_buf = fill_tree_desc_strict(&two, hash2);
int score = 0;
- enum object_type type;
- unsigned long size;
- one_buf = read_sha1_file(hash1, &type, &size);
- if (!one_buf)
- die("unable to read tree (%s)", sha1_to_hex(hash1));
- if (type != OBJ_TREE)
- die("%s is not a tree", sha1_to_hex(hash1));
- init_tree_desc(&one, one_buf, size);
- two_buf = read_sha1_file(hash2, &type, &size);
- if (!two_buf)
- die("unable to read tree (%s)", sha1_to_hex(hash2));
- if (type != OBJ_TREE)
- die("%s is not a tree", sha1_to_hex(hash2));
- init_tree_desc(&two, two_buf, size);
for (;;) {
struct name_entry e1, e2;
int got_entry_from_one = tree_entry(&one, &e1);
@@ -124,16 +127,7 @@ static void match_trees(const unsigned char *hash1,
int recurse_limit)
{
struct tree_desc one;
- void *one_buf;
- enum object_type type;
- unsigned long size;
-
- one_buf = read_sha1_file(hash1, &type, &size);
- if (!one_buf)
- die("unable to read tree (%s)", sha1_to_hex(hash1));
- if (type != OBJ_TREE)
- die("%s is not a tree", sha1_to_hex(hash1));
- init_tree_desc(&one, one_buf, size);
+ void *one_buf = fill_tree_desc_strict(&one, hash1);
while (one.size) {
const char *path;
diff --git a/notes-merge.c b/notes-merge.c
index 0f67bd3..ab18857 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -9,6 +9,7 @@
#include "notes.h"
#include "notes-merge.h"
#include "strbuf.h"
+#include "notes-utils.h"
struct notes_merge_pair {
unsigned char obj[20], base[20], local[20], remote[20];
@@ -530,32 +531,6 @@ static int merge_from_diffs(struct notes_merge_options *o,
return conflicts ? -1 : 1;
}
-void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
- const struct strbuf *msg, unsigned char *result_sha1)
-{
- unsigned char tree_sha1[20];
-
- assert(t->initialized);
-
- if (write_notes_tree(t, tree_sha1))
- die("Failed to write notes tree to database");
-
- if (!parents) {
- /* Deduce parent commit from t->ref */
- unsigned char parent_sha1[20];
- if (!read_ref(t->ref, parent_sha1)) {
- struct commit *parent = lookup_commit(parent_sha1);
- if (!parent || parse_commit(parent))
- die("Failed to find/parse commit %s", t->ref);
- commit_list_insert(parent, &parents);
- }
- /* else: t->ref points to nothing, assume root/orphan commit */
- }
-
- if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
- die("Failed to commit notes tree to database");
-}
-
int notes_merge(struct notes_merge_options *o,
struct notes_tree *local_tree,
unsigned char *result_sha1)
diff --git a/notes-merge.h b/notes-merge.h
index 0c11b17..1d01f6a 100644
--- a/notes-merge.h
+++ b/notes-merge.h
@@ -26,20 +26,6 @@ struct notes_merge_options {
void init_notes_merge_options(struct notes_merge_options *o);
/*
- * Create new notes commit from the given notes tree
- *
- * Properties of the created commit:
- * - tree: the result of converting t to a tree object with write_notes_tree().
- * - parents: the given parents OR (if NULL) the commit referenced by t->ref.
- * - author/committer: the default determined by commmit_tree().
- * - commit message: msg
- *
- * The resulting commit SHA1 is stored in result_sha1.
- */
-void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
- const struct strbuf *msg, unsigned char *result_sha1);
-
-/*
* Merge notes from o->remote_ref into o->local_ref
*
* The given notes_tree 'local_tree' must be the notes_tree referenced by the
diff --git a/notes-utils.c b/notes-utils.c
new file mode 100644
index 0000000..9107c37
--- /dev/null
+++ b/notes-utils.c
@@ -0,0 +1,157 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "notes-utils.h"
+
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+ const struct strbuf *msg, unsigned char *result_sha1)
+{
+ unsigned char tree_sha1[20];
+
+ assert(t->initialized);
+
+ if (write_notes_tree(t, tree_sha1))
+ die("Failed to write notes tree to database");
+
+ if (!parents) {
+ /* Deduce parent commit from t->ref */
+ unsigned char parent_sha1[20];
+ if (!read_ref(t->ref, parent_sha1)) {
+ struct commit *parent = lookup_commit(parent_sha1);
+ if (!parent || parse_commit(parent))
+ die("Failed to find/parse commit %s", t->ref);
+ commit_list_insert(parent, &parents);
+ }
+ /* else: t->ref points to nothing, assume root/orphan commit */
+ }
+
+ if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
+ die("Failed to commit notes tree to database");
+}
+
+void commit_notes(struct notes_tree *t, const char *msg)
+{
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char commit_sha1[20];
+
+ if (!t)
+ t = &default_notes_tree;
+ if (!t->initialized || !t->ref || !*t->ref)
+ die(_("Cannot commit uninitialized/unreferenced notes tree"));
+ if (!t->dirty)
+ return; /* don't have to commit an unchanged tree */
+
+ /* Prepare commit message and reflog message */
+ 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, 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);
+}
+
+static combine_notes_fn parse_combine_notes_fn(const char *v)
+{
+ if (!strcasecmp(v, "overwrite"))
+ return combine_notes_overwrite;
+ else if (!strcasecmp(v, "ignore"))
+ return combine_notes_ignore;
+ else if (!strcasecmp(v, "concatenate"))
+ return combine_notes_concatenate;
+ else if (!strcasecmp(v, "cat_sort_uniq"))
+ return combine_notes_cat_sort_uniq;
+ else
+ return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+ struct notes_rewrite_cfg *c = cb;
+ if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+ c->enabled = git_config_bool(k, v);
+ return 0;
+ } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
+ if (!v)
+ config_error_nonbool(k);
+ c->combine = parse_combine_notes_fn(v);
+ if (!c->combine) {
+ error(_("Bad notes.rewriteMode value: '%s'"), v);
+ return 1;
+ }
+ return 0;
+ } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
+ /* note that a refs/ prefix is implied in the
+ * underlying for_each_glob_ref */
+ if (!prefixcmp(v, "refs/notes/"))
+ string_list_add_refs_by_glob(c->refs, v);
+ else
+ warning(_("Refusing to rewrite notes in %s"
+ " (outside of refs/notes/)"), v);
+ return 0;
+ }
+
+ return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+ struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+ const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
+ const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
+ c->cmd = cmd;
+ c->enabled = 1;
+ c->combine = combine_notes_concatenate;
+ c->refs = xcalloc(1, sizeof(struct string_list));
+ c->refs->strdup_strings = 1;
+ c->refs_from_env = 0;
+ c->mode_from_env = 0;
+ if (rewrite_mode_env) {
+ c->mode_from_env = 1;
+ c->combine = parse_combine_notes_fn(rewrite_mode_env);
+ if (!c->combine)
+ /* TRANSLATORS: The first %s is the name of the
+ environment variable, the second %s is its value */
+ error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT,
+ rewrite_mode_env);
+ }
+ if (rewrite_refs_env) {
+ c->refs_from_env = 1;
+ string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
+ }
+ git_config(notes_rewrite_config, c);
+ if (!c->enabled || !c->refs->nr) {
+ string_list_clear(c->refs, 0);
+ free(c->refs);
+ free(c);
+ return NULL;
+ }
+ c->trees = load_notes_trees(c->refs);
+ string_list_clear(c->refs, 0);
+ free(c->refs);
+ return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+ const unsigned char *from_obj, const unsigned char *to_obj)
+{
+ int ret = 0;
+ int i;
+ for (i = 0; c->trees[i]; i++)
+ ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
+ return ret;
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg)
+{
+ int i;
+ for (i = 0; c->trees[i]; i++) {
+ commit_notes(c->trees[i], msg);
+ free_notes(c->trees[i]);
+ }
+ free(c->trees);
+ free(c);
+}
diff --git a/notes-utils.h b/notes-utils.h
new file mode 100644
index 0000000..b4cb1bf
--- /dev/null
+++ b/notes-utils.h
@@ -0,0 +1,37 @@
+#ifndef NOTES_UTILS_H
+#define NOTES_UTILS_H
+
+#include "notes.h"
+
+/*
+ * Create new notes commit from the given notes tree
+ *
+ * Properties of the created commit:
+ * - tree: the result of converting t to a tree object with write_notes_tree().
+ * - parents: the given parents OR (if NULL) the commit referenced by t->ref.
+ * - author/committer: the default determined by commmit_tree().
+ * - commit message: msg
+ *
+ * The resulting commit SHA1 is stored in result_sha1.
+ */
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+ const struct strbuf *msg, unsigned char *result_sha1);
+
+void commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg {
+ struct notes_tree **trees;
+ const char *cmd;
+ int enabled;
+ combine_notes_fn combine;
+ struct string_list *refs;
+ int refs_from_env;
+ int mode_from_env;
+};
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+ const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg);
+
+#endif
diff --git a/notes.c b/notes.c
index f63fd57..b69c0b8 100644
--- a/notes.c
+++ b/notes.c
@@ -918,17 +918,21 @@ out:
return ret;
}
-static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+static int string_list_add_one_ref(const char *refname, const unsigned char *sha1,
int flag, void *cb)
{
struct string_list *refs = cb;
- if (!unsorted_string_list_has_string(refs, path))
- string_list_append(refs, path);
+ if (!unsorted_string_list_has_string(refs, refname))
+ string_list_append(refs, refname);
return 0;
}
+/*
+ * The list argument must have strdup_strings set on it.
+ */
void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
{
+ assert(list->strdup_strings);
if (has_glob_specials(glob)) {
for_each_glob_ref(string_list_add_one_ref, glob, list);
} else {
diff --git a/object.c b/object.c
index 20703f5..cbc7333 100644
--- a/object.c
+++ b/object.c
@@ -71,13 +71,13 @@ static unsigned int hashtable_index(const unsigned char *sha1)
struct object *lookup_object(const unsigned char *sha1)
{
- unsigned int i;
+ unsigned int i, first;
struct object *obj;
if (!obj_hash)
return NULL;
- i = hashtable_index(sha1);
+ first = i = hashtable_index(sha1);
while ((obj = obj_hash[i]) != NULL) {
if (!hashcmp(sha1, obj->sha1))
break;
@@ -85,6 +85,16 @@ struct object *lookup_object(const unsigned char *sha1)
if (i == obj_hash_size)
i = 0;
}
+ if (obj && i != first) {
+ /*
+ * Move object to where we started to look for it so
+ * that we do not need to walk the hash table the next
+ * time we look for it.
+ */
+ struct object *tmp = obj_hash[i];
+ obj_hash[i] = obj_hash[first];
+ obj_hash[first] = tmp;
+ }
return obj;
}
@@ -260,11 +270,18 @@ void add_object_array(struct object *obj, const char *name, struct object_array
add_object_array_with_mode(obj, name, array, S_IFINVALID);
}
+/*
+ * A zero-length string to which object_array_entry::name can be
+ * initialized without requiring a malloc/free.
+ */
+static char object_array_slopbuf[1];
+
void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode)
{
unsigned nr = array->nr;
unsigned alloc = array->alloc;
struct object_array_entry *objects = array->objects;
+ struct object_array_entry *entry;
if (nr >= alloc) {
alloc = (alloc + 32) * 2;
@@ -272,28 +289,67 @@ void add_object_array_with_mode(struct object *obj, const char *name, struct obj
array->alloc = alloc;
array->objects = objects;
}
- objects[nr].item = obj;
- objects[nr].name = name;
- objects[nr].mode = mode;
+ entry = &objects[nr];
+ entry->item = obj;
+ if (!name)
+ entry->name = NULL;
+ else if (!*name)
+ /* Use our own empty string instead of allocating one: */
+ entry->name = object_array_slopbuf;
+ else
+ entry->name = xstrdup(name);
+ entry->mode = mode;
array->nr = ++nr;
}
-void object_array_remove_duplicates(struct object_array *array)
+void object_array_filter(struct object_array *array,
+ object_array_each_func_t want, void *cb_data)
{
- unsigned int ref, src, dst;
+ unsigned nr = array->nr, src, dst;
struct object_array_entry *objects = array->objects;
- for (ref = 0; ref + 1 < array->nr; ref++) {
- for (src = ref + 1, dst = src;
- src < array->nr;
- src++) {
- if (!strcmp(objects[ref].name, objects[src].name))
- continue;
+ for (src = dst = 0; src < nr; src++) {
+ if (want(&objects[src], cb_data)) {
if (src != dst)
objects[dst] = objects[src];
dst++;
+ } else {
+ if (objects[src].name != object_array_slopbuf)
+ free(objects[src].name);
+ }
+ }
+ array->nr = dst;
+}
+
+/*
+ * Return true iff array already contains an entry with name.
+ */
+static int contains_name(struct object_array *array, const char *name)
+{
+ unsigned nr = array->nr, i;
+ struct object_array_entry *object = array->objects;
+
+ for (i = 0; i < nr; i++, object++)
+ if (!strcmp(object->name, name))
+ return 1;
+ return 0;
+}
+
+void object_array_remove_duplicates(struct object_array *array)
+{
+ unsigned nr = array->nr, src;
+ struct object_array_entry *objects = array->objects;
+
+ array->nr = 0;
+ for (src = 0; src < nr; src++) {
+ if (!contains_name(array, objects[src].name)) {
+ if (src != array->nr)
+ objects[array->nr] = objects[src];
+ array->nr++;
+ } else {
+ if (objects[src].name != object_array_slopbuf)
+ free(objects[src].name);
}
- array->nr = dst;
}
}
diff --git a/object.h b/object.h
index 97d384b..2ff68c5 100644
--- a/object.h
+++ b/object.h
@@ -11,7 +11,13 @@ struct object_array {
unsigned int alloc;
struct object_array_entry {
struct object *item;
- const char *name;
+ /*
+ * name or NULL. If non-NULL, the memory pointed to
+ * is owned by this object *except* if it points at
+ * object_array_slopbuf, which is a static copy of the
+ * empty string.
+ */
+ char *name;
unsigned mode;
} *objects;
};
@@ -85,7 +91,22 @@ int object_list_contains(struct object_list *list, struct object *obj);
/* Object array handling .. */
void add_object_array(struct object *obj, const char *name, struct object_array *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 *);
+
+typedef int (*object_array_each_func_t)(struct object_array_entry *, void *);
+
+/*
+ * Apply want to each entry in array, retaining only the entries for
+ * which the function returns true. Preserve the order of the entries
+ * that are retained.
+ */
+void object_array_filter(struct object_array *array,
+ object_array_each_func_t want, void *cb_data);
+
+/*
+ * Remove from array all but the first entry with a given name.
+ * Warning: this function uses an O(N^2) algorithm.
+ */
+void object_array_remove_duplicates(struct object_array *array);
void clear_object_flags(unsigned flags);
diff --git a/pack-refs.c b/pack-refs.c
deleted file mode 100644
index 4461f71..0000000
--- a/pack-refs.c
+++ /dev/null
@@ -1,148 +0,0 @@
-#include "cache.h"
-#include "refs.h"
-#include "tag.h"
-#include "pack-refs.h"
-
-struct ref_to_prune {
- struct ref_to_prune *next;
- unsigned char sha1[20];
- char name[FLEX_ARRAY];
-};
-
-struct pack_refs_cb_data {
- unsigned int flags;
- struct ref_to_prune *ref_to_prune;
- FILE *refs_file;
-};
-
-static int do_not_prune(int flags)
-{
- /* If it is already packed or if it is a symref,
- * do not prune it.
- */
- return (flags & (REF_ISSYMREF|REF_ISPACKED));
-}
-
-static int handle_one_ref(const char *path, const unsigned char *sha1,
- int flags, void *cb_data)
-{
- struct pack_refs_cb_data *cb = cb_data;
- struct object *o;
- int is_tag_ref;
-
- /* Do not pack the symbolic refs */
- if ((flags & REF_ISSYMREF))
- return 0;
- is_tag_ref = !prefixcmp(path, "refs/tags/");
-
- /* ALWAYS pack refs that were already packed or are tags */
- if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref && !(flags & REF_ISPACKED))
- return 0;
-
- fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
-
- o = parse_object_or_die(sha1, path);
- if (o->type == OBJ_TAG) {
- o = deref_tag(o, path, 0);
- if (o)
- fprintf(cb->refs_file, "^%s\n",
- sha1_to_hex(o->sha1));
- }
-
- if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
- int namelen = strlen(path) + 1;
- struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
- hashcpy(n->sha1, sha1);
- strcpy(n->name, path);
- n->next = cb->ref_to_prune;
- cb->ref_to_prune = n;
- }
- return 0;
-}
-
-/*
- * Remove empty parents, but spare refs/ and immediate subdirs.
- * Note: munges *name.
- */
-static void try_remove_empty_parents(char *name)
-{
- char *p, *q;
- int i;
- p = name;
- for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
- while (*p && *p != '/')
- p++;
- /* tolerate duplicate slashes; see check_refname_format() */
- while (*p == '/')
- p++;
- }
- for (q = p; *q; q++)
- ;
- while (1) {
- while (q > p && *q != '/')
- q--;
- while (q > p && *(q-1) == '/')
- q--;
- if (q == p)
- break;
- *q = '\0';
- if (rmdir(git_path("%s", name)))
- break;
- }
-}
-
-/* make sure nobody touched the ref, and unlink */
-static void prune_ref(struct ref_to_prune *r)
-{
- struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
-
- if (lock) {
- unlink_or_warn(git_path("%s", r->name));
- unlock_ref(lock);
- try_remove_empty_parents(r->name);
- }
-}
-
-static void prune_refs(struct ref_to_prune *r)
-{
- while (r) {
- prune_ref(r);
- r = r->next;
- }
-}
-
-static struct lock_file packed;
-
-int pack_refs(unsigned int flags)
-{
- int fd;
- struct pack_refs_cb_data cbdata;
-
- memset(&cbdata, 0, sizeof(cbdata));
- cbdata.flags = flags;
-
- fd = hold_lock_file_for_update(&packed, git_path("packed-refs"),
- LOCK_DIE_ON_ERROR);
- cbdata.refs_file = fdopen(fd, "w");
- if (!cbdata.refs_file)
- die_errno("unable to create ref-pack file structure");
-
- /* perhaps other traits later as well */
- fprintf(cbdata.refs_file, "# pack-refs with: peeled fully-peeled \n");
-
- for_each_ref(handle_one_ref, &cbdata);
- if (ferror(cbdata.refs_file))
- die("failed to write ref-pack file");
- if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
- die_errno("failed to write ref-pack file");
- /*
- * Since the lock file was fdopen()'ed and then fclose()'ed above,
- * assign -1 to the lock file descriptor so that commit_lock_file()
- * won't try to close() it.
- */
- packed.fd = -1;
- if (commit_lock_file(&packed) < 0)
- die_errno("unable to overwrite old ref-pack file");
- prune_refs(cbdata.ref_to_prune);
- return 0;
-}
diff --git a/pack-refs.h b/pack-refs.h
deleted file mode 100644
index 518acfb..0000000
--- a/pack-refs.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef PACK_REFS_H
-#define PACK_REFS_H
-
-/*
- * Flags for controlling behaviour of pack_refs()
- * PACK_REFS_PRUNE: Prune loose refs after packing
- * PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs
- */
-#define PACK_REFS_PRUNE 0x0001
-#define PACK_REFS_ALL 0x0002
-
-/*
- * Write a packed-refs file for the current repository.
- * flags: Combination of the above PACK_REFS_* flags.
- */
-int pack_refs(unsigned int flags);
-
-#endif /* PACK_REFS_H */
diff --git a/pack-revindex.c b/pack-revindex.c
index 77a0465..b4d2b35 100644
--- a/pack-revindex.c
+++ b/pack-revindex.c
@@ -59,11 +59,101 @@ static void init_pack_revindex(void)
/* revindex elements are lazily initialized */
}
-static int cmp_offset(const void *a_, const void *b_)
+/*
+ * This is a least-significant-digit radix sort.
+ *
+ * It sorts each of the "n" items in "entries" by its offset field. The "max"
+ * parameter must be at least as large as the largest offset in the array,
+ * and lets us quit the sort early.
+ */
+static void sort_revindex(struct revindex_entry *entries, unsigned n, off_t max)
{
- const struct revindex_entry *a = a_;
- const struct revindex_entry *b = b_;
- return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0;
+ /*
+ * We use a "digit" size of 16 bits. That keeps our memory
+ * usage reasonable, and we can generally (for a 4G or smaller
+ * packfile) quit after two rounds of radix-sorting.
+ */
+#define DIGIT_SIZE (16)
+#define BUCKETS (1 << DIGIT_SIZE)
+ /*
+ * We want to know the bucket that a[i] will go into when we are using
+ * the digit that is N bits from the (least significant) end.
+ */
+#define BUCKET_FOR(a, i, bits) (((a)[(i)].offset >> (bits)) & (BUCKETS-1))
+
+ /*
+ * We need O(n) temporary storage. Rather than do an extra copy of the
+ * partial results into "entries", we sort back and forth between the
+ * real array and temporary storage. In each iteration of the loop, we
+ * keep track of them with alias pointers, always sorting from "from"
+ * to "to".
+ */
+ struct revindex_entry *tmp = xmalloc(n * sizeof(*tmp));
+ struct revindex_entry *from = entries, *to = tmp;
+ int bits;
+ unsigned *pos = xmalloc(BUCKETS * sizeof(*pos));
+
+ /*
+ * If (max >> bits) is zero, then we know that the radix digit we are
+ * on (and any higher) will be zero for all entries, and our loop will
+ * be a no-op, as everybody lands in the same zero-th bucket.
+ */
+ for (bits = 0; max >> bits; bits += DIGIT_SIZE) {
+ struct revindex_entry *swap;
+ unsigned i;
+
+ memset(pos, 0, BUCKETS * sizeof(*pos));
+
+ /*
+ * We want pos[i] to store the index of the last element that
+ * will go in bucket "i" (actually one past the last element).
+ * To do this, we first count the items that will go in each
+ * bucket, which gives us a relative offset from the last
+ * bucket. We can then cumulatively add the index from the
+ * previous bucket to get the true index.
+ */
+ for (i = 0; i < n; i++)
+ pos[BUCKET_FOR(from, i, bits)]++;
+ for (i = 1; i < BUCKETS; i++)
+ pos[i] += pos[i-1];
+
+ /*
+ * Now we can drop the elements into their correct buckets (in
+ * our temporary array). We iterate the pos counter backwards
+ * to avoid using an extra index to count up. And since we are
+ * going backwards there, we must also go backwards through the
+ * array itself, to keep the sort stable.
+ *
+ * Note that we use an unsigned iterator to make sure we can
+ * handle 2^32-1 objects, even on a 32-bit system. But this
+ * means we cannot use the more obvious "i >= 0" loop condition
+ * for counting backwards, and must instead check for
+ * wrap-around with UINT_MAX.
+ */
+ for (i = n - 1; i != UINT_MAX; i--)
+ to[--pos[BUCKET_FOR(from, i, bits)]] = from[i];
+
+ /*
+ * Now "to" contains the most sorted list, so we swap "from" and
+ * "to" for the next iteration.
+ */
+ swap = from;
+ from = to;
+ to = swap;
+ }
+
+ /*
+ * If we ended with our data in the original array, great. If not,
+ * we have to move it back from the temporary storage.
+ */
+ if (from != entries)
+ memcpy(entries, tmp, n * sizeof(*entries));
+ free(tmp);
+ free(pos);
+
+#undef BUCKET_FOR
+#undef BUCKETS
+#undef DIGIT_SIZE
}
/*
@@ -72,8 +162,8 @@ static int cmp_offset(const void *a_, const void *b_)
static void create_pack_revindex(struct pack_revindex *rix)
{
struct packed_git *p = rix->p;
- int num_ent = p->num_objects;
- int i;
+ unsigned num_ent = p->num_objects;
+ unsigned i;
const char *index = p->index_data;
rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
@@ -108,13 +198,13 @@ static void create_pack_revindex(struct pack_revindex *rix)
*/
rix->revindex[num_ent].offset = p->pack_size - 20;
rix->revindex[num_ent].nr = -1;
- qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset);
+ sort_revindex(rix->revindex, num_ent, p->pack_size);
}
struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs)
{
int num;
- int lo, hi;
+ unsigned lo, hi;
struct pack_revindex *rix;
struct revindex_entry *revindex;
@@ -132,7 +222,7 @@ struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs)
lo = 0;
hi = p->num_objects + 1;
do {
- int mi = (lo + hi) / 2;
+ unsigned mi = lo + (hi - lo) / 2;
if (revindex[mi].offset == ofs) {
return revindex + mi;
} else if (ofs < revindex[mi].offset)
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 0de5fb1..be8c413 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -33,6 +33,12 @@ int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
return 0;
}
+int parse_opt_expiry_date_cb(const struct option *opt, const char *arg,
+ int unset)
+{
+ return parse_expiry_date(arg, (unsigned long *)opt->value);
+}
+
int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
int unset)
{
diff --git a/parse-options.h b/parse-options.h
index 1c8bd8d..c378b75 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -140,6 +140,9 @@ struct option {
#define OPT_DATE(s, l, v, h) \
{ OPTION_CALLBACK, (s), (l), (v), N_("time"),(h), 0, \
parse_opt_approxidate_cb }
+#define OPT_EXPIRY_DATE(s, l, v, h) \
+ { OPTION_CALLBACK, (s), (l), (v), N_("expiry date"),(h), 0, \
+ parse_opt_expiry_date_cb }
#define OPT_CALLBACK(s, l, v, a, h, f) \
{ OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
#define OPT_NUMBER_CALLBACK(v, h, f) \
@@ -219,6 +222,7 @@ extern int parse_options_concat(struct option *dst, size_t, struct option *src);
/*----- some often used options -----*/
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
+extern int parse_opt_expiry_date_cb(const struct option *, const char *, int);
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);
diff --git a/pretty.c b/pretty.c
index 9e43154..74563c9 100644
--- a/pretty.c
+++ b/pretty.c
@@ -406,7 +406,7 @@ static const char *show_ident_date(const struct ident_split *ident,
return show_date(date, tz, mode);
}
-void pp_user_info(const struct pretty_print_context *pp,
+void pp_user_info(struct pretty_print_context *pp,
const char *what, struct strbuf *sb,
const char *line, const char *encoding)
{
@@ -432,6 +432,23 @@ void pp_user_info(const struct pretty_print_context *pp,
map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
if (pp->fmt == CMIT_FMT_EMAIL) {
+ if (pp->from_ident) {
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addstr(&buf, "From: ");
+ strbuf_add(&buf, namebuf, namelen);
+ strbuf_addstr(&buf, " <");
+ strbuf_add(&buf, mailbuf, maillen);
+ strbuf_addstr(&buf, ">\n");
+ string_list_append(&pp->in_body_headers,
+ strbuf_detach(&buf, NULL));
+
+ mailbuf = pp->from_ident->mail_begin;
+ maillen = pp->from_ident->mail_end - mailbuf;
+ namebuf = pp->from_ident->name_begin;
+ namelen = pp->from_ident->name_end - namebuf;
+ }
+
strbuf_addstr(sb, "From: ");
if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) {
add_rfc2047(sb, namebuf, namelen,
@@ -1514,7 +1531,7 @@ void format_commit_message(const struct commit *commit,
free(context.signature_check.signer);
}
-static void pp_header(const struct pretty_print_context *pp,
+static void pp_header(struct pretty_print_context *pp,
const char *encoding,
const struct commit *commit,
const char **msg_p,
@@ -1575,7 +1592,7 @@ static void pp_header(const struct pretty_print_context *pp,
}
}
-void pp_title_line(const struct pretty_print_context *pp,
+void pp_title_line(struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
const char *encoding,
@@ -1602,6 +1619,16 @@ void pp_title_line(const struct pretty_print_context *pp,
}
strbuf_addch(sb, '\n');
+ if (need_8bit_cte == 0) {
+ int i;
+ for (i = 0; i < pp->in_body_headers.nr; i++) {
+ if (has_non_ascii(pp->in_body_headers.items[i].string)) {
+ need_8bit_cte = 1;
+ break;
+ }
+ }
+ }
+
if (need_8bit_cte > 0) {
const char *header_fmt =
"MIME-Version: 1.0\n"
@@ -1615,10 +1642,21 @@ void pp_title_line(const struct pretty_print_context *pp,
if (pp->fmt == CMIT_FMT_EMAIL) {
strbuf_addch(sb, '\n');
}
+
+ if (pp->in_body_headers.nr) {
+ int i;
+ for (i = 0; i < pp->in_body_headers.nr; i++) {
+ strbuf_addstr(sb, pp->in_body_headers.items[i].string);
+ free(pp->in_body_headers.items[i].string);
+ }
+ string_list_clear(&pp->in_body_headers, 0);
+ strbuf_addch(sb, '\n');
+ }
+
strbuf_release(&title);
}
-void pp_remainder(const struct pretty_print_context *pp,
+void pp_remainder(struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
int indent)
@@ -1650,7 +1688,7 @@ void pp_remainder(const struct pretty_print_context *pp,
}
}
-void pretty_print_commit(const struct pretty_print_context *pp,
+void pretty_print_commit(struct pretty_print_context *pp,
const struct commit *commit,
struct strbuf *sb)
{
diff --git a/prio-queue.c b/prio-queue.c
new file mode 100644
index 0000000..c9f8c6d
--- /dev/null
+++ b/prio-queue.c
@@ -0,0 +1,84 @@
+#include "cache.h"
+#include "commit.h"
+#include "prio-queue.h"
+
+void prio_queue_reverse(struct prio_queue *queue)
+{
+ int i, j;
+
+ if (queue->compare != NULL)
+ die("BUG: prio_queue_reverse() on non-LIFO queue");
+ for (i = 0; i <= (j = (queue->nr - 1) - i); i++) {
+ struct commit *swap = queue->array[i];
+ queue->array[i] = queue->array[j];
+ queue->array[j] = swap;
+ }
+}
+
+void clear_prio_queue(struct prio_queue *queue)
+{
+ free(queue->array);
+ queue->nr = 0;
+ queue->alloc = 0;
+ queue->array = NULL;
+}
+
+void prio_queue_put(struct prio_queue *queue, void *thing)
+{
+ prio_queue_compare_fn compare = queue->compare;
+ int ix, parent;
+
+ /* Append at the end */
+ ALLOC_GROW(queue->array, queue->nr + 1, queue->alloc);
+ queue->array[queue->nr++] = thing;
+ if (!compare)
+ return; /* LIFO */
+
+ /* Bubble up the new one */
+ for (ix = queue->nr - 1; ix; ix = parent) {
+ parent = (ix - 1) / 2;
+ if (compare(queue->array[parent], queue->array[ix],
+ queue->cb_data) <= 0)
+ break;
+
+ thing = queue->array[parent];
+ queue->array[parent] = queue->array[ix];
+ queue->array[ix] = thing;
+ }
+}
+
+void *prio_queue_get(struct prio_queue *queue)
+{
+ void *result, *swap;
+ int ix, child;
+ prio_queue_compare_fn compare = queue->compare;
+
+ if (!queue->nr)
+ return NULL;
+ if (!compare)
+ return queue->array[--queue->nr]; /* LIFO */
+
+ result = queue->array[0];
+ if (!--queue->nr)
+ return result;
+
+ queue->array[0] = queue->array[queue->nr];
+
+ /* Push down the one at the root */
+ for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
+ child = ix * 2 + 1; /* left */
+ if ((child + 1 < queue->nr) &&
+ (compare(queue->array[child], queue->array[child + 1],
+ queue->cb_data) >= 0))
+ child++; /* use right child */
+
+ if (compare(queue->array[ix], queue->array[child],
+ queue->cb_data) <= 0)
+ break;
+
+ swap = queue->array[child];
+ queue->array[child] = queue->array[ix];
+ queue->array[ix] = swap;
+ }
+ return result;
+}
diff --git a/prio-queue.h b/prio-queue.h
new file mode 100644
index 0000000..9c3cd1f
--- /dev/null
+++ b/prio-queue.h
@@ -0,0 +1,48 @@
+#ifndef PRIO_QUEUE_H
+#define PRIO_QUEUE_H
+
+/*
+ * A priority queue implementation, primarily for keeping track of
+ * commits in the 'date-order' so that we process them from new to old
+ * as they are discovered, but can be used to hold any pointer to
+ * struct. The caller is responsible for supplying a function to
+ * compare two "things".
+ *
+ * Alternatively, this data structure can also be used as a LIFO stack
+ * by specifying NULL as the comparison function.
+ */
+
+/*
+ * Compare two "things", one and two; the third parameter is cb_data
+ * in the prio_queue structure. The result is returned as a sign of
+ * the return value, being the same as the sign of the result of
+ * subtracting "two" from "one" (i.e. negative if "one" sorts earlier
+ * than "two").
+ */
+typedef int (*prio_queue_compare_fn)(const void *one, const void *two, void *cb_data);
+
+struct prio_queue {
+ prio_queue_compare_fn compare;
+ void *cb_data;
+ int alloc, nr;
+ void **array;
+};
+
+/*
+ * Add the "thing" to the queue.
+ */
+extern void prio_queue_put(struct prio_queue *, void *thing);
+
+/*
+ * Extract the "thing" that compares the smallest out of the queue,
+ * or NULL. If compare function is NULL, the queue acts as a LIFO
+ * stack.
+ */
+extern void *prio_queue_get(struct prio_queue *);
+
+extern void clear_prio_queue(struct prio_queue *);
+
+/* Reverse the LIFO elements */
+extern void prio_queue_reverse(struct prio_queue *);
+
+#endif /* PRIO_QUEUE_H */
diff --git a/read-cache.c b/read-cache.c
index 04ed561..d5201f9 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -67,6 +67,61 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
}
+void fill_stat_data(struct stat_data *sd, struct stat *st)
+{
+ sd->sd_ctime.sec = (unsigned int)st->st_ctime;
+ sd->sd_mtime.sec = (unsigned int)st->st_mtime;
+ sd->sd_ctime.nsec = ST_CTIME_NSEC(*st);
+ sd->sd_mtime.nsec = ST_MTIME_NSEC(*st);
+ sd->sd_dev = st->st_dev;
+ sd->sd_ino = st->st_ino;
+ sd->sd_uid = st->st_uid;
+ sd->sd_gid = st->st_gid;
+ sd->sd_size = st->st_size;
+}
+
+int match_stat_data(const struct stat_data *sd, struct stat *st)
+{
+ int changed = 0;
+
+ if (sd->sd_mtime.sec != (unsigned int)st->st_mtime)
+ changed |= MTIME_CHANGED;
+ if (trust_ctime && check_stat &&
+ sd->sd_ctime.sec != (unsigned int)st->st_ctime)
+ changed |= CTIME_CHANGED;
+
+#ifdef USE_NSEC
+ if (check_stat && sd->sd_mtime.nsec != ST_MTIME_NSEC(*st))
+ changed |= MTIME_CHANGED;
+ if (trust_ctime && check_stat &&
+ sd->sd_ctime.nsec != ST_CTIME_NSEC(*st))
+ changed |= CTIME_CHANGED;
+#endif
+
+ if (check_stat) {
+ if (sd->sd_uid != (unsigned int) st->st_uid ||
+ sd->sd_gid != (unsigned int) st->st_gid)
+ changed |= OWNER_CHANGED;
+ if (sd->sd_ino != (unsigned int) st->st_ino)
+ changed |= INODE_CHANGED;
+ }
+
+#ifdef USE_STDEV
+ /*
+ * st_dev breaks on network filesystems where different
+ * clients will have different views of what "device"
+ * the filesystem is on
+ */
+ if (check_stat && sd->sd_dev != (unsigned int) st->st_dev)
+ changed |= INODE_CHANGED;
+#endif
+
+ if (sd->sd_size != (unsigned int) st->st_size)
+ changed |= DATA_CHANGED;
+
+ return changed;
+}
+
/*
* This only updates the "non-critical" parts of the directory
* cache, ie the parts that aren't tracked by GIT, and only used
@@ -74,15 +129,7 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
*/
void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
{
- ce->ce_ctime.sec = (unsigned int)st->st_ctime;
- ce->ce_mtime.sec = (unsigned int)st->st_mtime;
- ce->ce_ctime.nsec = ST_CTIME_NSEC(*st);
- ce->ce_mtime.nsec = ST_MTIME_NSEC(*st);
- ce->ce_dev = st->st_dev;
- ce->ce_ino = st->st_ino;
- ce->ce_uid = st->st_uid;
- ce->ce_gid = st->st_gid;
- ce->ce_size = st->st_size;
+ fill_stat_data(&ce->ce_stat_data, st);
if (assume_unchanged)
ce->ce_flags |= CE_VALID;
@@ -91,7 +138,7 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
ce_mark_uptodate(ce);
}
-static int ce_compare_data(struct cache_entry *ce, struct stat *st)
+static int ce_compare_data(const struct cache_entry *ce, struct stat *st)
{
int match = -1;
int fd = open(ce->name, O_RDONLY);
@@ -105,7 +152,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st)
return match;
}
-static int ce_compare_link(struct cache_entry *ce, size_t expected_size)
+static int ce_compare_link(const struct cache_entry *ce, size_t expected_size)
{
int match = -1;
void *buffer;
@@ -126,7 +173,7 @@ static int ce_compare_link(struct cache_entry *ce, size_t expected_size)
return match;
}
-static int ce_compare_gitlink(struct cache_entry *ce)
+static int ce_compare_gitlink(const struct cache_entry *ce)
{
unsigned char sha1[20];
@@ -143,7 +190,7 @@ static int ce_compare_gitlink(struct cache_entry *ce)
return hashcmp(sha1, ce->sha1);
}
-static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
+static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st)
{
switch (st->st_mode & S_IFMT) {
case S_IFREG:
@@ -163,7 +210,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
return 0;
}
-static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
+static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
{
unsigned int changed = 0;
@@ -195,43 +242,11 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
default:
die("internal error: ce_mode is %o", ce->ce_mode);
}
- if (ce->ce_mtime.sec != (unsigned int)st->st_mtime)
- changed |= MTIME_CHANGED;
- if (trust_ctime && check_stat &&
- ce->ce_ctime.sec != (unsigned int)st->st_ctime)
- changed |= CTIME_CHANGED;
-#ifdef USE_NSEC
- if (check_stat && ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
- changed |= MTIME_CHANGED;
- if (trust_ctime && check_stat &&
- ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
- changed |= CTIME_CHANGED;
-#endif
-
- if (check_stat) {
- if (ce->ce_uid != (unsigned int) st->st_uid ||
- ce->ce_gid != (unsigned int) st->st_gid)
- changed |= OWNER_CHANGED;
- if (ce->ce_ino != (unsigned int) st->st_ino)
- changed |= INODE_CHANGED;
- }
-
-#ifdef USE_STDEV
- /*
- * st_dev breaks on network filesystems where different
- * clients will have different views of what "device"
- * the filesystem is on
- */
- if (check_stat && ce->ce_dev != (unsigned int) st->st_dev)
- changed |= INODE_CHANGED;
-#endif
-
- if (ce->ce_size != (unsigned int) st->st_size)
- changed |= DATA_CHANGED;
+ changed |= match_stat_data(&ce->ce_stat_data, st);
/* Racily smudged entry? */
- if (!ce->ce_size) {
+ if (!ce->ce_stat_data.sd_size) {
if (!is_empty_blob_sha1(ce->sha1))
changed |= DATA_CHANGED;
}
@@ -239,23 +254,24 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
return changed;
}
-static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce)
+static int is_racy_timestamp(const struct index_state *istate,
+ const struct cache_entry *ce)
{
return (!S_ISGITLINK(ce->ce_mode) &&
istate->timestamp.sec &&
#ifdef USE_NSEC
/* nanosecond timestamped files can also be racy! */
- (istate->timestamp.sec < ce->ce_mtime.sec ||
- (istate->timestamp.sec == ce->ce_mtime.sec &&
- istate->timestamp.nsec <= ce->ce_mtime.nsec))
+ (istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||
+ (istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&
+ istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))
#else
- istate->timestamp.sec <= ce->ce_mtime.sec
+ istate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec
#endif
);
}
int ie_match_stat(const struct index_state *istate,
- struct cache_entry *ce, struct stat *st,
+ const struct cache_entry *ce, struct stat *st,
unsigned int options)
{
unsigned int changed;
@@ -311,7 +327,8 @@ int ie_match_stat(const struct index_state *istate,
}
int ie_modified(const struct index_state *istate,
- struct cache_entry *ce, struct stat *st, unsigned int options)
+ const struct cache_entry *ce,
+ struct stat *st, unsigned int options)
{
int changed, changed_fs;
@@ -340,7 +357,7 @@ int ie_modified(const struct index_state *istate,
* then we know it is.
*/
if ((changed & DATA_CHANGED) &&
- (S_ISGITLINK(ce->ce_mode) || ce->ce_size != 0))
+ (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
return changed;
changed_fs = ce_modified_check_fs(ce, st);
@@ -626,7 +643,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
if (*ptr == '/') {
struct cache_entry *foundce;
++ptr;
- foundce = index_name_exists(&the_index, ce->name, ptr - ce->name, ignore_case);
+ foundce = index_name_exists(istate, ce->name, ptr - ce->name, ignore_case);
if (foundce) {
memcpy((void *)startPtr, foundce->name + (startPtr - ce->name), ptr - startPtr);
startPtr = ptr;
@@ -979,7 +996,7 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
if (istate->cache_nr == istate->cache_alloc) {
istate->cache_alloc = alloc_nr(istate->cache_alloc);
istate->cache = xrealloc(istate->cache,
- istate->cache_alloc * sizeof(struct cache_entry *));
+ istate->cache_alloc * sizeof(*istate->cache));
}
/* Add it in.. */
@@ -1322,16 +1339,16 @@ static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *on
{
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_stat_data.sd_ctime.sec = ntoh_l(ondisk->ctime.sec);
+ ce->ce_stat_data.sd_mtime.sec = ntoh_l(ondisk->mtime.sec);
+ ce->ce_stat_data.sd_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
+ ce->ce_stat_data.sd_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
+ ce->ce_stat_data.sd_dev = ntoh_l(ondisk->dev);
+ ce->ce_stat_data.sd_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_stat_data.sd_uid = ntoh_l(ondisk->uid);
+ ce->ce_stat_data.sd_gid = ntoh_l(ondisk->gid);
+ ce->ce_stat_data.sd_size = ntoh_l(ondisk->size);
ce->ce_flags = flags & ~CE_NAMEMASK;
ce->ce_namelen = len;
hashcpy(ce->sha1, ondisk->sha1);
@@ -1449,7 +1466,7 @@ int read_index_from(struct index_state *istate, const char *path)
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 *));
+ istate->cache = xcalloc(istate->cache_alloc, sizeof(*istate->cache));
istate->initialized = 1;
if (istate->version == 4)
@@ -1518,8 +1535,9 @@ int discard_index(struct index_state *istate)
free_name_hash(istate);
cache_tree_free(&(istate->cache_tree));
istate->initialized = 0;
-
- /* no need to throw away allocated active_cache */
+ free(istate->cache);
+ istate->cache = NULL;
+ istate->cache_alloc = 0;
return 0;
}
@@ -1608,7 +1626,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
* The only thing we care about in this function is to smudge the
* falsely clean entry due to touch-update-touch race, so we leave
* everything else as they are. We are called for entries whose
- * ce_mtime match the index file mtime.
+ * ce_stat_data.sd_mtime match the index file mtime.
*
* Note that this actually does not do much for gitlinks, for
* which ce_match_stat_basic() always goes to the actual
@@ -1647,7 +1665,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
* file, and never calls us, so the cached size information
* for "frotz" stays 6 which does not match the filesystem.
*/
- ce->ce_size = 0;
+ ce->ce_stat_data.sd_size = 0;
}
}
@@ -1657,16 +1675,16 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
{
short flags;
- ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
- ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
- ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
- ondisk->mtime.nsec = htonl(ce->ce_mtime.nsec);
- ondisk->dev = htonl(ce->ce_dev);
- ondisk->ino = htonl(ce->ce_ino);
+ ondisk->ctime.sec = htonl(ce->ce_stat_data.sd_ctime.sec);
+ ondisk->mtime.sec = htonl(ce->ce_stat_data.sd_mtime.sec);
+ ondisk->ctime.nsec = htonl(ce->ce_stat_data.sd_ctime.nsec);
+ ondisk->mtime.nsec = htonl(ce->ce_stat_data.sd_mtime.nsec);
+ ondisk->dev = htonl(ce->ce_stat_data.sd_dev);
+ ondisk->ino = htonl(ce->ce_stat_data.sd_ino);
ondisk->mode = htonl(ce->ce_mode);
- ondisk->uid = htonl(ce->ce_uid);
- ondisk->gid = htonl(ce->ce_gid);
- ondisk->size = htonl(ce->ce_size);
+ ondisk->uid = htonl(ce->ce_stat_data.sd_uid);
+ ondisk->gid = htonl(ce->ce_stat_data.sd_gid);
+ ondisk->size = htonl(ce->ce_stat_data.sd_size);
hashcpy(ondisk->sha1, ce->sha1);
flags = ce->ce_flags;
@@ -1933,3 +1951,33 @@ void *read_blob_data_from_index(struct index_state *istate, const char *path, un
*size = sz;
return data;
}
+
+void stat_validity_clear(struct stat_validity *sv)
+{
+ free(sv->sd);
+ sv->sd = NULL;
+}
+
+int stat_validity_check(struct stat_validity *sv, const char *path)
+{
+ struct stat st;
+
+ if (stat(path, &st) < 0)
+ return sv->sd == NULL;
+ if (!sv->sd)
+ return 0;
+ return S_ISREG(st.st_mode) && !match_stat_data(sv->sd, &st);
+}
+
+void stat_validity_update(struct stat_validity *sv, int fd)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode))
+ stat_validity_clear(sv);
+ else {
+ if (!sv->sd)
+ sv->sd = xcalloc(1, sizeof(struct stat_data));
+ fill_stat_data(sv->sd, &st);
+ }
+}
diff --git a/refs.c b/refs.c
index de2d8eb..4302206 100644
--- a/refs.c
+++ b/refs.c
@@ -72,6 +72,10 @@ int check_refname_format(const char *refname, int flags)
{
int component_len, component_count = 0;
+ if (!strcmp(refname, "@"))
+ /* Refname is a single character '@'. */
+ return -1;
+
while (1) {
/* We are at the start of a path component. */
component_len = check_refname_component(refname, flags);
@@ -109,7 +113,20 @@ struct ref_entry;
* (ref_entry->flag & REF_DIR) is zero.
*/
struct ref_value {
+ /*
+ * The name of the object to which this reference resolves
+ * (which may be a tag object). If REF_ISBROKEN, this is
+ * null. If REF_ISSYMREF, then this is the name of the object
+ * referred to by the last reference in the symlink chain.
+ */
unsigned char sha1[20];
+
+ /*
+ * If REF_KNOWS_PEELED, then this field holds the peeled value
+ * of this reference, or null if the reference is known not to
+ * be peelable. See the documentation for peel_ref() for an
+ * exact definition of "peelable".
+ */
unsigned char peeled[20];
};
@@ -158,7 +175,17 @@ struct ref_dir {
struct ref_entry **entries;
};
-/* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */
+/*
+ * Bit values for ref_entry::flag. REF_ISSYMREF=0x01,
+ * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
+ * refs.h.
+ */
+
+/*
+ * The field ref_entry->u.value.peeled of this value entry contains
+ * the correct peeled value for the reference, which might be
+ * null_sha1 if the reference is not a tag or if it is broken.
+ */
#define REF_KNOWS_PEELED 0x08
/* ref_entry represents a directory of references */
@@ -343,18 +370,17 @@ static int ref_entry_cmp_sslice(const void *key_, const void *ent_)
}
/*
- * 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.
+ * Return the index of the entry with the given refname from the
+ * ref_dir (non-recursively), sorting dir if necessary. Return -1 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)
+static int 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;
+ return -1;
sort_ref_dir(dir);
key.len = len;
@@ -363,9 +389,9 @@ static struct ref_entry *search_ref_dir(struct ref_dir *dir,
ref_entry_cmp_sslice);
if (r == NULL)
- return NULL;
+ return -1;
- return *r;
+ return r - dir->entries;
}
/*
@@ -379,8 +405,9 @@ 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) {
+ int entry_index = search_ref_dir(dir, subdirname, len);
+ struct ref_entry *entry;
+ if (entry_index == -1) {
if (!mkdir)
return NULL;
/*
@@ -391,6 +418,8 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir,
*/
entry = create_dir_entry(dir->ref_cache, subdirname, len, 0);
add_entry_to_dir(dir, entry);
+ } else {
+ entry = dir->entries[entry_index];
}
return get_ref_dir(entry);
}
@@ -429,12 +458,67 @@ static struct ref_dir *find_containing_dir(struct ref_dir *dir,
*/
static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname)
{
+ int entry_index;
struct ref_entry *entry;
dir = find_containing_dir(dir, refname, 0);
if (!dir)
return NULL;
- entry = search_ref_dir(dir, refname, strlen(refname));
- return (entry && !(entry->flag & REF_DIR)) ? entry : NULL;
+ entry_index = search_ref_dir(dir, refname, strlen(refname));
+ if (entry_index == -1)
+ return NULL;
+ entry = dir->entries[entry_index];
+ return (entry->flag & REF_DIR) ? NULL : entry;
+}
+
+/*
+ * Remove the entry with the given name from dir, recursing into
+ * subdirectories as necessary. If refname is the name of a directory
+ * (i.e., ends with '/'), then remove the directory and its contents.
+ * If the removal was successful, return the number of entries
+ * remaining in the directory entry that contained the deleted entry.
+ * If the name was not found, return -1. Please note that this
+ * function only deletes the entry from the cache; it does not delete
+ * it from the filesystem or ensure that other cache entries (which
+ * might be symbolic references to the removed entry) are updated.
+ * Nor does it remove any containing dir entries that might be made
+ * empty by the removal. dir must represent the top-level directory
+ * and must already be complete.
+ */
+static int remove_entry(struct ref_dir *dir, const char *refname)
+{
+ int refname_len = strlen(refname);
+ int entry_index;
+ struct ref_entry *entry;
+ int is_dir = refname[refname_len - 1] == '/';
+ if (is_dir) {
+ /*
+ * refname represents a reference directory. Remove
+ * the trailing slash; otherwise we will get the
+ * directory *representing* refname rather than the
+ * one *containing* it.
+ */
+ char *dirname = xmemdupz(refname, refname_len - 1);
+ dir = find_containing_dir(dir, dirname, 0);
+ free(dirname);
+ } else {
+ dir = find_containing_dir(dir, refname, 0);
+ }
+ if (!dir)
+ return -1;
+ entry_index = search_ref_dir(dir, refname, refname_len);
+ if (entry_index == -1)
+ return -1;
+ entry = dir->entries[entry_index];
+
+ memmove(&dir->entries[entry_index],
+ &dir->entries[entry_index + 1],
+ (dir->nr - entry_index - 1) * sizeof(*dir->entries)
+ );
+ dir->nr--;
+ if (dir->sorted > entry_index)
+ dir->sorted--;
+ free_ref_entry(entry);
+ return dir->nr;
}
/*
@@ -503,27 +587,64 @@ static void sort_ref_dir(struct ref_dir *dir)
dir->sorted = dir->nr = i;
}
-#define DO_FOR_EACH_INCLUDE_BROKEN 01
+/* Include broken references in a do_for_each_ref*() iteration: */
+#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
+/*
+ * Return true iff the reference described by entry can be resolved to
+ * an object in the database. Emit a warning if the referred-to
+ * object does not exist.
+ */
+static int ref_resolves_to_object(struct ref_entry *entry)
+{
+ if (entry->flag & REF_ISBROKEN)
+ return 0;
+ if (!has_sha1_file(entry->u.value.sha1)) {
+ error("%s does not point to a valid object!", entry->name);
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * current_ref is a performance hack: when iterating over references
+ * using the for_each_ref*() functions, current_ref is set to the
+ * current reference's entry before calling the callback function. If
+ * the callback function calls peel_ref(), then peel_ref() first
+ * checks whether the reference to be peeled is the current reference
+ * (it usually is) and if so, returns that reference's peeled version
+ * if it is available. This avoids a refname lookup in a common case.
+ */
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)
+typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data);
+
+struct ref_entry_cb {
+ const char *base;
+ int trim;
+ int flags;
+ each_ref_fn *fn;
+ void *cb_data;
+};
+
+/*
+ * Handle one reference in a do_for_each_ref*()-style iteration,
+ * calling an each_ref_fn for each entry.
+ */
+static int do_one_ref(struct ref_entry *entry, void *cb_data)
{
+ struct ref_entry_cb *data = cb_data;
int retval;
- if (prefixcmp(entry->name, base))
+ if (prefixcmp(entry->name, data->base))
+ return 0;
+
+ if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ !ref_resolves_to_object(entry))
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);
+ retval = data->fn(entry->name + data->trim, entry->u.value.sha1,
+ entry->flag, data->cb_data);
current_ref = NULL;
return retval;
}
@@ -532,11 +653,11 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
* 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.
+ * does not sort dir itself; it should be sorted beforehand. fn is
+ * called for all references, including broken ones.
*/
-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)
+static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset,
+ each_ref_entry_fn fn, void *cb_data)
{
int i;
assert(dir->sorted == dir->nr);
@@ -546,10 +667,9 @@ static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
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);
+ retval = do_for_each_entry_in_dir(subdir, 0, fn, cb_data);
} else {
- retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
+ retval = fn(entry, cb_data);
}
if (retval)
return retval;
@@ -562,12 +682,12 @@ static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
* 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.
+ * sorted as needed. fn is called for all references, including
+ * broken ones.
*/
-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)
+static int do_for_each_entry_in_dirs(struct ref_dir *dir1,
+ struct ref_dir *dir2,
+ each_ref_entry_fn fn, void *cb_data)
{
int retval;
int i1 = 0, i2 = 0;
@@ -578,12 +698,10 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
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);
+ return do_for_each_entry_in_dir(dir2, i2, fn, cb_data);
}
if (i2 == dir2->nr) {
- return do_for_each_ref_in_dir(dir1, i1,
- base, fn, trim, flags, cb_data);
+ return do_for_each_entry_in_dir(dir1, i1, fn, cb_data);
}
e1 = dir1->entries[i1];
e2 = dir2->entries[i2];
@@ -595,14 +713,13 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
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);
+ retval = do_for_each_entry_in_dirs(
+ subdir1, subdir2, fn, 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);
+ retval = fn(e2, cb_data);
i1++;
i2++;
} else {
@@ -621,26 +738,33 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
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);
+ retval = do_for_each_entry_in_dir(
+ subdir, 0, fn, cb_data);
} else {
- retval = do_one_ref(base, fn, trim, flags, cb_data, e);
+ retval = fn(e, cb_data);
}
}
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;
}
/*
+ * Load all of the refs from the dir into our in-memory cache. The hard work
+ * of loading loose refs is done by get_ref_dir(), so we just need to recurse
+ * through all of the sub-directories. We do not even need to care about
+ * sorting, as traversal order does not matter to us.
+ */
+static void prime_ref_dir(struct ref_dir *dir)
+{
+ int i;
+ for (i = 0; i < dir->nr; i++) {
+ struct ref_entry *entry = dir->entries[i];
+ if (entry->flag & REF_DIR)
+ prime_ref_dir(get_ref_dir(entry));
+ }
+}
+/*
* 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
@@ -661,14 +785,13 @@ struct name_conflict_cb {
const char *conflicting_refname;
};
-static int name_conflict_fn(const char *existingrefname, const unsigned char *sha1,
- int flags, void *cb_data)
+static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
{
struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
- if (data->oldrefname && !strcmp(data->oldrefname, existingrefname))
+ if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
return 0;
- if (names_conflict(data->refname, existingrefname)) {
- data->conflicting_refname = existingrefname;
+ if (names_conflict(data->refname, entry->name)) {
+ data->conflicting_refname = entry->name;
return 1;
}
return 0;
@@ -676,7 +799,7 @@ static int name_conflict_fn(const char *existingrefname, const unsigned char *sh
/*
* Return true iff a reference named refname could be created without
- * conflicting with the name of an existing reference in array. If
+ * conflicting with the name of an existing reference in dir. If
* oldrefname is non-NULL, ignore potential conflicts with oldrefname
* (e.g., because oldrefname is scheduled for deletion in the same
* operation).
@@ -690,9 +813,7 @@ static int is_refname_available(const char *refname, const char *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)) {
+ if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
error("'%s' exists; cannot create '%s'",
data.conflicting_refname, refname);
return 0;
@@ -700,6 +821,30 @@ static int is_refname_available(const char *refname, const char *oldrefname,
return 1;
}
+struct packed_ref_cache {
+ struct ref_entry *root;
+
+ /*
+ * Count of references to the data structure in this instance,
+ * including the pointer from ref_cache::packed if any. The
+ * data will not be freed as long as the reference count is
+ * nonzero.
+ */
+ unsigned int referrers;
+
+ /*
+ * Iff the packed-refs file associated with this instance is
+ * currently locked for writing, this points at the associated
+ * lock (which is owned by somebody else). The referrer count
+ * is also incremented when the file is locked and decremented
+ * when it is unlocked.
+ */
+ struct lock_file *lock;
+
+ /* The metadata from when this packed-refs cache was read */
+ struct stat_validity validity;
+};
+
/*
* Future: need to be in "struct repository"
* when doing a full libification.
@@ -707,16 +852,51 @@ static int is_refname_available(const char *refname, const char *oldrefname,
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;
+ struct packed_ref_cache *packed;
+ /*
+ * The submodule name, or "" for the main repo. We allocate
+ * length 1 rather than FLEX_ARRAY so that the main ref_cache
+ * is initialized correctly.
+ */
+ char name[1];
+} ref_cache, *submodule_ref_caches;
+
+/* Lock used for the main packed-refs file: */
+static struct lock_file packlock;
+
+/*
+ * Increment the reference count of *packed_refs.
+ */
+static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
+{
+ packed_refs->referrers++;
+}
+
+/*
+ * Decrease the reference count of *packed_refs. If it goes to zero,
+ * free *packed_refs and return true; otherwise return false.
+ */
+static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
+{
+ if (!--packed_refs->referrers) {
+ free_ref_entry(packed_refs->root);
+ stat_validity_clear(&packed_refs->validity);
+ free(packed_refs);
+ return 1;
+ } else {
+ return 0;
+ }
+}
static void clear_packed_ref_cache(struct ref_cache *refs)
{
if (refs->packed) {
- free_ref_entry(refs->packed);
+ struct packed_ref_cache *packed_refs = refs->packed;
+
+ if (packed_refs->lock)
+ die("internal error: packed-ref cache cleared while locked");
refs->packed = NULL;
+ release_packed_ref_cache(packed_refs);
}
}
@@ -748,18 +928,18 @@ static struct ref_cache *create_ref_cache(const char *submodule)
*/
static struct ref_cache *get_ref_cache(const char *submodule)
{
- struct ref_cache *refs = ref_cache;
- if (!submodule)
- submodule = "";
- while (refs) {
+ struct ref_cache *refs;
+
+ if (!submodule || !*submodule)
+ return &ref_cache;
+
+ for (refs = submodule_ref_caches; refs; refs = refs->next)
if (!strcmp(submodule, refs->name))
return refs;
- refs = refs->next;
- }
refs = create_ref_cache(submodule);
- refs->next = ref_cache;
- ref_cache = refs;
+ refs->next = submodule_ref_caches;
+ submodule_ref_caches = refs;
return refs;
}
@@ -770,6 +950,16 @@ void invalidate_ref_cache(const char *submodule)
clear_loose_ref_cache(refs);
}
+/* The length of a peeled reference line in packed-refs, including EOL: */
+#define PEELED_LINE_LENGTH 42
+
+/*
+ * The packed-refs header line that we write out. Perhaps other
+ * traits will be added later. The trailing space is required.
+ */
+static const char PACKED_REFS_HEADER[] =
+ "# pack-refs with: peeled fully-peeled \n";
+
/*
* Parse one line from a packed-refs file. Write the SHA1 to sha1.
* Return a pointer to the refname within the line (null-terminated),
@@ -862,8 +1052,8 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
}
if (last &&
refline[0] == '^' &&
- strlen(refline) == 42 &&
- refline[41] == '\n' &&
+ strlen(refline) == PEELED_LINE_LENGTH &&
+ refline[PEELED_LINE_LENGTH - 1] == '\n' &&
!get_sha1_hex(refline + 1, sha1)) {
hashcpy(last->u.value.peeled, sha1);
/*
@@ -876,30 +1066,58 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
}
}
-static struct ref_dir *get_packed_refs(struct ref_cache *refs)
+/*
+ * Get the packed_ref_cache for the specified ref_cache, creating it
+ * if necessary.
+ */
+static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
{
+ const char *packed_refs_file;
+
+ if (*refs->name)
+ packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+ else
+ packed_refs_file = git_path("packed-refs");
+
+ if (refs->packed &&
+ !stat_validity_check(&refs->packed->validity, packed_refs_file))
+ clear_packed_ref_cache(refs);
+
if (!refs->packed) {
- const char *packed_refs_file;
FILE *f;
- 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");
+ refs->packed = xcalloc(1, sizeof(*refs->packed));
+ acquire_packed_ref_cache(refs->packed);
+ refs->packed->root = create_dir_entry(refs, "", 0, 0);
f = fopen(packed_refs_file, "r");
if (f) {
- read_packed_refs(f, get_ref_dir(refs->packed));
+ stat_validity_update(&refs->packed->validity, fileno(f));
+ read_packed_refs(f, get_ref_dir(refs->packed->root));
fclose(f);
}
}
- return get_ref_dir(refs->packed);
+ return refs->packed;
+}
+
+static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
+{
+ return get_ref_dir(packed_ref_cache->root);
+}
+
+static struct ref_dir *get_packed_refs(struct ref_cache *refs)
+{
+ return get_packed_ref_dir(get_packed_ref_cache(refs));
}
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));
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed refs not locked");
+ add_ref(get_packed_ref_dir(packed_ref_cache),
+ create_ref_entry(refname, sha1, REF_ISPACKED, 1));
}
/*
@@ -1069,18 +1287,43 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sh
}
/*
- * Try to read ref from the packed references. On success, set sha1
- * and return 0; otherwise, return -1.
+ * Return the ref_entry for the given refname from the packed
+ * references. If it does not exist, return NULL.
*/
-static int get_packed_ref(const char *refname, unsigned char *sha1)
+static struct ref_entry *get_packed_ref(const char *refname)
{
- struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
- struct ref_entry *entry = find_ref(packed, refname);
+ return find_ref(get_packed_refs(&ref_cache), refname);
+}
+
+/*
+ * A loose ref file doesn't exist; check for a packed ref. The
+ * options are forwarded from resolve_safe_unsafe().
+ */
+static const char *handle_missing_loose_ref(const char *refname,
+ unsigned char *sha1,
+ int reading,
+ int *flag)
+{
+ struct ref_entry *entry;
+
+ /*
+ * The loose reference file does not exist; check for a packed
+ * reference.
+ */
+ entry = get_packed_ref(refname);
if (entry) {
hashcpy(sha1, entry->u.value.sha1);
- return 0;
+ if (flag)
+ *flag |= REF_ISPACKED;
+ return refname;
+ }
+ /* The reference is not a packed reference, either. */
+ if (reading) {
+ return NULL;
+ } else {
+ hashclr(sha1);
+ return refname;
}
- return -1;
}
const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
@@ -1107,32 +1350,34 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
git_snpath(path, sizeof(path), "%s", refname);
+ /*
+ * We might have to loop back here to avoid a race
+ * condition: first we lstat() the file, then we try
+ * to read it as a link or as a file. But if somebody
+ * changes the type of the file (file <-> directory
+ * <-> symlink) between the lstat() and reading, then
+ * we don't want to report that as an error but rather
+ * try again starting with the lstat().
+ */
+ stat_ref:
if (lstat(path, &st) < 0) {
- 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;
- }
- /* The reference is not a packed reference, either. */
- if (reading) {
+ if (errno == ENOENT)
+ return handle_missing_loose_ref(refname, sha1,
+ reading, flag);
+ else
return NULL;
- } 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 < 0)
- return NULL;
+ if (len < 0) {
+ if (errno == ENOENT || errno == EINVAL)
+ /* inconsistent with lstat; retry */
+ goto stat_ref;
+ else
+ return NULL;
+ }
buffer[len] = 0;
if (!prefixcmp(buffer, "refs/") &&
!check_refname_format(buffer, 0)) {
@@ -1155,8 +1400,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
* a ref
*/
fd = open(path, O_RDONLY);
- if (fd < 0)
- return NULL;
+ if (fd < 0) {
+ if (errno == ENOENT)
+ /* inconsistent with lstat; retry */
+ goto stat_ref;
+ else
+ return NULL;
+ }
len = read_in_full(fd, buffer, sizeof(buffer)-1);
close(fd);
if (len < 0)
@@ -1168,8 +1418,19 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
/*
* Is it a symbolic ref?
*/
- if (prefixcmp(buffer, "ref:"))
- break;
+ if (prefixcmp(buffer, "ref:")) {
+ /*
+ * 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 refname;
+ }
if (flag)
*flag |= REF_ISSYMREF;
buf = buffer + 4;
@@ -1182,13 +1443,6 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
}
refname = strcpy(refname_buffer, buf);
}
- /* 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 refname;
}
char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
@@ -1231,54 +1485,130 @@ static int filter_refs(const char *refname, const unsigned char *sha1, int flags
return filter->fn(refname, sha1, flags, filter->cb_data);
}
+enum peel_status {
+ /* object was peeled successfully: */
+ PEEL_PEELED = 0,
+
+ /*
+ * object cannot be peeled because the named object (or an
+ * object referred to by a tag in the peel chain), does not
+ * exist.
+ */
+ PEEL_INVALID = -1,
+
+ /* object cannot be peeled because it is not a tag: */
+ PEEL_NON_TAG = -2,
+
+ /* ref_entry contains no peeled value because it is a symref: */
+ PEEL_IS_SYMREF = -3,
+
+ /*
+ * ref_entry cannot be peeled because it is broken (i.e., the
+ * symbolic reference cannot even be resolved to an object
+ * name):
+ */
+ PEEL_BROKEN = -4
+};
+
+/*
+ * Peel the named object; i.e., if the object is a tag, resolve the
+ * tag recursively until a non-tag is found. If successful, store the
+ * result to sha1 and return PEEL_PEELED. If the object is not a tag
+ * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,
+ * and leave sha1 unchanged.
+ */
+static enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
+{
+ struct object *o = lookup_unknown_object(name);
+
+ if (o->type == OBJ_NONE) {
+ int type = sha1_object_info(name, NULL);
+ if (type < 0)
+ return PEEL_INVALID;
+ o->type = type;
+ }
+
+ if (o->type != OBJ_TAG)
+ return PEEL_NON_TAG;
+
+ o = deref_tag_noverify(o);
+ if (!o)
+ return PEEL_INVALID;
+
+ hashcpy(sha1, o->sha1);
+ return PEEL_PEELED;
+}
+
+/*
+ * Peel the entry (if possible) and return its new peel_status. If
+ * repeel is true, re-peel the entry even if there is an old peeled
+ * value that is already stored in it.
+ *
+ * It is OK to call this function with a packed reference entry that
+ * might be stale and might even refer to an object that has since
+ * been garbage-collected. In such a case, if the entry has
+ * REF_KNOWS_PEELED then leave the status unchanged and return
+ * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID.
+ */
+static enum peel_status peel_entry(struct ref_entry *entry, int repeel)
+{
+ enum peel_status status;
+
+ if (entry->flag & REF_KNOWS_PEELED) {
+ if (repeel) {
+ entry->flag &= ~REF_KNOWS_PEELED;
+ hashclr(entry->u.value.peeled);
+ } else {
+ return is_null_sha1(entry->u.value.peeled) ?
+ PEEL_NON_TAG : PEEL_PEELED;
+ }
+ }
+ if (entry->flag & REF_ISBROKEN)
+ return PEEL_BROKEN;
+ if (entry->flag & REF_ISSYMREF)
+ return PEEL_IS_SYMREF;
+
+ status = peel_object(entry->u.value.sha1, entry->u.value.peeled);
+ if (status == PEEL_PEELED || status == PEEL_NON_TAG)
+ entry->flag |= REF_KNOWS_PEELED;
+ return status;
+}
+
int peel_ref(const char *refname, unsigned char *sha1)
{
int flag;
unsigned char base[20];
- struct object *o;
if (current_ref && (current_ref->name == refname
- || !strcmp(current_ref->name, refname))) {
- if (current_ref->flag & REF_KNOWS_PEELED) {
- if (is_null_sha1(current_ref->u.value.peeled))
- return -1;
- hashcpy(sha1, current_ref->u.value.peeled);
- return 0;
- }
- hashcpy(base, current_ref->u.value.sha1);
- goto fallback;
+ || !strcmp(current_ref->name, refname))) {
+ if (peel_entry(current_ref, 0))
+ return -1;
+ hashcpy(sha1, current_ref->u.value.peeled);
+ return 0;
}
if (read_ref_full(refname, base, 1, &flag))
return -1;
- if ((flag & REF_ISPACKED)) {
- struct ref_dir *dir = get_packed_refs(get_ref_cache(NULL));
- struct ref_entry *r = find_ref(dir, refname);
-
- if (r != NULL && r->flag & REF_KNOWS_PEELED) {
+ /*
+ * If the reference is packed, read its ref_entry from the
+ * cache in the hope that we already know its peeled value.
+ * We only try this optimization on packed references because
+ * (a) forcing the filling of the loose reference cache could
+ * be expensive and (b) loose references anyway usually do not
+ * have REF_KNOWS_PEELED.
+ */
+ if (flag & REF_ISPACKED) {
+ struct ref_entry *r = get_packed_ref(refname);
+ if (r) {
+ if (peel_entry(r, 0))
+ return -1;
hashcpy(sha1, r->u.value.peeled);
return 0;
}
}
-fallback:
- o = lookup_unknown_object(base);
- if (o->type == OBJ_NONE) {
- int type = sha1_object_info(base, NULL);
- if (type < 0)
- return -1;
- o->type = type;
- }
-
- if (o->type == OBJ_TAG) {
- o = deref_tag_noverify(o);
- if (o) {
- hashcpy(sha1, o->sha1);
- return 0;
- }
- }
- return -1;
+ return peel_object(base, sha1);
}
struct warn_if_dangling_data {
@@ -1316,40 +1646,84 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
for_each_rawref(warn_if_dangling_symref, &data);
}
-static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
- int trim, int flags, void *cb_data)
+/*
+ * Call fn for each reference in the specified ref_cache, omitting
+ * references not in the containing_dir of base. fn is called for all
+ * references, including broken ones. If fn ever returns a non-zero
+ * value, stop the iteration and return that value; otherwise, return
+ * 0.
+ */
+static int do_for_each_entry(struct ref_cache *refs, const char *base,
+ each_ref_entry_fn fn, 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);
+ struct packed_ref_cache *packed_ref_cache;
+ struct ref_dir *loose_dir;
+ struct ref_dir *packed_dir;
int retval = 0;
+ /*
+ * We must make sure that all loose refs are read before accessing the
+ * packed-refs file; this avoids a race condition in which loose refs
+ * are migrated to the packed-refs file by a simultaneous process, but
+ * our in-memory view is from before the migration. get_packed_ref_cache()
+ * takes care of making sure our view is up to date with what is on
+ * disk.
+ */
+ loose_dir = get_loose_refs(refs);
if (base && *base) {
- packed_dir = find_containing_dir(packed_dir, base, 0);
loose_dir = find_containing_dir(loose_dir, base, 0);
}
+ if (loose_dir)
+ prime_ref_dir(loose_dir);
+
+ packed_ref_cache = get_packed_ref_cache(refs);
+ acquire_packed_ref_cache(packed_ref_cache);
+ packed_dir = get_packed_ref_dir(packed_ref_cache);
+ if (base && *base) {
+ packed_dir = find_containing_dir(packed_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);
+ retval = do_for_each_entry_in_dirs(
+ packed_dir, loose_dir, fn, 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);
+ retval = do_for_each_entry_in_dir(
+ packed_dir, 0, fn, 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);
+ retval = do_for_each_entry_in_dir(
+ loose_dir, 0, fn, cb_data);
}
+ release_packed_ref_cache(packed_ref_cache);
return retval;
}
+/*
+ * Call fn for each reference in the specified ref_cache for which the
+ * refname begins with base. If trim is non-zero, then trim that many
+ * characters off the beginning of each refname before passing the
+ * refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to include
+ * broken references in the iteration. If fn ever returns a non-zero
+ * value, stop the iteration and return that value; otherwise, return
+ * 0.
+ */
+static int do_for_each_ref(struct ref_cache *refs, const char *base,
+ each_ref_fn fn, int trim, int flags, void *cb_data)
+{
+ struct ref_entry_cb data;
+ data.base = base;
+ data.trim = trim;
+ data.flags = flags;
+ data.fn = fn;
+ data.cb_data = cb_data;
+
+ return do_for_each_entry(refs, base, do_one_ref, &data);
+}
+
static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
unsigned char sha1[20];
@@ -1380,23 +1754,23 @@ 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, "", fn, 0, 0, cb_data);
+ return do_for_each_ref(&ref_cache, "", 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, "", fn, 0, 0, cb_data);
+ return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data);
}
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
+ return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
}
int for_each_ref_in_submodule(const char *submodule, const char *prefix,
each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
+ return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);
}
int for_each_tag_ref(each_ref_fn fn, void *cb_data)
@@ -1431,7 +1805,7 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c
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);
+ return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data);
}
int head_ref_namespaced(each_ref_fn fn, void *cb_data)
@@ -1454,7 +1828,7 @@ 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);
+ ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data);
strbuf_release(&buf);
return ret;
}
@@ -1496,7 +1870,7 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
int for_each_rawref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(NULL, "", fn, 0,
+ return do_for_each_ref(&ref_cache, "", fn, 0,
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
@@ -1702,7 +2076,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
* name is a proper prefix of our refname.
*/
if (missing &&
- !is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) {
+ !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {
last_errno = ENOTDIR;
goto error_return;
}
@@ -1754,48 +2128,322 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
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)
+/*
+ * Write an entry to the packed-refs file for the specified refname.
+ * If peeled is non-NULL, write it as the entry's peeled value.
+ */
+static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
+ unsigned char *peeled)
{
- 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);
+ write_or_die(fd, line, len);
+
+ if (peeled) {
+ if (snprintf(line, sizeof(line), "^%s\n",
+ sha1_to_hex(peeled)) != PEELED_LINE_LENGTH)
+ die("internal error");
+ write_or_die(fd, line, PEELED_LINE_LENGTH);
+ }
+}
+
+/*
+ * An each_ref_entry_fn that writes the entry to a packed-refs file.
+ */
+static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
+{
+ int *fd = cb_data;
+ enum peel_status peel_status = peel_entry(entry, 0);
+
+ if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
+ error("internal error: %s is not a valid packed reference!",
+ entry->name);
+ write_packed_entry(*fd, entry->name, entry->u.value.sha1,
+ peel_status == PEEL_PEELED ?
+ entry->u.value.peeled : NULL);
return 0;
}
-static struct lock_file packlock;
+int lock_packed_refs(int flags)
+{
+ struct packed_ref_cache *packed_ref_cache;
-static int repack_without_ref(const char *refname)
+ /* Discard the old cache because it might be invalid: */
+ clear_packed_ref_cache(&ref_cache);
+ if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
+ return -1;
+ /* Read the current packed-refs while holding the lock: */
+ packed_ref_cache = get_packed_ref_cache(&ref_cache);
+ packed_ref_cache->lock = &packlock;
+ /* Increment the reference count to prevent it from being freed: */
+ acquire_packed_ref_cache(packed_ref_cache);
+ return 0;
+}
+
+int commit_packed_refs(void)
+{
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+ int error = 0;
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed-refs not locked");
+ write_or_die(packed_ref_cache->lock->fd,
+ PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
+
+ do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
+ 0, write_packed_entry_fn,
+ &packed_ref_cache->lock->fd);
+ if (commit_lock_file(packed_ref_cache->lock))
+ error = -1;
+ packed_ref_cache->lock = NULL;
+ release_packed_ref_cache(packed_ref_cache);
+ return error;
+}
+
+void rollback_packed_refs(void)
+{
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed-refs not locked");
+ rollback_lock_file(packed_ref_cache->lock);
+ packed_ref_cache->lock = NULL;
+ release_packed_ref_cache(packed_ref_cache);
+ clear_packed_ref_cache(&ref_cache);
+}
+
+struct ref_to_prune {
+ struct ref_to_prune *next;
+ unsigned char sha1[20];
+ char name[FLEX_ARRAY];
+};
+
+struct pack_refs_cb_data {
+ unsigned int flags;
+ struct ref_dir *packed_refs;
+ struct ref_to_prune *ref_to_prune;
+};
+
+/*
+ * An each_ref_entry_fn that is run over loose references only. If
+ * the loose reference can be packed, add an entry in the packed ref
+ * cache. If the reference should be pruned, also add it to
+ * ref_to_prune in the pack_refs_cb_data.
+ */
+static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
{
- struct repack_without_ref_sb data;
- struct ref_cache *refs = get_ref_cache(NULL);
- struct ref_dir *packed = get_packed_refs(refs);
- if (find_ref(packed, refname) == NULL)
+ struct pack_refs_cb_data *cb = cb_data;
+ enum peel_status peel_status;
+ struct ref_entry *packed_entry;
+ int is_tag_ref = !prefixcmp(entry->name, "refs/tags/");
+
+ /* ALWAYS pack tags */
+ if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
return 0;
- data.refname = refname;
- data.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
- if (data.fd < 0) {
+
+ /* Do not pack symbolic or broken refs: */
+ if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry))
+ return 0;
+
+ /* Add a packed ref cache entry equivalent to the loose entry. */
+ peel_status = peel_entry(entry, 1);
+ if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
+ die("internal error peeling reference %s (%s)",
+ entry->name, sha1_to_hex(entry->u.value.sha1));
+ packed_entry = find_ref(cb->packed_refs, entry->name);
+ if (packed_entry) {
+ /* Overwrite existing packed entry with info from loose entry */
+ packed_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED;
+ hashcpy(packed_entry->u.value.sha1, entry->u.value.sha1);
+ } else {
+ packed_entry = create_ref_entry(entry->name, entry->u.value.sha1,
+ REF_ISPACKED | REF_KNOWS_PEELED, 0);
+ add_ref(cb->packed_refs, packed_entry);
+ }
+ hashcpy(packed_entry->u.value.peeled, entry->u.value.peeled);
+
+ /* Schedule the loose reference for pruning if requested. */
+ if ((cb->flags & PACK_REFS_PRUNE)) {
+ int namelen = strlen(entry->name) + 1;
+ struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
+ hashcpy(n->sha1, entry->u.value.sha1);
+ strcpy(n->name, entry->name);
+ n->next = cb->ref_to_prune;
+ cb->ref_to_prune = n;
+ }
+ return 0;
+}
+
+/*
+ * Remove empty parents, but spare refs/ and immediate subdirs.
+ * Note: munges *name.
+ */
+static void try_remove_empty_parents(char *name)
+{
+ char *p, *q;
+ int i;
+ p = name;
+ for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
+ while (*p && *p != '/')
+ p++;
+ /* tolerate duplicate slashes; see check_refname_format() */
+ while (*p == '/')
+ p++;
+ }
+ for (q = p; *q; q++)
+ ;
+ while (1) {
+ while (q > p && *q != '/')
+ q--;
+ while (q > p && *(q-1) == '/')
+ q--;
+ if (q == p)
+ break;
+ *q = '\0';
+ if (rmdir(git_path("%s", name)))
+ break;
+ }
+}
+
+/* make sure nobody touched the ref, and unlink */
+static void prune_ref(struct ref_to_prune *r)
+{
+ struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+
+ if (lock) {
+ unlink_or_warn(git_path("%s", r->name));
+ unlock_ref(lock);
+ try_remove_empty_parents(r->name);
+ }
+}
+
+static void prune_refs(struct ref_to_prune *r)
+{
+ while (r) {
+ prune_ref(r);
+ r = r->next;
+ }
+}
+
+int pack_refs(unsigned int flags)
+{
+ struct pack_refs_cb_data cbdata;
+
+ memset(&cbdata, 0, sizeof(cbdata));
+ cbdata.flags = flags;
+
+ lock_packed_refs(LOCK_DIE_ON_ERROR);
+ cbdata.packed_refs = get_packed_refs(&ref_cache);
+
+ do_for_each_entry_in_dir(get_loose_refs(&ref_cache), 0,
+ pack_if_possible_fn, &cbdata);
+
+ if (commit_packed_refs())
+ die_errno("unable to overwrite old ref-pack file");
+
+ prune_refs(cbdata.ref_to_prune);
+ return 0;
+}
+
+/*
+ * If entry is no longer needed in packed-refs, add it to the string
+ * list pointed to by cb_data. Reasons for deleting entries:
+ *
+ * - Entry is broken.
+ * - Entry is overridden by a loose ref.
+ * - Entry does not point at a valid object.
+ *
+ * In the first and third cases, also emit an error message because these
+ * are indications of repository corruption.
+ */
+static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
+{
+ struct string_list *refs_to_delete = cb_data;
+
+ if (entry->flag & REF_ISBROKEN) {
+ /* This shouldn't happen to packed refs. */
+ error("%s is broken!", entry->name);
+ string_list_append(refs_to_delete, entry->name);
+ return 0;
+ }
+ if (!has_sha1_file(entry->u.value.sha1)) {
+ unsigned char sha1[20];
+ int flags;
+
+ if (read_ref_full(entry->name, sha1, 0, &flags))
+ /* We should at least have found the packed ref. */
+ die("Internal error");
+ if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {
+ /*
+ * This packed reference is overridden by a
+ * loose reference, so it is OK that its value
+ * is no longer valid; for example, it might
+ * refer to an object that has been garbage
+ * collected. For this purpose we don't even
+ * care whether the loose reference itself is
+ * invalid, broken, symbolic, etc. Silently
+ * remove the packed reference.
+ */
+ string_list_append(refs_to_delete, entry->name);
+ return 0;
+ }
+ /*
+ * There is no overriding loose reference, so the fact
+ * that this reference doesn't refer to a valid object
+ * indicates some kind of repository corruption.
+ * Report the problem, then omit the reference from
+ * the output.
+ */
+ error("%s does not point to a valid object!", entry->name);
+ string_list_append(refs_to_delete, entry->name);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int repack_without_ref(const char *refname)
+{
+ struct ref_dir *packed;
+ struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+ struct string_list_item *ref_to_delete;
+
+ if (!get_packed_ref(refname))
+ return 0; /* refname does not exist in packed refs */
+
+ if (lock_packed_refs(0)) {
unable_to_lock_error(git_path("packed-refs"), errno);
return error("cannot delete '%s' from packed refs", refname);
}
- clear_packed_ref_cache(refs);
- packed = get_packed_refs(refs);
- do_for_each_ref_in_dir(packed, 0, "", repack_without_ref_fn, 0, 0, &data);
- return commit_lock_file(&packlock);
+ packed = get_packed_refs(&ref_cache);
+
+ /* Remove refname from the cache: */
+ if (remove_entry(packed, refname) == -1) {
+ /*
+ * The packed entry disappeared while we were
+ * acquiring the lock.
+ */
+ rollback_packed_refs();
+ return 0;
+ }
+
+ /* Remove any other accumulated cruft: */
+ do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
+ for_each_string_list_item(ref_to_delete, &refs_to_delete) {
+ if (remove_entry(packed, ref_to_delete->string) == -1)
+ die("internal error");
+ }
+
+ /* Write what remains: */
+ return commit_packed_refs();
}
int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
@@ -1823,7 +2471,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
ret |= repack_without_ref(lock->ref_name);
unlink_or_warn(git_path("logs/%s", lock->ref_name));
- invalidate_ref_cache(NULL);
+ clear_loose_ref_cache(&ref_cache);
unlock_ref(lock);
return ret;
}
@@ -1845,7 +2493,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
struct stat 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", oldrefname);
@@ -1857,10 +2504,10 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
if (!symref)
return error("refname %s not found", oldrefname);
- if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs)))
+ if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))
return 1;
- if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs)))
+ if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))
return 1;
if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
@@ -2116,7 +2763,7 @@ int write_ref_sha1(struct ref_lock *lock,
unlock_ref(lock);
return -1;
}
- clear_loose_ref_cache(get_ref_cache(NULL));
+ clear_loose_ref_cache(&ref_cache);
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)) {
diff --git a/refs.h b/refs.h
index a35eafc..9e5db3a 100644
--- a/refs.h
+++ b/refs.h
@@ -10,18 +10,41 @@ struct ref_lock {
int force_write;
};
+/*
+ * Bit values set in the flags argument passed to each_ref_fn():
+ */
+
+/* Reference is a symbolic reference. */
#define REF_ISSYMREF 0x01
+
+/* Reference is a packed reference. */
#define REF_ISPACKED 0x02
+
+/*
+ * Reference cannot be resolved to an object name: dangling symbolic
+ * reference (directly or indirectly), corrupt reference file, or
+ * symbolic reference refers to ill-formatted reference name.
+ */
#define REF_ISBROKEN 0x04
/*
- * 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.
+ * The signature for the callback function for the for_each_*()
+ * functions below. The memory pointed to by the refname and sha1
+ * arguments is only guaranteed to be valid for the duration of a
+ * single callback invocation.
+ */
+typedef int each_ref_fn(const char *refname,
+ const unsigned char *sha1, int flags, void *cb_data);
+
+/*
+ * The following functions invoke the specified callback function for
+ * each reference indicated. If the function ever returns a nonzero
+ * value, stop the iteration and return that 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 *);
extern int for_each_ref(each_ref_fn, void *);
extern int for_each_ref_in(const char *, each_ref_fn, void *);
@@ -54,13 +77,57 @@ extern int for_each_rawref(each_ref_fn, void *);
extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
/*
- * Add a reference to the in-memory packed reference cache. To actually
- * write the reference to the packed-refs file, call pack_refs().
+ * Lock the packed-refs file for writing. Flags is passed to
+ * hold_lock_file_for_update(). Return 0 on success.
+ */
+extern int lock_packed_refs(int flags);
+
+/*
+ * Add a reference to the in-memory packed reference cache. This may
+ * only be called while the packed-refs file is locked (see
+ * lock_packed_refs()). To actually write the packed-refs file, call
+ * commit_packed_refs().
*/
extern void add_packed_ref(const char *refname, const unsigned char *sha1);
+/*
+ * Write the current version of the packed refs cache from memory to
+ * disk. The packed-refs file must already be locked for writing (see
+ * lock_packed_refs()). Return zero on success.
+ */
+extern int commit_packed_refs(void);
+
+/*
+ * Rollback the lockfile for the packed-refs file, and discard the
+ * in-memory packed reference cache. (The packed-refs file will be
+ * read anew if it is needed again after this function is called.)
+ */
+extern void rollback_packed_refs(void);
+
+/*
+ * Flags for controlling behaviour of pack_refs()
+ * PACK_REFS_PRUNE: Prune loose refs after packing
+ * PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs
+ */
+#define PACK_REFS_PRUNE 0x0001
+#define PACK_REFS_ALL 0x0002
+
+/*
+ * Write a packed-refs file for the current repository.
+ * flags: Combination of the above PACK_REFS_* flags.
+ */
+int pack_refs(unsigned int flags);
+
extern int ref_exists(const char *);
+/*
+ * If refname is a non-symbolic reference that refers to a tag object,
+ * and the tag can be (recursively) dereferenced to a non-tag object,
+ * store the SHA1 of the referred-to object to sha1 and return 0. If
+ * any of these conditions are not met, return a non-zero value.
+ * Symbolic references are considered unpeelable, even if they
+ * ultimately resolve to a peelable tag.
+ */
extern int peel_ref(const char *refname, unsigned char *sha1);
/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
diff --git a/remote-curl.c b/remote-curl.c
index 60eda63..5b3ce9e 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -7,6 +7,7 @@
#include "run-command.h"
#include "pkt-line.h"
#include "sideband.h"
+#include "argv-array.h"
static struct remote *remote;
static const char *url; /* always ends with a trailing slash */
@@ -787,36 +788,35 @@ static int push_dav(int nr_spec, char **specs)
static int push_git(struct discovery *heads, int nr_spec, char **specs)
{
struct rpc_state rpc;
- const char **argv;
- int argc = 0, i, err;
+ int i, err;
+ struct argv_array args;
+
+ argv_array_init(&args);
+ argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
+ NULL);
- argv = xmalloc((10 + nr_spec) * sizeof(char*));
- argv[argc++] = "send-pack";
- argv[argc++] = "--stateless-rpc";
- argv[argc++] = "--helper-status";
if (options.thin)
- argv[argc++] = "--thin";
+ argv_array_push(&args, "--thin");
if (options.dry_run)
- argv[argc++] = "--dry-run";
+ argv_array_push(&args, "--dry-run");
if (options.verbosity == 0)
- argv[argc++] = "--quiet";
+ argv_array_push(&args, "--quiet");
else if (options.verbosity > 1)
- argv[argc++] = "--verbose";
- argv[argc++] = options.progress ? "--progress" : "--no-progress";
- argv[argc++] = url;
+ argv_array_push(&args, "--verbose");
+ argv_array_push(&args, options.progress ? "--progress" : "--no-progress");
+ argv_array_push(&args, url);
for (i = 0; i < nr_spec; i++)
- argv[argc++] = specs[i];
- argv[argc++] = NULL;
+ argv_array_push(&args, specs[i]);
memset(&rpc, 0, sizeof(rpc));
rpc.service_name = "git-receive-pack",
- rpc.argv = argv;
+ rpc.argv = args.argv;
err = rpc_service(&rpc, heads);
if (rpc.result.len)
write_or_die(1, rpc.result.buf, rpc.result.len);
strbuf_release(&rpc.result);
- free(argv);
+ argv_array_clear(&args);
return err;
}
diff --git a/remote-testsvn.c b/remote-testsvn.c
index 5ddf11c..d7cd5d2 100644
--- a/remote-testsvn.c
+++ b/remote-testsvn.c
@@ -286,7 +286,7 @@ static int do_command(struct strbuf *line)
return 0;
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
struct strbuf buf = STRBUF_INIT, url_sb = STRBUF_INIT,
private_ref_sb = STRBUF_INIT, marksfilename_sb = STRBUF_INIT,
diff --git a/remote.c b/remote.c
index 68eb99b..efcba93 100644
--- a/remote.c
+++ b/remote.c
@@ -276,10 +276,9 @@ static void read_remotes_file(struct remote *remote)
static void read_branches_file(struct remote *remote)
{
- const char *slash = strchr(remote->name, '/');
char *frag;
struct strbuf branch = STRBUF_INIT;
- int n = slash ? slash - remote->name : 1000;
+ int n = 1000;
FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
char *s, *p;
int len;
@@ -299,21 +298,11 @@ static void read_branches_file(struct remote *remote)
while (isspace(p[-1]))
*--p = 0;
len = p - s;
- if (slash)
- len += strlen(slash);
p = xmalloc(len + 1);
strcpy(p, s);
- if (slash)
- strcat(p, slash);
/*
- * With "slash", e.g. "git fetch jgarzik/netdev-2.6" when
- * reading from $GIT_DIR/branches/jgarzik fetches "HEAD" from
- * the partial URL obtained from the branches file plus
- * "/netdev-2.6" and does not store it in any tracking ref.
- * #branch specifier in the file is ignored.
- *
- * Otherwise, the branches file would have URL and optionally
+ * The branches file would have URL and optionally
* #branch specified. The "master" (or specified) branch is
* fetched and stored in the local branch of the same name.
*/
@@ -323,12 +312,8 @@ static void read_branches_file(struct remote *remote)
strbuf_addf(&branch, "refs/heads/%s", frag);
} else
strbuf_addstr(&branch, "refs/heads/master");
- if (!slash) {
- strbuf_addf(&branch, ":refs/heads/%s", remote->name);
- } else {
- strbuf_reset(&branch);
- strbuf_addstr(&branch, "HEAD:");
- }
+
+ strbuf_addf(&branch, ":refs/heads/%s", remote->name);
add_url_alias(remote, p);
add_fetch_refspec(remote, strbuf_detach(&branch, NULL));
/*
@@ -1317,6 +1302,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
free(sent_tips.tip);
}
+static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
+{
+ for ( ; ref; ref = ref->next)
+ string_list_append_nodup(ref_index, ref->name)->util = ref;
+
+ sort_string_list(ref_index);
+}
+
/*
* Given the set of refs the local repository has, the set of refs the
* remote repository has, and the refspec used for push, determine
@@ -1335,6 +1328,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
int errs;
static const char *default_refspec[] = { ":", NULL };
struct ref *ref, **dst_tail = tail_ref(dst);
+ struct string_list dst_ref_index = STRING_LIST_INIT_NODUP;
if (!nr_refspec) {
nr_refspec = 1;
@@ -1345,6 +1339,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
/* pick the remainder */
for (ref = src; ref; ref = ref->next) {
+ struct string_list_item *dst_item;
struct ref *dst_peer;
const struct refspec *pat = NULL;
char *dst_name;
@@ -1353,7 +1348,11 @@ int match_push_refs(struct ref *src, struct ref **dst,
if (!dst_name)
continue;
- dst_peer = find_ref_by_name(*dst, dst_name);
+ if (!dst_ref_index.nr)
+ prepare_ref_index(&dst_ref_index, *dst);
+
+ dst_item = string_list_lookup(&dst_ref_index, dst_name);
+ dst_peer = dst_item ? dst_item->util : NULL;
if (dst_peer) {
if (dst_peer->peer_ref)
/* We're already sending something to this ref. */
@@ -1370,6 +1369,8 @@ int match_push_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, ref->new_sha1);
+ string_list_insert(&dst_ref_index,
+ dst_peer->name)->util = dst_peer;
}
dst_peer->peer_ref = copy_ref(ref);
dst_peer->force = pat->force;
@@ -1377,10 +1378,13 @@ int match_push_refs(struct ref *src, struct ref **dst,
free(dst_name);
}
+ string_list_clear(&dst_ref_index, 0);
+
if (flags & MATCH_REFS_FOLLOW_TAGS)
add_missing_tags(src, dst, &dst_tail);
if (send_prune) {
+ struct string_list src_ref_index = STRING_LIST_INIT_NODUP;
/* check for missing refs on the remote */
for (ref = *dst; ref; ref = ref->next) {
char *src_name;
@@ -1391,11 +1395,15 @@ int match_push_refs(struct ref *src, struct ref **dst,
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))
+ if (!src_ref_index.nr)
+ prepare_ref_index(&src_ref_index, src);
+ if (!string_list_has_string(&src_ref_index,
+ src_name))
ref->peer_ref = alloc_delete_ref();
free(src_name);
}
}
+ string_list_clear(&src_ref_index, 0);
}
if (errs)
return -1;
@@ -1474,8 +1482,7 @@ struct branch *branch_get(const char *name)
ret->remote = remote_get(ret->remote_name);
if (ret->merge_nr) {
int i;
- ret->merge = xcalloc(sizeof(*ret->merge),
- ret->merge_nr);
+ ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
for (i = 0; i < ret->merge_nr; i++) {
ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
ret->merge[i]->src = xstrdup(ret->merge_name[i]);
diff --git a/revision.c b/revision.c
index a67b615..2f0142f 100644
--- a/revision.c
+++ b/revision.c
@@ -13,6 +13,7 @@
#include "decorate.h"
#include "log-tree.h"
#include "string-list.h"
+#include "line-log.h"
#include "mailmap.h"
volatile show_early_output_fn_t show_early_output;
@@ -70,7 +71,8 @@ static int show_path_truncated(FILE *out, const struct name_path *path)
return ours || emitted;
}
-void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component)
+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;
@@ -87,7 +89,9 @@ void add_object(struct object *obj,
struct name_path *path,
const char *name)
{
- add_object_array(obj, path_name(path, name), p);
+ char *pn = path_name(path, name);
+ add_object_array(obj, pn, p);
+ free(pn);
}
static void mark_blob_uninteresting(struct blob *blob)
@@ -186,7 +190,9 @@ void mark_parents_uninteresting(struct commit *commit)
}
}
-static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
+static void add_pending_object_with_mode(struct rev_info *revs,
+ struct object *obj,
+ const char *name, unsigned mode)
{
if (!obj)
return;
@@ -209,7 +215,8 @@ static void add_pending_object_with_mode(struct rev_info *revs, struct object *o
add_object_array_with_mode(obj, name, &revs->pending, mode);
}
-void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+void add_pending_object(struct rev_info *revs,
+ struct object *obj, const char *name)
{
add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
}
@@ -226,7 +233,9 @@ void add_head_to_pending(struct rev_info *revs)
add_pending_object(revs, obj, "HEAD");
}
-static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+static struct object *get_reference(struct rev_info *revs, const char *name,
+ const unsigned char *sha1,
+ unsigned int flags)
{
struct object *object;
@@ -247,7 +256,8 @@ void add_pending_sha1(struct rev_info *revs, const char *name,
add_pending_object(revs, object, name);
}
-static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+static struct commit *handle_commit(struct rev_info *revs,
+ struct object *object, const char *name)
{
unsigned long flags = object->flags;
@@ -333,6 +343,80 @@ static int everybody_uninteresting(struct commit_list *orig)
}
/*
+ * A definition of "relevant" commit that we can use to simplify limited graphs
+ * by eliminating side branches.
+ *
+ * A "relevant" commit is one that is !UNINTERESTING (ie we are including it
+ * in our list), or that is a specified BOTTOM commit. Then after computing
+ * a limited list, during processing we can generally ignore boundary merges
+ * coming from outside the graph, (ie from irrelevant parents), and treat
+ * those merges as if they were single-parent. TREESAME is defined to consider
+ * only relevant parents, if any. If we are TREESAME to our on-graph parents,
+ * we don't care if we were !TREESAME to non-graph parents.
+ *
+ * Treating bottom commits as relevant ensures that a limited graph's
+ * connection to the actual bottom commit is not viewed as a side branch, but
+ * treated as part of the graph. For example:
+ *
+ * ....Z...A---X---o---o---B
+ * . /
+ * W---Y
+ *
+ * When computing "A..B", the A-X connection is at least as important as
+ * Y-X, despite A being flagged UNINTERESTING.
+ *
+ * And when computing --ancestry-path "A..B", the A-X connection is more
+ * important than Y-X, despite both A and Y being flagged UNINTERESTING.
+ */
+static inline int relevant_commit(struct commit *commit)
+{
+ return (commit->object.flags & (UNINTERESTING | BOTTOM)) != UNINTERESTING;
+}
+
+/*
+ * Return a single relevant commit from a parent list. If we are a TREESAME
+ * commit, and this selects one of our parents, then we can safely simplify to
+ * that parent.
+ */
+static struct commit *one_relevant_parent(const struct rev_info *revs,
+ struct commit_list *orig)
+{
+ struct commit_list *list = orig;
+ struct commit *relevant = NULL;
+
+ if (!orig)
+ return NULL;
+
+ /*
+ * For 1-parent commits, or if first-parent-only, then return that
+ * first parent (even if not "relevant" by the above definition).
+ * TREESAME will have been set purely on that parent.
+ */
+ if (revs->first_parent_only || !orig->next)
+ return orig->item;
+
+ /*
+ * For multi-parent commits, identify a sole relevant parent, if any.
+ * If we have only one relevant parent, then TREESAME will be set purely
+ * with regard to that parent, and we can simplify accordingly.
+ *
+ * If we have more than one relevant parent, or no relevant parents
+ * (and multiple irrelevant ones), then we can't select a parent here
+ * and return NULL.
+ */
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (relevant_commit(commit)) {
+ if (relevant)
+ return NULL;
+ relevant = commit;
+ }
+ }
+ return relevant;
+}
+
+/*
* The goal is to get REV_TREE_NEW as the result only if the
* diff consists of all '+' (and no other changes), REV_TREE_OLD
* if the whole diff is removal of old data, and otherwise
@@ -368,7 +452,8 @@ static void file_change(struct diff_options *options,
DIFF_OPT_SET(options, HAS_CHANGES);
}
-static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct commit *commit)
+static int rev_compare_tree(struct rev_info *revs,
+ struct commit *parent, struct commit *commit)
{
struct tree *t1 = parent->tree;
struct tree *t2 = commit->tree;
@@ -429,10 +514,125 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
return retval >= 0 && (tree_difference == REV_TREE_SAME);
}
+struct treesame_state {
+ unsigned int nparents;
+ unsigned char treesame[FLEX_ARRAY];
+};
+
+static struct treesame_state *initialise_treesame(struct rev_info *revs, struct commit *commit)
+{
+ unsigned n = commit_list_count(commit->parents);
+ struct treesame_state *st = xcalloc(1, sizeof(*st) + n);
+ st->nparents = n;
+ add_decoration(&revs->treesame, &commit->object, st);
+ return st;
+}
+
+/*
+ * Must be called immediately after removing the nth_parent from a commit's
+ * parent list, if we are maintaining the per-parent treesame[] decoration.
+ * This does not recalculate the master TREESAME flag - update_treesame()
+ * should be called to update it after a sequence of treesame[] modifications
+ * that may have affected it.
+ */
+static int compact_treesame(struct rev_info *revs, struct commit *commit, unsigned nth_parent)
+{
+ struct treesame_state *st;
+ int old_same;
+
+ if (!commit->parents) {
+ /*
+ * Have just removed the only parent from a non-merge.
+ * Different handling, as we lack decoration.
+ */
+ if (nth_parent != 0)
+ die("compact_treesame %u", nth_parent);
+ old_same = !!(commit->object.flags & TREESAME);
+ if (rev_same_tree_as_empty(revs, commit))
+ commit->object.flags |= TREESAME;
+ else
+ commit->object.flags &= ~TREESAME;
+ return old_same;
+ }
+
+ st = lookup_decoration(&revs->treesame, &commit->object);
+ if (!st || nth_parent >= st->nparents)
+ die("compact_treesame %u", nth_parent);
+
+ old_same = st->treesame[nth_parent];
+ memmove(st->treesame + nth_parent,
+ st->treesame + nth_parent + 1,
+ st->nparents - nth_parent - 1);
+
+ /*
+ * If we've just become a non-merge commit, update TREESAME
+ * immediately, and remove the no-longer-needed decoration.
+ * If still a merge, defer update until update_treesame().
+ */
+ if (--st->nparents == 1) {
+ if (commit->parents->next)
+ die("compact_treesame parents mismatch");
+ if (st->treesame[0] && revs->dense)
+ commit->object.flags |= TREESAME;
+ else
+ commit->object.flags &= ~TREESAME;
+ free(add_decoration(&revs->treesame, &commit->object, NULL));
+ }
+
+ return old_same;
+}
+
+static unsigned update_treesame(struct rev_info *revs, struct commit *commit)
+{
+ if (commit->parents && commit->parents->next) {
+ unsigned n;
+ struct treesame_state *st;
+ struct commit_list *p;
+ unsigned relevant_parents;
+ unsigned relevant_change, irrelevant_change;
+
+ st = lookup_decoration(&revs->treesame, &commit->object);
+ if (!st)
+ die("update_treesame %s", sha1_to_hex(commit->object.sha1));
+ relevant_parents = 0;
+ relevant_change = irrelevant_change = 0;
+ for (p = commit->parents, n = 0; p; n++, p = p->next) {
+ if (relevant_commit(p->item)) {
+ relevant_change |= !st->treesame[n];
+ relevant_parents++;
+ } else
+ irrelevant_change |= !st->treesame[n];
+ }
+ if (relevant_parents ? relevant_change : irrelevant_change)
+ commit->object.flags &= ~TREESAME;
+ else
+ commit->object.flags |= TREESAME;
+ }
+
+ return commit->object.flags & TREESAME;
+}
+
+static inline int limiting_can_increase_treesame(const struct rev_info *revs)
+{
+ /*
+ * TREESAME is irrelevant unless prune && dense;
+ * if simplify_history is set, we can't have a mixture of TREESAME and
+ * !TREESAME INTERESTING parents (and we don't have treesame[]
+ * decoration anyway);
+ * if first_parent_only is set, then the TREESAME flag is locked
+ * against the first parent (and again we lack treesame[] decoration).
+ */
+ return revs->prune && revs->dense &&
+ !revs->simplify_history &&
+ !revs->first_parent_only;
+}
+
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, nth_parent = 0;
+ struct treesame_state *ts = NULL;
+ int relevant_change = 0, irrelevant_change = 0;
+ int relevant_parents, nth_parent;
/*
* If we don't do pruning, everything is interesting
@@ -456,33 +656,54 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
if (!revs->dense && !commit->parents->next)
return;
- pp = &commit->parents;
- while ((parent = *pp) != NULL) {
+ for (pp = &commit->parents, nth_parent = 0, relevant_parents = 0;
+ (parent = *pp) != NULL;
+ pp = &parent->next, nth_parent++) {
struct commit *p = parent->item;
+ if (relevant_commit(p))
+ relevant_parents++;
- /*
- * 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 (nth_parent == 1) {
+ /*
+ * This our second loop iteration - so we now know
+ * we're dealing with a merge.
+ *
+ * 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)
+ break;
+ /*
+ * If this will remain a potentially-simplifiable
+ * merge, remember per-parent treesame if needed.
+ * Initialise the array with the comparison from our
+ * first iteration.
+ */
+ if (revs->treesame.name &&
+ !revs->simplify_history &&
+ !(commit->object.flags & UNINTERESTING)) {
+ ts = initialise_treesame(revs, commit);
+ if (!(irrelevant_change || relevant_change))
+ ts->treesame[0] = 1;
+ }
+ }
if (parse_commit(p) < 0)
die("cannot simplify commit %s (because of %s)",
sha1_to_hex(commit->object.sha1),
sha1_to_hex(p->object.sha1));
switch (rev_compare_tree(revs, p, commit)) {
case REV_TREE_SAME:
- tree_same = 1;
- if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
+ if (!revs->simplify_history || !relevant_commit(p)) {
/* Even if a merge with an uninteresting
* side branch brought the entire change
* we are interested in, we do not want
* to lose the other branches of this
* merge, so we just keep going.
*/
- pp = &parent->next;
+ if (ts)
+ ts->treesame[nth_parent] = 1;
continue;
}
parent->next = NULL;
@@ -510,15 +731,27 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
/* fallthrough */
case REV_TREE_OLD:
case REV_TREE_DIFFERENT:
- tree_changed = 1;
- pp = &parent->next;
+ if (relevant_commit(p))
+ relevant_change = 1;
+ else
+ irrelevant_change = 1;
continue;
}
die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
}
- if (tree_changed && !tree_same)
- return;
- commit->object.flags |= TREESAME;
+
+ /*
+ * TREESAME is straightforward for single-parent commits. For merge
+ * commits, it is most useful to define it so that "irrelevant"
+ * parents cannot make us !TREESAME - if we have any relevant
+ * parents, then we only consider TREESAMEness with respect to them,
+ * allowing irrelevant merges from uninteresting branches to be
+ * simplified away. Only if we have only irrelevant parents do we
+ * base TREESAME on them. Note that this logic is replicated in
+ * update_treesame, which should be kept in sync.
+ */
+ if (relevant_parents ? !relevant_change : !irrelevant_change)
+ commit->object.flags |= TREESAME;
}
static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,
@@ -801,16 +1034,12 @@ 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 rev_info *revs)
+static struct commit_list *collect_bottom_commits(struct commit_list *list)
{
- 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);
- }
+ struct commit_list *elem, *bottom = NULL;
+ for (elem = list; elem; elem = elem->next)
+ if (elem->item->object.flags & BOTTOM)
+ commit_list_insert(elem->item, &bottom);
return bottom;
}
@@ -841,7 +1070,7 @@ static int limit_list(struct rev_info *revs)
struct commit_list *bottom = NULL;
if (revs->ancestry_path) {
- bottom = collect_bottom_commits(revs);
+ bottom = collect_bottom_commits(list);
if (!bottom)
die("--ancestry-path given but there are no bottom commits");
}
@@ -894,10 +1123,26 @@ static int limit_list(struct rev_info *revs)
free_commit_list(bottom);
}
+ /*
+ * Check if any commits have become TREESAME by some of their parents
+ * becoming UNINTERESTING.
+ */
+ if (limiting_can_increase_treesame(revs))
+ for (list = newlist; list; list = list->next) {
+ struct commit *c = list->item;
+ if (c->object.flags & (UNINTERESTING | TREESAME))
+ continue;
+ update_treesame(revs, c);
+ }
+
revs->commits = newlist;
return 0;
}
+/*
+ * Add an entry to refs->cmdline with the specified information.
+ * *name is copied.
+ */
static void add_rev_cmdline(struct rev_info *revs,
struct object *item,
const char *name,
@@ -909,12 +1154,25 @@ static void add_rev_cmdline(struct rev_info *revs,
ALLOC_GROW(info->rev, nr + 1, info->alloc);
info->rev[nr].item = item;
- info->rev[nr].name = name;
+ info->rev[nr].name = xstrdup(name);
info->rev[nr].whence = whence;
info->rev[nr].flags = flags;
info->nr++;
}
+static void add_rev_cmdline_list(struct rev_info *revs,
+ struct commit_list *commit_list,
+ int whence,
+ unsigned flags)
+{
+ while (commit_list) {
+ struct object *object = &commit_list->item->object;
+ add_rev_cmdline(revs, object, sha1_to_hex(object->sha1),
+ whence, flags);
+ commit_list = commit_list->next;
+ }
+}
+
struct all_refs_cb {
int all_flags;
int warned_bad_reflog;
@@ -1000,7 +1258,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
const char *arg = arg_;
if (*arg == '^') {
- flags ^= UNINTERESTING;
+ flags ^= UNINTERESTING | BOTTOM;
arg++;
}
if (get_sha1_committish(arg, sha1))
@@ -1038,7 +1296,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
DIFF_OPT_SET(&revs->pruning, QUICK);
revs->pruning.add_remove = file_add_remove;
revs->pruning.change = file_change;
- revs->lifo = 1;
+ revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
revs->dense = 1;
revs->prefix = prefix;
revs->max_age = -1;
@@ -1092,7 +1350,8 @@ static void prepare_show_merge(struct rev_info *revs)
add_pending_object(revs, &head->object, "HEAD");
add_pending_object(revs, &other->object, "MERGE_HEAD");
bases = get_merge_bases(head, other, 1);
- add_pending_commit_list(revs, bases, UNINTERESTING);
+ add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);
+ add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);
free_commit_list(bases);
head->object.flags |= SYMMETRIC_LEFT;
@@ -1128,13 +1387,15 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
unsigned get_sha1_flags = 0;
+ flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
+
dotdot = strstr(arg, "..");
if (dotdot) {
unsigned char from_sha1[20];
const char *next = dotdot + 2;
const char *this = arg;
int symmetric = *next == '.';
- unsigned int flags_exclude = flags ^ UNINTERESTING;
+ unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
static const char head_by_default[] = "HEAD";
unsigned int a_flags;
@@ -1179,6 +1440,9 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
if (symmetric) {
exclude = get_merge_bases(a, b, 1);
+ add_rev_cmdline_list(revs, exclude,
+ REV_CMD_MERGE_BASE,
+ flags_exclude);
add_pending_commit_list(revs, exclude,
flags_exclude);
free_commit_list(exclude);
@@ -1207,13 +1471,13 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
dotdot = strstr(arg, "^!");
if (dotdot && !dotdot[2]) {
*dotdot = 0;
- if (!add_parents_only(revs, arg, flags ^ UNINTERESTING))
+ if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM)))
*dotdot = '^';
}
local_flags = 0;
if (*arg == '^') {
- local_flags = UNINTERESTING;
+ local_flags = UNINTERESTING | BOTTOM;
arg++;
}
@@ -1276,7 +1540,7 @@ static void read_revisions_from_stdin(struct rev_info *revs,
}
die("options not supported in --stdin mode");
}
- if (handle_revision_arg(xstrdup(sb.buf), revs, 0,
+ if (handle_revision_arg(sb.buf, revs, 0,
REVARG_CANNOT_BE_FILENAME))
die("bad revision '%s'", sb.buf);
}
@@ -1374,7 +1638,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--merge")) {
revs->show_merge = 1;
} else if (!strcmp(arg, "--topo-order")) {
- revs->lifo = 1;
+ revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
revs->topo_order = 1;
} else if (!strcmp(arg, "--simplify-merges")) {
revs->simplify_merges = 1;
@@ -1392,7 +1656,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->prune = 1;
load_ref_decorations(DECORATE_SHORT_REFS);
} else if (!strcmp(arg, "--date-order")) {
- revs->lifo = 0;
+ revs->sort_order = REV_SORT_BY_COMMIT_DATE;
+ revs->topo_order = 1;
+ } else if (!strcmp(arg, "--author-date-order")) {
+ revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
revs->topo_order = 1;
} else if (!prefixcmp(arg, "--early-output")) {
int count = 100;
@@ -1690,7 +1957,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
} else if (!strcmp(arg, "--bisect")) {
handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
- handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref);
+ handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);
revs->bisect = 1;
} else if (!strcmp(arg, "--tags")) {
handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
@@ -1716,7 +1983,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
} else if (!strcmp(arg, "--reflog")) {
handle_reflog(revs, *flags);
} else if (!strcmp(arg, "--not")) {
- *flags ^= UNINTERESTING;
+ *flags ^= UNINTERESTING | BOTTOM;
} else if (!strcmp(arg, "--no-walk")) {
revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
} else if (!prefixcmp(arg, "--no-walk=")) {
@@ -1897,6 +2164,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (revs->combine_merges)
revs->ignore_merges = 0;
revs->diffopt.abbrev = revs->abbrev;
+
+ if (revs->line_level_traverse) {
+ revs->limited = 1;
+ revs->topo_order = 1;
+ }
+
diff_setup_done(&revs->diffopt);
grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
@@ -1930,28 +2203,32 @@ static void add_child(struct rev_info *revs, struct commit *parent, struct commi
l->next = add_decoration(&revs->children, &parent->object, l);
}
-static int remove_duplicate_parents(struct commit *commit)
+static int remove_duplicate_parents(struct rev_info *revs, struct commit *commit)
{
+ struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
struct commit_list **pp, *p;
int surviving_parents;
/* Examine existing parents while marking ones we have seen... */
pp = &commit->parents;
+ surviving_parents = 0;
while ((p = *pp) != NULL) {
struct commit *parent = p->item;
if (parent->object.flags & TMP_MARK) {
*pp = p->next;
+ if (ts)
+ compact_treesame(revs, commit, surviving_parents);
continue;
}
parent->object.flags |= TMP_MARK;
+ surviving_parents++;
pp = &p->next;
}
- /* count them while clearing the temporary mark */
- surviving_parents = 0;
+ /* clear the temporary mark */
for (p = commit->parents; p; p = p->next) {
p->item->object.flags &= ~TMP_MARK;
- surviving_parents++;
}
+ /* no update_treesame() - removing duplicates can't affect TREESAME */
return surviving_parents;
}
@@ -1971,9 +2248,157 @@ static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs,
return st;
}
+static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list *h = reduce_heads(commit->parents);
+ int i = 0, marked = 0;
+ struct commit_list *po, *pn;
+
+ /* Want these for sanity-checking only */
+ int orig_cnt = commit_list_count(commit->parents);
+ int cnt = commit_list_count(h);
+
+ /*
+ * Not ready to remove items yet, just mark them for now, based
+ * on the output of reduce_heads(). reduce_heads outputs the reduced
+ * set in its original order, so this isn't too hard.
+ */
+ po = commit->parents;
+ pn = h;
+ while (po) {
+ if (pn && po->item == pn->item) {
+ pn = pn->next;
+ i++;
+ } else {
+ po->item->object.flags |= TMP_MARK;
+ marked++;
+ }
+ po=po->next;
+ }
+
+ if (i != cnt || cnt+marked != orig_cnt)
+ die("mark_redundant_parents %d %d %d %d", orig_cnt, cnt, i, marked);
+
+ free_commit_list(h);
+
+ return marked;
+}
+
+static int mark_treesame_root_parents(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list *p;
+ int marked = 0;
+
+ for (p = commit->parents; p; p = p->next) {
+ struct commit *parent = p->item;
+ if (!parent->parents && (parent->object.flags & TREESAME)) {
+ parent->object.flags |= TMP_MARK;
+ marked++;
+ }
+ }
+
+ return marked;
+}
+
+/*
+ * Awkward naming - this means one parent we are TREESAME to.
+ * cf mark_treesame_root_parents: root parents that are TREESAME (to an
+ * empty tree). Better name suggestions?
+ */
+static int leave_one_treesame_to_parent(struct rev_info *revs, struct commit *commit)
+{
+ struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
+ struct commit *unmarked = NULL, *marked = NULL;
+ struct commit_list *p;
+ unsigned n;
+
+ for (p = commit->parents, n = 0; p; p = p->next, n++) {
+ if (ts->treesame[n]) {
+ if (p->item->object.flags & TMP_MARK) {
+ if (!marked)
+ marked = p->item;
+ } else {
+ if (!unmarked) {
+ unmarked = p->item;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * If we are TREESAME to a marked-for-deletion parent, but not to any
+ * unmarked parents, unmark the first TREESAME parent. This is the
+ * parent that the default simplify_history==1 scan would have followed,
+ * and it doesn't make sense to omit that path when asking for a
+ * simplified full history. Retaining it improves the chances of
+ * understanding odd missed merges that took an old version of a file.
+ *
+ * Example:
+ *
+ * I--------*X A modified the file, but mainline merge X used
+ * \ / "-s ours", so took the version from I. X is
+ * `-*A--' TREESAME to I and !TREESAME to A.
+ *
+ * Default log from X would produce "I". Without this check,
+ * --full-history --simplify-merges would produce "I-A-X", showing
+ * the merge commit X and that it changed A, but not making clear that
+ * it had just taken the I version. With this check, the topology above
+ * is retained.
+ *
+ * Note that it is possible that the simplification chooses a different
+ * TREESAME parent from the default, in which case this test doesn't
+ * activate, and we _do_ drop the default parent. Example:
+ *
+ * I------X A modified the file, but it was reverted in B,
+ * \ / meaning mainline merge X is TREESAME to both
+ * *A-*B parents.
+ *
+ * Default log would produce "I" by following the first parent;
+ * --full-history --simplify-merges will produce "I-A-B". But this is a
+ * reasonable result - it presents a logical full history leading from
+ * I to X, and X is not an important merge.
+ */
+ if (!unmarked && marked) {
+ marked->object.flags &= ~TMP_MARK;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int remove_marked_parents(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list **pp, *p;
+ int nth_parent, removed = 0;
+
+ pp = &commit->parents;
+ nth_parent = 0;
+ while ((p = *pp) != NULL) {
+ struct commit *parent = p->item;
+ if (parent->object.flags & TMP_MARK) {
+ parent->object.flags &= ~TMP_MARK;
+ *pp = p->next;
+ free(p);
+ removed++;
+ compact_treesame(revs, commit, nth_parent);
+ continue;
+ }
+ pp = &p->next;
+ nth_parent++;
+ }
+
+ /* Removing parents can only increase TREESAMEness */
+ if (removed && !(commit->object.flags & TREESAME))
+ update_treesame(revs, commit);
+
+ return nth_parent;
+}
+
static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
{
struct commit_list *p;
+ struct commit *parent;
struct merge_simplify_state *st, *pst;
int cnt;
@@ -2015,7 +2440,9 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
}
/*
- * Rewrite our list of parents.
+ * Rewrite our list of parents. Note that this cannot
+ * affect our TREESAME flags in any way - a commit is
+ * always TREESAME to its simplification.
*/
for (p = commit->parents; p; p = p->next) {
pst = locate_simplify_state(revs, p->item);
@@ -2027,43 +2454,53 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
if (revs->first_parent_only)
cnt = 1;
else
- cnt = remove_duplicate_parents(commit);
+ cnt = remove_duplicate_parents(revs, commit);
/*
* It is possible that we are a merge and one side branch
* does not have any commit that touches the given paths;
- * in such a case, the immediate parents will be rewritten
- * to different commits.
+ * in such a case, the immediate parent from that branch
+ * will be rewritten to be the merge base.
*
* o----X X: the commit we are looking at;
* / / o: a commit that touches the paths;
* ---o----'
*
- * Further reduce the parents by removing redundant parents.
+ * Further, a merge of an independent branch that doesn't
+ * touch the path will reduce to a treesame root parent:
+ *
+ * ----o----X X: the commit we are looking at;
+ * / o: a commit that touches the paths;
+ * r r: a root commit not touching the paths
+ *
+ * Detect and simplify both cases.
*/
if (1 < cnt) {
- struct commit_list *h = reduce_heads(commit->parents);
- cnt = commit_list_count(h);
- free_commit_list(commit->parents);
- commit->parents = h;
+ int marked = mark_redundant_parents(revs, commit);
+ marked += mark_treesame_root_parents(revs, commit);
+ if (marked)
+ marked -= leave_one_treesame_to_parent(revs, commit);
+ if (marked)
+ cnt = remove_marked_parents(revs, commit);
}
/*
* A commit simplifies to itself if it is a root, if it is
* UNINTERESTING, if it touches the given paths, or if it is a
- * merge and its parents simplifies to more than one commits
+ * merge and its parents don't simplify to one relevant commit
* (the first two cases are already handled at the beginning of
* this function).
*
- * Otherwise, it simplifies to what its sole parent simplifies to.
+ * Otherwise, it simplifies to what its sole relevant parent
+ * simplifies to.
*/
if (!cnt ||
(commit->object.flags & UNINTERESTING) ||
!(commit->object.flags & TREESAME) ||
- (1 < cnt))
+ (parent = one_relevant_parent(revs, commit->parents)) == NULL)
st->simplified = commit;
else {
- pst = locate_simplify_state(revs, commit->parents->item);
+ pst = locate_simplify_state(revs, parent);
st->simplified = pst->simplified;
}
return tail;
@@ -2159,6 +2596,11 @@ int prepare_revision_walk(struct rev_info *revs)
if (!revs->leak_pending)
free(list);
+ /* Signal whether we need per-parent treesame decoration */
+ if (revs->simplify_merges ||
+ (revs->limited && limiting_can_increase_treesame(revs)))
+ revs->treesame.name = "treesame";
+
if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
commit_list_sort_by_date(&revs->commits);
if (revs->no_walk)
@@ -2167,7 +2609,9 @@ int prepare_revision_walk(struct rev_info *revs)
if (limit_list(revs) < 0)
return -1;
if (revs->topo_order)
- sort_in_topological_order(&revs->commits, revs->lifo);
+ sort_in_topological_order(&revs->commits, revs->sort_order);
+ if (revs->line_level_traverse)
+ line_log_filter(revs);
if (revs->simplify_merges)
simplify_merges(revs);
if (revs->children.name)
@@ -2175,12 +2619,6 @@ int prepare_revision_walk(struct rev_info *revs)
return 0;
}
-enum rewrite_result {
- rewrite_one_ok,
- rewrite_one_noparents,
- rewrite_one_error
-};
-
static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
{
struct commit_list *cache = NULL;
@@ -2190,24 +2628,25 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
if (!revs->limited)
if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)
return rewrite_one_error;
- if (p->parents && p->parents->next)
- return rewrite_one_ok;
if (p->object.flags & UNINTERESTING)
return rewrite_one_ok;
if (!(p->object.flags & TREESAME))
return rewrite_one_ok;
if (!p->parents)
return rewrite_one_noparents;
- *pp = p->parents->item;
+ if ((p = one_relevant_parent(revs, p->parents)) == NULL)
+ return rewrite_one_ok;
+ *pp = p;
}
}
-static int rewrite_parents(struct rev_info *revs, struct commit *commit)
+int rewrite_parents(struct rev_info *revs, struct commit *commit,
+ rewrite_parent_fn_t rewrite_parent)
{
struct commit_list **pp = &commit->parents;
while (*pp) {
struct commit_list *parent = *pp;
- switch (rewrite_one(revs, &parent->item)) {
+ switch (rewrite_parent(revs, &parent->item)) {
case rewrite_one_ok:
break;
case rewrite_one_noparents:
@@ -2218,7 +2657,7 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)
}
pp = &parent->next;
}
- remove_duplicate_parents(commit);
+ remove_duplicate_parents(revs, commit);
return 0;
}
@@ -2342,10 +2781,7 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
if (revs->min_age != -1 && (commit->date > revs->min_age))
return commit_ignore;
if (revs->min_parents || (revs->max_parents >= 0)) {
- int n = 0;
- struct commit_list *p;
- for (p = commit->parents; p; p = p->next)
- n++;
+ int n = commit_list_count(commit->parents);
if ((n < revs->min_parents) ||
((revs->max_parents >= 0) && (n > revs->max_parents)))
return commit_ignore;
@@ -2355,12 +2791,23 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
if (revs->prune && revs->dense) {
/* Commit without changes? */
if (commit->object.flags & TREESAME) {
+ int n;
+ struct commit_list *p;
/* drop merges unless we want parenthood */
if (!want_ancestry(revs))
return commit_ignore;
- /* non-merge - always ignore it */
- if (!commit->parents || !commit->parents->next)
- return commit_ignore;
+ /*
+ * If we want ancestry, then need to keep any merges
+ * between relevant commits to tie together topology.
+ * For consistency with TREESAME and simplification
+ * use "relevant" here rather than just INTERESTING,
+ * to treat bottom commit(s) as part of the topology.
+ */
+ for (n = 0, p = commit->parents; p; p = p->next)
+ if (relevant_commit(p->item))
+ if (++n >= 2)
+ return commit_show;
+ return commit_ignore;
}
}
return commit_show;
@@ -2373,7 +2820,7 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
if (action == commit_show &&
!revs->show_all &&
revs->prune && revs->dense && want_ancestry(revs)) {
- if (rewrite_parents(revs, commit) < 0)
+ if (rewrite_parents(revs, commit, rewrite_one) < 0)
return commit_error;
}
return action;
@@ -2423,25 +2870,23 @@ static struct commit *get_revision_1(struct rev_info *revs)
return NULL;
}
-static void gc_boundary(struct object_array *array)
+/*
+ * Return true for entries that have not yet been shown. (This is an
+ * object_array_each_func_t.)
+ */
+static int entry_unshown(struct object_array_entry *entry, void *cb_data_unused)
{
- unsigned nr = array->nr;
- unsigned alloc = array->alloc;
- struct object_array_entry *objects = array->objects;
+ return !(entry->item->flags & SHOWN);
+}
- if (alloc <= nr) {
- unsigned i, j;
- for (i = j = 0; i < nr; i++) {
- if (objects[i].item->flags & SHOWN)
- continue;
- if (i != j)
- objects[j] = objects[i];
- j++;
- }
- for (i = j; i < nr; i++)
- objects[i].item = NULL;
- array->nr = j;
- }
+/*
+ * If array is on the verge of a realloc, garbage-collect any entries
+ * that have already been shown to try to free up some space.
+ */
+static void gc_boundary(struct object_array *array)
+{
+ if (array->nr == array->alloc)
+ object_array_filter(array, entry_unshown, NULL);
}
static void create_boundary_commit_list(struct rev_info *revs)
@@ -2482,7 +2927,7 @@ static void create_boundary_commit_list(struct rev_info *revs)
* If revs->topo_order is set, sort the boundary commits
* in topological order
*/
- sort_in_topological_order(&revs->commits, revs->lifo);
+ sort_in_topological_order(&revs->commits, revs->sort_order);
}
static struct commit *get_revision_internal(struct rev_info *revs)
diff --git a/revision.h b/revision.h
index 01bd2b7..95859ba 100644
--- a/revision.h
+++ b/revision.h
@@ -4,6 +4,7 @@
#include "parse-options.h"
#include "grep.h"
#include "notes.h"
+#include "commit.h"
#define SEEN (1u<<0)
#define UNINTERESTING (1u<<1)
@@ -15,7 +16,8 @@
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
#define PATCHSAME (1u<<9)
-#define ALL_REV_FLAGS ((1u<<10)-1)
+#define BOTTOM (1u<<10)
+#define ALL_REV_FLAGS ((1u<<11)-1)
#define DECORATE_SHORT_REFS 1
#define DECORATE_FULL_REFS 2
@@ -35,6 +37,7 @@ struct rev_cmdline_info {
REV_CMD_PARENTS_ONLY,
REV_CMD_LEFT,
REV_CMD_RIGHT,
+ REV_CMD_MERGE_BASE,
REV_CMD_REV
} whence;
unsigned flags;
@@ -60,6 +63,10 @@ struct rev_info {
const char *prefix;
const char *def;
struct pathspec prune_data;
+
+ /* topo-sort */
+ enum rev_sort_order sort_order;
+
unsigned int early_output:1,
ignore_missing:1;
@@ -70,7 +77,6 @@ struct rev_info {
show_all:1,
remove_empty_trees:1,
simplify_history:1,
- lifo:1,
topo_order:1,
simplify_merges:1,
simplify_by_decoration:1,
@@ -96,7 +102,8 @@ struct rev_info {
cherry_mark:1,
bisect:1,
ancestry_path:1,
- first_parent_only:1;
+ first_parent_only:1,
+ line_level_traverse:1;
/* Diff flags */
unsigned int diff:1,
@@ -137,6 +144,7 @@ struct rev_info {
int numbered_files;
int reroll_count;
char *message_id;
+ struct ident_split from_ident;
struct string_list *ref_message_ids;
int add_signoff;
const char *extra_headers;
@@ -167,6 +175,7 @@ struct rev_info {
struct reflog_walk_info *reflog_info;
struct decoration children;
struct decoration merge_simplification;
+ struct decoration treesame;
/* notes-specific options: which refs to show */
struct display_notes_opt notes_opt;
@@ -175,6 +184,9 @@ struct rev_info {
int count_left;
int count_right;
int count_same;
+
+ /* line level range that we are chasing */
+ struct decoration line_log_data;
};
#define REV_TREE_SAME 0
@@ -195,19 +207,23 @@ struct setup_revision_opt {
};
extern void init_revisions(struct rev_info *revs, const char *prefix);
-extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *);
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs,
+ struct setup_revision_opt *);
extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
- const struct option *options,
- const char * const usagestr[]);
+ const struct option *options,
+ const char * const usagestr[]);
#define REVARG_CANNOT_BE_FILENAME 01
#define REVARG_COMMITTISH 02
-extern int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt);
+extern int handle_revision_arg(const char *arg, struct rev_info *revs,
+ int flags, unsigned revarg_opt);
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);
-extern void put_revision_mark(const struct rev_info *revs, const struct commit *commit);
+extern char *get_revision_mark(const struct rev_info *revs,
+ const struct commit *commit);
+extern void put_revision_mark(const struct rev_info *revs,
+ const struct commit *commit);
extern void mark_parents_uninteresting(struct commit *commit);
extern void mark_tree_uninteresting(struct tree *tree);
@@ -220,15 +236,19 @@ 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 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_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 *);
@@ -238,7 +258,19 @@ enum commit_action {
commit_error
};
-extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);
-extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
+extern enum commit_action get_commit_action(struct rev_info *revs,
+ struct commit *commit);
+extern enum commit_action simplify_commit(struct rev_info *revs,
+ struct commit *commit);
+
+enum rewrite_result {
+ rewrite_one_ok,
+ rewrite_one_noparents,
+ rewrite_one_error
+};
+
+typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct commit **pp);
+extern int rewrite_parents(struct rev_info *revs, struct commit *commit,
+ rewrite_parent_fn_t rewrite_parent);
#endif
diff --git a/run-command.c b/run-command.c
index 1b32a12..1b7f88e 100644
--- a/run-command.c
+++ b/run-command.c
@@ -72,11 +72,14 @@ static inline void close_pair(int fd[2])
close(fd[1]);
}
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
static inline void dup_devnull(int to)
{
int fd = open("/dev/null", O_RDWR);
- dup2(fd, to);
+ if (fd < 0)
+ die_errno(_("open /dev/null failed"));
+ if (dup2(fd, to) < 0)
+ die_errno(_("dup2(%d,%d) failed"), fd, to);
close(fd);
}
#endif
@@ -159,7 +162,7 @@ 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
+#ifndef GIT_WINDOWS_NATIVE
nargv[nargc++] = SHELL_PATH;
#else
nargv[nargc++] = "sh";
@@ -182,7 +185,7 @@ static const char **prepare_shell_cmd(const char **argv)
return nargv;
}
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
static int execv_shell_cmd(const char **argv)
{
const char **nargv = prepare_shell_cmd(argv);
@@ -193,7 +196,7 @@ static int execv_shell_cmd(const char **argv)
}
#endif
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
static int child_err = 2;
static int child_notifier = -1;
@@ -334,7 +337,7 @@ fail_pipe:
trace_argv_printf(cmd->argv, "trace: run_command:");
fflush(NULL);
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
{
int notify_pipe[2];
if (pipe(notify_pipe))
diff --git a/sequencer.c b/sequencer.c
index ab6f8a7..425207a 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -270,15 +270,20 @@ static int error_dirty_index(struct replay_opts *opts)
}
static int fast_forward_to(const unsigned char *to, const unsigned char *from,
- int unborn)
+ int unborn, struct replay_opts *opts)
{
struct ref_lock *ref_lock;
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
read_cache();
if (checkout_fast_forward(from, to, 1))
exit(1); /* the callee should have complained already */
ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0);
- return write_ref_sha1(ref_lock, to, "cherry-pick");
+ strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
+ ret = write_ref_sha1(ref_lock, to, sb.buf);
+ strbuf_release(&sb);
+ return ret;
}
static int do_recursive_merge(struct commit *base, struct commit *next,
@@ -474,7 +479,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
char *defmsg = NULL;
struct strbuf msgbuf = STRBUF_INIT;
- int res, unborn = 0;
+ int res, unborn = 0, allow;
if (opts->no_commit) {
/*
@@ -523,7 +528,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
if (opts->allow_ff &&
((parent && !hashcmp(parent->object.sha1, head)) ||
(!parent && unborn)))
- return fast_forward_to(commit->object.sha1, head, unborn);
+ return fast_forward_to(commit->object.sha1, head, unborn, opts);
if (parent && parse_commit(parent) < 0)
/* TRANSLATORS: The first %s will be "revert" or
@@ -624,14 +629,18 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
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);
+ goto leave;
+ }
+
+ allow = allow_empty(opts, commit);
+ if (allow < 0) {
+ res = allow;
+ goto leave;
}
+ if (!opts->no_commit)
+ res = run_git_commit(defmsg, opts, allow);
+leave:
free_message(&msg);
free(defmsg);
diff --git a/sha1_file.c b/sha1_file.c
index 67e815b..4c2365f 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -36,6 +36,9 @@ static inline uintmax_t sz_fmt(size_t s) { return s; }
const unsigned char null_sha1[20];
+static const char *no_log_pack_access = "no_log_pack_access";
+static const char *log_pack_access;
+
/*
* This is meant to hold a *small* number of objects that you would
* want read_sha1_file() to be able to return, but yet you do not want
@@ -1694,7 +1697,8 @@ static int retry_bad_packed_offset(struct packed_git *p, off_t obj_offset)
#define POI_STACK_PREALLOC 64
static int packed_object_info(struct packed_git *p, off_t obj_offset,
- unsigned long *sizep, int *rtype)
+ unsigned long *sizep, int *rtype,
+ unsigned long *disk_sizep)
{
struct pack_window *w_curs = NULL;
unsigned long size;
@@ -1728,6 +1732,11 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset,
}
}
+ if (disk_sizep) {
+ struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
+ *disk_sizep = revidx[1].offset - obj_offset;
+ }
+
while (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
off_t base_offset;
/* Push the object we're going to leave behind */
@@ -1956,12 +1965,19 @@ static void write_pack_access_log(struct packed_git *p, off_t obj_offset)
{
static FILE *log_file;
+ if (!log_pack_access)
+ log_pack_access = getenv("GIT_TRACE_PACK_ACCESS");
+ if (!log_pack_access)
+ log_pack_access = no_log_pack_access;
+ if (log_pack_access == no_log_pack_access)
+ return;
+
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;
+ log_pack_access = no_log_pack_access;
return;
}
}
@@ -1992,7 +2008,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
int delta_stack_nr = 0, delta_stack_alloc = UNPACK_ENTRY_STACK_PREALLOC;
int base_from_cache = 0;
- if (log_pack_access)
+ if (log_pack_access != no_log_pack_access)
write_pack_access_log(p, obj_offset);
/* PHASE 1: drill down to the innermost base object */
@@ -2135,10 +2151,19 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
data = patch_delta(base, base_size,
delta_data, delta_size,
&size);
+
+ /*
+ * We could not apply the delta; warn the user, but keep going.
+ * Our failure will be noticed either in the next iteration of
+ * the loop, or if this is the final delta, in the caller when
+ * we return NULL. Those code paths will take care of making
+ * a more explicit warning and retrying with another copy of
+ * the object.
+ */
if (!data)
- die("failed to apply delta");
+ error("failed to apply delta");
- free (delta_data);
+ free(delta_data);
}
*final_type = type;
@@ -2338,7 +2363,8 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1,
}
-static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizep)
+static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizep,
+ unsigned long *disk_sizep)
{
int status;
unsigned long mapsize, size;
@@ -2348,7 +2374,9 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size
map = map_sha1_file(sha1, &mapsize);
if (!map)
- return error("unable to find %s", sha1_to_hex(sha1));
+ return -1;
+ if (disk_sizep)
+ *disk_sizep = mapsize;
if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
status = error("unable to unpack %s header",
sha1_to_hex(sha1));
@@ -2372,13 +2400,15 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
if (co) {
if (oi->sizep)
*(oi->sizep) = co->size;
+ if (oi->disk_sizep)
+ *(oi->disk_sizep) = 0;
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, oi->sizep);
+ status = sha1_loose_object_info(sha1, oi->sizep, oi->disk_sizep);
if (status >= 0) {
oi->whence = OI_LOOSE;
return status;
@@ -2390,7 +2420,8 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
return status;
}
- status = packed_object_info(e.p, e.offset, oi->sizep, &rtype);
+ status = packed_object_info(e.p, e.offset, oi->sizep, &rtype,
+ oi->disk_sizep);
if (status < 0) {
mark_bad_packed_object(e.p, sha1);
status = sha1_object_info_extended(sha1, oi);
@@ -2409,7 +2440,7 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
{
- struct object_info oi;
+ struct object_info oi = {0};
oi.sizep = sizep;
return sha1_object_info_extended(sha1, &oi);
diff --git a/sha1_name.c b/sha1_name.c
index 3820f28..1a75fac 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -241,7 +241,7 @@ static int disambiguate_committish_only(const unsigned char *sha1, void *cb_data
return 0;
/* We need to do this the hard way... */
- obj = deref_tag(lookup_object(sha1), NULL, 0);
+ obj = deref_tag(parse_object(sha1), NULL, 0);
if (obj && obj->type == OBJ_COMMIT)
return 1;
return 0;
@@ -431,22 +431,49 @@ static inline int upstream_mark(const char *string, int len)
}
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
+static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf);
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *warn_msg = "refname '%.*s' is ambiguous.";
+ static const char *object_name_msg = N_(
+ "Git normally never creates a ref that ends with 40 hex characters\n"
+ "because it will be ignored when you just specify 40-hex. These refs\n"
+ "may be created by mistake. For example,\n"
+ "\n"
+ " git checkout -b $br $(git rev-parse ...)\n"
+ "\n"
+ "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
+ "examine these refs and maybe delete them. Turn this message off by\n"
+ "running \"git config advice.object_name_warning false\"");
+ unsigned char tmp_sha1[20];
char *real_ref = NULL;
int refs_found = 0;
- int at, reflog_len;
-
- if (len == 40 && !get_sha1_hex(str, sha1))
+ int at, reflog_len, nth_prior = 0;
+
+ if (len == 40 && !get_sha1_hex(str, sha1)) {
+ refs_found = dwim_ref(str, len, tmp_sha1, &real_ref);
+ if (refs_found > 0 && warn_ambiguous_refs) {
+ warning(warn_msg, len, str);
+ if (advice_object_name_warning)
+ fprintf(stderr, "%s\n", _(object_name_msg));
+ }
+ free(real_ref);
return 0;
+ }
/* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
if (len && str[len-1] == '}') {
- for (at = len-2; at >= 0; at--) {
+ for (at = len-4; at >= 0; at--) {
if (str[at] == '@' && str[at+1] == '{') {
+ if (str[at+2] == '-') {
+ if (at != 0)
+ /* @{-N} not at start */
+ return -1;
+ nth_prior = 1;
+ continue;
+ }
if (!upstream_mark(str + at, len - at)) {
reflog_len = (len-1) - (at+2);
len = at;
@@ -460,20 +487,22 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (len && ambiguous_path(str, len))
return -1;
- if (!len && reflog_len) {
+ if (nth_prior) {
struct strbuf buf = STRBUF_INIT;
- int ret;
- /* try the @{-N} syntax for n-th checkout */
- ret = interpret_branch_name(str+at, &buf);
- if (ret > 0) {
- /* substitute this branch name and restart */
- return get_sha1_1(buf.buf, buf.len, sha1, 0);
- } else if (ret == 0) {
- return -1;
+ int detached;
+
+ if (interpret_nth_prior_checkout(str, &buf) > 0) {
+ detached = (buf.len == 40 && !get_sha1_hex(buf.buf, sha1));
+ strbuf_release(&buf);
+ if (detached)
+ return 0;
}
+ }
+
+ if (!len && reflog_len)
/* allow "@{...}" to mean the current branch reflog */
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
- } else if (reflog_len)
+ else if (reflog_len)
refs_found = dwim_log(str, len, sha1, &real_ref);
else
refs_found = dwim_ref(str, len, sha1, &real_ref);
@@ -481,7 +510,9 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (!refs_found)
return -1;
- if (warn_ambiguous_refs && refs_found > 1)
+ if (warn_ambiguous_refs &&
+ (refs_found > 1 ||
+ !get_short_sha1(str, len, tmp_sha1, GET_SHA1_QUIETLY)))
warning(warn_msg, len, str);
if (reflog_len) {
@@ -490,10 +521,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
unsigned long co_time;
int co_tz, co_cnt;
- /* a @{-N} placed anywhere except the start is an error */
- if (str[at+2] == '-')
- return -1;
-
/* Is it asking for N-th entry, or approxidate? */
for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
char ch = str[at+2+i];
@@ -517,12 +544,21 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
}
if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
&co_time, &co_tz, &co_cnt)) {
+ if (!len) {
+ if (!prefixcmp(real_ref, "refs/heads/")) {
+ str = real_ref + 11;
+ len = strlen(real_ref + 11);
+ } else {
+ /* detached HEAD */
+ str = "HEAD";
+ len = 4;
+ }
+ }
if (at_time)
warning("Log for '%.*s' only goes "
"back to %s.", len, str,
show_date(co_time, co_tz, DATE_RFC2822));
else {
- free(real_ref);
die("Log for '%.*s' only has %d entries.",
len, str, co_cnt);
}
@@ -966,6 +1002,38 @@ int get_sha1_mb(const char *name, unsigned char *sha1)
return st;
}
+/* parse @something syntax, when 'something' is not {.*} */
+static int interpret_empty_at(const char *name, int namelen, int len, struct strbuf *buf)
+{
+ if (len || name[1] == '{')
+ return -1;
+
+ strbuf_reset(buf);
+ strbuf_add(buf, "HEAD", 4);
+ return 1;
+}
+
+static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
+{
+ /* we have extra data, which might need further processing */
+ struct strbuf tmp = STRBUF_INIT;
+ int used = buf->len;
+ int ret;
+
+ strbuf_add(buf, name + len, namelen - len);
+ ret = interpret_branch_name(buf->buf, &tmp);
+ /* that data was not interpreted, remove our cruft */
+ if (ret < 0) {
+ strbuf_setlen(buf, used);
+ return len;
+ }
+ strbuf_reset(buf);
+ strbuf_addbuf(buf, &tmp);
+ strbuf_release(&tmp);
+ /* tweak for size of {-N} versus expanded ref name */
+ return ret - used + len;
+}
+
/*
* This reads short-hand syntax that not only evaluates to a commit
* object name, but also can act as if the end user spelled the name
@@ -995,36 +1063,27 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
int len = interpret_nth_prior_checkout(name, buf);
int tmp_len;
- if (!len)
+ if (!len) {
return len; /* syntax Ok, not enough switches */
- if (0 < len && len == namelen)
- return len; /* consumed all */
- else if (0 < len) {
- /* we have extra data, which might need further processing */
- struct strbuf tmp = STRBUF_INIT;
- int used = buf->len;
- int ret;
-
- strbuf_add(buf, name + len, namelen - len);
- ret = interpret_branch_name(buf->buf, &tmp);
- /* that data was not interpreted, remove our cruft */
- if (ret < 0) {
- strbuf_setlen(buf, used);
- return len;
- }
- strbuf_reset(buf);
- strbuf_addbuf(buf, &tmp);
- strbuf_release(&tmp);
- /* tweak for size of {-N} versus expanded ref name */
- return ret - used + len;
+ } else if (len > 0) {
+ if (len == namelen)
+ return len; /* consumed all */
+ else
+ return reinterpret(name, namelen, len, buf);
}
cp = strchr(name, '@');
if (!cp)
return -1;
+
+ len = interpret_empty_at(name, namelen, cp - name, buf);
+ if (len > 0)
+ return reinterpret(name, namelen, len, buf);
+
tmp_len = upstream_mark(cp, namelen - (cp - name));
if (!tmp_len)
return -1;
+
len = cp + tmp_len - name;
cp = xstrndup(name, cp - name);
upstream = branch_get(*cp ? cp : NULL);
@@ -1033,14 +1092,15 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
* points to something different than a branch.
*/
if (!upstream)
- return error(_("HEAD does not point to a branch"));
+ die(_("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(
+ die(_("No such branch: '%s'"), cp);
+ if (!upstream->merge) {
+ die(_("No upstream configured for branch '%s'"),
+ upstream->name);
+ }
+ die(
_("Upstream branch '%s' not stored as a remote-tracking branch"),
upstream->merge[0]->src);
}
@@ -1055,9 +1115,13 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
int strbuf_branchname(struct strbuf *sb, const char *name)
{
int len = strlen(name);
- if (interpret_branch_name(name, sb) == len)
+ int used = interpret_branch_name(name, sb);
+
+ if (used == len)
return 0;
- strbuf_add(sb, name, len);
+ if (used < 0)
+ used = 0;
+ strbuf_add(sb, name + used, len - used);
return len;
}
diff --git a/shallow.c b/shallow.c
index 6be915f..cbe2526 100644
--- a/shallow.c
+++ b/shallow.c
@@ -3,6 +3,16 @@
#include "tag.h"
static int is_shallow = -1;
+static struct stat shallow_stat;
+static char *alternate_shallow_file;
+
+void set_alternate_shallow_file(const char *path)
+{
+ if (is_shallow != -1)
+ die("BUG: is_repository_shallow must not be called before set_alternate_shallow_file");
+ free(alternate_shallow_file);
+ alternate_shallow_file = path ? xstrdup(path) : NULL;
+}
int register_shallow(const unsigned char *sha1)
{
@@ -21,12 +31,21 @@ int is_repository_shallow(void)
{
FILE *fp;
char buf[1024];
+ const char *path = alternate_shallow_file;
if (is_shallow >= 0)
return is_shallow;
- fp = fopen(git_path("shallow"), "r");
- if (!fp) {
+ if (!path)
+ path = git_path("shallow");
+ /*
+ * fetch-pack sets '--shallow-file ""' as an indicator that no
+ * shallow file should be used. We could just open it and it
+ * will likely fail. But let's do an explicit check instead.
+ */
+ if (!*path ||
+ stat(path, &shallow_stat) ||
+ (fp = fopen(path, "r")) == NULL) {
is_shallow = 0;
return is_shallow;
}
@@ -108,3 +127,22 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
return result;
}
+
+void check_shallow_file_for_update(void)
+{
+ struct stat st;
+
+ if (!is_shallow)
+ return;
+ else if (is_shallow == -1)
+ die("BUG: shallow must be initialized by now");
+
+ if (stat(git_path("shallow"), &st))
+ die("shallow file was removed during fetch");
+ else if (st.st_mtime != shallow_stat.st_mtime
+#ifdef USE_NSEC
+ || ST_MTIME_NSEC(st) != ST_MTIME_NSEC(shallow_stat)
+#endif
+ )
+ die("shallow file was changed during fetch");
+}
diff --git a/streaming.c b/streaming.c
index cabcd9d..cac282f 100644
--- a/streaming.c
+++ b/streaming.c
@@ -135,7 +135,7 @@ struct git_istream *open_istream(const unsigned char *sha1,
struct stream_filter *filter)
{
struct git_istream *st;
- struct object_info oi;
+ struct object_info oi = {0};
const unsigned char *real = lookup_replace_object(sha1);
enum input_source src = istream_source(real, type, &oi);
diff --git a/submodule.c b/submodule.c
index e728025..85415d0 100644
--- a/submodule.c
+++ b/submodule.c
@@ -226,6 +226,7 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f,
while ((commit = get_revision(rev))) {
struct pretty_print_context ctx = {0};
ctx.date_mode = rev->date_mode;
+ ctx.output_encoding = get_log_output_encoding();
strbuf_setlen(&sb, 0);
strbuf_addstr(&sb, line_prefix);
if (commit->object.flags & SYMMETRIC_LEFT) {
@@ -603,9 +604,8 @@ int fetch_populated_submodules(const struct argv_array *options,
if (!work_tree)
goto out;
- if (!the_index.initialized)
- if (read_cache() < 0)
- die("index file corrupt");
+ if (read_cache() < 0)
+ die("index file corrupt");
argv_array_push(&argv, "fetch");
for (i = 0; i < options->argc; i++)
@@ -846,7 +846,7 @@ static int find_first_merges(struct object_array *result, const char *path,
struct commit *a, struct commit *b)
{
int i, j;
- struct object_array merges;
+ struct object_array merges = OBJECT_ARRAY_INIT;
struct commit *commit;
int contains_another;
@@ -856,7 +856,6 @@ static int find_first_merges(struct object_array *result, const char *path,
struct rev_info revs;
struct setup_revision_opt rev_opts;
- memset(&merges, 0, sizeof(merges));
memset(result, 0, sizeof(struct object_array));
memset(&rev_opts, 0, sizeof(rev_opts));
@@ -894,8 +893,7 @@ static int find_first_merges(struct object_array *result, const char *path,
}
if (!contains_another)
- add_object_array(merges.objects[i].item,
- merges.objects[i].name, result);
+ add_object_array(merges.objects[i].item, NULL, result);
}
free(merges.objects);
diff --git a/t/Makefile b/t/Makefile
index 44ca7d3..2373a04 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -15,9 +15,16 @@ PROVE ?= prove
DEFAULT_TEST_TARGET ?= test
TEST_LINT ?= test-lint-duplicates test-lint-executable
+ifdef TEST_OUTPUT_DIRECTORY
+TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results
+else
+TEST_RESULTS_DIRECTORY = test-results
+endif
+
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY))
T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh))
@@ -36,10 +43,10 @@ $(T):
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
pre-clean:
- $(RM) -r test-results
+ $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'
clean-except-prove-cache:
- $(RM) -r 'trash directory'.* test-results
+ $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'
$(RM) -r valgrind/bin
clean: clean-except-prove-cache
@@ -65,7 +72,7 @@ aggregate-results-and-cleanup: $(T)
$(MAKE) clean
aggregate-results:
- for f in test-results/t*-*.counts; do \
+ for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \
echo "$$f"; \
done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh
diff --git a/t/README b/t/README
index e669bb3..2167125 100644
--- a/t/README
+++ b/t/README
@@ -76,6 +76,11 @@ appropriately before running "make".
command being run and their output if any are also
output.
+--verbose-only=<pattern>::
+ Like --verbose, but the effect is limited to tests with
+ numbers matching <pattern>. The number matched against is
+ simply the running count of the test within the file.
+
--debug::
This may help the person who is developing a new test.
It causes the command defined with test_debug to run.
@@ -121,6 +126,11 @@ appropriately before running "make".
the 't/valgrind/' directory and use the commands under
't/valgrind/bin/'.
+--valgrind-only=<pattern>::
+ Like --valgrind, but the effect is limited to tests with
+ numbers matching <pattern>. The number matched against is
+ simply the running count of the test within the file.
+
--tee::
In addition to printing the test output to the terminal,
write it to files named 't/test-results/$TEST_NAME.out'.
@@ -324,6 +334,9 @@ Don't:
use 'test_must_fail git cmd'. This will signal a failure if git
dies in an unexpected way (e.g. segfault).
+ On the other hand, don't use test_must_fail for running regular
+ platform commands; just use '! cmd'.
+
- 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
@@ -592,6 +605,20 @@ library for your script to use.
test_cmp expected actual
'
+ - test_ln_s_add <path1> <path2>
+
+ This function helps systems whose filesystem does not support symbolic
+ links. Use it to add a symbolic link entry to the index when it is not
+ important that the file system entry is a symbolic link, i.e., instead
+ of the sequence
+
+ ln -s foo bar &&
+ git add bar
+
+ Sometimes it is possible to split a test in a part that does not need
+ the symbolic link in the file system and a part that does; then only
+ the latter part need be protected by a SYMLINKS prerequisite (see below).
+
Prerequisites
-------------
diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl
index 8b5a71d..45971f4 100755
--- a/t/check-non-portable-shell.pl
+++ b/t/check-non-portable-shell.pl
@@ -21,6 +21,7 @@ while (<>) {
/^\s*declare\s+/ and err 'arrays/declare not portable';
/^\s*[^#]\s*which\s/ and err 'which is not portable (please use type)';
/test\s+[^=]*==/ and err '"test a == b" is not portable (please use =)';
+ /^\s*export\s+[^=]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)';
# this resets our $. for each file
close ARGV if eof;
}
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index b5bce45..dd17e3a 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -1,5 +1,4 @@
ServerName dummy
-LockFile accept.lock
PidFile httpd.pid
DocumentRoot www
LogFormat "%h %l %u %t \"%r\" %>s %b" common
@@ -24,6 +23,10 @@ ErrorLog error.log
LoadModule version_module modules/mod_version.so
</IfModule>
+<IfVersion < 2.4>
+LockFile accept.lock
+</IfVersion>
+
<IfVersion < 2.1>
<IfModule !mod_auth.c>
LoadModule auth_module modules/mod_auth.so
@@ -45,6 +48,21 @@ ErrorLog error.log
</IfModule>
</IfVersion>
+<IfVersion >= 2.4>
+<IfModule !mod_authn_core.c>
+ LoadModule authn_core_module modules/mod_authn_core.so
+</IfModule>
+<IfModule !mod_authz_core.c>
+ LoadModule authz_core_module modules/mod_authz_core.so
+</IfModule>
+<IfModule !mod_access_compat.c>
+ LoadModule access_compat_module modules/mod_access_compat.so
+</IfModule>
+<IfModule !mod_mpm_prefork.c>
+ LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
+</IfModule>
+</IfVersion>
+
PassEnv GIT_VALGRIND
PassEnv GIT_VALGRIND_OPTIONS
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index 6ccf797..8ff87fb 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -17,51 +17,98 @@
# ("squash", "fixup", "edit", or "reword") and the SHA1 taken
# from the specified line.
#
+# "exec_cmd_with_args" -- add an "exec cmd with args" line.
+#
# "#" -- Add a comment line.
#
# ">" -- Add a blank line.
set_fake_editor () {
- echo "#!$SHELL_PATH" >fake-editor.sh
- cat >> fake-editor.sh <<\EOF
-case "$1" in
-*/COMMIT_EDITMSG)
- test -z "$EXPECT_HEADER_COUNT" ||
- test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" ||
+ write_script fake-editor.sh <<-\EOF
+ case "$1" in
+ */COMMIT_EDITMSG)
+ test -z "$EXPECT_HEADER_COUNT" ||
+ test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" ||
+ exit
+ test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
+ test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
exit
- test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
- test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
- exit
- ;;
-esac
-test -z "$EXPECT_COUNT" ||
- test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
- exit
-test -z "$FAKE_LINES" && exit
-grep -v '^#' < "$1" > "$1".tmp
-rm -f "$1"
-echo 'rebase -i script before editing:'
-cat "$1".tmp
-action=pick
-for line in $FAKE_LINES; do
- case $line in
- squash|fixup|edit|reword)
- action="$line";;
- exec*)
- echo "$line" | sed 's/_/ /g' >> "$1";;
- "#")
- echo '# comment' >> "$1";;
- ">")
- echo >> "$1";;
- *)
- sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
- action=pick;;
+ ;;
esac
-done
-echo 'rebase -i script after editing:'
-cat "$1"
-EOF
+ test -z "$EXPECT_COUNT" ||
+ test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
+ exit
+ test -z "$FAKE_LINES" && exit
+ grep -v '^#' < "$1" > "$1".tmp
+ rm -f "$1"
+ echo 'rebase -i script before editing:'
+ cat "$1".tmp
+ action=pick
+ for line in $FAKE_LINES; do
+ case $line in
+ squash|fixup|edit|reword)
+ action="$line";;
+ exec*)
+ echo "$line" | sed 's/_/ /g' >> "$1";;
+ "#")
+ echo '# comment' >> "$1";;
+ ">")
+ echo >> "$1";;
+ *)
+ sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
+ action=pick;;
+ esac
+ done
+ echo 'rebase -i script after editing:'
+ cat "$1"
+ EOF
+
+ test_set_editor "$(pwd)/fake-editor.sh"
+}
+
+# After set_cat_todo_editor, rebase -i will write the todo list (ignoring
+# blank lines and comments) to stdout, and exit failure (so you should run
+# it with test_must_fail). This can be used to verify the expected user
+# experience, for todo list changes that do not affect the outcome of
+# rebase; or as an extra check in addition to checking the outcome.
+set_cat_todo_editor () {
+ write_script fake-editor.sh <<-\EOF
+ grep "^[^#]" "$1"
+ exit 1
+ EOF
test_set_editor "$(pwd)/fake-editor.sh"
- chmod a+x fake-editor.sh
+}
+
+# checks that the revisions in "$2" represent a linear range with the
+# subjects in "$1"
+test_linear_range () {
+ revlist_merges=$(git rev-list --merges "$2") &&
+ test -z "$revlist_merges" &&
+ expected=$1
+ set -- $(git log --reverse --format=%s "$2")
+ test "$expected" = "$*"
+}
+
+reset_rebase () {
+ test_might_fail git rebase --abort &&
+ git reset --hard &&
+ git clean -f
+}
+
+cherry_pick () {
+ git cherry-pick -n "$2" &&
+ git commit -m "$1" &&
+ git tag "$1"
+}
+
+revert () {
+ git revert -n "$2" &&
+ git commit -m "$1" &&
+ git tag "$1"
+}
+
+make_empty () {
+ git commit --allow-empty -m "$1" &&
+ git tag "$1"
}
diff --git a/t/lib-t6000.sh b/t/lib-t6000.sh
index ea25dd8..4ffd901 100644
--- a/t/lib-t6000.sh
+++ b/t/lib-t6000.sh
@@ -1,55 +1,50 @@
: included from 6002 and others
-[ -d .git/refs/tags ] || mkdir -p .git/refs/tags
+mkdir -p .git/refs/tags
-:> sed.script
+>sed.script
-# Answer the sha1 has associated with the tag. The tag must exist in .git or .git/refs/tags
-tag()
-{
+# Answer the sha1 has associated with the tag. The tag must exist in .git/refs/tags
+tag () {
_tag=$1
- [ -f .git/refs/tags/$_tag ] || error "tag: \"$_tag\" does not exist"
- cat .git/refs/tags/$_tag
+ test -f ".git/refs/tags/$_tag" || error "tag: \"$_tag\" does not exist"
+ cat ".git/refs/tags/$_tag"
}
# Generate a commit using the text specified to make it unique and the tree
# named by the tag specified.
-unique_commit()
-{
+unique_commit () {
_text=$1
- _tree=$2
+ _tree=$2
shift 2
- echo $_text | git commit-tree $(tag $_tree) "$@"
+ echo "$_text" | git commit-tree $(tag "$_tree") "$@"
}
# Save the output of a command into the tag specified. Prepend
# a substitution script for the tag onto the front of sed.script
-save_tag()
-{
+save_tag () {
_tag=$1
- [ -n "$_tag" ] || error "usage: save_tag tag commit-args ..."
+ test -n "$_tag" || error "usage: save_tag tag commit-args ..."
shift 1
- "$@" >.git/refs/tags/$_tag
+ "$@" >".git/refs/tags/$_tag"
- echo "s/$(tag $_tag)/$_tag/g" > sed.script.tmp
- cat sed.script >> sed.script.tmp
+ echo "s/$(tag $_tag)/$_tag/g" >sed.script.tmp
+ cat sed.script >>sed.script.tmp
rm sed.script
mv sed.script.tmp sed.script
}
# Replace unhelpful sha1 hashses with their symbolic equivalents
-entag()
-{
+entag () {
sed -f sed.script
}
# Execute a command after first saving, then setting the GIT_AUTHOR_EMAIL
# tag to a specified value. Restore the original value on return.
-as_author()
-{
+as_author () {
_author=$1
shift 1
- _save=$GIT_AUTHOR_EMAIL
+ _save=$GIT_AUTHOR_EMAIL
GIT_AUTHOR_EMAIL="$_author"
export GIT_AUTHOR_EMAIL
@@ -63,45 +58,58 @@ as_author()
fi
}
-commit_date()
-{
- _commit=$1
- git cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p"
+commit_date () {
+ _commit=$1
+ git cat-file commit $_commit |
+ sed -n "s/^committer .*> \([0-9]*\) .*/\1/p"
}
-on_committer_date()
-{
- _date=$1
- shift 1
- GIT_COMMITTER_DATE="$_date"
- export GIT_COMMITTER_DATE
- "$@"
- unset GIT_COMMITTER_DATE
+# Assign the value of fake date to a variable, but
+# allow fairly common "1971-08-16 00:00" to be omittd
+assign_fake_date () {
+ case "$2" in
+ ??:??:??) eval "$1='1971-08-16 $2'" ;;
+ ??:??) eval "$1='1971-08-16 00:$2'" ;;
+ ??) eval "$1='1971-08-16 00:00:$2'" ;;
+ *) eval "$1='$2'" ;;
+ esac
+}
+
+on_committer_date () {
+ assign_fake_date GIT_COMMITTER_DATE "$1"
+ export GIT_COMMITTER_DATE
+ shift 1
+ "$@"
+}
+
+on_dates () {
+ assign_fake_date GIT_COMMITTER_DATE "$1"
+ assign_fake_date GIT_AUTHOR_DATE "$2"
+ export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+ shift 2
+ "$@"
}
# Execute a command and suppress any error output.
-hide_error()
-{
+hide_error () {
"$@" 2>/dev/null
}
-check_output()
-{
+check_output () {
_name=$1
shift 1
- if eval "$*" | entag > $_name.actual
+ if eval "$*" | entag >"$_name.actual"
then
- test_cmp $_name.expected $_name.actual
+ test_cmp "$_name.expected" "$_name.actual"
else
- return 1;
+ return 1
fi
}
# Turn a reasonable test description into a reasonable test name.
# All alphanums translated into -'s which are then compressed and stripped
# from front and back.
-name_from_description()
-{
+name_from_description () {
perl -pe '
s/[^A-Za-z0-9.]/-/g;
s/-+/-/g;
@@ -119,9 +127,11 @@ name_from_description()
test_output_expect_success()
{
_description=$1
- _test=$2
- [ $# -eq 2 ] || error "usage: test_output_expect_success description test <<EOF ... EOF"
- _name=$(echo $_description | name_from_description)
- cat > $_name.expected
+ _test=$2
+ test $# -eq 2 ||
+ error "usage: test_output_expect_success description test <<EOF ... EOF"
+
+ _name=$(echo $_description | name_from_description)
+ cat >"$_name.expected"
test_expect_success "$_description" "check_output $_name \"$_test\""
}
diff --git a/t/perf/README b/t/perf/README
index c552f56..8848c14 100644
--- a/t/perf/README
+++ b/t/perf/README
@@ -66,7 +66,7 @@ You can set the following variables (also in your config.mak):
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.
+ probably be about linux.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
diff --git a/t/perf/p0002-read-cache.sh b/t/perf/p0002-read-cache.sh
new file mode 100755
index 0000000..9180ae9
--- /dev/null
+++ b/t/perf/p0002-read-cache.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+test_description="Tests performance of reading the index"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+count=1000
+test_perf "read_cache/discard_cache $count times" "
+ test-read-cache $count
+"
+
+test_done
diff --git a/t/perf/p4211-line-log.sh b/t/perf/p4211-line-log.sh
new file mode 100755
index 0000000..3d074b0
--- /dev/null
+++ b/t/perf/p4211-line-log.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+test_description='Tests log -L performance'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+# Pick a file to log pseudo-randomly. The sort key is the blob hash,
+# so it is stable.
+test_expect_success 'select a file' '
+ git ls-tree HEAD | grep ^100644 |
+ sort -k 3 | head -1 | cut -f 2 >filelist
+'
+
+file=$(cat filelist)
+export file
+
+test_perf 'git rev-list --topo-order (baseline)' '
+ git rev-list --topo-order HEAD >/dev/null
+'
+
+test_perf 'git log --follow (baseline for -M)' '
+ git log --oneline --follow -- "$file" >/dev/null
+'
+
+test_perf 'git log -L' '
+ git log -L 1:"$file" >/dev/null
+'
+
+test_perf 'git log -M -L' '
+ git log -M -L 1:"$file" >/dev/null
+'
+
+test_done
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh
index a816fbc..c61d535 100644
--- a/t/perf/perf-lib.sh
+++ b/t/perf/perf-lib.sh
@@ -150,6 +150,7 @@ exit $ret' >&3 2>&4
test_perf () {
+ test_start_
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"
@@ -187,7 +188,7 @@ test_perf () {
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 ""
+ test_finish_
}
# We extend test_done to print timings at the end (./run disables this
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index cefe33d..10be52b 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -47,8 +47,14 @@ test_expect_failure 'pretend we have a known breakage' '
run_sub_test_lib_test () {
name="$1" descr="$2" # stdin is the body of the test code
+ shift 2
mkdir "$name" &&
(
+ # Pretend we're a test harness. This prevents
+ # test-lib from writing the counts to a file that will
+ # later be summarized, showing spurious "failed" tests
+ HARNESS_ACTIVE=t &&
+ export HARNESS_ACTIVE &&
cd "$name" &&
cat >"$name.sh" <<-EOF &&
#!$SHELL_PATH
@@ -65,7 +71,7 @@ run_sub_test_lib_test () {
cat >>"$name.sh" &&
chmod +x "$name.sh" &&
export TEST_DIRECTORY &&
- ./"$name.sh" >out 2>err
+ ./"$name.sh" "$@" >out 2>err
)
}
@@ -215,6 +221,60 @@ test_expect_success 'pretend we have a mix of all possible results' "
EOF
"
+test_expect_success 'test --verbose' '
+ test_must_fail run_sub_test_lib_test \
+ test-verbose "test verbose" --verbose <<-\EOF &&
+ test_expect_success "passing test" true
+ test_expect_success "test with output" "echo foo"
+ test_expect_success "failing test" false
+ test_done
+ EOF
+ mv test-verbose/out test-verbose/out+
+ grep -v "^Initialized empty" test-verbose/out+ >test-verbose/out &&
+ check_sub_test_lib_test test-verbose <<-\EOF
+ > expecting success: true
+ > Z
+ > ok 1 - passing test
+ > Z
+ > expecting success: echo foo
+ > foo
+ > Z
+ > ok 2 - test with output
+ > Z
+ > expecting success: false
+ > Z
+ > not ok 3 - failing test
+ > # false
+ > Z
+ > # failed 1 among 3 test(s)
+ > 1..3
+ EOF
+'
+
+test_expect_success 'test --verbose-only' '
+ test_must_fail run_sub_test_lib_test \
+ test-verbose-only-2 "test verbose-only=2" \
+ --verbose-only=2 <<-\EOF &&
+ test_expect_success "passing test" true
+ test_expect_success "test with output" "echo foo"
+ test_expect_success "failing test" false
+ test_done
+ EOF
+ check_sub_test_lib_test test-verbose-only-2 <<-\EOF
+ > ok 1 - passing test
+ > Z
+ > expecting success: echo foo
+ > foo
+ > Z
+ > ok 2 - test with output
+ > Z
+ > not ok 3 - failing test
+ > # false
+ > # failed 1 among 3 test(s)
+ > 1..3
+ EOF
+'
+
test_set_prereq HAVEIT
haveit=no
test_expect_success HAVEIT 'test runs if prerequisite is satisfied' '
@@ -367,22 +427,6 @@ test_expect_success 'validate object ID of a known tree' '
# Various types of objects
-# Some filesystems do not support symblic links; on such systems
-# some expected values are different
-if test_have_prereq SYMLINKS
-then
- expectfilter=cat
- expectedtree=087704a96baf1c2d1c869a8b084481e121c88b5b
- expectedptree1=21ae8269cacbe57ae09138dcc3a2887f904d02b3
- expectedptree2=3c5e5399f3a333eddecce7a9b9465b63f65f51e2
-else
- expectfilter='grep -v sym'
- expectedtree=8e18edf7d7edcf4371a3ac6ae5f07c2641db7c46
- expectedptree1=cfb8591b2f65de8b8cc1020cd7d9e67e7793b325
- expectedptree2=ce580448f0148b985a513b693fdf7d802cacb44f
-fi
-
-
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" &&
@@ -390,10 +434,7 @@ test_expect_success 'adding various types of objects with git update-index --add
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
+ test_ln_s_add "hello $p" ${p}sym || exit 1
done
) &&
find path* ! -type d -print | xargs git update-index --add
@@ -405,7 +446,7 @@ test_expect_success 'showing stage with git ls-files --stage' '
'
test_expect_success 'validate git ls-files output for a known tree' '
- $expectfilter >expected <<-\EOF &&
+ cat >expected <<-\EOF &&
100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0
120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym
100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0 path2/file2
@@ -423,14 +464,14 @@ test_expect_success 'writing tree out with git write-tree' '
'
test_expect_success 'validate object ID for a known tree' '
- test "$tree" = "$expectedtree"
+ test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b
'
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' '
+test_expect_success 'git ls-tree output for a known tree' '
cat >expected <<-\EOF &&
100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
@@ -447,7 +488,7 @@ test_expect_success 'showing tree with git ls-tree -r' '
'
test_expect_success 'git ls-tree -r output for a known tree' '
- $expectfilter >expected <<-\EOF &&
+ cat >expected <<-\EOF &&
100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
@@ -465,7 +506,7 @@ test_expect_success 'showing tree with git ls-tree -r -t' '
git ls-tree -r -t $tree >current
'
-test_expect_success SYMLINKS 'git ls-tree -r output for a known tree' '
+test_expect_success 'git ls-tree -r output for a known tree' '
cat >expected <<-\EOF &&
100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
@@ -487,7 +528,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' '
'
test_expect_success 'validate object ID for a known tree' '
- test "$ptree" = "$expectedptree1"
+ test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3
'
test_expect_success 'writing partial tree out with git write-tree --prefix' '
@@ -495,7 +536,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' '
'
test_expect_success 'validate object ID for a known tree' '
- test "$ptree" = "$expectedptree2"
+ test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2
'
test_expect_success 'put invalid objects into the index' '
@@ -529,7 +570,7 @@ test_expect_success 'git read-tree followed by write-tree should be idempotent'
'
test_expect_success 'validate git diff-files output for a know cache/work tree state' '
- $expectfilter >expected <<\EOF &&
+ cat >expected <<\EOF &&
:100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M path0
:120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M path0sym
:100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M path2/file2
@@ -553,7 +594,7 @@ test_expect_success 'no diff after checkout and git update-index --refresh' '
'
################################################################
-P=$expectedtree
+P=087704a96baf1c2d1c869a8b084481e121c88b5b
test_expect_success 'git commit-tree records the correct tree in a commit' '
commit0=$(echo NO | git commit-tree $P) &&
diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh
index 93e58c0..981437b 100755
--- a/t/t0005-signals.sh
+++ b/t/t0005-signals.sh
@@ -20,4 +20,11 @@ test_expect_success 'sigchain works' '
test_cmp expect actual
'
+test_expect_success !MINGW 'signals are propagated using shell convention' '
+ # we use exec here to avoid any sub-shell interpretation
+ # of the exit code
+ git config alias.sigterm "!exec test-sigchain" &&
+ test_expect_code 143 git sigterm
+'
+
test_done
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
index 9c1bde1..a56db80 100755
--- a/t/t0008-ignores.sh
+++ b/t/t0008-ignores.sh
@@ -66,16 +66,23 @@ test_check_ignore () {
init_vars &&
rm -f "$HOME/stdout" "$HOME/stderr" "$HOME/cmd" &&
- echo git $global_args check-ignore $quiet_opt $verbose_opt $args \
+ echo git $global_args check-ignore $quiet_opt $verbose_opt $non_matching_opt $args \
>"$HOME/cmd" &&
+ echo "$expect_code" >"$HOME/expected-exit-code" &&
test_expect_code "$expect_code" \
- git $global_args check-ignore $quiet_opt $verbose_opt $args \
+ git $global_args check-ignore $quiet_opt $verbose_opt $non_matching_opt $args \
>"$HOME/stdout" 2>"$HOME/stderr" &&
test_cmp "$HOME/expected-stdout" "$HOME/stdout" &&
stderr_empty_on_success "$expect_code"
}
-# Runs the same code with 3 different levels of output verbosity,
+# Runs the same code with 4 different levels of output verbosity:
+#
+# 1. with -q / --quiet
+# 2. with default verbosity
+# 3. with -v / --verbose
+# 4. with -v / --verbose, *and* -n / --non-matching
+#
# expecting success each time. Takes advantage of the fact that
# check-ignore --verbose output is the same as normal output except
# for the extra first column.
@@ -83,7 +90,9 @@ test_check_ignore () {
# Arguments:
# - (optional) prereqs for this test, e.g. 'SYMLINKS'
# - test name
-# - output to expect from -v / --verbose mode
+# - output to expect from the fourth verbosity mode (the output
+# from the other verbosity modes is automatically inferred
+# from this value)
# - code to run (should invoke test_check_ignore)
test_expect_success_multi () {
prereq=
@@ -92,8 +101,9 @@ test_expect_success_multi () {
prereq=$1
shift
fi
- testname="$1" expect_verbose="$2" code="$3"
+ testname="$1" expect_all="$2" code="$3"
+ expect_verbose=$( echo "$expect_all" | grep -v '^:: ' )
expect=$( echo "$expect_verbose" | sed -e 's/.* //' )
test_expect_success $prereq "$testname" '
@@ -101,23 +111,40 @@ test_expect_success_multi () {
eval "$code"
'
- for quiet_opt in '-q' '--quiet'
- do
- test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" "
+ # --quiet is only valid when a single pattern is passed
+ if test $( echo "$expect_all" | wc -l ) = 1
+ then
+ for quiet_opt in '-q' '--quiet'
+ do
+ test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" "
expect '' &&
$code
"
- done
- quiet_opt=
+ done
+ quiet_opt=
+ fi
for verbose_opt in '-v' '--verbose'
do
- test_expect_success $prereq "$testname${verbose_opt:+ with $verbose_opt}" "
- expect '$expect_verbose' &&
- $code
- "
+ for non_matching_opt in '' ' -n' ' --non-matching'
+ do
+ if test -n "$non_matching_opt"
+ then
+ my_expect="$expect_all"
+ else
+ my_expect="$expect_verbose"
+ fi
+
+ test_code="
+ expect '$my_expect' &&
+ $code
+ "
+ opts="$verbose_opt$non_matching_opt"
+ test_expect_success $prereq "$testname${opts:+ with $opts}" "$test_code"
+ done
done
verbose_opt=
+ non_matching_opt=
}
test_expect_success 'setup' '
@@ -178,7 +205,7 @@ test_expect_success 'setup' '
#
# test invalid inputs
-test_expect_success_multi '. corner-case' '' '
+test_expect_success_multi '. corner-case' ':: .' '
test_check_ignore . 1
'
@@ -189,11 +216,7 @@ test_expect_success_multi 'empty command line' '' '
test_expect_success_multi '--stdin with empty STDIN' '' '
test_check_ignore "--stdin" 1 </dev/null &&
- if test -n "$quiet_opt"; then
- test_stderr ""
- else
- test_stderr "no pathspec given."
- fi
+ test_stderr ""
'
test_expect_success '-q with multiple args' '
@@ -276,27 +299,39 @@ do
where="in subdir $subdir"
fi
- test_expect_success_multi "non-existent file $where not ignored" '' "
- test_check_ignore '${subdir}non-existent' 1
- "
+ test_expect_success_multi "non-existent file $where not ignored" \
+ ":: ${subdir}non-existent" \
+ "test_check_ignore '${subdir}non-existent' 1"
test_expect_success_multi "non-existent file $where ignored" \
- ".gitignore:1:one ${subdir}one" "
- test_check_ignore '${subdir}one'
- "
+ ".gitignore:1:one ${subdir}one" \
+ "test_check_ignore '${subdir}one'"
- test_expect_success_multi "existing untracked file $where not ignored" '' "
- test_check_ignore '${subdir}not-ignored' 1
- "
+ test_expect_success_multi "existing untracked file $where not ignored" \
+ ":: ${subdir}not-ignored" \
+ "test_check_ignore '${subdir}not-ignored' 1"
- test_expect_success_multi "existing tracked file $where not ignored" '' "
- test_check_ignore '${subdir}ignored-but-in-index' 1
- "
+ test_expect_success_multi "existing tracked file $where not ignored" \
+ ":: ${subdir}ignored-but-in-index" \
+ "test_check_ignore '${subdir}ignored-but-in-index' 1"
test_expect_success_multi "existing untracked file $where ignored" \
- ".gitignore:2:ignored-* ${subdir}ignored-and-untracked" "
- test_check_ignore '${subdir}ignored-and-untracked'
- "
+ ".gitignore:2:ignored-* ${subdir}ignored-and-untracked" \
+ "test_check_ignore '${subdir}ignored-and-untracked'"
+
+ test_expect_success_multi "mix of file types $where" \
+":: ${subdir}non-existent
+.gitignore:1:one ${subdir}one
+:: ${subdir}not-ignored
+:: ${subdir}ignored-but-in-index
+.gitignore:2:ignored-* ${subdir}ignored-and-untracked" \
+ "test_check_ignore '
+ ${subdir}non-existent
+ ${subdir}one
+ ${subdir}not-ignored
+ ${subdir}ignored-but-in-index
+ ${subdir}ignored-and-untracked'
+ "
done
# Having established the above, from now on we mostly test against
@@ -391,7 +426,7 @@ test_expect_success 'cd to ignored sub-directory with -v' '
#
# test handling of symlinks
-test_expect_success_multi SYMLINKS 'symlink' '' '
+test_expect_success_multi SYMLINKS 'symlink' ':: a/symlink' '
test_check_ignore "a/symlink" 1
'
@@ -574,37 +609,34 @@ cat <<-\EOF >stdin
globaltwo
b/globaltwo
../b/globaltwo
+ c/not-ignored
EOF
-cat <<-\EOF >expected-default
- ../one
- one
- b/on
- b/one
- b/one one
- b/one two
- "b/one\"three"
- b/two
- b/twooo
- ../globaltwo
- globaltwo
- b/globaltwo
- ../b/globaltwo
-EOF
-cat <<-EOF >expected-verbose
+# N.B. we deliberately end STDIN with a non-matching pattern in order
+# to test that the exit code indicates that one or more of the
+# provided paths is ignored - in other words, that it represents an
+# aggregation of all the results, not just the final result.
+
+cat <<-EOF >expected-all
.gitignore:1:one ../one
+ :: ../not-ignored
.gitignore:1:one one
+ :: not-ignored
a/b/.gitignore:8:!on* b/on
a/b/.gitignore:8:!on* b/one
a/b/.gitignore:8:!on* b/one one
a/b/.gitignore:8:!on* b/one two
a/b/.gitignore:8:!on* "b/one\"three"
a/b/.gitignore:9:!two b/two
+ :: b/not-ignored
a/.gitignore:1:two* b/twooo
$global_excludes:2:!globaltwo ../globaltwo
$global_excludes:2:!globaltwo globaltwo
$global_excludes:2:!globaltwo b/globaltwo
$global_excludes:2:!globaltwo ../b/globaltwo
+ :: c/not-ignored
EOF
+grep -v '^:: ' expected-all >expected-verbose
+sed -e 's/.* //' expected-verbose >expected-default
sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \
tr "\n" "\0" >stdin0
@@ -629,6 +661,14 @@ test_expect_success '--stdin from subdirectory with -v' '
)
'
+test_expect_success '--stdin from subdirectory with -v -n' '
+ expect_from_stdin <expected-all &&
+ (
+ cd a &&
+ test_check_ignore "--stdin -v -n" <../stdin
+ )
+'
+
for opts in '--stdin -z' '-z --stdin'
do
test_expect_success "$opts from subdirectory" '
@@ -648,5 +688,23 @@ do
'
done
+test_expect_success PIPE 'streaming support for --stdin' '
+ mkfifo in out &&
+ (git check-ignore -n -v --stdin <in >out &) &&
+
+ # We cannot just "echo >in" because check-ignore would get EOF
+ # after echo exited; instead we open the descriptor in our
+ # shell, and then echo to the fd. We make sure to close it at
+ # the end, so that the subprocess does get EOF and dies
+ # properly.
+ exec 9>in &&
+ test_when_finished "exec 9>&-" &&
+ echo >&9 one &&
+ read response <out &&
+ echo "$response" | grep "^\.gitignore:1:one one" &&
+ echo >&9 two &&
+ read response <out &&
+ echo "$response" | grep "^:: two"
+'
test_done
diff --git a/t/t0009-prio-queue.sh b/t/t0009-prio-queue.sh
new file mode 100755
index 0000000..94045c3
--- /dev/null
+++ b/t/t0009-prio-queue.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='basic tests for priority queue implementation'
+. ./test-lib.sh
+
+cat >expect <<'EOF'
+1
+2
+3
+4
+5
+5
+6
+7
+8
+9
+10
+EOF
+test_expect_success 'basic ordering' '
+ test-prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+2
+3
+4
+1
+5
+6
+EOF
+test_expect_success 'mixed put and get' '
+ test-prio-queue 6 2 4 get 5 3 get get 1 dump >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+1
+2
+NULL
+1
+2
+NULL
+EOF
+test_expect_success 'notice empty queue' '
+ test-prio-queue 1 2 get get get 1 2 get get get >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
index 1a8f44c..e526184 100755
--- a/t/t0020-crlf.sh
+++ b/t/t0020-crlf.sh
@@ -81,6 +81,14 @@ test_expect_success 'safecrlf: print warning only once' '
test $(git add doublewarn 2>&1 | grep "CRLF will be replaced by LF" | wc -l) = 1
'
+
+test_expect_success 'safecrlf: git diff demotes safecrlf=true to warn' '
+ git config core.autocrlf input &&
+ git config core.safecrlf true &&
+ git diff HEAD
+'
+
+
test_expect_success 'switch off autocrlf, safecrlf, reset HEAD' '
git config core.autocrlf false &&
git config core.safecrlf false &&
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 244a43c..65606df 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -50,7 +50,7 @@ EOF
test_expect_success 'test help' '
test_must_fail test-parse-options -h > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_i18ncmp expect output
'
@@ -75,7 +75,7 @@ check() {
shift &&
sed "s/^$what .*/$what $expect/" <expect.template >expect &&
test-parse-options $* >output 2>output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
}
@@ -86,7 +86,7 @@ check_i18n() {
shift &&
sed "s/^$what .*/$what $expect/" <expect.template >expect &&
test-parse-options $* >output 2>output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_i18ncmp expect output
}
@@ -99,7 +99,7 @@ check_unknown() {
esac &&
cat expect.err >>expect &&
test_must_fail test-parse-options $* >output 2>output.err &&
- test ! -s output &&
+ test_must_be_empty output &&
test_cmp expect output.err
}
@@ -112,7 +112,7 @@ check_unknown_i18n() {
esac &&
cat expect.err >>expect &&
test_must_fail test-parse-options $* >output 2>output.err &&
- test ! -s output &&
+ test_must_be_empty output &&
test_i18ncmp expect output.err
}
@@ -149,7 +149,7 @@ test_expect_success 'short options' '
test-parse-options -s123 -b -i 1729 -b -vv -n -F my.file \
> output 2> output.err &&
test_cmp expect output &&
- test ! -s output.err
+ test_must_be_empty output.err
'
cat > expect << EOF
@@ -168,7 +168,7 @@ test_expect_success 'long options' '
test-parse-options --boolean --integer 1729 --boolean --string2=321 \
--verbose --verbose --no-dry-run --abbrev=10 --file fi.le\
--obsolete > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -199,7 +199,7 @@ EOF
test_expect_success 'intermingled arguments' '
test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \
> output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -217,13 +217,13 @@ EOF
test_expect_success 'unambiguously abbreviated option' '
test-parse-options --int 2 --boolean --no-bo > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
test_expect_success 'unambiguously abbreviated option with "="' '
test-parse-options --int=2 > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -246,7 +246,7 @@ EOF
test_expect_success 'non ambiguous option (after two options it abbreviates)' '
test-parse-options --st 123 > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -256,7 +256,7 @@ EOF
test_expect_success 'detect possible typos' '
test_must_fail test-parse-options -boolean > output 2> output.err &&
- test ! -s output &&
+ test_must_be_empty output &&
test_cmp typo.err output.err
'
@@ -266,7 +266,7 @@ EOF
test_expect_success 'detect possible typos' '
test_must_fail test-parse-options -ambiguous > output 2> output.err &&
- test ! -s output &&
+ test_must_be_empty output &&
test_cmp typo.err output.err
'
@@ -285,7 +285,7 @@ EOF
test_expect_success 'keep some options as arguments' '
test-parse-options --quux > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -305,7 +305,7 @@ EOF
test_expect_success 'OPT_DATE() and OPT_SET_PTR() work' '
test-parse-options -t "1970-01-01 00:00:01 +0000" --default-string \
foo -q > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -324,7 +324,7 @@ EOF
test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' '
test-parse-options --length=four -b -4 > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -352,13 +352,13 @@ EOF
test_expect_success 'OPT_BIT() and OPT_SET_INT() work' '
test-parse-options --set23 -bbbbb --no-or4 > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' '
test-parse-options --set23 -bbbbb --neg-or4 > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -376,19 +376,19 @@ EOF
test_expect_success 'OPT_BIT() works' '
test-parse-options -bb --or4 > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
test_expect_success 'OPT_NEGBIT() works' '
test-parse-options -bb --no-neg-or4 > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' '
test-parse-options + + + + + + > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -406,7 +406,7 @@ EOF
test_expect_success 'OPT_NUMBER_CALLBACK() works' '
test-parse-options -12345 > output 2> output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
@@ -424,7 +424,7 @@ EOF
test_expect_success 'negation of OPT_NONEG flags is not ambiguous' '
test-parse-options --no-ambig >output 2>output.err &&
- test ! -s output.err &&
+ test_must_be_empty output.err &&
test_cmp expect output
'
diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh
index da2c504..986b2a8 100755
--- a/t/t0070-fundamental.sh
+++ b/t/t0070-fundamental.sh
@@ -17,7 +17,7 @@ test_expect_success 'mktemp to nonexistent directory prints filename' '
grep "doesnotexist/test" err
'
-test_expect_success POSIXPERM 'mktemp to unwritable directory prints filename' '
+test_expect_success POSIXPERM,SANITY 'mktemp to unwritable directory prints filename' '
mkdir cannotwrite &&
chmod -w cannotwrite &&
test_when_finished "chmod +w cannotwrite" &&
diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh
index 315b9b3..e0a6940 100755
--- a/t/t0100-previous.sh
+++ b/t/t0100-previous.sh
@@ -27,6 +27,7 @@ test_expect_success 'merge @{-1}' '
test_commit B &&
git checkout A &&
test_commit C &&
+ test_commit D &&
git branch -f master B &&
git branch -f other &&
git checkout other &&
@@ -35,14 +36,24 @@ test_expect_success 'merge @{-1}' '
git cat-file commit HEAD | grep "Merge branch '\''other'\''"
'
-test_expect_success 'merge @{-1} when there is not enough switches yet' '
+test_expect_success 'merge @{-1}~1' '
+ git checkout master &&
+ git reset --hard B &&
+ git checkout other &&
+ git checkout master &&
+ git merge @{-1}~1 &&
+ git cat-file commit HEAD >actual &&
+ grep "Merge branch '\''other'\''" actual
+'
+
+test_expect_success 'merge @{-100} before checking out that many branches yet' '
git reflog expire --expire=now &&
git checkout -f master &&
git reset --hard B &&
git branch -f other C &&
git checkout other &&
git checkout master &&
- test_must_fail git merge @{-12}
+ test_must_fail git merge @{-100}
'
test_done
diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh
index b3ae7d5..3e72aff 100755
--- a/t/t1004-read-tree-m-u-wf.sh
+++ b/t/t1004-read-tree-m-u-wf.sh
@@ -158,7 +158,7 @@ test_expect_success '3-way not overwriting local changes (their side)' '
'
-test_expect_success SYMLINKS 'funny symlink in work tree' '
+test_expect_success 'funny symlink in work tree' '
git reset --hard &&
git checkout -b sym-b side-b &&
@@ -170,15 +170,14 @@ test_expect_success SYMLINKS 'funny symlink in work tree' '
rm -fr a &&
git checkout -b sym-a side-a &&
mkdir -p a &&
- ln -s ../b a/b &&
- git add a/b &&
+ test_ln_s_add ../b a/b &&
git commit -m "we add a/b" &&
read_tree_u_must_succeed -m -u sym-a sym-a sym-b
'
-test_expect_success SYMLINKS,SANITY 'funny symlink in work tree, un-unlink-able' '
+test_expect_success SANITY 'funny symlink in work tree, un-unlink-able' '
rm -fr a b &&
git reset --hard &&
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index 9cc5c6b..d499d02 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -36,66 +36,54 @@ $content"
'
test_expect_success "Type of $type is correct" '
- test $type = "$(git cat-file -t $sha1)"
+ echo $type >expect &&
+ git cat-file -t $sha1 >actual &&
+ test_cmp expect actual
'
test_expect_success "Size of $type is correct" '
- test $size = "$(git cat-file -s $sha1)"
+ echo $size >expect &&
+ git cat-file -s $sha1 >actual &&
+ test_cmp expect actual
'
test -z "$content" ||
test_expect_success "Content of $type is correct" '
- expect="$(maybe_remove_timestamp "$content" $no_ts)"
- actual="$(maybe_remove_timestamp "$(git cat-file $type $sha1)" $no_ts)"
-
- if test "z$expect" = "z$actual"
- then
- : happy
- else
- echo "Oops: expected $expect"
- echo "but got $actual"
- false
- fi
+ maybe_remove_timestamp "$content" $no_ts >expect &&
+ maybe_remove_timestamp "$(git cat-file $type $sha1)" $no_ts >actual &&
+ test_cmp expect actual
'
test_expect_success "Pretty content of $type is correct" '
- expect="$(maybe_remove_timestamp "$pretty_content" $no_ts)"
- actual="$(maybe_remove_timestamp "$(git cat-file -p $sha1)" $no_ts)"
- if test "z$expect" = "z$actual"
- then
- : happy
- else
- echo "Oops: expected $expect"
- echo "but got $actual"
- false
- fi
+ maybe_remove_timestamp "$pretty_content" $no_ts >expect &&
+ maybe_remove_timestamp "$(git cat-file -p $sha1)" $no_ts >actual &&
+ test_cmp expect actual
'
test -z "$content" ||
test_expect_success "--batch output of $type is correct" '
- expect="$(maybe_remove_timestamp "$batch_output" $no_ts)"
- actual="$(maybe_remove_timestamp "$(echo $sha1 | git cat-file --batch)" $no_ts)"
- if test "z$expect" = "z$actual"
- then
- : happy
- else
- echo "Oops: expected $expect"
- echo "but got $actual"
- false
- fi
+ maybe_remove_timestamp "$batch_output" $no_ts >expect &&
+ maybe_remove_timestamp "$(echo $sha1 | git cat-file --batch)" $no_ts >actual &&
+ test_cmp expect actual
'
test_expect_success "--batch-check output of $type is correct" '
- expect="$sha1 $type $size"
- actual="$(echo_without_newline $sha1 | git cat-file --batch-check)"
- if test "z$expect" = "z$actual"
- then
- : happy
- else
- echo "Oops: expected $expect"
- echo "but got $actual"
- false
- fi
+ echo "$sha1 $type $size" >expect &&
+ echo_without_newline $sha1 | git cat-file --batch-check >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "custom --batch-check format" '
+ echo "$type $sha1" >expect &&
+ echo $sha1 | git cat-file --batch-check="%(objecttype) %(objectname)" >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success '--batch-check with %(rest)' '
+ echo "$type this is some extra content" >expect &&
+ echo "$sha1 this is some extra content" |
+ git cat-file --batch-check="%(objecttype) %(rest)" >actual &&
+ test_cmp expect actual
'
}
diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh
index b27a720..2a19e79 100755
--- a/t/t1507-rev-parse-upstream.sh
+++ b/t/t1507-rev-parse-upstream.sh
@@ -129,8 +129,7 @@ test_expect_success 'branch@{u} works when tracking a local branch' '
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
+ fatal: No upstream configured for branch ${sq}non-tracking${sq}
EOF
error_message non-tracking@{u} 2>actual &&
test_i18ncmp expect actual
@@ -138,8 +137,7 @@ test_expect_success 'branch@{u} error message when no upstream' '
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
+ fatal: No upstream configured for branch ${sq}master${sq}
EOF
test_must_fail git rev-parse --verify @{u} 2>actual &&
test_i18ncmp expect actual
@@ -147,8 +145,7 @@ test_expect_success '@{u} error message when no upstream' '
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
+ fatal: No such branch: ${sq}no-such-branch${sq}
EOF
error_message no-such-branch@{u} 2>actual &&
test_i18ncmp expect actual
@@ -156,8 +153,7 @@ test_expect_success 'branch@{u} error message with misspelt branch' '
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
+ fatal: HEAD does not point to a branch
EOF
git checkout HEAD^0 &&
test_must_fail git rev-parse --verify @{u} 2>actual &&
@@ -166,8 +162,7 @@ test_expect_success '@{u} error message when not on a branch' '
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
+ fatal: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
EOF
error_message bad-upstream@{u} 2>actual &&
test_i18ncmp expect actual
diff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh
index d5d6244..4db1613 100755
--- a/t/t1508-at-combinations.sh
+++ b/t/t1508-at-combinations.sh
@@ -4,17 +4,24 @@ test_description='test various @{X} syntax combinations together'
. ./test-lib.sh
check() {
-test_expect_${3:-success} "$1 = $2" "
- echo '$2' >expect &&
- git log -1 --format=%s '$1' >actual &&
- test_cmp expect actual
-"
+ test_expect_${4:-success} "$1 = $3" "
+ echo '$3' >expect &&
+ if test '$2' = 'commit'
+ then
+ git log -1 --format=%s '$1' >actual
+ else
+ git rev-parse --symbolic-full-name '$1' >actual
+ fi &&
+ test_cmp expect actual
+ "
}
+
nonsense() {
-test_expect_${2:-success} "$1 is nonsensical" "
- test_must_fail git log -1 '$1'
-"
+ test_expect_${2:-success} "$1 is nonsensical" "
+ test_must_fail git rev-parse --verify '$1'
+ "
}
+
fail() {
"$@" failure
}
@@ -31,21 +38,42 @@ test_expect_success 'setup' '
git checkout -b new-branch &&
test_commit new-one &&
test_commit new-two &&
- git config branch.old-branch.remote . &&
- git config branch.old-branch.merge refs/heads/master &&
- git config branch.new-branch.remote . &&
- git config branch.new-branch.merge refs/heads/upstream-branch
+ git branch -u master old-branch &&
+ git branch -u upstream-branch new-branch
'
-check HEAD new-two
-check "@{1}" new-one
-check "@{-1}" old-two
-check "@{-1}@{1}" old-one
-check "@{u}" upstream-two
-check "@{u}@{1}" upstream-one
-check "@{-1}@{u}" master-two
-check "@{-1}@{u}@{1}" master-one
+check HEAD ref refs/heads/new-branch
+check "@{1}" commit new-one
+check "HEAD@{1}" commit new-one
+check "@{now}" commit new-two
+check "HEAD@{now}" commit new-two
+check "@{-1}" ref refs/heads/old-branch
+check "@{-1}@{0}" commit old-two
+check "@{-1}@{1}" commit old-one
+check "@{u}" ref refs/heads/upstream-branch
+check "HEAD@{u}" ref refs/heads/upstream-branch
+check "@{u}@{1}" commit upstream-one
+check "@{-1}@{u}" ref refs/heads/master
+check "@{-1}@{u}@{1}" commit master-one
+check "@" commit new-two
+check "@@{u}" ref refs/heads/upstream-branch
nonsense "@{u}@{-1}"
+nonsense "@{0}@{0}"
nonsense "@{1}@{u}"
+nonsense "HEAD@{-1}"
+nonsense "@{-1}@{-1}"
+
+# @{N} versus HEAD@{N}
+
+check "HEAD@{3}" commit old-two
+nonsense "@{3}"
+
+test_expect_success 'switch to old-branch' '
+ git checkout old-branch
+'
+
+check HEAD ref refs/heads/old-branch
+check "HEAD@{1}" commit new-two
+check "@{1}" commit old-one
test_done
diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh
index 6b3d797..4a155c8 100755
--- a/t/t1512-rev-parse-disambiguation.sh
+++ b/t/t1512-rev-parse-disambiguation.sh
@@ -77,6 +77,7 @@ test_expect_success 'disambiguate blob' '
test_expect_success 'disambiguate tree' '
commit=$(echo "d7xm" | git commit-tree 000000000) &&
+ # this commit is fffff2e and not ambiguous with the 00000* objects
test $(git rev-parse $commit^{tree}) = $(git rev-parse 0000000000cdc)
'
@@ -99,10 +100,14 @@ test_expect_success 'disambiguate commit-ish' '
test_expect_success 'disambiguate commit' '
commit=$(echo "hoaxj" | git commit-tree 0000000000cdc -p 000000000) &&
+ # this commit is ffffffd8 and not ambiguous with the 00000* objects
test $(git rev-parse $commit^) = $(git rev-parse 0000000000e4f)
'
test_expect_success 'log name1..name2 takes only commit-ishes on both ends' '
+ # These are underspecified from the prefix-length point of view
+ # to disambiguate the commit with other objects, but there is only
+ # one commit that has 00000* prefix at this point.
git log 000000000..000000000 &&
git log ..000000000 &&
git log 000000000.. &&
@@ -112,16 +117,19 @@ test_expect_success 'log name1..name2 takes only commit-ishes on both ends' '
'
test_expect_success 'rev-parse name1..name2 takes only commit-ishes on both ends' '
+ # Likewise.
git rev-parse 000000000..000000000 &&
git rev-parse ..000000000 &&
git rev-parse 000000000..
'
test_expect_success 'git log takes only commit-ish' '
+ # Likewise.
git log 000000000
'
test_expect_success 'git reset takes only commit-ish' '
+ # Likewise.
git reset 000000000
'
@@ -131,26 +139,30 @@ test_expect_success 'first tag' '
'
test_expect_failure 'two semi-ambiguous commit-ish' '
+ # At this point, we have a tag 0000000000f8f that points
+ # at a commit 0000000000e4f, and a tree and a blob that
+ # share 0000000000 prefix with these tag and commit.
+ #
# Once the parser becomes ultra-smart, it could notice that
- # 110282 before ^{commit} name many different objects, but
+ # 0000000000 before ^{commit} name many different objects, but
# that only two (HEAD and v1.0.0 tag) can be peeled to commit,
# and that peeling them down to commit yield the same commit
# without ambiguity.
- git rev-parse --verify 110282^{commit} &&
+ git rev-parse --verify 0000000000^{commit} &&
# likewise
- git log 000000000..000000000 &&
- git log ..000000000 &&
- git log 000000000.. &&
- git log 000000000...000000000 &&
- git log ...000000000 &&
- git log 000000000...
+ git log 0000000000..0000000000 &&
+ git log ..0000000000 &&
+ git log 0000000000.. &&
+ git log 0000000000...0000000000 &&
+ git log ...0000000000 &&
+ git log 0000000000...
'
test_expect_failure 'three semi-ambiguous tree-ish' '
# Likewise for tree-ish. HEAD, v1.0.0 and HEAD^{tree} share
# the prefix but peeling them to tree yields the same thing
- git rev-parse --verify 000000000^{tree}
+ git rev-parse --verify 0000000000^{tree}
'
test_expect_success 'parse describe name' '
@@ -241,7 +253,7 @@ test_expect_success 'ambiguous commit-ish' '
# Now there are many commits that begin with the
# common prefix, none of these should pick one at
# random. They all should result in ambiguity errors.
- test_must_fail git rev-parse --verify 110282^{commit} &&
+ test_must_fail git rev-parse --verify 00000000^{commit} &&
# likewise
test_must_fail git log 000000000..000000000 &&
@@ -261,4 +273,22 @@ test_expect_success 'rev-parse --disambiguate' '
test "$(sed -e "s/^\(.........\).*/\1/" actual | sort -u)" = 000000000
'
+test_expect_success 'ambiguous 40-hex ref' '
+ TREE=$(git mktree </dev/null) &&
+ REF=`git rev-parse HEAD` &&
+ VAL=$(git commit-tree $TREE </dev/null) &&
+ git update-ref refs/heads/$REF $VAL &&
+ test `git rev-parse $REF 2>err` = $REF &&
+ grep "refname.*${REF}.*ambiguous" err
+'
+
+test_expect_success 'ambiguous short sha1 ref' '
+ TREE=$(git mktree </dev/null) &&
+ REF=`git rev-parse --short HEAD` &&
+ VAL=$(git commit-tree $TREE </dev/null) &&
+ git update-ref refs/heads/$REF $VAL &&
+ test `git rev-parse $REF 2>err` = $VAL &&
+ grep "refname.*${REF}.*ambiguous" err
+'
+
test_done
diff --git a/t/t1513-rev-parse-prefix.sh b/t/t1513-rev-parse-prefix.sh
new file mode 100755
index 0000000..87ec3ae
--- /dev/null
+++ b/t/t1513-rev-parse-prefix.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+test_description='Tests for rev-parse --prefix'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ mkdir -p sub1/sub2 &&
+ echo top >top &&
+ echo file1 >sub1/file1 &&
+ echo file2 >sub1/sub2/file2 &&
+ git add top sub1/file1 sub1/sub2/file2 &&
+ git commit -m commit
+'
+
+test_expect_success 'empty prefix -- file' '
+ git rev-parse --prefix "" -- top sub1/file1 >actual &&
+ cat <<-\EOF >expected &&
+ --
+ top
+ sub1/file1
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'valid prefix -- file' '
+ git rev-parse --prefix sub1/ -- file1 sub2/file2 >actual &&
+ cat <<-\EOF >expected &&
+ --
+ sub1/file1
+ sub1/sub2/file2
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'valid prefix -- ../file' '
+ git rev-parse --prefix sub1/ -- ../top sub2/file2 >actual &&
+ cat <<-\EOF >expected &&
+ --
+ sub1/../top
+ sub1/sub2/file2
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'empty prefix HEAD:./path' '
+ git rev-parse --prefix "" HEAD:./top >actual &&
+ git rev-parse HEAD:top >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'valid prefix HEAD:./path' '
+ git rev-parse --prefix sub1/ HEAD:./file1 >actual &&
+ git rev-parse HEAD:sub1/file1 >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'valid prefix HEAD:../path' '
+ git rev-parse --prefix sub1/ HEAD:../top >actual &&
+ git rev-parse HEAD:top >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'prefix ignored with HEAD:top' '
+ git rev-parse --prefix sub1/ HEAD:top >actual &&
+ git rev-parse HEAD:top >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'disambiguate path with valid prefix' '
+ git rev-parse --prefix sub1/ file1 >actual &&
+ cat <<-\EOF >expected &&
+ sub1/file1
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'file and refs with prefix' '
+ git rev-parse --prefix sub1/ master file1 >actual &&
+ cat <<-EOF >expected &&
+ $(git rev-parse master)
+ sub1/file1
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'two-levels deep' '
+ git rev-parse --prefix sub1/sub2/ -- file2 >actual &&
+ cat <<-\EOF >expected &&
+ --
+ sub1/sub2/file2
+ EOF
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t2001-checkout-cache-clash.sh b/t/t2001-checkout-cache-clash.sh
index 98aa73e..1fc8e63 100755
--- a/t/t2001-checkout-cache-clash.sh
+++ b/t/t2001-checkout-cache-clash.sh
@@ -59,10 +59,9 @@ test_expect_success \
'git read-tree -m $tree1 && git checkout-index -f -a'
test_debug 'show_files $tree1'
-test_expect_success SYMLINKS \
- 'git update-index --add a symlink.' \
- 'ln -s path0 path1 &&
- git update-index --add path1'
+test_expect_success \
+ 'add a symlink' \
+ 'test_ln_s_add path0 path1'
test_expect_success \
'writing tree out with git write-tree' \
'tree3=$(git write-tree)'
diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh
index 0f4b289..f171a55 100755
--- a/t/t2004-checkout-cache-temp.sh
+++ b/t/t2004-checkout-cache-temp.sh
@@ -194,11 +194,10 @@ test_expect_success \
test $(cat ../$s1) = tree1asubdir/path5)
)'
-test_expect_success SYMLINKS \
+test_expect_success \
'checkout --temp symlink' '
rm -f path* .merge_* out .git/index &&
-ln -s b a &&
-git update-index --add a &&
+test_ln_s_add b a &&
t4=$(git write-tree) &&
rm -f .git/index &&
git read-tree $t4 &&
diff --git a/t/t2007-checkout-symlink.sh b/t/t2007-checkout-symlink.sh
index e6f59f1..fc9aad5 100755
--- a/t/t2007-checkout-symlink.sh
+++ b/t/t2007-checkout-symlink.sh
@@ -6,7 +6,7 @@ test_description='git checkout to switch between branches with symlink<->dir'
. ./test-lib.sh
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
mkdir frotz &&
echo hello >frotz/filfre &&
@@ -25,25 +25,25 @@ test_expect_success SYMLINKS setup '
git rm --cached frotz/filfre &&
mv frotz xyzzy &&
- ln -s xyzzy frotz &&
- git add xyzzy/filfre frotz &&
+ test_ln_s_add xyzzy frotz &&
+ git add xyzzy/filfre &&
test_tick &&
git commit -m "side moves frotz/ to xyzzy/ and adds frotz->xyzzy/"
'
-test_expect_success SYMLINKS 'switch from symlink to dir' '
+test_expect_success 'switch from symlink to dir' '
git checkout master
'
-test_expect_success SYMLINKS 'Remove temporary directories & switch to master' '
+test_expect_success 'Remove temporary directories & switch to master' '
rm -fr frotz xyzzy nitfol &&
git checkout -f master
'
-test_expect_success SYMLINKS 'switch from dir to symlink' '
+test_expect_success 'switch from dir to symlink' '
git checkout side
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
index b44de9d..e7ba8c5 100755
--- a/t/t2012-checkout-last.sh
+++ b/t/t2012-checkout-last.sh
@@ -116,4 +116,38 @@ test_expect_success 'master...' '
test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
'
+test_expect_success '"checkout -" works after a rebase A' '
+ git checkout master &&
+ git checkout other &&
+ git rebase master &&
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" works after a rebase A B' '
+ git branch moodle master~1 &&
+ git checkout master &&
+ git checkout other &&
+ git rebase master moodle &&
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" works after a rebase -i A' '
+ git checkout master &&
+ git checkout other &&
+ git rebase -i master &&
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" works after a rebase -i A B' '
+ git branch foodle master~1 &&
+ git checkout master &&
+ git checkout other &&
+ git rebase master foodle &&
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
test_done
diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh
index 5da63e9..c2ada7d 100755
--- a/t/t2021-checkout-overwrite.sh
+++ b/t/t2021-checkout-overwrite.sh
@@ -29,21 +29,25 @@ test_expect_success 'checkout commit with dir must not remove untracked a/b' '
test -f a/b
'
-test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' '
+test_expect_success 'create a commit where dir a/b changed to symlink' '
rm -rf a/b && # cleanup if previous test failed
git checkout -f -b symlink start &&
rm -rf a/b &&
- ln -s foo a/b &&
git add -A &&
+ test_ln_s_add foo a/b &&
git commit -m "dir to symlink"
'
-test_expect_success SYMLINKS 'checkout commit with dir must not remove untracked a/b' '
+test_expect_success 'checkout commit with dir must not remove untracked a/b' '
git rm --cached a/b &&
git commit -m "un-track the symlink" &&
- test_must_fail git checkout start &&
+ test_must_fail git checkout start
+'
+
+test_expect_success SYMLINKS 'the symlink remained' '
+
test -h a/b
'
diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh
new file mode 100755
index 0000000..dee55e4
--- /dev/null
+++ b/t/t2024-checkout-dwim.sh
@@ -0,0 +1,167 @@
+#!/bin/sh
+
+test_description='checkout <branch>
+
+Ensures that checkout on an unborn branch does what the user expects'
+
+. ./test-lib.sh
+
+# Is the current branch "refs/heads/$1"?
+test_branch () {
+ printf "%s\n" "refs/heads/$1" >expect.HEAD &&
+ git symbolic-ref HEAD >actual.HEAD &&
+ test_cmp expect.HEAD actual.HEAD
+}
+
+# Is branch "refs/heads/$1" set to pull from "$2/$3"?
+test_branch_upstream () {
+ printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
+ {
+ git config "branch.$1.remote" &&
+ git config "branch.$1.merge"
+ } >actual.upstream &&
+ test_cmp expect.upstream actual.upstream
+}
+
+test_expect_success 'setup' '
+ test_commit my_master &&
+ git init repo_a &&
+ (
+ cd repo_a &&
+ test_commit a_master &&
+ git checkout -b foo &&
+ test_commit a_foo &&
+ git checkout -b bar &&
+ test_commit a_bar
+ ) &&
+ git init repo_b &&
+ (
+ cd repo_b &&
+ test_commit b_master &&
+ git checkout -b foo &&
+ test_commit b_foo &&
+ git checkout -b baz &&
+ test_commit b_baz
+ ) &&
+ git remote add repo_a repo_a &&
+ git remote add repo_b repo_b &&
+ git config remote.repo_b.fetch \
+ "+refs/heads/*:refs/remotes/other_b/*" &&
+ git fetch --all
+'
+
+test_expect_success 'checkout of non-existing branch fails' '
+ git checkout -B master &&
+ test_might_fail git branch -D xyzzy &&
+
+ test_must_fail git checkout xyzzy &&
+ test_must_fail git rev-parse --verify refs/heads/xyzzy &&
+ test_branch master
+'
+
+test_expect_success 'checkout of branch from multiple remotes fails #1' '
+ git checkout -B master &&
+ test_might_fail git branch -D foo &&
+
+ test_must_fail git checkout foo &&
+ test_must_fail git rev-parse --verify refs/heads/foo &&
+ test_branch master
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #1' '
+ git checkout -B master &&
+ test_might_fail git branch -D bar &&
+
+ git checkout bar &&
+ test_branch bar &&
+ test_cmp_rev remotes/repo_a/bar HEAD &&
+ test_branch_upstream bar repo_a bar
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #2' '
+ git checkout -B master &&
+ test_might_fail git branch -D baz &&
+
+ git checkout baz &&
+ test_branch baz &&
+ test_cmp_rev remotes/other_b/baz HEAD &&
+ test_branch_upstream baz repo_b baz
+'
+
+test_expect_success '--no-guess suppresses branch auto-vivification' '
+ git checkout -B master &&
+ test_might_fail git branch -D bar &&
+
+ test_must_fail git checkout --no-guess bar &&
+ test_must_fail git rev-parse --verify refs/heads/bar &&
+ test_branch master
+'
+
+test_expect_success 'setup more remotes with unconventional refspecs' '
+ git checkout -B master &&
+ git init repo_c &&
+ (
+ cd repo_c &&
+ test_commit c_master &&
+ git checkout -b bar &&
+ test_commit c_bar
+ git checkout -b spam &&
+ test_commit c_spam
+ ) &&
+ git init repo_d &&
+ (
+ cd repo_d &&
+ test_commit d_master &&
+ git checkout -b baz &&
+ test_commit f_baz
+ git checkout -b eggs &&
+ test_commit c_eggs
+ ) &&
+ git remote add repo_c repo_c &&
+ git config remote.repo_c.fetch \
+ "+refs/heads/*:refs/remotes/extra_dir/repo_c/extra_dir/*" &&
+ git remote add repo_d repo_d &&
+ git config remote.repo_d.fetch \
+ "+refs/heads/*:refs/repo_d/*" &&
+ git fetch --all
+'
+
+test_expect_success 'checkout of branch from multiple remotes fails #2' '
+ git checkout -B master &&
+ test_might_fail git branch -D bar &&
+
+ test_must_fail git checkout bar &&
+ test_must_fail git rev-parse --verify refs/heads/bar &&
+ test_branch master
+'
+
+test_expect_success 'checkout of branch from multiple remotes fails #3' '
+ git checkout -B master &&
+ test_might_fail git branch -D baz &&
+
+ test_must_fail git checkout baz &&
+ test_must_fail git rev-parse --verify refs/heads/baz &&
+ test_branch master
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #3' '
+ git checkout -B master &&
+ test_might_fail git branch -D spam &&
+
+ git checkout spam &&
+ test_branch spam &&
+ test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
+ test_branch_upstream spam repo_c spam
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #4' '
+ git checkout -B master &&
+ test_might_fail git branch -D eggs &&
+
+ git checkout eggs &&
+ test_branch eggs &&
+ test_cmp_rev refs/repo_d/eggs HEAD &&
+ test_branch_upstream eggs repo_d eggs
+'
+
+test_done
diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh
index b2bd419..9bf2bdf 100755
--- a/t/t2200-add-update.sh
+++ b/t/t2200-add-update.sh
@@ -96,11 +96,10 @@ test_expect_success 'non-limited update in subdir leaves root alone' '
test_cmp expect actual
'
-test_expect_success SYMLINKS 'replace a file with a symlink' '
+test_expect_success 'replace a file with a symlink' '
rm foo &&
- ln -s top foo &&
- git add -u -- foo
+ test_ln_s_add top foo
'
diff --git a/t/t2202-add-addremove.sh b/t/t2202-add-addremove.sh
index 6a81510..fc8b59e 100755
--- a/t/t2202-add-addremove.sh
+++ b/t/t2202-add-addremove.sh
@@ -41,4 +41,14 @@ test_expect_success 'git add --all' '
test_cmp expect actual
'
+test_expect_success 'Just "git add" is a no-op' '
+ git reset --hard &&
+ echo >will-remove &&
+ >will-not-be-added &&
+ git add &&
+ git diff-index --name-status --cached HEAD >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index 4e3735f..f0421c0 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -175,6 +175,24 @@ test_expect_success 'negated exclude matches can override previous ones' '
grep "^a.1" output
'
+test_expect_success 'excluded directory overrides content patterns' '
+
+ git ls-files --others --exclude="one" --exclude="!one/a.1" >output &&
+ if grep "^one/a.1" output
+ then
+ false
+ fi
+'
+
+test_expect_success 'negated directory doesn'\''t affect content patterns' '
+
+ git ls-files --others --exclude="!one" --exclude="one/a.1" >output &&
+ if grep "^one/a.1" output
+ then
+ false
+ fi
+'
+
test_expect_success 'subdirectory ignore (setup)' '
mkdir -p top/l1/l2 &&
(
diff --git a/t/t3010-ls-files-killed-modified.sh b/t/t3010-ls-files-killed-modified.sh
index 95671c2..f611d79 100755
--- a/t/t3010-ls-files-killed-modified.sh
+++ b/t/t3010-ls-files-killed-modified.sh
@@ -11,6 +11,8 @@ This test prepares the following in the cache:
path1 - a symlink
path2/file2 - a file in a directory
path3/file3 - a file in a directory
+ submod1/ - a submodule
+ submod2/ - another submodule
and the following on the filesystem:
@@ -21,9 +23,11 @@ and the following on the filesystem:
path4 - a file
path5 - a symlink
path6/file6 - a file in a directory
+ submod1/ - a submodule (modified from the cache)
+ submod2/ - a submodule (matches the cache)
-git ls-files -k should report that existing filesystem
-objects except path4, path5 and path6/file6 to be killed.
+git ls-files -k should report that existing filesystem objects
+path0/*, path1/*, path2 and path3 to be killed.
Also for modification test, the cache and working tree have:
@@ -33,75 +37,82 @@ Also for modification test, the cache and working tree have:
path10 - a non-empty file, cache dirtied.
We should report path0, path1, path2/file2, path3/file3, path7 and path8
-modified without reporting path9 and path10.
+modified without reporting path9 and path10. submod1 is also modified.
'
. ./test-lib.sh
-date >path0
-if test_have_prereq SYMLINKS
-then
- ln -s xyzzy path1
-else
- date > path1
-fi
-mkdir path2 path3
-date >path2/file2
-date >path3/file3
-: >path7
-date >path8
-: >path9
-date >path10
-test_expect_success \
- 'git update-index --add to add various paths.' \
- "git update-index --add -- path0 path1 path?/file? path7 path8 path9 path10"
-
-rm -fr path? ;# leave path10 alone
-date >path2
-if test_have_prereq SYMLINKS
-then
- ln -s frotz path3
- ln -s nitfol path5
-else
- date > path3
- date > path5
-fi
-mkdir path0 path1 path6
-date >path0/file0
-date >path1/file1
-date >path6/file6
-date >path7
-: >path8
-: >path9
-touch path10
+test_expect_success 'git update-index --add to add various paths.' '
+ date >path0 &&
+ test_ln_s_add xyzzy path1 &&
+ mkdir path2 path3 &&
+ date >path2/file2 &&
+ date >path3/file3 &&
+ : >path7 &&
+ date >path8 &&
+ : >path9 &&
+ date >path10 &&
+ git update-index --add -- path0 path?/file? path7 path8 path9 path10 &&
+ for i in 1 2
+ do
+ git init submod$i &&
+ (
+ cd submod$i && git commit --allow-empty -m "empty $i"
+ ) || break
+ done &&
+ git update-index --add submod[12]
+ (
+ cd submod1 &&
+ git commit --allow-empty -m "empty 1 (updated)"
+ ) &&
+ rm -fr path? # leave path10 alone
+'
-test_expect_success \
- 'git ls-files -k to show killed files.' \
- 'git ls-files -k >.output'
-cat >.expected <<EOF
-path0/file0
-path1/file1
-path2
-path3
-EOF
+test_expect_success 'git ls-files -k to show killed files.' '
+ date >path2 &&
+ if test_have_prereq SYMLINKS
+ then
+ ln -s frotz path3 &&
+ ln -s nitfol path5
+ else
+ date >path3 &&
+ date >path5
+ fi &&
+ mkdir path0 path1 path6 &&
+ date >path0/file0 &&
+ date >path1/file1 &&
+ date >path6/file6 &&
+ date >path7 &&
+ : >path8 &&
+ : >path9 &&
+ touch path10 &&
+ git ls-files -k >.output
+'
-test_expect_success \
- 'validate git ls-files -k output.' \
- 'test_cmp .expected .output'
+test_expect_success 'validate git ls-files -k output.' '
+ cat >.expected <<-\EOF &&
+ path0/file0
+ path1/file1
+ path2
+ path3
+ EOF
+ test_cmp .expected .output
+'
-test_expect_success \
- 'git ls-files -m to show modified files.' \
- 'git ls-files -m >.output'
-cat >.expected <<EOF
-path0
-path1
-path2/file2
-path3/file3
-path7
-path8
-EOF
+test_expect_success 'git ls-files -m to show modified files.' '
+ git ls-files -m >.output
+'
-test_expect_success \
- 'validate git ls-files -m output.' \
- 'test_cmp .expected .output'
+test_expect_success 'validate git ls-files -m output.' '
+ cat >.expected <<-\EOF &&
+ path0
+ path1
+ path2/file2
+ path3/file3
+ path7
+ path8
+ submod1
+ EOF
+ test_cmp .expected .output
+'
test_done
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
index a5e3da7..2f96100 100755
--- a/t/t3030-merge-recursive.sh
+++ b/t/t3030-merge-recursive.sh
@@ -25,10 +25,7 @@ test_expect_success 'setup 1' '
git branch submod &&
git branch copy &&
git branch rename &&
- if test_have_prereq SYMLINKS
- then
- git branch rename-ln
- fi &&
+ git branch rename-ln &&
echo hello >>a &&
cp a d/e &&
@@ -260,16 +257,12 @@ test_expect_success 'setup 8' '
git add e &&
test_tick &&
git commit -m "rename a->e" &&
- if test_have_prereq SYMLINKS
- then
- git checkout rename-ln &&
- git mv a e &&
- ln -s e a &&
- git add a e &&
- test_tick &&
- git commit -m "rename a->e, symlink a->e" &&
- oln=`printf e | git hash-object --stdin`
- fi
+ git checkout rename-ln &&
+ git mv a e &&
+ test_ln_s_add e a &&
+ test_tick &&
+ git commit -m "rename a->e, symlink a->e" &&
+ oln=`printf e | git hash-object --stdin`
'
test_expect_success 'setup 9' '
@@ -569,28 +562,25 @@ test_expect_success 'merge-recursive copy vs. rename' '
test_cmp expected actual
'
-if test_have_prereq SYMLINKS
-then
- 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"
- echo "100644 $o0 0 e"
- ) >expected &&
- test_cmp expected actual
- '
-fi
+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"
+ echo "100644 $o0 0 e"
+ ) >expected &&
+ test_cmp expected actual
+'
test_done
diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh
index 4c37057..38446a0 100755
--- a/t/t3070-wildmatch.sh
+++ b/t/t3070-wildmatch.sh
@@ -6,20 +6,20 @@ test_description='wildmatch tests'
match() {
if [ $1 = 1 ]; then
- test_expect_success "wildmatch: match '$3' '$4'" "
+ test_expect_success "wildmatch: match '$3' '$4'" "
test-wildmatch wildmatch '$3' '$4'
"
else
- test_expect_success "wildmatch: no match '$3' '$4'" "
+ test_expect_success "wildmatch: no match '$3' '$4'" "
! test-wildmatch wildmatch '$3' '$4'
"
fi
if [ $2 = 1 ]; then
- test_expect_success "fnmatch: match '$3' '$4'" "
+ test_expect_success "fnmatch: match '$3' '$4'" "
test-wildmatch fnmatch '$3' '$4'
"
elif [ $2 = 0 ]; then
- test_expect_success "fnmatch: no match '$3' '$4'" "
+ test_expect_success "fnmatch: no match '$3' '$4'" "
! test-wildmatch fnmatch '$3' '$4'
"
# else
@@ -29,13 +29,25 @@ match() {
fi
}
+imatch() {
+ if [ $1 = 1 ]; then
+ test_expect_success "iwildmatch: match '$2' '$3'" "
+ test-wildmatch iwildmatch '$2' '$3'
+ "
+ else
+ test_expect_success "iwildmatch: no match '$2' '$3'" "
+ ! test-wildmatch iwildmatch '$2' '$3'
+ "
+ fi
+}
+
pathmatch() {
if [ $1 = 1 ]; then
- test_expect_success "pathmatch: match '$2' '$3'" "
+ test_expect_success "pathmatch: match '$2' '$3'" "
test-wildmatch pathmatch '$2' '$3'
"
else
- test_expect_success "pathmatch: no match '$2' '$3'" "
+ test_expect_success "pathmatch: no match '$2' '$3'" "
! test-wildmatch pathmatch '$2' '$3'
"
fi
@@ -235,4 +247,35 @@ pathmatch 1 abcXdefXghi '*X*i'
pathmatch 1 ab/cXd/efXg/hi '*/*X*/*/*i'
pathmatch 1 ab/cXd/efXg/hi '*Xg*i'
+# Case-sensitivy features
+match 0 x 'a' '[A-Z]'
+match 1 x 'A' '[A-Z]'
+match 0 x 'A' '[a-z]'
+match 1 x 'a' '[a-z]'
+match 0 x 'a' '[[:upper:]]'
+match 1 x 'A' '[[:upper:]]'
+match 0 x 'A' '[[:lower:]]'
+match 1 x 'a' '[[:lower:]]'
+match 0 x 'A' '[B-Za]'
+match 1 x 'a' '[B-Za]'
+match 0 x 'A' '[B-a]'
+match 1 x 'a' '[B-a]'
+match 0 x 'z' '[Z-y]'
+match 1 x 'Z' '[Z-y]'
+
+imatch 1 'a' '[A-Z]'
+imatch 1 'A' '[A-Z]'
+imatch 1 'A' '[a-z]'
+imatch 1 'a' '[a-z]'
+imatch 1 'a' '[[:upper:]]'
+imatch 1 'A' '[[:upper:]]'
+imatch 1 'A' '[[:lower:]]'
+imatch 1 'a' '[[:lower:]]'
+imatch 1 'A' '[B-Za]'
+imatch 1 'a' '[B-Za]'
+imatch 1 'A' '[B-a]'
+imatch 1 'a' '[B-a]'
+imatch 1 'z' '[Z-y]'
+imatch 1 'Z' '[Z-y]'
+
test_done
diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
index 81d90b6..eb73c06 100755
--- a/t/t3100-ls-tree-restrict.sh
+++ b/t/t3100-ls-tree-restrict.sh
@@ -22,20 +22,8 @@ test_expect_success \
'setup' \
'mkdir path2 path2/baz &&
echo Hi >path0 &&
- if test_have_prereq SYMLINKS
- then
- ln -s path0 path1 &&
- ln -s ../path1 path2/bazbo
- make_expected () {
- cat >expected
- }
- else
- printf path0 > path1 &&
- printf ../path1 > path2/bazbo
- make_expected () {
- sed -e "s/120000 /100644 /" >expected
- }
- fi &&
+ test_ln_s_add path0 path1 &&
+ test_ln_s_add ../path1 path2/bazbo &&
echo Lo >path2/foo &&
echo Mi >path2/baz/b &&
find path? \( -type f -o -type l \) -print |
@@ -51,7 +39,7 @@ test_output () {
test_expect_success \
'ls-tree plain' \
'git ls-tree $tree >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
100644 blob X path0
120000 blob X path1
040000 tree X path2
@@ -61,7 +49,7 @@ EOF
test_expect_success \
'ls-tree recursive' \
'git ls-tree -r $tree >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
100644 blob X path0
120000 blob X path1
100644 blob X path2/baz/b
@@ -73,7 +61,7 @@ EOF
test_expect_success \
'ls-tree recursive with -t' \
'git ls-tree -r -t $tree >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
100644 blob X path0
120000 blob X path1
040000 tree X path2
@@ -87,7 +75,7 @@ EOF
test_expect_success \
'ls-tree recursive with -d' \
'git ls-tree -r -d $tree >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
040000 tree X path2
040000 tree X path2/baz
EOF
@@ -96,7 +84,7 @@ EOF
test_expect_success \
'ls-tree filtered with path' \
'git ls-tree $tree path >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
EOF
test_output'
@@ -106,7 +94,7 @@ EOF
test_expect_success \
'ls-tree filtered with path1 path0' \
'git ls-tree $tree path1 path0 >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
100644 blob X path0
120000 blob X path1
EOF
@@ -115,7 +103,7 @@ EOF
test_expect_success \
'ls-tree filtered with path0/' \
'git ls-tree $tree path0/ >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
EOF
test_output'
@@ -124,7 +112,7 @@ EOF
test_expect_success \
'ls-tree filtered with path2' \
'git ls-tree $tree path2 >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
040000 tree X path2
EOF
test_output'
@@ -133,7 +121,7 @@ EOF
test_expect_success \
'ls-tree filtered with path2/' \
'git ls-tree $tree path2/ >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
040000 tree X path2/baz
120000 blob X path2/bazbo
100644 blob X path2/foo
@@ -145,7 +133,7 @@ EOF
test_expect_success \
'ls-tree filtered with path2/baz' \
'git ls-tree $tree path2/baz >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
040000 tree X path2/baz
EOF
test_output'
@@ -153,14 +141,14 @@ EOF
test_expect_success \
'ls-tree filtered with path2/bak' \
'git ls-tree $tree path2/bak >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
EOF
test_output'
test_expect_success \
'ls-tree -t filtered with path2/bak' \
'git ls-tree -t $tree path2/bak >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
040000 tree X path2
EOF
test_output'
@@ -168,7 +156,7 @@ EOF
test_expect_success \
'ls-tree with one path a prefix of the other' \
'git ls-tree $tree path2/baz path2/bazbo >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
040000 tree X path2/baz
120000 blob X path2/bazbo
EOF
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index d969f0e..44ec6a4 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -317,13 +317,13 @@ test_expect_success 'test tracking setup (non-wildcard, matching)' '
test $(git config branch.my4.merge) = refs/heads/master
'
-test_expect_success 'test tracking setup (non-wildcard, not matching)' '
+test_expect_success 'tracking setup fails on non-matching refspec' '
git config remote.local.url . &&
git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
(git show-ref -q refs/remotes/local/master || git fetch local) &&
- git branch --track my5 local/master &&
- ! test "$(git config branch.my5.remote)" = local &&
- ! test "$(git config branch.my5.merge)" = refs/heads/master
+ test_must_fail git branch --track my5 local/master &&
+ test_must_fail git config branch.my5.remote &&
+ test_must_fail git config branch.my5.merge
'
test_expect_success 'test tracking setup via config' '
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
index cd04361..1a2080e 100755
--- a/t/t3210-pack-refs.sh
+++ b/t/t3210-pack-refs.sh
@@ -118,4 +118,37 @@ test_expect_success 'pack, prune and repack' '
test_cmp all-of-them again
'
+test_expect_success 'explicit pack-refs with dangling packed reference' '
+ git commit --allow-empty -m "soon to be garbage-collected" &&
+ git pack-refs --all &&
+ git reset --hard HEAD^ &&
+ git reflog expire --expire=all --all &&
+ git prune --expire=all &&
+ git pack-refs --all 2>result &&
+ test_cmp /dev/null result
+'
+
+test_expect_success 'delete ref with dangling packed version' '
+ git checkout -b lamb &&
+ git commit --allow-empty -m "future garbage" &&
+ git pack-refs --all &&
+ git reset --hard HEAD^ &&
+ git checkout master &&
+ git reflog expire --expire=all --all &&
+ git prune --expire=all &&
+ git branch -d lamb 2>result &&
+ test_cmp /dev/null result
+'
+
+test_expect_success 'delete ref while another dangling packed ref' '
+ git branch lamb &&
+ git commit --allow-empty -m "future garbage" &&
+ git pack-refs --all &&
+ git reset --hard HEAD^ &&
+ git reflog expire --expire=all --all &&
+ git prune --expire=all &&
+ git branch -d lamb 2>result &&
+ test_cmp /dev/null result
+'
+
test_done
diff --git a/t/t3211-peel-ref.sh b/t/t3211-peel-ref.sh
index d4d7792..3b7caca 100755
--- a/t/t3211-peel-ref.sh
+++ b/t/t3211-peel-ref.sh
@@ -61,4 +61,13 @@ test_expect_success 'refs are peeled outside of refs/tags (old packed)' '
test_cmp expect actual
'
+test_expect_success 'peeled refs survive deletion of packed ref' '
+ git pack-refs --all &&
+ cp .git/packed-refs fully-peeled &&
+ git branch yadda &&
+ git pack-refs --all &&
+ git branch -d yadda &&
+ test_cmp fully-peeled .git/packed-refs
+'
+
test_done
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index b58fa1a..ebf93b0 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -40,13 +40,6 @@ test_expect_success 'prepare repository with topic branches' '
echo Side >>C &&
git add C &&
git commit -m "Add C" &&
- git checkout -b nonlinear my-topic-branch &&
- echo Edit >>B &&
- git add B &&
- git commit -m "Modify B" &&
- git merge side &&
- git checkout -b upstream-merged-nonlinear &&
- git merge master &&
git checkout -f my-topic-branch &&
git tag topic
'
@@ -66,26 +59,15 @@ test_expect_success 'rebase against master' '
git rebase master
'
-test_expect_success 'rebase against master twice' '
- git rebase master >out &&
- test_i18ngrep "Current branch my-topic-branch is up to date" out
-'
-
-test_expect_success 'rebase against master twice with --force' '
- git rebase --force-rebase master >out &&
- test_i18ngrep "Current branch my-topic-branch is up to date, rebase forced" out
-'
-
-test_expect_success 'rebase against master twice from another branch' '
- git checkout my-topic-branch^ &&
- git rebase master my-topic-branch >out &&
- test_i18ngrep "Current branch my-topic-branch is up to date" out
-'
-
-test_expect_success 'rebase fast-forward to master' '
- git checkout my-topic-branch^ &&
- git rebase my-topic-branch >out &&
- test_i18ngrep "Fast-forwarded HEAD to my-topic-branch" out
+test_expect_success 'rebase, with <onto> and <upstream> specified as :/quuxery' '
+ test_when_finished "git branch -D torebase" &&
+ git checkout -b torebase my-topic-branch^ &&
+ upstream=$(git rev-parse ":/Add B") &&
+ onto=$(git rev-parse ":/Add A") &&
+ git rebase --onto $onto $upstream &&
+ git reset --hard my-topic-branch^ &&
+ git rebase --onto ":/Add A" ":/Add B" &&
+ git checkout my-topic-branch
'
test_expect_success 'the rebase operation should not have destroyed author information' '
@@ -106,31 +88,9 @@ test_expect_success 'rebase from ambiguous branch name' '
git rebase master
'
-test_expect_success 'rebase after merge master' '
- git checkout --detach refs/tags/topic &&
- git branch -D topic &&
- git reset --hard topic &&
- git merge master &&
- git rebase master &&
- ! (git show | grep "^Merge:")
-'
-
-test_expect_success 'rebase of history with merges is linearized' '
- git checkout nonlinear &&
- test 4 = $(git rev-list master.. | wc -l) &&
- git rebase master &&
- test 3 = $(git rev-list master.. | wc -l)
-'
-
-test_expect_success 'rebase of history with merges after upstream merge is linearized' '
- git checkout upstream-merged-nonlinear &&
- test 5 = $(git rev-list master.. | wc -l) &&
- git rebase master &&
- test 3 = $(git rev-list master.. | wc -l)
-'
-
test_expect_success 'rebase a single mode change' '
git checkout master &&
+ git branch -D topic &&
echo 1 >X &&
git add X &&
test_tick &&
@@ -185,7 +145,7 @@ test_expect_success 'default to @{upstream} when upstream arg is missing' '
test_expect_success 'rebase -q is quiet' '
git checkout -b quiet topic &&
git rebase -q master >output.out 2>&1 &&
- test ! -s output.out
+ test_must_be_empty output.out
'
test_expect_success 'Rebase a commit that sprinkles CRs in' '
diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh
deleted file mode 100755
index 58f4823..0000000
--- a/t/t3401-rebase-partial.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006 Yann Dirson, based on t3400 by Amos Waterland
-#
-
-test_description='git rebase should detect patches integrated upstream
-
-This test cherry-picks one local change of two into master branch, and
-checks that git rebase succeeds with only the second patch in the
-local branch.
-'
-. ./test-lib.sh
-
-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 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_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 reset --hard C &&
- git rebase --merge master &&
- test_path_is_missing .git/rebase-merge
-'
-
-test_expect_success 'rebase ignores empty commit' '
- git reset --hard A &&
- git commit --allow-empty -m empty &&
- test_commit D &&
- git rebase C &&
- test "$(git log --format=%s C..)" = "D"
-'
-
-test_expect_success 'rebase --keep-empty' '
- git reset --hard D &&
- git rebase --keep-empty C &&
- test "$(git log --format=%s C..)" = "D
-empty"
-'
-
-test_expect_success 'rebase --keep-empty keeps empty even if already in upstream' '
- git reset --hard A &&
- git commit --allow-empty -m also-empty &&
- git rebase --keep-empty D &&
- test "$(git log --format=%s A..)" = "also-empty
-D
-empty"
-'
-
-test_done
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index 826500b..3968020 100755
--- a/t/t3403-rebase-skip.sh
+++ b/t/t3403-rebase-skip.sh
@@ -64,10 +64,11 @@ test_expect_success 'rebase with --merge' '
test_expect_success 'rebase --skip with --merge' '
git rebase --skip
- '
+'
-test_expect_success 'merge and reference trees equal' \
- 'test -z "`git diff-tree skip-merge skip-reference`"'
+test_expect_success 'merge and reference trees equal' '
+ test -z "`git diff-tree skip-merge skip-reference`"
+'
test_expect_success 'moved back to branch correctly' '
test refs/heads/skip-merge = $(git symbolic-ref HEAD)
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index a58406d..49ccb38 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -477,19 +477,11 @@ test_expect_success 'interrupted squash works as expected (case 2)' '
test $one = $(git rev-parse HEAD~2)
'
-test_expect_success 'ignore patch if in upstream' '
- HEAD=$(git rev-parse HEAD) &&
- git checkout -b has-cherry-picked HEAD^ &&
+test_expect_success '--continue tries to commit, even for "edit"' '
echo unrelated > file7 &&
git add file7 &&
test_tick &&
git commit -m "unrelated change" &&
- git cherry-pick $HEAD &&
- EXPECT_COUNT=1 git rebase -i $HEAD &&
- test $HEAD = $(git rev-parse HEAD^)
-'
-
-test_expect_success '--continue tries to commit, even for "edit"' '
parent=$(git rev-parse HEAD^) &&
test_tick &&
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
@@ -692,7 +684,7 @@ test_expect_success 'rebase -i can copy notes' '
test_commit n2 &&
test_commit n3 &&
git notes add -m"a note" n3 &&
- git rebase --onto n1 n2 &&
+ git rebase -i --onto n1 n2 &&
test "a note" = "$(git notes show HEAD)"
'
@@ -934,6 +926,21 @@ test_expect_success 'rebase --edit-todo can be used to modify todo' '
test L = $(git cat-file commit HEAD | sed -ne \$p)
'
+test_expect_success 'rebase -i produces readable reflog' '
+ git reset --hard &&
+ git branch -f branch-reflog-test H &&
+ git rebase -i --onto I F branch-reflog-test &&
+ cat >expect <<-\EOF &&
+ rebase -i (start): checkout I
+ rebase -i (pick): G
+ rebase -i (pick): H
+ rebase -i (finish): returning to refs/heads/branch-reflog-test
+ EOF
+ tail -n 4 .git/logs/HEAD |
+ sed -e "s/.* //" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'rebase -i respects core.commentchar' '
git reset --hard &&
git checkout E^0 &&
@@ -947,4 +954,26 @@ test_expect_success 'rebase -i respects core.commentchar' '
test B = $(git cat-file commit HEAD^ | sed -ne \$p)
'
+test_expect_success 'rebase -i, with <onto> and <upstream> specified as :/quuxery' '
+ test_when_finished "git branch -D torebase" &&
+ git checkout -b torebase branch1 &&
+ upstream=$(git rev-parse ":/J") &&
+ onto=$(git rev-parse ":/A") &&
+ git rebase --onto $onto $upstream &&
+ git reset --hard branch1 &&
+ git rebase --onto ":/A" ":/J" &&
+ git checkout branch1
+'
+
+test_expect_success 'rebase -i with --strategy and -X' '
+ git checkout -b conflict-merge-use-theirs conflict-branch &&
+ git reset --hard HEAD^ &&
+ echo five >conflict &&
+ echo Z >file1 &&
+ git commit -a -m "one file conflict" &&
+ EDITOR=true git rebase -i --strategy=recursive -Xours conflict-branch &&
+ test $(git show conflict-branch:conflict) = $(cat conflict) &&
+ test $(cat file1) = Z
+'
+
test_done
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index e6a9a0d..0392e36 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -4,27 +4,17 @@ test_description='messages from rebase operation'
. ./test-lib.sh
-quick_one () {
- echo "$1" >"file$1" &&
- git add "file$1" &&
- test_tick &&
- git commit -m "$1"
-}
+test_expect_success 'setup' '
+ test_commit O fileO &&
+ test_commit X fileX &&
+ test_commit A fileA &&
+ test_commit B fileB &&
+ test_commit Y fileY &&
-test_expect_success setup '
- quick_one O &&
- git branch topic &&
- quick_one X &&
- quick_one A &&
- quick_one B &&
- quick_one Y &&
-
- git checkout topic &&
- quick_one A &&
- quick_one B &&
- quick_one Z &&
+ git checkout -b topic O &&
+ git cherry-pick A B &&
+ test_commit Z fileZ &&
git tag start
-
'
cat >expect <<\EOF
@@ -34,12 +24,32 @@ Committed: 0003 Z
EOF
test_expect_success 'rebase -m' '
-
git rebase -m master >report &&
sed -n -e "/^Already applied: /p" \
-e "/^Committed: /p" report >actual &&
test_cmp expect actual
+'
+
+test_expect_success 'rebase against master twice' '
+ git rebase master >out &&
+ test_i18ngrep "Current branch topic is up to date" out
+'
+
+test_expect_success 'rebase against master twice with --force' '
+ git rebase --force-rebase master >out &&
+ test_i18ngrep "Current branch topic is up to date, rebase forced" out
+'
+
+test_expect_success 'rebase against master twice from another branch' '
+ git checkout topic^ &&
+ git rebase master topic >out &&
+ test_i18ngrep "Current branch topic is up to date" out
+'
+test_expect_success 'rebase fast-forward to master' '
+ git checkout topic^ &&
+ git rebase topic >out &&
+ test_i18ngrep "Fast-forwarded HEAD to topic" out
'
test_expect_success 'rebase --stat' '
diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh
index 6de4e22..2e0c364 100755
--- a/t/t3409-rebase-preserve-merges.sh
+++ b/t/t3409-rebase-preserve-merges.sh
@@ -11,14 +11,6 @@ Run "git rebase -p" and check that merges are properly carried along
GIT_AUTHOR_EMAIL=bogus_email_address
export GIT_AUTHOR_EMAIL
-# Clone 1 (trivial merge):
-#
-# A1--A2 <-- origin/master
-# \ \
-# B1--M <-- topic
-# \
-# B2 <-- origin/topic
-#
# Clone 2 (conflicting merge):
#
# A1--A2--B3 <-- origin/master
@@ -36,16 +28,6 @@ export GIT_AUTHOR_EMAIL
# \--A3 <-- topic2
# \
# B2 <-- origin/topic
-#
-# Clone 4 (merge using second parent as base):
-#
-# A1--A2--B3 <-- origin/master
-# \
-# B1--A3--M <-- topic
-# \ /
-# \--A4 <-- topic2
-# \
-# B2 <-- origin/topic
test_expect_success 'setup for merge-preserving rebase' \
'echo First > A &&
@@ -58,20 +40,6 @@ test_expect_success 'setup for merge-preserving rebase' \
git checkout -f master &&
echo Third >> A &&
git commit -a -m "Modify A2" &&
-
- git clone ./. clone1 &&
- (cd clone1 &&
- git checkout -b topic origin/topic &&
- git merge origin/master
- ) &&
-
- git clone ./. clone4 &&
- (
- cd clone4 &&
- git checkout -b topic origin/topic &&
- git merge origin/master
- ) &&
-
echo Fifth > B &&
git add B &&
git commit -m "Add different B" &&
@@ -101,16 +69,6 @@ test_expect_success 'setup for merge-preserving rebase' \
git commit -a -m "Modify B2"
'
-test_expect_success 'rebase -p fakes interactive rebase' '
- (
- cd clone1 &&
- git fetch &&
- git rebase -p origin/topic &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l)
- )
-'
-
test_expect_success '--continue works after a conflict' '
(
cd clone2 &&
@@ -138,15 +96,4 @@ test_expect_success 'rebase -p preserves no-ff merges' '
)
'
-test_expect_success 'rebase -p works when base inside second parent' '
- (
- cd clone4 &&
- git fetch &&
- git rebase -p HEAD^2 &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Modify B" | wc -l) &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l)
- )
-'
-
test_done
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
index a1e86c4..41370ab 100755
--- a/t/t3415-rebase-autosquash.sh
+++ b/t/t3415-rebase-autosquash.sh
@@ -4,6 +4,8 @@ test_description='auto squash'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
test_expect_success setup '
echo 0 >file0 &&
git add . &&
@@ -193,4 +195,59 @@ test_expect_success 'use commit --squash' '
test_auto_commit_flags squash 2
'
+test_auto_fixup_fixup () {
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "$1! first" &&
+ echo 2 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "$1! $2! first" &&
+ git tag "final-$1-$2" &&
+ test_tick &&
+ (
+ set_cat_todo_editor &&
+ test_must_fail git rebase --autosquash -i HEAD^^^^ >actual &&
+ cat >expected <<-EOF &&
+ pick $(git rev-parse --short HEAD^^^) first commit
+ $1 $(git rev-parse --short HEAD^) $1! first
+ $1 $(git rev-parse --short HEAD) $1! $2! first
+ pick $(git rev-parse --short HEAD^^) second commit
+ EOF
+ test_cmp expected actual
+ ) &&
+ git rebase --autosquash -i HEAD^^^^ &&
+ git log --oneline >actual &&
+ test_line_count = 3 actual
+ git diff --exit-code "final-$1-$2" &&
+ test 2 = "$(git cat-file blob HEAD^:file1)" &&
+ if test "$1" = "fixup"
+ then
+ test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+ elif test "$1" = "squash"
+ then
+ test 3 = $(git cat-file commit HEAD^ | grep first | wc -l)
+ else
+ false
+ fi
+}
+
+test_expect_success 'fixup! fixup!' '
+ test_auto_fixup_fixup fixup fixup
+'
+
+test_expect_success 'fixup! squash!' '
+ test_auto_fixup_fixup fixup squash
+'
+
+test_expect_success 'squash! squash!' '
+ test_auto_fixup_fixup squash squash
+'
+
+test_expect_success 'squash! fixup!' '
+ test_auto_fixup_fixup squash fixup
+'
+
test_done
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
new file mode 100755
index 0000000..90eb264
--- /dev/null
+++ b/t/t3420-rebase-autostash.sh
@@ -0,0 +1,170 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Ramkumar Ramachandra
+#
+
+test_description='git rebase --autostash tests'
+. ./test-lib.sh
+
+test_expect_success setup '
+ echo hello-world >file0 &&
+ git add . &&
+ test_tick &&
+ git commit -m "initial commit" &&
+ git checkout -b feature-branch &&
+ echo another-hello >file1 &&
+ echo goodbye >file2 &&
+ git add . &&
+ test_tick &&
+ git commit -m "second commit" &&
+ echo final-goodbye >file3 &&
+ git add . &&
+ test_tick &&
+ git commit -m "third commit" &&
+ git checkout -b unrelated-onto-branch master &&
+ echo unrelated >file4 &&
+ git add . &&
+ test_tick &&
+ git commit -m "unrelated commit" &&
+ git checkout -b related-onto-branch master &&
+ echo conflicting-change >file2 &&
+ git add . &&
+ test_tick &&
+ git commit -m "related commit"
+'
+
+testrebase() {
+ type=$1
+ dotest=$2
+
+ test_expect_success "rebase$type: dirty worktree, non-conflicting rebase" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b rebased-feature-branch feature-branch &&
+ test_when_finished git branch -D rebased-feature-branch &&
+ echo dirty >>file3 &&
+ git rebase$type unrelated-onto-branch &&
+ grep unrelated file4 &&
+ grep dirty file3 &&
+ git checkout feature-branch
+ '
+
+ test_expect_success "rebase$type: dirty index, non-conflicting rebase" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b rebased-feature-branch feature-branch &&
+ test_when_finished git branch -D rebased-feature-branch &&
+ echo dirty >>file3 &&
+ git add file3 &&
+ git rebase$type unrelated-onto-branch &&
+ grep unrelated file4 &&
+ grep dirty file3 &&
+ git checkout feature-branch
+ '
+
+ test_expect_success "rebase$type: conflicting rebase" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b rebased-feature-branch feature-branch &&
+ test_when_finished git branch -D rebased-feature-branch &&
+ echo dirty >>file3 &&
+ test_must_fail git rebase$type related-onto-branch &&
+ test_path_is_file $dotest/autostash &&
+ ! grep dirty file3 &&
+ rm -rf $dotest &&
+ git reset --hard &&
+ git checkout feature-branch
+ '
+
+ test_expect_success "rebase$type: --continue" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b rebased-feature-branch feature-branch &&
+ test_when_finished git branch -D rebased-feature-branch &&
+ echo dirty >>file3 &&
+ test_must_fail git rebase$type related-onto-branch &&
+ test_path_is_file $dotest/autostash &&
+ ! grep dirty file3 &&
+ echo "conflicting-plus-goodbye" >file2 &&
+ git add file2 &&
+ git rebase --continue &&
+ test_path_is_missing $dotest/autostash &&
+ grep dirty file3 &&
+ git checkout feature-branch
+ '
+
+ test_expect_success "rebase$type: --skip" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b rebased-feature-branch feature-branch &&
+ test_when_finished git branch -D rebased-feature-branch &&
+ echo dirty >>file3 &&
+ test_must_fail git rebase$type related-onto-branch &&
+ test_path_is_file $dotest/autostash &&
+ ! grep dirty file3 &&
+ git rebase --skip &&
+ test_path_is_missing $dotest/autostash &&
+ grep dirty file3 &&
+ git checkout feature-branch
+ '
+
+ test_expect_success "rebase$type: --abort" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b rebased-feature-branch feature-branch &&
+ test_when_finished git branch -D rebased-feature-branch &&
+ echo dirty >>file3 &&
+ test_must_fail git rebase$type related-onto-branch &&
+ test_path_is_file $dotest/autostash &&
+ ! grep dirty file3 &&
+ git rebase --abort &&
+ test_path_is_missing $dotest/autostash &&
+ grep dirty file3 &&
+ git checkout feature-branch
+ '
+
+ test_expect_success "rebase$type: non-conflicting rebase, conflicting stash" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b rebased-feature-branch feature-branch &&
+ test_when_finished git branch -D rebased-feature-branch &&
+ echo dirty >file4 &&
+ git add file4 &&
+ git rebase$type unrelated-onto-branch &&
+ test_path_is_missing $dotest &&
+ git reset --hard &&
+ grep unrelated file4 &&
+ ! grep dirty file4 &&
+ git checkout feature-branch &&
+ git stash pop &&
+ grep dirty file4
+ '
+}
+
+test_expect_success "rebase: fast-forward rebase" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b behind-feature-branch feature-branch~1 &&
+ test_when_finished git branch -D behind-feature-branch &&
+ echo dirty >>file1 &&
+ git rebase feature-branch &&
+ grep dirty file1 &&
+ git checkout feature-branch
+'
+
+test_expect_success "rebase: noop rebase" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b same-feature-branch feature-branch &&
+ test_when_finished git branch -D same-feature-branch &&
+ echo dirty >>file1 &&
+ git rebase feature-branch &&
+ grep dirty file1 &&
+ git checkout feature-branch
+'
+
+testrebase "" .git/rebase-apply
+testrebase " --merge" .git/rebase-merge
+testrebase " --interactive" .git/rebase-merge
+
+test_done
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
new file mode 100755
index 0000000..9c55cba
--- /dev/null
+++ b/t/t3421-rebase-topology-linear.sh
@@ -0,0 +1,350 @@
+#!/bin/sh
+
+test_description='basic rebase topology tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+# a---b---c
+# \
+# d---e
+test_expect_success 'setup' '
+ test_commit a &&
+ test_commit b &&
+ test_commit c &&
+ git checkout b &&
+ test_commit d &&
+ test_commit e
+'
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "simple rebase $*" "
+ reset_rebase &&
+ git rebase $* c e &&
+ test_cmp_rev c HEAD~2 &&
+ test_linear_range 'd e' c..
+ "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* is no-op if upstream is an ancestor" "
+ reset_rebase &&
+ git rebase $* b e &&
+ test_cmp_rev e HEAD
+ "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* -f rewrites even if upstream is an ancestor" "
+ reset_rebase &&
+ git rebase $* -f b e &&
+ ! test_cmp_rev e HEAD &&
+ test_cmp_rev b HEAD~2 &&
+ test_linear_range 'd e' b..
+ "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* fast-forwards from ancestor of upstream" "
+ reset_rebase &&
+ git rebase $* e b &&
+ test_cmp_rev e HEAD
+ "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+# f
+# /
+# a---b---c---g---h
+# \
+# d---gp--i
+#
+# gp = cherry-picked g
+# h = reverted g
+#
+# Reverted patches are there for tests to be able to check if a commit
+# that introduced the same change as another commit is
+# dropped. Without reverted commits, we could get false positives
+# because applying the patch succeeds, but simply results in no
+# changes.
+test_expect_success 'setup of linear history for range selection tests' '
+ git checkout c &&
+ test_commit g &&
+ revert h g &&
+ git checkout d &&
+ cherry_pick gp g &&
+ test_commit i &&
+ git checkout b &&
+ test_commit f
+'
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* drops patches in upstream" "
+ reset_rebase &&
+ git rebase $* h i &&
+ test_cmp_rev h HEAD~2 &&
+ test_linear_range 'd i' h..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* can drop last patch if in upstream" "
+ reset_rebase &&
+ git rebase $* h gp &&
+ test_cmp_rev h HEAD^ &&
+ test_linear_range 'd' h..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* --onto drops patches in upstream" "
+ reset_rebase &&
+ git rebase $* --onto f h i &&
+ test_cmp_rev f HEAD~2 &&
+ test_linear_range 'd i' f..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* --onto does not drop patches in onto" "
+ reset_rebase &&
+ git rebase $* --onto h f i &&
+ test_cmp_rev h HEAD~3 &&
+ test_linear_range 'd gp i' h..
+ "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+# a---b---c---j!
+# \
+# d---k!--l
+#
+# ! = empty
+test_expect_success 'setup of linear history for empty commit tests' '
+ git checkout c &&
+ make_empty j &&
+ git checkout d &&
+ make_empty k &&
+ test_commit l
+'
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* drops empty commit" "
+ reset_rebase &&
+ git rebase $* c l &&
+ test_cmp_rev c HEAD~2 &&
+ test_linear_range 'd l' c..
+ "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* --keep-empty" "
+ reset_rebase &&
+ git rebase $* --keep-empty c l &&
+ test_cmp_rev c HEAD~3 &&
+ test_linear_range 'd k l' c..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* --keep-empty keeps empty even if already in upstream" "
+ reset_rebase &&
+ git rebase $* --keep-empty j l &&
+ test_cmp_rev j HEAD~3 &&
+ test_linear_range 'd k l' j..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase failure -i
+test_run_rebase failure -p
+
+# m
+# /
+# a---b---c---g
+#
+# x---y---bp
+#
+# bp = cherry-picked b
+# m = reverted b
+#
+# Reverted patches are there for tests to be able to check if a commit
+# that introduced the same change as another commit is
+# dropped. Without reverted commits, we could get false positives
+# because applying the patch succeeds, but simply results in no
+# changes.
+test_expect_success 'setup of linear history for test involving root' '
+ git checkout b &&
+ revert m b &&
+ git checkout --orphan disjoint &&
+ git rm -rf . &&
+ test_commit x &&
+ test_commit y &&
+ cherry_pick bp b
+'
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* --onto --root" "
+ reset_rebase &&
+ git rebase $* --onto c --root y &&
+ test_cmp_rev c HEAD~2 &&
+ test_linear_range 'x y' c..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* without --onto --root with disjoint history" "
+ reset_rebase &&
+ git rebase $* c y &&
+ test_cmp_rev c HEAD~2 &&
+ test_linear_range 'x y' c..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* --onto --root drops patch in onto" "
+ reset_rebase &&
+ git rebase $* --onto m --root bp &&
+ test_cmp_rev m HEAD~2 &&
+ test_linear_range 'x y' m..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* --onto --root with merge-base does not go to root" "
+ reset_rebase &&
+ git rebase $* --onto m --root g &&
+ test_cmp_rev m HEAD~2 &&
+ test_linear_range 'c g' m..
+ "
+}
+
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* without --onto --root with disjoint history drops patch in onto" "
+ reset_rebase &&
+ git rebase $* m bp &&
+ test_cmp_rev m HEAD~2 &&
+ test_linear_range 'x y' m..
+ "
+}
+test_run_rebase success ''
+test_run_rebase failure -m
+test_run_rebase success -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* --root on linear history is a no-op" "
+ reset_rebase &&
+ git rebase $* --root c &&
+ test_cmp_rev c HEAD
+ "
+}
+test_run_rebase failure ''
+test_run_rebase failure -m
+test_run_rebase failure -i
+test_run_rebase failure -p
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* -f --root on linear history causes re-write" "
+ reset_rebase &&
+ git rebase $* -f --root c &&
+ ! test_cmp_rev a HEAD~2 &&
+ test_linear_range 'a b c' HEAD
+ "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+test_run_rebase success -p
+
+test_done
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
new file mode 100755
index 0000000..1d195fb
--- /dev/null
+++ b/t/t3425-rebase-topology-merges.sh
@@ -0,0 +1,258 @@
+#!/bin/sh
+
+test_description='rebase topology tests with merges'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+test_revision_subjects () {
+ expected="$1"
+ shift
+ set -- $(git log --format=%s --no-walk=unsorted "$@")
+ test "$expected" = "$*"
+}
+
+# a---b-----------c
+# \ \
+# d-------e \
+# \ \ \
+# n---o---w---v
+# \
+# z
+test_expect_success 'setup of non-linear-history' '
+ test_commit a &&
+ test_commit b &&
+ test_commit c &&
+ git checkout b &&
+ test_commit d &&
+ test_commit e
+
+ git checkout c &&
+ test_commit g &&
+ revert h g &&
+ git checkout d &&
+ cherry_pick gp g &&
+ test_commit i &&
+ git checkout b &&
+ test_commit f
+
+ git checkout d &&
+ test_commit n &&
+ test_commit o &&
+ test_merge w e &&
+ test_merge v c &&
+ git checkout o &&
+ test_commit z
+'
+
+test_run_rebase () {
+ result=$1
+ shift
+ test_expect_$result "rebase $* after merge from upstream" "
+ reset_rebase &&
+ git rebase $* e w &&
+ test_cmp_rev e HEAD~2 &&
+ test_linear_range 'n o' e..
+ "
+}
+test_run_rebase success ''
+test_run_rebase success -m
+test_run_rebase success -i
+
+test_run_rebase () {
+ result=$1
+ shift
+ expected=$1
+ shift
+ test_expect_$result "rebase $* of non-linear history is linearized in place" "
+ reset_rebase &&
+ git rebase $* d w &&
+ test_cmp_rev d HEAD~3 &&
+ test_linear_range "\'"$expected"\'" d..
+ "
+}
+#TODO: make order consistent across all flavors of rebase
+test_run_rebase success 'e n o' ''
+test_run_rebase success 'e n o' -m
+test_run_rebase success 'n o e' -i
+
+test_run_rebase () {
+ result=$1
+ shift
+ expected=$1
+ shift
+ test_expect_$result "rebase $* of non-linear history is linearized upstream" "
+ reset_rebase &&
+ git rebase $* c w &&
+ test_cmp_rev c HEAD~4 &&
+ test_linear_range "\'"$expected"\'" c..
+ "
+}
+#TODO: make order consistent across all flavors of rebase
+test_run_rebase success 'd e n o' ''
+test_run_rebase success 'd e n o' -m
+test_run_rebase success 'd n o e' -i
+
+test_run_rebase () {
+ result=$1
+ shift
+ expected=$1
+ shift
+ test_expect_$result "rebase $* of non-linear history with merges after upstream merge is linearized" "
+ reset_rebase &&
+ git rebase $* c v &&
+ test_cmp_rev c HEAD~4 &&
+ test_linear_range "\'"$expected"\'" c..
+ "
+}
+#TODO: make order consistent across all flavors of rebase
+test_run_rebase success 'd e n o' ''
+test_run_rebase success 'd e n o' -m
+test_run_rebase success 'd n o e' -i
+
+test_expect_success "rebase -p is no-op in non-linear history" "
+ reset_rebase &&
+ git rebase -p d w &&
+ test_cmp_rev w HEAD
+"
+
+test_expect_success "rebase -p is no-op when base inside second parent" "
+ reset_rebase &&
+ git rebase -p e w &&
+ test_cmp_rev w HEAD
+"
+
+test_expect_failure "rebase -p --root on non-linear history is a no-op" "
+ reset_rebase &&
+ git rebase -p --root w &&
+ test_cmp_rev w HEAD
+"
+
+test_expect_success "rebase -p re-creates merge from side branch" "
+ reset_rebase &&
+ git rebase -p z w &&
+ test_cmp_rev z HEAD^ &&
+ test_cmp_rev w^2 HEAD^2
+"
+
+test_expect_success "rebase -p re-creates internal merge" "
+ reset_rebase &&
+ git rebase -p c w &&
+ test_cmp_rev c HEAD~4 &&
+ test_cmp_rev HEAD^2^ HEAD~3 &&
+ test_revision_subjects 'd n e o w' HEAD~3 HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+test_expect_success "rebase -p can re-create two branches on onto" "
+ reset_rebase &&
+ git rebase -p --onto c d w &&
+ test_cmp_rev c HEAD~3 &&
+ test_cmp_rev c HEAD^2^ &&
+ test_revision_subjects 'n e o w' HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+# f
+# /
+# a---b---c---g---h
+# \
+# d---gp--i
+# \ \
+# e-------u
+#
+# gp = cherry-picked g
+# h = reverted g
+test_expect_success 'setup of non-linear-history for patch-equivalence tests' '
+ git checkout e &&
+ test_merge u i
+'
+
+test_expect_success "rebase -p re-creates history around dropped commit matching upstream" "
+ reset_rebase &&
+ git rebase -p h u &&
+ test_cmp_rev h HEAD~3 &&
+ test_cmp_rev HEAD^2^ HEAD~2 &&
+ test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+test_expect_success "rebase -p --onto in merged history drops patches in upstream" "
+ reset_rebase &&
+ git rebase -p --onto f h u &&
+ test_cmp_rev f HEAD~3 &&
+ test_cmp_rev HEAD^2^ HEAD~2 &&
+ test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+test_expect_success "rebase -p --onto in merged history does not drop patches in onto" "
+ reset_rebase &&
+ git rebase -p --onto h f u &&
+ test_cmp_rev h HEAD~3 &&
+ test_cmp_rev HEAD^2~2 HEAD~2 &&
+ test_revision_subjects 'd gp i e u' HEAD~2 HEAD^2^ HEAD^2 HEAD^ HEAD
+"
+
+# a---b---c---g---h
+# \
+# d---gp--s
+# \ \ /
+# \ X
+# \ / \
+# e---t
+#
+# gp = cherry-picked g
+# h = reverted g
+test_expect_success 'setup of non-linear-history for dropping whole side' '
+ git checkout gp &&
+ test_merge s e &&
+ git checkout e &&
+ test_merge t gp
+'
+
+test_expect_failure "rebase -p drops merge commit when entire first-parent side is dropped" "
+ reset_rebase &&
+ git rebase -p h s &&
+ test_cmp_rev h HEAD~2 &&
+ test_linear_range 'd e' h..
+"
+
+test_expect_success "rebase -p drops merge commit when entire second-parent side is dropped" "
+ reset_rebase &&
+ git rebase -p h t &&
+ test_cmp_rev h HEAD~2 &&
+ test_linear_range 'd e' h..
+"
+
+# a---b---c
+# \
+# d---e
+# \ \
+# n---r
+# \
+# o
+#
+# r = tree-same with n
+test_expect_success 'setup of non-linear-history for empty commits' '
+ git checkout n &&
+ git merge --no-commit e &&
+ git reset n . &&
+ git commit -m r &&
+ git reset --hard &&
+ git clean -f &&
+ git tag r
+'
+
+test_expect_success "rebase -p re-creates empty internal merge commit" "
+ reset_rebase &&
+ git rebase -p c r &&
+ test_cmp_rev c HEAD~3 &&
+ test_cmp_rev HEAD^2^ HEAD~2 &&
+ test_revision_subjects 'd e n r' HEAD~2 HEAD^2 HEAD^ HEAD
+"
+
+test_expect_success "rebase -p re-creates empty merge commit" "
+ reset_rebase &&
+ git rebase -p o r &&
+ test_cmp_rev e HEAD^2 &&
+ test_cmp_rev o HEAD^ &&
+ test_revision_subjects 'r' HEAD
+"
+
+test_done
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index a0c6e30..fbdc47c 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -28,29 +28,21 @@ test_expect_success setup '
'
test_expect_success 'cherry-pick an empty commit' '
- git checkout master && {
- git cherry-pick empty-branch^
- test "$?" = 1
- }
+ git checkout master &&
+ test_expect_code 1 git cherry-pick empty-branch^
'
test_expect_success 'index lockfile was removed' '
-
test ! -f .git/index.lock
-
'
test_expect_success 'cherry-pick a commit with an empty message' '
- git checkout master && {
- git cherry-pick empty-branch
- test "$?" = 1
- }
+ git checkout master &&
+ test_expect_code 1 git cherry-pick empty-branch
'
test_expect_success 'index lockfile was removed' '
-
test ! -f .git/index.lock
-
'
test_expect_success 'cherry-pick a commit with an empty message with --allow-empty-message' '
@@ -101,7 +93,7 @@ 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 &&
+ git show -s --format=%s >actual &&
echo "add file2 on master" >expect &&
test_cmp expect actual
'
diff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh
index df921d1..a5b6a5f 100755
--- a/t/t3509-cherry-pick-merge-df.sh
+++ b/t/t3509-cherry-pick-merge-df.sh
@@ -10,17 +10,15 @@ test_expect_success 'Initialize repository' '
git commit -m a
'
-test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' '
+test_expect_success 'Setup rename across paths each below D/F conflicts' '
mkdir b &&
- ln -s ../a b/a &&
- git add b &&
+ test_ln_s_add ../a b/a &&
git commit -m b &&
git checkout -b branch &&
rm b/a &&
- mv a b/a &&
- ln -s b/a a &&
- git add . &&
+ git mv a b/a &&
+ test_ln_s_add b/a a &&
git commit -m swap &&
>f1 &&
@@ -28,7 +26,7 @@ test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts
git commit -m f1
'
-test_expect_success SYMLINKS 'Cherry-pick succeeds with rename across D/F conflicts' '
+test_expect_success 'Cherry-pick succeeds with rename across D/F conflicts' '
git reset --hard &&
git checkout master^0 &&
git cherry-pick branch
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index 0c44e9f..5c87b55 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -687,4 +687,100 @@ test_expect_failure SYMLINKS 'rm across a symlinked leading path (w/ index)' '
test_path_is_file e/f
'
+test_expect_success 'setup for testing rm messages' '
+ >bar.txt &&
+ >foo.txt &&
+ git add bar.txt foo.txt
+'
+
+test_expect_success 'rm files with different staged content' '
+ cat >expect <<-\EOF &&
+ error: the following files have staged content different from both the
+ file and the HEAD:
+ bar.txt
+ foo.txt
+ (use -f to force removal)
+ EOF
+ echo content1 >foo.txt &&
+ echo content1 >bar.txt &&
+ test_must_fail git rm foo.txt bar.txt 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'rm files with different staged content without hints' '
+ cat >expect <<-\EOF &&
+ error: the following files have staged content different from both the
+ file and the HEAD:
+ bar.txt
+ foo.txt
+ EOF
+ echo content2 >foo.txt &&
+ echo content2 >bar.txt &&
+ test_must_fail git -c advice.rmhints=false rm foo.txt bar.txt 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'rm file with local modification' '
+ cat >expect <<-\EOF &&
+ error: the following file has local modifications:
+ foo.txt
+ (use --cached to keep the file, or -f to force removal)
+ EOF
+ git commit -m "testing rm 3" &&
+ echo content3 >foo.txt &&
+ test_must_fail git rm foo.txt 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'rm file with local modification without hints' '
+ cat >expect <<-\EOF &&
+ error: the following file has local modifications:
+ bar.txt
+ EOF
+ echo content4 >bar.txt &&
+ test_must_fail git -c advice.rmhints=false rm bar.txt 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'rm file with changes in the index' '
+ cat >expect <<-\EOF &&
+ error: the following file has changes staged in the index:
+ foo.txt
+ (use --cached to keep the file, or -f to force removal)
+ EOF
+ git reset --hard &&
+ echo content5 >foo.txt &&
+ git add foo.txt &&
+ test_must_fail git rm foo.txt 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'rm file with changes in the index without hints' '
+ cat >expect <<-\EOF &&
+ error: the following file has changes staged in the index:
+ foo.txt
+ EOF
+ test_must_fail git -c advice.rmhints=false rm foo.txt 2>actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'rm files with two different errors' '
+ cat >expect <<-\EOF &&
+ error: the following file has staged content different from both the
+ file and the HEAD:
+ foo1.txt
+ (use -f to force removal)
+ error: the following file has changes staged in the index:
+ bar1.txt
+ (use --cached to keep the file, or -f to force removal)
+ EOF
+ echo content >foo1.txt &&
+ git add foo1.txt &&
+ echo content6 >foo1.txt &&
+ echo content6 >bar1.txt &&
+ git add bar1.txt &&
+ test_must_fail git rm bar1.txt foo1.txt 2>actual &&
+ test_i18ncmp expect actual
+'
+
test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index 874b3a6..aab86e8 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -30,10 +30,9 @@ test_expect_success \
*) echo fail; git ls-files --stage xfoo1; (exit 1);;
esac'
-test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' '
+test_expect_success 'git add: filemode=0 should not get confused by symlink' '
rm -f xfoo1 &&
- ln -s foo xfoo1 &&
- git add xfoo1 &&
+ test_ln_s_add foo xfoo1 &&
case "`git ls-files --stage xfoo1`" in
120000" "*xfoo1) echo pass;;
*) echo fail; git ls-files --stage xfoo1; (exit 1);;
@@ -51,21 +50,19 @@ test_expect_success \
*) echo fail; git ls-files --stage xfoo2; (exit 1);;
esac'
-test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' '
+test_expect_success 'git add: filemode=0 should not get confused by symlink' '
rm -f xfoo2 &&
- ln -s foo xfoo2 &&
- git update-index --add xfoo2 &&
+ test_ln_s_add foo xfoo2 &&
case "`git ls-files --stage xfoo2`" in
120000" "*xfoo2) echo pass;;
*) echo fail; git ls-files --stage xfoo2; (exit 1);;
esac
'
-test_expect_success SYMLINKS \
+test_expect_success \
'git update-index --add: Test that executable bit is not used...' \
'git config core.filemode 0 &&
- ln -s xfoo2 xfoo3 &&
- git update-index --add xfoo3 &&
+ test_ln_s_add xfoo2 xfoo3 && # runs git update-index --add
case "`git ls-files --stage xfoo3`" in
120000" "*xfoo3) echo pass;;
*) echo fail; git ls-files --stage xfoo3; (exit 1);;
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index 37ddabb..38b00c3 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -39,6 +39,42 @@ test_expect_failure 'UTF-16 refused because of NULs' '
git commit -a -F "$TEST_DIRECTORY"/t3900/UTF-16.txt
'
+test_expect_success 'UTF-8 invalid characters refused' '
+ test_when_finished "rm -f $HOME/stderr $HOME/invalid" &&
+ echo "UTF-8 characters" >F &&
+ printf "Commit message\n\nInvalid surrogate:\355\240\200\n" \
+ >"$HOME/invalid" &&
+ git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr &&
+ grep "did not conform" "$HOME"/stderr
+'
+
+test_expect_success 'UTF-8 overlong sequences rejected' '
+ test_when_finished "rm -f $HOME/stderr $HOME/invalid" &&
+ rm -f "$HOME/stderr" "$HOME/invalid" &&
+ echo "UTF-8 overlong" >F &&
+ printf "\340\202\251ommit message\n\nThis is not a space:\300\240\n" \
+ >"$HOME/invalid" &&
+ git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr &&
+ grep "did not conform" "$HOME"/stderr
+'
+
+test_expect_success 'UTF-8 non-characters refused' '
+ test_when_finished "rm -f $HOME/stderr $HOME/invalid" &&
+ echo "UTF-8 non-character 1" >F &&
+ printf "Commit message\n\nNon-character:\364\217\277\276\n" \
+ >"$HOME/invalid" &&
+ git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr &&
+ grep "did not conform" "$HOME"/stderr
+'
+
+test_expect_success 'UTF-8 non-characters refused' '
+ test_when_finished "rm -f $HOME/stderr $HOME/invalid" &&
+ echo "UTF-8 non-character 2." >F &&
+ printf "Commit message\n\nNon-character:\357\267\220\n" \
+ >"$HOME/invalid" &&
+ git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr &&
+ grep "did not conform" "$HOME"/stderr
+'
for H in ISO8859-1 eucJP ISO-2022-JP
do
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 5dfbda7..5d22f17 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -200,17 +200,17 @@ test_expect_success 'apply -q is quiet' '
echo foo > file &&
git stash &&
git stash apply -q > output.out 2>&1 &&
- test ! -s output.out
+ test_must_be_empty output.out
'
test_expect_success 'save -q is quiet' '
git stash save --quiet > output.out 2>&1 &&
- test ! -s output.out
+ test_must_be_empty output.out
'
test_expect_success 'pop -q is quiet' '
git stash pop -q > output.out 2>&1 &&
- test ! -s output.out
+ test_must_be_empty output.out
'
test_expect_success 'pop -q --index works and is quiet' '
@@ -219,13 +219,13 @@ test_expect_success 'pop -q --index works and is quiet' '
git stash save --quiet &&
git stash pop -q --index > output.out 2>&1 &&
test foo = "$(git show :file)" &&
- test ! -s output.out
+ test_must_be_empty output.out
'
test_expect_success 'drop -q is quiet' '
git stash &&
git stash drop -q > output.out 2>&1 &&
- test ! -s output.out
+ test_must_be_empty output.out
'
test_expect_success 'stash -k' '
@@ -336,41 +336,58 @@ test_expect_success SYMLINKS 'stash file to symlink (full stage)' '
# This test creates a commit with a symlink used for the following tests
-test_expect_success SYMLINKS 'stash symlink to file' '
+test_expect_success 'stash symlink to file' '
git reset --hard &&
- ln -s file filelink &&
- git add filelink &&
+ test_ln_s_add file filelink &&
git commit -m "Add symlink" &&
rm filelink &&
cp file filelink &&
- git stash save "symlink to file" &&
+ git stash save "symlink to file"
+'
+
+test_expect_success SYMLINKS 'this must have re-created the symlink' '
test -h filelink &&
- case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+ case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac
+'
+
+test_expect_success 'unstash must re-create the file' '
git stash apply &&
! test -h filelink &&
test bar = "$(cat file)"
'
-test_expect_success SYMLINKS 'stash symlink to file (stage rm)' '
+test_expect_success 'stash symlink to file (stage rm)' '
git reset --hard &&
git rm filelink &&
cp file filelink &&
- git stash save "symlink to file (stage rm)" &&
+ git stash save "symlink to file (stage rm)"
+'
+
+test_expect_success SYMLINKS 'this must have re-created the symlink' '
test -h filelink &&
- case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+ case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac
+'
+
+test_expect_success 'unstash must re-create the file' '
git stash apply &&
! test -h filelink &&
test bar = "$(cat file)"
'
-test_expect_success SYMLINKS 'stash symlink to file (full stage)' '
+test_expect_success 'stash symlink to file (full stage)' '
git reset --hard &&
rm filelink &&
cp file filelink &&
git add filelink &&
- git stash save "symlink to file (full stage)" &&
+ git stash save "symlink to file (full stage)"
+'
+
+test_expect_success SYMLINKS 'this must have re-created the symlink' '
test -h filelink &&
- case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+ case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac
+'
+
+test_expect_success 'unstash must re-create the file' '
git stash apply &&
! test -h filelink &&
test bar = "$(cat file)"
@@ -637,4 +654,41 @@ test_expect_success 'stash where working directory contains "HEAD" file' '
test_cmp output expect
'
+test_expect_success 'store called with invalid commit' '
+ test_must_fail git stash store foo
+'
+
+test_expect_success 'store updates stash ref and reflog' '
+ git stash clear &&
+ git reset --hard &&
+ echo quux >bazzy &&
+ git add bazzy &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ ! grep quux bazzy &&
+ git stash store -m quuxery $STASH_ID &&
+ test $(cat .git/refs/stash) = $STASH_ID &&
+ grep $STASH_ID .git/logs/refs/stash &&
+ git stash pop &&
+ grep quux bazzy
+'
+
+test_expect_success 'stash a change to turn a non-directory to a directory' '
+ git reset --hard &&
+ >testfile &&
+ git add testfile &&
+ git commit -m "add testfile as a regular file" &&
+ rm testfile &&
+ mkdir testfile &&
+ >testfile/file &&
+ test_must_fail git stash save "recover regular file" &&
+ test -f testfile/file
+'
+
+test_expect_success 'stash a change to turn a non-directory to a directory (forced)' '
+ git stash save --force "recover regular file (forced)" &&
+ ! test -f testfile/file &&
+ test -f testfile
+'
+
test_done
diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh
index 73b4a24..27e98a8 100755
--- a/t/t4008-diff-break-rewrite.sh
+++ b/t/t4008-diff-break-rewrite.sh
@@ -99,11 +99,11 @@ test_expect_success \
'validate result of -B -M (#4)' \
'compare_diff_raw expected current'
-test_expect_success SYMLINKS \
+test_expect_success \
'make file0 into something completely different' \
'rm -f file0 &&
- ln -s frotz file0 &&
- git update-index file0 file1'
+ test_ln_s_add frotz file0 &&
+ git update-index file1'
test_expect_success \
'run diff with -B' \
@@ -114,7 +114,7 @@ cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1
EOF
-test_expect_success SYMLINKS \
+test_expect_success \
'validate result of -B (#5)' \
'compare_diff_raw expected current'
@@ -129,7 +129,7 @@ cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R file0 file1
EOF
-test_expect_success SYMLINKS \
+test_expect_success \
'validate result of -B -M (#6)' \
'compare_diff_raw expected current'
@@ -144,7 +144,7 @@ cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M file1
EOF
-test_expect_success SYMLINKS \
+test_expect_success \
'validate result of -M (#7)' \
'compare_diff_raw expected current'
diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh
index f0d5041..13e7f62 100755
--- a/t/t4011-diff-symlink.sh
+++ b/t/t4011-diff-symlink.sh
@@ -9,7 +9,7 @@ test_description='Test diff of symlinks.
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh
-test_expect_success SYMLINKS 'diff new symlink and file' '
+test_expect_success 'diff new symlink and file' '
cat >expected <<-\EOF &&
diff --git a/frotz b/frotz
new file mode 120000
@@ -27,22 +27,25 @@ test_expect_success SYMLINKS 'diff new symlink and file' '
@@ -0,0 +1 @@
+xyzzy
EOF
- ln -s xyzzy frotz &&
- echo xyzzy >nitfol &&
+
+ # the empty tree
git update-index &&
tree=$(git write-tree) &&
- git update-index --add frotz nitfol &&
+
+ test_ln_s_add xyzzy frotz &&
+ echo xyzzy >nitfol &&
+ git update-index --add nitfol &&
GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current &&
compare_diff_patch expected current
'
-test_expect_success SYMLINKS 'diff unchanged symlink and file' '
+test_expect_success 'diff unchanged symlink and file' '
tree=$(git write-tree) &&
git update-index frotz nitfol &&
test -z "$(git diff-index --name-only $tree)"
'
-test_expect_success SYMLINKS 'diff removed symlink and file' '
+test_expect_success 'diff removed symlink and file' '
cat >expected <<-\EOF &&
diff --git a/frotz b/frotz
deleted file mode 120000
@@ -66,12 +69,18 @@ test_expect_success SYMLINKS 'diff removed symlink and file' '
compare_diff_patch expected current
'
-test_expect_success SYMLINKS 'diff identical, but newly created symlink and file' '
+test_expect_success 'diff identical, but newly created symlink and file' '
>expected &&
rm -f frotz nitfol &&
echo xyzzy >nitfol &&
test-chmtime +10 nitfol &&
- ln -s xyzzy frotz &&
+ if test_have_prereq SYMLINKS
+ then
+ ln -s xyzzy frotz
+ else
+ printf xyzzy >frotz
+ # the symlink property propagates from the index
+ fi &&
git diff-index -M -p $tree >current &&
compare_diff_patch expected current &&
@@ -80,7 +89,7 @@ test_expect_success SYMLINKS 'diff identical, but newly created symlink and file
compare_diff_patch expected current
'
-test_expect_success SYMLINKS 'diff different symlink and file' '
+test_expect_success 'diff different symlink and file' '
cat >expected <<-\EOF &&
diff --git a/frotz b/frotz
index 7c465af..df1db54 120000
@@ -100,7 +109,13 @@ test_expect_success SYMLINKS 'diff different symlink and file' '
+yxyyz
EOF
rm -f frotz &&
- ln -s yxyyz frotz &&
+ if test_have_prereq SYMLINKS
+ then
+ ln -s yxyyz frotz
+ else
+ printf yxyyz >frotz
+ # the symlink property propagates from the index
+ fi &&
echo yxyyz >nitfol &&
git diff-index -M -p $tree >current &&
compare_diff_patch expected current
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 58d4180..668933b 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -972,6 +972,49 @@ test_expect_success 'empty subject prefix does not have extra space' '
test_cmp expect actual
'
+test_expect_success '--from=ident notices bogus ident' '
+ test_must_fail git format-patch -1 --stdout --from=foo >patch
+'
+
+test_expect_success '--from=ident replaces author' '
+ git format-patch -1 --stdout --from="Me <me@example.com>" >patch &&
+ cat >expect <<-\EOF &&
+ From: Me <me@example.com>
+
+ From: A U Thor <author@example.com>
+
+ EOF
+ sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
+ test_cmp expect patch.head
+'
+
+test_expect_success '--from uses committer ident' '
+ git format-patch -1 --stdout --from >patch &&
+ cat >expect <<-\EOF &&
+ From: C O Mitter <committer@example.com>
+
+ From: A U Thor <author@example.com>
+
+ EOF
+ sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
+ test_cmp expect patch.head
+'
+
+test_expect_success 'in-body headers trigger content encoding' '
+ GIT_AUTHOR_NAME="éxötìc" test_commit exotic &&
+ test_when_finished "git reset --hard HEAD^" &&
+ git format-patch -1 --stdout --from >patch &&
+ cat >expect <<-\EOF &&
+ From: C O Mitter <committer@example.com>
+ Content-Type: text/plain; charset=UTF-8
+
+ From: éxötìc <author@example.com>
+
+ EOF
+ sed -ne "/^From:/p; /^$/p; /^Content-Type/p; /^---$/q" <patch >patch.head &&
+ test_cmp expect patch.head
+'
+
append_signoff()
{
C=$(git commit-tree HEAD^^{tree} -p HEAD) &&
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index cc3db13..3fb4b97 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -142,6 +142,351 @@ EOF
git diff --ignore-space-at-eol > out
test_expect_success 'another test, with --ignore-space-at-eol' 'test_cmp expect out'
+test_expect_success 'ignore-blank-lines: only new lines' '
+ test_seq 5 >x &&
+ git update-index x &&
+ test_seq 5 | sed "/3/i \\
+" >x &&
+ git diff --ignore-blank-lines >out &&
+ >expect &&
+ test_cmp out expect
+'
+
+test_expect_success 'ignore-blank-lines: only new lines with space' '
+ test_seq 5 >x &&
+ git update-index x &&
+ test_seq 5 | sed "/3/i \ " >x &&
+ git diff -w --ignore-blank-lines >out &&
+ >expect &&
+ test_cmp out expect
+'
+
+test_expect_success 'ignore-blank-lines: after change' '
+ cat <<-\EOF >x &&
+ 1
+ 2
+
+ 3
+ 4
+ 5
+
+ 6
+ 7
+ EOF
+ git update-index x &&
+ cat <<-\EOF >x &&
+ change
+
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+
+ 7
+ EOF
+ git diff --inter-hunk-context=100 --ignore-blank-lines >out.tmp &&
+ cat <<-\EOF >expected &&
+ diff --git a/x b/x
+ --- a/x
+ +++ b/x
+ @@ -1,6 +1,7 @@
+ +change
+ +
+ 1
+ 2
+ -
+ 3
+ 4
+ 5
+ EOF
+ compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: before change' '
+ cat <<-\EOF >x &&
+ 1
+ 2
+
+ 3
+ 4
+ 5
+ 6
+ 7
+ EOF
+ git update-index x &&
+ cat <<-\EOF >x &&
+
+ 1
+ 2
+ 3
+ 4
+ 5
+
+ 6
+ 7
+ change
+ EOF
+ git diff --inter-hunk-context=100 --ignore-blank-lines >out.tmp &&
+ cat <<-\EOF >expected &&
+ diff --git a/x b/x
+ --- a/x
+ +++ b/x
+ @@ -4,5 +4,7 @@
+ 3
+ 4
+ 5
+ +
+ 6
+ 7
+ +change
+ EOF
+ compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: between changes' '
+ cat <<-\EOF >x &&
+ 1
+ 2
+ 3
+ 4
+ 5
+
+
+ 6
+ 7
+ 8
+ 9
+ 10
+ EOF
+ git update-index x &&
+ cat <<-\EOF >x &&
+ change
+ 1
+ 2
+
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+
+ 9
+ 10
+ change
+ EOF
+ git diff --ignore-blank-lines >out.tmp &&
+ cat <<-\EOF >expected &&
+ diff --git a/x b/x
+ --- a/x
+ +++ b/x
+ @@ -1,5 +1,7 @@
+ +change
+ 1
+ 2
+ +
+ 3
+ 4
+ 5
+ @@ -8,5 +8,7 @@
+ 6
+ 7
+ 8
+ +
+ 9
+ 10
+ +change
+ EOF
+ compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: between changes (with interhunkctx)' '
+ test_seq 10 >x &&
+ git update-index x &&
+ cat <<-\EOF >x &&
+ change
+ 1
+ 2
+
+ 3
+ 4
+ 5
+
+ 6
+ 7
+ 8
+ 9
+
+ 10
+ change
+ EOF
+ git diff --inter-hunk-context=2 --ignore-blank-lines >out.tmp &&
+ cat <<-\EOF >expected &&
+ diff --git a/x b/x
+ --- a/x
+ +++ b/x
+ @@ -1,10 +1,15 @@
+ +change
+ 1
+ 2
+ +
+ 3
+ 4
+ 5
+ +
+ 6
+ 7
+ 8
+ 9
+ +
+ 10
+ +change
+ EOF
+ compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: scattered spaces' '
+ test_seq 10 >x &&
+ git update-index x &&
+ cat <<-\EOF >x &&
+ change
+ 1
+ 2
+ 3
+
+ 4
+
+ 5
+
+ 6
+
+
+ 7
+
+ 8
+ 9
+ 10
+ change
+ EOF
+ git diff --inter-hunk-context=4 --ignore-blank-lines >out.tmp &&
+ cat <<-\EOF >expected &&
+ diff --git a/x b/x
+ --- a/x
+ +++ b/x
+ @@ -1,3 +1,4 @@
+ +change
+ 1
+ 2
+ 3
+ @@ -8,3 +15,4 @@
+ 8
+ 9
+ 10
+ +change
+ EOF
+ compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: spaces coalesce' '
+ test_seq 6 >x &&
+ git update-index x &&
+ cat <<-\EOF >x &&
+ change
+ 1
+ 2
+ 3
+
+ 4
+
+ 5
+
+ 6
+ change
+ EOF
+ git diff --inter-hunk-context=4 --ignore-blank-lines >out.tmp &&
+ cat <<-\EOF >expected &&
+ diff --git a/x b/x
+ --- a/x
+ +++ b/x
+ @@ -1,6 +1,11 @@
+ +change
+ 1
+ 2
+ 3
+ +
+ 4
+ +
+ 5
+ +
+ 6
+ +change
+ EOF
+ compare_diff_patch expected out.tmp
+'
+
+test_expect_success 'ignore-blank-lines: mix changes and blank lines' '
+ test_seq 16 >x &&
+ git update-index x &&
+ cat <<-\EOF >x &&
+ change
+ 1
+ 2
+
+ 3
+ 4
+ 5
+ change
+ 6
+ 7
+ 8
+
+ 9
+ 10
+ 11
+ change
+ 12
+ 13
+ 14
+
+ 15
+ 16
+ change
+ EOF
+ git diff --ignore-blank-lines >out.tmp &&
+ cat <<-\EOF >expected &&
+ diff --git a/x b/x
+ --- a/x
+ +++ b/x
+ @@ -1,8 +1,11 @@
+ +change
+ 1
+ 2
+ +
+ 3
+ 4
+ 5
+ +change
+ 6
+ 7
+ 8
+ @@ -9,8 +13,11 @@
+ 9
+ 10
+ 11
+ +change
+ 12
+ 13
+ 14
+ +
+ 15
+ 16
+ +change
+ EOF
+ compare_diff_patch expected out.tmp
+'
+
test_expect_success 'check mixed spaces and tabs in indent' '
# This is indented with SP HT SP.
diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh
index 5d20acf..55d549f 100755
--- a/t/t4023-diff-rename-typechange.sh
+++ b/t/t4023-diff-rename-typechange.sh
@@ -4,44 +4,44 @@ test_description='typechange rename detection'
. ./test-lib.sh
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
rm -f foo bar &&
cat "$TEST_DIRECTORY"/../COPYING >foo &&
- ln -s linklink bar &&
- git add foo bar &&
+ test_ln_s_add linklink bar &&
+ git add foo &&
git commit -a -m Initial &&
git tag one &&
- rm -f foo bar &&
+ git rm -f foo bar &&
cat "$TEST_DIRECTORY"/../COPYING >bar &&
- ln -s linklink foo &&
- git add foo bar &&
+ test_ln_s_add linklink foo &&
+ git add bar &&
git commit -a -m Second &&
git tag two &&
- rm -f foo bar &&
+ git rm -f foo bar &&
cat "$TEST_DIRECTORY"/../COPYING >foo &&
git add foo &&
git commit -a -m Third &&
git tag three &&
mv foo bar &&
- ln -s linklink foo &&
- git add foo bar &&
+ test_ln_s_add linklink foo &&
+ git add bar &&
git commit -a -m Fourth &&
git tag four &&
# This is purely for sanity check
- rm -f foo bar &&
+ git rm -f foo bar &&
cat "$TEST_DIRECTORY"/../COPYING >foo &&
cat "$TEST_DIRECTORY"/../Makefile >bar &&
git add foo bar &&
git commit -a -m Fifth &&
git tag five &&
- rm -f foo bar &&
+ git rm -f foo bar &&
cat "$TEST_DIRECTORY"/../Makefile >foo &&
cat "$TEST_DIRECTORY"/../COPYING >bar &&
git add foo bar &&
@@ -50,7 +50,7 @@ test_expect_success SYMLINKS setup '
'
-test_expect_success SYMLINKS 'cross renames to be detected for regular files' '
+test_expect_success 'cross renames to be detected for regular files' '
git diff-tree five six -r --name-status -B -M | sort >actual &&
{
@@ -61,7 +61,7 @@ test_expect_success SYMLINKS 'cross renames to be detected for regular files' '
'
-test_expect_success SYMLINKS 'cross renames to be detected for typechange' '
+test_expect_success 'cross renames to be detected for typechange' '
git diff-tree one two -r --name-status -B -M | sort >actual &&
{
@@ -72,7 +72,7 @@ test_expect_success SYMLINKS 'cross renames to be detected for typechange' '
'
-test_expect_success SYMLINKS 'moves and renames' '
+test_expect_success 'moves and renames' '
git diff-tree three four -r --name-status -B -M | sort >actual &&
{
diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh
index 53ec330..f75f46f 100755
--- a/t/t4030-diff-textconv.sh
+++ b/t/t4030-diff-textconv.sh
@@ -139,12 +139,10 @@ index 0000000..67be421
+frotz
\ No newline at end of file
EOF
-# make a symlink the hard way that works on symlink-challenged file systems
+
test_expect_success 'textconv does not act on symlinks' '
- printf frotz > file &&
- git add file &&
- git ls-files -s | sed -e s/100644/120000/ |
- git update-index --index-info &&
+ rm -f file &&
+ test_ln_s_add frotz file &&
git commit -m typechange &&
git show >diff &&
find_diff <diff >actual &&
diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh
index 1261dbb..1019d7b 100755
--- a/t/t4038-diff-combined.sh
+++ b/t/t4038-diff-combined.sh
@@ -353,4 +353,52 @@ test_expect_failure 'combine diff coalesce three parents' '
compare_diff_patch expected actual
'
+# Test for a bug reported at
+# http://thread.gmane.org/gmane.comp.version-control.git/224410
+# where a delete lines were missing from combined diff output when they
+# occurred exactly before the context lines of a later change.
+test_expect_success 'combine diff missing delete bug' '
+ git commit -m initial --allow-empty &&
+ cat <<-\EOF >test &&
+ 1
+ 2
+ 3
+ 4
+ EOF
+ git add test &&
+ git commit -a -m side1 &&
+ git checkout -B side1 &&
+ git checkout HEAD^ &&
+ cat <<-\EOF >test &&
+ 0
+ 1
+ 2
+ 3
+ 4modified
+ EOF
+ git add test &&
+ git commit -m side2 &&
+ git branch -f side2 &&
+ test_must_fail git merge --no-commit side1 &&
+ cat <<-\EOF >test &&
+ 1
+ 2
+ 3
+ 4modified
+ EOF
+ git add test &&
+ git commit -a -m merge &&
+ git diff-tree -c -p HEAD >actual.tmp &&
+ sed -e "1,/^@@@/d" < actual.tmp >actual &&
+ tr -d Q <<-\EOF >expected &&
+ - 0
+ 1
+ 2
+ 3
+ -4
+ +4modified
+ EOF
+ compare_diff_patch expected actual
+'
+
test_done
diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh
index 32d4a60..1751c83 100755
--- a/t/t4041-diff-submodule-option.sh
+++ b/t/t4041-diff-submodule-option.sh
@@ -1,6 +1,7 @@
#!/bin/sh
#
# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin
+# Copyright (c) 2011 Alexey Shumkin (+ non-UTF-8 commit encoding tests)
#
test_description='Support for verbose submodule differences in git diff
@@ -10,6 +11,9 @@ This test tries to verify the sanity of the --submodule option of git diff.
. ./test-lib.sh
+# String "added" in German (translated with Google Translate), encoded in UTF-8,
+# used in sample commit log messages in add_file() function below.
+added=$(printf "hinzugef\303\274gt")
add_file () {
(
cd "$1" &&
@@ -19,7 +23,8 @@ add_file () {
echo "$name" >"$name" &&
git add "$name" &&
test_tick &&
- git commit -m "Add $name" || exit
+ msg_added_iso88591=$(echo "Add $name ($added $name)" | iconv -f utf-8 -t iso8859-1) &&
+ git -c 'i18n.commitEncoding=iso8859-1' commit -m "$msg_added_iso88591"
done >/dev/null &&
git rev-parse --short --verify HEAD
)
@@ -93,7 +98,7 @@ test_expect_success 'modified submodule(forward)' '
git diff-index -p --submodule=log HEAD >actual &&
cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
- > Add foo3
+ > Add foo3 ($added foo3)
EOF
test_cmp expected actual
'
@@ -102,7 +107,7 @@ test_expect_success 'modified submodule(forward)' '
git diff --submodule=log >actual &&
cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
- > Add foo3
+ > Add foo3 ($added foo3)
EOF
test_cmp expected actual
'
@@ -111,7 +116,7 @@ test_expect_success 'modified submodule(forward) --submodule' '
git diff --submodule >actual &&
cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
- > Add foo3
+ > Add foo3 ($added foo3)
EOF
test_cmp expected actual
'
@@ -142,8 +147,8 @@ test_expect_success 'modified submodule(backward)' '
git diff-index -p --submodule=log HEAD >actual &&
cat >expected <<-EOF &&
Submodule sm1 $head2..$head3 (rewind):
- < Add foo3
- < Add foo2
+ < Add foo3 ($added foo3)
+ < Add foo2 ($added foo2)
EOF
test_cmp expected actual
'
@@ -153,10 +158,10 @@ test_expect_success 'modified submodule(backward and forward)' '
git diff-index -p --submodule=log HEAD >actual &&
cat >expected <<-EOF &&
Submodule sm1 $head2...$head4:
- > Add foo5
- > Add foo4
- < Add foo3
- < Add foo2
+ > Add foo5 ($added foo5)
+ > Add foo4 ($added foo4)
+ < Add foo3 ($added foo3)
+ < Add foo2 ($added foo2)
EOF
test_cmp expected actual
'
diff --git a/t/t4111-apply-subdir.sh b/t/t4111-apply-subdir.sh
index 7c39843..1618a6d 100755
--- a/t/t4111-apply-subdir.sh
+++ b/t/t4111-apply-subdir.sh
@@ -86,6 +86,20 @@ test_expect_success 'apply --index from subdir of toplevel' '
test_cmp expected sub/dir/file
'
+test_expect_success 'apply half-broken patch from subdir of toplevel' '
+ (
+ cd sub/dir &&
+ test_must_fail git apply <<-EOF
+ --- sub/dir/file
+ +++ sub/dir/file
+ @@ -1,0 +1,0 @@
+ --- file_in_root
+ +++ file_in_root
+ @@ -1,0 +1,0 @@
+ EOF
+ )
+'
+
test_expect_success 'apply from .git dir' '
cp postimage expected &&
cp preimage .git/file &&
diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh
index f12826f..ebadbc3 100755
--- a/t/t4114-apply-typechange.sh
+++ b/t/t4114-apply-typechange.sh
@@ -9,20 +9,19 @@ test_description='git apply should not get confused with type changes.
. ./test-lib.sh
-test_expect_success SYMLINKS 'setup repository and commits' '
+test_expect_success 'setup repository and commits' '
echo "hello world" > foo &&
echo "hi planet" > bar &&
git update-index --add foo bar &&
git commit -m initial &&
git branch initial &&
rm -f foo &&
- ln -s bar foo &&
- git update-index foo &&
+ test_ln_s_add bar foo &&
git commit -m "foo symlinked to bar" &&
git branch foo-symlinked-to-bar &&
- rm -f foo &&
+ git rm -f foo &&
echo "how far is the sun?" > foo &&
- git update-index foo &&
+ git update-index --add foo &&
git commit -m "foo back to file" &&
git branch foo-back-to-file &&
printf "\0" > foo &&
@@ -42,7 +41,7 @@ test_expect_success SYMLINKS 'setup repository and commits' '
git branch foo-baz-renamed-from-foo
'
-test_expect_success SYMLINKS 'file renamed from foo to foo/baz' '
+test_expect_success 'file renamed from foo to foo/baz' '
git checkout -f initial &&
git diff-tree -M -p HEAD foo-baz-renamed-from-foo > patch &&
git apply --index < patch
@@ -50,7 +49,7 @@ test_expect_success SYMLINKS 'file renamed from foo to foo/baz' '
test_debug 'cat patch'
-test_expect_success SYMLINKS 'file renamed from foo/baz to foo' '
+test_expect_success 'file renamed from foo/baz to foo' '
git checkout -f foo-baz-renamed-from-foo &&
git diff-tree -M -p HEAD initial > patch &&
git apply --index < patch
@@ -58,7 +57,7 @@ test_expect_success SYMLINKS 'file renamed from foo/baz to foo' '
test_debug 'cat patch'
-test_expect_success SYMLINKS 'directory becomes file' '
+test_expect_success 'directory becomes file' '
git checkout -f foo-becomes-a-directory &&
git diff-tree -p HEAD initial > patch &&
git apply --index < patch
@@ -66,7 +65,7 @@ test_expect_success SYMLINKS 'directory becomes file' '
test_debug 'cat patch'
-test_expect_success SYMLINKS 'file becomes directory' '
+test_expect_success 'file becomes directory' '
git checkout -f initial &&
git diff-tree -p HEAD foo-becomes-a-directory > patch &&
git apply --index < patch
@@ -74,7 +73,7 @@ test_expect_success SYMLINKS 'file becomes directory' '
test_debug 'cat patch'
-test_expect_success SYMLINKS 'file becomes symlink' '
+test_expect_success 'file becomes symlink' '
git checkout -f initial &&
git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
git apply --index < patch
@@ -82,21 +81,21 @@ test_expect_success SYMLINKS 'file becomes symlink' '
test_debug 'cat patch'
-test_expect_success SYMLINKS 'symlink becomes file' '
+test_expect_success 'symlink becomes file' '
git checkout -f foo-symlinked-to-bar &&
git diff-tree -p HEAD foo-back-to-file > patch &&
git apply --index < patch
'
test_debug 'cat patch'
-test_expect_success SYMLINKS 'binary file becomes symlink' '
+test_expect_success 'binary file becomes symlink' '
git checkout -f foo-becomes-binary &&
git diff-tree -p --binary HEAD foo-symlinked-to-bar > patch &&
git apply --index < patch
'
test_debug 'cat patch'
-test_expect_success SYMLINKS 'symlink becomes binary file' '
+test_expect_success 'symlink becomes binary file' '
git checkout -f foo-symlinked-to-bar &&
git diff-tree -p --binary HEAD foo-becomes-binary > patch &&
git apply --index < patch
@@ -104,7 +103,7 @@ test_expect_success SYMLINKS 'symlink becomes binary file' '
test_debug 'cat patch'
-test_expect_success SYMLINKS 'symlink becomes directory' '
+test_expect_success 'symlink becomes directory' '
git checkout -f foo-symlinked-to-bar &&
git diff-tree -p HEAD foo-becomes-a-directory > patch &&
git apply --index < patch
@@ -112,7 +111,7 @@ test_expect_success SYMLINKS 'symlink becomes directory' '
test_debug 'cat patch'
-test_expect_success SYMLINKS 'directory becomes symlink' '
+test_expect_success 'directory becomes symlink' '
git checkout -f foo-becomes-a-directory &&
git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
git apply --index < patch
diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh
index 7674dd2..872fcda 100755
--- a/t/t4115-apply-symlink.sh
+++ b/t/t4115-apply-symlink.sh
@@ -9,18 +9,16 @@ test_description='git apply symlinks and partial files
. ./test-lib.sh
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
- ln -s path1/path2/path3/path4/path5 link1 &&
- git add link? &&
+ test_ln_s_add path1/path2/path3/path4/path5 link1 &&
git commit -m initial &&
git branch side &&
rm -f link? &&
- ln -s htap6 link1 &&
- git update-index link? &&
+ test_ln_s_add htap6 link1 &&
git commit -m second &&
git diff-tree -p HEAD^ HEAD >patch &&
@@ -37,7 +35,7 @@ test_expect_success SYMLINKS 'apply symlink patch' '
'
-test_expect_success SYMLINKS 'apply --index symlink patch' '
+test_expect_success 'apply --index symlink patch' '
git checkout -f side &&
git apply --index patch &&
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 3940737..70b3a06 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -10,11 +10,11 @@ lecho () {
done
}
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
mkdir -p arch/i386/boot arch/x86_64 &&
lecho 1 2 3 4 5 >arch/i386/boot/Makefile &&
- ln -s ../i386/boot arch/x86_64/boot &&
+ test_ln_s_add ../i386/boot arch/x86_64/boot &&
git add . &&
test_tick &&
git commit -m initial &&
@@ -31,7 +31,7 @@ test_expect_success SYMLINKS setup '
'
-test_expect_success SYMLINKS apply '
+test_expect_success apply '
git checkout test &&
git diff --exit-code test &&
@@ -40,7 +40,7 @@ test_expect_success SYMLINKS apply '
'
-test_expect_success SYMLINKS 'check result' '
+test_expect_success 'check result' '
git diff --exit-code master &&
git diff --exit-code --cached master &&
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 12f6b02..5edb79a 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -147,7 +147,7 @@ test_expect_success 'am applies patch correctly' '
git checkout first &&
test_tick &&
git am <patch1 &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .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^)"
@@ -158,7 +158,7 @@ test_expect_success 'am applies patch e-mail not in a mbox' '
git reset --hard &&
git checkout first &&
git am patch1.eml &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .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^)"
@@ -169,7 +169,7 @@ test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
git reset --hard &&
git checkout first &&
git am patch1-crlf.eml &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .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^)"
@@ -180,7 +180,7 @@ test_expect_success 'am applies patch e-mail with preceding whitespace' '
git reset --hard &&
git checkout first &&
git am patch1-ws.eml &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .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^)"
@@ -206,7 +206,7 @@ test_expect_success 'am changes committer and keeps author' '
git reset --hard &&
git checkout first &&
git am patch2 &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
git diff --exit-code master..HEAD &&
git diff --exit-code master^..HEAD^ &&
@@ -258,7 +258,7 @@ test_expect_success 'am --keep really keeps the subject' '
git reset --hard &&
git checkout HEAD^ &&
git am --keep patch4 &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git cat-file commit HEAD >actual &&
grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual
'
@@ -268,7 +268,7 @@ test_expect_success 'am --keep-non-patch really keeps the non-patch part' '
git reset --hard &&
git checkout HEAD^ &&
git am --keep-non-patch patch4 &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git cat-file commit HEAD >actual &&
grep "^\[foo\] third" actual
'
@@ -283,7 +283,7 @@ test_expect_success 'am -3 falls back to 3-way merge' '
test_tick &&
git commit -m "copied stuff" &&
git am -3 lorem-move.patch &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git diff --exit-code lorem
'
@@ -297,7 +297,7 @@ test_expect_success 'am -3 -p0 can read --no-prefix patch' '
test_tick &&
git commit -m "copied stuff" &&
git am -3 -p0 lorem-zero.patch &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git diff --exit-code lorem
'
@@ -307,7 +307,7 @@ test_expect_success 'am can rename a file' '
git reset --hard &&
git checkout lorem^0 &&
git am rename.patch &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git update-index --refresh &&
git diff --exit-code rename
'
@@ -318,7 +318,7 @@ test_expect_success 'am -3 can rename a file' '
git reset --hard &&
git checkout lorem^0 &&
git am -3 rename.patch &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git update-index --refresh &&
git diff --exit-code rename
'
@@ -329,7 +329,7 @@ test_expect_success 'am -3 can rename a file after falling back to 3-way merge'
git reset --hard &&
git checkout lorem^0 &&
git am -3 rename-add.patch &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git update-index --refresh &&
git diff --exit-code rename
'
@@ -358,11 +358,17 @@ test_expect_success 'am pauses on conflict' '
test_expect_success 'am --skip works' '
echo goodbye >expected &&
git am --skip &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git diff --exit-code lorem2^^ -- file &&
test_cmp expected another
'
+test_expect_success 'am --abort removes a stray directory' '
+ mkdir .git/rebase-apply &&
+ git am --abort &&
+ test_path_is_missing .git/rebase-apply
+'
+
test_expect_success 'am --resolved works' '
echo goodbye >expected &&
rm -fr .git/rebase-apply &&
@@ -373,7 +379,7 @@ test_expect_success 'am --resolved works' '
echo resolved >>file &&
git add file &&
git am --resolved &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
test_cmp expected another
'
@@ -382,7 +388,7 @@ test_expect_success 'am takes patches from a Pine mailbox' '
git reset --hard &&
git checkout first &&
cat pine patch1 | git am &&
- ! test -d .git/rebase-apply &&
+ test_path_is_missing .git/rebase-apply &&
git diff --exit-code master^..HEAD
'
@@ -391,7 +397,7 @@ test_expect_success 'am fails on mail without patch' '
git reset --hard &&
test_must_fail git am <failmail &&
git am --abort &&
- ! test -d .git/rebase-apply
+ test_path_is_missing .git/rebase-apply
'
test_expect_success 'am fails on empty patch' '
@@ -400,7 +406,7 @@ test_expect_success 'am fails on empty patch' '
echo "---" >>failmail &&
test_must_fail git am <failmail &&
git am --skip &&
- ! test -d .git/rebase-apply
+ test_path_is_missing .git/rebase-apply
'
test_expect_success 'am works from stdin in subdirectory' '
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 9243a97..cb03d28 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -530,6 +530,20 @@ test_expect_success 'show added path under "--follow -M"' '
)
'
+test_expect_success 'git log -c --follow' '
+ test_create_repo follow-c &&
+ (
+ cd follow-c &&
+ test_commit initial file original &&
+ git rm file &&
+ test_commit rename file2 original &&
+ git reset --hard initial &&
+ test_commit modify file foo &&
+ git merge -m merge rename &&
+ git log -c --follow file2
+ )
+'
+
cat >expect <<\EOF
* commit COMMIT_OBJECT_NAME
|\ Merge: MERGE_PARENTS
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
index 842b754..c32df80 100755
--- a/t/t4203-mailmap.sh
+++ b/t/t4203-mailmap.sh
@@ -247,6 +247,24 @@ test_expect_success 'cleanup after mailmap.blob tests' '
rm -f .mailmap
'
+test_expect_success 'single-character name' '
+ echo " 1 A <author@example.com>" >expect &&
+ echo " 1 nick1 <bugs@company.xx>" >>expect &&
+ echo "A <author@example.com>" >.mailmap &&
+ test_when_finished "rm .mailmap" &&
+ git shortlog -es HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'preserve canonical email case' '
+ echo " 1 A U Thor <AUTHOR@example.com>" >expect &&
+ echo " 1 nick1 <bugs@company.xx>" >>expect &&
+ echo "<AUTHOR@example.com> <author@example.com>" >.mailmap &&
+ test_when_finished "rm .mailmap" &&
+ git shortlog -es HEAD >actual &&
+ test_cmp expect actual
+'
+
# Extended mailmap configurations should give us the following output for shortlog
cat >expect <<\EOF
A U Thor <author@example.com> (1):
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index 26fbfde..fb00041 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -1,20 +1,38 @@
#!/bin/sh
#
# Copyright (c) 2010, Will Palmer
+# Copyright (c) 2011, Alexey Shumkin (+ non-UTF-8 commit encoding tests)
#
test_description='Test pretty formats'
. ./test-lib.sh
+sample_utf8_part=$(printf "f\303\244ng")
+
+commit_msg () {
+ # String "initial. initial" partly in German
+ # (translated with Google Translate),
+ # encoded in UTF-8, used as a commit log message below.
+ msg="initial. an${sample_utf8_part}lich\n"
+ if test -n "$1"
+ then
+ printf "$msg" | iconv -f utf-8 -t "$1"
+ else
+ printf "$msg"
+ fi
+}
+
test_expect_success 'set up basic repos' '
>foo &&
>bar &&
git add foo &&
test_tick &&
- git commit -m initial &&
+ git config i18n.commitEncoding iso8859-1 &&
+ git commit -m "$(commit_msg iso8859-1)" &&
git add bar &&
test_tick &&
- git commit -m "add bar"
+ git commit -m "add bar" &&
+ git config --unset i18n.commitEncoding
'
test_expect_success 'alias builtin format' '
@@ -38,6 +56,20 @@ test_expect_success 'alias user-defined format' '
test_cmp expected actual
'
+test_expect_success 'alias user-defined tformat with %s (iso8859-1 encoding)' '
+ git config i18n.logOutputEncoding iso8859-1 &&
+ git log --oneline >expected-s &&
+ git log --pretty="tformat:%h %s" >actual-s &&
+ git config --unset i18n.logOutputEncoding &&
+ test_cmp expected-s actual-s
+'
+
+test_expect_success 'alias user-defined tformat with %s (utf-8 encoding)' '
+ git log --oneline >expected-s &&
+ git log --pretty="tformat:%h %s" >actual-s &&
+ test_cmp expected-s actual-s
+'
+
test_expect_success 'alias user-defined tformat' '
git log --pretty="tformat:%h" >expected &&
git config pretty.test-alias "tformat:%h" &&
@@ -72,13 +104,13 @@ test_expect_success 'alias loop' '
'
test_expect_success 'NUL separation' '
- printf "add bar\0initial" >expected &&
+ printf "add bar\0$(commit_msg)" >expected &&
git log -z --pretty="format:%s" >actual &&
test_cmp expected actual
'
test_expect_success 'NUL termination' '
- printf "add bar\0initial\0" >expected &&
+ printf "add bar\0$(commit_msg)\0" >expected &&
git log -z --pretty="tformat:%s" >actual &&
test_cmp expected actual
'
@@ -86,7 +118,7 @@ test_expect_success 'NUL termination' '
test_expect_success 'NUL separation with --stat' '
stat0_part=$(git diff --stat HEAD^ HEAD) &&
stat1_part=$(git diff-tree --no-commit-id --stat --root HEAD^) &&
- printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n" >expected &&
+ printf "add bar\n$stat0_part\n\0$(commit_msg)\n$stat1_part\n" >expected &&
git log -z --stat --pretty="format:%s" >actual &&
test_i18ncmp expected actual
'
@@ -94,25 +126,29 @@ test_expect_success 'NUL separation with --stat' '
test_expect_failure 'NUL termination with --stat' '
stat0_part=$(git diff --stat HEAD^ HEAD) &&
stat1_part=$(git diff-tree --no-commit-id --stat --root HEAD^) &&
- printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n\0" >expected &&
+ printf "add bar\n$stat0_part\n\0$(commit_msg)\n$stat1_part\n0" >expected &&
git log -z --stat --pretty="tformat:%s" >actual &&
test_i18ncmp expected actual
'
test_expect_success 'setup more commits' '
test_commit "message one" one one message-one &&
- test_commit "message two" two two message-two
+ test_commit "message two" two two message-two &&
+ head1=$(git rev-parse --verify --short HEAD~0) &&
+ head2=$(git rev-parse --verify --short HEAD~1) &&
+ head3=$(git rev-parse --verify --short HEAD~2) &&
+ head4=$(git rev-parse --verify --short HEAD~3)
'
test_expect_success 'left alignment formatting' '
git log --pretty="format:%<(40)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
+ qz_to_tab_space <<EOF >expected &&
message two Z
message one Z
add bar Z
-initial Z
+$(commit_msg) Z
EOF
test_cmp expected actual
'
@@ -121,11 +157,11 @@ test_expect_success 'left alignment formatting at the nth column' '
git log --pretty="format:%h %<|(40)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
-fa33ab1 message two Z
-7cd6c63 message one Z
-1711bf9 add bar Z
-af20c06 initial Z
+ qz_to_tab_space <<EOF >expected &&
+$head1 message two Z
+$head2 message one Z
+$head3 add bar Z
+$head4 $(commit_msg) Z
EOF
test_cmp expected actual
'
@@ -134,11 +170,11 @@ test_expect_success 'left alignment formatting with no padding' '
git log --pretty="format:%<(1)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- cat <<\EOF >expected &&
+ cat <<EOF >expected &&
message two
message one
add bar
-initial
+$(commit_msg)
EOF
test_cmp expected actual
'
@@ -147,11 +183,11 @@ test_expect_success 'left alignment formatting with trunc' '
git log --pretty="format:%<(10,trunc)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
+ qz_to_tab_space <<EOF >expected &&
message ..
message ..
add bar Z
-initial Z
+initial...
EOF
test_cmp expected actual
'
@@ -160,11 +196,11 @@ test_expect_success 'left alignment formatting with ltrunc' '
git log --pretty="format:%<(10,ltrunc)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
+ qz_to_tab_space <<EOF >expected &&
..sage two
..sage one
add bar Z
-initial Z
+..${sample_utf8_part}lich
EOF
test_cmp expected actual
'
@@ -173,11 +209,11 @@ test_expect_success 'left alignment formatting with mtrunc' '
git log --pretty="format:%<(10,mtrunc)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
+ qz_to_tab_space <<EOF >expected &&
mess.. two
mess.. one
add bar Z
-initial Z
+init..lich
EOF
test_cmp expected actual
'
@@ -186,11 +222,11 @@ test_expect_success 'right alignment formatting' '
git log --pretty="format:%>(40)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
+ qz_to_tab_space <<EOF >expected &&
Z message two
Z message one
Z add bar
-Z initial
+Z $(commit_msg)
EOF
test_cmp expected actual
'
@@ -199,11 +235,11 @@ test_expect_success 'right alignment formatting at the nth column' '
git log --pretty="format:%h %>|(40)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
-fa33ab1 message two
-7cd6c63 message one
-1711bf9 add bar
-af20c06 initial
+ qz_to_tab_space <<EOF >expected &&
+$head1 message two
+$head2 message one
+$head3 add bar
+$head4 $(commit_msg)
EOF
test_cmp expected actual
'
@@ -212,11 +248,11 @@ test_expect_success 'right alignment formatting with no padding' '
git log --pretty="format:%>(1)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- cat <<\EOF >expected &&
+ cat <<EOF >expected &&
message two
message one
add bar
-initial
+$(commit_msg)
EOF
test_cmp expected actual
'
@@ -225,11 +261,11 @@ test_expect_success 'center alignment formatting' '
git log --pretty="format:%><(40)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
+ qz_to_tab_space <<EOF >expected &&
Z message two Z
Z message one Z
Z add bar Z
-Z initial Z
+Z $(commit_msg) Z
EOF
test_cmp expected actual
'
@@ -238,11 +274,11 @@ test_expect_success 'center alignment formatting at the nth column' '
git log --pretty="format:%h %><|(40)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- qz_to_tab_space <<\EOF >expected &&
-fa33ab1 message two Z
-7cd6c63 message one Z
-1711bf9 add bar Z
-af20c06 initial Z
+ qz_to_tab_space <<EOF >expected &&
+$head1 message two Z
+$head2 message one Z
+$head3 add bar Z
+$head4 $(commit_msg) Z
EOF
test_cmp expected actual
'
@@ -251,11 +287,11 @@ test_expect_success 'center alignment formatting with no padding' '
git log --pretty="format:%><(1)%s" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- cat <<\EOF >expected &&
+ cat <<EOF >expected &&
message two
message one
add bar
-initial
+$(commit_msg)
EOF
test_cmp expected actual
'
@@ -265,11 +301,11 @@ test_expect_success 'left/right alignment formatting with stealing' '
git log --pretty="format:%<(10,trunc)%s%>>(10,ltrunc)% an" >actual &&
# complete the incomplete line at the end
echo >>actual &&
- cat <<\EOF >expected &&
+ cat <<EOF >expected &&
short long long long
message .. A U Thor
add bar A U Thor
-initial A U Thor
+initial... A U Thor
EOF
test_cmp expected actual
'
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh
new file mode 100755
index 0000000..7776f93
--- /dev/null
+++ b/t/t4211-line-log.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+test_description='test log -L'
+. ./test-lib.sh
+
+test_expect_success 'setup (import history)' '
+ git fast-import < "$TEST_DIRECTORY"/t4211/history.export &&
+ git reset --hard
+'
+
+canned_test_1 () {
+ test_expect_$1 "$2" "
+ git log $2 >actual &&
+ test_cmp \"\$TEST_DIRECTORY\"/t4211/expect.$3 actual
+ "
+}
+
+canned_test () {
+ canned_test_1 success "$@"
+}
+canned_test_failure () {
+ canned_test_1 failure "$@"
+}
+
+test_bad_opts () {
+ test_expect_success "invalid args: $1" "
+ test_must_fail git log $1 2>errors &&
+ grep '$2' errors
+ "
+}
+
+canned_test "-L 4,12:a.c simple" simple-f
+canned_test "-L 4,+9:a.c simple" simple-f
+canned_test "-L '/long f/,/^}/:a.c' simple" simple-f
+canned_test "-L :f:a.c simple" simple-f-to-main
+
+canned_test "-L '/main/,/^}/:a.c' simple" simple-main
+canned_test "-L :main:a.c simple" simple-main-to-end
+
+canned_test "-L 1,+4:a.c simple" beginning-of-file
+
+canned_test "-L 20:a.c simple" end-of-file
+
+canned_test "-L '/long f/',/^}/:a.c -L /main/,/^}/:a.c simple" two-ranges
+canned_test "-L 24,+1:a.c simple" vanishes-early
+
+canned_test "-M -L '/long f/,/^}/:b.c' move-support" move-support-f
+canned_test "-M -L ':f:b.c' parallel-change" parallel-change-f-to-main
+
+canned_test "-L 4,12:a.c -L :main:a.c simple" multiple
+canned_test "-L 4,18:a.c -L :main:a.c simple" multiple-overlapping
+canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping
+canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
+canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset
+
+test_bad_opts "-L" "switch.*requires a value"
+test_bad_opts "-L b.c" "argument.*not of the form"
+test_bad_opts "-L 1:" "argument.*not of the form"
+test_bad_opts "-L 1:nonexistent" "There is no path"
+test_bad_opts "-L 1:simple" "There is no path"
+test_bad_opts "-L '/foo:b.c'" "argument.*not of the form"
+test_bad_opts "-L 1000:b.c" "has only.*lines"
+test_bad_opts "-L 1,1000:b.c" "has only.*lines"
+test_bad_opts "-L :b.c" "argument.*not of the form"
+test_bad_opts "-L :foo:b.c" "no match"
+
+test_done
diff --git a/t/t4211/expect.beginning-of-file b/t/t4211/expect.beginning-of-file
new file mode 100644
index 0000000..91b4054
--- /dev/null
+++ b/t/t4211/expect.beginning-of-file
@@ -0,0 +1,43 @@
+commit 4a23ae5c98d59a58c6da036156959f2dc9f472ad
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:47:40 2013 +0100
+
+ change at very beginning
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -1,3 +1,4 @@
++#include <unistd.h>
+ #include <stdio.h>
+
+ long f(long x)
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -1,3 +1,3 @@
+ #include <stdio.h>
+
+-int f(int x)
++long f(long x)
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +1,3 @@
++#include <stdio.h>
++
++int f(int x)
diff --git a/t/t4211/expect.end-of-file b/t/t4211/expect.end-of-file
new file mode 100644
index 0000000..bd25bb2
--- /dev/null
+++ b/t/t4211/expect.end-of-file
@@ -0,0 +1,62 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -20,3 +20,5 @@
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -20,3 +20,3 @@
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -19,3 +19,3 @@
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +18,3 @@
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/expect.move-support-f b/t/t4211/expect.move-support-f
new file mode 100644
index 0000000..c905e01
--- /dev/null
+++ b/t/t4211/expect.move-support-f
@@ -0,0 +1,80 @@
+commit 6ce3c4ff690136099bb17e1a8766b75764726ea7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:49:50 2013 +0100
+
+ another simple change
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -4,9 +4,9 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+- x >>= 1;
++ x /= 2;
+ s++;
+ }
+ return s;
+ }
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
diff --git a/t/t4211/expect.multiple b/t/t4211/expect.multiple
new file mode 100644
index 0000000..76ad5b5
--- /dev/null
+++ b/t/t4211/expect.multiple
@@ -0,0 +1,104 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
diff --git a/t/t4211/expect.multiple-overlapping b/t/t4211/expect.multiple-overlapping
new file mode 100644
index 0000000..d930b6e
--- /dev/null
+++ b/t/t4211/expect.multiple-overlapping
@@ -0,0 +1,187 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,19 +4,21 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * This is only an example!
+ */
+
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,19 +4,19 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * This is only an example!
+ */
+
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:41 2013 +0100
+
+ touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+- * A comment.
++ * This is only an example!
+ */
+
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,18 +3,19 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+ int main ()
+ {
+ printf("%d\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,18 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
++
++/*
++ * A comment.
++ */
++
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/expect.multiple-superset b/t/t4211/expect.multiple-superset
new file mode 100644
index 0000000..d930b6e
--- /dev/null
+++ b/t/t4211/expect.multiple-superset
@@ -0,0 +1,187 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,19 +4,21 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * This is only an example!
+ */
+
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,19 +4,19 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * This is only an example!
+ */
+
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:41 2013 +0100
+
+ touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+- * A comment.
++ * This is only an example!
+ */
+
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,18 +3,19 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+ int main ()
+ {
+ printf("%d\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,18 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
++
++/*
++ * A comment.
++ */
++
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/expect.parallel-change-f-to-main b/t/t4211/expect.parallel-change-f-to-main
new file mode 100644
index 0000000..052def8
--- /dev/null
+++ b/t/t4211/expect.parallel-change-f-to-main
@@ -0,0 +1,160 @@
+commit 0469c60bc4837d52d97b1f081dec5f98dea20fed
+Merge: ba227c6 6ce3c4f
+Author: Thomas Rast <trast@inf.ethz.ch>
+Date: Fri Apr 12 16:16:24 2013 +0200
+
+ Merge across the rename
+
+
+commit 6ce3c4ff690136099bb17e1a8766b75764726ea7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:49:50 2013 +0100
+
+ another simple change
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -4,14 +4,14 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+- x >>= 1;
++ x /= 2;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * This is only an example!
+ */
+
+
+commit ba227c6632349700fbb957dec2b50f5e2358be3f
+Author: Thomas Rast <trast@inf.ethz.ch>
+Date: Fri Apr 12 16:15:57 2013 +0200
+
+ change on another line of history while rename happens
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,14 +4,14 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+- * This is only an example!
++ * This is only a short example!
+ */
+
+
+commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:41 2013 +0100
+
+ touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+- * A comment.
++ * This is only an example!
+ */
+
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,13 +3,14 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,13 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
++
++/*
++ * A comment.
++ */
++
diff --git a/t/t4211/expect.simple-f b/t/t4211/expect.simple-f
new file mode 100644
index 0000000..a1f5bc4
--- /dev/null
+++ b/t/t4211/expect.simple-f
@@ -0,0 +1,59 @@
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
diff --git a/t/t4211/expect.simple-f-to-main b/t/t4211/expect.simple-f-to-main
new file mode 100644
index 0000000..a475768
--- /dev/null
+++ b/t/t4211/expect.simple-f-to-main
@@ -0,0 +1,100 @@
+commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:41 2013 +0100
+
+ touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+- * A comment.
++ * This is only an example!
+ */
+
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,13 +3,14 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,13 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
++
++/*
++ * A comment.
++ */
++
diff --git a/t/t4211/expect.simple-main b/t/t4211/expect.simple-main
new file mode 100644
index 0000000..39ce39b
--- /dev/null
+++ b/t/t4211/expect.simple-main
@@ -0,0 +1,68 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/expect.simple-main-to-end b/t/t4211/expect.simple-main-to-end
new file mode 100644
index 0000000..8480bd9
--- /dev/null
+++ b/t/t4211/expect.simple-main-to-end
@@ -0,0 +1,70 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/expect.two-ranges b/t/t4211/expect.two-ranges
new file mode 100644
index 0000000..6109aa0
--- /dev/null
+++ b/t/t4211/expect.two-ranges
@@ -0,0 +1,102 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
diff --git a/t/t4211/expect.vanishes-early b/t/t4211/expect.vanishes-early
new file mode 100644
index 0000000..1f7cd06
--- /dev/null
+++ b/t/t4211/expect.vanishes-early
@@ -0,0 +1,39 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -22,1 +24,1 @@
+-}
+\ No newline at end of file
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -22,1 +22,1 @@
+-}
++}
+\ No newline at end of file
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +20,1 @@
++}
diff --git a/t/t4211/history.export b/t/t4211/history.export
new file mode 100644
index 0000000..f9f41e2
--- /dev/null
+++ b/t/t4211/history.export
@@ -0,0 +1,406 @@
+blob
+mark :1
+data 157
+#include <stdio.h>
+
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}
+
+/*
+ * A comment.
+ */
+
+int main ()
+{
+ printf("%d\n", f(15));
+ return 0;
+}
+
+reset refs/tags/simple
+commit refs/tags/simple
+mark :2
+author Thomas Rast <trast@student.ethz.ch> 1362044688 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044688 +0100
+data 8
+initial
+M 100644 :1 a.c
+
+blob
+mark :3
+data 168
+#include <stdio.h>
+
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * A comment.
+ */
+
+int main ()
+{
+ printf("%d\n", f(15));
+ return 0;
+}
+
+commit refs/tags/simple
+mark :4
+author Thomas Rast <trast@student.ethz.ch> 1362044695 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044695 +0100
+data 11
+change f()
+from :2
+M 100644 :3 a.c
+
+blob
+mark :5
+data 171
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * A comment.
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+commit refs/tags/simple
+mark :6
+author Thomas Rast <trast@student.ethz.ch> 1362044716 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044716 +0100
+data 21
+touch both functions
+from :4
+M 100644 :5 a.c
+
+blob
+mark :7
+data 185
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only an example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+commit refs/tags/simple
+mark :8
+author Thomas Rast <trast@student.ethz.ch> 1362044741 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044741 +0100
+data 14
+touch comment
+from :6
+M 100644 :7 a.c
+
+blob
+mark :9
+data 205
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only an example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+commit refs/tags/simple
+mark :10
+author Thomas Rast <trast@student.ethz.ch> 1362044860 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044860 +0100
+data 25
+change at very beginning
+from :8
+M 100644 :9 a.c
+
+blob
+mark :11
+data 204
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only an example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+commit refs/tags/simple
+mark :12
+author Thomas Rast <trast@student.ethz.ch> 1362044890 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044890 +0100
+data 36
+change to an incomplete line at end
+from :10
+M 100644 :11 a.c
+
+blob
+mark :13
+data 238
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only an example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+commit refs/tags/simple
+mark :14
+author Thomas Rast <trast@student.ethz.ch> 1362044923 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044923 +0100
+data 29
+change back to complete line
+from :12
+M 100644 :13 a.c
+
+commit refs/tags/move-support
+mark :15
+author Thomas Rast <trast@student.ethz.ch> 1362044968 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044968 +0100
+data 10
+move file
+from :14
+D a.c
+M 100644 :13 b.c
+
+blob
+mark :16
+data 237
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x /= 2;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only an example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+commit refs/tags/move-support
+mark :17
+author Thomas Rast <trast@student.ethz.ch> 1362044990 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044990 +0100
+data 22
+another simple change
+from :15
+M 100644 :16 b.c
+
+blob
+mark :18
+data 254
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x);
+
+/*
+ * This is only an example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x /= 2;
+ s++;
+ }
+ return s;
+}
+
+commit refs/heads/master
+mark :19
+author Thomas Rast <trast@student.ethz.ch> 1362045024 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362045024 +0100
+data 21
+move within the file
+from :17
+M 100644 :18 b.c
+
+blob
+mark :20
+data 243
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only a short example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+commit refs/heads/parallel-change
+mark :21
+author Thomas Rast <trast@inf.ethz.ch> 1365776157 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1365776157 +0200
+data 55
+change on another line of history while rename happens
+from :14
+M 100644 :20 a.c
+
+blob
+mark :22
+data 242
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x /= 2;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only a short example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+commit refs/heads/parallel-change
+mark :23
+author Thomas Rast <trast@inf.ethz.ch> 1365776184 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1365776191 +0200
+data 24
+Merge across the rename
+from :21
+merge :17
+D a.c
+M 100644 :22 b.c
+
+reset refs/heads/parallel-change
+from :23
+
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 3fbd366..c2023b1 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -30,10 +30,76 @@ GUNZIP=${GUNZIP:-gzip -d}
SUBSTFORMAT=%H%n
+test_lazy_prereq TAR_NEEDS_PAX_FALLBACK '
+ (
+ mkdir pax &&
+ cd pax &&
+ "$TAR" xf "$TEST_DIRECTORY"/t5000/pax.tar &&
+ test -f PaxHeaders.1791/file
+ )
+'
+
+get_pax_header() {
+ file=$1
+ header=$2=
+
+ while read len rest
+ do
+ if test "$len" = $(echo "$len $rest" | wc -c)
+ then
+ case "$rest" in
+ $header*)
+ echo "${rest#$header}"
+ ;;
+ esac
+ fi
+ done <"$file"
+}
+
+check_tar() {
+ tarfile=$1.tar
+ listfile=$1.lst
+ dir=$1
+ dir_with_prefix=$dir/$2
+
+ test_expect_success ' extract tar archive' '
+ (mkdir $dir && cd $dir && "$TAR" xf -) <$tarfile
+ '
+
+ test_expect_success TAR_NEEDS_PAX_FALLBACK ' interpret pax headers' '
+ (
+ cd $dir &&
+ for header in *.paxheader
+ do
+ data=${header%.paxheader}.data &&
+ if test -h $data -o -e $data
+ then
+ path=$(get_pax_header $header path) &&
+ if test -n "$path"
+ then
+ mv "$data" "$path"
+ fi
+ fi
+ done
+ )
+ '
+
+ test_expect_success ' validate filenames' '
+ (cd ${dir_with_prefix}a && find .) | sort >$listfile &&
+ test_cmp a.lst $listfile
+ '
+
+ test_expect_success ' validate file contents' '
+ diff -r a ${dir_with_prefix}a
+ '
+}
+
test_expect_success \
'populate workdir' \
- 'mkdir a b c &&
+ 'mkdir a &&
echo simple textfile >a/a &&
+ ten=0123456789 && hundred=$ten$ten$ten$ten$ten$ten$ten$ten$ten$ten &&
+ echo long filename >a/four$hundred &&
mkdir a/bin &&
cp /bin/sh a/bin &&
printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 &&
@@ -62,6 +128,12 @@ test_expect_success \
git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
git commit-tree $treeid </dev/null)'
+test_expect_success 'setup export-subst' '
+ echo "substfile?" export-subst >>.git/info/attributes &&
+ git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
+ >a/substfile1
+'
+
test_expect_success \
'create bare clone' \
'git clone --bare . bare.git &&
@@ -75,13 +147,19 @@ test_expect_success \
'git archive' \
'git archive HEAD >b.tar'
-test_expect_success \
- 'git tar-tree' \
- 'git tar-tree HEAD >b2.tar'
+check_tar b
-test_expect_success \
- 'git archive vs. git tar-tree' \
- 'test_cmp b.tar b2.tar'
+test_expect_success 'git archive --prefix=prefix/' '
+ git archive --prefix=prefix/ HEAD >with_prefix.tar
+'
+
+check_tar with_prefix prefix/
+
+test_expect_success 'git-archive --prefix=olde-' '
+ git archive --prefix=olde- HEAD >with_olde-prefix.tar
+'
+
+check_tar with_olde-prefix olde-
test_expect_success 'git archive on large files' '
test_config core.bigfilethreshold 1 &&
@@ -118,66 +196,14 @@ test_expect_success \
'git get-tar-commit-id <b.tar >b.commitid &&
test_cmp .git/$(git symbolic-ref HEAD) b.commitid'
-test_expect_success \
- 'extract tar archive' \
- '(cd b && "$TAR" xf -) <b.tar'
-
-test_expect_success \
- 'validate filenames' \
- '(cd b/a && find .) | sort >b.lst &&
- test_cmp a.lst b.lst'
-
-test_expect_success \
- 'validate file contents' \
- 'diff -r a b/a'
-
-test_expect_success \
- 'git tar-tree with prefix' \
- 'git tar-tree HEAD prefix >c.tar'
-
-test_expect_success \
- 'extract tar archive with prefix' \
- '(cd c && "$TAR" xf -) <c.tar'
-
-test_expect_success \
- 'validate filenames with prefix' \
- '(cd c/prefix/a && find .) | sort >c.lst &&
- test_cmp a.lst c.lst'
-
-test_expect_success \
- 'validate file contents with prefix' \
- 'diff -r a c/prefix/a'
-
-test_expect_success \
- 'create archives with substfiles' \
- 'cp .git/info/attributes .git/info/attributes.before &&
- echo "substfile?" export-subst >>.git/info/attributes &&
- git archive HEAD >f.tar &&
- git archive --prefix=prefix/ HEAD >g.tar &&
- mv .git/info/attributes.before .git/info/attributes'
-
-test_expect_success \
- 'extract substfiles' \
- '(mkdir f && cd f && "$TAR" xf -) <f.tar'
-
-test_expect_success \
- 'validate substfile contents' \
- 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
- >f/a/substfile1.expected &&
- test_cmp f/a/substfile1.expected f/a/substfile1 &&
- test_cmp a/substfile2 f/a/substfile2
+test_expect_success 'git tar-tree' '
+ git tar-tree HEAD >tar-tree.tar &&
+ test_cmp b.tar tar-tree.tar
'
-test_expect_success \
- 'extract substfiles from archive with prefix' \
- '(mkdir g && cd g && "$TAR" xf -) <g.tar'
-
-test_expect_success \
- 'validate substfile contents from archive with prefix' \
- 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
- >g/prefix/a/substfile1.expected &&
- test_cmp g/prefix/a/substfile1.expected g/prefix/a/substfile1 &&
- test_cmp a/substfile2 g/prefix/a/substfile2
+test_expect_success 'git tar-tree with prefix' '
+ git tar-tree HEAD prefix >tar-tree_with_prefix.tar &&
+ test_cmp with_prefix.tar tar-tree_with_prefix.tar
'
test_expect_success 'git archive with --output, override inferred format' '
@@ -197,18 +223,6 @@ test_expect_success 'clients cannot access unreachable commits' '
test_must_fail git archive --remote=. $sha1 >remote.tar
'
-test_expect_success 'git-archive --prefix=olde-' '
- git archive --prefix=olde- >h.tar HEAD &&
- (
- mkdir h &&
- cd h &&
- "$TAR" xf - <../h.tar
- ) &&
- test -d h/olde-a &&
- test -d h/olde-a/bin &&
- 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" &&
diff --git a/t/t5000/pax.tar b/t/t5000/pax.tar
new file mode 100644
index 0000000..d911737
--- /dev/null
+++ b/t/t5000/pax.tar
Binary files differ
diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh
index 4e7b05d..c72f71e 100755
--- a/t/t5003-archive-zip.sh
+++ b/t/t5003-archive-zip.sh
@@ -37,7 +37,7 @@ check_zip() {
test_expect_success \
'populate workdir' \
- 'mkdir a b c &&
+ 'mkdir a &&
echo simple textfile >a/a &&
mkdir a/bin &&
cp /bin/sh a/bin &&
diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh
index 8d1bbd3..67f3b54 100755
--- a/t/t5004-archive-corner-cases.sh
+++ b/t/t5004-archive-corner-cases.sh
@@ -27,6 +27,21 @@ check_dir() {
test_cmp expect actual
}
+
+# bsdtar/libarchive versions before 3.1.3 consider a tar file with a
+# global pax header that is not followed by a file record as corrupt.
+if "$TAR" tf "$TEST_DIRECTORY"/t5004/empty-with-pax-header.tar >/dev/null 2>&1
+then
+ test_set_prereq HEADER_ONLY_TAR_OK
+fi
+
+test_expect_success HEADER_ONLY_TAR_OK 'tar archive of commit with empty tree' '
+ git archive --format=tar HEAD >empty-with-pax-header.tar &&
+ make_dir extract &&
+ "$TAR" xf empty-with-pax-header.tar -C extract &&
+ check_dir extract
+'
+
test_expect_success 'tar archive of empty tree is empty' '
git archive --format=tar HEAD: >empty.tar &&
perl -e "print \"\\0\" x 10240" >10knuls.tar &&
diff --git a/t/t5004/empty-with-pax-header.tar b/t/t5004/empty-with-pax-header.tar
new file mode 100644
index 0000000..da9e39e
--- /dev/null
+++ b/t/t5004/empty-with-pax-header.tar
Binary files differ
diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh
index 432f98c..1afa0d5 100755
--- a/t/t5150-request-pull.sh
+++ b/t/t5150-request-pull.sh
@@ -80,12 +80,12 @@ test_expect_success 'setup: two scripts for reading pull requests' '
cat <<-EOT >fuzz.sed
#!/bin/sed -nf
+ s/$downstream_url_for_sed/URL/g
s/$_x40/OBJECT_NAME/g
s/A U Thor/AUTHOR/g
s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g
s/ [^ ].*/ SUBJECT/g
s/ [^ ].* (DATE)/ SUBJECT (DATE)/g
- s/$downstream_url_for_sed/URL/g
s/for-upstream/BRANCH/g
s/mnemonic.txt/FILENAME/g
s/^version [0-9]/VERSION/
diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh
index 5b1250f..35926de 100755
--- a/t/t5303-pack-corruption-resilience.sh
+++ b/t/t5303-pack-corruption-resilience.sh
@@ -51,7 +51,7 @@ do_corrupt_object() {
ofs=`git show-index < ${pack}.idx | grep $1 | cut -f1 -d" "` &&
ofs=$(($ofs + $2)) &&
chmod +w ${pack}.pack &&
- dd of=${pack}.pack count=1 bs=1 conv=notrunc seek=$ofs &&
+ dd of=${pack}.pack bs=1 conv=notrunc seek=$ofs &&
test_must_fail git verify-pack ${pack}.pack
}
@@ -276,6 +276,33 @@ test_expect_success \
git cat-file blob $blob_3 > /dev/null'
test_expect_success \
+ 'corruption of delta base reference pointing to wrong object' \
+ 'create_new_pack --delta-base-offset &&
+ git prune-packed &&
+ printf "\220\033" | do_corrupt_object $blob_3 2 &&
+ git cat-file blob $blob_1 >/dev/null &&
+ git cat-file blob $blob_2 >/dev/null &&
+ test_must_fail git cat-file blob $blob_3 >/dev/null'
+
+test_expect_success \
+ '... but having a loose copy allows for full recovery' \
+ 'mv ${pack}.idx tmp &&
+ git hash-object -t blob -w file_3 &&
+ mv tmp ${pack}.idx &&
+ git cat-file blob $blob_1 > /dev/null &&
+ git cat-file blob $blob_2 > /dev/null &&
+ git cat-file blob $blob_3 > /dev/null'
+
+test_expect_success \
+ '... and then a repack "clears" the corruption' \
+ 'do_repack --delta-base-offset --no-reuse-delta &&
+ git prune-packed &&
+ git verify-pack ${pack}.pack &&
+ git cat-file blob $blob_1 > /dev/null &&
+ git cat-file blob $blob_2 > /dev/null &&
+ git cat-file blob $blob_3 > /dev/null'
+
+test_expect_success \
'corrupting header to have too small output buffer fails unpack' \
'create_new_pack &&
git prune-packed &&
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index baa670c..ea2e0d4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -31,8 +31,8 @@ clear_hook_input () {
}
verify_hook_input () {
- test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
- test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+ test_cmp expected.args "$TRASH_DIRECTORY"/post-rewrite.args &&
+ test_cmp expected.data "$TRASH_DIRECTORY"/post-rewrite.data
}
test_expect_success 'git commit --amend' '
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index d574085..fd2598e 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -135,6 +135,13 @@ test_expect_success 'clone shallow depth 1' '
test "`git --git-dir=shallow0/.git rev-list --count HEAD`" = 1
'
+test_expect_success 'clone shallow depth 1 with fsck' '
+ git config --global fetch.fsckobjects true &&
+ git clone --no-single-branch --depth 1 "file://$(pwd)/." shallow0fsck &&
+ test "`git --git-dir=shallow0fsck/.git rev-list --count HEAD`" = 1 &&
+ git config --global --unset fetch.fsckobjects
+'
+
test_expect_success 'clone shallow' '
git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
'
@@ -373,6 +380,20 @@ test_expect_success 'clone shallow with packed refs' '
test_cmp count8.expected count8.actual
'
+test_expect_success 'fetch in shallow repo unreachable shallow objects' '
+ (
+ git clone --bare --branch B --single-branch "file://$(pwd)/." no-reflog &&
+ git clone --depth 1 "file://$(pwd)/no-reflog" shallow9 &&
+ cd no-reflog &&
+ git tag -d TAGB1 TAGB2 &&
+ git update-ref refs/heads/B B~~ &&
+ git gc --prune=now &&
+ cd ../shallow9 &&
+ git fetch origin &&
+ git fsck --no-dangling
+ )
+'
+
test_expect_success 'setup tests for the --stdin parameter' '
for head in C D E F
do
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index dd10ff0..8f6e392 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -42,107 +42,104 @@ check_tracking_branch () {
}
test_expect_success setup '
-
setup_repository one &&
setup_repository two &&
(
- cd two && git branch another
+ cd two &&
+ git branch another
) &&
git clone one test
-
'
test_expect_success C_LOCALE_OUTPUT 'remote information for the origin' '
-(
- cd test &&
- tokens_match origin "$(git remote)" &&
- check_remote_track origin master side &&
- check_tracking_branch origin HEAD master side
-)
+ (
+ cd test &&
+ tokens_match origin "$(git remote)" &&
+ check_remote_track origin master side &&
+ check_tracking_branch origin HEAD master side
+ )
'
test_expect_success 'add another remote' '
-(
- cd test &&
- git remote add -f second ../two &&
- tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second master side another &&
- git for-each-ref "--format=%(refname)" refs/remotes |
- sed -e "/^refs\/remotes\/origin\//d" \
- -e "/^refs\/remotes\/second\//d" >actual &&
- >expect &&
- test_cmp expect actual
-)
+ (
+ cd test &&
+ git remote add -f second ../two &&
+ tokens_match "origin second" "$(git remote)" &&
+ check_tracking_branch second master side another &&
+ git for-each-ref "--format=%(refname)" refs/remotes |
+ sed -e "/^refs\/remotes\/origin\//d" \
+ -e "/^refs\/remotes\/second\//d" >actual &&
+ >expect &&
+ test_cmp expect actual
+ )
'
-test_expect_success C_LOCALE_OUTPUT 'check remote tracking' '
-(
- cd test &&
- check_remote_track origin master side &&
- check_remote_track second master side another
-)
+test_expect_success C_LOCALE_OUTPUT 'check remote-tracking' '
+ (
+ cd test &&
+ check_remote_track origin master side &&
+ check_remote_track second master side another
+ )
'
test_expect_success 'remote forces tracking branches' '
-(
- cd test &&
- case `git config remote.second.fetch` in
- +*) true ;;
- *) false ;;
- esac
-)
+ (
+ cd test &&
+ case `git config remote.second.fetch` in
+ +*) true ;;
+ *) false ;;
+ esac
+ )
'
test_expect_success 'remove remote' '
-(
- cd test &&
- git symbolic-ref refs/remotes/second/HEAD refs/remotes/second/master &&
- git remote rm second
-)
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/second/HEAD refs/remotes/second/master &&
+ git remote rm second
+ )
'
test_expect_success C_LOCALE_OUTPUT 'remove remote' '
-(
- cd test &&
- tokens_match origin "$(git remote)" &&
- check_remote_track origin master side &&
- git for-each-ref "--format=%(refname)" refs/remotes |
- sed -e "/^refs\/remotes\/origin\//d" >actual &&
- >expect &&
- test_cmp expect actual
-)
+ (
+ cd test &&
+ tokens_match origin "$(git remote)" &&
+ check_remote_track origin master side &&
+ git for-each-ref "--format=%(refname)" refs/remotes |
+ sed -e "/^refs\/remotes\/origin\//d" >actual &&
+ >expect &&
+ test_cmp expect actual
+ )
'
test_expect_success 'remove remote protects local branches' '
-(
- cd test &&
- { cat >expect1 <<EOF
-Note: A branch outside the refs/remotes/ hierarchy was not removed;
-to delete it, use:
- git branch -d master
-EOF
- } &&
- { cat >expect2 <<EOF
-Note: Some branches outside the refs/remotes/ hierarchy were not removed;
-to delete them, use:
- git branch -d foobranch
- git branch -d master
-EOF
- } &&
- git tag footag &&
- git config --add remote.oops.fetch "+refs/*:refs/*" &&
- git remote remove oops 2>actual1 &&
- git branch foobranch &&
- git config --add remote.oops.fetch "+refs/*:refs/*" &&
- git remote rm oops 2>actual2 &&
- git branch -d foobranch &&
- git tag -d footag &&
- test_i18ncmp expect1 actual1 &&
- test_i18ncmp expect2 actual2
-)
-'
-
-cat > test/expect << EOF
+ (
+ cd test &&
+ cat >expect1 <<-\EOF &&
+ Note: A branch outside the refs/remotes/ hierarchy was not removed;
+ to delete it, use:
+ git branch -d master
+ EOF
+ cat >expect2 <<-\EOF &&
+ Note: Some branches outside the refs/remotes/ hierarchy were not removed;
+ to delete them, use:
+ git branch -d foobranch
+ git branch -d master
+ EOF
+ git tag footag &&
+ git config --add remote.oops.fetch "+refs/*:refs/*" &&
+ git remote remove oops 2>actual1 &&
+ git branch foobranch &&
+ git config --add remote.oops.fetch "+refs/*:refs/*" &&
+ git remote rm oops 2>actual2 &&
+ git branch -d foobranch &&
+ git tag -d footag &&
+ test_i18ncmp expect1 actual1 &&
+ test_i18ncmp expect2 actual2
+ )
+'
+
+cat >test/expect <<EOF
* remote origin
Fetch URL: $(pwd)/one
Push URL: $(pwd)/one
@@ -172,36 +169,40 @@ cat > test/expect << EOF
EOF
test_expect_success 'show' '
- (cd test &&
- git config --add remote.origin.fetch refs/heads/master:refs/heads/upstream &&
- git fetch &&
- git checkout -b ahead origin/master &&
- echo 1 >> file &&
- test_tick &&
- git commit -m update file &&
- git checkout master &&
- git branch --track octopus origin/master &&
- git branch --track rebase origin/master &&
- git branch -d -r origin/master &&
- git config --add remote.two.url ../two &&
- git config --add remote.two.pushurl ../three &&
- git config branch.rebase.rebase true &&
- git config branch.octopus.merge "topic-a topic-b topic-c" &&
- (cd ../one &&
- echo 1 > file &&
- test_tick &&
- git commit -m update file) &&
- git config --add remote.origin.push : &&
- git config --add remote.origin.push refs/heads/master:refs/heads/upstream &&
- git config --add remote.origin.push +refs/tags/lastbackup &&
- git config --add remote.two.push +refs/heads/ahead:refs/heads/master &&
- git config --add remote.two.push refs/heads/master:refs/heads/another &&
- git remote show origin two > output &&
- git branch -d rebase octopus &&
- test_i18ncmp expect output)
-'
-
-cat > test/expect << EOF
+ (
+ cd test &&
+ git config --add remote.origin.fetch refs/heads/master:refs/heads/upstream &&
+ git fetch &&
+ git checkout -b ahead origin/master &&
+ echo 1 >>file &&
+ test_tick &&
+ git commit -m update file &&
+ git checkout master &&
+ git branch --track octopus origin/master &&
+ git branch --track rebase origin/master &&
+ git branch -d -r origin/master &&
+ git config --add remote.two.url ../two &&
+ git config --add remote.two.pushurl ../three &&
+ git config branch.rebase.rebase true &&
+ git config branch.octopus.merge "topic-a topic-b topic-c" &&
+ (
+ cd ../one &&
+ echo 1 >file &&
+ test_tick &&
+ git commit -m update file
+ ) &&
+ git config --add remote.origin.push : &&
+ git config --add remote.origin.push refs/heads/master:refs/heads/upstream &&
+ git config --add remote.origin.push +refs/tags/lastbackup &&
+ git config --add remote.two.push +refs/heads/ahead:refs/heads/master &&
+ git config --add remote.two.push refs/heads/master:refs/heads/another &&
+ git remote show origin two >output &&
+ git branch -d rebase octopus &&
+ test_i18ncmp expect output
+ )
+'
+
+cat >test/expect <<EOF
* remote origin
Fetch URL: $(pwd)/one
Push URL: $(pwd)/one
@@ -219,152 +220,187 @@ cat > test/expect << EOF
EOF
test_expect_success 'show -n' '
- (mv one one.unreachable &&
- cd test &&
- git remote show -n origin > output &&
- mv ../one.unreachable ../one &&
- test_i18ncmp expect output)
+ mv one one.unreachable &&
+ (
+ cd test &&
+ git remote show -n origin >output &&
+ mv ../one.unreachable ../one &&
+ test_i18ncmp expect output
+ )
'
test_expect_success 'prune' '
- (cd one &&
- git branch -m side side2) &&
- (cd test &&
- git fetch origin &&
- git remote prune origin &&
- git rev-parse refs/remotes/origin/side2 &&
- test_must_fail git rev-parse refs/remotes/origin/side)
+ (
+ cd one &&
+ git branch -m side side2
+ ) &&
+ (
+ cd test &&
+ git fetch origin &&
+ git remote prune origin &&
+ git rev-parse refs/remotes/origin/side2 &&
+ test_must_fail git rev-parse refs/remotes/origin/side
+ )
'
test_expect_success 'set-head --delete' '
- (cd test &&
- git symbolic-ref refs/remotes/origin/HEAD &&
- git remote set-head --delete origin &&
- test_must_fail git symbolic-ref refs/remotes/origin/HEAD)
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD &&
+ git remote set-head --delete origin &&
+ test_must_fail git symbolic-ref refs/remotes/origin/HEAD
+ )
'
test_expect_success 'set-head --auto' '
- (cd test &&
- git remote set-head --auto origin &&
- echo refs/remotes/origin/master >expect &&
- git symbolic-ref refs/remotes/origin/HEAD >output &&
- test_cmp expect output
+ (
+ cd test &&
+ git remote set-head --auto origin &&
+ echo refs/remotes/origin/master >expect &&
+ git symbolic-ref refs/remotes/origin/HEAD >output &&
+ test_cmp expect output
)
'
-cat >test/expect <<EOF
+cat >test/expect <<\EOF
error: Multiple remote HEAD branches. Please choose one explicitly with:
git remote set-head two another
git remote set-head two master
EOF
test_expect_success 'set-head --auto fails w/multiple HEADs' '
- (cd test &&
- test_must_fail git remote set-head --auto two >output 2>&1 &&
- test_i18ncmp expect output)
+ (
+ cd test &&
+ test_must_fail git remote set-head --auto two >output 2>&1 &&
+ test_i18ncmp expect output
+ )
'
-cat >test/expect <<EOF
+cat >test/expect <<\EOF
refs/remotes/origin/side2
EOF
test_expect_success 'set-head explicit' '
- (cd test &&
- git remote set-head origin side2 &&
- git symbolic-ref refs/remotes/origin/HEAD >output &&
- git remote set-head origin master &&
- test_cmp expect output)
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git symbolic-ref refs/remotes/origin/HEAD >output &&
+ git remote set-head origin master &&
+ test_cmp expect output
+ )
'
-cat > test/expect << EOF
+cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
* [would prune] origin/side2
EOF
test_expect_success 'prune --dry-run' '
- (cd one &&
- git branch -m side2 side) &&
- (cd test &&
- git remote prune --dry-run origin > output &&
- git rev-parse refs/remotes/origin/side2 &&
- test_must_fail git rev-parse refs/remotes/origin/side &&
- (cd ../one &&
- git branch -m side side2) &&
- test_i18ncmp expect output)
+ (
+ cd one &&
+ git branch -m side2 side) &&
+ (
+ cd test &&
+ git remote prune --dry-run origin >output &&
+ git rev-parse refs/remotes/origin/side2 &&
+ test_must_fail git rev-parse refs/remotes/origin/side &&
+ (
+ cd ../one &&
+ git branch -m side side2) &&
+ test_i18ncmp expect output
+ )
'
test_expect_success 'add --mirror && prune' '
- (mkdir mirror &&
- cd mirror &&
- git init --bare &&
- git remote add --mirror -f origin ../one) &&
- (cd one &&
- git branch -m side2 side) &&
- (cd mirror &&
- git rev-parse --verify refs/heads/side2 &&
- test_must_fail git rev-parse --verify refs/heads/side &&
- git fetch origin &&
- git remote prune origin &&
- test_must_fail git rev-parse --verify refs/heads/side2 &&
- git rev-parse --verify refs/heads/side)
+ mkdir mirror &&
+ (
+ cd mirror &&
+ git init --bare &&
+ git remote add --mirror -f origin ../one
+ ) &&
+ (
+ cd one &&
+ git branch -m side2 side
+ ) &&
+ (
+ cd mirror &&
+ git rev-parse --verify refs/heads/side2 &&
+ test_must_fail git rev-parse --verify refs/heads/side &&
+ git fetch origin &&
+ git remote prune origin &&
+ test_must_fail git rev-parse --verify refs/heads/side2 &&
+ git rev-parse --verify refs/heads/side
+ )
'
test_expect_success 'add --mirror=fetch' '
mkdir mirror-fetch &&
git init mirror-fetch/parent &&
- (cd mirror-fetch/parent &&
- test_commit one) &&
+ (
+ cd mirror-fetch/parent &&
+ test_commit one
+ ) &&
git init --bare mirror-fetch/child &&
- (cd mirror-fetch/child &&
- git remote add --mirror=fetch -f parent ../parent)
+ (
+ cd mirror-fetch/child &&
+ git remote add --mirror=fetch -f parent ../parent
+ )
'
test_expect_success 'fetch mirrors act as mirrors during fetch' '
- (cd mirror-fetch/parent &&
- git branch new &&
- git branch -m master renamed
+ (
+ cd mirror-fetch/parent &&
+ git branch new &&
+ git branch -m master renamed
) &&
- (cd mirror-fetch/child &&
- git fetch parent &&
- git rev-parse --verify refs/heads/new &&
- git rev-parse --verify refs/heads/renamed
+ (
+ cd mirror-fetch/child &&
+ git fetch parent &&
+ git rev-parse --verify refs/heads/new &&
+ git rev-parse --verify refs/heads/renamed
)
'
test_expect_success 'fetch mirrors can prune' '
- (cd mirror-fetch/child &&
- git remote prune parent &&
- test_must_fail git rev-parse --verify refs/heads/master
+ (
+ cd mirror-fetch/child &&
+ git remote prune parent &&
+ test_must_fail git rev-parse --verify refs/heads/master
)
'
test_expect_success 'fetch mirrors do not act as mirrors during push' '
- (cd mirror-fetch/parent &&
- git checkout HEAD^0
+ (
+ cd mirror-fetch/parent &&
+ git checkout HEAD^0
) &&
- (cd mirror-fetch/child &&
- git branch -m renamed renamed2 &&
- git push parent :
+ (
+ cd mirror-fetch/child &&
+ git branch -m renamed renamed2 &&
+ git push parent :
) &&
- (cd mirror-fetch/parent &&
- git rev-parse --verify renamed &&
- test_must_fail git rev-parse --verify refs/heads/renamed2
+ (
+ cd mirror-fetch/parent &&
+ git rev-parse --verify renamed &&
+ test_must_fail git rev-parse --verify refs/heads/renamed2
)
'
test_expect_success 'add fetch mirror with specific branches' '
git init --bare mirror-fetch/track &&
- (cd mirror-fetch/track &&
- git remote add --mirror=fetch -t heads/new parent ../parent
+ (
+ cd mirror-fetch/track &&
+ git remote add --mirror=fetch -t heads/new parent ../parent
)
'
test_expect_success 'fetch mirror respects specific branches' '
- (cd mirror-fetch/track &&
- git fetch parent &&
- git rev-parse --verify refs/heads/new &&
- test_must_fail git rev-parse --verify refs/heads/renamed
+ (
+ cd mirror-fetch/track &&
+ git fetch parent &&
+ git rev-parse --verify refs/heads/new &&
+ test_must_fail git rev-parse --verify refs/heads/renamed
)
'
@@ -372,60 +408,72 @@ test_expect_success 'add --mirror=push' '
mkdir mirror-push &&
git init --bare mirror-push/public &&
git init mirror-push/private &&
- (cd mirror-push/private &&
- test_commit one &&
- git remote add --mirror=push public ../public
+ (
+ cd mirror-push/private &&
+ test_commit one &&
+ git remote add --mirror=push public ../public
)
'
test_expect_success 'push mirrors act as mirrors during push' '
- (cd mirror-push/private &&
- git branch new &&
- git branch -m master renamed &&
- git push public
+ (
+ cd mirror-push/private &&
+ git branch new &&
+ git branch -m master renamed &&
+ git push public
) &&
- (cd mirror-push/private &&
- git rev-parse --verify refs/heads/new &&
- git rev-parse --verify refs/heads/renamed &&
- test_must_fail git rev-parse --verify refs/heads/master
+ (
+ cd mirror-push/private &&
+ git rev-parse --verify refs/heads/new &&
+ git rev-parse --verify refs/heads/renamed &&
+ test_must_fail git rev-parse --verify refs/heads/master
)
'
test_expect_success 'push mirrors do not act as mirrors during fetch' '
- (cd mirror-push/public &&
- git branch -m renamed renamed2 &&
- git symbolic-ref HEAD refs/heads/renamed2
+ (
+ cd mirror-push/public &&
+ git branch -m renamed renamed2 &&
+ git symbolic-ref HEAD refs/heads/renamed2
) &&
- (cd mirror-push/private &&
- git fetch public &&
- git rev-parse --verify refs/heads/renamed &&
- test_must_fail git rev-parse --verify refs/heads/renamed2
+ (
+ cd mirror-push/private &&
+ git fetch public &&
+ git rev-parse --verify refs/heads/renamed &&
+ test_must_fail git rev-parse --verify refs/heads/renamed2
)
'
test_expect_success 'push mirrors do not allow you to specify refs' '
git init mirror-push/track &&
- (cd mirror-push/track &&
- test_must_fail git remote add --mirror=push -t new public ../public
+ (
+ cd mirror-push/track &&
+ test_must_fail git remote add --mirror=push -t new public ../public
)
'
test_expect_success 'add alt && prune' '
- (mkdir alttst &&
- cd alttst &&
- git init &&
- git remote add -f origin ../one &&
- git config remote.alt.url ../one &&
- git config remote.alt.fetch "+refs/heads/*:refs/remotes/origin/*") &&
- (cd one &&
- git branch -m side side2) &&
- (cd alttst &&
- git rev-parse --verify refs/remotes/origin/side &&
- test_must_fail git rev-parse --verify refs/remotes/origin/side2 &&
- git fetch alt &&
- git remote prune alt &&
- test_must_fail git rev-parse --verify refs/remotes/origin/side &&
- git rev-parse --verify refs/remotes/origin/side2)
+ mkdir alttst &&
+ (
+ cd alttst &&
+ git init &&
+ git remote add -f origin ../one &&
+ git config remote.alt.url ../one &&
+ git config remote.alt.fetch "+refs/heads/*:refs/remotes/origin/*"
+ ) &&
+ (
+ cd one &&
+ git branch -m side side2
+ ) &&
+ (
+ cd alttst &&
+ git rev-parse --verify refs/remotes/origin/side &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/side2 &&
+ git fetch alt &&
+ git remote prune alt &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/side &&
+ git rev-parse --verify refs/remotes/origin/side2
+ )
'
cat >test/expect <<\EOF
@@ -433,20 +481,24 @@ some-tag
EOF
test_expect_success 'add with reachable tags (default)' '
- (cd one &&
- >foobar &&
- git add foobar &&
- git commit -m "Foobar" &&
- git tag -a -m "Foobar tag" foobar-tag &&
- git reset --hard HEAD~1 &&
- git tag -a -m "Some tag" some-tag) &&
- (mkdir add-tags &&
- cd add-tags &&
- git init &&
- git remote add -f origin ../one &&
- git tag -l some-tag >../test/output &&
- git tag -l foobar-tag >>../test/output &&
- test_must_fail git config remote.origin.tagopt) &&
+ (
+ cd one &&
+ >foobar &&
+ git add foobar &&
+ git commit -m "Foobar" &&
+ git tag -a -m "Foobar tag" foobar-tag &&
+ git reset --hard HEAD~1 &&
+ git tag -a -m "Some tag" some-tag
+ ) &&
+ mkdir add-tags &&
+ (
+ cd add-tags &&
+ git init &&
+ git remote add -f origin ../one &&
+ git tag -l some-tag >../test/output &&
+ git tag -l foobar-tag >>../test/output &&
+ test_must_fail git config remote.origin.tagopt
+ ) &&
test_cmp test/expect test/output
'
@@ -457,14 +509,16 @@ foobar-tag
EOF
test_expect_success 'add --tags' '
- (rm -rf add-tags &&
- mkdir add-tags &&
- cd add-tags &&
- git init &&
- git remote add -f --tags origin ../one &&
- git tag -l some-tag >../test/output &&
- git tag -l foobar-tag >>../test/output &&
- git config remote.origin.tagopt >>../test/output) &&
+ rm -rf add-tags &&
+ (
+ mkdir add-tags &&
+ cd add-tags &&
+ git init &&
+ git remote add -f --tags origin ../one &&
+ git tag -l some-tag >../test/output &&
+ git tag -l foobar-tag >>../test/output &&
+ git config remote.origin.tagopt >>../test/output
+ ) &&
test_cmp test/expect test/output
'
@@ -473,25 +527,31 @@ cat >test/expect <<\EOF
EOF
test_expect_success 'add --no-tags' '
- (rm -rf add-tags &&
- mkdir add-no-tags &&
- cd add-no-tags &&
- git init &&
- git remote add -f --no-tags origin ../one &&
- git tag -l some-tag >../test/output &&
- git tag -l foobar-tag >../test/output &&
- git config remote.origin.tagopt >>../test/output) &&
- (cd one &&
- git tag -d some-tag foobar-tag) &&
+ rm -rf add-tags &&
+ (
+ mkdir add-no-tags &&
+ cd add-no-tags &&
+ git init &&
+ git remote add -f --no-tags origin ../one &&
+ git tag -l some-tag >../test/output &&
+ git tag -l foobar-tag >../test/output &&
+ git config remote.origin.tagopt >>../test/output
+ ) &&
+ (
+ cd one &&
+ git tag -d some-tag foobar-tag
+ ) &&
test_cmp test/expect test/output
'
test_expect_success 'reject --no-no-tags' '
- (cd add-no-tags &&
- test_must_fail git remote add -f --no-no-tags neworigin ../one)
+ (
+ cd add-no-tags &&
+ test_must_fail git remote add -f --no-no-tags neworigin ../one
+ )
'
-cat > one/expect << EOF
+cat >one/expect <<\EOF
apis/master
apis/side
drosophila/another
@@ -500,17 +560,17 @@ cat > one/expect << EOF
EOF
test_expect_success 'update' '
-
- (cd one &&
- git remote add drosophila ../two &&
- git remote add apis ../mirror &&
- git remote update &&
- git branch -r > output &&
- test_cmp expect output)
-
+ (
+ cd one &&
+ git remote add drosophila ../two &&
+ git remote add apis ../mirror &&
+ git remote update &&
+ git branch -r >output &&
+ test_cmp expect output
+ )
'
-cat > one/expect << EOF
+cat >one/expect <<\EOF
drosophila/another
drosophila/master
drosophila/side
@@ -521,34 +581,40 @@ cat > one/expect << EOF
EOF
test_expect_success 'update with arguments' '
-
- (cd one &&
- for b in $(git branch -r)
- do
+ (
+ cd one &&
+ for b in $(git branch -r)
+ do
git branch -r -d $b || break
- done &&
- git remote add manduca ../mirror &&
- git remote add megaloprepus ../mirror &&
- git config remotes.phobaeticus "drosophila megaloprepus" &&
- git config remotes.titanus manduca &&
- git remote update phobaeticus titanus &&
- git branch -r > output &&
- test_cmp expect output)
-
+ done &&
+ git remote add manduca ../mirror &&
+ git remote add megaloprepus ../mirror &&
+ git config remotes.phobaeticus "drosophila megaloprepus" &&
+ git config remotes.titanus manduca &&
+ git remote update phobaeticus titanus &&
+ git branch -r >output &&
+ test_cmp expect output
+ )
'
test_expect_success 'update --prune' '
-
- (cd one &&
- git branch -m side2 side3) &&
- (cd test &&
- git remote update --prune &&
- (cd ../one && git branch -m side3 side2) &&
- git rev-parse refs/remotes/origin/side3 &&
- test_must_fail git rev-parse refs/remotes/origin/side2)
+ (
+ cd one &&
+ git branch -m side2 side3
+ ) &&
+ (
+ cd test &&
+ git remote update --prune &&
+ (
+ cd ../one &&
+ git branch -m side3 side2
+ ) &&
+ git rev-parse refs/remotes/origin/side3 &&
+ test_must_fail git rev-parse refs/remotes/origin/side2
+ )
'
-cat > one/expect << EOF
+cat >one/expect <<-\EOF
apis/master
apis/side
manduca/master
@@ -558,176 +624,204 @@ cat > one/expect << EOF
EOF
test_expect_success 'update default' '
-
- (cd one &&
- for b in $(git branch -r)
- do
+ (
+ cd one &&
+ for b in $(git branch -r)
+ do
git branch -r -d $b || break
- done &&
- git config remote.drosophila.skipDefaultUpdate true &&
- git remote update default &&
- git branch -r > output &&
- test_cmp expect output)
-
+ done &&
+ git config remote.drosophila.skipDefaultUpdate true &&
+ git remote update default &&
+ git branch -r >output &&
+ test_cmp expect output
+ )
'
-cat > one/expect << EOF
+cat >one/expect <<\EOF
drosophila/another
drosophila/master
drosophila/side
EOF
test_expect_success 'update default (overridden, with funny whitespace)' '
-
- (cd one &&
- for b in $(git branch -r)
- do
+ (
+ cd one &&
+ for b in $(git branch -r)
+ do
git branch -r -d $b || break
- done &&
- git config remotes.default "$(printf "\t drosophila \n")" &&
- git remote update default &&
- git branch -r > output &&
- test_cmp expect output)
-
+ done &&
+ git config remotes.default "$(printf "\t drosophila \n")" &&
+ git remote update default &&
+ git branch -r >output &&
+ test_cmp expect output
+ )
'
test_expect_success 'update (with remotes.default defined)' '
-
- (cd one &&
- for b in $(git branch -r)
- do
+ (
+ cd one &&
+ for b in $(git branch -r)
+ do
git branch -r -d $b || break
- done &&
- git config remotes.default "drosophila" &&
- git remote update &&
- git branch -r > output &&
- test_cmp expect output)
-
+ done &&
+ git config remotes.default "drosophila" &&
+ git remote update &&
+ git branch -r >output &&
+ test_cmp expect output
+ )
'
test_expect_success '"remote show" does not show symbolic refs' '
-
git clone one three &&
- (cd three &&
- git remote show origin > output &&
- ! grep "^ *HEAD$" < output &&
- ! grep -i stale < output)
-
+ (
+ cd three &&
+ git remote show origin >output &&
+ ! grep "^ *HEAD$" < output &&
+ ! grep -i stale < output
+ )
'
test_expect_success 'reject adding remote with an invalid name' '
-
test_must_fail git remote add some:url desired-name
-
'
# The first three test if the tracking branches are properly renamed,
# the last two ones check if the config is updated.
test_expect_success 'rename a remote' '
-
git clone one four &&
- (cd four &&
- git remote rename origin upstream &&
- rmdir .git/refs/remotes/origin &&
- test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" &&
- test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
- test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
- test "$(git config branch.master.remote)" = "upstream")
-
+ (
+ cd four &&
+ git remote rename origin upstream &&
+ rmdir .git/refs/remotes/origin &&
+ test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" &&
+ test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
+ test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
+ test "$(git config branch.master.remote)" = "upstream"
+ )
'
test_expect_success 'rename does not update a non-default fetch refspec' '
-
git clone one four.one &&
- (cd four.one &&
- git config remote.origin.fetch +refs/heads/*:refs/heads/origin/* &&
- git remote rename origin upstream &&
- test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/heads/origin/*" &&
- git rev-parse -q origin/master)
-
+ (
+ cd four.one &&
+ git config remote.origin.fetch +refs/heads/*:refs/heads/origin/* &&
+ git remote rename origin upstream &&
+ test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/heads/origin/*" &&
+ git rev-parse -q origin/master
+ )
'
test_expect_success 'rename a remote with name part of fetch spec' '
-
git clone one four.two &&
- (cd four.two &&
- git remote rename origin remote &&
- git remote rename remote upstream &&
- test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*")
-
+ (
+ cd four.two &&
+ git remote rename origin remote &&
+ git remote rename remote upstream &&
+ test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*"
+ )
'
test_expect_success 'rename a remote with name prefix of other remote' '
-
git clone one four.three &&
- (cd four.three &&
- git remote add o git://example.com/repo.git &&
- git remote rename o upstream &&
- test "$(git rev-parse origin/master)" = "$(git rev-parse master)")
-
+ (
+ cd four.three &&
+ git remote add o git://example.com/repo.git &&
+ git remote rename o upstream &&
+ test "$(git rev-parse origin/master)" = "$(git rev-parse master)"
+ )
'
-cat > remotes_origin << EOF
+cat >remotes_origin <<EOF
URL: $(pwd)/one
Push: refs/heads/master:refs/heads/upstream
+Push: refs/heads/next:refs/heads/upstream2
Pull: refs/heads/master:refs/heads/origin
+Pull: refs/heads/next:refs/heads/origin2
EOF
test_expect_success 'migrate a remote from named file in $GIT_DIR/remotes' '
git clone one five &&
origin_url=$(pwd)/one &&
- (cd five &&
- git remote remove origin &&
- mkdir -p .git/remotes &&
- cat ../remotes_origin > .git/remotes/origin &&
- git remote rename origin origin &&
- ! test -f .git/remotes/origin &&
- test "$(git config remote.origin.url)" = "$origin_url" &&
- test "$(git config remote.origin.push)" = "refs/heads/master:refs/heads/upstream" &&
- test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin")
+ (
+ cd five &&
+ git remote remove origin &&
+ mkdir -p .git/remotes &&
+ cat ../remotes_origin >.git/remotes/origin &&
+ git remote rename origin origin &&
+ test_path_is_missing .git/remotes/origin &&
+ test "$(git config remote.origin.url)" = "$origin_url" &&
+ cat >push_expected <<-\EOF &&
+ refs/heads/master:refs/heads/upstream
+ refs/heads/next:refs/heads/upstream2
+ EOF
+ cat >fetch_expected <<-\EOF &&
+ refs/heads/master:refs/heads/origin
+ refs/heads/next:refs/heads/origin2
+ EOF
+ git config --get-all remote.origin.push >push_actual &&
+ git config --get-all remote.origin.fetch >fetch_actual &&
+ test_cmp push_expected push_actual &&
+ test_cmp fetch_expected fetch_actual
+ )
'
test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' '
git clone one six &&
origin_url=$(pwd)/one &&
- (cd six &&
- git remote rm origin &&
- echo "$origin_url" > .git/branches/origin &&
- git remote rename origin origin &&
- ! test -f .git/branches/origin &&
- test "$(git config remote.origin.url)" = "$origin_url" &&
- test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin")
+ (
+ cd six &&
+ git remote rm origin &&
+ echo "$origin_url" >.git/branches/origin &&
+ git remote rename origin origin &&
+ test_path_is_missing .git/branches/origin &&
+ test "$(git config remote.origin.url)" = "$origin_url" &&
+ test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin" &&
+ test "$(git config remote.origin.push)" = "HEAD:refs/heads/master"
+ )
'
-test_expect_success 'remote prune to cause a dangling symref' '
+test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)' '
git clone one seven &&
(
+ cd seven &&
+ git remote rm origin &&
+ echo "quux#foom" > .git/branches/origin &&
+ git remote rename origin origin &&
+ test_path_is_missing .git/branches/origin &&
+ test "$(git config remote.origin.url)" = "quux" &&
+ test "$(git config remote.origin.fetch)" = "refs/heads/foom:refs/heads/origin"
+ test "$(git config remote.origin.push)" = "HEAD:refs/heads/foom"
+ )
+'
+
+test_expect_success 'remote prune to cause a dangling symref' '
+ git clone one eight &&
+ (
cd one &&
git checkout side2 &&
git branch -D master
) &&
(
- cd seven &&
+ cd eight &&
git remote prune origin
) >err 2>&1 &&
test_i18ngrep "has become dangling" err &&
: And the dangling symref will not cause other annoying errors &&
(
- cd seven &&
+ cd eight &&
git branch -a
) 2>err &&
! grep "points nowhere" err &&
(
- cd seven &&
+ cd eight &&
test_must_fail git branch nomore origin
) 2>err &&
grep "dangling symref" err
'
test_expect_success 'show empty remote' '
-
test_create_repo empty &&
git clone empty empty-clone &&
(
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index d7a19a1..fde6891 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -370,30 +370,39 @@ test_expect_success 'bundle should record HEAD correctly' '
'
-test_expect_success 'explicit fetch should not update tracking' '
+test_expect_success 'mark initial state of origin/master' '
+ (
+ cd three &&
+ git tag base-origin-master refs/remotes/origin/master
+ )
+'
+
+test_expect_success 'explicit fetch should update tracking' '
cd "$D" &&
git branch -f side &&
(
cd three &&
+ git update-ref refs/remotes/origin/master base-origin-master &&
o=$(git rev-parse --verify refs/remotes/origin/master) &&
git fetch origin master &&
n=$(git rev-parse --verify refs/remotes/origin/master) &&
- test "$o" = "$n" &&
+ test "$o" != "$n" &&
test_must_fail git rev-parse --verify refs/remotes/origin/side
)
'
-test_expect_success 'explicit pull should not update tracking' '
+test_expect_success 'explicit pull should update tracking' '
cd "$D" &&
git branch -f side &&
(
cd three &&
+ git update-ref refs/remotes/origin/master base-origin-master &&
o=$(git rev-parse --verify refs/remotes/origin/master) &&
git pull origin master &&
n=$(git rev-parse --verify refs/remotes/origin/master) &&
- test "$o" = "$n" &&
+ test "$o" != "$n" &&
test_must_fail git rev-parse --verify refs/remotes/origin/side
)
'
@@ -404,6 +413,7 @@ test_expect_success 'configured fetch updates tracking' '
git branch -f side &&
(
cd three &&
+ git update-ref refs/remotes/origin/master base-origin-master &&
o=$(git rev-parse --verify refs/remotes/origin/master) &&
git fetch origin &&
n=$(git rev-parse --verify refs/remotes/origin/master) &&
@@ -412,6 +422,22 @@ test_expect_success 'configured fetch updates tracking' '
)
'
+test_expect_success 'non-matching refspecs do not confuse tracking update' '
+ cd "$D" &&
+ git update-ref refs/odd/location HEAD &&
+ (
+ cd three &&
+ git update-ref refs/remotes/origin/master base-origin-master &&
+ git config --add remote.origin.fetch \
+ refs/odd/location:refs/remotes/origin/odd &&
+ o=$(git rev-parse --verify refs/remotes/origin/master) &&
+ git fetch origin master &&
+ n=$(git rev-parse --verify refs/remotes/origin/master) &&
+ test "$o" != "$n" &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/odd
+ )
+'
+
test_expect_success 'pushing nonexistent branch by mistake should not segv' '
cd "$D" &&
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 6af6c63..ed4d9c8 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -57,6 +57,35 @@ test_expect_success 'pulling into void does not overwrite untracked files' '
)
'
+test_expect_success 'pulling into void does not overwrite staged files' '
+ git init cloned-staged-colliding &&
+ (
+ cd cloned-staged-colliding &&
+ echo "alternate content" >file &&
+ git add file &&
+ test_must_fail git pull .. master &&
+ echo "alternate content" >expect &&
+ test_cmp expect file &&
+ git cat-file blob :file >file.index &&
+ test_cmp expect file.index
+ )
+'
+
+
+test_expect_success 'pulling into void does not remove new staged files' '
+ git init cloned-staged-new &&
+ (
+ cd cloned-staged-new &&
+ echo "new tracked file" >newfile &&
+ git add newfile &&
+ git pull .. master &&
+ echo "new tracked file" >expect &&
+ test_cmp expect newfile &&
+ git cat-file blob :newfile >newfile.index &&
+ test_cmp expect newfile.index
+ )
+'
+
test_expect_success 'test . as a remote' '
git branch copy master &&
diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh
index aa31abe..453aba5 100755
--- a/t/t5521-pull-options.sh
+++ b/t/t5521-pull-options.sh
@@ -15,19 +15,19 @@ test_expect_success 'git pull -q' '
mkdir clonedq &&
(cd clonedq && git init &&
git pull -q "../parent" >out 2>err &&
- test ! -s err &&
- test ! -s out)
+ test_must_be_empty err &&
+ test_must_be_empty out)
'
test_expect_success 'git pull -q --rebase' '
mkdir clonedqrb &&
(cd clonedqrb && git init &&
git pull -q --rebase "../parent" >out 2>err &&
- test ! -s err &&
- test ! -s out &&
+ test_must_be_empty err &&
+ test_must_be_empty out &&
git pull -q --rebase "../parent" >out 2>err &&
- test ! -s err &&
- test ! -s out)
+ test_must_be_empty err &&
+ test_must_be_empty out)
'
test_expect_success 'git pull' '
@@ -35,7 +35,7 @@ test_expect_success 'git pull' '
(cd cloned && git init &&
git pull "../parent" >out 2>err &&
test -s err &&
- test ! -s out)
+ test_must_be_empty out)
'
test_expect_success 'git pull --rebase' '
@@ -43,7 +43,7 @@ test_expect_success 'git pull --rebase' '
(cd clonedrb && git init &&
git pull --rebase "../parent" >out 2>err &&
test -s err &&
- test ! -s out)
+ test_must_be_empty out)
'
test_expect_success 'git pull -v' '
@@ -51,7 +51,7 @@ test_expect_success 'git pull -v' '
(cd clonedv && git init &&
git pull -v "../parent" >out 2>err &&
test -s err &&
- test ! -s out)
+ test_must_be_empty out)
'
test_expect_success 'git pull -v --rebase' '
@@ -59,22 +59,22 @@ test_expect_success 'git pull -v --rebase' '
(cd clonedvrb && git init &&
git pull -v --rebase "../parent" >out 2>err &&
test -s err &&
- test ! -s out)
+ test_must_be_empty out)
'
test_expect_success 'git pull -v -q' '
mkdir clonedvq &&
(cd clonedvq && git init &&
git pull -v -q "../parent" >out 2>err &&
- test ! -s out &&
- test ! -s err)
+ test_must_be_empty out &&
+ test_must_be_empty err)
'
test_expect_success 'git pull -q -v' '
mkdir clonedqv &&
(cd clonedqv && git init &&
git pull -q -v "../parent" >out 2>err &&
- test ! -s out &&
+ test_must_be_empty out &&
test -s err)
'
diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh
index 4736da8..6a5ac3a 100755
--- a/t/t5528-push-default.sh
+++ b/t/t5528-push-default.sh
@@ -15,17 +15,19 @@ test_expect_success 'setup bare remotes' '
# $1 = local revision
# $2 = remote revision (tested to be equal to the local one)
+# $3 = [optional] repo to check for actual output (repo1 by default)
check_pushed_commit () {
git log -1 --format='%h %s' "$1" >expect &&
- git --git-dir=repo1 log -1 --format='%h %s' "$2" >actual &&
+ git --git-dir="${3:-repo1}" log -1 --format='%h %s' "$2" >actual &&
test_cmp expect actual
}
# $1 = push.default value
# $2 = expected target branch for the push
+# $3 = [optional] repo to check for actual output (repo1 by default)
test_push_success () {
git -c push.default="$1" push &&
- check_pushed_commit HEAD "$2"
+ check_pushed_commit HEAD "$2" "$3"
}
# $1 = push.default value
@@ -37,6 +39,26 @@ test_push_failure () {
test_cmp expect actual
}
+# $1 = success or failure
+# $2 = push.default value
+# $3 = branch to check for actual output (master or foo)
+# $4 = [optional] switch to triangular workflow
+test_pushdefault_workflow () {
+ workflow=central
+ pushdefault=parent1
+ if test -n "${4-}"; then
+ workflow=triangular
+ pushdefault=parent2
+ fi
+ test_expect_success "push.default = $2 $1 in $workflow workflows" "
+ test_config branch.master.remote parent1 &&
+ test_config branch.master.merge refs/heads/foo &&
+ test_config remote.pushdefault $pushdefault &&
+ test_commit commit-for-$2${4+-triangular} &&
+ test_push_$1 $2 $3 ${4+repo2}
+ "
+}
+
test_expect_success '"upstream" pushes to configured upstream' '
git checkout master &&
test_config branch.master.remote parent1 &&
@@ -48,7 +70,6 @@ test_expect_success '"upstream" pushes to configured upstream' '
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
'
@@ -57,7 +78,6 @@ 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
'
@@ -115,4 +135,41 @@ test_expect_success 'push to existing branch, upstream configured with different
test_cmp expect-other-name actual-other-name
'
+# We are on 'master', which integrates with 'foo' from parent1
+# remote (set in test_pushdefault_workflow helper). Push to
+# parent1 in centralized, and push to parent2 in triangular workflow.
+# The parent1 repository has 'master' and 'foo' branches, while
+# the parent2 repository has only 'master' branch.
+#
+# test_pushdefault_workflow() arguments:
+# $1 = success or failure
+# $2 = push.default value
+# $3 = branch to check for actual output (master or foo)
+# $4 = [optional] switch to triangular workflow
+
+# update parent1's master (which is not our upstream)
+test_pushdefault_workflow success current master
+
+# update parent1's foo (which is our upstream)
+test_pushdefault_workflow success upstream foo
+
+# upsream is foo which is not the name of the current branch
+test_pushdefault_workflow failure simple master
+
+# master and foo are updated
+test_pushdefault_workflow success matching master
+
+# master is updated
+test_pushdefault_workflow success current master triangular
+
+# upstream mode cannot be used in triangular
+test_pushdefault_workflow failure upstream foo triangular
+
+# in triangular, 'simple' works as 'current' and update the branch
+# with the same name.
+test_pushdefault_workflow success simple master triangular
+
+# master is updated (parent2 does not have foo)
+test_pushdefault_workflow success matching master triangular
+
test_done
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
index b23efbb..55a866a 100755
--- a/t/t5551-http-fetch.sh
+++ b/t/t5551-http-fetch.sh
@@ -209,13 +209,17 @@ test_expect_success EXPENSIVE 'create 50,000 tags in the repo' '
# 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
+ 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
+ test_line_count = 0 err &&
+ (
+ cd too-many-refs &&
+ test $(git for-each-ref refs/tags | wc -l) = 50000
+ )
'
stop_httpd
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 67869b4..0629149 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -280,4 +280,9 @@ test_expect_success 'clone checking out a tag' '
test_cmp fetch.expected fetch.actual
'
+test_expect_success NOT_MINGW,NOT_CYGWIN 'clone local path foo:bar' '
+ cp -R src "foo:bar" &&
+ git clone "./foo:bar" foobar
+'
+
test_done
diff --git a/t/t5702-clone-options.sh b/t/t5702-clone-options.sh
index 02cb024..85cadfa 100755
--- a/t/t5702-clone-options.sh
+++ b/t/t5702-clone-options.sh
@@ -22,7 +22,7 @@ test_expect_success 'clone -o' '
test_expect_success 'redirected clone' '
git clone "file://$(pwd)/parent" clone-redirected >out 2>err &&
- test ! -s err
+ test_must_be_empty err
'
test_expect_success 'redirected clone -v' '
diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh
index 8956c21..5a6e49d 100755
--- a/t/t5710-info-alternate.sh
+++ b/t/t5710-info-alternate.sh
@@ -58,7 +58,13 @@ test_expect_success 'creating too deep nesting' \
git clone -l -s D E &&
git clone -l -s E F &&
git clone -l -s F G &&
-test_must_fail git clone --bare -l -s G H'
+git clone --bare -l -s G H'
+
+test_expect_success 'invalidity of deepest repository' \
+'cd H && {
+ test_valid_repo
+ test $? -ne 0
+}'
cd "$base_dir"
diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh
index dbb02e2..8c4c539 100755
--- a/t/t5801-remote-helpers.sh
+++ b/t/t5801-remote-helpers.sh
@@ -8,11 +8,6 @@ test_description='Test remote-helper import and export commands'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
-if ! type "${BASH-bash}" >/dev/null 2>&1; then
- skip_all='skipping remote-testgit tests, bash not available'
- test_done
-fi
-
compare_refs() {
git --git-dir="$1/.git" rev-parse --verify $2 >expect &&
git --git-dir="$3/.git" rev-parse --verify $4 >actual &&
@@ -101,39 +96,28 @@ test_expect_failure 'push new branch with old:new refspec' '
test_expect_success 'cloning without refspec' '
GIT_REMOTE_TESTGIT_REFSPEC="" \
- git clone "testgit::${PWD}/server" local2 &&
+ git clone "testgit::${PWD}/server" local2 2>error &&
+ grep "This remote helper should implement refspec capability" error &&
compare_refs local2 HEAD server HEAD
'
test_expect_success 'pulling without refspecs' '
(cd local2 &&
git reset --hard &&
- GIT_REMOTE_TESTGIT_REFSPEC="" git pull) &&
+ GIT_REMOTE_TESTGIT_REFSPEC="" git pull 2>../error) &&
+ grep "This remote helper should implement refspec capability" error &&
compare_refs local2 HEAD server HEAD
'
-test_expect_failure 'pushing without refspecs' '
+test_expect_success 'pushing without refspecs' '
test_when_finished "(cd local2 && git reset --hard origin)" &&
(cd local2 &&
echo content >>file &&
git commit -a -m ten &&
- GIT_REMOTE_TESTGIT_REFSPEC="" git push) &&
- compare_refs local2 HEAD server HEAD
-'
-
-test_expect_success 'pulling with straight refspec' '
- (cd local2 &&
- GIT_REMOTE_TESTGIT_REFSPEC="*:*" git pull) &&
- compare_refs local2 HEAD server HEAD
-'
-
-test_expect_failure 'pushing with straight refspec' '
- test_when_finished "(cd local2 && git reset --hard origin)" &&
- (cd local2 &&
- echo content >>file &&
- git commit -a -m eleven &&
- GIT_REMOTE_TESTGIT_REFSPEC="*:*" git push) &&
- compare_refs local2 HEAD server HEAD
+ GIT_REMOTE_TESTGIT_REFSPEC="" &&
+ export GIT_REMOTE_TESTGIT_REFSPEC &&
+ test_must_fail git push 2>../error) &&
+ grep "remote-helper doesn.t support push; refspec needed" error
'
test_expect_success 'pulling without marks' '
@@ -186,6 +170,50 @@ test_expect_success GPG 'push signed tag with signed-tags capability' '
compare_refs local signed-tag-2 server signed-tag-2
'
+test_expect_success 'push update refs' '
+ (cd local &&
+ git checkout -b update master &&
+ echo update >>file &&
+ git commit -a -m update &&
+ git push origin update &&
+ git rev-parse --verify remotes/origin/update >expect &&
+ git rev-parse --verify testgit/origin/heads/update >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'push update refs failure' '
+ (cd local &&
+ git checkout update &&
+ echo "update fail" >>file &&
+ git commit -a -m "update fail" &&
+ git rev-parse --verify testgit/origin/heads/update >expect &&
+ GIT_REMOTE_TESTGIT_PUSH_ERROR="non-fast forward" &&
+ export GIT_REMOTE_TESTGIT_PUSH_ERROR &&
+ test_expect_code 1 git push origin update &&
+ git rev-parse --verify testgit/origin/heads/update >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'proper failure checks for fetching' '
+ (GIT_REMOTE_TESTGIT_FAILURE=1 &&
+ export GIT_REMOTE_TESTGIT_FAILURE &&
+ cd local &&
+ test_must_fail git fetch 2> error &&
+ cat error &&
+ grep -q "Error while running fast-import" error
+ )
+'
+
+test_expect_success 'proper failure checks for pushing' '
+ (GIT_REMOTE_TESTGIT_FAILURE=1 &&
+ export GIT_REMOTE_TESTGIT_FAILURE &&
+ cd local &&
+ test_must_fail git push --all
+ )
+'
+
test_expect_success 'push messages' '
(cd local &&
git checkout -b new_branch master &&
diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh
index fb07536..43ad772 100755
--- a/t/t6002-rev-list-bisect.sh
+++ b/t/t6002-rev-list-bisect.sh
@@ -39,25 +39,25 @@ test_bisection_diff()
date >path0
git update-index --add path0
save_tag tree git write-tree
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
-on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
-on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
-on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
-on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
-on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
-on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
-on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
-on_committer_date "1971-08-16 00:00:08" save_tag b2 unique_commit b2 tree -p b1
-on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
-on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
-on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
-on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
-on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
-on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
-on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
-on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
-on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
-on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
+on_committer_date "00:00" hide_error save_tag root unique_commit root tree
+on_committer_date "00:01" save_tag l0 unique_commit l0 tree -p root
+on_committer_date "00:02" save_tag l1 unique_commit l1 tree -p l0
+on_committer_date "00:03" save_tag l2 unique_commit l2 tree -p l1
+on_committer_date "00:04" save_tag a0 unique_commit a0 tree -p l2
+on_committer_date "00:05" save_tag a1 unique_commit a1 tree -p a0
+on_committer_date "00:06" save_tag b1 unique_commit b1 tree -p a0
+on_committer_date "00:07" save_tag c1 unique_commit c1 tree -p b1
+on_committer_date "00:08" save_tag b2 unique_commit b2 tree -p b1
+on_committer_date "00:09" save_tag b3 unique_commit b2 tree -p b2
+on_committer_date "00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_committer_date "00:11" save_tag c3 unique_commit c3 tree -p c2
+on_committer_date "00:12" save_tag a2 unique_commit a2 tree -p a1
+on_committer_date "00:13" save_tag a3 unique_commit a3 tree -p a2
+on_committer_date "00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_committer_date "00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+on_committer_date "00:16" save_tag l3 unique_commit l3 tree -p a4
+on_committer_date "00:17" save_tag l4 unique_commit l4 tree -p l3
+on_committer_date "00:18" save_tag l5 unique_commit l5 tree -p l4
git update-ref HEAD $(tag l5)
@@ -90,29 +90,29 @@ git update-ref HEAD $(tag l5)
# F
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag F unique_commit F tree
-on_committer_date "1971-08-16 00:00:01" save_tag e8 unique_commit e8 tree -p F
-on_committer_date "1971-08-16 00:00:02" save_tag e7 unique_commit e7 tree -p e8
-on_committer_date "1971-08-16 00:00:03" save_tag e6 unique_commit e6 tree -p e7
-on_committer_date "1971-08-16 00:00:04" save_tag e5 unique_commit e5 tree -p e6
-on_committer_date "1971-08-16 00:00:05" save_tag f4 unique_commit f4 tree -p F
-on_committer_date "1971-08-16 00:00:06" save_tag f3 unique_commit f3 tree -p f4
-on_committer_date "1971-08-16 00:00:07" save_tag f2 unique_commit f2 tree -p f3
-on_committer_date "1971-08-16 00:00:08" save_tag f1 unique_commit f1 tree -p f2
-on_committer_date "1971-08-16 00:00:09" save_tag e4 unique_commit e4 tree -p e5
-on_committer_date "1971-08-16 00:00:10" save_tag e3 unique_commit e3 tree -p e4
-on_committer_date "1971-08-16 00:00:11" save_tag e2 unique_commit e2 tree -p e3
-on_committer_date "1971-08-16 00:00:12" save_tag e1 unique_commit e1 tree -p e2
-on_committer_date "1971-08-16 00:00:13" save_tag E unique_commit E tree -p e1 -p f1
-
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag U unique_commit U tree
-on_committer_date "1971-08-16 00:00:01" save_tag u0 unique_commit u0 tree -p U
-on_committer_date "1971-08-16 00:00:01" save_tag u1 unique_commit u1 tree -p u0
-on_committer_date "1971-08-16 00:00:02" save_tag u2 unique_commit u2 tree -p u0
-on_committer_date "1971-08-16 00:00:03" save_tag u3 unique_commit u3 tree -p u0
-on_committer_date "1971-08-16 00:00:04" save_tag u4 unique_commit u4 tree -p u0
-on_committer_date "1971-08-16 00:00:05" save_tag u5 unique_commit u5 tree -p u0
-on_committer_date "1971-08-16 00:00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5
+on_committer_date "00:00" hide_error save_tag F unique_commit F tree
+on_committer_date "00:01" save_tag e8 unique_commit e8 tree -p F
+on_committer_date "00:02" save_tag e7 unique_commit e7 tree -p e8
+on_committer_date "00:03" save_tag e6 unique_commit e6 tree -p e7
+on_committer_date "00:04" save_tag e5 unique_commit e5 tree -p e6
+on_committer_date "00:05" save_tag f4 unique_commit f4 tree -p F
+on_committer_date "00:06" save_tag f3 unique_commit f3 tree -p f4
+on_committer_date "00:07" save_tag f2 unique_commit f2 tree -p f3
+on_committer_date "00:08" save_tag f1 unique_commit f1 tree -p f2
+on_committer_date "00:09" save_tag e4 unique_commit e4 tree -p e5
+on_committer_date "00:10" save_tag e3 unique_commit e3 tree -p e4
+on_committer_date "00:11" save_tag e2 unique_commit e2 tree -p e3
+on_committer_date "00:12" save_tag e1 unique_commit e1 tree -p e2
+on_committer_date "00:13" save_tag E unique_commit E tree -p e1 -p f1
+
+on_committer_date "00:00" hide_error save_tag U unique_commit U tree
+on_committer_date "00:01" save_tag u0 unique_commit u0 tree -p U
+on_committer_date "00:01" save_tag u1 unique_commit u1 tree -p u0
+on_committer_date "00:02" save_tag u2 unique_commit u2 tree -p u0
+on_committer_date "00:03" save_tag u3 unique_commit u3 tree -p u0
+on_committer_date "00:04" save_tag u4 unique_commit u4 tree -p u0
+on_committer_date "00:05" save_tag u5 unique_commit u5 tree -p u0
+on_committer_date "00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5
test_sequence()
{
diff --git a/t/t6003-rev-list-topo-order.sh b/t/t6003-rev-list-topo-order.sh
index e4c52b0..24d1836 100755
--- a/t/t6003-rev-list-topo-order.sh
+++ b/t/t6003-rev-list-topo-order.sh
@@ -16,39 +16,34 @@ list_duplicates()
date >path0
git update-index --add path0
save_tag tree git write-tree
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
-on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
-on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
-on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
-on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
-on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
-on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
-on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
-on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
-on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b3 tree -p b2
-on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
-on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
-on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
-on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
-on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
-on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
-on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
-on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
-on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
-on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
-on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
-on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree
-on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root
-on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0
-on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
-on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
+on_dates "00:00" "00:00" hide_error save_tag root unique_commit root tree
+on_dates "00:01" "00:01" save_tag l0 unique_commit l0 tree -p root
+on_dates "00:02" "00:02" save_tag l1 unique_commit l1 tree -p l0
+on_dates "00:03" "00:03" save_tag l2 unique_commit l2 tree -p l1
+on_dates "00:04" "00:04" save_tag a0 unique_commit a0 tree -p l2
+on_dates "00:05" "00:05" save_tag a1 unique_commit a1 tree -p a0
+on_dates "00:06" "00:06" save_tag b1 unique_commit b1 tree -p a0
+on_dates "00:07" "00:07" save_tag c1 unique_commit c1 tree -p b1
+on_dates "00:08" "00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
+on_dates "00:09" "00:09" save_tag b3 unique_commit b3 tree -p b2
+on_dates "00:10" "00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_dates "00:11" "00:11" save_tag c3 unique_commit c3 tree -p c2
+on_dates "00:12" "00:00" save_tag a2 unique_commit a2 tree -p a1
+on_dates "00:13" "00:01" save_tag a3 unique_commit a3 tree -p a2
+on_dates "00:14" "00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_dates "00:15" "00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+on_dates "00:16" "00:16" save_tag l3 unique_commit l3 tree -p a4
+on_dates "00:17" "00:17" save_tag l4 unique_commit l4 tree -p l3
+on_dates "00:18" "00:18" save_tag l5 unique_commit l5 tree -p l4
+on_dates "00:19" "00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
+on_dates "00:20" "00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
+on_dates "00:21" "00:21" hide_error save_tag alt_root unique_commit alt_root tree
+on_dates "00:22" "00:22" save_tag r0 unique_commit r0 tree -p alt_root
+on_dates "00:23" "00:23" save_tag r1 unique_commit r1 tree -p r0
+on_dates "00:24" "00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
+on_dates "00:25" "00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
-#
-# note: as of 20/6, it isn't possible to create duplicate parents, so this
-# can't be tested.
-#
-#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
@@ -105,6 +100,50 @@ l0
root
EOF
+test_output_expect_success 'simple date order' 'git rev-list --date-order HEAD' <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+c2
+b3
+b2
+c1
+b1
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'simple author-date order' 'git rev-list --author-date-order HEAD' <<EOF
+l5
+l4
+l3
+a4
+b4
+c3
+c2
+b3
+b2
+c1
+b1
+a3
+a2
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
test_output_expect_success 'two diamonds topo order (g6)' 'git rev-list --topo-order g4' <<EOF
g4
h2
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
index 0393c9f..9874403 100755
--- a/t/t6006-rev-list-format.sh
+++ b/t/t6006-rev-list-format.sh
@@ -1,20 +1,45 @@
#!/bin/sh
+# Copyright (c) 2009 Jens Lehmann
+# Copyright (c) 2011 Alexey Shumkin (+ non-UTF-8 commit encoding tests)
+
test_description='git rev-list --pretty=format test'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
test_tick
+# String "added" in German
+# (translated with Google Translate),
+# encoded in UTF-8, used as a commit log message below.
+added=$(printf "added (hinzugef\303\274gt) foo")
+added_iso88591=$(echo "$added" | iconv -f utf-8 -t iso8859-1)
+# same but "changed"
+changed=$(printf "changed (ge\303\244ndert) foo")
+changed_iso88591=$(echo "$changed" | iconv -f utf-8 -t iso8859-1)
+
test_expect_success 'setup' '
-touch foo && git add foo && git commit -m "added foo" &&
- echo changed >foo && git commit -a -m "changed foo"
+ : >foo &&
+ git add foo &&
+ git config i18n.commitEncoding iso8859-1 &&
+ git commit -m "$added_iso88591" &&
+ head1=$(git rev-parse --verify HEAD) &&
+ head1_short=$(git rev-parse --verify --short $head1) &&
+ tree1=$(git rev-parse --verify HEAD:) &&
+ tree1_short=$(git rev-parse --verify --short $tree1) &&
+ echo "$changed" > foo &&
+ git commit -a -m "$changed_iso88591" &&
+ head2=$(git rev-parse --verify HEAD) &&
+ head2_short=$(git rev-parse --verify --short $head2) &&
+ tree2=$(git rev-parse --verify HEAD:) &&
+ tree2_short=$(git rev-parse --verify --short $tree2)
+ git config --unset i18n.commitEncoding
'
-# usage: test_format name format_string <expected_output
+# usage: test_format name format_string [failure] <expected_output
test_format () {
cat >expect.$1
- test_expect_success "format $1" "
+ test_expect_${3:-success} "format $1" "
git rev-list --pretty=format:'$2' master >output.$1 &&
test_cmp expect.$1 output.$1
"
@@ -32,49 +57,49 @@ has_no_color () {
test_cmp expect "$1"
}
-test_format percent %%h <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
+test_format percent %%h <<EOF
+commit $head2
%h
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+commit $head1
%h
EOF
-test_format hash %H%n%h <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
-131a310eb913d107dd3c09a65d1651175898735d
-131a310
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
-86c75cfd708a0e5868dc876ed5b8bb66c80b4873
-86c75cf
+test_format hash %H%n%h <<EOF
+commit $head2
+$head2
+$head2_short
+commit $head1
+$head1
+$head1_short
EOF
-test_format tree %T%n%t <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
-fe722612f26da5064c32ca3843aa154bdb0b08a0
-fe72261
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
-4d5fcadc293a348e88f777dc0920f11e7d71441c
-4d5fcad
+test_format tree %T%n%t <<EOF
+commit $head2
+$tree2
+$tree2_short
+commit $head1
+$tree1
+$tree1_short
EOF
-test_format parents %P%n%p <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
-86c75cfd708a0e5868dc876ed5b8bb66c80b4873
-86c75cf
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+test_format parents %P%n%p <<EOF
+commit $head2
+$head1
+$head1_short
+commit $head1
EOF
# we don't test relative here
-test_format author %an%n%ae%n%ad%n%aD%n%at <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
+test_format author %an%n%ae%n%ad%n%aD%n%at <<EOF
+commit $head2
A U Thor
author@example.com
Thu Apr 7 15:13:13 2005 -0700
Thu, 7 Apr 2005 15:13:13 -0700
1112911993
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+commit $head1
A U Thor
author@example.com
Thu Apr 7 15:13:13 2005 -0700
@@ -82,14 +107,14 @@ Thu, 7 Apr 2005 15:13:13 -0700
1112911993
EOF
-test_format committer %cn%n%ce%n%cd%n%cD%n%ct <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
+test_format committer %cn%n%ce%n%cd%n%cD%n%ct <<EOF
+commit $head2
C O Mitter
committer@example.com
Thu Apr 7 15:13:13 2005 -0700
Thu, 7 Apr 2005 15:13:13 -0700
1112911993
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+commit $head1
C O Mitter
committer@example.com
Thu Apr 7 15:13:13 2005 -0700
@@ -97,43 +122,45 @@ Thu, 7 Apr 2005 15:13:13 -0700
1112911993
EOF
-test_format encoding %e <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+test_format encoding %e <<EOF
+commit $head2
+iso8859-1
+commit $head1
+iso8859-1
EOF
-test_format subject %s <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
-changed foo
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
-added foo
+test_format subject %s <<EOF
+commit $head2
+$changed
+commit $head1
+$added
EOF
-test_format body %b <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+test_format body %b <<EOF
+commit $head2
+commit $head1
EOF
-test_format raw-body %B <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
-changed foo
+test_format raw-body %B <<EOF
+commit $head2
+$changed
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
-added foo
+commit $head1
+$added
EOF
-test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
+test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<EOF
+commit $head2
foobarbazxyzzy
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+commit $head1
foobarbazxyzzy
EOF
-test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<'EOF'
-commit 131a310eb913d107dd3c09a65d1651175898735d
+test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<EOF
+commit $head2
foo
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+commit $head1
foo
EOF
@@ -179,46 +206,71 @@ test_expect_success '%C(auto) respects --color=auto (stdout not tty)' '
)
'
-cat >commit-msg <<'EOF'
+iconv -f utf-8 -t iso8859-1 > commit-msg <<EOF
Test printing of complex bodies
This commit message is much longer than the others,
and it will be encoded in iso8859-1. We should therefore
-include an iso8859 character: ¡bueno!
+include an iso8859 character: ¡bueno!
EOF
+
test_expect_success 'setup complex body' '
-git config i18n.commitencoding iso8859-1 &&
- echo change2 >foo && git commit -a -F commit-msg
+ git config i18n.commitencoding iso8859-1 &&
+ echo change2 >foo && git commit -a -F commit-msg &&
+ head3=$(git rev-parse --verify HEAD) &&
+ head3_short=$(git rev-parse --short $head3)
'
-test_format complex-encoding %e <<'EOF'
-commit 1ed88da4a5b5ed8c449114ac131efc62178734c3
+test_format complex-encoding %e <<EOF
+commit $head3
+iso8859-1
+commit $head2
+iso8859-1
+commit $head1
iso8859-1
-commit 131a310eb913d107dd3c09a65d1651175898735d
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
EOF
-test_format complex-subject %s <<'EOF'
-commit 1ed88da4a5b5ed8c449114ac131efc62178734c3
+test_format complex-subject %s <<EOF
+commit $head3
Test printing of complex bodies
-commit 131a310eb913d107dd3c09a65d1651175898735d
-changed foo
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
-added foo
+commit $head2
+$changed_iso88591
+commit $head1
+$added_iso88591
EOF
-test_format complex-body %b <<'EOF'
-commit 1ed88da4a5b5ed8c449114ac131efc62178734c3
-This commit message is much longer than the others,
-and it will be encoded in iso8859-1. We should therefore
-include an iso8859 character: ¡bueno!
+test_expect_success 'prepare expected messages (for test %b)' '
+ cat <<-EOF >expected.utf-8 &&
+ commit $head3
+ This commit message is much longer than the others,
+ and it will be encoded in iso8859-1. We should therefore
+ include an iso8859 character: ¡bueno!
+
+ commit $head2
+ commit $head1
+ EOF
+ iconv -f utf-8 -t iso8859-1 expected.utf-8 >expected.iso8859-1
+'
+
+test_format complex-body %b <expected.iso8859-1
-commit 131a310eb913d107dd3c09a65d1651175898735d
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+# Git uses i18n.commitEncoding if no i18n.logOutputEncoding set
+# so unset i18n.commitEncoding to test encoding conversion
+git config --unset i18n.commitEncoding
+
+test_format complex-subject-commitencoding-unset %s <<EOF
+commit $head3
+Test printing of complex bodies
+commit $head2
+$changed
+commit $head1
+$added
EOF
+test_format complex-body-commitencoding-unset %b <expected.utf-8
+
test_expect_success '%x00 shows NUL' '
- echo >expect commit 1ed88da4a5b5ed8c449114ac131efc62178734c3 &&
+ echo >expect commit $head3 &&
echo >>expect fooQbar &&
git rev-list -1 --format=foo%x00bar HEAD >actual.nul &&
nul_to_q <actual.nul >actual &&
@@ -265,12 +317,12 @@ test_expect_success 'add LF before non-empty (2)' '
test_expect_success 'add SP before non-empty (1)' '
git show -s --pretty=format:"%s% bThanks" HEAD^^ >actual &&
- test $(wc -w <actual) = 2
+ test $(wc -w <actual) = 3
'
test_expect_success 'add SP before non-empty (2)' '
git show -s --pretty=format:"%s% sThanks" HEAD^^ >actual &&
- test $(wc -w <actual) = 4
+ test $(wc -w <actual) = 6
'
test_expect_success '--abbrev' '
diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh
index dd6dc84..57ce239 100755
--- a/t/t6012-rev-list-simplify.sh
+++ b/t/t6012-rev-list-simplify.sh
@@ -14,21 +14,24 @@ unnote () {
test_expect_success setup '
echo "Hi there" >file &&
- git add file &&
- test_tick && git commit -m "Initial file" &&
+ echo "initial" >lost &&
+ git add file lost &&
+ test_tick && git commit -m "Initial file and lost" &&
note A &&
git branch other-branch &&
echo "Hello" >file &&
- git add file &&
- test_tick && git commit -m "Modified file" &&
+ echo "second" >lost &&
+ git add file lost &&
+ test_tick && git commit -m "Modified file and lost" &&
note B &&
git checkout other-branch &&
echo "Hello" >file &&
- git add file &&
+ >lost &&
+ git add file lost &&
test_tick && git commit -m "Modified the file identically" &&
note C &&
@@ -37,7 +40,9 @@ test_expect_success setup '
test_tick && git commit -m "Add another file" &&
note D &&
- test_tick && git merge -m "merge" master &&
+ test_tick &&
+ test_must_fail git merge -m "merge" master &&
+ >lost && git commit -a -m "merge" &&
note E &&
echo "Yet another" >elif &&
@@ -105,9 +110,21 @@ check_result 'L K J I H G F E D C B A' --full-history
check_result 'K I H E C B A' --full-history -- file
check_result 'K I H E C B A' --full-history --topo-order -- file
check_result 'K I H E C B A' --full-history --date-order -- file
-check_outcome failure 'I E C B A' --simplify-merges -- 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
+check_result 'E C B A' --full-history E -- lost
+test_expect_success 'full history simplification without parent' '
+ printf "%s\n" E C B A >expect &&
+ git log --pretty="$FMT" --full-history E -- lost |
+ unnote >actual &&
+ sed -e "s/^.* \([^ ]*\) .*/\1/" >check <actual &&
+ test_cmp expect check || {
+ cat actual
+ false
+ }
+'
+
test_done
diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh
index 39b4cb0..dabebae 100755
--- a/t/t6019-rev-list-ancestry-path.sh
+++ b/t/t6019-rev-list-ancestry-path.sh
@@ -13,6 +13,13 @@ test_description='--ancestry-path'
#
# D..M -- M.t == M
# --ancestry-path D..M -- M.t == M
+#
+# F...I == F G H I
+# --ancestry-path F...I == F H I
+#
+# G..M -- G.t == [nothing - was dropped in "-s ours" merge L]
+# --ancestry-path G..M -- G.t == L
+# --ancestry-path --simplify-merges G^..M -- G.t == G L
. ./test-lib.sh
@@ -63,13 +70,52 @@ test_expect_success 'rev-list D..M -- M.t' '
test_cmp expect actual
'
-test_expect_success 'rev-list --ancestry-patch D..M -- M.t' '
+test_expect_success 'rev-list --ancestry-path D..M -- M.t' '
echo M >expect &&
git rev-list --ancestry-path --format=%s D..M -- M.t |
sed -e "/^commit /d" >actual &&
test_cmp expect actual
'
+test_expect_success 'rev-list F...I' '
+ for c in F G H I; do echo $c; done >expect &&
+ git rev-list --format=%s F...I |
+ sed -e "/^commit /d" |
+ sort >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path F...I' '
+ for c in F H I; do echo $c; done >expect &&
+ git rev-list --ancestry-path --format=%s F...I |
+ sed -e "/^commit /d" |
+ sort >actual &&
+ test_cmp expect actual
+'
+
+# G.t is dropped in an "-s ours" merge
+test_expect_success 'rev-list G..M -- G.t' '
+ >expect &&
+ git rev-list --format=%s G..M -- G.t |
+ sed -e "/^commit /d" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path G..M -- G.t' '
+ echo L >expect &&
+ git rev-list --ancestry-path --format=%s G..M -- G.t |
+ sed -e "/^commit /d" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path --simplify-merges G^..M -- G.t' '
+ for c in G L; do echo $c; done >expect &&
+ git rev-list --ancestry-path --simplify-merges --format=%s G^..M -- G.t |
+ sed -e "/^commit /d" |
+ sort >actual &&
+ test_cmp expect actual
+'
+
# b---bc
# / \ /
# a X
diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh
index 2599ae5..9324ea4 100755
--- a/t/t6035-merge-dir-to-symlink.sh
+++ b/t/t6035-merge-dir-to-symlink.sh
@@ -3,7 +3,7 @@
test_description='merging when a directory was replaced with a symlink'
. ./test-lib.sh
-test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' '
+test_expect_success 'create a commit where dir a/b changed to symlink' '
mkdir -p a/b/c a/b-2/c &&
> a/b/c/d &&
> a/b-2/c/d &&
@@ -12,12 +12,12 @@ test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink'
git commit -m base &&
git tag start &&
rm -rf a/b &&
- ln -s b-2 a/b &&
git add -A &&
+ test_ln_s_add b-2 a/b &&
git commit -m "dir to symlink"
'
-test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' '
+test_expect_success 'checkout does not clobber untracked symlink' '
git checkout HEAD^0 &&
git reset --hard master &&
git rm --cached a/b &&
@@ -25,7 +25,7 @@ test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' '
test_must_fail git checkout start^0
'
-test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' '
+test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' '
git checkout HEAD^0 &&
git reset --hard master &&
git rm --cached a/b &&
@@ -34,14 +34,14 @@ test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' '
test -f a/b-2/c/d
'
-test_expect_success SYMLINKS 'checkout should not have deleted a/b-2/c/d' '
+test_expect_success 'checkout should not have deleted a/b-2/c/d' '
git checkout HEAD^0 &&
git reset --hard master &&
git checkout start^0 &&
test -f a/b-2/c/d
'
-test_expect_success SYMLINKS 'setup for merge test' '
+test_expect_success 'setup for merge test' '
git reset --hard &&
test -f a/b-2/c/d &&
echo x > a/x &&
@@ -50,39 +50,51 @@ test_expect_success SYMLINKS 'setup for merge test' '
git tag baseline
'
-test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' '
+test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s resolve master &&
- test -h a/b &&
test -f a/b-2/c/d
'
-test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' '
+test_expect_success SYMLINKS 'a/b was resolved as symlink' '
+ test -h a/b
+'
+
+test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s recursive master &&
- test -h a/b &&
test -f a/b-2/c/d
'
-test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' '
+test_expect_success SYMLINKS 'a/b was resolved as symlink' '
+ test -h a/b
+'
+
+test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' '
git reset --hard &&
git checkout master^0 &&
git merge -s resolve baseline^0 &&
- test -h a/b &&
test -f a/b-2/c/d
'
-test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' '
+test_expect_success SYMLINKS 'a/b was resolved as symlink' '
+ test -h a/b
+'
+
+test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' '
git reset --hard &&
git checkout master^0 &&
git merge -s recursive baseline^0 &&
- test -h a/b &&
test -f a/b-2/c/d
'
-test_expect_failure SYMLINKS 'do not lose untracked in merge (resolve)' '
+test_expect_success SYMLINKS 'a/b was resolved as symlink' '
+ test -h a/b
+'
+
+test_expect_failure 'do not lose untracked in merge (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
>a/b/c/e &&
@@ -91,7 +103,7 @@ test_expect_failure SYMLINKS 'do not lose untracked in merge (resolve)' '
test -f a/b-2/c/d
'
-test_expect_success SYMLINKS 'do not lose untracked in merge (recursive)' '
+test_expect_success 'do not lose untracked in merge (recursive)' '
git reset --hard &&
git checkout baseline^0 &&
>a/b/c/e &&
@@ -100,52 +112,61 @@ test_expect_success SYMLINKS 'do not lose untracked in merge (recursive)' '
test -f a/b-2/c/d
'
-test_expect_success SYMLINKS 'do not lose modifications in merge (resolve)' '
+test_expect_success 'do not lose modifications in merge (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
echo more content >>a/b/c/d &&
test_must_fail git merge -s resolve master
'
-test_expect_success SYMLINKS 'do not lose modifications in merge (recursive)' '
+test_expect_success 'do not lose modifications in merge (recursive)' '
git reset --hard &&
git checkout baseline^0 &&
echo more content >>a/b/c/d &&
test_must_fail git merge -s recursive master
'
-test_expect_success SYMLINKS 'setup a merge where dir a/b-2 changed to symlink' '
+test_expect_success 'setup a merge where dir a/b-2 changed to symlink' '
git reset --hard &&
git checkout start^0 &&
rm -rf a/b-2 &&
- ln -s b a/b-2 &&
git add -A &&
+ test_ln_s_add b a/b-2 &&
git commit -m "dir a/b-2 to symlink" &&
git tag test2
'
-test_expect_success SYMLINKS 'merge should not have D/F conflicts (resolve)' '
+test_expect_success 'merge should not have D/F conflicts (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s resolve test2 &&
- test -h a/b-2 &&
test -f a/b/c/d
'
-test_expect_success SYMLINKS 'merge should not have D/F conflicts (recursive)' '
+test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
+ test -h a/b-2
+'
+
+test_expect_success 'merge should not have D/F conflicts (recursive)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s recursive test2 &&
- test -h a/b-2 &&
test -f a/b/c/d
'
-test_expect_success SYMLINKS 'merge should not have F/D conflicts (recursive)' '
+test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
+ test -h a/b-2
+'
+
+test_expect_success 'merge should not have F/D conflicts (recursive)' '
git reset --hard &&
git checkout -b foo test2 &&
git merge -s recursive baseline^0 &&
- test -h a/b-2 &&
test -f a/b/c/d
'
+test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' '
+ test -h a/b-2
+'
+
test_done
diff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh
new file mode 100755
index 0000000..88b84df
--- /dev/null
+++ b/t/t6111-rev-list-treesame.sh
@@ -0,0 +1,196 @@
+#!/bin/sh
+#
+# ,---E--. *H----------. * marks !TREESAME parent paths
+# / \ / \*
+# *A--*B---D--*F-*G---------K-*L-*M
+# \ /* \ /
+# `-C-' `-*I-*J
+#
+# A creates "file", B and F change it.
+# Odd merge G takes the old version from B.
+# I changes it, but J reverts it, so K is TREESAME to both parents.
+# H and L both change "file", and M merges those changes.
+
+test_description='TREESAME and limiting'
+
+. ./test-lib.sh
+
+note () {
+ git tag "$1"
+}
+
+unnote () {
+ git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\))\([ ]\)|\1\2|g"
+}
+
+test_expect_success setup '
+ test_commit "Initial file" file "Hi there" A &&
+ git branch other-branch &&
+
+ test_commit "file=Hello" file "Hello" B &&
+ git branch third-branch &&
+
+ git checkout other-branch &&
+ test_commit "Added other" other "Hello" C &&
+
+ git checkout master &&
+ test_merge D other-branch &&
+
+ git checkout third-branch &&
+ test_commit "Third file" third "Nothing" E &&
+
+ git checkout master &&
+ test_commit "file=Blah" file "Blah" F &&
+
+ test_tick && git merge --no-commit third-branch &&
+ git checkout third-branch file &&
+ git commit &&
+ note G &&
+ git branch fiddler-branch &&
+
+ git checkout -b part2-branch &&
+ test_commit "file=Part 2" file "Part 2" H &&
+
+ git checkout fiddler-branch &&
+ test_commit "Bad commit" file "Silly" I &&
+
+ test_tick && git revert I && note J &&
+
+ git checkout master &&
+ test_tick && git merge --no-ff fiddler-branch &&
+ note K
+
+ test_commit "file=Part 1" file "Part 1" L &&
+
+ test_tick && test_must_fail git merge part2-branch &&
+ test_commit M file "Parts 1+2"
+'
+
+check_outcome () {
+ outcome=$1
+ shift
+
+ case "$1" in
+ *"("*)
+ FMT="%P %H | %s"
+ munge_actual="
+ s/^\([^ ]*\) \([^ ]*\) .*/(\1)\2/
+ s/ //g
+ s/()//
+ "
+ ;;
+ *)
+ FMT="%H | %s"
+ munge_actual="s/^\([^ ]*\) .*/\1/"
+ ;;
+ esac &&
+ printf "%s\n" $1 >expect &&
+ shift
+
+ param="$*" &&
+ test_expect_$outcome "log $param" '
+ git log --format="$FMT" $param |
+ unnote >actual &&
+ sed -e "$munge_actual" <actual >check &&
+ test_cmp expect check || {
+ cat actual
+ false
+ }
+ '
+}
+
+check_result () {
+ check_outcome success "$@"
+}
+
+# Odd merge G drops a change in F. Important that G is listed in all
+# except the most basic list. Achieving this means normal merge D will also be
+# shown in normal full-history, as we can't distinguish unless we do a
+# simplification pass. After simplification, D is dropped but G remains.
+# Also, merge simplification of G should not drop the parent B that the default
+# simple history follows.
+check_result 'M L K J I H G F E D C B A'
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G (D)F (B)E (BC)D (A)C (A)B A'
+check_result 'M H L K J I G E F D C B A' --topo-order
+check_result 'M L H B A' -- file
+check_result '(LH)M (B)L (B)H (A)B A' --parents -- file
+check_result 'M L J I H G F D B A' --full-history -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G (D)F (BA)D (A)B A' --full-history --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file
+check_result 'M L K G F D B A' --first-parent
+check_result 'M L G F B A' --first-parent -- file
+
+# Check that odd merge G remains shown when F is the bottom.
+check_result 'M L K J I H G E' F..M
+check_result 'M H L K J I G E' F..M --topo-order
+check_result 'M L H' F..M -- file
+check_result '(LH)M (B)L (B)H' --parents F..M -- file
+check_result 'M L J I H G' F..M --full-history -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G' F..M --full-history --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file
+check_result 'M L K J I H G' F..M --ancestry-path
+check_result 'M L J I H G' F..M --ancestry-path -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G' F..M --ancestry-path --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FE)G' F..M --ancestry-path --simplify-merges -- file
+check_result 'M L K G' F..M --first-parent
+check_result 'M L G' F..M --first-parent -- file
+
+# Note that G is pruned when E is the bottom, even if it's the same commit list
+# If we want history since E, then we're quite happy to ignore G that took E.
+check_result 'M L K J I H G' E..M --ancestry-path
+check_result 'M L J I H' E..M --ancestry-path -- file
+check_result '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file
+check_result '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file
+
+# Should still be able to ignore I-J branch in simple log, despite limiting
+# to G.
+check_result 'M L K J I H' G..M
+check_result 'M H L K J I' G..M --topo-order
+check_result 'M L H' G..M -- file
+check_result '(LH)M (G)L (G)H' G..M --parents -- file
+check_result 'M L J I H' G..M --full-history -- file
+check_result 'M L K J I H' G..M --full-history --parents -- file
+check_result 'M H L J I' G..M --simplify-merges -- file
+check_result 'M L K J I H' G..M --ancestry-path
+check_result 'M L J I H' G..M --ancestry-path -- file
+check_result 'M L K J I H' G..M --ancestry-path --parents -- file
+check_result 'M H L J I' G..M --ancestry-path --simplify-merges -- file
+
+# B..F should be able to simplify the merge D from irrelevant side branch C.
+# Default log should also be free to follow B-D, and ignore C.
+# But --full-history shouldn't drop D on its own - without simplification,
+# we can't decide if the merge from INTERESTING commit C was sensible.
+check_result 'F D C' B..F
+check_result 'F' B..F -- file
+check_result '(B)F' B..F --parents -- file
+check_result 'F D' B..F --full-history -- file
+check_result '(D)F (BA)D' B..F --full-history --parents -- file
+check_result '(B)F' B..F --simplify-merges -- file
+check_result 'F D' B..F --ancestry-path
+check_result 'F' B..F --ancestry-path -- file
+check_result 'F' B..F --ancestry-path --parents -- file
+check_result 'F' B..F --ancestry-path --simplify-merges -- file
+check_result 'F D' B..F --first-parent
+check_result 'F' B..F --first-parent -- file
+
+# E...F should be equivalent to E F ^B, and be able to drop D as above.
+check_result 'F' E F ^B -- file # includes D
+check_result 'F' E...F -- file # includes D
+
+# Any sort of full history of C..F should show D, as it's the connection to C,
+# and it differs from it.
+check_result 'F D B' C..F
+check_result 'F B' C..F -- file
+check_result '(B)F (A)B' C..F --parents -- file
+check_result 'F D B' C..F --full-history -- file
+check_result '(D)F (BC)D (A)B' C..F --full-history --parents -- file
+check_result '(D)F (BC)D (A)B' C..F --simplify-merges -- file
+check_result 'F D' C..F --ancestry-path
+check_result 'F D' C..F --ancestry-path -- file
+check_result 'F D' C..F --ancestry-path --parents -- file
+check_result 'F D' C..F --ancestry-path --simplify-merges -- file
+check_result 'F D B' C..F --first-parent
+check_result 'F B' C..F --first-parent -- file
+
+
+test_done
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index f67aa6f..a25729f 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -110,6 +110,9 @@ check_describe tags/e --all HEAD^^^
check_describe B-0-* --long HEAD^^2^
check_describe A-3-* --long HEAD^^2
+check_describe c-7-* --tags
+check_describe e-3-* --first-parent --tags
+
: >err.expect
check_describe A --all A^0
test_expect_success 'no warning was displayed for A' '
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index a845b15..101816e 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -218,13 +218,13 @@ test_expect_success 'git mv should not change sha1 of moved cache entry' '
rm -f dirty dirty2
-test_expect_success SYMLINKS 'git mv should overwrite symlink to a file' '
+test_expect_success 'git mv should overwrite symlink to a file' '
rm -fr .git &&
git init &&
echo 1 >moved &&
- ln -s moved symlink &&
- git add moved symlink &&
+ test_ln_s_add moved symlink &&
+ git add moved &&
test_must_fail git mv moved symlink &&
git mv -f moved symlink &&
! test -e moved &&
@@ -237,22 +237,26 @@ test_expect_success SYMLINKS 'git mv should overwrite symlink to a file' '
rm -f moved symlink
-test_expect_success SYMLINKS 'git mv should overwrite file with a symlink' '
+test_expect_success 'git mv should overwrite file with a symlink' '
rm -fr .git &&
git init &&
echo 1 >moved &&
- ln -s moved symlink &&
- git add moved symlink &&
+ test_ln_s_add moved symlink &&
+ git add moved &&
test_must_fail git mv symlink moved &&
git mv -f symlink moved &&
! test -e symlink &&
- test -h moved &&
git update-index --refresh &&
git diff-files --quiet
'
+test_expect_success SYMLINKS 'check moved symlink' '
+
+ test -h moved
+'
+
rm -f moved symlink
test_done
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
index 8f3b54d..88d60c1 100755
--- a/t/t7011-skip-worktree-reading.sh
+++ b/t/t7011-skip-worktree-reading.sh
@@ -91,12 +91,12 @@ test_expect_success 'update-index --remove' '
test_cmp expected 1
'
-test_expect_success 'ls-files --delete' '
+test_expect_success 'ls-files --deleted' '
setup_absent &&
test -z "$(git ls-files -d)"
'
-test_expect_success 'ls-files --delete' '
+test_expect_success 'ls-files --deleted' '
setup_dirty &&
test -z "$(git ls-files -d)"
'
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index df82ec9..8d4b50d 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -9,6 +9,19 @@ Documented tests for git reset'
. ./test-lib.sh
+commit_msg () {
+ # String "modify 2nd file (changed)" partly in German
+ # (translated with Google Translate),
+ # encoded in UTF-8, used as a commit log message below.
+ msg="modify 2nd file (ge\303\244ndert)\n"
+ if test -n "$1"
+ then
+ printf "$msg" | iconv -f utf-8 -t "$1"
+ else
+ printf "$msg"
+ fi
+}
+
test_expect_success 'creating initial files and commits' '
test_tick &&
echo "1st file" >first &&
@@ -28,7 +41,7 @@ test_expect_success 'creating initial files and commits' '
echo "1st line 2nd file" >secondfile &&
echo "2nd line 2nd file" >>secondfile &&
- git commit -a -m "modify 2nd file" &&
+ git -c "i18n.commitEncoding=iso8859-1" commit -a -m "$(commit_msg iso8859-1)" &&
head5=$(git rev-parse --verify HEAD)
'
# git log --pretty=oneline # to see those SHA1 involved
@@ -44,6 +57,20 @@ check_changes () {
done | test_cmp .cat_expect -
}
+test_expect_success 'reset --hard message' '
+ hex=$(git log -1 --format="%h") &&
+ git reset --hard > .actual &&
+ echo HEAD is now at $hex $(commit_msg) > .expected &&
+ test_cmp .expected .actual
+'
+
+test_expect_success 'reset --hard message (iso8859-1 logoutputencoding)' '
+ hex=$(git log -1 --format="%h") &&
+ git -c "i18n.logOutputEncoding=iso8859-1" reset --hard > .actual &&
+ echo HEAD is now at $hex $(commit_msg iso8859-1) > .expected &&
+ test_cmp .expected .actual
+'
+
>.diff_expect
>.cached_expect
cat >.cat_expect <<EOF
@@ -192,7 +219,8 @@ test_expect_success \
'changing files and redo the last commit should succeed' '
echo "3rd line 2nd file" >>secondfile &&
git commit -a -C ORIG_HEAD &&
- check_changes 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d &&
+ head4=$(git rev-parse --verify HEAD) &&
+ check_changes $head4 &&
test "$(git rev-parse ORIG_HEAD)" = \
$head5
'
@@ -211,7 +239,7 @@ test_expect_success \
git reset --hard HEAD~2 &&
check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
test "$(git rev-parse ORIG_HEAD)" = \
- 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d
+ $head4
'
>.diff_expect
@@ -303,7 +331,7 @@ test_expect_success 'redoing the last two commits should succeed' '
echo "1st line 2nd file" >secondfile &&
echo "2nd line 2nd file" >>secondfile &&
- git commit -a -m "modify 2nd file" &&
+ git -c "i18n.commitEncoding=iso8859-1" commit -a -m "$(commit_msg iso8859-1)" &&
check_changes $head5
'
@@ -326,10 +354,11 @@ test_expect_success '--hard reset to HEAD should clear a failed merge' '
git checkout branch2 &&
echo "3rd line in branch2" >>secondfile &&
git commit -a -m "change in branch2" &&
+ head3=$(git rev-parse --verify HEAD) &&
test_must_fail git pull . branch1 &&
git reset --hard &&
- check_changes 77abb337073fb4369a7ad69ff6f5ec0e4d6b54bb
+ check_changes $head3
'
>.diff_expect
@@ -457,7 +486,7 @@ test_expect_success 'disambiguation (1)' '
test_must_fail git diff --quiet -- secondfile &&
test -z "$(git diff --cached --name-only)" &&
test -f secondfile &&
- test ! -s secondfile
+ test_must_be_empty secondfile
'
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index be9672e..0c9ec0a 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -431,6 +431,7 @@ test_expect_success 'detach a symbolic link HEAD' '
test_expect_success \
'checkout with --track fakes a sensible -b <name>' '
+ git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" &&
git update-ref refs/remotes/origin/koala/bear renamer &&
git checkout --track origin/koala/bear &&
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index ff26535..5ee97b0 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -78,7 +78,7 @@ test_expect_success 'submodule add' '
(
cd addtest &&
git submodule add -q "$submodurl" submod >actual &&
- test ! -s actual &&
+ test_must_be_empty actual &&
echo "gitdir: ../.git/modules/submod" >expect &&
test_cmp expect submod/.git &&
(
@@ -212,6 +212,32 @@ test_expect_success 'submodule add with ./, /.. and // in path' '
test_cmp empty untracked
'
+test_expect_success 'submodule add in subdirectory' '
+ echo "refs/heads/master" >expect &&
+ >empty &&
+
+ mkdir addtest/sub &&
+ (
+ cd addtest/sub &&
+ git submodule add "$submodurl" ../realsubmod3 &&
+ git submodule init
+ ) &&
+
+ rm -f heads head untracked &&
+ inspect addtest/realsubmod3 ../.. &&
+ test_cmp expect heads &&
+ test_cmp expect head &&
+ test_cmp empty untracked
+'
+
+test_expect_success 'submodule add in subdirectory with relative path should fail' '
+ (
+ cd addtest/sub &&
+ test_must_fail git submodule add ../../ submod3 2>../../output.err
+ ) &&
+ test_i18ngrep toplevel output.err
+'
+
test_expect_success 'setup - add an example entry to .gitmodules' '
GIT_CONFIG=.gitmodules \
git config submodule.example.url git://example.com/init.git
@@ -308,7 +334,7 @@ test_expect_success 'update should work when path is an empty dir' '
mkdir init &&
git submodule update -q >update.out &&
- test ! -s update.out &&
+ test_must_be_empty update.out &&
inspect init &&
test_cmp expect head-sha1
@@ -319,6 +345,26 @@ test_expect_success 'status should be "up-to-date" after update' '
grep "^ $rev1" list
'
+test_expect_success 'status "up-to-date" from subdirectory' '
+ mkdir -p sub &&
+ (
+ cd sub &&
+ git submodule status >../list
+ ) &&
+ grep "^ $rev1" list &&
+ grep "\\.\\./init" list
+'
+
+test_expect_success 'status "up-to-date" from subdirectory with path' '
+ mkdir -p sub &&
+ (
+ cd sub &&
+ git submodule status ../init >../list
+ ) &&
+ grep "^ $rev1" list &&
+ grep "\\.\\./init" list
+'
+
test_expect_success 'status should be "modified" after submodule commit' '
(
cd init &&
@@ -399,6 +445,25 @@ test_expect_success 'update --init' '
git rev-parse --resolve-git-dir init/.git
'
+test_expect_success 'update --init from subdirectory' '
+ mv init init2 &&
+ git config -f .gitmodules submodule.example.url "$(pwd)/init2" &&
+ git config --remove-section submodule.example &&
+ test_must_fail git config submodule.example.url &&
+
+ mkdir -p sub &&
+ (
+ cd sub &&
+ git submodule update ../init >update.out &&
+ cat update.out &&
+ test_i18ngrep "not initialized" update.out &&
+ test_must_fail git rev-parse --resolve-git-dir ../init/.git &&
+
+ git submodule update --init ../init
+ ) &&
+ git rev-parse --resolve-git-dir init/.git
+'
+
test_expect_success 'do not add files from a submodule' '
git reset --hard &&
@@ -696,7 +761,7 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano
rm -rf repo &&
git rm repo &&
git submodule add -q --name repo_new "$submodurl/bare.git" repo >actual &&
- test ! -s actual &&
+ test_must_be_empty actual &&
echo "gitdir: ../.git/modules/submod" >expect &&
test_cmp expect submod/.git &&
(
@@ -772,6 +837,21 @@ test_expect_success 'submodule deinit should remove the whole submodule section
rmdir init
'
+test_expect_success 'submodule deinit from subdirectory' '
+ git submodule update --init &&
+ git config submodule.example.foo bar &&
+ mkdir -p sub &&
+ (
+ cd sub &&
+ git submodule deinit ../init >../output
+ ) &&
+ grep "\\.\\./init" output &&
+ test -z "$(git config --get-regexp "submodule\.example\.")" &&
+ test -n "$(git config --get-regexp "submodule\.example2\.")" &&
+ test -f example2/.git &&
+ rmdir init
+'
+
test_expect_success 'submodule deinit . deinits all initialized submodules' '
git submodule update --init &&
git config submodule.example.foo bar &&
@@ -868,4 +948,35 @@ test_expect_success 'submodule deinit fails when submodule has a .git directory
test -n "$(git config --get-regexp "submodule\.example\.")"
'
+test_expect_success 'submodule with UTF-8 name' '
+ svname=$(printf "\303\245 \303\244\303\266") &&
+ mkdir "$svname" &&
+ (
+ cd "$svname" &&
+ git init &&
+ >sub &&
+ git add sub &&
+ git commit -m "init sub"
+ ) &&
+ test_config core.precomposeunicode true &&
+ git submodule add ./"$svname" &&
+ git submodule >&2 &&
+ test -n "$(git submodule | grep "$svname")"
+'
+
+test_expect_success 'submodule add clone shallow submodule' '
+ mkdir super &&
+ pwd=$(pwd)
+ (
+ cd super &&
+ git init &&
+ git submodule add --depth=1 file://"$pwd"/example2 submodule &&
+ (
+ cd submodule &&
+ test 1 = $(git log --oneline | wc -l)
+ )
+ )
+'
+
+
test_done
diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh
index 30b429e..ac2434c 100755
--- a/t/t7401-submodule-summary.sh
+++ b/t/t7401-submodule-summary.sh
@@ -45,6 +45,42 @@ EOF
test_cmp expected actual
"
+test_expect_success 'added submodule (subdirectory)' "
+ mkdir sub &&
+ (
+ cd sub &&
+ git submodule summary >../actual
+ ) &&
+ cat >expected <<-EOF &&
+* ../sm1 0000000...$head1 (2):
+ > Add foo2
+
+EOF
+ test_cmp expected actual
+"
+
+test_expect_success 'added submodule (subdirectory only)' "
+ (
+ cd sub &&
+ git submodule summary . >../actual
+ ) &&
+ >expected &&
+ test_cmp expected actual
+"
+
+test_expect_success 'added submodule (subdirectory with explicit path)' "
+ (
+ cd sub &&
+ git submodule summary ../sm1 >../actual
+ ) &&
+ cat >expected <<-EOF &&
+* ../sm1 0000000...$head1 (2):
+ > Add foo2
+
+EOF
+ test_cmp expected actual
+"
+
commit_file sm1 &&
head2=$(add_file sm1 foo3)
@@ -76,8 +112,8 @@ head3=$(
)
test_expect_success 'modified submodule(backward)' "
- git submodule summary >actual &&
- cat >expected <<-EOF &&
+ git submodule summary >actual &&
+ cat >expected <<-EOF &&
* sm1 $head2...$head3 (2):
< Add foo3
< Add foo2
@@ -89,8 +125,8 @@ EOF
head4=$(add_file sm1 foo4 foo5) &&
head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD)
test_expect_success 'modified submodule(backward and forward)' "
- git submodule summary >actual &&
- cat >expected <<-EOF &&
+ git submodule summary >actual &&
+ cat >expected <<-EOF &&
* sm1 $head2...$head4 (4):
> Add foo5
> Add foo4
@@ -102,15 +138,15 @@ EOF
"
test_expect_success '--summary-limit' "
- git submodule summary -n 3 >actual &&
- cat >expected <<-EOF &&
+ git submodule summary -n 3 >actual &&
+ cat >expected <<-EOF &&
* sm1 $head2...$head4 (4):
> Add foo5
> Add foo4
< Add foo3
EOF
- test_cmp expected actual
+ test_cmp expected actual
"
commit_file sm1 &&
@@ -122,8 +158,8 @@ rm -f sm1 &&
mv sm1-bak sm1
test_expect_success 'typechanged submodule(submodule->blob), --cached' "
- git submodule summary --cached >actual &&
- cat >expected <<-EOF &&
+ git submodule summary --cached >actual &&
+ cat >expected <<-EOF &&
* sm1 $head4(submodule)->$head5(blob) (3):
< Add foo5
@@ -132,59 +168,59 @@ EOF
"
test_expect_success 'typechanged submodule(submodule->blob), --files' "
- git submodule summary --files >actual &&
- cat >expected <<-EOF &&
+ git submodule summary --files >actual &&
+ cat >expected <<-EOF &&
* sm1 $head5(blob)->$head4(submodule) (3):
> Add foo5
EOF
- test_i18ncmp actual expected
+ test_i18ncmp actual expected
"
rm -rf sm1 &&
git checkout-index sm1
test_expect_success 'typechanged submodule(submodule->blob)' "
- git submodule summary >actual &&
- cat >expected <<-EOF &&
+ git submodule summary >actual &&
+ cat >expected <<-EOF &&
* sm1 $head4(submodule)->$head5(blob):
EOF
- test_i18ncmp actual expected
+ test_i18ncmp actual expected
"
rm -f sm1 &&
test_create_repo sm1 &&
head6=$(add_file sm1 foo6 foo7)
test_expect_success 'nonexistent commit' "
- git submodule summary >actual &&
- cat >expected <<-EOF &&
+ git submodule summary >actual &&
+ cat >expected <<-EOF &&
* sm1 $head4...$head6:
Warn: sm1 doesn't contain commit $head4_full
EOF
- test_i18ncmp actual expected
+ test_i18ncmp actual expected
"
commit_file
test_expect_success 'typechanged submodule(blob->submodule)' "
- git submodule summary >actual &&
- cat >expected <<-EOF &&
+ git submodule summary >actual &&
+ cat >expected <<-EOF &&
* sm1 $head5(blob)->$head6(submodule) (2):
> Add foo7
EOF
- test_i18ncmp expected actual
+ test_i18ncmp expected actual
"
commit_file sm1 &&
rm -rf sm1
test_expect_success 'deleted submodule' "
- git submodule summary >actual &&
- cat >expected <<-EOF &&
+ git submodule summary >actual &&
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
EOF
- test_cmp expected actual
+ test_cmp expected actual
"
test_create_repo sm2 &&
@@ -192,43 +228,43 @@ head7=$(add_file sm2 foo8 foo9) &&
git add sm2
test_expect_success 'multiple submodules' "
- git submodule summary >actual &&
- cat >expected <<-EOF &&
+ git submodule summary >actual &&
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
* sm2 0000000...$head7 (2):
> Add foo9
EOF
- test_cmp expected actual
+ test_cmp expected actual
"
test_expect_success 'path filter' "
- git submodule summary sm2 >actual &&
- cat >expected <<-EOF &&
+ git submodule summary sm2 >actual &&
+ cat >expected <<-EOF &&
* sm2 0000000...$head7 (2):
> Add foo9
EOF
- test_cmp expected actual
+ test_cmp expected actual
"
commit_file sm2
test_expect_success 'given commit' "
- git submodule summary HEAD^ >actual &&
- cat >expected <<-EOF &&
+ git submodule summary HEAD^ >actual &&
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
* sm2 0000000...$head7 (2):
> Add foo9
EOF
- test_cmp expected actual
+ test_cmp expected actual
"
test_expect_success '--for-status' "
- git submodule summary --for-status HEAD^ >actual &&
- test_i18ncmp actual - <<EOF
+ git submodule summary --for-status HEAD^ >actual &&
+ test_i18ncmp actual - <<EOF
# Submodule changes to be committed:
#
# * sm1 $head6...0000000:
@@ -240,14 +276,14 @@ EOF
"
test_expect_success 'fail when using --files together with --cached' "
- test_must_fail git submodule summary --files --cached
+ test_must_fail git submodule summary --files --cached
"
test_expect_success 'should not fail in an empty repo' "
- git init xyzzy &&
- cd xyzzy &&
- git submodule summary >output 2>&1 &&
- test_cmp output /dev/null
+ git init xyzzy &&
+ cd xyzzy &&
+ git submodule summary >output 2>&1 &&
+ test_cmp output /dev/null
"
test_done
diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh
index 94e26c4..79bc135 100755
--- a/t/t7403-submodule-sync.sh
+++ b/t/t7403-submodule-sync.sh
@@ -11,216 +11,338 @@ These tests exercise the "git submodule sync" subcommand.
. ./test-lib.sh
test_expect_success setup '
- echo file > file &&
+ echo file >file &&
git add file &&
test_tick &&
git commit -m upstream &&
git clone . super &&
git clone super submodule &&
- (cd submodule &&
- git submodule add ../submodule sub-submodule &&
- test_tick &&
- git commit -m "sub-submodule"
+ (
+ cd submodule &&
+ git submodule add ../submodule sub-submodule &&
+ test_tick &&
+ git commit -m "sub-submodule"
) &&
- (cd super &&
- git submodule add ../submodule submodule &&
- test_tick &&
- git commit -m "submodule"
+ (
+ cd super &&
+ git submodule add ../submodule submodule &&
+ test_tick &&
+ git commit -m "submodule"
) &&
git clone super super-clone &&
- (cd super-clone && git submodule update --init --recursive) &&
+ (
+ cd super-clone &&
+ git submodule update --init --recursive
+ ) &&
git clone super empty-clone &&
- (cd empty-clone && git submodule init) &&
+ (
+ cd empty-clone &&
+ git submodule init
+ ) &&
git clone super top-only-clone &&
git clone super relative-clone &&
- (cd relative-clone && git submodule update --init --recursive) &&
+ (
+ cd relative-clone &&
+ git submodule update --init --recursive
+ ) &&
git clone super recursive-clone &&
- (cd recursive-clone && git submodule update --init --recursive)
+ (
+ cd recursive-clone &&
+ git submodule update --init --recursive
+ )
'
test_expect_success 'change submodule' '
- (cd submodule &&
- echo second line >> file &&
- test_tick &&
- git commit -a -m "change submodule"
+ (
+ cd submodule &&
+ echo second line >>file &&
+ test_tick &&
+ git commit -a -m "change submodule"
)
'
+reset_submodule_urls () {
+ local root
+ root=$(pwd) &&
+ (
+ cd super-clone/submodule &&
+ git config remote.origin.url "$root/submodule"
+ ) &&
+ (
+ cd super-clone/submodule/sub-submodule &&
+ git config remote.origin.url "$root/submodule"
+ )
+}
+
test_expect_success 'change submodule url' '
- (cd super &&
- cd submodule &&
- git checkout master &&
- git pull
+ (
+ cd super &&
+ cd submodule &&
+ git checkout master &&
+ git pull
) &&
mv submodule moved-submodule &&
- (cd moved-submodule &&
- git config -f .gitmodules submodule.sub-submodule.url ../moved-submodule &&
- test_tick &&
- git commit -a -m moved-sub-submodule
+ (
+ cd moved-submodule &&
+ git config -f .gitmodules submodule.sub-submodule.url ../moved-submodule &&
+ test_tick &&
+ git commit -a -m moved-sub-submodule
) &&
- (cd super &&
- git config -f .gitmodules submodule.submodule.url ../moved-submodule &&
- test_tick &&
- git commit -a -m moved-submodule
+ (
+ cd super &&
+ git config -f .gitmodules submodule.submodule.url ../moved-submodule &&
+ test_tick &&
+ git commit -a -m moved-submodule
)
'
test_expect_success '"git submodule sync" should update submodule URLs' '
- (cd super-clone &&
- git pull --no-recurse-submodules &&
- git submodule sync
+ (
+ cd super-clone &&
+ git pull --no-recurse-submodules &&
+ git submodule sync
) &&
- test -d "$(cd super-clone/submodule &&
- git config remote.origin.url
+ test -d "$(
+ cd super-clone/submodule &&
+ git config remote.origin.url
)" &&
- test ! -d "$(cd super-clone/submodule/sub-submodule &&
- git config remote.origin.url
+ test ! -d "$(
+ cd super-clone/submodule/sub-submodule &&
+ git config remote.origin.url
)" &&
- (cd super-clone/submodule &&
- git checkout master &&
- git pull
+ (
+ cd super-clone/submodule &&
+ git checkout master &&
+ git pull
) &&
- (cd super-clone &&
- test -d "$(git config submodule.submodule.url)"
+ (
+ cd super-clone &&
+ test -d "$(git config submodule.submodule.url)"
)
'
test_expect_success '"git submodule sync --recursive" should update all submodule URLs' '
- (cd super-clone &&
- (cd submodule &&
- git pull --no-recurse-submodules
- ) &&
- git submodule sync --recursive
+ (
+ cd super-clone &&
+ (
+ cd submodule &&
+ git pull --no-recurse-submodules
+ ) &&
+ git submodule sync --recursive
+ ) &&
+ test -d "$(
+ cd super-clone/submodule &&
+ git config remote.origin.url
+ )" &&
+ test -d "$(
+ cd super-clone/submodule/sub-submodule &&
+ git config remote.origin.url
+ )" &&
+ (
+ cd super-clone/submodule/sub-submodule &&
+ git checkout master &&
+ git pull
+ )
+'
+
+test_expect_success 'reset submodule URLs' '
+ reset_submodule_urls super-clone
+'
+
+test_expect_success '"git submodule sync" should update submodule URLs - subdirectory' '
+ (
+ cd super-clone &&
+ git pull --no-recurse-submodules &&
+ mkdir -p sub &&
+ cd sub &&
+ git submodule sync >../../output
+ ) &&
+ grep "\\.\\./submodule" output &&
+ test -d "$(
+ cd super-clone/submodule &&
+ git config remote.origin.url
+ )" &&
+ test ! -d "$(
+ cd super-clone/submodule/sub-submodule &&
+ git config remote.origin.url
+ )" &&
+ (
+ cd super-clone/submodule &&
+ git checkout master &&
+ git pull
+ ) &&
+ (
+ cd super-clone &&
+ test -d "$(git config submodule.submodule.url)"
+ )
+'
+
+test_expect_success '"git submodule sync --recursive" should update all submodule URLs - subdirectory' '
+ (
+ cd super-clone &&
+ (
+ cd submodule &&
+ git pull --no-recurse-submodules
+ ) &&
+ mkdir -p sub &&
+ cd sub &&
+ git submodule sync --recursive >../../output
) &&
- test -d "$(cd super-clone/submodule &&
- git config remote.origin.url
+ grep "\\.\\./submodule/sub-submodule" output &&
+ test -d "$(
+ cd super-clone/submodule &&
+ git config remote.origin.url
)" &&
- test -d "$(cd super-clone/submodule/sub-submodule &&
- git config remote.origin.url
+ test -d "$(
+ cd super-clone/submodule/sub-submodule &&
+ git config remote.origin.url
)" &&
- (cd super-clone/submodule/sub-submodule &&
- git checkout master &&
- git pull
+ (
+ cd super-clone/submodule/sub-submodule &&
+ git checkout master &&
+ git pull
)
'
test_expect_success '"git submodule sync" should update known submodule URLs' '
- (cd empty-clone &&
- git pull &&
- git submodule sync &&
- test -d "$(git config submodule.submodule.url)"
+ (
+ cd empty-clone &&
+ git pull &&
+ git submodule sync &&
+ test -d "$(git config submodule.submodule.url)"
)
'
test_expect_success '"git submodule sync" should not vivify uninteresting submodule' '
- (cd top-only-clone &&
- git pull &&
- git submodule sync &&
- test -z "$(git config submodule.submodule.url)" &&
- git submodule sync submodule &&
- test -z "$(git config submodule.submodule.url)"
+ (
+ cd top-only-clone &&
+ git pull &&
+ git submodule sync &&
+ test -z "$(git config submodule.submodule.url)" &&
+ git submodule sync submodule &&
+ test -z "$(git config submodule.submodule.url)"
)
'
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"
- )
+ (
+ 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"
- )
- (cd submodule/sub-submodule &&
- test "$(git config remote.origin.url)" != "../../foo/submodule"
- )
+ (
+ 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"
+ ) &&
+ (
+ cd submodule/sub-submodule &&
+ test "$(git config remote.origin.url)" != "../../foo/submodule"
+ )
)
'
test_expect_success '"git submodule sync --recursive" propagates changes in origin' '
- (cd recursive-clone &&
- git remote set-url origin foo/bar &&
- git submodule sync --recursive &&
- (cd submodule &&
- #actual foo/submodule
- test "$(git config remote.origin.url)" = "../foo/submodule"
- )
- (cd submodule/sub-submodule &&
- test "$(git config remote.origin.url)" = "../../foo/submodule"
- )
+ (
+ cd recursive-clone &&
+ git remote set-url origin foo/bar &&
+ git submodule sync --recursive &&
+ (
+ cd submodule &&
+ #actual foo/submodule
+ test "$(git config remote.origin.url)" = "../foo/submodule"
+ ) &&
+ (
+ cd submodule/sub-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"
- )
+ (
+ 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"
- )
+ (
+ 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"
- )
+ (
+ 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"
- )
+ (
+ 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"
- )
+ (
+ 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"
+ )
)
'
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index a4ffea0..b192f93 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -80,6 +80,21 @@ test_expect_success 'submodule update detaching the HEAD ' '
)
'
+test_expect_success 'submodule update from subdirectory' '
+ (cd super/submodule &&
+ git reset --hard HEAD~1
+ ) &&
+ mkdir super/sub &&
+ (cd super/sub &&
+ (cd ../submodule &&
+ compare_head
+ ) &&
+ git submodule update ../submodule &&
+ cd ../submodule &&
+ ! compare_head
+ )
+'
+
apos="'";
test_expect_success 'submodule update does not fetch already present commits' '
(cd submodule &&
@@ -279,6 +294,35 @@ test_expect_success 'submodule update - checkout in .git/config' '
)
'
+test_expect_success 'submodule update - command in .git/config' '
+ (cd super &&
+ git config submodule.submodule.update "!git checkout"
+ ) &&
+ (cd super/submodule &&
+ git reset --hard HEAD^
+ ) &&
+ (cd super &&
+ (cd submodule &&
+ compare_head
+ ) &&
+ git submodule update submodule &&
+ cd submodule &&
+ ! compare_head
+ )
+'
+
+test_expect_success 'submodule update - command in .git/config catches failure' '
+ (cd super &&
+ git config submodule.submodule.update "!false"
+ ) &&
+ (cd super/submodule &&
+ git reset --hard HEAD^
+ ) &&
+ (cd super &&
+ test_must_fail git submodule update submodule
+ )
+'
+
test_expect_success 'submodule init picks up rebase' '
(cd super &&
git config -f .gitmodules submodule.rebasing.update rebase &&
@@ -685,14 +729,24 @@ test_expect_success 'submodule update properly revives a moved submodule' '
test_expect_success SYMLINKS 'submodule update can handle symbolic links in pwd' '
mkdir -p linked/dir &&
ln -s linked/dir linkto &&
- (
- cd linkto &&
- git clone "$TRASH_DIRECTORY"/super_update_r2 super &&
- (
- cd super &&
- git submodule update --init --recursive
- )
+ (cd linkto &&
+ git clone "$TRASH_DIRECTORY"/super_update_r2 super &&
+ (cd super &&
+ git submodule update --init --recursive
+ )
)
'
+test_expect_success 'submodule update clone shallow submodule' '
+ git clone cloned super3 &&
+ pwd=$(pwd)
+ (cd super3 &&
+ sed -e "s#url = ../#url = file://$pwd/#" <.gitmodules >.gitmodules.tmp &&
+ mv -f .gitmodules.tmp .gitmodules &&
+ git submodule update --init --depth=3
+ (cd submodule &&
+ test 1 = $(git log --oneline | wc -l)
+ )
+ )
+'
test_done
diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh
index 107b4b7..91d4fd1 100755
--- a/t/t7407-submodule-foreach.sh
+++ b/t/t7407-submodule-foreach.sh
@@ -80,6 +80,22 @@ test_expect_success 'test basic "submodule foreach" usage' '
test_i18ncmp expect actual
'
+cat >expect <<EOF
+Entering '../sub1'
+$pwd/clone-foo1-../sub1-$sub1sha1
+Entering '../sub3'
+$pwd/clone-foo3-../sub3-$sub3sha1
+EOF
+
+test_expect_success 'test "submodule foreach" from subdirectory' '
+ mkdir clone/sub &&
+ (
+ cd clone/sub &&
+ git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$sha1" >../../actual
+ ) &&
+ test_i18ncmp expect actual
+'
+
test_expect_success 'setup nested submodules' '
git clone submodule nested1 &&
git clone submodule nested2 &&
diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh
index 436b7b6..bdc1f29 100755
--- a/t/t7500-commit.sh
+++ b/t/t7500-commit.sh
@@ -13,9 +13,9 @@ commit_msg_is () {
expect=commit_msg_is.expect
actual=commit_msg_is.actual
- printf "%s" "$(git log --pretty=format:%s%b -1)" >$expect &&
- printf "%s" "$1" >$actual &&
- test_i18ncmp $expect $actual
+ printf "%s" "$(git log --pretty=format:%s%b -1)" >"$actual" &&
+ printf "%s" "$1" >"$expect" &&
+ test_i18ncmp "$expect" "$actual"
}
# A sanity check to see if commit is working at all.
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 195e747..99ce36f 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -524,4 +524,17 @@ test_expect_success 'commit a file whose name is a dash' '
test_i18ngrep " changed, 5 insertions" output
'
+test_expect_success '--only works on to-be-born branch' '
+ # This test relies on having something in the index, as it
+ # would not otherwise actually prove much. So check this.
+ test -n "$(git ls-files)" &&
+ git checkout --orphan orphan &&
+ echo foo >newfile &&
+ git add newfile &&
+ git commit --only newfile -m"--only on unborn branch" &&
+ echo newfile >expected &&
+ git ls-tree -r --name-only HEAD >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index a4938b1..6313da2 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
@@ -361,6 +361,23 @@ test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' '
test_cmp expect .git/result
'
+test_expect_success 'do not fire editor if -m <msg> was given' '
+ echo tick >file &&
+ git add file &&
+ echo "editor not started" >.git/result &&
+ (GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" git commit -m tick) &&
+ test "$(cat .git/result)" = "editor not started"
+'
+
+test_expect_success 'do not fire editor if -m "" was given' '
+ echo tock >file &&
+ git add file &&
+ echo "editor not started" >.git/result &&
+ (GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" \
+ git commit -m "" --allow-empty-message) &&
+ test "$(cat .git/result)" = "editor not started"
+'
+
test_expect_success 'do not fire editor in the presence of conflicts' '
git clean -f &&
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index e2ffdac..ac3d0fe 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1335,4 +1335,66 @@ test_expect_failure '.git/config ignore=all suppresses submodule summary' '
git config -f .gitmodules --remove-section submodule.subname
'
+test_expect_success 'setup of test environment' '
+ git config status.showUntrackedFiles no &&
+ git status -s >expected_short &&
+ git status --no-short >expected_noshort
+'
+
+test_expect_success '"status.short=true" same as "-s"' '
+ git -c status.short=true status >actual &&
+ test_cmp expected_short actual
+'
+
+test_expect_success '"status.short=true" weaker than "--no-short"' '
+ git -c status.short=true status --no-short >actual &&
+ test_cmp expected_noshort actual
+'
+
+test_expect_success '"status.short=false" same as "--no-short"' '
+ git -c status.short=false status >actual &&
+ test_cmp expected_noshort actual
+'
+
+test_expect_success '"status.short=false" weaker than "-s"' '
+ git -c status.short=false status -s >actual &&
+ test_cmp expected_short actual
+'
+
+test_expect_success '"status.branch=true" same as "-b"' '
+ git status -sb >expected_branch &&
+ git -c status.branch=true status -s >actual &&
+ test_cmp expected_branch actual
+'
+
+test_expect_success '"status.branch=true" different from "--no-branch"' '
+ git status -s --no-branch >expected_nobranch &&
+ git -c status.branch=true status -s >actual &&
+ test_must_fail test_cmp expected_nobranch actual
+'
+
+test_expect_success '"status.branch=true" weaker than "--no-branch"' '
+ git -c status.branch=true status -s --no-branch >actual &&
+ test_cmp expected_nobranch actual
+'
+
+test_expect_success '"status.branch=true" weaker than "--porcelain"' '
+ git -c status.branch=true status --porcelain >actual &&
+ test_cmp expected_nobranch actual
+'
+
+test_expect_success '"status.branch=false" same as "--no-branch"' '
+ git -c status.branch=false status -s >actual &&
+ test_cmp expected_nobranch actual
+'
+
+test_expect_success '"status.branch=false" weaker than "-b"' '
+ git -c status.branch=false status -sb >actual &&
+ test_cmp expected_branch actual
+'
+
+test_expect_success 'Restore default test environment' '
+ git config --unset status.showUntrackedFiles
+'
+
test_done
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index bf08d4e..31a798f 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -77,7 +77,7 @@ test_expect_success 'status when rebase in progress before resolving conflicts'
ONTO=$(git rev-parse --short HEAD^^) &&
test_must_fail git rebase HEAD^ --onto HEAD^^ &&
cat >expected <<-EOF &&
- # HEAD detached at $ONTO
+ # rebase in progress; onto $ONTO
# You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
# (fix conflicts and then run "git rebase --continue")
# (use "git rebase --skip" to skip this patch)
@@ -104,7 +104,7 @@ test_expect_success 'status when rebase in progress before rebase --continue' '
echo three >main.txt &&
git add main.txt &&
cat >expected <<-EOF &&
- # HEAD detached at $ONTO
+ # rebase in progress; onto $ONTO
# You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
# (all conflicts fixed: run "git rebase --continue")
#
@@ -136,7 +136,7 @@ test_expect_success 'status during rebase -i when conflicts unresolved' '
ONTO=$(git rev-parse --short rebase_i_conflicts) &&
test_must_fail git rebase -i rebase_i_conflicts &&
cat >expected <<-EOF &&
- # HEAD detached at $ONTO
+ # rebase in progress; onto $ONTO
# You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
# (fix conflicts and then run "git rebase --continue")
# (use "git rebase --skip" to skip this patch)
@@ -162,7 +162,7 @@ test_expect_success 'status during rebase -i after resolving conflicts' '
test_must_fail git rebase -i rebase_i_conflicts &&
git add main.txt &&
cat >expected <<-EOF &&
- # HEAD detached at $ONTO
+ # rebase in progress; onto $ONTO
# You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
# (all conflicts fixed: run "git rebase --continue")
#
@@ -188,10 +188,9 @@ test_expect_success 'status when rebasing -i in edit mode' '
export FAKE_LINES &&
test_when_finished "git rebase --abort" &&
ONTO=$(git rev-parse --short HEAD~2) &&
- TGT=$(git rev-parse --short two_rebase_i) &&
git rebase -i HEAD~2 &&
cat >expected <<-EOF &&
- # HEAD detached from $TGT
+ # rebase in progress; onto $ONTO
# You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
@@ -216,9 +215,8 @@ test_expect_success 'status when splitting a commit' '
ONTO=$(git rev-parse --short HEAD~3) &&
git rebase -i HEAD~3 &&
git reset HEAD^ &&
- TGT=$(git rev-parse --short HEAD) &&
cat >expected <<-EOF &&
- # HEAD detached at $TGT
+ # rebase in progress; onto $ONTO
# You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
# (Once your working directory is clean, run "git rebase --continue")
#
@@ -246,11 +244,10 @@ test_expect_success 'status after editing the last commit with --amend during a
export FAKE_LINES &&
test_when_finished "git rebase --abort" &&
ONTO=$(git rev-parse --short HEAD~3) &&
- TGT=$(git rev-parse --short three_amend) &&
git rebase -i HEAD~3 &&
git commit --amend -m "foo" &&
cat >expected <<-EOF &&
- # HEAD detached from $TGT
+ # rebase in progress; onto $ONTO
# You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
@@ -280,7 +277,7 @@ test_expect_success 'status: (continue first edit) second edit' '
git rebase -i HEAD~3 &&
git rebase --continue &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
@@ -302,7 +299,7 @@ test_expect_success 'status: (continue first edit) second edit and split' '
git rebase --continue &&
git reset HEAD^ &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (Once your working directory is clean, run "git rebase --continue")
#
@@ -329,7 +326,7 @@ test_expect_success 'status: (continue first edit) second edit and amend' '
git rebase --continue &&
git commit --amend -m "foo" &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
@@ -351,7 +348,7 @@ test_expect_success 'status: (amend first edit) second edit' '
git commit --amend -m "a" &&
git rebase --continue &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
@@ -374,7 +371,7 @@ test_expect_success 'status: (amend first edit) second edit and split' '
git rebase --continue &&
git reset HEAD^ &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (Once your working directory is clean, run "git rebase --continue")
#
@@ -402,7 +399,7 @@ test_expect_success 'status: (amend first edit) second edit and amend' '
git rebase --continue &&
git commit --amend -m "d" &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
@@ -426,7 +423,7 @@ test_expect_success 'status: (split first edit) second edit' '
git commit -m "e" &&
git rebase --continue &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
@@ -451,7 +448,7 @@ test_expect_success 'status: (split first edit) second edit and split' '
git rebase --continue &&
git reset HEAD^ &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (Once your working directory is clean, run "git rebase --continue")
#
@@ -481,7 +478,7 @@ test_expect_success 'status: (split first edit) second edit and amend' '
git rebase --continue &&
git commit --amend -m "h" &&
cat >expected <<-EOF &&
- # HEAD detached from $ONTO
+ # rebase in progress; onto $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
@@ -510,7 +507,7 @@ test_expect_success 'status in an am session: file already exists' '
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")
+ # (fix conflicts and then run "git am --continue")
# (use "git am --skip" to skip this patch)
# (use "git am --abort" to restore the original branch)
#
@@ -532,7 +529,7 @@ test_expect_success 'status in an am session: file does not exist' '
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")
+ # (fix conflicts and then run "git am --continue")
# (use "git am --skip" to skip this patch)
# (use "git am --abort" to restore the original branch)
#
@@ -601,7 +598,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
ONTO=$(git rev-parse --short HEAD^^) &&
test_must_fail git rebase HEAD^ --onto HEAD^^ &&
cat >expected <<-EOF &&
- # HEAD detached at $ONTO
+ # rebase in progress; onto $ONTO
# You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
#
# Unmerged paths:
@@ -632,7 +629,8 @@ test_expect_success 'status when cherry-picking before resolving conflicts' '
cat >expected <<-\EOF &&
# On branch cherry_branch
# You are currently cherry-picking.
- # (fix conflicts and run "git commit")
+ # (fix conflicts and run "git cherry-pick --continue")
+ # (use "git cherry-pick --abort" to cancel the cherry-pick operation)
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
@@ -655,7 +653,8 @@ test_expect_success 'status when cherry-picking after resolving conflicts' '
cat >expected <<-\EOF &&
# On branch cherry_branch
# You are currently cherry-picking.
- # (all conflicts fixed: run "git commit")
+ # (all conflicts fixed: run "git cherry-pick --continue")
+ # (use "git cherry-pick --abort" to cancel the cherry-pick operation)
#
# Changes to be committed:
#
@@ -667,7 +666,7 @@ test_expect_success 'status when cherry-picking after resolving conflicts' '
test_i18ncmp expected actual
'
-test_expect_success 'status showing detached from a tag' '
+test_expect_success 'status showing detached at and from a tag' '
test_commit atag tagging &&
git checkout atag &&
cat >expected <<-\EOF
@@ -675,6 +674,14 @@ test_expect_success 'status showing detached from a tag' '
nothing to commit (use -u to show untracked files)
EOF
git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual &&
+
+ git reset --hard HEAD^ &&
+ cat >expected <<-\EOF
+ # HEAD detached from atag
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 2f70433..3ff5fb8 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -316,7 +316,7 @@ test_expect_success 'merge c1 with c2 (squash)' '
test_debug 'git log --graph --decorate --oneline --all'
-test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' '
+test_expect_success 'unsuccessful merge of c1 with c2 (squash, ff-only)' '
git reset --hard c1 &&
test_must_fail git merge --squash --ff-only c2
'
@@ -497,9 +497,15 @@ test_expect_success 'combining --squash and --no-ff is refused' '
test_must_fail git merge --no-ff --squash c1
'
-test_expect_success 'combining --ff-only and --no-ff is refused' '
- test_must_fail git merge --ff-only --no-ff c1 &&
- test_must_fail git merge --no-ff --ff-only c1
+test_expect_success 'option --ff-only overwrites --no-ff' '
+ git merge --no-ff --ff-only c1 &&
+ test_must_fail git merge --no-ff --ff-only c2
+'
+
+test_expect_success 'option --ff-only overwrites merge.ff=only config' '
+ git reset --hard c0 &&
+ test_config merge.ff only &&
+ git merge --no-ff c1
'
test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh
index 6547eb8..758a623 100755
--- a/t/t7607-merge-overwrite.sh
+++ b/t/t7607-merge-overwrite.sh
@@ -141,11 +141,10 @@ test_expect_success SYMLINKS 'will not overwrite untracked symlink in leading pa
test_path_is_missing .git/MERGE_HEAD
'
-test_expect_success SYMLINKS 'will not be confused by symlink in leading path' '
+test_expect_success 'will not be confused by symlink in leading path' '
git reset --hard c0 &&
rm -rf sub &&
- ln -s sub2 sub &&
- git add sub &&
+ test_ln_s_add sub2 sub &&
git commit -m ln &&
git checkout sub
'
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index d46f041..2418528 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -385,6 +385,25 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstage
test_cmp actual expect
'
+write_script modify-right-file <<\EOF
+echo "new content" >"$2/file"
+EOF
+
+run_dir_diff_test 'difftool --dir-diff syncs worktree with unstaged change' '
+ test_when_finished git reset --hard &&
+ echo "orig content" >file &&
+ git difftool -d $symlinks --extcmd "$(pwd)/modify-right-file" branch &&
+ echo "new content" >expect &&
+ test_cmp expect file
+'
+
+run_dir_diff_test 'difftool --dir-diff syncs worktree without unstaged change' '
+ test_when_finished git reset --hard &&
+ git difftool -d $symlinks --extcmd "$(pwd)/modify-right-file" branch &&
+ echo "new content" >expect &&
+ test_cmp expect file
+'
+
write_script modify-file <<\EOF
echo "new content" >file
EOF
diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh
index 230143c..e7cac1d 100755
--- a/t/t8003-blame-corner-cases.sh
+++ b/t/t8003-blame-corner-cases.sh
@@ -175,6 +175,12 @@ test_expect_success 'blame -L with invalid end' '
grep "has only 2 lines" errors
'
+test_expect_success 'blame parses <end> part of -L' '
+ git blame -L1,1 tres >out &&
+ cat out &&
+ test $(wc -l < out) -eq 1
+'
+
test_expect_success 'indent of line numbers, nine lines' '
git blame nine_lines >actual &&
test $(grep -c " " actual) = 0
diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh
index bf6caa4..7683515 100755
--- a/t/t8006-blame-textconv.sh
+++ b/t/t8006-blame-textconv.sh
@@ -18,17 +18,13 @@ 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
- ln -s one.bin symlink.bin
- fi &&
+ test_ln_s_add one.bin symlink.bin &&
git add . &&
GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
echo "bin: test 1 version 2" >one.bin &&
echo "bin: test number 2 version 2" >>two.bin &&
- if test_have_prereq SYMLINKS; then
- rm symlink.bin &&
- ln -s two.bin symlink.bin
- fi &&
+ rm -f symlink.bin &&
+ test_ln_s_add two.bin symlink.bin &&
GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
'
@@ -135,7 +131,7 @@ test_expect_success SYMLINKS 'blame --textconv (on symlink)' '
# cp two.bin three.bin and make small tweak
# (this will direct blame -C -C three.bin to consider two.bin and symlink.bin)
-test_expect_success SYMLINKS 'make another new commit' '
+test_expect_success 'make another new commit' '
cat >three.bin <<\EOF &&
bin: test number 2
bin: test number 2 version 2
@@ -146,7 +142,7 @@ EOF
GIT_AUTHOR_NAME=Number4 git commit -a -m Fourth --date="2010-01-01 23:00:00"
'
-test_expect_success SYMLINKS 'blame on last commit (-C -C, symlink)' '
+test_expect_success 'blame on last commit (-C -C, symlink)' '
git blame -C -C three.bin >blame &&
find_blame <blame >result &&
cat >expected <<\EOF &&
diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh
index 78a0085..b95e102 100755
--- a/t/t8007-cat-file-textconv.sh
+++ b/t/t8007-cat-file-textconv.sh
@@ -12,9 +12,7 @@ chmod +x helper
test_expect_success 'setup ' '
echo "bin: test" >one.bin &&
- if test_have_prereq SYMLINKS; then
- ln -s one.bin symlink.bin
- fi &&
+ test_ln_s_add one.bin symlink.bin &&
git add . &&
GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
echo "bin: test version 2" >one.bin &&
@@ -72,14 +70,14 @@ test_expect_success 'cat-file --textconv on previous commit' '
test_cmp expected result
'
-test_expect_success SYMLINKS 'cat-file without --textconv (symlink)' '
+test_expect_success 'cat-file without --textconv (symlink)' '
git cat-file blob :symlink.bin >result &&
printf "%s" "one.bin" >expected
test_cmp expected result
'
-test_expect_success SYMLINKS 'cat-file --textconv on index (symlink)' '
+test_expect_success 'cat-file --textconv on index (symlink)' '
! git cat-file --textconv :symlink.bin 2>result &&
cat >expected <<\EOF &&
fatal: git cat-file --textconv: unable to run textconv on :symlink.bin
@@ -87,7 +85,7 @@ EOF
test_cmp expected result
'
-test_expect_success SYMLINKS 'cat-file --textconv on HEAD (symlink)' '
+test_expect_success 'cat-file --textconv on HEAD (symlink)' '
! git cat-file --textconv HEAD:symlink.bin 2>result &&
cat >expected <<EOF &&
fatal: git cat-file --textconv: unable to run textconv on HEAD:symlink.bin
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index ebd5c5d..2813aa9 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -171,6 +171,81 @@ Result: OK
EOF
"
+test_suppress_self () {
+ test_commit $3 &&
+ test_when_finished "git reset --hard HEAD^" &&
+
+ write_script cccmd-sed <<-EOF &&
+ sed -n -e s/^cccmd--//p "\$1"
+ EOF
+
+ git commit --amend --author="$1 <$2>" -F - &&
+ clean_fake_sendmail &&
+ git format-patch --stdout -1 >"suppress-self-$3.patch" &&
+
+ git send-email --from="$1 <$2>" \
+ --to=nobody@example.com \
+ --cc-cmd=./cccmd-sed \
+ --suppress-cc=self \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ suppress-self-$3.patch &&
+
+ mv msgtxt1 msgtxt1-$3 &&
+ sed -e '/^$/q' msgtxt1-$3 >"msghdr1-$3" &&
+ >"expected-no-cc-$3" &&
+
+ (grep '^Cc:' msghdr1-$3 >"actual-no-cc-$3";
+ test_cmp expected-no-cc-$3 actual-no-cc-$3)
+}
+
+test_suppress_self_unquoted () {
+ test_suppress_self "$1" "$2" "unquoted-$3" <<-EOF
+ test suppress-cc.self unquoted-$3 with name $1 email $2
+
+ unquoted-$3
+
+ cccmd--$1 <$2>
+
+ Cc: $1 <$2>
+ Signed-off-by: $1 <$2>
+ EOF
+}
+
+test_suppress_self_quoted () {
+ test_suppress_self "$1" "$2" "quoted-$3" <<-EOF
+ test suppress-cc.self quoted-$3 with name $1 email $2
+
+ quoted-$3
+
+ cccmd--"$1" <$2>
+
+ Cc: $1 <$2>
+ Cc: "$1" <$2>
+ Signed-off-by: $1 <$2>
+ Signed-off-by: "$1" <$2>
+ EOF
+}
+
+test_expect_success $PREREQ 'self name is suppressed' "
+ test_suppress_self_unquoted 'A U Thor' 'author@example.com' \
+ 'self_name_suppressed'
+"
+
+test_expect_success $PREREQ 'self name with dot is suppressed' "
+ test_suppress_self_quoted 'A U. Thor' 'author@example.com' \
+ 'self_name_dot_suppressed'
+"
+
+test_expect_success $PREREQ 'non-ascii self name is suppressed' "
+ test_suppress_self_quoted 'Füñný Nâmé' 'odd_?=mail@example.com' \
+ 'non_ascii_self_suppressed'
+"
+
+test_expect_success $PREREQ 'sanitized self name is suppressed' "
+ test_suppress_self_unquoted '\"A U. Thor\"' 'author@example.com' \
+ 'self_name_sanitized_suppressed'
+"
+
test_expect_success $PREREQ 'Show all headers' '
git send-email \
--dry-run \
@@ -881,6 +956,20 @@ test_expect_success $PREREQ 'utf8 author is correctly passed on' '
grep "^From: Füñný Nâmé <odd_?=mail@example.com>" msgtxt1
'
+test_expect_success $PREREQ 'utf8 sender is not duplicated' '
+ clean_fake_sendmail &&
+ test_commit weird_sender &&
+ test_when_finished "git reset --hard HEAD^" &&
+ git commit --amend --author "Füñný Nâmé <odd_?=mail@example.com>" &&
+ git format-patch --stdout -1 >funny_name.patch &&
+ git send-email --from="Füñný Nâmé <odd_?=mail@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ funny_name.patch &&
+ grep "^From: " msgtxt1 >msgfrom &&
+ test_line_count = 1 msgfrom
+'
+
test_expect_success $PREREQ 'sendemail.composeencoding works' '
clean_fake_sendmail &&
git config sendemail.composeencoding iso-8859-1 &&
@@ -1003,55 +1092,6 @@ test_expect_success $PREREQ 'threading but no chain-reply-to' '
grep "In-Reply-To: " stdout
'
-test_expect_success $PREREQ 'warning with an implicit --chain-reply-to' '
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- outdir/000?-*.patch 2>errors >out &&
- grep "no-chain-reply-to" errors
-'
-
-test_expect_success $PREREQ 'no warning with an explicit --chain-reply-to' '
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- --chain-reply-to \
- outdir/000?-*.patch 2>errors >out &&
- ! grep "no-chain-reply-to" errors
-'
-
-test_expect_success $PREREQ 'no warning with an explicit --no-chain-reply-to' '
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- --nochain-reply-to \
- outdir/000?-*.patch 2>errors >out &&
- ! grep "no-chain-reply-to" errors
-'
-
-test_expect_success $PREREQ 'no warning with sendemail.chainreplyto = false' '
- git config sendemail.chainreplyto false &&
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- outdir/000?-*.patch 2>errors >out &&
- ! grep "no-chain-reply-to" errors
-'
-
-test_expect_success $PREREQ 'no warning with sendemail.chainreplyto = true' '
- git config sendemail.chainreplyto true &&
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- outdir/000?-*.patch 2>errors >out &&
- ! grep "no-chain-reply-to" errors
-'
-
test_expect_success $PREREQ 'sendemail.to works' '
git config --replace-all sendemail.to "Somebody <somebody@ex.com>" &&
git send-email \
diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh
index 3077851..f524d2f 100755
--- a/t/t9114-git-svn-dcommit-merge.sh
+++ b/t/t9114-git-svn-dcommit-merge.sh
@@ -48,7 +48,7 @@ test_expect_success 'setup svn repository' '
test_expect_success 'setup git mirror and merge' '
git svn init "$svnrepo" -t tags -T trunk -b branches &&
git svn fetch &&
- git checkout --track -b svn remotes/trunk &&
+ git checkout -b svn remotes/trunk &&
git checkout -b merge &&
echo new file > new_file &&
git add new_file &&
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index 2471bc6..34c2d8f 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -396,7 +396,7 @@ test_expect_success 'tree_tag-obj' 'git fast-export tree_tag-obj'
test_expect_success 'tag-obj_tag' 'git fast-export tag-obj_tag'
test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
-test_expect_success SYMLINKS 'directory becomes symlink' '
+test_expect_success 'directory becomes symlink' '
git init dirtosymlink &&
git init result &&
(
@@ -408,8 +408,7 @@ test_expect_success SYMLINKS 'directory becomes symlink' '
git add foo/world bar/world &&
git commit -q -mone &&
git rm -r foo &&
- ln -s bar foo &&
- git add foo &&
+ test_ln_s_add bar foo &&
git commit -q -mtwo
) &&
(
diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh
index 735a018..db69af2 100755
--- a/t/t9402-git-cvsserver-refs.sh
+++ b/t/t9402-git-cvsserver-refs.sh
@@ -330,7 +330,7 @@ test_expect_success 'validate result of edits [cvswork2]' '
test_expect_success 'validate basic diffs saved during above cvswork2 edits' '
test $(grep Index: cvsEdit1.diff | wc -l) = 1 &&
- test ! -s cvsEdit2-empty.diff &&
+ test_must_be_empty cvsEdit2-empty.diff &&
test $(grep Index: cvsEdit2-N.diff | wc -l) = 1 &&
test $(grep Index: cvsEdit3.diff | wc -l) = 3 &&
rm -rf diffSandbox &&
@@ -456,20 +456,20 @@ test_expect_success 'cvs up -r $(git rev-parse v1)' '
test_expect_success 'cvs diff -r v1 -u' '
( cd cvswork && cvs -f diff -r v1 -u ) >cvsDiff.out 2>cvs.log &&
- test ! -s cvsDiff.out &&
- test ! -s cvs.log
+ test_must_be_empty cvsDiff.out &&
+ test_must_be_empty cvs.log
'
test_expect_success 'cvs diff -N -r v2 -u' '
( cd cvswork && ! cvs -f diff -N -r v2 -u ) >cvsDiff.out 2>cvs.log &&
- test ! -s cvs.log &&
+ test_must_be_empty cvs.log &&
test -s cvsDiff.out &&
check_diff cvsDiff.out v2 v1 >check_diff.out 2>&1
'
test_expect_success 'cvs diff -N -r v2 -r v1.2' '
( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u ) >cvsDiff.out 2>cvs.log &&
- test ! -s cvs.log &&
+ test_must_be_empty cvs.log &&
test -s cvsDiff.out &&
check_diff cvsDiff.out v2 v1.2 >check_diff.out 2>&1
'
@@ -488,7 +488,7 @@ test_expect_success 'apply early [cvswork3] diff to b3' '
test_expect_success 'check [cvswork3] diff' '
( cd cvswork3 && ! cvs -f diff -N -u ) >"$WORKDIR/cvsDiff.out" 2>cvs.log &&
- test ! -s cvs.log &&
+ test_must_be_empty cvs.log &&
test -s cvsDiff.out &&
test $(grep Index: cvsDiff.out | wc -l) = 3 &&
test_cmp cvsDiff.out cvswork3edit.diff &&
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
index 6783c14..6fca193 100755
--- a/t/t9500-gitweb-standalone-no-errors.sh
+++ b/t/t9500-gitweb-standalone-no-errors.sh
@@ -156,10 +156,10 @@ test_expect_success \
git commit -a -m "File renamed." &&
gitweb_run "p=.git;a=commitdiff"'
-test_expect_success SYMLINKS \
+test_expect_success \
'commitdiff(0): file to symlink' \
'rm renamed_file &&
- ln -s file renamed_file &&
+ test_ln_s_add file renamed_file &&
git commit -a -m "File to symlink." &&
gitweb_run "p=.git;a=commitdiff"'
@@ -212,15 +212,14 @@ test_expect_success \
# ----------------------------------------------------------------------
# commitdiff testing (taken from t4114-apply-typechange.sh)
-test_expect_success SYMLINKS 'setup typechange commits' '
+test_expect_success 'setup typechange commits' '
echo "hello world" > foo &&
echo "hi planet" > bar &&
git update-index --add foo bar &&
git commit -m initial &&
git branch initial &&
rm -f foo &&
- ln -s bar foo &&
- git update-index foo &&
+ test_ln_s_add bar foo &&
git commit -m "foo symlinked to bar" &&
git branch foo-symlinked-to-bar &&
rm -f foo &&
@@ -361,11 +360,7 @@ test_expect_success \
echo "Changed" >> 04-rename-to &&
test_chmod +x 05-mode-change &&
rm -f 06-file-or-symlink &&
- if test_have_prereq SYMLINKS; then
- ln -s 01-change 06-file-or-symlink
- else
- printf %s 01-change > 06-file-or-symlink
- fi &&
+ test_ln_s_add 01-change 06-file-or-symlink &&
echo "Changed and have mode changed" > 07-change-mode-change &&
test_chmod +x 07-change-mode-change &&
git commit -a -m "Large commit" &&
diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh
index 9730821..2bf142d 100755
--- a/t/t9801-git-p4-branch.sh
+++ b/t/t9801-git-p4-branch.sh
@@ -469,9 +469,11 @@ test_expect_success 'use-client-spec detect-branches skips branches setup' '
View: //depot/usecs/b1/... //depot/usecs/b3/...
EOF
- echo b3/b3-file3 >b3/b3-file3 &&
- p4 add b3/b3-file3 &&
- p4 submit -d "b3/b3-file3"
+ echo b3/b3-file3_1 >b3/b3-file3_1 &&
+ echo b3/b3-file3_2 >b3/b3-file3_2 &&
+ p4 add b3/b3-file3_1 &&
+ p4 add b3/b3-file3_2 &&
+ p4 submit -d "b3/b3-file3_1 b3/b3-file3_2"
)
'
@@ -487,6 +489,21 @@ test_expect_success 'use-client-spec detect-branches skips branches' '
)
'
+test_expect_success 'use-client-spec detect-branches skips files in branches' '
+ client_view "//depot/usecs/... //client/..." \
+ "-//depot/usecs/b3/b3-file3_1 //client/b3/b3-file3_1" &&
+ test_when_finished cleanup_git &&
+ test_create_repo "$git" &&
+ (
+ cd "$git" &&
+ git p4 sync --detect-branches --use-client-spec //depot/usecs@all &&
+ git checkout -b master p4/usecs/b3 &&
+ test_path_is_file b1-file1 &&
+ test_path_is_file b3-file3_2 &&
+ test_path_is_missing b3-file3_1
+ )
+'
+
test_expect_success 'kill p4d' '
kill_p4d
'
diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh
index eeefa67..b0d1d94 100755
--- a/t/t9802-git-p4-filetype.sh
+++ b/t/t9802-git-p4-filetype.sh
@@ -95,7 +95,7 @@ test_expect_success 'gitattributes setting eol=lf produces lf newlines' '
git init &&
echo "* eol=lf" >.gitattributes &&
git p4 sync //depot@all &&
- git checkout master &&
+ git checkout -b master p4/master &&
test_cmp "$cli"/f-unix-orig f-unix &&
test_cmp "$cli"/f-win-as-lf f-win
)
@@ -109,7 +109,7 @@ test_expect_success 'gitattributes setting eol=crlf produces crlf newlines' '
git init &&
echo "* eol=crlf" >.gitattributes &&
git p4 sync //depot@all &&
- git checkout master &&
+ git checkout -b master p4/master &&
test_cmp "$cli"/f-unix-as-crlf f-unix &&
test_cmp "$cli"/f-win-orig f-win
)
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 6d9d141..272a071 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -69,7 +69,7 @@ run_completion ()
local -a COMPREPLY _words
local _cword
_words=( $1 )
- test "${1: -1}" == ' ' && _words+=('')
+ test "${1: -1}" = ' ' && _words+=('')
(( _cword = ${#_words[@]} - 1 ))
__git_wrap__git_main && print_comp
}
@@ -122,6 +122,140 @@ test_gitcomp_nl ()
invalid_variable_name='${foo.bar}'
+actual="$TRASH_DIRECTORY/actual"
+
+test_expect_success 'setup for __gitdir tests' '
+ mkdir -p subdir/subsubdir &&
+ git init otherrepo
+'
+
+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 "$(pwd -P)/.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 "$(pwd -P)/.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 "$(pwd -P)/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 "$(pwd -P)/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 "$(pwd -P)/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 '__gitcomp - trailing space - options' '
test_gitcomp "--re" "--dry-run --reuse-message= --reedit-message=
--reset-author" <<-EOF
@@ -231,6 +365,7 @@ test_expect_success 'double dash "git" itself' '
--exec-path Z
--exec-path=
--html-path Z
+ --man-path Z
--info-path Z
--work-tree=
--namespace=
@@ -347,4 +482,81 @@ test_expect_success 'send-email' '
test_completion "git send-email ma" "master "
'
+test_expect_success 'complete files' '
+ git init tmp && cd tmp &&
+ test_when_finished "cd .. && rm -rf tmp" &&
+
+ echo "expected" > .gitignore &&
+ echo "out" >> .gitignore &&
+
+ git add .gitignore &&
+ test_completion "git commit " ".gitignore" &&
+
+ git commit -m ignore &&
+
+ touch new &&
+ test_completion "git add " "new" &&
+
+ git add new &&
+ git commit -a -m new &&
+ test_completion "git add " "" &&
+
+ git mv new modified &&
+ echo modify > modified &&
+ test_completion "git add " "modified" &&
+
+ touch untracked &&
+
+ : TODO .gitignore should not be here &&
+ test_completion "git rm " <<-\EOF &&
+ .gitignore
+ modified
+ EOF
+
+ test_completion "git clean " "untracked" &&
+
+ : TODO .gitignore should not be here &&
+ test_completion "git mv " <<-\EOF &&
+ .gitignore
+ modified
+ EOF
+
+ mkdir dir &&
+ touch dir/file-in-dir &&
+ git add dir/file-in-dir &&
+ git commit -m dir &&
+
+ mkdir untracked-dir &&
+
+ : TODO .gitignore should not be here &&
+ test_completion "git mv modified " <<-\EOF &&
+ .gitignore
+ dir
+ modified
+ untracked
+ untracked-dir
+ EOF
+
+ test_completion "git commit " "modified" &&
+
+ : TODO .gitignore should not be here &&
+ test_completion "git ls-files " <<-\EOF
+ .gitignore
+ dir
+ modified
+ EOF
+
+ touch momified &&
+ test_completion "git add mom" "momified"
+'
+
+test_expect_failure 'complete with tilde expansion' '
+ git init tmp && cd tmp &&
+ test_when_finished "cd .. && rm -rf tmp" &&
+
+ touch ~/tmp/file &&
+
+ test_completion "git add ~/tmp/" "~/tmp/file"
+'
+
test_done
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 083b319..3c3e4e8 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -10,528 +10,558 @@ test_description='test git-specific bash prompt functions'
. "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh"
actual="$TRASH_DIRECTORY/actual"
+c_red='\\[\\e[31m\\]'
+c_green='\\[\\e[32m\\]'
+c_lblue='\\[\\e[1;34m\\]'
+c_clear='\\[\\e[0m\\]'
test_expect_success 'setup for prompt tests' '
- mkdir -p subdir/subsubdir &&
git init otherrepo &&
- echo 1 > file &&
+ 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 &&
+ echo 2 >file &&
git commit -m "second b1" file &&
- echo 3 > file &&
+ echo 3 >file &&
git commit -m "third b1" file &&
git tag -a -m msg2 t2 &&
git checkout -b b2 master &&
- echo 0 > file &&
+ echo 0 >file &&
git commit -m "second b2" file &&
- echo 00 > file &&
+ echo 00 >file &&
git commit -m "another b2" file &&
- echo 000 > file &&
+ echo 000 >file &&
git commit -m "yet another 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 "$(pwd -P)/.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 "$(pwd -P)/.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 "$(pwd -P)/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 "$(pwd -P)/otherrepo/.git" > expected &&
- echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
- test_when_finished "rm -f subdir/.git" &&
- (
- cd subdir/subsubdir &&
- __gitdir > "$actual"
- ) &&
+test_expect_success 'prompt - branch name' '
+ printf " (master)" >expected &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
-test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' '
- echo "$(pwd -P)/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_expect_success SYMLINKS 'prompt - branch name - symlink symref' '
+ printf " (master)" >expected &&
+ test_when_finished "git checkout master" &&
+ test_config core.preferSymlinkRefs true &&
+ git checkout master &&
+ __git_ps1 >"$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_expect_success 'prompt - unborn branch' '
+ printf " (unborn)" >expected &&
+ git checkout --orphan unborn &&
+ test_when_finished "git checkout master" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - detached head' '
- printf " ((%s...))" $(git log -1 --format="%h" b1^) > expected &&
+ printf " ((%s...))" $(git log -1 --format="%h" --abbrev=13 b1^) >expected &&
+ test_config core.abbrev 13 &&
git checkout b1^ &&
test_when_finished "git checkout master" &&
- __git_ps1 > "$actual" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - describe detached head - contains' '
- printf " ((t2~1))" > expected &&
+ printf " ((t2~1))" >expected &&
git checkout b1^ &&
test_when_finished "git checkout master" &&
(
GIT_PS1_DESCRIBE_STYLE=contains &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - describe detached head - branch' '
- printf " ((b1~1))" > expected &&
+ printf " ((b1~1))" >expected &&
git checkout b1^ &&
test_when_finished "git checkout master" &&
(
GIT_PS1_DESCRIBE_STYLE=branch &&
- __git_ps1 > "$actual"
+ __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 &&
+ 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"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - describe detached head - default' '
- printf " ((t2))" > expected &&
+ printf " ((t2))" >expected &&
git checkout --detach b1 &&
test_when_finished "git checkout master" &&
- __git_ps1 > "$actual" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - inside .git directory' '
- printf " (GIT_DIR!)" > expected &&
+ printf " (GIT_DIR!)" >expected &&
(
cd .git &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - deep inside .git directory' '
- printf " (GIT_DIR!)" > expected &&
+ printf " (GIT_DIR!)" >expected &&
(
cd .git/refs/heads &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - inside bare repository' '
- printf " (BARE:master)" > expected &&
+ printf " (BARE:master)" >expected &&
git init --bare bare.git &&
test_when_finished "rm -rf bare.git" &&
(
cd bare.git &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - interactive rebase' '
- printf " (b1|REBASE-i 2/3)" > expected
- echo "#!$SHELL_PATH" >fake_editor.sh &&
- cat >>fake_editor.sh <<\EOF &&
-echo "exec echo" > "$1"
-echo "edit $(git log -1 --format="%h")" >> "$1"
-echo "exec echo" >> "$1"
-EOF
+ printf " (b1|REBASE-i 2/3)" >expected
+ write_script fake_editor.sh <<-\EOF &&
+ echo "exec echo" >"$1"
+ echo "edit $(git log -1 --format="%h")" >>"$1"
+ echo "exec echo" >>"$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" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - rebase merge' '
- printf " (b2|REBASE-m 1/3)" > expected &&
+ printf " (b2|REBASE-m 1/3)" >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" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - rebase' '
- printf " ((t2)|REBASE 1/3)" > expected &&
+ printf " (b2|REBASE 1/3)" >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" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - merge' '
- printf " (b1|MERGING)" > expected &&
+ 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" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - cherry-pick' '
- printf " (master|CHERRY-PICKING)" > expected &&
+ printf " (master|CHERRY-PICKING)" >expected &&
test_must_fail git cherry-pick b1 &&
test_when_finished "git reset --hard" &&
- __git_ps1 > "$actual" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - bisect' '
- printf " (master|BISECTING)" > expected &&
+ printf " (master|BISECTING)" >expected &&
git bisect start &&
test_when_finished "git bisect reset" &&
- __git_ps1 > "$actual" &&
+ __git_ps1 >"$actual" &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - dirty status indicator - clean' '
- printf " (master)" > expected &&
+ printf " (master)" >expected &&
(
GIT_PS1_SHOWDIRTYSTATE=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - dirty status indicator - dirty worktree' '
- printf " (master *)" > expected &&
- echo "dirty" > file &&
+ printf " (master *)" >expected &&
+ echo "dirty" >file &&
test_when_finished "git reset --hard" &&
(
GIT_PS1_SHOWDIRTYSTATE=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - dirty status indicator - dirty index' '
- printf " (master +)" > expected &&
- echo "dirty" > file &&
+ printf " (master +)" >expected &&
+ echo "dirty" >file &&
test_when_finished "git reset --hard" &&
git add -u &&
(
GIT_PS1_SHOWDIRTYSTATE=y &&
- __git_ps1 > "$actual"
+ __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 &&
+ printf " (master *+)" >expected &&
+ echo "dirty index" >file &&
test_when_finished "git reset --hard" &&
git add -u &&
- echo "dirty worktree" > file &&
+ echo "dirty worktree" >file &&
(
GIT_PS1_SHOWDIRTYSTATE=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - dirty status indicator - before root commit' '
- printf " (master #)" > expected &&
+ printf " (master #)" >expected &&
(
GIT_PS1_SHOWDIRTYSTATE=y &&
cd otherrepo &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - dirty status indicator - shell variable unset with config disabled' '
- printf " (master)" > expected &&
- echo "dirty" > file &&
+ printf " (master)" >expected &&
+ echo "dirty" >file &&
test_when_finished "git reset --hard" &&
test_config bash.showDirtyState false &&
(
sane_unset GIT_PS1_SHOWDIRTYSTATE &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - dirty status indicator - shell variable unset with config enabled' '
- printf " (master)" > expected &&
- echo "dirty" > file &&
+ printf " (master)" >expected &&
+ echo "dirty" >file &&
test_when_finished "git reset --hard" &&
test_config bash.showDirtyState true &&
(
sane_unset GIT_PS1_SHOWDIRTYSTATE &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - dirty status indicator - shell variable set with config disabled' '
- printf " (master)" > expected &&
- echo "dirty" > file &&
+ printf " (master)" >expected &&
+ echo "dirty" >file &&
test_when_finished "git reset --hard" &&
test_config bash.showDirtyState false &&
(
GIT_PS1_SHOWDIRTYSTATE=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - dirty status indicator - shell variable set with config enabled' '
- printf " (master *)" > expected &&
- echo "dirty" > file &&
+ printf " (master *)" >expected &&
+ echo "dirty" >file &&
test_when_finished "git reset --hard" &&
test_config bash.showDirtyState true &&
(
GIT_PS1_SHOWDIRTYSTATE=y &&
- __git_ps1 > "$actual"
+ __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 &&
+ printf " (GIT_DIR!)" >expected &&
+ echo "dirty" >file &&
test_when_finished "git reset --hard" &&
(
GIT_PS1_SHOWDIRTYSTATE=y &&
cd .git &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - stash status indicator - no stash' '
- printf " (master)" > expected &&
+ printf " (master)" >expected &&
(
GIT_PS1_SHOWSTASHSTATE=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - stash status indicator - stash' '
- printf " (master $)" > expected &&
+ printf " (master $)" >expected &&
echo 2 >file &&
git stash &&
test_when_finished "git stash drop" &&
+ git pack-refs --all &&
(
GIT_PS1_SHOWSTASHSTATE=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - stash status indicator - not shown inside .git directory' '
- printf " (GIT_DIR!)" > expected &&
+ printf " (GIT_DIR!)" >expected &&
echo 2 >file &&
git stash &&
test_when_finished "git stash drop" &&
(
GIT_PS1_SHOWSTASHSTATE=y &&
cd .git &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - untracked files status indicator - no untracked files' '
- printf " (master)" > expected &&
+ printf " (master)" >expected &&
(
GIT_PS1_SHOWUNTRACKEDFILES=y &&
cd otherrepo &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - untracked files status indicator - untracked files' '
- printf " (master %%)" > expected &&
+ printf " (master %%)" >expected &&
(
GIT_PS1_SHOWUNTRACKEDFILES=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - untracked files status indicator - shell variable unset with config disabled' '
- printf " (master)" > expected &&
+ printf " (master)" >expected &&
test_config bash.showUntrackedFiles false &&
(
sane_unset GIT_PS1_SHOWUNTRACKEDFILES &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - untracked files status indicator - shell variable unset with config enabled' '
- printf " (master)" > expected &&
+ printf " (master)" >expected &&
test_config bash.showUntrackedFiles true &&
(
sane_unset GIT_PS1_SHOWUNTRACKEDFILES &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - untracked files status indicator - shell variable set with config disabled' '
- printf " (master)" > expected &&
+ printf " (master)" >expected &&
test_config bash.showUntrackedFiles false &&
(
GIT_PS1_SHOWUNTRACKEDFILES=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - untracked files status indicator - shell variable set with config enabled' '
- printf " (master %%)" > expected &&
+ printf " (master %%)" >expected &&
test_config bash.showUntrackedFiles true &&
(
GIT_PS1_SHOWUNTRACKEDFILES=y &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - untracked files status indicator - not shown inside .git directory' '
- printf " (GIT_DIR!)" > expected &&
+ printf " (GIT_DIR!)" >expected &&
(
GIT_PS1_SHOWUNTRACKEDFILES=y &&
cd .git &&
- __git_ps1 > "$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
test_expect_success 'prompt - format string starting with dash' '
- printf -- "-master" > expected &&
- __git_ps1 "-%s" > "$actual" &&
+ printf -- "-master" >expected &&
+ __git_ps1 "-%s" >"$actual" &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - pc mode' '
+ printf "BEFORE: (master):AFTER" >expected &&
+ printf "" >expected_output &&
+ (
+ __git_ps1 "BEFORE:" ":AFTER" >"$actual" &&
+ test_cmp expected_output "$actual" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - branch name' '
+ printf "BEFORE: (${c_green}master${c_clear}):AFTER" >expected &&
+ (
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ __git_ps1 "BEFORE:" ":AFTER" >"$actual"
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - detached head' '
+ printf "BEFORE: (${c_red}(%s...)${c_clear}):AFTER" $(git log -1 --format="%h" b1^) >expected &&
+ git checkout b1^ &&
+ test_when_finished "git checkout master" &&
+ (
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty worktree' '
+ printf "BEFORE: (${c_green}master${c_clear} ${c_red}*${c_clear}):AFTER" >expected &&
+ echo "dirty" >file &&
+ test_when_finished "git reset --hard" &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty index' '
+ printf "BEFORE: (${c_green}master${c_clear} ${c_green}+${c_clear}):AFTER" >expected &&
+ echo "dirty" >file &&
+ test_when_finished "git reset --hard" &&
+ git add -u &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty index and worktree' '
+ printf "BEFORE: (${c_green}master${c_clear} ${c_red}*${c_green}+${c_clear}):AFTER" >expected &&
+ echo "dirty index" >file &&
+ test_when_finished "git reset --hard" &&
+ git add -u &&
+ echo "dirty worktree" >file &&
+ (
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - dirty status indicator - before root commit' '
+ printf "BEFORE: (${c_green}master${c_clear} ${c_green}#${c_clear}):AFTER" >expected &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ cd otherrepo &&
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - inside .git directory' '
+ printf "BEFORE: (${c_green}GIT_DIR!${c_clear}):AFTER" >expected &&
+ echo "dirty" >file &&
+ test_when_finished "git reset --hard" &&
+ (
+ GIT_PS1_SHOWDIRTYSTATE=y &&
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ cd .git &&
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - stash status indicator' '
+ printf "BEFORE: (${c_green}master${c_clear} ${c_lblue}\$${c_clear}):AFTER" >expected &&
+ echo 2 >file &&
+ git stash &&
+ test_when_finished "git stash drop" &&
+ (
+ GIT_PS1_SHOWSTASHSTATE=y &&
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bash color pc mode - untracked files status indicator' '
+ printf "BEFORE: (${c_green}master${c_clear} ${c_red}%%${c_clear}):AFTER" >expected &&
+ (
+ GIT_PS1_SHOWUNTRACKEDFILES=y &&
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ __git_ps1 "BEFORE:" ":AFTER" &&
+ printf "%s" "$PS1" >"$actual"
+ ) &&
+ test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - zsh color pc mode' '
+ printf "BEFORE: (%%F{green}master%%f):AFTER" >expected &&
+ (
+ ZSH_VERSION=5.0.0 &&
+ GIT_PS1_SHOWCOLORHINTS=y &&
+ __git_ps1 "BEFORE:" ":AFTER" >"$actual"
+ printf "%s" "$PS1" >"$actual"
+ ) &&
test_cmp expected "$actual"
'
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 5251009..a7e9aac 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -343,6 +343,7 @@ test_declared_prereq () {
}
test_expect_failure () {
+ test_start_
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"
@@ -357,10 +358,11 @@ test_expect_failure () {
test_known_broken_failure_ "$1"
fi
fi
- echo >&3 ""
+ test_finish_
}
test_expect_success () {
+ test_start_
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"
@@ -375,7 +377,7 @@ test_expect_success () {
test_failure_ "$@"
fi
fi
- echo >&3 ""
+ test_finish_
}
# test_external runs external test scripts that provide continuous
@@ -609,6 +611,18 @@ test_cmp() {
$GIT_TEST_CMP "$@"
}
+# Check if the file expected to be empty is indeed empty, and barfs
+# otherwise.
+
+test_must_be_empty () {
+ if test -s "$1"
+ then
+ echo "'$1' is not empty, it contains:"
+ cat "$1"
+ return 1
+ fi
+}
+
# Tests that its two parameters refer to the same revision
test_cmp_rev () {
git rev-parse --verify "$1" >expect.rev &&
@@ -679,3 +693,20 @@ test_create_repo () {
mv .git/hooks .git/hooks-disabled
) || exit
}
+
+# This function helps on symlink challenged file systems when it is not
+# important that the file system entry is a symbolic link.
+# Use test_ln_s_add instead of "ln -s x y && git add y" to add a
+# symbolic link entry y to the index.
+
+test_ln_s_add () {
+ if test_have_prereq SYMLINKS
+ then
+ ln -s "$1" "$2" &&
+ git update-index --add "$2"
+ else
+ printf '%s' "$1" >"$2" &&
+ ln_s_obj=$(git hash-object -w "$2") &&
+ git update-index --add --cacheinfo 120000 $ln_s_obj "$2"
+ fi
+}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index ca6bdef..b4902831 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -54,8 +54,8 @@ done,*)
# do not redirect again
;;
*' --tee '*|*' --va'*)
- mkdir -p test-results
- BASE=test-results/$(basename "$0" .sh)
+ mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results"
+ BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
(GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1;
echo $? > $BASE.exit) | tee $BASE.out
test "$(cat $BASE.exit)" = 0
@@ -92,6 +92,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e '
print join("\n", @vars);
')
unset XDG_CONFIG_HOME
+unset GITPERLLIB
GIT_AUTHOR_EMAIL=author@example.com
GIT_AUTHOR_NAME='A U Thor'
GIT_COMMITTER_EMAIL=committer@example.com
@@ -184,6 +185,9 @@ do
help=t; shift ;;
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
verbose=t; shift ;;
+ --verbose-only=*)
+ verbose_only=$(expr "z$1" : 'z[^=]*=\(.*\)')
+ shift ;;
-q|--q|--qu|--qui|--quie|--quiet)
# Ignore --quiet under a TAP::Harness. Saying how many tests
# passed without the ok/not ok details is always an error.
@@ -198,17 +202,39 @@ do
--valgrind=*)
valgrind=$(expr "z$1" : 'z[^=]*=\(.*\)')
shift ;;
+ --valgrind-only=*)
+ valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)')
+ shift ;;
+ --valgrind-parallel=*)
+ valgrind_parallel=$(expr "z$1" : 'z[^=]*=\(.*\)')
+ shift ;;
+ --valgrind-only-stride=*)
+ valgrind_only_stride=$(expr "z$1" : 'z[^=]*=\(.*\)')
+ shift ;;
+ --valgrind-only-offset=*)
+ valgrind_only_offset=$(expr "z$1" : 'z[^=]*=\(.*\)')
+ shift ;;
--tee)
shift ;; # was handled already
--root=*)
root=$(expr "z$1" : 'z[^=]*=\(.*\)')
shift ;;
+ --statusprefix=*)
+ statusprefix=$(expr "z$1" : 'z[^=]*=\(.*\)')
+ shift ;;
*)
echo "error: unknown test option '$1'" >&2; exit 1 ;;
esac
done
-test -n "$valgrind" && verbose=t
+if test -n "$valgrind_only" || test -n "$valgrind_only_stride"
+then
+ test -z "$valgrind" && valgrind=memcheck
+ test -z "$verbose" && verbose_only="$valgrind_only"
+elif test -n "$valgrind"
+then
+ verbose=t
+fi
if test -n "$color"
then
@@ -303,12 +329,12 @@ trap 'die' EXIT
test_ok_ () {
test_success=$(($test_success + 1))
- say_color "" "ok $test_count - $@"
+ say_color "" "${statusprefix}ok $test_count - $@"
}
test_failure_ () {
test_failure=$(($test_failure + 1))
- say_color error "not ok $test_count - $1"
+ say_color error "${statusprefix}not ok $test_count - $1"
shift
echo "$@" | sed -e 's/^/# /'
test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; }
@@ -316,18 +342,83 @@ test_failure_ () {
test_known_broken_ok_ () {
test_fixed=$(($test_fixed+1))
- say_color error "ok $test_count - $@ # TODO known breakage vanished"
+ say_color error "${statusprefix}ok $test_count - $@ # TODO known breakage vanished"
}
test_known_broken_failure_ () {
test_broken=$(($test_broken+1))
- say_color warn "not ok $test_count - $@ # TODO known breakage"
+ say_color warn "${statusprefix}not ok $test_count - $@ # TODO known breakage"
}
test_debug () {
test "$debug" = "" || eval "$1"
}
+match_pattern_list () {
+ arg="$1"
+ shift
+ test -z "$*" && return 1
+ for pattern_
+ do
+ case "$arg" in
+ $pattern_)
+ return 0
+ esac
+ done
+ return 1
+}
+
+maybe_teardown_verbose () {
+ test -z "$verbose_only" && return
+ exec 4>/dev/null 3>/dev/null
+ verbose=
+}
+
+last_verbose=t
+maybe_setup_verbose () {
+ test -z "$verbose_only" && return
+ if match_pattern_list $test_count $verbose_only ||
+ { test -n "$valgrind_only_stride" &&
+ expr $test_count "%" $valgrind_only_stride - $valgrind_only_offset = 0 >/dev/null; }
+ then
+ exec 4>&2 3>&1
+ # Emit a delimiting blank line when going from
+ # non-verbose to verbose. Within verbose mode the
+ # delimiter is printed by test_expect_*. The choice
+ # of the initial $last_verbose is such that before
+ # test 1, we do not print it.
+ test -z "$last_verbose" && echo >&3 ""
+ verbose=t
+ else
+ exec 4>/dev/null 3>/dev/null
+ verbose=
+ fi
+ last_verbose=$verbose
+}
+
+maybe_teardown_valgrind () {
+ test -z "$GIT_VALGRIND" && return
+ GIT_VALGRIND_ENABLED=
+}
+
+maybe_setup_valgrind () {
+ test -z "$GIT_VALGRIND" && return
+ if test -z "$valgrind_only" && test -z "$valgrind_only_stride"
+ then
+ GIT_VALGRIND_ENABLED=t
+ return
+ fi
+ GIT_VALGRIND_ENABLED=
+ if match_pattern_list $test_count $valgrind_only
+ then
+ GIT_VALGRIND_ENABLED=t
+ elif test -n "$valgrind_only_stride" &&
+ expr $test_count "%" $valgrind_only_stride - $valgrind_only_offset = 0 >/dev/null
+ then
+ GIT_VALGRIND_ENABLED=t
+ fi
+}
+
test_eval_ () {
# This is a separate function because some tests use
# "return" to end a test_expect_success block early.
@@ -337,8 +428,10 @@ test_eval_ () {
test_run_ () {
test_cleanup=:
expecting_failure=$2
+ setup_malloc_check
test_eval_ "$1"
eval_ret=$?
+ teardown_malloc_check
if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"
then
@@ -353,17 +446,24 @@ test_run_ () {
return "$eval_ret"
}
-test_skip () {
+test_start_ () {
test_count=$(($test_count+1))
+ maybe_setup_verbose
+ maybe_setup_valgrind
+}
+
+test_finish_ () {
+ echo >&3 ""
+ maybe_teardown_valgrind
+ maybe_teardown_verbose
+}
+
+test_skip () {
to_skip=
- for skp in $GIT_SKIP_TESTS
- do
- case $this_test.$test_count in
- $skp)
- to_skip=t
- break
- esac
- done
+ if match_pattern_list $this_test.$test_count $GIT_SKIP_TESTS
+ then
+ to_skip=t
+ fi
if test -z "$to_skip" && test -n "$test_prereq" &&
! test_have_prereq "$test_prereq"
then
@@ -377,8 +477,8 @@ test_skip () {
of_prereq=" of $test_prereq"
fi
- say_color skip >&3 "skipping test: $@"
- say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})"
+ say_color skip >&3 "${statusprefix}skipping test: $@"
+ say_color skip "${statusprefix}ok $test_count # skip $1 (missing $missing_prereq${of_prereq})"
: true
;;
*)
@@ -395,6 +495,8 @@ test_at_end_hook_ () {
test_done () {
GIT_EXIT_OK=t
+ # Note: t0000 relies on $HARNESS_ACTIVE disabling the .counts
+ # output file
if test -z "$HARNESS_ACTIVE"
then
test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results"
@@ -414,11 +516,11 @@ test_done () {
if test "$test_fixed" != 0
then
- say_color error "# $test_fixed known breakage(s) vanished; please update test(s)"
+ say_color error "${statusprefix}# $test_fixed known breakage(s) vanished; please update test(s)"
fi
if test "$test_broken" != 0
then
- say_color warn "# still have $test_broken known breakage(s)"
+ say_color warn "${statusprefix}# still have $test_broken known breakage(s)"
fi
if test "$test_broken" != 0 || test "$test_fixed" != 0
then
@@ -441,9 +543,9 @@ test_done () {
then
if test $test_remaining -gt 0
then
- say_color pass "# passed all $msg"
+ say_color pass "${statusprefix}# passed all $msg"
fi
- say "1..$test_count$skip_all"
+ say "${statusprefix}1..$test_count$skip_all"
fi
test -d "$remove_trash" &&
@@ -457,8 +559,8 @@ test_done () {
*)
if test $test_external_has_tap -eq 0
then
- say_color error "# failed $test_failure among $msg"
- say "1..$test_count"
+ say_color error "${statusprefix}# failed $test_failure among $msg"
+ say "${statusprefix}1..$test_count"
fi
exit 1 ;;
@@ -466,6 +568,9 @@ test_done () {
esac
}
+
+# Set up a directory that we can put in PATH which redirects all git
+# calls to 'valgrind git ...'.
if test -n "$valgrind"
then
make_symlink () {
@@ -513,31 +618,43 @@ then
make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit
}
- # override all git executables in TEST_DIRECTORY/..
- GIT_VALGRIND=$TEST_DIRECTORY/valgrind
- mkdir -p "$GIT_VALGRIND"/bin
- for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-*
- 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
- do
- ls "$path"/git-* 2> /dev/null |
- while read file
+ # In the case of --valgrind-parallel, we only need to do the
+ # wrapping once, in the main script. The worker children all
+ # have $valgrind_only_stride set, so we can skip based on that.
+ if test -z "$valgrind_only_stride"
+ then
+ # override all git executables in TEST_DIRECTORY/..
+ GIT_VALGRIND=$TEST_DIRECTORY/valgrind
+ mkdir -p "$GIT_VALGRIND"/bin
+ for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-*
do
- make_valgrind_symlink "$file"
+ make_valgrind_symlink $file
done
- done
- IFS=$OLDIFS
+ # special-case the mergetools loadables
+ make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools"
+ OLDIFS=$IFS
+ IFS=:
+ for path in $PATH
+ do
+ ls "$path"/git-* 2> /dev/null |
+ while read file
+ do
+ make_valgrind_symlink "$file"
+ done
+ done
+ IFS=$OLDIFS
+ fi
PATH=$GIT_VALGRIND/bin:$PATH
GIT_EXEC_PATH=$GIT_VALGRIND/bin
export GIT_VALGRIND
GIT_VALGRIND_MODE="$valgrind"
export GIT_VALGRIND_MODE
+ GIT_VALGRIND_ENABLED=t
+ if test -n "$valgrind_only" || test -n "$valgrind_only_stride"
+ then
+ GIT_VALGRIND_ENABLED=
+ fi
+ export GIT_VALGRIND_ENABLED
elif test -n "$GIT_TEST_INSTALLED"
then
GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) ||
@@ -622,21 +739,53 @@ then
else
mkdir -p "$TRASH_DIRECTORY"
fi
+
+# Gross hack to spawn N sub-instances of the tests in parallel, and
+# summarize the results. Note that if this is enabled, the script
+# terminates at the end of this 'if' block.
+if test -n "$valgrind_parallel"
+then
+ for i in $(test_seq 1 $valgrind_parallel)
+ do
+ root="$TRASH_DIRECTORY/vgparallel-$i"
+ mkdir "$root"
+ TEST_OUTPUT_DIRECTORY="$root" \
+ ${SHELL_PATH} "$0" \
+ --root="$root" --statusprefix="[$i] " \
+ --valgrind="$valgrind" \
+ --valgrind-only-stride="$valgrind_parallel" \
+ --valgrind-only-offset="$i" &
+ pids="$pids $!"
+ done
+ trap "kill $pids" INT TERM HUP
+ wait $pids
+ trap - INT TERM HUP
+ for i in $(test_seq 1 $valgrind_parallel)
+ do
+ root="$TRASH_DIRECTORY/vgparallel-$i"
+ eval "$(cat "$root/test-results/$(basename "$0" .sh)"-*.counts |
+ sed 's/^\([a-z][a-z]*\) \([0-9][0-9]*\)/inner_\1=\2/')"
+ test_count=$(expr $test_count + $inner_total)
+ test_success=$(expr $test_success + $inner_success)
+ test_fixed=$(expr $test_fixed + $inner_fixed)
+ test_broken=$(expr $test_broken + $inner_broken)
+ test_failure=$(expr $test_failure + $inner_failed)
+ done
+ test_done
+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 "$TRASH_DIRECTORY" || exit 1
this_test=${0##*/}
this_test=${this_test%%-*}
-for skp in $GIT_SKIP_TESTS
-do
- case "$this_test" in
- $skp)
- say_color info >&3 "skipping test $this_test altogether"
- skip_all="skip all tests in $this_test"
- test_done
- esac
-done
+if match_pattern_list "$this_test" $GIT_SKIP_TESTS
+then
+ say_color info >&3 "skipping test $this_test altogether"
+ skip_all="skip all tests in $this_test"
+ test_done
+fi
# Provide an implementation of the 'yes' utility
yes () {
@@ -737,7 +886,14 @@ test_i18ngrep () {
test_lazy_prereq PIPE '
# test whether the filesystem supports FIFOs
- rm -f testfifo && mkfifo testfifo
+ case $(uname -s) in
+ CYGWIN*)
+ false
+ ;;
+ *)
+ rm -f testfifo && mkfifo testfifo
+ ;;
+ esac
'
test_lazy_prereq SYMLINKS '
diff --git a/t/valgrind/analyze.sh b/t/valgrind/analyze.sh
index d8105d9..2ffc80f 100755
--- a/t/valgrind/analyze.sh
+++ b/t/valgrind/analyze.sh
@@ -1,6 +1,10 @@
#!/bin/sh
-out_prefix=$(dirname "$0")/../test-results/valgrind.out
+# Get TEST_OUTPUT_DIRECTORY from GIT-BUILD-OPTIONS if it's there...
+. "$(dirname "$0")/../../GIT-BUILD-OPTIONS"
+# ... otherwise set it to the default value.
+: ${TEST_OUTPUT_DIRECTORY=$(dirname "$0")/..}
+
output=
count=0
total_count=0
@@ -115,7 +119,7 @@ handle_one () {
finish_output
}
-for test_script in "$(dirname "$0")"/../test-results/*.out
+for test_script in "$TEST_OUTPUT_DIRECTORY"/test-results/*.out
do
handle_one $test_script
done
diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh
index 6b87c91..4215303 100755
--- a/t/valgrind/valgrind.sh
+++ b/t/valgrind/valgrind.sh
@@ -4,6 +4,9 @@ base=$(basename "$0")
TOOL_OPTIONS='--leak-check=no'
+test -z "$GIT_VALGRIND_ENABLED" &&
+exec "$GIT_VALGRIND"/../../"$base" "$@"
+
case "$GIT_VALGRIND_MODE" in
memcheck-fast)
;;
diff --git a/templates/hooks--pre-push.sample b/templates/hooks--pre-push.sample
index 15ab6d8..15ab6d8 100644..100755
--- a/templates/hooks--pre-push.sample
+++ b/templates/hooks--pre-push.sample
diff --git a/test-chmtime.c b/test-chmtime.c
index 02b42ba..0df7487 100644
--- a/test-chmtime.c
+++ b/test-chmtime.c
@@ -56,7 +56,7 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq)
return 1;
}
-int main(int argc, const char *argv[])
+int main(int argc, char *argv[])
{
static int verbose;
@@ -84,15 +84,15 @@ int main(int argc, const char *argv[])
if (stat(argv[i], &sb) < 0) {
fprintf(stderr, "Failed to stat %s: %s\n",
argv[i], strerror(errno));
- return -1;
+ return 1;
}
-#ifdef WIN32
+#ifdef GIT_WINDOWS_NATIVE
if (!(sb.st_mode & S_IWUSR) &&
chmod(argv[i], sb.st_mode | S_IWUSR)) {
fprintf(stderr, "Could not make user-writable %s: %s",
argv[i], strerror(errno));
- return -1;
+ return 1;
}
#endif
@@ -107,7 +107,7 @@ int main(int argc, const char *argv[])
if (utb.modtime != sb.st_mtime && utime(argv[i], &utb) < 0) {
fprintf(stderr, "Failed to modify time on %s: %s\n",
argv[i], strerror(errno));
- return -1;
+ return 1;
}
}
@@ -115,5 +115,5 @@ int main(int argc, const char *argv[])
usage:
fprintf(stderr, "usage: %s %s\n", argv[0], usage_str);
- return -1;
+ return 1;
}
diff --git a/test-index-version.c b/test-index-version.c
index bfaad9e..05d4699 100644
--- a/test-index-version.c
+++ b/test-index-version.c
@@ -1,6 +1,6 @@
#include "cache.h"
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
struct cache_header hdr;
int version;
diff --git a/test-mergesort.c b/test-mergesort.c
index 3f388b4..ea3b959 100644
--- a/test-mergesort.c
+++ b/test-mergesort.c
@@ -22,7 +22,7 @@ static int compare_strings(const void *a, const void *b)
return strcmp(x->text, y->text);
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
struct line *line, *p = NULL, *lines = NULL;
struct strbuf sb = STRBUF_INIT;
diff --git a/test-parse-options.c b/test-parse-options.c
index 3c9510a..434e8b8 100644
--- a/test-parse-options.c
+++ b/test-parse-options.c
@@ -29,7 +29,7 @@ static int number_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
const char *prefix = "prefix/";
const char *usage[] = {
@@ -81,7 +81,7 @@ int main(int argc, const char **argv)
};
int i;
- argc = parse_options(argc, argv, prefix, options, usage, 0);
+ argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);
printf("boolean: %d\n", boolean);
printf("integer: %u\n", integer);
diff --git a/test-prio-queue.c b/test-prio-queue.c
new file mode 100644
index 0000000..7be72f0
--- /dev/null
+++ b/test-prio-queue.c
@@ -0,0 +1,39 @@
+#include "cache.h"
+#include "prio-queue.h"
+
+static int intcmp(const void *va, const void *vb, void *data)
+{
+ const int *a = va, *b = vb;
+ return *a - *b;
+}
+
+static void show(int *v)
+{
+ if (!v)
+ printf("NULL\n");
+ else
+ printf("%d\n", *v);
+ free(v);
+}
+
+int main(int argc, char **argv)
+{
+ struct prio_queue pq = { intcmp };
+
+ while (*++argv) {
+ if (!strcmp(*argv, "get"))
+ show(prio_queue_get(&pq));
+ else if (!strcmp(*argv, "dump")) {
+ int *v;
+ while ((v = prio_queue_get(&pq)))
+ show(v);
+ }
+ else {
+ int *v = malloc(sizeof(*v));
+ *v = atoi(*argv);
+ prio_queue_put(&pq, v);
+ }
+ }
+
+ return 0;
+}
diff --git a/test-read-cache.c b/test-read-cache.c
new file mode 100644
index 0000000..b25bcf1
--- /dev/null
+++ b/test-read-cache.c
@@ -0,0 +1,13 @@
+#include "cache.h"
+
+int main (int argc, char **argv)
+{
+ int i, cnt = 1;
+ if (argc == 2)
+ cnt = strtol(argv[1], NULL, 0);
+ for (i = 0; i < cnt; i++) {
+ read_cache();
+ discard_cache();
+ }
+ return 0;
+}
diff --git a/test-subprocess.c b/test-subprocess.c
index f2d4c0d..93525eb 100644
--- a/test-subprocess.c
+++ b/test-subprocess.c
@@ -1,7 +1,7 @@
#include "cache.h"
#include "run-command.h"
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
struct child_process cp;
int nogit = 0;
@@ -15,6 +15,6 @@ int main(int argc, const char **argv)
}
memset(&cp, 0, sizeof(cp));
cp.git_cmd = 1;
- cp.argv = argv + 1;
+ cp.argv = (const char **)argv + 1;
return run_command(&cp);
}
diff --git a/thread-utils.c b/thread-utils.c
index 7f4b76a..97396a7 100644
--- a/thread-utils.c
+++ b/thread-utils.c
@@ -24,7 +24,7 @@ int online_cpus(void)
long ncpus;
#endif
-#ifdef _WIN32
+#ifdef GIT_WINDOWS_NATIVE
SYSTEM_INFO info;
GetSystemInfo(&info);
diff --git a/transport-helper.c b/transport-helper.c
index 522d791..db9bd18 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -11,6 +11,7 @@
#include "thread-utils.h"
#include "sigchain.h"
#include "argv-array.h"
+#include "refs.h"
static int debug;
@@ -47,7 +48,7 @@ static void sendline(struct helper_data *helper, struct strbuf *buffer)
die_errno("Full write to remote helper failed");
}
-static int recvline_fh(FILE *helper, struct strbuf *buffer)
+static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name)
{
strbuf_reset(buffer);
if (debug)
@@ -65,7 +66,7 @@ static int recvline_fh(FILE *helper, struct strbuf *buffer)
static int recvline(struct helper_data *helper, struct strbuf *buffer)
{
- return recvline_fh(helper->out, buffer);
+ return recvline_fh(helper->out, buffer, helper->name);
}
static void xchgline(struct helper_data *helper, struct strbuf *buffer)
@@ -217,6 +218,8 @@ static struct child_process *get_helper(struct transport *transport)
for (i = 0; i < refspec_nr; i++)
free((char *)refspecs[i]);
free(refspecs);
+ } else if (data->import || data->bidi_import || data->export) {
+ warning("This remote helper should implement refspec capability.");
}
strbuf_release(&buf);
if (debug)
@@ -473,7 +476,7 @@ static int fetch_with_import(struct transport *transport,
* were fetching.
*
* (If no "refspec" capability was specified, for historical
- * reasons we default to *:*.)
+ * reasons we default to the equivalent of *:*.)
*
* Store the result in to_fetch[i].old_sha1. Callers such
* as "git fetch" can use the value to write feedback to the
@@ -540,7 +543,7 @@ static int process_connect_service(struct transport *transport,
goto exit;
sendline(data, &cmdbuf);
- recvline_fh(input, &cmdbuf);
+ recvline_fh(input, &cmdbuf, name);
if (!strcmp(cmdbuf.buf, "")) {
data->no_disconnect_req = 1;
if (debug)
@@ -622,7 +625,7 @@ static int fetch(struct transport *transport,
return -1;
}
-static void push_update_ref_status(struct strbuf *buf,
+static int push_update_ref_status(struct strbuf *buf,
struct ref **ref,
struct ref *remote_refs)
{
@@ -688,7 +691,7 @@ static void push_update_ref_status(struct strbuf *buf,
*ref = find_ref_by_name(remote_refs, refname);
if (!*ref) {
warning("helper reported unexpected status of %s", refname);
- return;
+ return 1;
}
if ((*ref)->status != REF_STATUS_NONE) {
@@ -697,11 +700,12 @@ static void push_update_ref_status(struct strbuf *buf,
* status reported by the remote helper if the latter is 'no match'.
*/
if (status == REF_STATUS_NONE)
- return;
+ return 1;
}
(*ref)->status = status;
(*ref)->remote_status = msg;
+ return !(status == REF_STATUS_OK);
}
static void push_update_refs_status(struct helper_data *data,
@@ -710,11 +714,24 @@ static void push_update_refs_status(struct helper_data *data,
struct strbuf buf = STRBUF_INIT;
struct ref *ref = remote_refs;
for (;;) {
+ char *private;
+
recvline(data, &buf);
if (!buf.len)
break;
- push_update_ref_status(&buf, &ref, remote_refs);
+ if (push_update_ref_status(&buf, &ref, remote_refs))
+ continue;
+
+ if (!data->refspecs)
+ continue;
+
+ /* propagate back the update to the remote namespace */
+ private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
+ if (!private)
+ continue;
+ update_ref("update by helper", private, ref->new_sha1, NULL, 0, 0);
+ free(private);
}
strbuf_release(&buf);
}
@@ -789,6 +806,14 @@ static int push_refs_with_export(struct transport *transport,
struct string_list revlist_args = STRING_LIST_INIT_NODUP;
struct strbuf buf = STRBUF_INIT;
+ if (!data->refspecs)
+ die("remote-helper doesn't support push; refspec needed");
+
+ if (flags & TRANSPORT_PUSH_DRY_RUN) {
+ if (set_helper_option(transport, "dry-run", "true") != 0)
+ die("helper %s does not support dry-run", data->name);
+ }
+
helper = get_helper(transport);
write_constant(helper->in, "export\n");
@@ -799,8 +824,9 @@ static int push_refs_with_export(struct transport *transport,
char *private;
unsigned char sha1[20];
- if (!data->refspecs)
- continue;
+ if (ref->deletion)
+ die("remote-helpers do not support ref deletion");
+
private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
if (private && !get_sha1(private, sha1)) {
strbuf_addf(&buf, "^%s", private);
@@ -809,13 +835,14 @@ static int push_refs_with_export(struct transport *transport,
}
free(private);
- if (ref->deletion) {
+ if (ref->deletion)
die("remote-helpers do not support ref deletion");
- }
- if (ref->peer_ref)
+ if (ref->peer_ref) {
+ if (strcmp(ref->peer_ref->name, ref->name))
+ die("remote-helpers do not support old:new syntax");
string_list_append(&revlist_args, ref->peer_ref->name);
-
+ }
}
if (get_exporter(transport, &exporter, &revlist_args))
diff --git a/transport.c b/transport.c
index ba5d8af..e15db98 100644
--- a/transport.c
+++ b/transport.c
@@ -534,6 +534,8 @@ static int fetch_refs_via_pack(struct transport *transport,
args.quiet = (transport->verbose < 0);
args.no_progress = !transport->progress;
args.depth = data->options.depth;
+ args.check_self_contained_and_connected =
+ data->options.check_self_contained_and_connected;
if (!data->got_remote_heads) {
connect_setup(transport, 0, 0);
@@ -551,6 +553,8 @@ static int fetch_refs_via_pack(struct transport *transport,
refs = NULL;
data->conn = NULL;
data->got_remote_heads = 0;
+ data->options.self_contained_and_connected =
+ args.self_contained_and_connected;
free_refs(refs_tmp);
@@ -1228,7 +1232,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
* then local and remote refs are likely to still be equal.
* Just feed them all to the fetch method in that case.
* This condition shouldn't be met in a non-deepening fetch
- * (see builtin-fetch.c:quickfetch()).
+ * (see builtin/fetch.c:quickfetch()).
*/
heads = xmalloc(nr_refs * sizeof(*heads));
for (rm = refs; rm; rm = rm->next)
diff --git a/transport.h b/transport.h
index fcb1d25..ea70ea7 100644
--- a/transport.h
+++ b/transport.h
@@ -8,6 +8,8 @@ struct git_transport_options {
unsigned thin : 1;
unsigned keep : 1;
unsigned followtags : 1;
+ unsigned check_self_contained_and_connected : 1;
+ unsigned self_contained_and_connected : 1;
int depth;
const char *uploadpack;
const char *receivepack;
@@ -168,7 +170,7 @@ int transport_connect(struct transport *transport, const char *name,
int transport_helper_init(struct transport *transport, const char *name);
int bidirectional_transfer_loop(int input, int output);
-/* common methods used by transport.c and builtin-send-pack.c */
+/* common methods used by transport.c and builtin/send-pack.c */
void transport_verify_remote_names(int nr_heads, const char **heads);
void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose);
diff --git a/tree-walk.h b/tree-walk.h
index 2bf0db9..ae04b64 100644
--- a/tree-walk.h
+++ b/tree-walk.h
@@ -46,7 +46,7 @@ struct traverse_info {
int pathlen;
struct pathspec *pathspec;
- unsigned long conflicts;
+ unsigned long df_conflicts;
traverse_callback_t fn;
void *data;
int show_all_errors;
diff --git a/unpack-trees.c b/unpack-trees.c
index ede4299..b27f2a6 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -116,14 +116,20 @@ static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *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)
+static struct cache_entry *dup_entry(const struct cache_entry *ce)
{
unsigned int size = ce_size(ce);
struct cache_entry *new = xmalloc(size);
memcpy(new, ce, size);
- do_add_entry(o, new, set, clear);
+ return new;
+}
+
+static void add_entry(struct unpack_trees_options *o,
+ const struct cache_entry *ce,
+ unsigned int set, unsigned int clear)
+{
+ do_add_entry(o, dup_entry(ce), set, clear);
}
/*
@@ -235,8 +241,11 @@ static int check_updates(struct unpack_trees_options *o)
return errs != 0;
}
-static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
-static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o);
+static int verify_uptodate_sparse(const struct cache_entry *ce,
+ struct unpack_trees_options *o);
+static int verify_absent_sparse(const struct cache_entry *ce,
+ enum unpack_trees_error_types,
+ struct unpack_trees_options *o);
static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
{
@@ -291,7 +300,8 @@ static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_opt
return 0;
}
-static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o)
+static inline int call_unpack_fn(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
int ret = o->fn(src, o);
if (ret > 0)
@@ -320,7 +330,7 @@ static void mark_all_ce_unused(struct index_state *index)
index->cache[i]->ce_flags &= ~(CE_UNPACKED | CE_ADDED | CE_NEW_SKIP_WORKTREE);
}
-static int locate_in_src_index(struct cache_entry *ce,
+static int locate_in_src_index(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
struct index_state *index = o->src_index;
@@ -388,7 +398,7 @@ static void add_same_unmerged(struct cache_entry *ce,
static int unpack_index_entry(struct cache_entry *ce,
struct unpack_trees_options *o)
{
- struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
+ const struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
int ret;
src[0] = ce;
@@ -454,7 +464,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
newinfo.pathspec = info->pathspec;
newinfo.name = *p;
newinfo.pathlen += tree_entry_len(p) + 1;
- newinfo.conflicts |= df_conflicts;
+ newinfo.df_conflicts |= df_conflicts;
for (i = 0; i < n; i++, dirmask >>= 1) {
const unsigned char *sha1 = NULL;
@@ -555,17 +565,12 @@ static int unpack_nondirectories(int n, unsigned long mask,
{
int i;
struct unpack_trees_options *o = info->data;
- unsigned long conflicts;
+ unsigned long conflicts = info->df_conflicts | dirmask;
/* Do we have *only* directories? Nothing to do */
if (mask == dirmask && !src[0])
return 0;
- conflicts = info->conflicts;
- if (o->merge)
- conflicts >>= 1;
- conflicts |= dirmask;
-
/*
* Ok, we've filled in up to any potential index entry in src[0],
* now do the rest.
@@ -590,8 +595,16 @@ static int unpack_nondirectories(int n, unsigned long mask,
src[i + o->merge] = create_ce_entry(info, names + i, stage);
}
- if (o->merge)
- return call_unpack_fn(src, o);
+ if (o->merge) {
+ int rc = call_unpack_fn((const struct cache_entry * const *)src,
+ o);
+ for (i = 0; i < n; i++) {
+ struct cache_entry *ce = src[i + o->merge];
+ if (ce != o->df_conflict_entry)
+ free(ce);
+ }
+ return rc;
+ }
for (i = 0; i < n; i++)
if (src[i] && src[i] != o->df_conflict_entry)
@@ -789,13 +802,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
/* Now handle any directories.. */
if (dirmask) {
- unsigned long conflicts = mask & ~dirmask;
- if (o->merge) {
- conflicts <<= 1;
- if (src[0])
- conflicts |= 1;
- }
-
/* special case: "diff-index --cached" looking at a tree */
if (o->diff_index_cached &&
n == 1 && dirmask == 1 && S_ISDIR(names->mode)) {
@@ -814,7 +820,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
}
}
- if (traverse_trees_recursive(n, dirmask, conflicts,
+ if (traverse_trees_recursive(n, dirmask, mask & ~dirmask,
names, info) < 0)
return -1;
return mask;
@@ -995,7 +1001,9 @@ static void mark_new_skip_worktree(struct exclude_list *el,
select_flag, skip_wt_flag, el);
}
-static int verify_absent(struct cache_entry *, enum unpack_trees_error_types, struct unpack_trees_options *);
+static int verify_absent(const struct cache_entry *,
+ enum unpack_trees_error_types,
+ struct unpack_trees_options *);
/*
* N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the
* resulting index, -2 on failure to reflect the changes to the work tree.
@@ -1165,12 +1173,13 @@ return_failed:
/* Here come the merge functions */
-static int reject_merge(struct cache_entry *ce, struct unpack_trees_options *o)
+static int reject_merge(const struct cache_entry *ce,
+ struct unpack_trees_options *o)
{
return add_rejected_path(o, ERROR_WOULD_OVERWRITE, ce->name);
}
-static int same(struct cache_entry *a, struct cache_entry *b)
+static int same(const struct cache_entry *a, const struct cache_entry *b)
{
if (!!a != !!b)
return 0;
@@ -1187,9 +1196,9 @@ static int same(struct cache_entry *a, struct cache_entry *b)
* When a CE gets turned into an unmerged entry, we
* want it to be up-to-date
*/
-static int verify_uptodate_1(struct cache_entry *ce,
- struct unpack_trees_options *o,
- enum unpack_trees_error_types error_type)
+static int verify_uptodate_1(const struct cache_entry *ce,
+ struct unpack_trees_options *o,
+ enum unpack_trees_error_types error_type)
{
struct stat st;
@@ -1228,7 +1237,7 @@ static int verify_uptodate_1(struct cache_entry *ce,
add_rejected_path(o, error_type, ce->name);
}
-static int verify_uptodate(struct cache_entry *ce,
+static int verify_uptodate(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
@@ -1236,13 +1245,14 @@ static int verify_uptodate(struct cache_entry *ce,
return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
}
-static int verify_uptodate_sparse(struct cache_entry *ce,
+static int verify_uptodate_sparse(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
}
-static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
+static void invalidate_ce_path(const struct cache_entry *ce,
+ struct unpack_trees_options *o)
{
if (ce)
cache_tree_invalidate_path(o->src_index->cache_tree, ce->name);
@@ -1255,16 +1265,16 @@ static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_optio
* Currently, git does not checkout subprojects during a superproject
* checkout, so it is not going to overwrite anything.
*/
-static int verify_clean_submodule(struct cache_entry *ce,
- enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+static int verify_clean_submodule(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
return 0;
}
-static int verify_clean_subdirectory(struct cache_entry *ce,
- enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+static int verify_clean_subdirectory(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
/*
* we are about to extract "ce->name"; we would not want to lose
@@ -1350,7 +1360,7 @@ static int icase_exists(struct unpack_trees_options *o, const char *name, int le
}
static int check_ok_to_remove(const char *name, int len, int dtype,
- struct cache_entry *ce, struct stat *st,
+ const struct cache_entry *ce, struct stat *st,
enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
@@ -1405,9 +1415,9 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
* We do not want to remove or overwrite a working tree file that
* is not tracked, unless it is ignored.
*/
-static int verify_absent_1(struct cache_entry *ce,
- enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+static int verify_absent_1(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
int len;
struct stat st;
@@ -1440,7 +1450,7 @@ static int verify_absent_1(struct cache_entry *ce,
}
}
-static int verify_absent(struct cache_entry *ce,
+static int verify_absent(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
@@ -1449,9 +1459,9 @@ static int verify_absent(struct cache_entry *ce,
return verify_absent_1(ce, error_type, o);
}
-static int verify_absent_sparse(struct cache_entry *ce,
- enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+static int verify_absent_sparse(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
enum unpack_trees_error_types orphaned_error = error_type;
if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
@@ -1460,10 +1470,12 @@ static int verify_absent_sparse(struct cache_entry *ce,
return verify_absent_1(ce, orphaned_error, o);
}
-static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
- struct unpack_trees_options *o)
+static int merged_entry(const struct cache_entry *ce,
+ const struct cache_entry *old,
+ struct unpack_trees_options *o)
{
int update = CE_UPDATE;
+ struct cache_entry *merge = dup_entry(ce);
if (!old) {
/*
@@ -1481,8 +1493,11 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
update |= CE_ADDED;
merge->ce_flags |= CE_NEW_SKIP_WORKTREE;
- if (verify_absent(merge, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+ if (verify_absent(merge,
+ ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+ free(merge);
return -1;
+ }
invalidate_ce_path(merge, o);
} else if (!(old->ce_flags & CE_CONFLICTED)) {
/*
@@ -1496,8 +1511,10 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
copy_cache_entry(merge, old);
update = 0;
} else {
- if (verify_uptodate(old, o))
+ if (verify_uptodate(old, o)) {
+ free(merge);
return -1;
+ }
/* Migrate old flags over */
update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
invalidate_ce_path(old, o);
@@ -1510,12 +1527,13 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
invalidate_ce_path(old, o);
}
- add_entry(o, merge, update, CE_STAGEMASK);
+ do_add_entry(o, merge, update, CE_STAGEMASK);
return 1;
}
-static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
- struct unpack_trees_options *o)
+static int deleted_entry(const struct cache_entry *ce,
+ const struct cache_entry *old,
+ struct unpack_trees_options *o)
{
/* Did it exist in the index? */
if (!old) {
@@ -1530,7 +1548,8 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
return 1;
}
-static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o)
+static int keep_entry(const struct cache_entry *ce,
+ struct unpack_trees_options *o)
{
add_entry(o, ce, 0, 0);
return 1;
@@ -1552,11 +1571,12 @@ static void show_stage_entry(FILE *o,
}
#endif
-int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+int threeway_merge(const struct cache_entry * const *stages,
+ struct unpack_trees_options *o)
{
- struct cache_entry *index;
- struct cache_entry *head;
- struct cache_entry *remote = stages[o->head_idx + 1];
+ const struct cache_entry *index;
+ const struct cache_entry *head;
+ const struct cache_entry *remote = stages[o->head_idx + 1];
int count;
int head_match = 0;
int remote_match = 0;
@@ -1641,7 +1661,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
if (o->aggressive) {
int head_deleted = !head;
int remote_deleted = !remote;
- struct cache_entry *ce = NULL;
+ const struct cache_entry *ce = NULL;
if (index)
ce = index;
@@ -1724,11 +1744,12 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
* "carry forward" rule, please see <Documentation/git-read-tree.txt>.
*
*/
-int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
+int twoway_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
- struct cache_entry *current = src[0];
- struct cache_entry *oldtree = src[1];
- struct cache_entry *newtree = src[2];
+ const struct cache_entry *current = src[0];
+ const struct cache_entry *oldtree = src[1];
+ const struct cache_entry *newtree = src[2];
if (o->merge_size != 2)
return error("Cannot do a twoway merge of %d trees",
@@ -1790,11 +1811,11 @@ int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
* Keep the index entries at stage0, collapse stage1 but make sure
* stage0 does not have anything there.
*/
-int bind_merge(struct cache_entry **src,
- struct unpack_trees_options *o)
+int bind_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
- struct cache_entry *old = src[0];
- struct cache_entry *a = src[1];
+ const struct cache_entry *old = src[0];
+ const struct cache_entry *a = src[1];
if (o->merge_size != 1)
return error("Cannot do a bind merge of %d trees",
@@ -1814,10 +1835,11 @@ int bind_merge(struct cache_entry **src,
* The rule is:
* - take the stat information from stage0, take the data from stage1
*/
-int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
+int oneway_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
- struct cache_entry *old = src[0];
- struct cache_entry *a = src[1];
+ const struct cache_entry *old = src[0];
+ const struct cache_entry *a = src[1];
if (o->merge_size != 1)
return error("Cannot do a oneway merge of %d trees",
diff --git a/unpack-trees.h b/unpack-trees.h
index 5e432f5..36a73a6 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -8,7 +8,7 @@
struct unpack_trees_options;
struct exclude_list;
-typedef int (*merge_fn_t)(struct cache_entry **src,
+typedef int (*merge_fn_t)(const struct cache_entry * const *src,
struct unpack_trees_options *options);
enum unpack_trees_error_types {
@@ -77,9 +77,13 @@ struct unpack_trees_options {
extern int unpack_trees(unsigned n, struct tree_desc *t,
struct unpack_trees_options *options);
-int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o);
-int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o);
-int bind_merge(struct cache_entry **src, struct unpack_trees_options *o);
-int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o);
+int threeway_merge(const struct cache_entry * const *stages,
+ struct unpack_trees_options *o);
+int twoway_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o);
+int bind_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o);
+int oneway_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o);
#endif
diff --git a/upload-pack.c b/upload-pack.c
index bfa6279..127e59a 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -592,7 +592,7 @@ static void receive_needs(void)
die("invalid shallow line: %s", line);
object = parse_object(sha1);
if (!object)
- die("did not find object for %s", line);
+ continue;
if (object->type != OBJ_COMMIT)
die("invalid shallow object %s", sha1_to_hex(sha1));
if (!(object->flags & CLIENT_SHALLOW)) {
diff --git a/wildmatch.c b/wildmatch.c
index 7192bdc..f91ba99 100644
--- a/wildmatch.c
+++ b/wildmatch.c
@@ -196,6 +196,11 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags)
}
if (t_ch <= p_ch && t_ch >= prev_ch)
matched = 1;
+ else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) {
+ uchar t_ch_upper = toupper(t_ch);
+ if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch)
+ matched = 1;
+ }
p_ch = 0; /* This makes "prev_ch" get set to 0. */
} else if (p_ch == '[' && p[1] == ':') {
const uchar *s;
@@ -245,6 +250,8 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags)
} else if (CC_EQ(s,i, "upper")) {
if (ISUPPER(t_ch))
matched = 1;
+ else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch))
+ matched = 1;
} else if (CC_EQ(s,i, "xdigit")) {
if (ISXDIGIT(t_ch))
matched = 1;
diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh
index 53a8dd0..701d233 100644
--- a/wrap-for-bin.sh
+++ b/wrap-for-bin.sh
@@ -14,7 +14,7 @@ else
GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt'
export GIT_TEMPLATE_DIR
fi
-GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
+GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'"${GITPERLLIB:+:$GITPERLLIB}"
GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale'
PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR
diff --git a/wrapper.c b/wrapper.c
index bac59d2..6a015de 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -322,7 +322,7 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
template[5] = letters[v % num_letters]; v /= num_letters;
fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
- if (fd > 0)
+ if (fd >= 0)
return fd;
/*
* Fatal error (EPERM, ENOSPC etc).
@@ -408,18 +408,24 @@ void warn_on_inaccessible(const char *path)
warning(_("unable to access '%s': %s"), path, strerror(errno));
}
-int access_or_warn(const char *path, int mode)
+static int access_error_is_ok(int err, unsigned flag)
+{
+ return err == ENOENT || err == ENOTDIR ||
+ ((flag & ACCESS_EACCES_OK) && err == EACCES);
+}
+
+int access_or_warn(const char *path, int mode, unsigned flag)
{
int ret = access(path, mode);
- if (ret && errno != ENOENT && errno != ENOTDIR)
+ if (ret && !access_error_is_ok(errno, flag))
warn_on_inaccessible(path);
return ret;
}
-int access_or_die(const char *path, int mode)
+int access_or_die(const char *path, int mode, unsigned flag)
{
int ret = access(path, mode);
- if (ret && errno != ENOENT && errno != ENOTDIR)
+ if (ret && !access_error_is_ok(errno, flag))
die_errno(_("unable to access '%s'"), path);
return ret;
}
diff --git a/wt-status.c b/wt-status.c
index bf84a86..9c4d086 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -127,6 +127,7 @@ void wt_status_prepare(struct wt_status *s)
s->change.strdup_strings = 1;
s->untracked.strdup_strings = 1;
s->ignored.strdup_strings = 1;
+ s->show_branch = -1; /* unspecified */
}
static void wt_status_print_unmerged_header(struct wt_status *s)
@@ -826,7 +827,7 @@ static void show_am_in_progress(struct wt_status *s,
if (advice_status_hints) {
if (!state->am_empty_patch)
status_printf_ln(s, color,
- _(" (fix conflicts and then run \"git am --resolved\")"));
+ _(" (fix conflicts and then run \"git am --continue\")"));
status_printf_ln(s, color,
_(" (use \"git am --skip\" to skip this patch)"));
status_printf_ln(s, color,
@@ -955,10 +956,12 @@ static void show_cherry_pick_in_progress(struct wt_status *s,
if (advice_status_hints) {
if (has_unmerged(s))
status_printf_ln(s, color,
- _(" (fix conflicts and run \"git commit\")"));
+ _(" (fix conflicts and run \"git cherry-pick --continue\")"));
else
status_printf_ln(s, color,
- _(" (all conflicts fixed: run \"git commit\")"));
+ _(" (all conflicts fixed: run \"git cherry-pick --continue\")"));
+ status_printf_ln(s, color,
+ _(" (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
}
wt_status_print_trailer(s);
}
@@ -1035,7 +1038,6 @@ got_nothing:
}
struct grab_1st_switch_cbdata {
- int found;
struct strbuf buf;
unsigned char nsha1[20];
};
@@ -1059,7 +1061,6 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
for (end = target; *end && *end != '\n'; end++)
;
strbuf_add(&cb->buf, target, end - target);
- cb->found = 1;
return 1;
}
@@ -1176,7 +1177,10 @@ void wt_status_print(struct wt_status *s)
branch_name += 11;
else if (!strcmp(branch_name, "HEAD")) {
branch_status_color = color(WT_STATUS_NOBRANCH, s);
- if (state.detached_from) {
+ if (state.rebase_in_progress || state.rebase_interactive_in_progress) {
+ on_what = _("rebase in progress; onto ");
+ branch_name = state.onto;
+ } else if (state.detached_from) {
unsigned char sha1[20];
branch_name = state.detached_from;
if (!get_sha1("HEAD", sha1) &&
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index 219a3bb..c033991 100644
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
@@ -39,6 +39,8 @@ extern "C" {
#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
+#define XDF_IGNORE_BLANK_LINES (1 << 7)
+
#define XDL_EMIT_FUNCNAMES (1 << 0)
#define XDL_EMIT_COMMON (1 << 1)
#define XDL_EMIT_FUNCCONTEXT (1 << 2)
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index b2eb6db..2358a2d 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -394,6 +394,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
xch->i2 = i2;
xch->chg1 = chg1;
xch->chg2 = chg2;
+ xch->ignore = 0;
return xch;
}
@@ -544,7 +545,9 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdchange_t *xch, *xche;
for (xch = xscr; xch; xch = xche->next) {
- xche = xdl_get_hunk(xch, xecfg);
+ xche = xdl_get_hunk(&xch, xecfg);
+ if (!xch)
+ break;
if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1,
xch->i2, xche->i2 + xche->chg2 - xch->i2,
ecb->priv) < 0)
@@ -553,6 +556,27 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
return 0;
}
+static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags)
+{
+ xdchange_t *xch;
+
+ for (xch = xscr; xch; xch = xch->next) {
+ int ignore = 1;
+ xrecord_t **rec;
+ long i;
+
+ rec = &xe->xdf1.recs[xch->i1];
+ for (i = 0; i < xch->chg1 && ignore; i++)
+ ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+
+ rec = &xe->xdf2.recs[xch->i2];
+ for (i = 0; i < xch->chg2 && ignore; i++)
+ ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+
+ xch->ignore = ignore;
+ }
+}
+
int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
xdchange_t *xscr;
@@ -571,6 +595,9 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
return -1;
}
if (xscr) {
+ if (xpp->flags & XDF_IGNORE_BLANK_LINES)
+ xdl_mark_ignorable(xscr, &xe, xpp->flags);
+
if (ef(&xe, xscr, ecb, xecfg) < 0) {
xdl_free_script(xscr);
diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h
index 7a92ea9..8b81206 100644
--- a/xdiff/xdiffi.h
+++ b/xdiff/xdiffi.h
@@ -41,6 +41,7 @@ typedef struct s_xdchange {
struct s_xdchange *next;
long i1, i2;
long chg1, chg2;
+ int ignore;
} xdchange_t;
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index d11dbf9..4d86458 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -56,16 +56,51 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *
/*
* Starting at the passed change atom, find the latest change atom to be included
* inside the differential hunk according to the specified configuration.
+ * Also advance xscr if the first changes must be discarded.
*/
-xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
- xdchange_t *xch, *xchp;
+xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
+{
+ xdchange_t *xch, *xchp, *lxch;
long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
+ long max_ignorable = xecfg->ctxlen;
+ unsigned long ignored = 0; /* number of ignored blank lines */
+
+ /* remove ignorable changes that are too far before other changes */
+ for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {
+ xch = xchp->next;
+
+ if (xch == NULL ||
+ xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable)
+ *xscr = xch;
+ }
+
+ if (*xscr == NULL)
+ return NULL;
+
+ lxch = *xscr;
- for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next)
- if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common)
+ for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) {
+ long distance = xch->i1 - (xchp->i1 + xchp->chg1);
+ if (distance > max_common)
break;
- return xchp;
+ if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) {
+ lxch = xch;
+ ignored = 0;
+ } else if (distance < max_ignorable && xch->ignore) {
+ ignored += xch->chg2;
+ } else if (lxch != xchp &&
+ xch->i1 + ignored - (lxch->i1 + lxch->chg1) > max_common) {
+ break;
+ } else if (!xch->ignore) {
+ lxch = xch;
+ ignored = 0;
+ } else {
+ ignored += xch->chg2;
+ }
+ }
+
+ return lxch;
}
@@ -139,7 +174,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
return xdl_emit_common(xe, xscr, ecb, xecfg);
for (xch = xscr; xch; xch = xche->next) {
- xche = xdl_get_hunk(xch, xecfg);
+ xche = xdl_get_hunk(&xch, xecfg);
+ if (!xch)
+ break;
s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
diff --git a/xdiff/xemit.h b/xdiff/xemit.h
index c2e2e83..d297107 100644
--- a/xdiff/xemit.h
+++ b/xdiff/xemit.h
@@ -27,7 +27,7 @@
typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg);
-xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg);
+xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg);
int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg);
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 9504eae..62cb23d 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -143,6 +143,19 @@ long xdl_guess_lines(mmfile_t *mf, long sample) {
return nl + 1;
}
+int xdl_blankline(const char *line, long size, long flags)
+{
+ long i;
+
+ if (!(flags & XDF_WHITESPACE_FLAGS))
+ return (size <= 1);
+
+ for (i = 0; i < size && XDL_ISSPACE(line[i]); i++)
+ ;
+
+ return (i == size);
+}
+
int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
{
int i1, i2;
diff --git a/xdiff/xutils.h b/xdiff/xutils.h
index ad1428e..4646ce5 100644
--- a/xdiff/xutils.h
+++ b/xdiff/xutils.h
@@ -32,6 +32,7 @@ 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);
long xdl_guess_lines(mmfile_t *mf, long sample);
+int xdl_blankline(const char *line, long size, long flags);
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);